pax_global_header00006660000000000000000000000064144537275370014533gustar00rootroot0000000000000052 comment=3b669c2b02e71e07a1cad6a4558f575608974a4e openmw-openmw-0.48.0/000077500000000000000000000000001445372753700144345ustar00rootroot00000000000000openmw-openmw-0.48.0/.clang-tidy000066400000000000000000000001721445372753700164700ustar00rootroot00000000000000--- Checks: "-*, boost-*, portability-*, " WarningsAsErrors: '' HeaderFilterRegex: '' FormatStyle: none openmw-openmw-0.48.0/.editorconfig000066400000000000000000000003531445372753700171120ustar00rootroot00000000000000root = true [*.cpp] indent_style = space indent_size = 4 insert_final_newline = true [*.hpp] indent_style = space indent_size = 4 insert_final_newline = true [*.glsl] indent_style = space indent_size = 4 insert_final_newline = falseopenmw-openmw-0.48.0/.github/000077500000000000000000000000001445372753700157745ustar00rootroot00000000000000openmw-openmw-0.48.0/.github/workflows/000077500000000000000000000000001445372753700200315ustar00rootroot00000000000000openmw-openmw-0.48.0/.github/workflows/cmake.yml000066400000000000000000000044061445372753700216400ustar00rootroot00000000000000name: CMake on: pull_request: branches: [ master ] env: BUILD_TYPE: RelWithDebInfo jobs: Ubuntu: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Add OpenMW PPA Dependancies run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update - name: Install Building Dependancies run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - name: Prime ccache uses: hendrikmuhs/ccache-action@v1 with: key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} max-size: 1000M - name: Install gtest run: | export CONFIGURATION="Release" export GOOGLETEST_DIR="." export GENERATOR="Unix Makefiles" export CC="gcc" export CXX="g++" sudo -E CI/build_googletest.sh - name: Configure run: cmake -S . -B . -DGTEST_ROOT="$(pwd)/googletest/build" -DGMOCK_ROOT="$(pwd)/googletest/build" -DBUILD_UNITTESTS=ON -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - name: Test run: ./openmw_test_suite # - name: Install # shell: bash # run: cmake --install . # - name: Create Artifact # shell: bash # working-directory: install # run: | # ls -laR # 7z a ../build_artifact.7z . # - name: Upload Artifact # uses: actions/upload-artifact@v1 # with: # path: ./build_artifact.7z # name: build_artifact.7z MacOS: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Install Building Dependancies run: CI/before_install.osx.sh - name: Prime ccache uses: hendrikmuhs/ccache-action@v1 with: key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} max-size: 1000M - name: Configure run: | rm -fr build # remove the build directory CI/before_script.osx.sh - name: Build run: | cd build make -j $(sysctl -n hw.logicalcpu) package openmw-openmw-0.48.0/.gitignore000066400000000000000000000024751445372753700164340ustar00rootroot00000000000000## make CMakeFiles */CMakeFiles CMakeCache.txt cmake_install.cmake Makefile makefile build*/ prebuilt ##windows build process /deps /MSVC* ## doxygen Doxygen ## ides/editors *~ *.kdev4 *.swp *.swo *.kate-swp .cproject .project .settings .directory .idea cmake-build-* files/windows/*.aps ## qt-creator CMakeLists.txt.user* .vs .vscode ## resources resources /*.cfg /*.desktop /*.install ## binaries /esmtool /openmw /opencs /niftest /bsatool /openmw-cs /openmw-essimporter /openmw-iniimporter /openmw-launcher /openmw-wizard ## generated objects apps/openmw/config.hpp apps/launcher/ui_contentselector.h apps/launcher/ui_settingspage.h apps/opencs/ui_contentselector.h apps/opencs/ui_filedialog.h apps/wizard/qrc_wizard.cxx apps/wizard/ui_componentselectionpage.h apps/wizard/ui_conclusionpage.h apps/wizard/ui_existinginstallationpage.h apps/wizard/ui_importpage.h apps/wizard/ui_installationpage.h apps/wizard/ui_installationtargetpage.h apps/wizard/ui_intropage.h apps/wizard/ui_languageselectionpage.h apps/wizard/ui_methodselectionpage.h components/ui_contentselector.h docs/mainpage.hpp docs/Doxyfile docs/DoxyfilePages docs/source/reference/lua-scripting/generated_html moc_*.cxx *.cxx_parameters *qrc_launcher.cxx *qrc_resources.cxx *__* *ui_datafilespage.h *ui_graphicspage.h *ui_mainwindow.h *ui_playpage.h *.[ao] *.so venv/ openmw-openmw-0.48.0/.gitlab-ci.yml000066400000000000000000000475131445372753700171020ustar00rootroot00000000000000default: interruptible: true # Note: We set `needs` on each job to control the job DAG. # See https://docs.gitlab.com/ee/ci/yaml/#needs stages: - build - test # https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/ variables: FF_USE_NEW_SHELL_ESCAPE: "true" FF_USE_FASTZIP: "true" # These can be specified per job or per pipeline ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" .Ubuntu_Image: tags: - docker - linux image: ubuntu:22.04 rules: - if: $CI_PIPELINE_SOURCE == "push" .Ubuntu: extends: .Ubuntu_Image cache: paths: - apt-cache/ - ccache/ stage: build variables: CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold script: - df -h - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - cd build - cmake --build . -- -j $(nproc) - df -h - du -sh . - find . | grep '\.o$' | xargs rm -f - df -h - du -sh . - cmake --install . - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite --gtest_output="xml:tests.xml"; fi - if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi - ccache -s - 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 - ls | grep -v -e '^extern$' -e '^install$' -e '^tests.xml$' | xargs -I '{}' rm -rf './{}' - cd .. - df -h - du -sh build/ - du -sh build/install/ - du -sh apt-cache/ - du -sh ccache/ artifacts: paths: - build/install/ Coverity: tags: - docker - linux image: ubuntu:20.04 stage: build rules: - if: $CI_PIPELINE_SOURCE == "schedule" cache: key: Coverity.ubuntu_20.04.v1 paths: - apt-cache/ - ccache/ variables: CCACHE_SIZE: 2G CC: clang CXX: clang++ CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 before_script: - CI/install_debian_deps.sh clang_ubuntu_20_04 openmw-deps openmw-deps-dynamic - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: - export CCACHE_BASEDIR="$(pwd)" - export CCACHE_DIR="$(pwd)/ccache" - mkdir -pv "${CCACHE_DIR}" - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - 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 after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" artifacts: paths: - /builds/OpenMW/openmw/cov-int/build-log.txt Ubuntu_GCC: extends: .Ubuntu cache: key: Ubuntu_GCC.ubuntu_22.04.v1 before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc CXX: g++ CCACHE_SIZE: 4G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h Ubuntu_GCC_Debug: extends: .Ubuntu cache: key: Ubuntu_GCC_Debug.ubuntu_22.04.v1 before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc CXX: g++ CCACHE_SIZE: 4G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h Ubuntu_GCC_tests: extends: Ubuntu_GCC cache: key: Ubuntu_GCC_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml .Ubuntu_GCC_tests_Debug: extends: Ubuntu_GCC cache: key: Ubuntu_GCC_tests_Debug.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_GCC_tests_asan: extends: Ubuntu_GCC cache: key: Ubuntu_GCC_asan.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_GCC_tests_ubsan: extends: Ubuntu_GCC cache: key: Ubuntu_GCC_ubsan.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O0 -fsanitize=undefined UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml .Ubuntu_GCC_tests_tsan: extends: Ubuntu_GCC cache: key: Ubuntu_GCC_tsan.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread -fuse-ld=mold TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_GCC_tests_coverage: extends: .Ubuntu_GCC_tests_Debug cache: key: Ubuntu_GCC_tests_coverage.ubuntu_22.04.v1 variables: BUILD_WITH_CODE_COVERAGE: 1 before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage coverage: /^\s*lines:\s*\d+.\d+\%/ artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: coverage_report: coverage_format: cobertura path: coverage.xml junit: build/tests.xml .Ubuntu_Static_Deps: extends: Ubuntu_Clang rules: - if: $CI_PIPELINE_SOURCE == "push" changes: - "**/CMakeLists.txt" - "cmake/**/*" - "CI/**/*" - ".gitlab-ci.yml" cache: key: Ubuntu_Static_Deps.ubuntu_22.04.v1 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - CI/install_debian_deps.sh clang openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 CC: clang CXX: clang++ CXXFLAGS: -O0 timeout: 3h .Ubuntu_Static_Deps_tests: extends: .Ubuntu_Static_Deps cache: key: Ubuntu_Static_Deps_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CC: clang CXX: clang++ CXXFLAGS: -O0 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_Clang: extends: .Ubuntu before_script: - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: key: Ubuntu_Clang.ubuntu_22.04.v2 variables: CC: clang CXX: clang++ CCACHE_SIZE: 2G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 3h Ubuntu_Clang_Tidy: extends: 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 variables: CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 CI_CLANG_TIDY: 1 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 3h .Ubuntu_Clang_tests: extends: Ubuntu_Clang cache: key: Ubuntu_Clang_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_Clang_tests_Debug: extends: Ubuntu_Clang cache: key: Ubuntu_Clang_tests_Debug.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug artifacts: paths: [] name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: junit: build/tests.xml Ubuntu_Clang_integration_tests: extends: .Ubuntu_Image stage: test needs: - Ubuntu_Clang variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" cache: key: Ubuntu_Clang_integration_tests.ubuntu_22.04.v1 paths: - .cache/pip - apt-cache/ before_script: - CI/install_debian_deps.sh openmw-integration-tests - pip3 install --user numpy matplotlib termtables click script: - CI/run_integration_tests.sh .MacOS: image: macos-11-xcode-12 tags: - shared-macos-amd64 stage: build only: variables: - $CI_PROJECT_ID == "7107382" cache: paths: - ccache/ script: - rm -fr build # remove the build directory - CI/before_install.osx.sh - export CCACHE_BASEDIR="$(pwd)" - export CCACHE_DIR="$(pwd)/ccache" - mkdir -pv "${CCACHE_DIR}" - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}_${CI_JOB_ID}.dmg"; done - ccache -s artifacts: paths: - build/OpenMW-*.dmg - "build/**/*.log" macOS12_Xcode13: extends: .MacOS image: macos-12-xcode-13 cache: key: macOS12_Xcode13.v1 variables: CCACHE_SIZE: 3G .Windows_Ninja_Base: tags: - windows rules: - if: $CI_PIPELINE_SOURCE == "push" before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - choco install ccache -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install vswhere -y - choco install ninja -y - choco install python -y - refreshenv - | function Make-SafeFileName { param( [Parameter(Mandatory=$true)] [String] $FileName ) [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { $FileName = $FileName.Replace($_, '_') } return $FileName } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: key: ninja-v0.48.5 paths: - ccache - deps - MSVC2019_64_Ninja/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - MSVC2019_64_Ninja/*.log - MSVC2019_64_Ninja/*/*.log - MSVC2019_64_Ninja/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h .Windows_Ninja_Release: extends: - .Windows_Ninja_Base variables: config: "Release" .Windows_Ninja_Release_MultiView: extends: - .Windows_Ninja_Base variables: multiview: "-M" config: "Release" .Windows_Ninja_Debug: extends: - .Windows_Ninja_Base variables: config: "Debug" .Windows_Ninja_RelWithDebInfo: extends: - .Windows_Ninja_Base variables: config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" .Windows_MSBuild_Base: tags: - windows rules: - if: $CI_PIPELINE_SOURCE == "push" before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - choco install ccache -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install vswhere -y - choco install python -y - refreshenv - | function Make-SafeFileName { param( [Parameter(Mandatory=$true)] [String] $FileName ) [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { $FileName = $FileName.Replace($_, '_') } return $FileName } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - cd MSVC2019_64 - cmake --build . --config $config - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: key: msbuild-v0.48.5 paths: - ccache - deps - MSVC2019_64/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - MSVC2019_64/*.log - MSVC2019_64/*/*.log - MSVC2019_64/*/*/*.log - MSVC2019_64/*/*/*/*.log - MSVC2019_64/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h .Windows_MSBuild_Release: extends: - .Windows_MSBuild_Base variables: config: "Release" # temporarily disabled while this isn't the thing we link on the downloads page # rules: # # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it # - if: $CI_PIPELINE_SOURCE == "push" # - if: $CI_PIPELINE_SOURCE == "schedule" .Windows_MSBuild_Debug: extends: - .Windows_MSBuild_Base variables: config: "Debug" Windows_MSBuild_RelWithDebInfo: extends: - .Windows_MSBuild_Base variables: config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" # temporarily enabled while we're linking these on the downloads page rules: # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it - if: $CI_PIPELINE_SOURCE == "push" - if: $CI_PIPELINE_SOURCE == "schedule" Ubuntu_AndroidNDK_arm64-v8a: tags: - linux image: psi29a/android-ndk:focal-ndk22 rules: - if: $CI_PIPELINE_SOURCE == "push" variables: CCACHE_SIZE: 3G cache: key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v2 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - CI/install_debian_deps.sh android stage: build script: - df -h - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - ccache -z -M "${CCACHE_SIZE}" - CI/before_install.android.sh - CI/before_script.android.sh - cd build - cmake --build . -- -j $(nproc) # - cmake --install . # no one uses builds anyway, disable until 'no space left' is resolved - ccache -s - df -h - ls | grep -v -e '^extern$' -e '^install$' | xargs -I '{}' rm -rf './{}' - cd .. - df -h - du -sh build/ # - du -sh build/install/ # no install dir because it's commented out above - du -sh apt-cache/ - du -sh ccache/ - du -sh build/extern/fetched/ artifacts: paths: - build/install/ # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 1h30m .FindMissingMergeRequests: image: python:latest stage: build rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" cache: key: FindMissingMergeRequests.v1 paths: - .cache/pip before_script: - pip3 install --user 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 .flatpak: image: 'docker.io/bilelmoussaoui/flatpak-github-actions' stage: build script: - flatpak install -y flathub org.kde.Platform/x86_64/5.15-21.08 - flatpak install -y flathub org.kde.Sdk/x86_64/5.15-21.08 - flatpak-builder --ccache --force-clean --repo=repo build CI/org.openmw.OpenMW.devel.yaml - flatpak build-bundle ./repo openmw.flatpak org.openmw.OpenMW.devel cache: key: flatpak paths: - ".flatpak-builder" artifacts: untracked: false paths: - "openmw.flatpak" # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # Flatpak Builds compile all dependencies aswell so need more time. Build results of libraries are cached timeout: 4h openmw-openmw-0.48.0/.readthedocs.yaml000066400000000000000000000002071445372753700176620ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/source/conf.py python: version: 3.8 install: - requirements: docs/requirements.txt openmw-openmw-0.48.0/.resubmitted_merge_requests.txt000066400000000000000000000000501445372753700227070ustar00rootroot000000000000001471 1450 1420 1314 1216 1172 1160 1051 openmw-openmw-0.48.0/AUTHORS.md000066400000000000000000000176561445372753700161220ustar00rootroot00000000000000Contributors ============ The OpenMW project was started in 2008 by Nicolay Korslund. In the course of years many people have contributed to the project. If you feel your name is missing from this list, please add it to `AUTHORS.md`. Programmers ----------- Bret Curtis (psi29a) - Project leader 2019-present Marc Zinnschlag (Zini) - Project leader 2010-2018 Nicolay Korslund - Project leader 2008-2010 scrawl - Top contributor Adam Hogan (aurix) Aesylwinn aegis AHSauge Aleksandar Jovanov Alex Haddad (rainChu) Alex McKibben alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ananace) Alex Rice Alex S (docwest) Alexey Yaryshev (skeevert) Allofich Andreas Stöckel Andrei Kortunov (akortunov) Andrew Appuhamy (andrew-app) AnyOldName3 Ardekantur Armin Preiml Artem Kotsynyak (greye) Artem Nykolenko (anikm21) artemutin Arthur Moore (EmperorArthur) Assumeru athile Aussiemon Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks Bo Svensson Britt Mathis (galdor557) Capostrophic Carl Maxwell cc9cii Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) Cody Glassman (Wazabear) Coleman Smith (olcoal) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 DanielVukelich darkf David Cernat (davidcernat) Declan Millar (declan-millar) devnexen Dieho Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) dreamer-dead David Teviotdale (dteviot) Diggory Hardy Dmitry Marakasov (AMDmi3) Duncan Frost (duncans_pumpkin) Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) Eris Caffee (eris) eroen escondida Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) Finbar Crago (finbar-crago) Florent Teppe (Tetramir) Florian Weber (Florianjw) Frédéric Chardon (fr3dz10) Gaëtan Dezeiraud (Brouilles) Gašper Sedej Gijsbert ter Horst (Ghostbird) Gohan1989 gugus/gus guidoj Haoda Wang (h313) hristoast Internecine Ivan Beloborodov (myrix) Jackerty Jacob Essex (Yacoby) Jacob Turnbull (Tankinfrank) Jake Westrip (16bitint) James Carty (MrTopCat) James Moore (moore.work) James Stephens (james-h-stephens) Jan-Peter Nilsson (peppe) Jan Borsodi (am0s) JanuarySnow Jason Hooks (jhooks) jeaye jefetienne Jeffrey Haines (Jyby) Jengerer Jiří Kuneš (kunesj) Joe Wilkerson (neuralroberts) Joel Graff (graffy) John Blomberg (fstp) Jordan Ayers Jordan Milne Josquin Frei Josua Grawitter Jules Blok (Armada651) julianko Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) Koncord Kurnevsky Evgeny (kurnevsky) Lars Söderberg (Lazaroth) lazydev Léo Peltier Leon Krieg (lkrieg) Leon Saunders (emoose) logzero lohikaarme Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) Marco Melletti (mellotanica) Marco Schulze Martin Otto (MAtahualpa) Mateusz Kołaczek (PL_kolek) Mateusz Malisz (malice) Max Henzerling (SaintMercury) megaton Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) Michael Stopa (Stomy) Michał Ściubidło (mike-sc) Michał Bień (Glorf) Michał Moroz (dragonee) Miloslav Číž (drummyfish) Miroslav Puda (pakanek) MiroslavR Mitchell Schwitzer (schwitzerm) naclander Narmo Nat Meo (Utopium) Nathan Jeffords (blunted2night) NeveHanter Nialsy Nick Crawford (nighthawk469) Nikolay Kasyanov (corristo) Noah Gooder nobrakal Nolan Poe (nopoe) Nurivan Gomez (Nuri-G) Oleg Chkan (mrcheko) Paul Cercueil (pcercuei) Paul McElroy (Greendogo) pchan3 Perry Hugh Petr Mikheev (ptmikheev) Phillip Andrews (PhillipAnd) Pi03k Pieter van der Kloet (pvdk) pkubik PLkolek PlutonicOverkill Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) Randy Davin (Kindi) rdimesio rexelion riothamus Rob Cutmore (rcutmore) Robert MacGregor (Ragora) Rohit Nirmal Roman Melnik (Kromgart) Roman Proskuryakov (kpp) Roman Siromakha (elsid) Sandy Carter (bwrsandman) Scott Howard (maqifrnswa) Sebastian Wick (swick) Sergey Fukanchik Sergey Shambir (sergey-shambir) sergoz ShadowRadiance Siimacore Simon Meulenbeek (simonmb) sir_herrbatka smbas Sophie Kirschner (pineapplemachine) spycrab Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) Stanislaw Halik (sthalik) Star-Demon stil-t svaante Sylvain Thesnieres (Garvek) t6 terrorfisch Tess (tescoShoppah) thegriglat Thomas Luppi (Digmaster) tlmullis tri4ng1e Thoronador Tobias Tribble (zackogenic) Tom Lowe (Vulpen) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) unelsson uramer viadanna Vidi_Aquam Vincent Heuken Vladimir Panteleev (CyberShadow) vocollapse Wang Ryu (bzzt) Will Herrmann (Thunderforge) Wolfgang Lieff xyzz Yohaulticetl Yuri Krupenin zelurker Documentation ------------- Adam Bowen (adamnbowen) Alejandro Sanchez (HiPhish) Bodillium Bret Curtis (psi29a) Cramal David Walley (Loriel) Diego Crespo Joakim Berg (lysol90) Ryan Tucker (Ravenwing) sir_herrbatka David Nagy (zuzaman) Packagers --------- Alexander Olofsson (Ananace) - Windows and Flatpak Alexey Sokolov (DarthGandalf) - Gentoo Linux Bret Curtis (psi29a) - Debian and Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux Karl-Felix Glatzer (k1ll) - Linux Binaries Kenny Armstrong (artorius) - Fedora Linux Nikolay Kasyanov (corristo) - Mac OS X Sandy Carter (bwrsandman) - Arch Linux Public Relations and Translations --------------------------------- Artem Kotsynyak (greye) - Russian News Writer Dawid Lakomy (Vedyimyn) - Polish News Writer ElderTroll - Release Manager Jim Clauwaert (Zedd) - Public Outreach juanmnzsk8 - Spanish News Writer Julien Voisin (jvoisin/ap0) - French News Writer Kingpix - Italian News Writer Lukasz Gromanowski (lgro) - English News Writer Martin Otto (Atahualpa) - Podcaster, Public Outreach, German Translator Mickey Lyle (raevol) - Release Manager Nekochan - English News Writer penguinroad - Indonesian News Writer Pithorn - Chinese News Writer sir_herrbatka - Polish News Writer spyboot - German Translator Tom Koenderink (Okulo) - English News Writer Website ------- Lukasz Gromanowski (Lgro) - Website Administrator Ryan Sardonic (Wry) - Wiki Editor sir_herrbatka - Forum Administrator Formula Research ---------------- Hrnchamd Epsilon fragonard Greendogo HiPhish modred11 Myckel natirips Sadler Artwork ------- Necrod - OpenMW Logo Mickey Lyle (raevol) - Wordpress Theme Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel, Lamoot - OpenMW Editor Icons Additional Credits ------------------ In this section we would like to thank people not part of OpenMW for their work. Thanks to Maxim Nikolaev, for allowing us to use his excellent Morrowind fan-art on our website and in other places. Thanks to DokterDume, for kindly providing us with the Moon and Star logo, used as the application icon and project logo. Thanks to Kevin Ryan, for creating the icon used for the Data Files tab of the OpenMW Launcher. Thanks to DejaVu team, for their DejaVuLGCSansMono fontface, see DejaVuFontLicense.txt for their license terms. openmw-openmw-0.48.0/CHANGELOG.md000066400000000000000000006440541445372753700162620ustar00rootroot000000000000000.48.0 ------ Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #1930: Followers are still fighting if a target stops combat with a leader Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3488: AI combat aiming is too slow Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3855: AI sometimes spams defensive spells Bug #3867: All followers attack player when one follower enters combat with player Bug #3905: Great House Dagoth issues Bug #4175: Objects "vibrate" when extremely far from (0,0) Bug #4203: Resurrecting an actor doesn't close the loot GUI Bug #4227: Spellcasting restrictions are checked before spellcasting animations are played Bug #4310: Spell description is centered Bug #4374: Player rotation reset when nearing area that hasn't been loaded yet Bug #4376: Moved actors don't respawn in their original cells Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node Bug #4526: Crash when additional maps are applied over a model with out of bounds UV Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: OpenMW-CS: Incorrect command implementation Bug #4744: Invisible particles aren't always processed Bug #4949: Incorrect particle lighting Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5192: Actor turn rate is too slow Bug #5207: Loose summons can be present in scene Bug #5279: Ingame console stops auto-scrolling after clicking output Bug #5318: Aiescort behaves differently from vanilla Bug #5377: Console does not appear after using menutest in inventory Bug #5379: Wandering NPCs falling through cantons Bug #5394: Windows snapping no longer works Bug #5434: Pinned windows shouldn't cover breath progress bar Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it Bug #5592: Weapon idle animations do not work properly Bug #5596: Effects in constant spells should not be merged Bug #5621: Drained stats cannot be restored Bug #5766: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #5858: Visible modal windows and dropdowns crashing game on exit Bug #5863: GetEffect should return true after the player has teleported Bug #5913: Failed assertion during Ritual of Trees quest Bug #5937: Lights always need to be rotated by 90 degrees Bug #5976: Invisibility is broken when the attack starts instead of when it ends Bug #5978: NPCs and Creatures talk to and headtrack a player character with a 75% chameleon effect or more Bug #5989: Simple water isn't affected by texture filter settings Bug #6037: Launcher: Morrowind content language cannot be set to English Bug #6049: Main Theme on OpenMW should begin on the second video like Vanilla. Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6054: Hotkey items can be equipped while in ready to attack stance Bug #6066: Addtopic "return" does not work from within script. No errors thrown Bug #6067: ESP loader fails for certain subrecord orders Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed Bug #6109: Crash when playing a custom made menu_background file Bug #6115: Showmap overzealous matching Bug #6118: Creature landing sound counts as a footstep Bug #6123: NPC with broken script freezes the game on hello Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active Bug #6131: Item selection in the avatar window not working correctly for large window sizes Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6142: Groundcover plugins change cells flags Bug #6143: Capturing a screenshot renders the engine temporarily unresponsive Bug #6154: Levitating player character is floating rather than on the floor when teleported back from Magas Volar Bug #6165: Paralyzed player character can pickup items when the inventory is open Bug #6168: Weather particles flicker for a frame at start of storms Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6177: Followers of player follower stop following after waiting for a day Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6191: Encumbrance messagebox timer works incorrectly Bug #6197: Infinite Casting Loop Bug #6253: Multiple instances of Reflect stack additively Bug #6255: Reflect is different from vanilla Bug #6256: Crash on exit with enabled shadows and statically linked OpenSceneGraph Bug #6258: Barter menu glitches out when modifying prices Bug #6273: Respawning NPCs rotation is inconsistent Bug #6276: Deleted groundcover instances are not deleted in game Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6285: OpenMW-CS: Brush template drawing and terrain selection drawing performance is very bad Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6303: After "go to jail" weapon can be stuck in the ready to attack state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Bug #6321: Arrow enchantments should always be applied to the target Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6324: Special Slave Companions: Can't buy the slave companions Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6327: Blocking roots the character in place Bug #6333: Werewolf stat changes should be implemented as damage/fortifications Bug #6343: Magic projectile speed doesn't take race weight into account Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6354: SFX abruptly cut off after crossing max distance Bug #6358: Changeweather command does not report an error when entering non-existent region Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation Bug #6389: Maximum light distance setting doesn't affect water reflections Bug #6395: Translations with longer tab titles may cause tabs to disappear from the options menu Bug #6396: Inputting certain Unicode characters triggers an assertion Bug #6416: Morphs are applied to the wrong target Bug #6417: OpenMW doesn't always use the right node to accumulate movement Bug #6429: Wyrmhaven: Can't add AI packages to player Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened Bug #6451: Weapon summoned from Cast When Used item will have the name "None" Bug #6473: Strings from NIF should be parsed only to first null terminator Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime Bug #6517: Rotations for KeyframeData in NIFs should be optional Bug #6519: Effects tooltips for ingredients work incorrectly Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary Bug #6544: Far from world origin objects jitter when camera is still Bug #6545: Player character momentum is preserved when going to a different cell Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Bug #6606: Quests with multiple IDs cannot always be restarted Bug #6653: With default settings the in-game console doesn't fit into screen Bug #6655: Constant effect absorb attribute causes the game to break Bug #6667: Pressing the Esc key while resting or waiting causes black screen. Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Bug #6682: HitOnMe doesn't fire as intended Bug #6697: Shaders vertex lighting incorrectly clamped Bug #6705: OpenMW CS: A typo in the Creature levelled list Bug #6711: Log time differs from real time Bug #6717: Broken script causes interpreter stack corruption Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body Bug #6730: LoopGroup stalls animation after playing :Stop frame until another animation is played Bug #6753: Info records without a DATA subrecords are loaded incorrectly Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing Bug #6799: Game crashes if an NPC has no Class attached Bug #6849: ImageButton texture is not scaled properly Bug #6860: Sinnammu randomly strafes while running on water Bug #6869: Hits queue stagger during swing animation Bug #6890: SDL_PeepEvents errors are not handled Bug #6895: Removing a negative number of items from a script, makes the script terminate with an error Bug #6896: Sounds played using PlaySound3D are cut off as the emitter leaves the cell Bug #6898: Accessing the Quick Inventory menu does not work while in menu mode Bug #6901: Morrowind.exe soul gem usage discrepancy Bug #6909: Using enchanted items has no animation Bug #6910: Torches should not be extinguished when not being held Bug #6913: Constant effect enchanted items don't break invisibility Bug #6923: Dispose of corpse prevents respawning after load Bug #6937: Divided by Nix Hounds quest is broken Bug #7008: Race condition on initializing a vector of reserved node names Bug #7121: Crash on TimeStamp construction with invalid hour value Bug #7251: Force shaders setting still renders some drawables with FFP Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" Feature #2554: OpenMW-CS: Modifying an object in the cell view should trigger the instances table to scroll to the corresponding record Feature #2766: Warn user if their version of Morrowind is not the latest. Feature #2780: A way to see current OpenMW version in the console Feature #2858: Add a tab to the launcher for handling datafolders Feature #3180: Support uncompressed colour-mapped TGA files Feature #3245: OpenMW-CS: Instance editing grid Feature #3616: Allow Zoom levels on the World Map Feature #3668: Support palettized DDS files Feature #4067: Post Processing Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier Feature #4974: Overridable MyGUI layout Feature #4975: Built-in TrueType fonts Feature #5198: Implement "Magic effect expired" event Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version Feature #5737: OpenMW-CS: Handle instance move from one cell to another Feature #5928: Allow Glow in the Dahrk to be disabled Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving Feature #6019: Add antialias alpha test to the launcher or enable by default if possible Feature #6032: Reverse-z depth buffer Feature #6078: Do not clear depth buffer for first-person meshes Feature #6128: Soft Particles Feature #6171: In-game log viewer Feature #6189: Navigation mesh disk cache Feature #6199: Support FBO Rendering Feature #6248: Embedded error marker mesh Feature #6249: Alpha testing support for Collada Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Feature #6288: OpenMW-CS: Preserve "blocked" record flags when saving Feature #6360: More realistic raindrop ripples Feature #6380: Treat commas as whitespace in scripts Feature #6419: Don't grey out topics if they can produce another topic reference Feature #6443: Support NiStencilProperty Feature #6496: Handle NCC flag in NIF files Feature #6534: Shader-based object texture blending Feature #6541: Gloss-mapping Feature #6557: Add support for controller gyroscope Feature #6592: Support for NiTriShape particle emitters Feature #6600: Support NiSortAdjustNode Feature #6631: Support FFMPEG 5 Feature #6684: Support NiFltAnimationNode Feature #6699: Support Ignored flag Feature #6700: Support windowed fullscreen Feature #6706: Save the size of the Options window Feature #6721: OpenMW-CS: Add option to open records in new window Feature #6823: Support for animation layering for osgAnimation formats Feature #6867: Add a way to localize hardcoded strings in GUI Feature #6888: Add switch for armor degradation fix Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu Feature #6941: Allow users to easily change font size and ttf resolution Feature #7434: Exponential fog Feature #7435: Sky blending Task #5534: Remove support for OSG 3.4 Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Task #6435: Add support for MSVC 2022 Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data` 0.47.0 ------ Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1901: Actors colliding behaviour is different from vanilla Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants Bug #2976: [reopened]: Issues combining settings from the command line and both config files Bug #3137: Walking into a wall prevents jumping Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3789: Crash in visitEffectSources while in battle Bug #3862: Random container contents behave differently than vanilla Bug #3929: Leveled list merchant containers respawn on barter Bug #4021: Attributes and skills are not stored as floats Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching Bug #4476: Abot Gondoliers: player hangs in air during scenic travel Bug #4568: Too many actors in one spot can push other actors out of bounds Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5026: Data races with rain intensity uniform set by sky and used by water Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5165: Active spells should use real time intead of timestamps Bug #5300: NPCs don't switch from torch to shield when starting combat Bug #5358: ForceGreeting always resets the dialogue window completely Bug #5363: Enchantment autocalc not always 0/1 Bug #5364: Script fails/stops if trying to startscript an unknown script Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key Bug #5384: OpenMW-CS: Deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5400: OpenMW-CS: Verifier checks race of non-skin bodyparts Bug #5403: Enchantment effect doesn't show on an enemy during death animation Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5416: Junk non-node records before the root node are not handled gracefully Bug #5422: The player loses all spells when resurrected Bug #5423: Guar follows actors too closely Bug #5424: Creatures do not headtrack player Bug #5425: Poison effect only appears for one frame Bug #5427: GetDistance unknown ID error is misleading Bug #5431: Physics performance degradation after a specific number of actors on a scene Bug #5435: Enemies can't hurt the player when collision is off Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster Bug #5452: Autowalk is being included in savegames Bug #5469: Local map is reset when re-entering certain cells Bug #5472: Mistify mod causes CTD in 0.46 on Mac Bug #5473: OpenMW-CS: Cell border lines don't update properly on terrain change Bug #5479: NPCs who should be walking around town are standing around without walking Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold Bug #5485: Intimidate doesn't increase disposition on marginal wins Bug #5490: Hits to carried left slot aren't redistributed if there's no shield equipped Bug #5499: Faction advance is available when requirements not met Bug #5502: Dead zone for analogue stick movement is too small Bug #5507: Sound volume is not clamped on ingame settings update Bug #5525: Case-insensitive search in the inventory window does not work with non-ASCII characters Bug #5531: Actors flee using current rotation by axis x Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue Bug #5557: Diagonal movement is noticeably slower with analogue stick Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics Bug #5603: Setting constant effect cast style doesn't correct effects view Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once Bug #5619: Input events are queued during save loading Bug #5622: Can't properly interact with the console when in pause menu Bug #5627: Bookart not shown if it isn't followed by
statement Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them Bug #5639: Tooltips cover Messageboxes Bug #5644: Summon effects running on the player during game initialization cause crashes Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-CS: FRMR subrecords are saved with the wrong MastIdx Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss Bug #5681: Player character can clip or pass through bridges instead of colliding against them Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view Bug #5731: OpenMW-CS: skirts are invisible on characters Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5800: Equipping a CE enchanted ring deselects an already equipped and selected enchanted ring from the spell menu Bug #5807: Video decoding crash on ARM Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Bug #5869: Guards can initiate arrest dialogue behind locked doors Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment Bug #5877: Effects appearing with empty icon Bug #5899: Visible modal windows and dropdowns crashing game on exit Bug #5902: NiZBufferProperty is unable to disable the depth test Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work Bug #5914: BM: The Swimmer can't reach destination Bug #5923: Clicking on empty spaces between journal entries might show random topics Bug #5934: AddItem command doesn't accept negative values Bug #5975: NIF controllers from sheath meshes are used Bug #5991: Activate should always be allowed for inventory items Bug #5995: NiUVController doesn't calculate the UV offset properly Bug #6007: Crash when ending cutscene is playing Bug #6016: Greeting interrupts Fargoth's sneak-walk Bug #6022: OpenMW-CS: Terrain selection is not updated when undoing/redoing terrain changes Bug #6023: OpenMW-CS: Clicking on a reference in "Terrain land editing" mode discards corresponding select/edit action Bug #6028: Particle system controller values are incorrectly used Bug #6035: OpenMW-CS: Circle brush in "Terrain land editing" mode sometimes includes vertices outside its radius Bug #6036: OpenMW-CS: Terrain selection at the border of cells omits certain corner vertices Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu Feature #2159: "Graying out" exhausted dialogue topics Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log Feature #2798: Mutable ESM records Feature #3171: OpenMW-CS: Instance drag selection Feature #3983: Wizard: Add link to buy Morrowind Feature #4201: Projectile-projectile collision Feature #4486: Handle crashes on Windows Feature #4894: Consider actors as obstacles for pathfinding Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #5043: Head Bobbing Feature #5199: OpenMW-CS: Improve scene view colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5445: Handle NiLines Feature #5456: Basic collada animation support Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5500: Prepare enough navmesh tiles before scene loading ends Feature #5511: Add in game option to toggle HRTF support in OpenMW Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5551: Do not reboot PC after OpenMW installation on Windows Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement Feature #5580: Service refusal filtering Feature #5610: Actors movement should be smoother Feature #5642: Ability to attach arrows to actor skeleton instead of bow mesh Feature #5649: Skyrim SE compressed BSA format support Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5813: Instanced groundcover support Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up Feature #5980: Support Bullet with double precision instead of one with single precision Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes Feature #6033: Include pathgrid to navigation mesh Feature #6034: Find path based on area cost depending on NPC stats Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation 0.46.0 ------ Bug #1515: Opening console masks dialogue, inventory menu Bug #1933: Actors can have few stocks of the same item Bug #2395: Duplicated plugins in the launcher when multiple data directories provide the same plugin Bug #2679: Unable to map mouse wheel under control settings Bug #2969: Scripted items can stack Bug #2976: [reopened in 0.47] Data lines in global openmw.cfg take priority over user openmw.cfg Bug #2987: Editor: some chance and AI data fields can overflow Bug #3006: 'else if' operator breaks script compilation Bug #3109: SetPos/Position handles actors differently Bug #3282: Unintended behaviour when assigning F3 and Windows keys Bug #3550: Companion from mod attacks the air after combat has ended Bug #3609: Items from evidence chest are not considered to be stolen if player is allowed to use the chest Bug #3623: Display scaling breaks mouse recognition Bug #3725: Using script function in a non-conditional expression breaks script compilation Bug #3733: Normal maps are inverted on mirrored UVs Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe Bug #3977: Non-ASCII characters in object ID's are not supported Bug #4009: Launcher does not show data files on the first run after installing Bug #4077: Enchanted items are not recharged if they are not in the player's inventory Bug #4141: PCSkipEquip isn't set to 1 when reading books/scrolls Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect Bug #4262: Rain settings are hardcoded Bug #4270: Closing doors while they are obstructed desyncs closing sfx Bug #4276: Resizing character window differs from vanilla Bug #4284: ForceSneak behaviour is inconsistent if the target has AiWander package Bug #4329: Removed birthsign abilities are restored after reloading the save Bug #4341: Error message about missing GDB is too vague Bug #4383: Bow model obscures crosshair when arrow is drawn Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4449: Value returned by GetWindSpeed is incorrect Bug #4456: AiActivate should not be cancelled after target activation Bug #4493: If the setup doesn't find what it is expecting, it fails silently and displays the requester again instead of letting the user know what wasn't found. Bug #4523: "player->ModCurrentFatigue -0.001" in global script does not cause the running player to fall Bug #4540: Rain delay when exiting water Bug #4594: Actors without AI packages don't use Hello dialogue Bug #4598: Script parser does not support non-ASCII characters Bug #4600: Crash when no sound output is available or --no-sound is used. Bug #4601: Filtering referenceables by gender is broken Bug #4639: Black screen after completing first mages guild mission + training Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog Bug #4680: Heap corruption on faulty esp Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4703: Editor: it's possible to preview levelled list records Bug #4705: Editor: unable to open exterior cell views from Instances table Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active Bug #4715: "Cannot get class of an empty object" exception after pressing ESC in the dialogue mode Bug #4720: Inventory avatar has shield with two-handed weapon during [un]equipping animation Bug #4723: ResetActors command works incorrectly Bug #4736: LandTexture records overrides do not work Bug #4745: Editor: Interior cell lighting field values are not displayed as colors Bug #4746: Non-solid player can't run or sneak Bug #4747: Bones are not read from X.NIF file for NPC animation Bug #4748: Editor: Cloned, moved, added instances re-use RefNum indices Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4756: Animation issues with VAOs Bug #4757: Content selector: files can be cleared when there aren't any files to clear Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken Bug #4783: Blizzard behavior is incorrect Bug #4787: Sneaking makes 1st person walking/bobbing animation super-slow Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4802: You can rest before taking falling damage from landing from a jump Bug #4803: Stray special characters before begin statement break script compilation Bug #4804: Particle system with the "Has Sizes = false" causes an exception Bug #4805: NPC movement speed calculations do not take race Weight into account Bug #4810: Raki creature broken in OpenMW Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal Bug #4820: Spell absorption is broken Bug #4823: Jail progress bar works incorrectly Bug #4826: Uninitialized memory in unit test Bug #4827: NiUVController is handled incorrectly Bug #4828: Potion looping effects VFX are not shown for NPCs Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded Bug #4841: Russian localization ignores implicit keywords Bug #4844: Data race in savegame loading / GlobalMap render Bug #4847: Idle animation reset oddities Bug #4851: No shadows since switch to OSG Bug #4860: Actors outside of processing range visible for one frame after spawning Bug #4867: Arbitrary text after local variable declarations breaks script compilation Bug #4876: AI ratings handling inconsistencies Bug #4877: Startup script executes only on a new game start Bug #4879: SayDone returns 0 on the frame Say is called Bug #4888: Global variable stray explicit reference calls break script compilation Bug #4896: Title screen music doesn't loop Bug #4902: Using scrollbars in settings causes resolution to change Bug #4904: Editor: Texture painting with duplicate of a base-version texture Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5 Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used. Bug #4918: Abilities don't play looping VFX when they're initially applied Bug #4920: Combat AI uses incorrect invisibility check Bug #4922: Werewolves can not attack if the transformation happens during attack Bug #4927: Spell effect having both a skill and an attribute assigned is a fatal error Bug #4932: Invalid records matching when loading save with edited plugin Bug #4933: Field of View not equal with Morrowind Bug #4938: Strings from subrecords with actually empty headers can't be empty Bug #4942: Hand-to-Hand attack type is chosen randomly when "always use best attack" is turned off Bug #4945: Poor random magic magnitude distribution Bug #4947: Player character doesn't use lip animation Bug #4948: Footstep sounds while levitating on ground level Bug #4952: Torches held by NPCs flicker too quickly Bug #4961: Flying creature combat engagement takes z-axis into account Bug #4963: Enchant skill progress is incorrect Bug #4964: Multiple effect spell projectile sounds play louder than vanilla Bug #4965: Global light attenuation settings setup is lacking Bug #4969: "Miss" sound plays for any actor Bug #4972: Player is able to use quickkeys while disableplayerfighting is active Bug #4979: AiTravel maximum range depends on "actors processing range" setting Bug #4980: Drowning mechanics is applied for actors indifferently from distance to player Bug #4984: "Friendly hits" feature should be used only for player's followers Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent Bug #4990: Dead bodies prevent you from hitting Bug #4991: Jumping occasionally takes too much fatigue Bug #4999: Drop instruction behaves differently from vanilla Bug #5001: Possible data race in the Animation::setAlpha() Bug #5004: Werewolves shield their eyes during storm Bug #5012: "Take all" on owned container generates a messagebox per item Bug #5018: Spell tooltips don't support purely negative magnitudes Bug #5025: Data race in the ICO::setMaximumNumOfObjectsToCompilePerFrame() Bug #5028: Offered price caps are not trading-specific Bug #5038: Enchanting success chance calculations are blatantly wrong Bug #5047: # in cell names sets color Bug #5050: Invalid spell effects are not handled gracefully Bug #5055: Mark, Recall, Intervention magic effect abilities have no effect when added and removed in the same frame Bug #5056: Calling Cast function on player doesn't equip the spell but casts it Bug #5059: Modded animation with combined attack keys always does max damage and can double damage Bug #5060: Magic effect visuals stop when death animation begins instead of when it ends Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden Bug #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine Bug #5069: Blocking creatures' attacks doesn't degrade shields Bug #5073: NPCs open doors in front of them even if they don't have to Bug #5074: Paralyzed actors greet the player Bug #5075: Enchanting cast style can be changed if there's no object Bug #5078: DisablePlayerLooking is broken Bug #5081: OpenMW-CS: Apparatus type "Alembic" is erroneously named "Albemic" Bug #5082: Scrolling with controller in GUI mode is broken Bug #5087: Some valid script names can't be used as string arguments Bug #5089: Swimming/Underwater creatures only swim around ground level Bug #5092: NPCs with enchanted weapons play sound when out of charges Bug #5093: Hand to hand sound plays on knocked out enemies Bug #5097: String arguments can't be parsed as number literals in scripts Bug #5099: Non-swimming enemies will enter water if player is water walking Bug #5103: Sneaking state behavior is still inconsistent Bug #5104: Black Dart's enchantment doesn't trigger at low Enchant levels Bug #5106: Still can jump even when encumbered Bug #5110: ModRegion with a redundant numerical argument breaks script execution Bug #5112: Insufficient magicka for current spell not reflected on HUD icon Bug #5113: Unknown alchemy question mark not centered Bug #5123: Script won't run on respawn Bug #5124: Arrow remains attached to actor if pulling animation was cancelled Bug #5126: Swimming creatures without RunForward animations are motionless during combat Bug #5134: Doors rotation by "Lock" console command is inconsistent Bug #5136: LegionUniform script: can not access local variables Bug #5137: Textures with Clamp Mode set to Clamp instead of Wrap are too dark outside the boundaries Bug #5138: Actors stuck in half closed door Bug #5149: Failing lock pick attempts isn't always a crime Bug #5155: Lock/unlock behavior differs from vanilla Bug #5158: Objects without a name don't fallback to their ID Bug #5159: NiMaterialColorController can only control the diffuse color Bug #5161: Creature companions can't be activated when they are knocked down Bug #5163: UserData is not copied during node cloning Bug #5164: Faction owned items handling is incorrect Bug #5166: Scripts still should be executed after player's death Bug #5167: Player can select and cast spells before magic menu is enabled Bug #5168: Force1stPerson and Force3rdPerson commands are not really force view change Bug #5169: Nested levelled items/creatures have significantly higher chance not to spawn Bug #5175: Random script function returns an integer value Bug #5177: Editor: Unexplored map tiles get corrupted after a file with terrain is saved Bug #5182: OnPCEquip doesn't trigger on skipped beast race attempts to equip something not equippable by beasts Bug #5186: Equipped item enchantments don't affect creatures Bug #5190: On-strike enchantments can be applied to and used with non-projectile ranged weapons Bug #5196: Dwarven ghosts do not use idle animations Bug #5206: A "class does not have NPC stats" error when player's follower kills an enemy with damage spell Bug #5209: Spellcasting ignores race height Bug #5210: AiActivate allows actors to open dialogue and inventory windows Bug #5211: Screen fades in if the first loaded save is in interior cell Bug #5212: AiTravel does not work for actors outside of AI processing range Bug #5213: SameFaction script function is broken Bug #5218: Crash when disabling ToggleBorders Bug #5220: GetLOS crashes when actor isn't loaded Bug #5222: Empty cell name subrecords are not saved Bug #5223: Bow replacement during attack animation removes attached arrow Bug #5226: Reputation should be capped Bug #5229: Crash if mesh controller node has no data node Bug #5239: OpenMW-CS does not support non-ASCII characters in path names Bug #5241: On-self absorb spells cannot be detected Bug #5242: ExplodeSpell behavior differs from Cast behavior Bug #5246: Water ripples persist after cell change Bug #5249: Wandering NPCs start walking too soon after they hello Bug #5250: Creatures display shield ground mesh instead of shield body part Bug #5255: "GetTarget, player" doesn't return 1 during NPC hello Bug #5261: Creatures can sometimes become stuck playing idles and never wander again Bug #5264: "Damage Fatigue" Magic Effect Can Bring Fatigue below 0 Bug #5269: Editor: Cell lighting in resaved cleaned content files is corrupted Bug #5278: Console command Show doesn't fall back to global variable after local var not found Bug #5308: World map copying makes save loading much slower Bug #5313: Node properties of identical type are not applied in the correct order Bug #5326: Formatting issues in the settings.cfg Bug #5328: Skills aren't properly reset for dead actors Bug #5345: Dopey Necromancy does not work due to a missing quote Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error Bug #5352: Light source items' duration is decremented while they aren't visible Feature #1724: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file Feature #3517: Multiple projectiles enchantment Feature #3610: Option to invert X axis Feature #3871: Editor: Terrain Selection Feature #3893: Implicit target for "set" function in console Feature #3980: In-game option to disable controller Feature #3999: Shift + Double Click should maximize/restore menu size Feature #4001: Toggle sneak controller shortcut Feature #4068: OpenMW-CS: Add a button to reset key bindings to defaults Feature #4129: Beta Comment to File Feature #4202: Open .omwaddon files without needing to open openmw-cs first Feature #4209: Editor: Faction rank sub-table Feature #4255: Handle broken RepairedOnMe script function Feature #4316: Implement RaiseRank/LowerRank functions properly Feature #4360: Improve default controller bindings Feature #4544: Actors movement deceleration Feature #4673: Weapon sheathing Feature #4675: Support for NiRollController Feature #4708: Radial fog support Feature #4730: Native animated containers support Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode Feature #4831: Item search in the player's inventory Feature #4836: Daytime node switch Feature #4840: Editor: Transient terrain change support Feature #4859: Make water reflections more configurable Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable Feature #4944: Pause audio when OpenMW is minimized Feature #4958: Support eight blood types Feature #4962: Add casting animations for magic items Feature #4968: Scalable UI widget skins Feature #4971: OpenMW-CS: Make rotations display as degrees instead of radians Feature #4994: Persistent pinnable windows hiding Feature #5000: Compressed BSA format support Feature #5005: Editor: Instance window via Scene window Feature #5010: Native graphics herbalism support Feature #5031: Make GetWeaponType function return different values for tools Feature #5033: Magic armor mitigation for creatures Feature #5034: Make enchanting window stay open after a failed attempt Feature #5036: Allow scripted faction leaving Feature #5046: Gamepad thumbstick cursor speed Feature #5051: Provide a separate textures for scrollbars Feature #5091: Human-readable light source duration Feature #5094: Unix like console hotkeys Feature #5098: Allow user controller bindings Feature #5114: Refresh launcher mod list Feature #5121: Handle NiTriStrips and NiTriStripsData Feature #5122: Use magic glow for enchanted arrows Feature #5131: Custom skeleton bones Feature #5132: Unique animations for different weapon types Feature #5146: Safe Dispose corpse Feature #5147: Show spell magicka cost in spell buying window Feature #5170: Editor: Land shape editing, land selection Feature #5172: Editor: Delete instances/references with keypress in scene window Feature #5193: Shields sheathing Feature #5201: Editor: Show tool outline in scene view, when using editmodes Feature #5219: Impelement TestCells console command Feature #5224: Handle NiKeyframeController for NiTriShape Feature #5274: Editor: Keyboard shortcut to drop objects to ground/obstacle in scene view Feature #5304: Morrowind-style bump-mapping Feature #5311: Support for gyroscopic input (e.g. Android) Feature #5314: Ingredient filter in the alchemy window Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions Task #4721: Add NMake support to the Windows prebuild script 0.45.0 ------ Bug #1875: Actors in inactive cells don't heal from resting Bug #1990: Sunrise/sunset not set correct Bug #2131: Lustidrike's spell misses the player every time Bug #2222: Fatigue's effect on selling price is backwards Bug #2256: Landing sound not playing when jumping immediately after landing Bug #2274: Thin platform clips through player character instead of lifting Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2446: Restore Attribute/Skill should allow restoring drained attributes Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2626: Resurrecting the player does not resume the game Bug #2772: Non-existing class or faction freezes the game Bug #2835: Player able to slowly move when overencumbered Bug #2852: No murder bounty when a player follower commits murder Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit Bug #2872: Tab completion in console doesn't work with explicit reference Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #3049: Drain and Fortify effects are not properly applied on health, magicka and fatigue Bug #3059: Unable to hit with marksman weapons when too close to an enemy Bug #3072: Fatal error on AddItem that has a script containing Equip Bug #3219: NPC and creature initial position tracing down limit is too small Bug #3249: Fixed revert function not updating views properly Bug #3288: TrueType fonts are handled incorrectly Bug #3374: Touch spells not hitting kwama foragers Bug #3486: [Mod] NPC Commands does not work Bug #3533: GetSpellEffects should detect effects with zero duration Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning Bug #3681: OpenMW-CS: Clicking Scripts in Preferences spawns many color pickers Bug #3762: AddSoulGem and RemoveSpell redundant count arguments break script execution Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned Bug #3890: Magic light source attenuation is inaccurate Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters Bug #3920: RemoveSpellEffects doesn't remove constant effects Bug #3948: AiCombat moving target aiming uses incorrect speed for magic projectiles Bug #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes Bug #3993: Terrain texture blending map is not upscaled Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts Bug #4125: OpenMW logo cropped on bugtracker Bug #4215: OpenMW shows book text after last EOL tag Bug #4217: Fixme implementation differs from Morrowind's Bug #4221: Characters get stuck in V-shaped terrain Bug #4230: AiTravel package issues break some Tribunal quests Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality Bug #4251: Stationary NPCs do not return to their position after combat Bug #4260: Keyboard navigation makes persuasion exploitable Bug #4271: Scamp flickers when attacking Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted Bug #4291: Non-persistent actors that started the game as dead do not play death animations Bug #4292: CenterOnCell implementation differs from vanilla Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished Bug #4311: OpenMW does not handle RootCollisionNode correctly Bug #4327: Missing animations during spell/weapon stance switching Bug #4333: Keyboard navigation in containers is not intuitive Bug #4358: Running animation is interrupted when magic mode is toggled Bug #4368: Settings window ok button doesn't have key focus by default Bug #4378: On-self absorb spells restore stats Bug #4393: NPCs walk back to where they were after using ResetActors Bug #4416: Non-music files crash the game when they are tried to be played Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4431: "Lock 0" console command is a no-op Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax Bug #4452: Default terrain texture bleeds through texture transitions Bug #4453: Quick keys behaviour is invalid for equipment Bug #4454: AI opens doors too slow Bug #4457: Item without CanCarry flag prevents shield autoequipping in dark areas Bug #4458: AiWander console command handles idle chances incorrectly Bug #4459: NotCell dialogue condition doesn't support partial matches Bug #4460: Script function "Equip" doesn't bypass beast restrictions Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4463: %g format doesn't return more digits Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal Bug #4470: Non-bipedal creatures with Weapon & Shield flag have inconsistent behaviour Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Bug #4479: "Game" category on Advanced page is getting too long Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory Bug #4489: Goodbye doesn't block dialogue hyperlinks Bug #4490: PositionCell on player gives "Error: tried to add local script twice" Bug #4494: Training cap based off Base Skill instead of Modified Skill Bug #4495: Crossbow animations blending is buggy Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused Bug #4497: File names starting with x or X are not classified as animation Bug #4503: Cast and ExplodeSpell commands increase alteration skill Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4519: Knockdown does not discard movement in the 1st-person mode Bug #4527: Sun renders on water shader in some situations where it shouldn't Bug #4531: Movement does not reset idle animations Bug #4532: Underwater sfx isn't tied to 3rd person camera Bug #4539: Paper Doll is affected by GUI scaling Bug #4543: Picking cursed items through inventory (menumode) makes it disappear Bug #4545: Creatures flee from werewolves Bug #4551: Replace 0 sound range with default range separately Bug #4553: Forcegreeting on non-actor opens a dialogue window which cannot be closed Bug #4557: Topics with reserved names are handled differently from vanilla Bug #4558: Mesh optimizer: check for reserved node name is case-sensitive Bug #4560: OpenMW does not update pinned windows properly Bug #4563: Fast travel price logic checks destination cell instead of service actor cell Bug #4565: Underwater view distance should be limited Bug #4573: Player uses headtracking in the 1st-person mode Bug #4574: Player turning animations are twitchy Bug #4575: Weird result of attack animation blending with movement animations Bug #4576: Reset of idle animations when attack can not be started Bug #4591: Attack strength should be 0 if player did not hold the attack button Bug #4593: Editor: Instance dragging is broken Bug #4597: <> operator causes a compile error Bug #4604: Picking up gold from the ground only makes 1 grabbed Bug #4607: Scaling for animated collision shapes is applied twice Bug #4608: Falling damage is applied twice Bug #4611: Instant magic effects have 0 duration in custom spell cost calculations unlike vanilla Bug #4614: Crash due to division by zero when FlipController has no textures Bug #4615: Flicker effects for light sources are handled incorrectly Bug #4617: First person sneaking offset is not applied while the character is in air Bug #4618: Sneaking is possible while the character is flying Bug #4622: Recharging enchanted items with Soul Gems does not award experience if it fails Bug #4628: NPC record reputation, disposition and faction rank should have unsigned char type Bug #4633: Sneaking stance affects speed even if the actor is not able to crouch Bug #4641: GetPCJumping is handled incorrectly Bug #4644: %Name should be available for all actors, not just for NPCs Bug #4646: Weapon force-equipment messes up ongoing attack animations Bug #4648: Hud thinks that throwing weapons have condition Bug #4649: Levelup fully restores health Bug #4653: Length of non-ASCII strings is handled incorrectly in ESM reader Bug #4654: Editor: UpdateVisitor does not initialize skeletons for animated objects Bug #4656: Combat AI: back up behaviour is incorrect Bug #4668: Editor: Light source color is displayed as an integer Bug #4669: ToggleCollision should trace the player down after collision being enabled Bug #4671: knownEffect functions should use modified Alchemy skill Bug #4672: Pitch factor is handled incorrectly for crossbow animations Bug #4674: Journal can be opened when settings window is open Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one Bug #4678: Crash in ESP parser when SCVR has no variable names Bug #4684: Spell Absorption is additive Bug #4685: Missing sound causes an exception inside Say command Bug #4689: Default creature soundgen entries are not used Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen Feature #912: Editor: Add missing icons to UniversalId tables Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one Feature #2845: Editor: add record view and preview default keybindings Feature #2847: Content selector: allow to copy the path to a file by using the context menu Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade Feature #3276: Editor: Search - Show number of (remaining) search results and indicate a search without any results Feature #3641: Editor: Limit FPS in 3d preview window Feature #3703: Ranged sneak attack criticals Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command Feature #4285: Support soundgen calls for activators Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically Feature #4444: Per-group KF-animation files support Feature #4466: Editor: Add option to ignore "Base" records when running verifier Feature #4488: Make water shader rougher during rain Feature #4509: Show count of enchanted items in stack in the spells list Feature #4512: Editor: Use markers for lights and creatures levelled lists Feature #4548: Weapon priority: use the actual chance to hit the target instead of weapon skill Feature #4549: Weapon priority: use the actual damage in weapon rating calculations Feature #4550: Weapon priority: make ranged weapon bonus more sensible Feature #4579: Add option for applying Strength into hand to hand damage Feature #4581: Use proper logging system Feature #4624: Spell priority: don't cast hit chance-affecting spells if the enemy is not in respective stance at the moment Feature #4625: Weapon priority: use weighted mean for melee damage rating Feature #4626: Weapon priority: account for weapon speed Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating Feature #4636: Use sTo GMST in spellmaking menu Feature #4642: Batching potion creation Feature #4647: Cull actors outside of AI processing range Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions Feature #4697: Use the real thrown weapon damage in tooltips and AI Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test Task #4605: Optimize skinning Task #4606: Support Rapture3D's OpenAL driver Task #4613: Incomplete type errors when compiling with g++ on OSX 10.9 Task #4621: Optimize combat AI Task #4643: Revise editor record verifying functionality Task #4645: Use constants instead of widely used magic numbers Task #4652: Move call to enemiesNearby() from InputManager::rest() to World::canRest() 0.44.0 ------ Bug #1428: Daedra summoning scripts aren't executed when the item is taken through the inventory Bug #1987: Some glyphs are not supported Bug #2254: Magic related visual effects are not rendered when loading a saved game Bug #2485: Journal alphabetical index doesn't match "Morrowind content language" setting Bug #2703: OnPCHitMe is not handled correctly Bug #2829: Incorrect order for content list consisting of a game file and an esp without dependencies Bug #2841: "Total eclipse" happens if weather settings are not defined. Bug #2897: Editor: Rename "Original creature" field Bug #3278: Editor: Unchecking "Auto Calc" flag changes certain values Bug #3343: Editor: ID sorting is case-sensitive in certain tables Bug #3557: Resource priority confusion when using the local data path as installation root Bug #3587: Pathgrid and Flying Creatures wrong behaviour – abotWhereAreAllBirdsGoing Bug #3603: SetPos should not skip weather transitions Bug #3618: Myar Aranath total conversion can't be started due to capital-case extension of the master file Bug #3638: Fast forwarding can move NPC inside objects Bug #3664: Combat music does not start in dialogue Bug #3696: Newlines are accompanied by empty rectangle glyph in dialogs Bug #3708: Controllers broken on macOS Bug #3726: Items with suppressed activation can be picked up via the inventory menu Bug #3783: [Mod] Abot's Silt Striders 1.16 - silt strider "falls" to ground and glides on floor during travel Bug #3863: Can be forced to not resist arrest if you cast Calm Humanoid on aggroed death warrant guards Bug #3884: Incorrect enemy behavior when exhausted Bug #3926: Installation Wizard places Morrowind.esm after Tribunal/Bloodmoon if it has a later file creation date Bug #4061: Scripts error on special token included in name Bug #4111: Crash when mouse over soulgem with a now-missing soul Bug #4122: Swim animation should not be interrupted during underwater attack Bug #4134: Battle music behaves different than vanilla Bug #4135: Reflecting an absorb spell different from vanilla Bug #4136: Enchanted weapons without "ignore normal weapons" flag don't bypass creature "ignore normal weapons" effect Bug #4143: Antialiasing produces graphical artifacts when used with shader lighting Bug #4159: NPCs' base skeleton files should not be optimized Bug #4177: Jumping/landing animation interference/flickering Bug #4179: NPCs do not face target Bug #4180: Weapon switch sound playing even though no weapon is switched Bug #4184: Guards can initiate dialogue even though you are far above them Bug #4190: Enchanted clothes changes visibility with Chameleon on equip/unequip Bug #4191: "screenshot saved" message also appears in the screenshot image Bug #4192: Archers in OpenMW have shorter attack range than archers in Morrowind Bug #4210: Some dialogue topics are not highlighted on first encounter Bug #4211: FPS drops after minimizing the game during rainy weather Bug #4216: Thrown weapon projectile doesn't rotate Bug #4223: Displayed spell casting chance must be 0 if player doesn't have enough magicka to cast it Bug #4225: Double "Activate" key presses with Mouse and Gamepad. Bug #4226: The current player's class should be default value in the class select menu Bug #4229: Tribunal/Bloodmoon summoned creatures fight other summons Bug #4233: W and A keys override S and D Keys Bug #4235: Wireframe mode affects local map Bug #4239: Quick load from container screen causes crash Bug #4242: Crime greetings display in Journal Bug #4245: Merchant NPCs sell ingredients growing on potted plants they own Bug #4246: Take armor condition into account when calcuting armor rating Bug #4250: Jumping is not as fluid as it was pre-0.43.0 Bug #4252: "Error in frame: FFmpeg exception: Failed to allocate input stream" message spam if OpenMW encounter non-music file in the Music folder Bug #4261: Magic effects from eaten ingredients always have 1 sec duration Bug #4263: Arrow position is incorrect in 3rd person view during attack for beast races Bug #4264: Player in god mode can be affected by some negative spell effects Bug #4269: Crash when hovering the faction section and the 'sAnd' GMST is missing (as in MW 1.0) Bug #4272: Root note transformations are discarded again Bug #4279: Sometimes cells are not marked as explored on the map Bug #4298: Problem with MessageBox and chargen menu interaction order Bug #4301: Optimizer breaks LOD nodes Bug #4308: PlaceAtMe doesn't inherit scale of calling object Bug #4309: Only harmful effects with resistance effect set are resistable Bug #4313: Non-humanoid creatures are capable of opening doors Bug #4314: Rainy weather slows down the game when changing from indoors/outdoors Bug #4319: Collisions for certain meshes are incorrectly ignored Bug #4320: Using mouse 1 to move forward causes selection dialogues to jump selections forward. Bug #4322: NPC disposition: negative faction reaction modifier doesn't take PC rank into account Bug #4328: Ownership by dead actors is not cleared from picked items Bug #4334: Torch and shield usage inconsistent with original game Bug #4336: Wizard: Incorrect Morrowind assets path autodetection Bug #4343: Error message for coc and starting cell shouldn't imply that it only works for interior cells Bug #4346: Count formatting does not work well with very high numbers Bug #4351: Using AddSoulgem fills all soul gems of the specified type Bug #4391: No visual indication is provided when an unavailable spell fails to be chosen via a quick key Bug #4392: Inventory filter breaks after loading a game Bug #4405: No default terrain in empty cells when distant terrain is enabled Bug #4410: [Mod] Arktwend: OpenMW does not use default marker definitions Bug #4412: openmw-iniimporter ignores data paths from config Bug #4413: Moving with 0 strength uses all of your fatigue Bug #4420: Camera flickering when I open up and close menus while sneaking Bug #4424: [macOS] Cursor is either empty or garbage when compiled against macOS 10.13 SDK Bug #4435: Item health is considered a signed integer Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game Feature #1786: Round up encumbrance value in the encumbrance bar Feature #2694: Editor: rename "model" column to make its purpose clear Feature #3870: Editor: Terrain Texture Brush Button Feature #3872: Editor: Edit functions in terrain texture editing mode Feature #4054: Launcher: Create menu for settings.cfg options Feature #4064: Option for fast travel services to charge for the first companion Feature #4142: Implement fWereWolfHealth GMST Feature #4174: Multiple quicksaves Feature #4407: Support NiLookAtController Feature #4423: Rebalance soul gem values Task #4015: Use AppVeyor build artifact features to make continuous builds available Editor: New (and more complete) icon set 0.43.0 ------ Bug #815: Different settings cause inconsistent underwater visibility Bug #1452: autosave is not executed when waiting Bug #1555: Closing containers with spacebar doesn't work after touching an item Bug #1692: Can't close container when item is "held" Bug #2405: Maximum distance for guards attacking hostile creatures is incorrect Bug #2445: Spellcasting can be interrupted Bug #2489: Keeping map open not persisted between saves Bug #2594: 1st person view uses wrong body texture with Better bodies Bug #2628: enablestatreviewmenu command doen't read race, class and sign values from current game Bug #2639: Attacking flag isn't reset upon reloading Bug #2698: Snow and rain VFX move with the player Bug #2704: Some creature swim animations not being used Bug #2789: Potential risk of misunderstanding using the colored "owned" crosshair feature Bug #3045: Settings containing '#' cannot be loaded Bug #3097: Drop() doesn't work when an item is held (with the mouse) Bug #3110: GetDetected doesn't work without a reference Bug #3126: Framerate nosedives when adjusting dialogue window size Bug #3243: Ampersand in configuration files isn't escaped automatically Bug #3365: Wrong water reflection along banks Bug #3441: Golden saint always dispelling soul trap / spell priority issue Bug #3528: Disposing of corpses breaks quests Bug #3531: No FPS limit when playing bink videos even though "framerate limit" is set in settings.cfg Bug #3647: Multi-effect spells play audio louder than in Vanilla Bug #3656: NPCs forget where their place in the world is Bug #3665: Music transitions are too abrupt Bug #3679: Spell cast effect should disappear after using rest command Bug #3684: Merchants do not restock empty soul gems if they acquire filled ones. Bug #3694: Wrong magicka bonus applied on character creation Bug #3706: Guards don't try to arrest the player if attacked Bug #3709: Editor: Camera is not positioned correctly on mode switches related to orbital mode Bug #3720: Death counter not cleaned of non-existing IDs when loading a game Bug #3744: "Greater/lesser or equal" operators are not parsed when their signs are swapped Bug #3749: Yagrum Bagarn moves to different position on encountering Bug #3766: DisableLevitation does not remove visuals of preexisting effect Bug #3787: Script commands in result box for voiced dialogue are ignored Bug #3793: OpenMW tries to animate animated references even when they are disabled Bug #3794: Default sound buffer size is too small for mods Bug #3796: Mod 'Undress for me' doesn't work: NPCs re-equip everything Bug #3798: tgm command behaviour differs from vanilla Bug #3804: [Mod] Animated Morrowind: some animations do not loop correctly Bug #3805: Slight enchant miscalculation Bug #3826: Rendering problems with an image in a letter Bug #3833: [Mod] Windows Glow: windows textures are much darker than in original game Bug #3835: Bodyparts with multiple NiTriShapes are not handled correctly Bug #3839: InventoryStore::purgeEffect() removes only first effect with argument ID Bug #3843: Wrong jumping fatigue loss calculations Bug #3850: Boethiah's voice is distorted underwater Bug #3851: NPCs and player say things while underwater Bug #3864: Crash when exiting to Khartag point from Ilunibi Bug #3878: Swapping soul gems while enchanting allows constant effect enchantments using any soul gem Bug #3879: Dialogue option: Go to jail, persists beyond quickload Bug #3891: Journal displays empty entries Bug #3892: Empty space before dialogue entry display Bug #3898: (mod) PositionCell in dialogue results closes dialogue window Bug #3906: "Could not find Data Files location" dialog can appear multiple times Bug #3908: [Wizard] User gets stuck if they cancel out of installing from a CD Bug #3909: Morrowind Content Language dropdown is the only element on the right half of the Settings window Bug #3910: Launcher window can be resized so that it cuts off the scroll Bug #3915: NC text key on nifs doesn't work Bug #3919: Closing inventory while cursor hovers over spell (or other magic menu item) produces left click sound Bug #3922: Combat AI should avoid enemy hits when casts Self-ranged spells Bug #3934: [macOS] Copy/Paste from system clipboard uses Control key instead of Command key Bug #3935: Incorrect attack strength for AI actors Bug #3937: Combat AI: enchanted weapons have too high rating Bug #3942: UI sounds are distorted underwater Bug #3943: CPU/GPU usage should stop when the game is minimised Bug #3944: Attempting to sell stolen items back to their owner does not remove them from your inventory Bug #3955: Player's avatar rendering issues Bug #3956: EditEffectDialog: Cancel button does not update a Range button and an Area slider properly Bug #3957: Weird bodypart rendering if a node has reserved name Bug #3960: Clothes with high cost (> 32768) are not handled properly Bug #3963: When on edge of being burdened the condition doesn't lower as you run. Bug #3971: Editor: Incorrect colour field in cell table Bug #3974: Journal page turning doesn't produce sounds Bug #3978: Instant opening and closing happens when using a Controller with Menus/Containers Bug #3981: Lagging when spells are cast, especially noticeable on new landmasses such as Tamriel Rebuilt Bug #3982: Down sounds instead of Up ones are played when trading Bug #3987: NPCs attack after some taunting with no "Goodbye" Bug #3991: Journal can still be opened at main menu Bug #3995: Dispel cancels every temporary magic effect Bug #4002: Build broken on OpenBSD with clang Bug #4003: Reduce Render Area of Inventory Doll to Fit Within Border Bug #4004: Manis Virmaulese attacks without saying anything Bug #4010: AiWander: "return to the spawn position" feature does not work properly Bug #4016: Closing menus with spacebar will still send certain assigned actions through afterwards Bug #4017: GetPCRunning and GetPCSneaking should check that the PC is actually moving Bug #4024: Poor music track distribution Bug #4025: Custom spell with copy-pasted name always sorts to top of spell list Bug #4027: Editor: OpenMW-CS misreports its own name as "OpenCS", under Mac OS Bug #4033: Archers don't attack if the arrows have run out and there is no other weapon Bug #4037: Editor: New greetings do not work in-game. Bug #4049: Reloading a saved game while falling prevents damage Bug #4056: Draw animation should not be played when player equips a new weapon Bug #4074: Editor: Merging of LAND/LTEX records Bug #4076: Disposition bar is not updated when "goodbye" selected in dialogue Bug #4079: Alchemy skill increases do not take effect until next batch Bug #4093: GetResistFire, getResistFrost and getResistShock doesn't work as in vanilla Bug #4094: Level-up messages for levels past 20 are hardcoded not to be used Bug #4095: Error in framelistener when take all items from a dead corpse Bug #4096: Messagebox with the "%0.f" format should use 0 digit precision Bug #4104: Cycling through weapons does not skip broken ones Bug #4105: birthsign generation menu does not show full details Bug #4107: Editor: Left pane in Preferences window is too narrow Bug #4112: Inventory sort order is inconsistent Bug #4113: 'Resolution not supported in fullscreen' message is inconvenient Bug #4131: Pickpocketing behaviour is different from vanilla Bug #4155: NPCs don't equip a second ring in some cases Bug #4156: Snow doesn't create water ripples Bug #4165: NPCs autoequip new clothing with the same price Feature #452: Rain-induced water ripples Feature #824: Fading for doors and teleport commands Feature #933: Editor: LTEX record table Feature #936: Editor: LAND record table Feature #1374: AI: Resurface to breathe Feature #2320: ess-Importer: convert projectiles Feature #2509: Editor: highlighting occurrences of a word in a script Feature #2748: Editor: Should use one resource manager per document Feature #2834: Have openMW's UI remember what menu items were 'pinned' across boots. Feature #2923: Option to show the damage of the arrows through tooltip. Feature #3099: Disabling inventory while dragging an item forces you to drop it Feature #3274: Editor: Script Editor - Shortcuts and context menu options for commenting code out and uncommenting code respectively Feature #3275: Editor: User Settings- Add an option to reset settings to their default status (per category / all) Feature #3400: Add keyboard shortcuts for menus Feature #3492: Show success rate while enchanting Feature #3530: Editor: Reload data files Feature #3682: Editor: Default key binding reset Feature #3921: Combat AI: aggro priorities Feature #3941: Allow starting at an unnamed exterior cell with --start Feature #3952: Add Visual Studio 2017 support Feature #3953: Combat AI: use "WhenUsed" enchantments Feature #4082: Leave the stack of ingredients or potions grabbed after using an ingredient/potion Task #2258: Windows installer: launch OpenMW tickbox Task #4152: The Windows CI script is moving files around that CMake should be dealing with 0.42.0 ------ Bug #1956: Duplicate objects after loading the game, when a mod was edited Bug #2100: Falling leaves in Vurt's Leafy West Gash II not rendered correctly Bug #2116: Cant fit through some doorways pressed against staircases Bug #2289: Some modal dialogs are not centered on the screen when the window resizes Bug #2409: Softlock when pressing weapon/magic switch keys during chargen, afterwards switches weapons even though a text field is selected Bug #2483: Previous/Next Weapon hotkeys triggered while typing the name of game save Bug #2629: centeroncell, coc causes death / fall damage time to time when teleporting from high Bug #2645: Cycling weapons is possible while console/pause menu is open Bug #2678: Combat with water creatures do not end upon exiting water Bug #2759: Light Problems in Therana's Chamber in Tel Branora Bug #2771: unhandled sdl event of type 0x302 Bug #2777: (constant/on cast) disintegrate armor/weapon on self is seemingly not working Bug #2838: Editor: '.' in a record name should be allowed Bug #2909: NPCs appear floating when standing on a slope Bug #3093: Controller movement cannot be used while mouse is moving Bug #3134: Crash possible when using console with open container Bug #3254: AI enemies hit between them. Bug #3344: Editor: Verification results sorting by Type is not alphabetical. Bug #3345: Editor: Cloned and added pathgrids are lost after reopen of saved omwgame file Bug #3355: [MGSO] Physics maxing out in south cornerclub Balmora Bug #3484: Editor: camera position is not set when changing cell via drag&drop Bug #3508: Slowfall kills Jump momentum Bug #3580: Crash: Error ElementBufferObject::remove BufferData<0> out of range Bug #3581: NPCs wander too much Bug #3601: Menu Titles not centered vertically Bug #3607: [Mac OS] Beginning of NPC speech cut off (same issue as closed bug #3453) Bug #3613: Can not map "next weapon" or "next spell" to controller Bug #3617: Enchanted arrows don't explode when hitting the ground Bug #3645: Unable to use steps in Vivec, Palace of Vivec Bug #3650: Tamriel Rebuilt 16.09.1 – Hist Cuirass GND nif is rendered inside a Pink Box Bug #3652: Item icon shadows get stuck in the alchemy GUI Bug #3653: Incorrect swish sounds Bug #3666: NPC collision should not be disabled until death animation has finished Bug #3669: Editor: Text field was missing from book object editing dialogue Bug #3670: Unhandled SDL event of type 0x304 Bug #3671: Incorrect local variable value after picking up bittercup Bug #3686: Travelling followers doesn't increase travel fee Bug #3689: Problematic greetings from Antares Big Mod that override the appropriate ones. Bug #3690: Certain summoned creatures do not engage in combat with underwater creatures Bug #3691: Enemies do not initiate combat with player followers on sight Bug #3695: [Regression] Dispel does not always dispel spell effects in 0.41 Bug #3699: Crash on MWWorld::ProjectileManager::moveMagicBolts Bug #3700: Climbing on rocks and mountains Bug #3704: Creatures don't auto-equip their shields on creation Bug #3705: AI combat engagement logic differs from vanilla Bug #3707: Animation playing does some very odd things if pc comes in contact with the animated mesh Bug #3712: [Mod] Freeze upon entering Adanumuran with mod Adanumuran Reclaimed Bug #3713: [Regression] Cancelling dialogue or using travel with creatures throws a (possibly game-breaking) exception Bug #3719: Dropped identification papers can't be picked up again Bug #3722: Command spell doesn't bring enemies out of combat Bug #3727: Using "Activate" mid-script-execution invalidates interpreter context Bug #3746: Editor: Book records show attribute IDs instead of skill IDs for teached skills entry. Bug #3755: Followers stop following after loading from savegame Bug #3772: ModStat lowers attribute to 100 if it was greater Bug #3781: Guns in Clean Hunter Rifles mod use crossbow sounds Bug #3797: NPC and creature names don't show up in combat when RMB windows are displayed Bug #3800: Wrong tooltip maximum width Bug #3801: Drowning widget is bugged Bug #3802: BarterOffer shouldn't limit pcMercantile Bug #3813: Some fatal error Bug #3816: Expression parser thinks the -> token is unexpected when a given explicit refID clashes with a journal ID Bug #3822: Custom added creatures are not animated Feature #451: Water sounds Feature #2691: Light particles sometimes not shown in inventory character preview Feature #3523: Light source on magic projectiles Feature #3644: Nif NiSphericalCollider Unknown Record Type Feature #3675: ess-Importer: convert mark location Feature #3693: ess-Importer: convert last known exterior cell Feature #3748: Editor: Replace "Scroll" check box in Book records with "Book Type" combo box. Feature #3751: Editor: Replace "Xyz Blood" check boxes in NPC and Creature records with "Blood Type" combo box Feature #3752: Editor: Replace emitter check boxes in Light records with "Emitter Type" combo box Feature #3756: Editor: Replace "Female" check box in NPC records with "Gender" combo box Feature #3757: Editor: Replace "Female" check box in BodyPart records with "Gender" combo box Task #3092: const version of ContainerStoreIterator Task #3795: /deps folder not in .gitignore 0.41.0 ------ Bug #1138: Casting water walking doesn't move the player out of the water Bug #1931: Rocks from blocked passage in Bamz-Amschend, Radacs Forge can reset and cant be removed again. Bug #2048: Almvisi and Divine Intervention display wrong spell effect Bug #2054: Show effect-indicator for "instant effect" spells and potions Bug #2150: Clockwork City door animation problem Bug #2288: Playback of weapon idle animation not correct Bug #2410: Stat-review window doesn't display starting spells, powers, or abilities Bug #2493: Repairing occasionally very slow Bug #2716: [OSG] Water surface is too transparent from some angles Bug #2859: [MAC OS X] Cannot exit fullscreen once enabled Bug #3091: Editor: will not save addon if global variable value type is null Bug #3277: Editor: Non-functional nested tables in subviews need to be hidden instead of being disabled Bug #3348: Disabled map markers show on minimap Bug #3350: Extending selection to instances with same object results in duplicates. Bug #3353: [Mod] Romance version 3.7 script failed Bug #3376: [Mod] Vampire Embrace script fails to execute Bug #3385: Banners don't animate in stormy weather as they do in the original game Bug #3393: Akulakhan re-enabled after main quest Bug #3427: Editor: OpenMW-CS instances won´t get deleted Bug #3451: Feril Salmyn corpse isn't where it is supposed to be Bug #3497: Zero-weight armor is displayed as "heavy" in inventory tooltip Bug #3499: Idle animations don't always loop Bug #3500: Spark showers at Sotha Sil do not appear until you look at the ceiling Bug #3515: Editor: Moved objects in interior cells are teleported to exterior cells. Bug #3520: Editor: OpenMW-CS cannot find project file when launching the game Bug #3521: Armed NPCs don't use correct melee attacks Bug #3535: Changing cell immediately after dying causes character to freeze. Bug #3542: Unable to rest if unalerted slaughterfish are in the cell with you Bug #3549: Blood effects occur even when a hit is resisted Bug #3551: NPC Todwendy in german version can't interact Bug #3552: Opening the journal when fonts are missing results in a crash Bug #3555: SetInvisible command should not apply graphic effect Bug #3561: Editor: changes from omwaddon are not loaded in [New Addon] mode Bug #3562: Non-hostile NPCs can be disarmed by stealing their weapons via sneaking Bug #3564: Editor: openmw-cs verification results Bug #3568: Items that should be invisible are shown in the inventory Bug #3574: Alchemy: Alembics and retorts are used in reverse Bug #3575: Diaglog choices don't work in mw 0.40 Bug #3576: Minor differences in AI reaction to hostile spell effects Bug #3577: not local nolore dialog test Bug #3578: Animation Replacer hangs after one cicle/step Bug #3579: Bound Armor skillups and sounds Bug #3583: Targetted GetCurrentAiPackage returns 0 Bug #3584: Persuasion bug Bug #3590: Vendor, Ilen Faveran, auto equips items from stock Bug #3594: Weather doesn't seem to update correctly in Mournhold Bug #3598: Saving doesn't save status of objects Bug #3600: Screen goes black when trying to travel to Sadrith Mora Bug #3608: Water ripples aren't created when walking on water Bug #3626: Argonian NPCs swim like khajiits Bug #3627: Cannot delete "Blessed touch" spell from spellbook Bug #3634: An enchanted throwing weapon consumes charges from the stack in your inventory. (0.40.0) Bug #3635: Levelled items in merchants are "re-rolled" (not bug 2952, see inside) Feature #1118: AI combat: flee Feature #1596: Editor: Render water Feature #2042: Adding a non-portable Light to the inventory should cause the player to glow Feature #3166: Editor: Instance editing mode - rotate sub mode Feature #3167: Editor: Instance editing mode - scale sub mode Feature #3420: ess-Importer: player control flags Feature #3489: You shouldn't be be able to re-cast a bound equipment spell Feature #3496: Zero-weight boots should play light boot footsteps Feature #3516: Water Walking should give a "can't cast" message and fail when you are too deep Feature #3519: Play audio and visual effects for all effects in a spell Feature #3527: Double spell explosion scaling Feature #3534: Play particle textures for spell effects Feature #3539: Make NPCs use opponent's weapon range to decide whether to dodge Feature #3540: Allow dodging for creatures with "biped" flag Feature #3545: Drop shadow for items in menu Feature #3558: Implement same spell range for "on touch" spells as original engine Feature #3560: Allow using telekinesis with touch spells on objects Task #3585: Some objects added by Morrowind Rebirth do not display properly their texture 0.40.0 ------ Bug #1320: AiWander - Creatures in cells without pathgrids do not wander Bug #1873: Death events are triggered at the beginning of the death animation Bug #1996: Resting interrupts magic effects Bug #2399: Vampires can rest in broad daylight and survive the experience Bug #2604: Incorrect magicka recalculation Bug #2721: Telekinesis extends interaction range where it shouldn't Bug #2981: When waiting, NPCs can go where they wouldn't go normally. Bug #3045: Esp files containing the letter '#' in the file name cannot be loaded on startup Bug #3071: Slowfall does not stop momentum when jumping Bug #3085: Plugins can not replace parent cell references with a cell reference of different type Bug #3145: Bug with AI Cliff Racer. He will not attack you, unless you put in front of him. Bug #3149: Editor: Weather tables were missing from regions Bug #3201: Netch shoots over your head Bug #3269: If you deselect a mod and try to load a save made inside a cell added by it, you end bellow the terrain in the grid 0/0 Bug #3286: Editor: Script editor tab width Bug #3329: Teleportation spells cause crash to desktop after build update from 0.37 to 0.38.0 Bug #3331: Editor: Start Scripts table: Adding a script doesn't refresh the list of Start Scripts and allows to add a single script multiple times Bug #3332: Editor: Scene view: Tool tips only occur when holding the left mouse button Bug #3340: ESS-Importer does not separate item stacks Bug #3342: Editor: Creation of pathgrids did not check if the pathgrid already existed Bug #3346: "Talked to PC" is always 0 for "Hello" dialogue Bug #3349: AITravel doesn't repeat Bug #3370: NPCs wandering to invalid locations after training Bug #3378: "StopCombat" command does not function in vanilla quest Bug #3384: Battle at Nchurdamz - Larienna Macrina does not stop combat after killing Hrelvesuu Bug #3388: Monster Respawn tied to Quicksave Bug #3390: Strange visual effect in Dagoth Ur's chamber Bug #3391: Inappropriate Blight weather behavior at end of main quest Bug #3394: Replaced dialogue inherits some of its old data Bug #3397: Actors that start the game dead always have the same death pose Bug #3401: Sirollus Saccus sells not glass arrows Bug #3402: Editor: Weapon data not being properly set Bug #3405: Mulvisic Othril will not use her chitin throwing stars Bug #3407: Tanisie Verethi will immediately detect the player Bug #3408: Improper behavior of ashmire particles Bug #3412: Ai Wander start time resets when saving/loading the game Bug #3416: 1st person and 3rd person camera isn't converted from .ess correctly Bug #3421: Idling long enough while paralyzed sometimes causes character to get stuck Bug #3423: Sleep interruption inside dungeons too agressive Bug #3424: Pickpocketing sometimes won't work Bug #3432: AiFollow / AiEscort durations handled incorrectly Bug #3434: Dead NPC's and Creatures still contribute to sneak skill increases Bug #3437: Weather-conditioned dialogue should not play in interiors Bug #3439: Effects cast by summon stick around after their death Bug #3440: Parallax maps looks weird Bug #3443: Class graphic for custom class should be Acrobat Bug #3446: OpenMW segfaults when using Atrayonis's "Anthology Solstheim: Tomb of the Snow Prince" mod Bug #3448: After dispelled, invisibility icon is still displayed Bug #3453: First couple of seconds of NPC speech is muted Bug #3455: Portable house mods lock player and npc movement up exiting house. Bug #3456: Equipping an item will undo dispel of constant effect invisibility Bug #3458: Constant effect restore health doesn't work during Wait Bug #3466: It is possible to stack multiple scroll effects of the same type Bug #3471: When two mods delete the same references, many references are not disabled by the engine. Bug #3473: 3rd person camera can be glitched Feature #1424: NPC "Face" function Feature #2974: Editor: Multiple Deletion of Subrecords Feature #3044: Editor: Render path grid v2 Feature #3362: Editor: Configurable key bindings Feature #3375: Make sun / moon reflections weather dependent Feature #3386: Editor: Edit pathgrid 0.39.0 ------ Bug #1384: Dark Brotherhood Assassin (and other scripted NPCs?) spawns beneath/inside solid objects Bug #1544: "Drop" drops equipped item in a separate stack Bug #1587: Collision detection glitches Bug #1629: Container UI locks up in Vivec at Jeanne's Bug #1771: Dark Brotherhood Assassin oddity in Eight Plates Bug #1827: Unhandled NiTextureEffect in ex_dwrv_ruin30.nif Bug #2089: When saving while swimming in water in an interior cell, you will be spawned under water on loading Bug #2295: Internal texture not showing, nipixeldata Bug #2363: Corpses don't disappear Bug #2369: Respawns should be timed individually Bug #2393: Сharacter is stuck in the tree Bug #2444: [Mod] NPCs from Animated Morrowind appears not using proper animations Bug #2467: Creatures do not respawn Bug #2515: Ghosts in Ibar-Dad spawn stuck in walls Bug #2610: FixMe script still needs to be implemented Bug #2689: Riekling raider pig constantly screams while running Bug #2719: Vivec don't put their hands on the knees with this replacer (Psymoniser Vivec God Replacement NPC Edition v1.0 Bug #2737: Camera shaking when side stepping around object Bug #2760: AI Combat Priority Problem - Use of restoration spell instead of attacking Bug #2806: Stack overflow in LocalScripts::getNext Bug #2807: Collision detection allows player to become stuck inside objects Bug #2814: Stairs to Marandus have improper collision Bug #2925: Ranes Ienith will not appear, breaking the Morag Tong and Thieves Guid questlines Bug #3024: Editor: Creator bar in startscript subview does not accept script ID drops Bug #3046: Sleep creature: Velk is spawned half-underground in the Thirr River Valley Bug #3080: Calling aifollow without operant in local script every frame causes mechanics to overheat + log Bug #3101: Regression: White guar does not move Bug #3108: Game Freeze after Killing Diseased Rat in Foreign Quarter Tomb Bug #3124: Bloodmoon Quest - Rite of the Wolf Giver (BM_WolfGiver) – Innocent victim won't turn werewolf Bug #3125: Improper dialogue window behavior when talking to creatures Bug #3130: Some wandering NPCs disappearing, cannot finish quests Bug #3132: Editor: GMST ID named sMake Enchantment is instead named sMake when making new game from scratch Bug #3133: OpenMW and the OpenCS are writting warnings about scripts that use the function GetDisabled. Bug #3135: Journal entry for The Pigrim's Path missing name Bug #3136: Dropped bow is displaced Bug #3140: Editor: OpenMW-CS fails to open newly converted and saved omwaddon file. Bug #3142: Duplicate Resist Magic message Bug #3143: Azura missing her head Bug #3146: Potion effect showing when ingredient effects are not known Bug #3155: When executing chop attack with a spear, hands turn partly invisible Bug #3161: Fast travel from Silt Strider or Boat Ride will break save files made afterwards Bug #3163: Editor: Objects dropped to scene do not always save Bug #3173: Game Crashes After Casting Recall Spell Bug #3174: Constant effect enchantments play spell animation on dead bodies Bug #3175: Spell effects do not wear down when caster dies Bug #3176: NPCs appearing randomly far away from towns Bug #3177: Submerged corpse floats ontop of water when it shouldn't (Widow Vabdas' Deed quest) Bug #3184: Bacola Closcius in Balmora, South Wall Cornerclub spams magic effects if attacked Bug #3207: Editor: New objects do not render Bug #3212: Arrow of Ranged Silence Bug #3213: Looking at Floor After Magical Transport Bug #3220: The number of remaining ingredients in the alchemy window doesn't go down when failing to brew a potion Bug #3222: Falling through the water in Vivec Bug #3223: Crash at the beginning with MOD (The Symphony) Bug #3228: Purple screen when leveling up. Bug #3233: Infinite disposition via MWDialogue::Filter::testDisposition() glitch Bug #3234: Armor mesh stuck on body in inventory menu Bug #3235: Unlike vanilla, OpenMW don't allow statics and activators cast effects on the player. Bug #3238: Not loading cells when using Poorly Placed Object Fix.esm Bug #3248: Editor: Using the "Next Script" and "Previous Script" buttons changes the record status to "Modified" Bug #3258: Woman biped skeleton Bug #3259: No alternating punches Bug #3262: Crash in class selection menu Bug #3279: Load menu: Deleting a savegame makes scroll bar jump to the top Bug #3326: Starting a new game, getting to class selection, then starting another new game temporarily assigns Acrobat class Bug #3327: Stuck in table after loading when character was sneaking when quicksave Feature #652: Editor: GMST verifier Feature #929: Editor: Info record verifier Feature #1279: Editor: Render cell border markers Feature #2482: Background cell loading and caching of loaded cells Feature #2484: Editor: point lighting Feature #2801: Support NIF bump map textures in osg Feature #2926: Editor: Optional line wrap in script editor wrap lines Feature #3000: Editor: Reimplement 3D scene camera system Feature #3035: Editor: Make scenes a drop target for referenceables Feature #3043: Editor: Render cell markers v2 Feature #3164: Editor: Instance Selection Menu Feature #3165: Editor: Instance editing mode - move sub mode Feature #3244: Allow changing water Level of Interiors behaving like exteriors Feature #3250: Editor: Use "Enter" key instead of clicking "Create" button to confirm ID input in Creator Bar Support #3179: Fatal error on startup 0.38.0 ------ Bug #1699: Guard will continuously run into mudcrab Bug #1934: Saw in Dome of Kasia doesnt harm the player Bug #1962: Rat floats when killed near the door Bug #1963: Kwama eggsacks pulse too fast Bug #2198: NPC voice sound source should be placed at their head Bug #2210: OpenMW installation wizard crashes... Bug #2211: Editor: handle DELE subrecord at the end of a record Bug #2413: ESM error Unknown subrecord in Grandmaster of Hlaalu Bug #2537: Bloodmoon quest Ristaag: Sattir not consistently dying, plot fails to advance; same with Grerid Bug #2697: "The Swimmer" moves away after leading you to underwater cave Bug #2724: Loading previous save duplicates containers and harvestables Bug #2769: Inventory doll - Cursor not respecting order of clothes Bug #2865: Scripts silently fail when moving NPCs between cells. Bug #2873: Starting a new game leads to CTD / Fatal Error Bug #2918: Editor: it's not possible to create an omwaddon containing a dot in the file name Bug #2933: Dialog box can't disable a npc if it is in another cell. (Rescue Madura Seran). Bug #2942: atronach sign behavior (spell absorption) changes when trying to receive a blessing at "shrine of tribunal" Bug #2952: Enchantment Merchant Items reshuffled EVERY time 'barter' is clicked Bug #2961: ESM Error: Unknown subrecord if Deus Ex Machina mod is loaded Bug #2972: Resurrecting the player via console does not work when health was 0 Bug #2986: Projectile weapons work underwater Bug #2988: "Expected subrecord" bugs showing up. Bug #2991: Can't use keywords in strings for MessageBox Bug #2993: Tribunal:The Shrine of the Dead – Urvel Dulni can't stop to follow the player. Bug #3008: NIFFile Error while loading meshes with a NiLODNode Bug #3010: Engine: items should sink to the ground when dropped under water Bug #3011: NIFFile Error while loading meshes with a NiPointLight Bug #3016: Engine: something wrong with scripting - crash / fatal error Bug #3020: Editor: verify does not check if given "item ID" (as content) for a "container" exists Bug #3026: [MOD: Julan Ashlander Companion] Dialogue not triggering correctly Bug #3028: Tooltips for Health, Magicka and Fatigue show in Options menu even when bars aren't visible Bug #3034: Item count check dialogue option doesn't work (Guards accept gold even if you don't have enough) Bug #3036: Owned tooltip color affects spell tooltips incorrrectly Bug #3037: Fatal error loading old ES_Landscape.esp in Store::search Bug #3038: Player sounds come from underneath Bug #3040: Execution of script failed: There is a message box already Bug #3047: [MOD: Julan Ashlander Companion] Scripts KS_Bedscript or KS_JulanNight not working as intended Bug #3048: Fatal Error Bug #3051: High field of view results in first person rendering glitches Bug #3053: Crash on new game at character class selection Bug #3058: Physiched sleeves aren't rendered correctly. Bug #3060: NPCs use wrong landing sound Bug #3062: Mod support regression: Andromeda's fast travel. Bug #3063: Missing Journal Textures without Tribunal and Bloodmoon installed Bug #3077: repeated aifollow causes the distance to stack Bug #3078: Creature Dialogues not showing when certain Function/Conditions are required. Bug #3082: Crash when entering Holamayan Monastery with mesh replacer installed Bug #3086: Party at Boro's House – Creature with Class don't talk under OpenMW Bug #3089: Dreamers spawn too soon Bug #3100: Certain controls erroneously work as a werewolf Bug #3102: Multiple unique soultrap spell sources clone souls. Bug #3105: Summoned creatures and objects disappear at midnight Bug #3112: gamecontrollerdb file creation with wrong extension Bug #3116: Dialogue Function "Same Race" is avoided Bug #3117: Dialogue Bug: Choice conditions are tested when not in a choice Bug #3118: Body Parts are not rendered when used in a pose. Bug #3122: NPC direction is reversed during sneak awareness check Feature #776: Sound effects from one direction don't necessarily affect both speakers in stereo Feature #858: Different fov settings for hands and the game world Feature #1176: Handle movement of objects between cells Feature #2507: Editor: choosing colors for syntax highlighting Feature #2867: Editor: hide script error list when there are no errors Feature #2885: Accept a file format other than nif Feature #2982: player->SetDelete 1 results in: PC can't move, menu can be opened Feature #2996: Editor: make it possible to preset the height of the script check area in a script view Feature #3014: Editor: Tooltips in 3D scene Feature #3064: Werewolf field of view Feature #3074: Quicksave indicator Task #287: const version of Ptr Task #2542: Editor: redo user settings system 0.37.0 ------ Bug #385: Light emitting objects have a too short distance of activation Bug #455: Animation doesn't resize creature's bounding box Bug #602: Only collision model is updated when modifying objects trough console Bug #639: Sky horizon at nighttime Bug #672: incorrect trajectory of the moons Bug #814: incorrect NPC width Bug #827: Inaccurate raycasting for dead actors Bug #996: Can see underwater clearly when at right height/angle Bug #1317: Erene Llenim in Seyda Neen does not walk around Bug #1330: Cliff racers fail to hit the player Bug #1366: Combat AI can't aim down (in order to hit small creatures) Bug #1511: View distance while under water is much too short Bug #1563: Terrain positioned incorrectly and appears to vibrate in far-out cells Bug #1612: First person models clip through walls Bug #1647: Crash switching from full screen to windows mode - D3D9 Bug #1650: No textures with directx on windows Bug #1730: Scripts names starting with digit(s) fail to compile Bug #1738: Socucius Ergalla's greetings are doubled during the tutorial Bug #1784: First person weapons always in the same position Bug #1813: Underwater flora lighting up entire area. Bug #1871: Handle controller extrapolation flags Bug #1921: Footstep frequency and velocity do not immediately update when speed attribute changes Bug #2001: OpenMW crashes on start with OpenGL 1.4 drivers Bug #2014: Antialiasing setting does nothing on Linux Bug #2037: Some enemies attack the air when spotting the player Bug #2052: NIF rotation matrices including scales are not supported Bug #2062: Crank in Old Mournhold: Forgotten Sewer turns about the wrong axis Bug #2111: Raindrops in front of fire look wrong Bug #2140: [OpenGL] Water effects, flames and parts of creatures solid black when observed through brazier flame Bug #2147: Trueflame and Hopesfire flame effects not properly aligned with blade Bug #2148: Verminous fabricants have little coloured box beneath their feet Bug #2149: Sparks in Clockwork City should bounce off the floor Bug #2151: Clockwork City dicer trap doesn't activate when you're too close Bug #2186: Mini map contains scrambled pixels that cause the mini map to flicker Bug #2187: NIF file with more than 255 NiBillboardNodes does not load Bug #2191: Editor: Crash when trying to view cell in render view in OpenCS Bug #2270: Objects flicker transparently Bug #2280: Latest 32bit windows build of openmw runns out of vram Bug #2281: NPCs don't scream when they die Bug #2286: Jumping animation restarts when equipping mid-air Bug #2287: Weapon idle animation stops when turning Bug #2355: Light spell doesn't work in 1st person view Bug #2362: Lantern glas opaque to flame effect from certain viewing angles Bug #2364: Light spells are not as bright as in Morrowind Bug #2383: Remove the alpha testing override list Bug #2436: Crash on entering cell "Tower of Tel Fyr, Hall of Fyr" Bug #2457: Player followers should not report crimes Bug #2458: crash in some fighting situations Bug #2464: Hiding an emitter node should make that emitter stop firing particles Bug #2466: Can't load a save created with OpenMW-0.35.0-win64 Bug #2468: music from title screen continues after loading savegame Bug #2494: Map not consistent between saves Bug #2504: Dialog scroll should always start at the top Bug #2506: Editor: Undo/Redo shortcuts do not work in script editor Bug #2513: Mannequins in mods appear as dead bodies Bug #2524: Editor: TopicInfo "custom" condition section is missing Bug #2540: Editor: search and verification result table can not be sorted by clicking on the column names Bug #2543: Editor: there is a problem with spell effects Bug #2544: Editor fails to save NPC information correctly. Bug #2545: Editor: delete record in Objects (referenceables) table messes up data Bug #2546: Editor: race base attributes and skill boni are not displayed, thus not editable Bug #2547: Editor: some NPC data is not displayed, thus not editable Bug #2551: Editor: missing data in cell definition Bug #2553: Editor: value filter does not work for float values Bug #2555: Editor: undo leaves the record status as Modified Bug #2559: Make Detect Enchantment marks appear on top of the player arrow Bug #2563: position consoling npc doesn't work without cell reload Bug #2564: Editor: Closing a subview from code does not clean up properly and will lead to crash on opening the next subview Bug #2568: Editor: Setting default window size is ignored Bug #2569: Editor: saving from an esp to omwaddon file results in data loss for TopicInfo Bug #2575: Editor: Deleted record (with Added (ModifiedOnly) status) remains in the Dialog SubView Bug #2576: Editor: Editor doesn't scroll to a newly opened subview, when ScrollBar Only mode is active Bug #2578: Editor: changing Level or Reputation of an NPC crashes the editor Bug #2579: Editor: filters not updated when adding or cloning records Bug #2580: Editor: omwaddon makes OpenMW crash Bug #2581: Editor: focus problems in edit subviews single- and multiline input fields Bug #2582: Editor: object verifier should check for non-existing scripts being referenced Bug #2583: Editor: applying filter to TopicInfo on mods that have added dialouge makes the Editor crash Bug #2586: Editor: some dialogue only editable items do not refresh after undo Bug #2588: Editor: Cancel button exits program Bug #2589: Editor: Regions table - mapcolor does not change correctly Bug #2591: Placeatme - spurious 5th parameter raises error Bug #2593: COC command prints multiple times when GUI is hidden Bug #2598: Editor: scene view of instances has to be zoomed out to displaying something - center camera instance please Bug #2607: water behind an invisible NPC becomes invisible as well Bug #2611: Editor: Sort problem in Objects table when few nested rows are added Bug #2621: crash when a creature has no model Bug #2624: Editor: missing columns in tables Bug #2627: Character sheet doesn't properly update when backing out of CharGen Bug #2642: Editor: endif without if - is not reported as error when "verify" was executed Bug #2644: Editor: rebuild the list of available content files when opening the open/new dialogues Bug #2656: OpenMW & OpenMW-CS: setting "Flies" flag for ghosts has no effect Bug #2659: OpenMW & OpenMW-CS: savegame load fail due to script attached to NPCs Bug #2668: Editor: reputation value in the input field is not stored Bug #2696: Horkers use land idle animations under water Bug #2705: Editor: Sort by Record Type (Objects table) is incorrect Bug #2711: Map notes on an exterior cell that shows up with a map marker on the world map do not show up in the tooltip for that cell's marker on the world map Bug #2714: Editor: Can't reorder rows with the same topic in different letter case Bug #2720: Head tracking for creatures not implemented Bug #2722: Alchemy should only include effects shared by at least 2 ingredients Bug #2723: "ori" console command is not working Bug #2726: Ashlanders in front of Ghostgate start wandering around Bug #2727: ESM writer does not handle encoding when saving the TES3 header Bug #2728: Editor: Incorrect position of an added row in Info tables Bug #2731: Editor: Deleting a record triggers a Qt warning Bug #2733: Editor: Undo doesn't restore the Modified status of a record when a nested data is changed Bug #2734: Editor: The Search doesn't work Bug #2738: Additive moon blending Bug #2746: NIF node names should be case insensitive Bug #2752: Fog depth/density not handled correctly Bug #2753: Editor: line edit in dialogue subview tables shows after a single click Bug #2755: Combat AI changes target too frequently Bug #2761: Can't attack during block animations Bug #2764: Player doesn't raise arm in 3rd person for weathertype 9 Bug #2768: Current screen resolution not selected in options when starting OpenMW Bug #2773: Editor: Deleted scripts are editable Bug #2776: ordinators still think I'm wearing their helm even though Khajiit and argonians can't Bug #2779: Slider bars continue to move if you don't release mouse button Bug #2781: sleep interruption is a little off (is this an added feature?) Bug #2782: erroneously able to ready weapon/magic (+sheathe weapon/magic) while paralyzed Bug #2785: Editor: Incorrect GMSTs for newly created omwgame files Bug #2786: Kwama Queen head is inverted under OpenMW Bug #2788: additem and removeitem incorrect gold behavior Bug #2790: --start doesn't trace down Bug #2791: Editor: Listed attributes and skill should not be based on number of NPC objects. Bug #2792: glitched merchantile/infinite free items Bug #2794: Need to ignore quotes in names of script function Bug #2797: Editor: Crash when removing the first row in a nested table Bug #2800: Show an error message when S3TC support is missing Bug #2811: Targetted Open spell effect persists. Bug #2819: Editor: bodypart's race filter not displayed correctly Bug #2820: Editor: table sorting is inverted Bug #2821: Editor: undo/redo command labels are incorrect Bug #2826: locking beds that have been locked via magic psuedo-freezes the game Bug #2830: Script compiler does not accept IDs as instruction/functions arguments if the ID is also a keyword Bug #2832: Cell names are not localized on the world map Bug #2833: [cosmetic] Players swimming at water's surface are slightly too low. Bug #2840: Save/load menu is not entirely localized Bug #2853: [exploit/bug] disintegrate weapon incorrectly applying to lockpicks, probes. creates unbreakable lockpicks Bug #2855: Mouse wheel in journal is not disabled by "Options" panel. Bug #2856: Heart of Lorkhan doesn't visually respond to attacks Bug #2863: Inventory highlights wrong category after load Bug #2864: Illuminated Order 1.0c Bug – The teleport amulet is not placed in the PC inventory. Bug #2866: Editor: use checkbox instead of combobox for boolean values Bug #2875: special cases of fSleepRandMod not behaving properly. Bug #2878: Editor: Verify reports "creature has non-positive level" but there is no level setting Bug #2879: Editor: entered value of field "Buys *" is not saved for a creature Bug #2880: OpenMW & OpenMW-CS: having a scale value of 0.000 makes the game laggy Bug #2882: Freeze when entering cell "Guild of Fighters (Ald'ruhn)" after dropping some items inside Bug #2883: game not playable if mod providing a spell is removed but the list of known spells still contains it Bug #2884: NPC chats about wrong player race Bug #2886: Adding custom races breaks existing numbering of PcRace Bug #2888: Editor: value entered in "AI Wander Idle" is not kept Bug #2889: Editor: creatures made with the CS (not cloned) are always dead Bug #2890: Editor: can't make NPC say a specific "Hello" voice-dialouge Bug #2893: Editor: making a creature use textual dialogue doesn't work. Bug #2901: Editor: gold for trading can not be set for creatures Bug #2907: looking from uderwater part of the PC that is below the surface looks like it would be above the water Bug #2914: Magicka not recalculated on character generation Bug #2915: When paralyzed, you can still enter and exit sneak Bug #2917: chameleon does not work for creatures Bug #2927: Editor: in the automatic script checker local variable caches are not invalidated/updated on modifications of other scripts Bug #2930: Editor: AIWander Idle can not be set for a creature Bug #2932: Editor: you can add rows to "Creature Attack" but you can not enter values Bug #2938: Editor: Can't add a start script. Bug #2944: Spell chance for power to show as 0 on hud when used Bug #2953: Editor: rightclick in an empty place in the menu bar shows an unnamed checkbox Bug #2956: Editor: freezes while editing Filter Bug #2959: space character in field enchantment (of an amulet) prevents rendering of surroundings Bug #2962: OpenMW: Assertion `it != invStore.end()' failed Bug #2964: Recursive script execution can corrupt script runtime data Bug #2973: Editor: placing a chest in the game world and activating it heavily blurrs the character portrait Bug #2978: Editor: Cannot edit alchemy ingredient properties Bug #2980: Editor: Attribute and Skill can be selected for spells that do not require these parameters, leading to non-functional spells Bug #2990: Compiling a script with warning mode 2 and enabled error downgrading leads to infinite recursion Bug #2992: [Mod: Great House Dagoth] Killing Dagoth Gares freezes the game Bug #3007: PlaceItem takes radians instead of degrees + angle reliability Feature #706: Editor: Script Editor enhancements Feature #872: Editor: Colour values in tables Feature #880: Editor: ID auto-complete Feature #928: Editor: Partial sorting in info tables Feature #942: Editor: Dialogue for editing/viewing content file meta information Feature #1057: NiStencilProperty Feature #1278: Editor: Mouse picking in worldspace widget Feature #1280: Editor: Cell border arrows Feature #1401: Editor: Cloning enhancements Feature #1463: Editor: Fine grained configuration of extended revert/delete commands Feature #1591: Editor: Make fields in creation bar drop targets where applicable Feature #1998: Editor: Magic effect record verifier Feature #1999: Editor Sound Gen record verifier Feature #2000: Editor: Pathgrid record verifier Feature #2528: Game Time Tracker Feature #2534: Editor: global search does not auomatically focus the search input field Feature #2535: OpenMW: allow comments in openmw.cfg Feature #2541: Editor: provide a go to the very bottom button for TopicInfo and JournalInfo Feature #2549: Editor: add a horizontal slider to scroll between opened tables Feature #2558: Editor: provide a shortcut for closing the subview that has the focus Feature #2565: Editor: add context menu for dialogue sub view fields with an item matching "Edit 'x'" from the table subview context menu Feature #2585: Editor: Ignore mouse wheel input for numeric values unless the respective widget has the focus Feature #2620: Editor: make the verify-view refreshable Feature #2622: Editor: Make double click behaviour in result tables configurable (see ID tables) Feature #2717: Editor: Add severity column to report tables Feature #2729: Editor: Various dialogue button bar improvements Feature #2739: Profiling overlay Feature #2740: Resource manager optimizations Feature #2741: Make NIF files into proper resources Feature #2742: Use the skinning data in NIF files as-is Feature #2743: Small feature culling Feature #2744: Configurable near clip distance Feature #2745: GUI scaling option Feature #2747: Support anonymous textures Feature #2749: Loading screen optimizations Feature #2751: Character preview optimization Feature #2804: Editor: Merge Tool Feature #2818: Editor: allow copying a record ID to the clipboard Feature #2946: Editor: add script line number in results of search Feature #2963: Editor: Mouse button bindings in 3D scene Feature #2983: Sun Glare fader Feature #2999: Scaling of journal and books Task #2665: Support building with Qt5 Task #2725: Editor: Remove Display_YesNo Task #2730: Replace hardcoded column numbers in SimpleDialogueSubView/DialogueSubView Task #2750: Bullet shape instancing optimization Task #2793: Replace grid size setting with half grid size setting Task #3003: Support FFMPEG 2.9 (Debian request) 0.36.1 ------ Bug #2590: Start scripts not added correctly 0.36.0 ------ Bug #923: Editor: Operations-Multithreading is broken Bug #1317: Erene Llenim in Seyda Neen does not walk around Bug #1405: Water rendering glitch near Seyda Neen lighthouse Bug #1621: "Error Detecting Morrowind Installation" in the default directory Bug #2216: Creating a clone of the player stops you moving. Bug #2387: Casting bound weapon spell doesn't switch to "ready weapon" mode Bug #2407: Default to (0, 0) when "unknown cell" is encountered. Bug #2411: enchanted item charges don't update/refresh if spell list window is pinned open Bug #2428: Editor: cloning / creating new container class results in invalid omwaddon file - openmw-0.35 Bug #2429: Editor - cloning omits some values or sets different values than the original has Bug #2430: NPC with negative fatigue don't fall (LGNPC Vivec, Foreign Quarter v2.21) Bug #2432: Error on startup with Uvirith's Legacy enabled Bug #2435: Editor: changed entries in the objects window are not shown as such Bug #2437: Editor: changing an entry of a container/NPC/clothing/ingredient/globals will not be saved in the omwaddon file Bug #2447: Editor doesn't save terrain information Bug #2451: Editor not listing files with accented characters Bug #2453: Chargen: sex, race and hair sliders not initialized properly Bug #2459: Minor terrain clipping through statics due to difference in triangle alignment Bug #2461: Invisible sound mark has collision in Sandus Ancestral Tomb Bug #2465: tainted gold stack Bug #2475: cumulative stacks of 100 point fortify skill speechcraft boosts do not apply correctly Bug #2498: Editor: crash when issuing undo command after the table subview is closed Bug #2500: Editor: object table - can't undo delete record Bug #2518: OpenMW detect spell returns false positives Bug #2521: NPCs don't react to stealing when inventory menu is open. Bug #2525: Can't click on red dialogue choice [rise of house telvanni][60fffec] Bug #2530: GetSpellEffects not working as in vanilla Bug #2557: Crash on first launch after choosing "Run installation wizard" Feature #139: Editor: Global Search & Replace Feature #1219: Editor: Add dialogue mode only columns Feature #2024: Hotkey for hand to hand (i.e. unequip any weapon) Feature #2119: "Always Sneak" key bind Feature #2262: Editor: Handle moved instances Feature #2425: Editor: Add start script table Feature #2426: Editor: start script record verifier Feature #2480: Launcher: Multiselect entries in the Data Files list Feature #2505: Editor: optionally show a line number column in the script editor Feature #2512: Editor: Offer use of monospace fonts in the script editor as an option Feature #2514: Editor: focus on ID input field on clone/add Feature #2519: it is not possible to change icons that appear on the map after casting the Detect spells Task #2460: OS X: Use Application Support directory as user data path Task #2516: Editor: Change References / Referenceables terminology 0.35.1 ------ Bug #781: incorrect trajectory of the sun Bug #1079: Wrong starting position in "Character Stuff Wonderland" Bug #1443: Repetitive taking of a stolen object is repetitively considered as a crime Bug #1533: Divine Intervention goes to the wrong place. Bug #1714: No visual indicator for time passed during training Bug #1916: Telekinesis does not allow safe opening of traps Bug #2227: Editor: addon file name inconsistency Bug #2271: Player can melee enemies from water with impunity Bug #2275: Objects with bigger scale move further using Move script Bug #2285: Aryon's Dominator enchantment does not work properly Bug #2290: No punishment for stealing gold from owned containers Bug #2328: Launcher does not respond to Ctrl+C Bug #2334: Drag-and-drop on a content file in the launcher creates duplicate items Bug #2338: Arrows reclaimed from corpses do not stack sometimes Bug #2344: Launcher - Settings importer running correctly? Bug #2346: Launcher - Importing plugins into content list screws up the load order Bug #2348: Mod: H.E.L.L.U.V.A. Handy Holdables does not appear in the content list Bug #2353: Detect Animal detects dead creatures Bug #2354: Cmake does not respect LIB_SUFFIX Bug #2356: Active magic set inactive when switching magic items Bug #2361: ERROR: ESM Error: Previous record contains unread bytes Bug #2382: Switching spells with "next spell" or "previous spell" while holding shift promps delete spell dialog Bug #2388: Regression: Can't toggle map on/off Bug #2392: MOD Shrines - Restore Health and Cancel Options adds 100 health points Bug #2394: List of Data Files tab in openmw-laucher needs to show all content files. Bug #2402: Editor: skills saved incorrectly Bug #2408: Equipping a constant effect Restore Health/Magicka/Fatigue item will permanently boost the stat it's restoring Bug #2415: It is now possible to fall off the prison ship into the water when starting a new game Bug #2419: MOD MCA crash to desktop Bug #2420: Game crashes when character enters a certain area Bug #2421: infinite loop when using cycle weapon without having a weapon Feature #2221: Cannot dress dead NPCs Feature #2349: Check CMake sets correct MSVC compiler settings for release build. Feature #2397: Set default values for global mandatory records. Feature #2412: Basic joystick support 0.35.0 ------ Bug #244: Clipping/static in relation to the ghostgate/fence sound. Bug #531: Missing transparent menu items Bug #811: Content Lists in openmw.cfg are overwritten Bug #925: OpenCS doesn't launch because it thinks its already started Bug #969: Water shader strange behaviour on AMD card Bug #1049: Partially highlighted word in dialogue may cause incorrect line break Bug #1069: omwlauncher.exe crashes due to file lock Bug #1192: It is possible to jump on top of hostile creatures in combat Bug #1342: Loud ambient sounds Bug #1431: Creatures can climb the player Bug #1605: Guard in CharGen doesn't turn around to face you when reaching stairs Bug #1624: Moon edges don't transition properly Bug #1634: Items dropped by PC have collision Bug #1637: Weird NPC behaviour in Vivec, Hlaalu Ancestral Vaults? Bug #1638: Cannot climb staircases Bug #1648: Enchanted equipment badly handled at game reload Bug #1663: Crash when casting spell at enemy near you Bug #1683: Scale doesn't apply to animated collision nodes Bug #1702: Active enchanted item forgotten Bug #1730: Scripts names starting with digit(s) fail to compile Bug #1743: Moons are transparent Bug #1745: Shadows crash: Assertion `mEffects.empty()' failed. Bug #1785: Can't equip two-handed weapon and shield Bug #1809: Player falls too easily Bug #1825: Sword of Perithia can´t run in OpenMW Bug #1899: The launcher resets any alterations you´ve made in the mod list order, Bug #1964: Idle voices/dialogs not triggered correctly Bug #1980: Please, change default click behavior in OpenMW Launchers Data Files list Bug #1984: Vampire corpses standing up when looting the first item Bug #1985: Calm spell does nothing Bug #1986: Spell name lights up on mouseover but spell cost does not Bug #1989: Tooltip still shown when menu toggled off Bug #2010: Raindrops Displayed While Underwater Bug #2023: Walking into plants causes massive framedrop Bug #2031: [MOD: Shrines - Restore Health and Cancel Options]: Restore health option doesn't work Bug #2039: Lake Fjalding pillar of fire not rendered Bug #2040: AI_follow should stop further from the target Bug #2076: Slaughterfish AI Bug #2077: Direction of long jump can be changed much more than it is possible in vanilla Bug #2078: error during rendering: Object '' not found (const) Bug #2105: Lockpicking causes screen sync glitch Bug #2113: [MOD: Julan Ashlander Companion] Julan does not act correctly within the Ghostfence. Bug #2123: Window glow mod: Collision issues Bug #2133: Missing collision for bridges in Balmora when using Morrowind Rebirth 2.81 Bug #2135: Casting a summon spell while the summon is active does not reset the summon. Bug #2144: Changing equipment will unequip drawn arrows/bolts Bug #2169: Yellow on faces when using opengl renderer and mods from overhaul on windows Bug #2175: Pathgrid mods do not overwrite the existing pathgrid Bug #2176: Morrowind -Russian localization end add-on ChaosHeart. Error in framelistener;object ;frenzying toush; not found Bug #2181: Mod Morrowind crafting merchants die. Bug #2182: mods changing skill progression double the bonus for class specialization Bug #2183: Editor: Skills "use value" only allows integer between 0 and 99 Bug #2184: Animated Morrowind Expanded produces an error on Open MW Launch Bug #2185: Conditional Operator formats Bug #2193: Quest: Gateway Ghost Bug #2194: Cannot summon multiples of the same creature Bug #2195: Pathgrid in the (0,0) exterior cell not loaded Bug #2200: Outdoor NPCs can stray away and keep walking into a wall Bug #2201: Creatures do not receive fall damage Bug #2202: The enchantment the item can hold is calculated incorrectly Bug #2203: Having the mod Living Cities of Vvardenfall running causes the game world to fail to load after leaving the prison ship Bug #2204: Abot's Water Life - Book rendered incorrectly Bug #2205: sound_waterfall script no longer compiles Bug #2206: Dialogue script fails to compile (extra .) Bug #2207: Script using – instead of - character does not compile Bug #2208: Failing dialogue scripts in french Morrowind.esm Bug #2214: LGNPC Vivec Redoran 1.62 and The King Rat (Size and inventory Issues) Bug #2215: Beast races can use enchanted boots Bug #2218: Incorrect names body parts in 3D models for open helmet with skinning Bug #2219: Orcs in Ghorak Manor in Caldera don't attack if you pick their pockets. Bug #2220: Chargen race preview head incorrect orientation Bug #2223: Reseting rock falling animation Bug #2224: Fortify Attribute effects do not stack when Spellmaking. Bug #2226: OpenCS pseudo-crash Bug #2230: segfaulting when entering Ald'ruhn with a specific mod: "fermeture la nuit" (closed by night) Bug #2233: Area effect spells on touch do not have the area effect Bug #2234: Dwarven Crossbow clips through the ground when dropped Bug #2235: class SettingsBase<> reverses the order of entries with multiple keys. Bug #2236: Weird two handed longsword + torch interaction Bug #2237: Shooting arrows while sneaking do not agro Bug #2238: Bipedal creatures not using weapons are not handled properly Bug #2245: Incorrect topic highlighting in HT_SpyBaladas quest Bug #2252: Tab completion incomplete for places using COC from the console. Bug #2255: Camera reverts to first person on load Bug #2259: enhancement: the save/load progress bar is not very progressive Bug #2263: TogglePOV can not be bound to Alt key Bug #2267: dialogue disabling via mod Bug #2268: Highlighting Files with load order problems in Data Files tab of Launcher Bug #2276: [Mod]ShotN issues with Karthwasten Bug #2283: Count argument for PlaceAt functions not working Bug #2284: Local map notes should be visible on door marker leading to the cell with the note Bug #2293: There is a graphical glitch at the end of the spell's animation in 3rd Person (looking over the shoulder) view Bug #2294: When using Skyrim UI Overhaul, the tops of pinnable menus are invisible Bug #2302: Random leveled items repeat way too often in a single dungeon Bug #2306: Enchanted arrows should not be retrievable from corpses Bug #2308: No sound effect when drawing the next throwing knife Bug #2309: Guards chase see the player character even if they're invisible Bug #2319: Inverted controls and other issues after becoming a vampire Bug #2324: Spells cast when crossing cell border are imprinted on the local map Bug #2330: Actors with Drain Health effect retain health after dying Bug #2331: tgm (god mode) won't allow the player to cast spells if the player doesn't have enough mana Bug #2332: Error in framelistener: Need a skeleton to attach the arrow to Feature #114: ess-Importer Feature #504: Editor: Delete selected rows from result windows Feature #1024: Addition of remaining equipping hotkeys Feature #1067: Handle NIF interpolation type 4 (XYZ_ROTATION_KEY) Feature #1125: AI fast-forward Feature #1228: Drowning while knocked out Feature #1325: Editor: Opening window and User Settings window cleanup Feature #1537: Ability to change the grid size from 3x3 to 5x5 (or more with good pc) Feature #1546: Leveled list script functions Feature #1659: Test dialogue scripts in --script-all Feature #1720: NPC lookAt controller Feature #2178: Load initial particle system state from NIF files Feature #2197: Editor: When clicking on a script error in the report window set cursor in script editor to the respective line/column Feature #2261: Warn when loading save games with mod mismatch Feature #2313: ess-Importer: convert global map exploration overlay Feature #2318: Add commandline option to load a save game Task #810: Rename "profile" to "content list" Task #2196: Label local/global openmw.cfg files via comments 0.34.0 ------ Bug #904: omwlauncher doesn't allow installing Tribunal and Bloodmoon if only MW is installed Bug #986: Launcher: renaming profile names is broken Bug #1061: "Browse to CD..." launcher crash Bug #1135: Launcher crashes if user does not have write permission Bug #1231: Current installer in launcher does not correctly import russian Morrowind.ini settings from setup.inx Bug #1288: Fix the Alignment of the Resolution Combobox Bug #1343: BIK videos occasionally out of sync with audio Bug #1684: Morrowind Grass Mod graphical glitches Bug #1734: NPC in fight with invisible/sneaking player Bug #1982: Long class names are cut off in the UI Bug #2012: Editor: OpenCS script compiler sometimes fails to find IDs Bug #2015: Running while levitating does not affect speed but still drains fatigue Bug #2018: OpenMW don´t reset modified cells to vanilla when a plugin is deselected and don´t apply changes to cells already visited. Bug #2045: ToggleMenus command should close dialogue windows Bug #2046: Crash: light_de_streetlight_01_223 Bug #2047: Buglamp tooltip minor correction Bug #2050: Roobrush floating texture bits Bug #2053: Slaves react negatively to PC picking up slave's bracers Bug #2055: Dremora corpses use the wrong model Bug #2056: Mansilamat Vabdas's corpse is floating in the water Bug #2057: "Quest: Larius Varro Tells A Little Story": Bounty not completely removed after finishing quest Bug #2059: Silenced enemies try to cast spells anyway Bug #2060: Editor: Special case implementation for top level window with single sub-window should be optional Bug #2061: Editor: SubView closing that is not directly triggered by the user isn't handled properly Bug #2063: Tribunal: Quest 'The Warlords' doesn't work Bug #2064: Sneak attack on hostiles causes bounty Bug #2065: Editor: Qt signal-slot error when closing a dialogue subview Bug #2070: Loading ESP in OpenMW works but fails in OpenCS Bug #2071: CTD in 0.33 Bug #2073: Storm atronach animation stops now and then Bug #2075: Molag Amur Region, Map shows water on solid ground Bug #2080: game won't work with fair magicka regen Bug #2082: NPCs appear frozen or switched off after leaving and quickly reentering a cell Bug #2088: OpenMW is unable to play OGG files. Bug #2093: Darth Gares talks to you in Ilunibi even when he's not there, screwing up the Main Quests Bug #2095: Coordinate and rotation editing in the Reference table does not work. Bug #2096: Some overflow fun and bartering exploit Bug #2098: [D3D] Game crash on maximize Bug #2099: Activate, player seems not to work Bug #2104: Only labels are sensitive in buttons Bug #2107: "Slowfall" effect is too weak Bug #2114: OpenCS doesn't load an ESP file full of errors even though Vanilla MW Construction Set can Bug #2117: Crash when encountering bandits on opposite side of river from the egg mine south of Balmora Bug #2124: [Mod: Baldurians Transparent Glass Amor] Armor above head Bug #2125: Unnamed NiNodes in weapons problem in First Person Bug #2126: Dirty dialog script in tribunal.esm causing bug in Tribunal MQ Bug #2128: Crash when picking character's face Bug #2129: Disable the third-person zoom feature by default Bug #2130: Ash storm particles shown too long during transition to clear sky Bug #2137: Editor: exception caused by following the Creature column of a SoundGen record Bug #2139: Mouse movement should be ignored during intro video Bug #2143: Editor: Saving is broken Bug #2145: OpenMW - crash while exiting x64 debug build Bug #2152: You can attack Almalexia during her final monologue Bug #2154: Visual effects behave weirdly after loading/taking a screenshot Bug #2155: Vivec has too little magicka Bug #2156: Azura's spirit fades away too fast Bug #2158: [Mod]Julan Ashlander Companion 2.0: Negative magicka Bug #2161: Editor: combat/magic/stealth values of creature not displayed correctly Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of a magic weapon. Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. Bug #2170: Mods using conversations to update PC inconsistant Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources Bug #2212: Crash or unexpected behavior while closing OpenCS cell render window on OS X Feature #238: Add UI to run INI-importer from the launcher Feature #854: Editor: Add user setting to show status bar Feature #987: Launcher: first launch instructions for CD need to be more explicit Feature #1232: There is no way to set the "encoding" option using launcher UI. Feature #1281: Editor: Render cell markers Feature #1918: Editor: Functionality for Double-Clicking in Tables Feature #1966: Editor: User Settings dialogue grouping/labelling/tooltips Feature #2097: Editor: Edit position of references in 3D scene Feature #2121: Editor: Add edit mode button to scene toolbar Task #1965: Editor: Improve layout of user settings dialogue 0.33.1 ------ Bug #2108: OpenCS fails to build 0.33.0 ------ Bug #371: If console assigned to ` (probably to any symbolic key), "`" symbol will be added to console every time it closed Bug #1148: Some books'/scrolls' contents are displayed incorrectly Bug #1290: Editor: status bar is not updated when record filter is changed Bug #1292: Editor: Documents are not removed on closing the last view Bug #1301: Editor: File->Exit only checks the document it was issued from. Bug #1353: Bluetooth on with no speaker connected results in significantly longer initial load times Bug #1436: NPCs react from too far distance Bug #1472: PC is placed on top of following NPC when changing cell Bug #1487: Tall PC can get stuck in staircases Bug #1565: Editor: Subviews are deleted on shutdown instead when they are closed Bug #1623: Door marker on Ghorak Manor's balcony makes PC stuck Bug #1633: Loaddoor to Sadrith Mora, Telvanni Council House spawns PC in the air Bug #1655: Use Appropriate Application Icons on Windows Bug #1679: Tribunal expansion, Meryn Othralas the backstage manager in the theatre group in Mournhold in the great bazaar district is floating a good feet above the ground. Bug #1705: Rain is broken in third person Bug #1706: Thunder and lighting still occurs while the game is paused during the rain Bug #1708: No long jumping Bug #1710: Editor: ReferenceableID drag to references record filter field creates incorrect filter Bug #1712: Rest on Water Bug #1715: "Cancel" button is not always on the same side of menu Bug #1725: Editor: content file can be opened multiple times from the same dialogue Bug #1730: [MOD: Less Generic Nerevarine] Compile failure attempting to enter the Corprusarium. Bug #1733: Unhandled ffmpeg sample formats Bug #1735: Editor: "Edit Record" context menu button not opening subview for journal infos Bug #1750: Editor: record edits result in duplicate entries Bug #1789: Editor: Some characters cannot be used in addon name Bug #1803: Resizing the map does not keep the pre-resize center at the post-resize center Bug #1821: Recovering Cloudcleaver quest: attacking Sosia is considered a crime when you side with Hlormar Bug #1838: Editor: Preferences window appears off screen Bug #1839: Editor: Record filter title should be moved two pixels to the right Bug #1849: Subrecord error in MAO_Containers Bug #1854: Knocked-out actors don't fully act knocked out Bug #1855: "Soul trapped" sound doesn't play Bug #1857: Missing sound effect for enchanted items with empty charge Bug #1859: Missing console command: ResetActors (RA) Bug #1861: Vendor category "MagicItems" is unhandled Bug #1862: Launcher doesn't start if a file listed in launcher.cfg has correct name but wrong capitalization Bug #1864: Editor: Region field for cell record in dialogue subview not working Bug #1869: Editor: Change label "Musics" to "Music" Bug #1870: Goblins killed while knocked down remain in knockdown-pose Bug #1874: CellChanged events should not trigger when crossing exterior cell border Bug #1877: Spriggans killed instantly if hit while regening Bug #1878: Magic Menu text not un-highlighting correctly when going from spell to item as active magic Bug #1881: Stuck in ceiling when entering castle karstaags tower Bug #1884: Unlit torches still produce a burning sound Bug #1885: Can type text in price field in barter window Bug #1887: Equipped items do not emit sounds Bug #1889: draugr lord aesliip will attack you and remain non-hostile Bug #1892: Guard asks player to pay bounty of 0 gold Bug #1895: getdistance should only return max float if ref and target are in different worldspaces Bug #1896: Crash Report Bug #1897: Conjured Equipment cant be re-equipped if removed Bug #1898: Only Gidar Verothan follows you during establish the mine quest Bug #1900: Black screen when you open the door and breath underwater Bug #1904: Crash on casting recall spell Bug #1906: Bound item checks should use the GMSTs Bug #1907: Bugged door. Mournhold, The Winged Guar Bug #1908: Crime reported for attacking Drathas Nerus's henchmen while they attack Dilborn Bug #1909: Weird Quest Flow Infidelities quest Bug #1910: Follower fighting with gone npc Bug #1911: Npcs will drown themselves Bug #1912: World map arrow stays static when inside a building Bug #1920: Ulyne Henim disappears when game is loaded inside Vas Bug #1922: alchemy-> potion of paralyze Bug #1923: "levitation magic cannot be used here" shows outside of tribunal Bug #1927: AI prefer melee over magic. Bug #1929: Tamriel Rebuilt: Named cells that lie within the overlap with Morrowind.esm are not shown Bug #1932: BTB - Spells 14.1 magic effects don´t overwrite the Vanilla ones but are added Bug #1935: Stacks of items are worth more when sold individually Bug #1940: Launcher does not list addon files if base game file is renamed to a different case Bug #1946: Mod "Tel Nechim - moved" breaks savegames Bug #1947: Buying/Selling price doesn't properly affect the growth of mercantile skill Bug #1950: followers from east empire company quest will fight each other if combat happens with anything Bug #1958: Journal can be scrolled indefinitely with a mouse wheel Bug #1959: Follower not leaving party on quest end Bug #1960: Key bindings not always saved correctly Bug #1961: Spell merchants selling racial bonus spells Bug #1967: segmentation fault on load saves Bug #1968: Jump sounds are not controlled by footsteps slider, sound weird compared to footsteps Bug #1970: PC suffers silently when taking damage from lava Bug #1971: Dwarven Sceptre collision area is not removed after killing one Bug #1974: Dalin/Daris Norvayne follows player indefinitely Bug #1975: East Empire Company faction rank breaks during Raven Rock questline Bug #1979: 0 strength = permanently over encumbered Bug #1993: Shrine blessing in Maar Gan doesn't work Bug #2008: Enchanted items do not recharge Bug #2011: Editor: OpenCS script compiler doesn't handle member variable access properly Bug #2016: Dagoth Ur already dead in Facility Cavern Bug #2017: Fighters Guild Quest: The Code Book - dialogue loop when UMP is loaded. Bug #2019: Animation of 'Correct UV Mudcrabs' broken Bug #2022: Alchemy window - Removing ingredient doesn't remove the number of ingredients Bug #2025: Missing mouse-over text for non affordable items Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall" Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding Bug #3066: Editor doesn't check if IDs and other strings are longer than their hardcoded field length Feature #471: Editor: Special case implementation for top-level window with single sub-window Feature #472: Editor: Sub-Window re-use settings Feature #704: Font colors import from fallback settings Feature #879: Editor: Open sub-views in a new top-level window Feature #932: Editor: magic effect table Feature #937: Editor: Path Grid table Feature #938: Editor: Sound Gen table Feature #1117: Death and LevelUp music Feature #1226: Editor: Request UniversalId editing from table columns Feature #1545: Targeting console on player Feature #1597: Editor: Render terrain Feature #1695: Editor: add column for CellRef's global variable Feature #1696: Editor: use ESM::Cell's RefNum counter Feature #1697: Redden player's vision when hit Feature #1856: Spellcasting for non-biped creatures Feature #1879: Editor: Run OpenMW with the currently edited content list Task #1851: Move AI temporary state out of AI packages Task #1865: Replace char type in records 0.32.0 ------ Bug #1132: Unable to jump when facing a wall Bug #1341: Summoned Creatures do not immediately disappear when killed. Bug #1430: CharGen Revamped script does not compile Bug #1451: NPCs shouldn't equip weapons prior to fighting Bug #1461: Stopped start scripts do not restart on load Bug #1473: Dead NPC standing and in 2 pieces Bug #1482: Abilities are depleted when interrupted during casting Bug #1503: Behaviour of NPCs facing the player Bug #1506: Missing character, French edition: three-points Bug #1528: Inventory very slow after 2 hours Bug #1540: Extra arguments should be ignored for script functions Bug #1541: Helseth's Champion: Tribunal Bug #1570: Journal cannot be opened while in inventory screen Bug #1573: PC joins factions at random Bug #1576: NPCs aren't switching their weapons when out of ammo Bug #1579: Guards detect creatures in far distance, instead on sight Bug #1588: The Siege of the Skaal Village: bloodmoon Bug #1593: The script compiler isn't recognising some names that contain a - Bug #1606: Books: Question marks instead of quotation marks Bug #1608: Dead bodies prevent door from opening/closing. Bug #1609: Imperial guards in Sadrith Mora are not using their spears Bug #1610: The bounty number is not displayed properly with high numbers Bug #1620: Implement correct formula for auto-calculated NPC spells Bug #1630: Boats standing vertically in Vivec Bug #1635: Arrest dialogue is executed second time after I select "Go to jail" Bug #1637: Weird NPC behaviour in Vivec, Hlaalu Ancestral Vaults? Bug #1641: Persuasion dialog remains after loading, possibly resulting in crash Bug #1644: "Goodbye" and similar options on dialogues prevents escape working properly. Bug #1646: PC skill stats are not updated immediately when changing equipment Bug #1652: Non-aggressive creature Bug #1653: Quickloading while the container window is open crashes the game Bug #1654: Priority of checks in organic containers Bug #1656: Inventory items merge issue when repairing Bug #1657: Attacked state of NPCs is not saved properly Bug #1660: Rank dialogue condition ignored Bug #1668: Game starts on day 2 instead of day 1 Bug #1669: Critical Strikes while fighting a target who is currently fighting me Bug #1672: OpenCS doesn't save the projects Bug #1673: Fatigue decreasing by only one point when running Bug #1675: Minimap and localmap graphic glitches Bug #1676: Pressing the OK button on the travel menu cancels the travel and exits the menu Bug #1677: Sleeping in a rented bed is considered a crime Bug #1685: NPCs turn towards player even if invisible/sneaking Bug #1686: UI bug: cursor is clicking "world/local" map button while inventory window is closed? Bug #1690: Double clicking on a inventory window header doesn't close it. Bug #1693: Spell Absorption does not absorb shrine blessings Bug #1694: journal displays learned topics as quests Bug #1700: Sideways scroll of text boxes Bug #1701: Player enchanting requires player hold money, always 100% sucessful. Bug #1704: self-made Fortify Intelligence/Drain willpower potions are broken Bug #1707: Pausing the game through the esc menu will silence rain, pausing it by opening the inventory will not. Bug #1709: Remesa Othril is hostile to Hlaalu members Bug #1713: Crash on load after death Bug #1719: Blind effect has slight border at the edge of the screen where it is ineffective. Bug #1722: Crash after creating enchanted item, reloading saved game Bug #1723: Content refs that are stacked share the same index after unstacking Bug #1726: Can't finish Aengoth the Jeweler's quest : Retrieve the Scrap Metal Bug #1727: Targets almost always resist soultrap scrolls Bug #1728: Casting a soultrap spell on invalid target yields no message Bug #1729: Chop attack doesn't work if walking diagonally Bug #1732: Error handling for missing script function arguments produces weird message Bug #1736: Alt-tabbing removes detail from overworld map. Bug #1737: Going through doors with (high magnitude?) leviation will put the player high up, possibly even out of bounds. Bug #1739: Setting a variable on an NPC from another NPC's dialogue result sets the wrong variable Bug #1741: The wait dialogue doesn't black the screen out properly during waiting. Bug #1742: ERROR: Object 'sDifficulty' not found (const) Bug #1744: Night sky in Skies V.IV (& possibly v3) by SWG rendered incorrectly Bug #1746: Bow/marksman weapon condition does not degrade with use Bug #1749: Constant Battle Music Bug #1752: Alt-Tabbing in the character menus makes the paper doll disappear temporarily Bug #1753: Cost of training is not added to merchant's inventory Bug #1755: Disposition changes do not persist if the conversation menu is closed by purchasing training. Bug #1756: Caught Blight after being cured of Corprus Bug #1758: Crash Upon Loading New Cell Bug #1760: Player's Magicka is not recalculated upon drained or boosted intelligence Bug #1761: Equiped torches lost on reload Bug #1762: Your spell did not get a target. Soul trap. Gorenea Andrano Bug #1763: Custom Spell Magicka Cost Bug #1765: Azuras Star breaks on recharging item Bug #1767: GetPCRank did not handle ignored explicit references Bug #1772: Dark Brotherhood Assassins never use their Carved Ebony Dart, sticking to their melee weapon. Bug #1774: String table overflow also occurs when loading TheGloryRoad.esm Bug #1776: dagoth uthol runs in slow motion Bug #1778: Incorrect values in spellmaking window Bug #1779: Icon of Master Propylon Index is not visible Bug #1783: Invisible NPC after looting corpse Bug #1787: Health Calculation Bug #1788: Skeletons, ghosts etc block doors when we try to open Bug #1791: [MOD: LGNPC Foreign Quarter] NPC in completely the wrong place. Bug #1792: Potions should show more effects Bug #1793: Encumbrance while bartering Bug #1794: Fortify attribute not affecting fatigue Bug #1795: Too much magicka Bug #1796: "Off by default" torch burning Bug #1797: Fish too slow Bug #1798: Rest until healed shouldn't show with full health and magicka Bug #1802: Mark location moved Bug #1804: stutter with recent builds Bug #1810: attack gothens dremora doesnt agro the others. Bug #1811: Regression: Crash Upon Loading New Cell Bug #1812: Mod: "QuickChar" weird button placement Bug #1815: Keys show value and weight, Vanilla Morrowind's keys dont. Bug #1817: Persuasion results do not show using unpatched MW ESM Bug #1818: Quest B3_ZainabBride moves to stage 47 upon loading save while Falura Llervu is following Bug #1823: AI response to theft incorrect - only guards react, in vanilla everyone does. Bug #1829: On-Target Spells Rendered Behind Water Surface Effects Bug #1830: Galsa Gindu's house is on fire Bug #1832: Fatal Error: OGRE Exception(2:InvalidParametersException) Bug #1836: Attacked Guards open "fine/jail/resist"-dialogue after killing you Bug #1840: Infinite recursion in ActionTeleport Bug #1843: Escorted people change into player's cell after completion of escort stage Bug #1845: Typing 'j' into 'Name' fields opens the journal Bug #1846: Text pasted into the console still appears twice (Windows) Bug #1847: "setfatigue 0" doesn't render NPC unconscious Bug #1848: I can talk to unconscious actors Bug #1866: Crash when player gets killed by a creature summoned by him Bug #1868: Memory leaking when openmw window is minimized Feature #47: Magic Effects Feature #642: Control NPC mouth movement using current Say sound Feature #939: Editor: Resources tables Feature #961: AI Combat for magic (spells, potions and enchanted items) Feature #1111: Collision script instructions (used e.g. by Lava) Feature #1120: Command creature/humanoid magic effects Feature #1121: Elemental shield magic effects Feature #1122: Light magic effect Feature #1139: AI: Friendly hits Feature #1141: AI: combat party Feature #1326: Editor: Add tooltips to all graphical buttons Feature #1489: Magic effect Get/Mod/Set functions Feature #1505: Difficulty slider Feature #1538: Targeted scripts Feature #1571: Allow creating custom markers on the local map Feature #1615: Determine local variables from compiled scripts instead of the values in the script record Feature #1616: Editor: Body part record verifier Feature #1651: Editor: Improved keyboard navigation for scene toolbar Feature #1666: Script blacklisting Feature #1711: Including the Git revision number from the command line "--version" switch. Feature #1721: NPC eye blinking Feature #1740: Scene toolbar buttons for selecting which type of elements are rendered Feature #1790: Mouse wheel scrolling for the journal Feature #1850: NiBSPArrayController Task #768: On windows, settings folder should be "OpenMW", not "openmw" Task #908: Share keyframe data Task #1716: Remove defunct option for building without FFmpeg 0.31.0 ------ Bug #245: Cloud direction and weather systems differ from Morrowind Bug #275: Local Map does not always show objects that span multiple cells Bug #538: Update CenterOnCell (COC) function behavior Bug #618: Local and World Map Textures are sometimes Black Bug #640: Water behaviour at night Bug #668: OpenMW doesn't support non-latin paths on Windows Bug #746: OpenMW doesn't check if the background music was already played Bug #747: Door is stuck if cell is left before animation finishes Bug #772: Disabled statics are visible on map Bug #829: OpenMW uses up all available vram, when playing for extended time Bug #869: Dead bodies don't collide with anything Bug #894: Various character creation issues Bug #897/#1369: opencs Segmentation Fault after "new" or "load" Bug #899: Various jumping issues Bug #952: Reflection effects are one frame delayed Bug #993: Able to interact with world during Wait/Rest dialog Bug #995: Dropped items can be placed inside the wall Bug #1008: Corpses always face up upon reentering the cell Bug #1035: Random colour patterns appearing in automap Bug #1037: Footstep volume issues Bug #1047: Creation of wrong links in dialogue window Bug #1129: Summoned creature time life duration seems infinite Bug #1134: Crimes can be committed against hostile NPCs Bug #1136: Creature run speed formula is incorrect Bug #1150: Weakness to Fire doesn't apply to Fire Damage in the same spell Bug #1155: NPCs killing each other Bug #1166: Bittercup script still does not work Bug #1178: .bsa file names are case sensitive. Bug #1179: Crash after trying to load game after being killed Bug #1180: Changing footstep sound location Bug #1196: Jumping not disabled when showing messageboxes Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works Bug #1216: Broken dialog topics in russian Morrowind Bug #1217: Container content changes based on the current position of the mouse Bug #1234: Loading/saving issues with dynamic records Bug #1277: Text pasted into the console appears twice Bug #1284: Crash on New Game Bug #1303: It's possible to skip the chargen Bug #1304: Slaughterfish should not detect the player unless the player is in the water Bug #1311: Editor: deleting Record Filter line does not reset the filter Bug #1324: ERROR: ESM Error: String table overflow when loading Animated Morrowind.esp Bug #1328: Editor: Bogus Filter created when dragging multiple records to filter bar of non-applicable table Bug #1331: Walking/running sound persist after killing NPC`s that are walking/running. Bug #1334: Previously equipped items not shown as unequipped after attempting to sell them. Bug #1335: Actors ignore vertical axis when deciding to attack Bug #1338: Unknown toggle option for shadows Bug #1339: "Ashlands Region" is visible when beginning new game during "Loading Area" process Bug #1340: Guards prompt Player with punishment options after resisting arrest with another guard. Bug #1348: Regression: Bug #1098 has returned with a vengeance Bug #1349: [TR] TR_Data mesh tr_ex_imp_gatejamb01 cannot be activated Bug #1352: Disabling an ESX file does not disable dependent ESX files Bug #1355: CppCat Checks OpenMW Bug #1356: Incorrect voice type filtering for sleep interrupts Bug #1357: Restarting the game clears saves Bug #1360: Seyda Neen silk rider dialog problem Bug #1361: Some lights don't work Bug #1364: It is difficult to bind "Mouse 1" to an action in the options menu Bug #1370: Animation compilation mod does not work properly Bug #1371: SL_Pick01.nif from third party fails to load in openmw, but works in Vanilla Bug #1373: When stealing in front of Sellus Gravius cannot exit the dialog Bug #1378: Installs to /usr/local are not working Bug #1380: Loading a save file fail if one of the content files is disabled Bug #1382: "getHExact() size mismatch" crash on loading official plugin "Siege at Firemoth.esp" Bug #1386: Arkngthand door will not open Bug #1388: Segfault when modifying View Distance in Menu options Bug #1389: Crash when loading a save after dying Bug #1390: Apostrophe characters not displayed [French version] Bug #1391: Custom made icon background texture for magical weapons and stuff isn't scaled properly on GUI. Bug #1393: Coin icon during the level up dialogue are off of the background Bug #1394: Alt+F4 doesn't work on Win version Bug #1395: Changing rings switches only the last one put on Bug #1396: Pauldron parts aren't showing when the robe is equipped Bug #1402: Dialogue of some shrines have wrong button orientation Bug #1403: Items are floating in the air when they're dropped onto dead bodies. Bug #1404: Forearms are not rendered on Argonian females Bug #1407: Alchemy allows making potions from two of the same item Bug #1408: "Max sale" button gives you all the items AND all the trader's gold Bug #1409: Rest "Until Healed" broken for characters with stunted magicka. Bug #1412: Empty travel window opens while playing through start game Bug #1413: Save game ignores missing writing permission Bug #1414: The Underground 2 ESM Error Bug #1416: Not all splash screens in the Splash directory are used Bug #1417: Loading saved game does not terminate Bug #1419: Skyrim: Home of the Nords error Bug #1422: ClearInfoActor Bug #1423: ForceGreeting closes existing dialogue windows Bug #1425: Cannot load save game Bug #1426: Read skill books aren't stored in savegame Bug #1427: Useless items can be set under hotkeys Bug #1429: Text variables in journal Bug #1432: When attacking friendly NPC, the crime is reported and bounty is raised after each swing Bug #1435: Stealing priceless items is without punishment Bug #1437: Door marker at Jobasha's Rare Books is spawning PC in the air Bug #1440: Topic selection menu should be wider Bug #1441: Dropping items on the rug makes them inaccessible Bug #1442: When dropping and taking some looted items, bystanders consider that as a crime Bug #1444: Arrows and bolts are not dropped where the cursor points Bug #1445: Security trainers offering acrobatics instead Bug #1447: Character dash not displayed, French edition Bug #1448: When the player is killed by the guard while having a bounty on his head, the guard dialogue opens over and over instead of loading dialogue Bug #1454: Script error in SkipTutorial Bug #1456: Bad lighting when using certain Morrowind.ini generated by MGE Bug #1457: Heart of Lorkan comes after you when attacking it Bug #1458: Modified Keybindings are not remembered Bug #1459: Dura Gra-Bol doesn't respond to PC attack Bug #1462: Interior cells not loaded with Morrowind Patch active Bug #1469: Item tooltip should show the base value, not real value Bug #1477: Death count is not stored in savegame Bug #1478: AiActivate does not trigger activate scripts Bug #1481: Weapon not rendered when partially submerged in water Bug #1483: Enemies are attacking even while dying Bug #1486: ESM Error: Don't know what to do with INFO Bug #1490: Arrows shot at PC can end up in inventory Bug #1492: Monsters respawn on top of one another Bug #1493: Dialogue box opens with follower NPC even if NPC is dead Bug #1494: Paralysed cliffracers remain airbourne Bug #1495: Dialogue box opens with follower NPC even the game is paused Bug #1496: GUI messages are not cleared when loading another saved game Bug #1499: Underwater sound sometimes plays when transitioning from interior. Bug #1500: Targetted spells and water. Bug #1502: Console error message on info refusal Bug #1507: Bloodmoon MQ The Ritual of Beasts: Can't remove the arrow Bug #1508: Bloodmoon: Fort Frostmoth, cant talk with Carnius Magius Bug #1516: PositionCell doesn't move actors to current cell Bug #1518: ForceGreeting broken for explicit references Bug #1522: Crash after attempting to play non-music file Bug #1523: World map empty after loading interior save Bug #1524: Arrows in waiting/resting dialog act like minimum and maximum buttons Bug #1525: Werewolf: Killed NPC's don't fill werewolfs hunger for blood Bug #1527: Werewolf: Detect life detects wrong type of actor Bug #1529: OpenMW crash during "the shrine of the dead" mission (tribunal) Bug #1530: Selected text in the console has the same color as the background Bug #1539: Barilzar's Mazed Band: Tribunal Bug #1542: Looping taunts from NPC`s after death: Tribunal Bug #1543: OpenCS crash when using drag&drop in script editor Bug #1547: Bamz-Amschend: Centurion Archers combat problem Bug #1548: The Missing Hand: Tribunal Bug #1549: The Mad God: Tribunal, Dome of Serlyn Bug #1557: A bounty is calculated from actual item cost Bug #1562: Invisible terrain on top of Red Mountain Bug #1564: Cave of the hidden music: Bloodmoon Bug #1567: Editor: Deleting of referenceables does not work Bug #1568: Picking up a stack of items and holding the enter key and moving your mouse around paints a bunch of garbage on screen. Bug #1574: Solstheim: Drauger cant inflict damage on player Bug #1578: Solstheim: Bonewolf running animation not working Bug #1585: Particle effects on PC are stopped when paralyzed Bug #1589: Tribunal: Crimson Plague quest does not update when Gedna Relvel is killed Bug #1590: Failed to save game: compile error Bug #1598: Segfault when making Drain/Fortify Skill spells Bug #1599: Unable to switch to fullscreen Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed Bug #1618: Death notice fails to show up Bug #1628: Alt+Tab Segfault Feature #32: Periodic Cleanup/Refill Feature #41: Precipitation and weather particles Feature #568: Editor: Configuration setup Feature #649: Editor: Threaded loading Feature #930: Editor: Cell record saving Feature #934: Editor: Body part table Feature #935: Editor: Enchantment effect table Feature #1162: Dialogue merging Feature #1174: Saved Game: add missing creature state Feature #1177: Saved Game: fog of war state Feature #1312: Editor: Combat/Magic/Stealth values for creatures are not displayed Feature #1314: Make NPCs and creatures fight each other Feature #1315: Crime: Murder Feature #1321: Sneak skill enhancements Feature #1323: Handle restocking items Feature #1332: Saved Game: levelled creatures Feature #1347: modFactionReaction script instruction Feature #1362: Animated main menu support Feature #1433: Store walk/run toggle Feature #1449: Use names instead of numbers for saved game files and folders Feature #1453: Adding Delete button to the load menu Feature #1460: Enable Journal screen while in dialogue Feature #1480: Play Battle music when in combat Feature #1501: Followers unable to fast travel with you Feature #1520: Disposition and distance-based aggression/ShouldAttack Feature #1595: Editor: Object rendering in cells Task #940: Move license to locations where applicable Task #1333: Remove cmake git tag reading Task #1566: Editor: Object rendering refactoring 0.30.0 ------ Bug #416: Extreme shaking can occur during cell transitions while moving Bug #1003: Province Cyrodiil: Ogre Exception in Stirk Bug #1071: Crash when given a non-existent content file Bug #1080: OpenMW allows resting/using a bed while in combat Bug #1097: Wrong punishment for stealing in Census and Excise Office at the start of a new game Bug #1098: Unlocked evidence chests should get locked after new evidence is put into them Bug #1099: NPCs that you attacked still fight you after you went to jail/paid your fine Bug #1100: Taking items from a corpse is considered stealing Bug #1126: Some creatures can't get close enough to attack Bug #1144: Killed creatures seem to die again each time player transitions indoors/outdoors Bug #1181: loading a saved game does not reset the player control status Bug #1185: Collision issues in Addamasartus Bug #1187: Athyn Sarethi mission, rescuing varvur sarethi from the doesnt end the mission Bug #1189: Crash when entering interior cell "Gnisis, Arvs-Drelen" Bug #1191: Picking up papers without inventory in new game Bug #1195: NPCs do not equip torches in certain interiors Bug #1197: mouse wheel makes things scroll too fast Bug #1200: door blocked by monsters Bug #1201: item's magical charges are only refreshed when they are used Bug #1203: Scribs do not defend themselves Bug #1204: creatures life is not empty when they are dead Bug #1205: armor experience does not progress when hits are taken Bug #1206: blood particules always red. Undeads and mechanicals should have a different one. Bug #1209: Tarhiel never falls Bug #1210: journal adding script is ran again after having saved/loaded Bug #1224: Names of custom classes are not properly handled in save games Bug #1227: Editor: Fixed case handling for broken localised versions of Morrowind.esm Bug #1235: Indoors walk stutter Bug #1236: Aborting intro movie brings up the menu Bug #1239: NPCs get stuck when walking past each other Bug #1240: BTB - Settings 14.1 and Health Bar. Bug #1241: BTB - Character and Khajiit Prejudice Bug #1248: GUI Weapon icon is changed to hand-to-hand after save load Bug #1254: Guild ranks do not show in dialogue Bug #1255: When opening a container and selecting "Take All", the screen flashes blue Bug #1260: Level Up menu doesn't show image when using a custom class Bug #1265: Quit Menu Has Misaligned Buttons Bug #1270: Active weapon icon is not updated when weapon is repaired Bug #1271: NPC Stuck in hovering "Jumping" animation Bug #1272: Crash when attempting to load Big City esm file. Bug #1276: Editor: Dropping a region into the filter of a cell subview fails Bug #1286: Dialogue topic list clips with window frame Bug #1291: Saved game: store faction membership Bug #1293: Pluginless Khajiit Head Pack by ashiraniir makes OpenMW close. Bug #1294: Pasting in console adds text to end, not at cursor Bug #1295: Conversation loop when asking about "specific place" in Vivec Bug #1296: Caius doesn't leave at start of quest "Mehra Milo and the Lost Prophecies" Bug #1297: Saved game: map markers Bug #1302: ring_keley script causes vector::_M_range_check exception Bug #1309: Bug on "You violated the law" dialog Bug #1319: Creatures sometimes rendered incorrectly Feature #50: Ranged Combat Feature #58: Sneaking Skill Feature #73: Crime and Punishment Feature #135: Editor: OGRE integration Feature #541: Editor: Dialogue Sub-Views Feature #853: Editor: Rework User Settings Feature #944: Editor: lighting modes Feature #945: Editor: Camera navigation mode Feature #953: Trader gold Feature #1140: AI: summoned creatures Feature #1142: AI follow: Run stance Feature #1154: Not all NPCs get aggressive when one is attacked Feature #1169: Terrain threading Feature #1172: Loading screen and progress bars during saved/loading game Feature #1173: Saved Game: include weather state Feature #1207: Class creation form does not remember Feature #1220: Editor: Preview Subview Feature #1223: Saved Game: Local Variables Feature #1229: Quicksave, quickload, autosave Feature #1230: Deleting saves Feature #1233: Bribe gold is placed into NPCs inventory Feature #1252: Saved Game: quick key bindings Feature #1273: Editor: Region Map context menu Feature #1274: Editor: Region Map drag & drop Feature #1275: Editor: Scene subview drop Feature #1282: Non-faction member crime recognition. Feature #1289: NPCs return to default position Task #941: Remove unused cmake files 0.29.0 ------ Bug #556: Video soundtrack not played when music volume is set to zero Bug #829: OpenMW uses up all available vram, when playing for extended time Bug #848: Wrong amount of footsteps playing in 1st person Bug #888: Ascended Sleepers have movement issues Bug #892: Explicit references are allowed on all script functions Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly Bug #1009: Lake Fjalding AI related slowdown. Bug #1041: Music playback issues on OS X >= 10.9 Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window Bug #1060: Some message boxes are cut off at the bottom Bug #1062: Bittercup script does not work ('end' variable) Bug #1074: Inventory paperdoll obscures armour rating Bug #1077: Message after killing an essential NPC disappears too fast Bug #1078: "Clutterbane" shows empty charge bar Bug #1083: UndoWerewolf fails Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered Bug #1090: Start scripts fail when going to a non-predefined cell Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior Bug #1105: Magicka is depleted when using uncastable spells Bug #1106: Creatures should be able to run Bug #1107: TR cliffs have way too huge collision boxes in OpenMW Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) Bug #1115: Memory leak when spying on Fargoth Bug #1137: Script execution fails (drenSlaveOwners script) Bug #1143: Mehra Milo quest (vivec informants) is broken Bug #1145: Issues with moving gold between inventory and containers Bug #1146: Issues with picking up stacks of gold Bug #1147: Dwemer Crossbows are held incorrectly Bug #1158: Armor rating should always stay below inventory mannequin Bug #1159: Quick keys can be set during character generation Bug #1160: Crash on equip lockpick when Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file Bug #1184: Game Save: overwriting an existing save does not actually overwrites the file Feature #30: Loading/Saving (still missing a few parts) Feature #101: AI Package: Activate Feature #103: AI Package: Follow, FollowCell Feature #138: Editor: Drag & Drop Feature #428: Player death Feature #505: Editor: Record Cloning Feature #701: Levelled creatures Feature #708: Improved Local Variable handling Feature #709: Editor: Script verifier Feature #764: Missing journal backend features Feature #777: Creature weapons/shields Feature #789: Editor: Referenceable record verifier Feature #924: Load/Save GUI (still missing loading screen and progress bars) Feature #946: Knockdown Feature #947: Decrease fatigue when running, swimming and attacking Feature #956: Melee Combat: Blocking Feature #957: Area magic Feature #960: Combat/AI combat for creatures Feature #962: Combat-Related AI instructions Feature #1075: Damage/Restore skill/attribute magic effects Feature #1076: Soultrap magic effect Feature #1081: Disease contraction Feature #1086: Blood particles Feature #1092: Interrupt resting Feature #1101: Inventory equip scripts Feature #1116: Version/Build number in Launcher window Feature #1119: Resistance/weakness to normal weapons magic effect Feature #1123: Slow Fall magic effect Feature #1130: Auto-calculate spells Feature #1164: Editor: Case-insensitive sorting in tables 0.28.0 ------ Bug #399: Inventory changes are not visible immediately Bug #417: Apply weather instantly when teleporting Bug #566: Global Map position marker not updated for interior cells Bug #712: Looting corpse delay Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod Bug #805: Two TR meshes appear black (v0.24RC) Bug #841: Third-person activation distance taken from camera rather than head Bug #845: NPCs hold torches during the day Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 Bug #856: Maormer race by Mac Kom - The heads are way up Bug #864: Walk locks during loading in 3rd person Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog Bug #882: Hircine's Ring doesn't always work Bug #909: [Tamriel Rebuilt] crashes in Akamora Bug #922: Launcher writing merged openmw.cfg files Bug #943: Random magnitude should be calculated per effect Bug #948: Negative fatigue level should be allowed Bug #949: Particles in world space Bug #950: Hard crash on x64 Linux running --new-game (on startup) Bug #951: setMagicka and setFatigue have no effect Bug #954: Problem with equipping inventory items when using a keyboard shortcut Bug #955: Issues with equipping torches Bug #966: Shield is visible when casting spell Bug #967: Game crashes when equipping silver candlestick Bug #970: Segmentation fault when starting at Bal Isra Bug #977: Pressing down key in console doesn't go forward in history Bug #979: Tooltip disappears when changing inventory Bug #980: Barter: item category is remembered, but not shown Bug #981: Mod: replacing model has wrong position/orientation Bug #982: Launcher: Addon unchecking is not saved Bug #983: Fix controllers to affect objects attached to the base node Bug #985: Player can talk to NPCs who are in combat Bug #989: OpenMW crashes when trying to include mod with capital .ESP Bug #991: Merchants equip items with harmful constant effect enchantments Bug #994: Don't cap skills/attributes when set via console Bug #998: Setting the max health should also set the current health Bug #1005: Torches are visible when casting spells and during hand to hand combat. Bug #1006: Many NPCs have 0 skill Bug #1007: Console fills up with text Bug #1013: Player randomly loses health or dies Bug #1014: Persuasion window is not centered in maximized window Bug #1015: Player status window scroll state resets on status change Bug #1016: Notification window not big enough for all skill level ups Bug #1020: Saved window positions are not rescaled appropriately on resolution change Bug #1022: Messages stuck permanently on screen when they pile up Bug #1023: Journals doesn't open Bug #1026: Game loses track of torch usage. Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level Bug #1029: Quick keys menu: Select compatible replacement when tool used up Bug #1042: TES3 header data wrong encoding Bug #1045: OS X: deployed OpenCS won't launch Bug #1046: All damaged weaponry is worth 1 gold Bug #1048: Links in "locked" dialogue are still clickable Bug #1052: Using color codes when naming your character actually changes the name's color Bug #1054: Spell effects not visible in front of water Bug #1055: Power-Spell animation starts even though you already casted it that day Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability Bug #1063: Crash upon checking out game start ship area in Seyda Neen Bug #1064: openmw binaries link to unnecessary libraries Bug #1065: Landing from a high place in water still causes fall damage Bug #1072: Drawing weapon increases torch brightness Bug #1073: Merchants sell stacks of gold Feature #43: Visuals for Magic Effects Feature #51: Ranged Magic Feature #52: Touch Range Magic Feature #53: Self Range Magic Feature #54: Spell Casting Feature #70: Vampirism Feature #100: Combat AI Feature #171: Implement NIF record NiFlipController Feature #410: Window to restore enchanted item charge Feature #647: Enchanted item glow Feature #723: Invisibility/Chameleon magic effects Feature #737: Resist Magicka magic effect Feature #758: GetLOS Feature #926: Editor: Info-Record tables Feature #958: Material controllers Feature #959: Terrain bump, specular, & parallax mapping Feature #990: Request: unlock mouse when in any menu Feature #1018: Do not allow view mode switching while performing an action Feature #1027: Vertex morph animation (NiGeomMorpherController) Feature #1031: Handle NiBillboardNode Feature #1051: Implement NIF texture slot DarkTexture Task #873: Unify OGRE initialisation 0.27.0 ------ Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp Bug #794: incorrect display of decimal numbers Bug #840: First-person sneaking camera height Bug #887: Ambient sounds playing while paused Bug #902: Problems with Polish character encoding Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key Bug #910: Some CDs not working correctly with Unshield installer Bug #917: Quick character creation plugin does not work Bug #918: Fatigue does not refill Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) Feature #57: Acrobatics Skill Feature #462: Editor: Start Dialogue Feature #546: Modify ESX selector to handle new content file scheme Feature #588: Editor: Adjust name/path of edited content files Feature #644: Editor: Save Feature #710: Editor: Configure script compiler context Feature #790: God Mode Feature #881: Editor: Allow only one instance of OpenCS Feature #889: Editor: Record filtering Feature #895: Extinguish torches Feature #898: Breath meter enhancements Feature #901: Editor: Default record filter Feature #913: Merge --master and --plugin switches 0.26.0 ------ Bug #274: Inconsistencies in the terrain Bug #557: Already-dead NPCs do not equip clothing/items. Bug #592: Window resizing Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) Bug #664: Heart of lorkhan acts like a dead body (container) Bug #767: Wonky ramp physics & water Bug #780: Swimming out of water Bug #792: Wrong ground alignment on actors when no clipping Bug #796: Opening and closing door sound issue Bug #797: No clipping hinders opening and closing of doors Bug #799: sliders in enchanting window Bug #838: Pressing key during startup procedure freezes the game Bug #839: Combat/magic stances during character creation Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment Bug #844: Resting "until healed" option given even with full stats Bug #846: Equipped torches are invisible. Bug #847: Incorrect formula for autocalculated NPC initial health Bug #850: Shealt weapon sound plays when leaving magic-ready stance Bug #852: Some boots do not produce footstep sounds Bug #860: FPS bar misalignment Bug #861: Unable to print screen Bug #863: No sneaking and jumping at the same time Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. Bug #868: Idle animations are repeated Bug #874: Underwater swimming close to the ground is jerky Bug #875: Animation problem while swimming on the surface and looking up Bug #876: Always a starting upper case letter in the inventory Bug #878: Active spell effects don't update the layout properly when ended Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load Bug #896: New game sound issue Feature #49: Melee Combat Feature #71: Lycanthropy Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList Feature #622: Multiple positions for inventory window Feature #627: Drowning Feature #786: Allow the 'Activate' key to close the countdialog window Feature #798: Morrowind installation via Launcher (Linux/Max OS only) Feature #851: First/Third person transitions with mouse wheel Task #689: change PhysicActor::enableCollisions Task #707: Reorganise Compiler 0.25.0 ------ Bug #411: Launcher crash on OS X < 10.8 Bug #604: Terrible performance drop in the Census and Excise Office. Bug #676: Start Scripts fail to load Bug #677: OpenMW does not accept script names with - Bug #766: Extra space in front of topic links Bug #793: AIWander Isn't Being Passed The Repeat Parameter Bug #795: Sound playing with drawn weapon and crossing cell-border Bug #800: can't select weapon for enchantment Bug #801: Player can move while over-encumbered Bug #802: Dead Keys not working Bug #808: mouse capture Bug #809: ini Importer does not work without an existing cfg file Bug #812: Launcher will run OpenMW with no ESM or ESP selected Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected Bug #817: Dead NPCs and Creatures still have collision boxes Bug #820: Incorrect sorting of answers (Dialogue) Bug #826: mwinimport dumps core when given an unknown parameter Bug #833: getting stuck in door Bug #835: Journals/books not showing up properly. Feature #38: SoundGen Feature #105: AI Package: Wander Feature #230: 64-bit compatibility for OS X Feature #263: Hardware mouse cursors Feature #449: Allow mouse outside of window while paused Feature #736: First person animations Feature #750: Using mouse wheel in third person mode Feature #822: Autorepeat for slider buttons 0.24.0 ------ Bug #284: Book's text misalignment Bug #445: Camera able to get slightly below floor / terrain Bug #582: Seam issue in Red Mountain Bug #632: Journal Next Button shows white square Bug #653: IndexedStore ignores index Bug #694: Parser does not recognize float values starting with . Bug #699: Resource handling broken with Ogre 1.9 trunk Bug #718: components/esm/loadcell is using the mwworld subsystem Bug #729: Levelled item list tries to add nonexistent item Bug #730: Arrow buttons in the settings menu do not work. Bug #732: Erroneous behavior when binding keys Bug #733: Unclickable dialogue topic Bug #734: Book empty line problem Bug #738: OnDeath only works with implicit references Bug #740: Script compiler fails on scripts with special names Bug #742: Wait while no clipping Bug #743: Problem with changeweather console command Bug #744: No wait dialogue after starting a new game Bug #748: Player is not able to unselect objects with the console Bug #751: AddItem should only spawn a message box when called from dialogue Bug #752: The enter button has several functions in trade and looting that is not impelemted. Bug #753: Fargoth's Ring Quest Strange Behavior Bug #755: Launcher writes duplicate lines into settings.cfg Bug #759: Second quest in mages guild does not work Bug #763: Enchantment cast cost is wrong Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly Bug #773: AIWander Isn't Being Passed The Correct idle Values Bug #778: The journal can be opened at the start of a new game Bug #779: Divayth Fyr starts as dead Bug #787: "Batch count" on detailed FPS counter gets cut-off Bug #788: chargen scroll layout does not match vanilla Feature #60: Atlethics Skill Feature #65: Security Skill Feature #74: Interaction with non-load-doors Feature #98: Render Weapon and Shield Feature #102: AI Package: Escort, EscortCell Feature #182: Advanced Journal GUI Feature #288: Trading enhancements Feature #405: Integrate "new game" into the menu Feature #537: Highlight dialogue topic links Feature #658: Rotate, RotateWorld script instructions and local rotations Feature #690: Animation Layering Feature #722: Night Eye/Blind magic effects Feature #735: Move, MoveWorld script instructions. Feature #760: Non-removable corpses 0.23.0 ------ Bug #522: Player collides with placeable items Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open Bug #561: Tooltip word wrapping delay Bug #578: Bribing works incorrectly Bug #601: PositionCell fails on negative coordinates Bug #606: Some NPCs hairs not rendered with Better Heads addon Bug #609: Bad rendering of bone boots Bug #613: Messagebox causing assert to fail Bug #631: Segfault on shutdown Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard Bug #635: Scale NPCs depending on race Bug #643: Dialogue Race select function is inverted Bug #646: Twohanded weapons don't work properly Bug #654: Crash when dropping objects without a collision shape Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell Bug #660: "g" in "change" cut off in Race Menu Bug #661: Arrille sells me the key to his upstairs room Bug #662: Day counter starts at 2 instead of 1 Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. Bug #666: Looking up/down problem Bug #667: Active effects border visible during loading Bug #669: incorrect player position at new game start Bug #670: race selection menu: sex, face and hair left button not totally clickable Bug #671: new game: player is naked Bug #674: buying or selling items doesn't change amount of gold Bug #675: fatigue is not set to its maximum when starting a new game Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly Bug #680: different gold coins in Tel Mara Bug #682: Race menu ignores playable flag for some hairs and faces Bug #685: Script compiler does not accept ":" after a function name Bug #688: dispose corpse makes cross-hair to disappear Bug #691: Auto equipping ignores equipment conditions Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder Bug #696: Draugr incorrect head offset Bug #697: Sail transparency issue Bug #700: "On the rocks" mod does not load its UV coordinates correctly. Bug #702: Some race mods don't work Bug #711: Crash during character creation Bug #715: Growing Tauryon Bug #725: Auto calculate stats Bug #728: Failure to open container and talk dialogue Bug #731: Crash with Mush-Mere's "background" topic Feature #55/657: Item Repairing Feature #62/87: Enchanting Feature #99: Pathfinding Feature #104: AI Package: Travel Feature #129: Levelled items Feature #204: Texture animations Feature #239: Fallback-Settings Feature #535: Console object selection improvements Feature #629: Add levelup description in levelup layout dialog Feature #630: Optional format subrecord in (tes3) header Feature #641: Armor rating Feature #645: OnDeath script function Feature #683: Companion item UI Feature #698: Basic Particles Task #648: Split up components/esm/loadlocks Task #695: mwgui cleanup 0.22.0 ------ Bug #311: Potential infinite recursion in script compiler Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. Bug #382: Weird effect in 3rd person on water Bug #387: Always use detailed shape for physics raycasts Bug #420: Potion/ingredient effects do not stack Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips Bug #434/Bug #605: Object movement between cells not properly implemented Bug #502: Duplicate player collision model at origin Bug #509: Dialogue topic list shifts inappropriately Bug #513: Sliding stairs Bug #515: Launcher does not support non-latin strings Bug #525: Race selection preview camera wrong position Bug #526: Attributes / skills should not go below zero Bug #529: Class and Birthsign menus options should be preselected Bug #530: Lock window button graphic missing Bug #532: Missing map menu graphics Bug #545: ESX selector does not list ESM files properly Bug #547: Global variables of type short are read incorrectly Bug #550: Invisible meshes collision and tooltip Bug #551: Performance drop when loading multiple ESM files Bug #552: Don't list CG in options if it is not available Bug #555: Character creation windows "OK" button broken Bug #558: Segmentation fault when Alt-tabbing with console opened Bug #559: Dialog window should not be available before character creation is finished Bug #560: Tooltip borders should be stretched Bug #562: Sound should not be played when an object cannot be picked up Bug #565: Water animation speed + timescale Bug #572: Better Bodies' textures don't work Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) Bug #574: Moving left/right should not cancel auto-run Bug #575: Crash entering the Chamber of Song Bug #576: Missing includes Bug #577: Left Gloves Addon causes ESMReader exception Bug #579: Unable to open container "Kvama Egg Sack" Bug #581: Mimicking vanilla Morrowind water Bug #583: Gender not recognized Bug #586: Wrong char gen behaviour Bug #587: "End" script statements with spaces don't work Bug #589: Closing message boxes by pressing the activation key Bug #590: Ugly Dagoth Ur rendering Bug #591: Race selection issues Bug #593: Persuasion response should be random Bug #595: Footless guard Bug #599: Waterfalls are invisible from a certain distance Bug #600: Waterfalls rendered incorrectly, cut off by water Bug #607: New beast bodies mod crashes Bug #608: Crash in cell "Mournhold, Royal Palace" Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt Bug #613: Messagebox causing assert to fail Bug #615: Meshes invisible from above water Bug #617: Potion effects should be hidden until discovered Bug #619: certain moss hanging from tree has rendering bug Bug #621: Batching bloodmoon's trees Bug #623: NiMaterialProperty alpha unhandled Bug #628: Launcher in latest master crashes the game Bug #633: Crash on startup: Better Heads Bug #636: Incorrect Char Gen Menu Behavior Feature #29: Allow ESPs and multiple ESMs Feature #94: Finish class selection-dialogue Feature #149: Texture Alphas Feature #237: Run Morrowind-ini importer from launcher Feature #286: Update Active Spell Icons Feature #334: Swimming animation Feature #335: Walking animation Feature #360: Proper collision shapes for NPCs and creatures Feature #367: Lights that behave more like original morrowind implementation Feature #477: Special local scripting variables Feature #528: Message boxes should close when enter is pressed under certain conditions. Feature #543: Add bsa files to the settings imported by the ini importer Feature #594: coordinate space and utility functions Feature #625: Zoom in vanity mode Task #464: Refactor launcher ESX selector into a re-usable component Task #624: Unified implementation of type-variable sub-records 0.21.0 ------ Bug #253: Dialogs don't work for Russian version of Morrowind Bug #267: Activating creatures without dialogue can still activate the dialogue GUI Bug #354: True flickering lights Bug #386: The main menu's first entry is wrong (in french) Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations Bug #495: Activation Range Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available Bug #500: Disposition for most NPCs is 0/100 Bug #501: Getdisposition command wrongly returns base disposition Bug #506: Journal UI doesn't update anymore Bug #507: EnableRestMenu is not a valid command - change it to EnableRest Bug #508: Crash in Ald Daedroth Shrine Bug #517: Wrong price calculation when untrading an item Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin Bug #524: Beast races are able to wear shoes Bug #527: Background music fails to play Bug #533: The arch at Gnisis entrance is not displayed Bug #534: Terrain gets its correct shape only some time after the cell is loaded Bug #536: The same entry can be added multiple times to the journal Bug #539: Race selection is broken Bug #544: Terrain normal map corrupt when the map is rendered Feature #39: Video Playback Feature #151: ^-escape sequences in text output Feature #392: Add AI related script functions Feature #456: Determine required ini fallback values and adjust the ini importer accordingly Feature #460: Experimental DirArchives improvements Feature #540: Execute scripts of objects in containers/inventories in active cells Task #401: Review GMST fixing Task #453: Unify case smashing/folding Task #512: Rewrite utf8 component 0.20.0 ------ Bug #366: Changing the player's race during character creation does not change the look of the player character Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell Bug #437: Stop animations when paused Bug #438: Time displays as "0 a.m." when it should be "12 a.m." Bug #439: Text in "name" field of potion/spell creation window is persistent Bug #440: Starting date at a new game is off by one day Bug #442: Console window doesn't close properly sometimes Bug #448: Do not break container window formatting when item names are very long Bug #458: Topics sometimes not automatically added to known topic list Bug #476: Auto-Moving allows player movement after using DisablePlayerControls Bug #478: After sleeping in a bed the rest dialogue window opens automtically again Bug #492: On creating potions the ingredients are removed twice Feature #63: Mercantile skill Feature #82: Persuasion Dialogue Feature #219: Missing dialogue filters/functions Feature #369: Add a FailedAction Feature #377: Select head/hair on character creation Feature #391: Dummy AI package classes Feature #435: Global Map, 2nd Layer Feature #450: Persuasion Feature #457: Add more script instructions Feature #474: update the global variable pcrace when the player's race is changed Task #158: Move dynamically generated classes from Player class to World Class Task #159: ESMStore rework and cleanup Task #163: More Component Namespace Cleanup Task #402: Move player data from MWWorld::Player to the player's NPC record Task #446: Fix no namespace in BulletShapeLoader 0.19.0 ------ Bug #374: Character shakes in 3rd person mode near the origin Bug #404: Gamma correct rendering Bug #407: Shoes of St. Rilm do not work Bug #408: Rugs has collision even if they are not supposed to Bug #412: Birthsign menu sorted incorrectly Bug #413: Resolutions presented multiple times in launcher Bug #414: launcher.cfg file stored in wrong directory Bug #415: Wrong esm order in openmw.cfg Bug #418: Sound listener position updates incorrectly Bug #423: wrong usage of "Version" entry in openmw.desktop Bug #426: Do not use hardcoded splash images Bug #431: Don't use markers for raycast Bug #432: Crash after picking up items from an NPC Feature #21/#95: Sleeping/resting Feature #61: Alchemy Skill Feature #68: Death Feature #69/#86: Spell Creation Feature #72/#84: Travel Feature #76: Global Map, 1st Layer Feature #120: Trainer Window Feature #152: Skill Increase from Skill Books Feature #160: Record Saving Task #400: Review GMST access 0.18.0 ------ Bug #310: Button of the "preferences menu" are too small Bug #361: Hand-to-hand skill is always 100 Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to Bug #372: playSound3D uses original coordinates instead of current coordinates. Bug #373: Static OGRE build faulty Bug #375: Alt-tab toggle view Bug #376: Screenshots are disable Bug #378: Exception when drinking self-made potions Bug #380: Cloth visibility problem Bug #384: Weird character on doors tooltip. Bug #398: Some objects do not collide in MW, but do so in OpenMW Feature #22: Implement level-up Feature #36: Hide Marker Feature #88: Hotkey Window Feature #91: Level-Up Dialogue Feature #118: Keyboard and Mouse-Button bindings Feature #119: Spell Buying Window Feature #133: Handle resources across multiple data directories Feature #134: Generate a suitable default-value for --data-local Feature #292: Object Movement/Creation Script Instructions Feature #340: AIPackage data structures Feature #356: Ingredients use Feature #358: Input system rewrite Feature #370: Target handling in actions Feature #379: Door markers on the local map Feature #389: AI framework Feature #395: Using keys to open doors / containers Feature #396: Loading screens Feature #397: Inventory avatar image and race selection head preview Task #339: Move sounds into Action 0.17.0 ------ Bug #225: Valgrind reports about 40MB of leaked memory Bug #241: Some physics meshes still don't match Bug #248: Some textures are too dark Bug #300: Dependency on proprietary CG toolkit Bug #302: Some objects don't collide although they should Bug #308: Freeze in Balmora, Meldor: Armorer Bug #313: openmw without a ~/.config/openmw folder segfault. Bug #317: adding non-existing spell via console locks game Bug #318: Wrong character normals Bug #341: Building with Ogre Debug libraries does not use debug version of plugins Bug #347: Crash when running openmw with --start="XYZ" Bug #353: FindMyGUI.cmake breaks path on Windows Bug #359: WindowManager throws exception at destruction Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation Feature #33: Allow objects to cross cell-borders Feature #59: Dropping Items (replaced stopgap implementation with a proper one) Feature #93: Main Menu Feature #96/329/330/331/332/333: Player Control Feature #180: Object rotation and scaling. Feature #272: Incorrect NIF material sharing Feature #314: Potion usage Feature #324: Skill Gain Feature #342: Drain/fortify dynamic stats/attributes magic effects Feature #350: Allow console only script instructions Feature #352: Run scripts in console on startup Task #107: Refactor mw*-subsystems Task #325: Make CreatureStats into a class Task #345: Use Ogre's animation system Task #351: Rewrite Action class to support automatic sound playing 0.16.0 ------ Bug #250: OpenMW launcher erratic behaviour Bug #270: Crash because of underwater effect on OS X Bug #277: Auto-equipping in some cells not working Bug #294: Container GUI ignores disabled inventory menu Bug #297: Stats review dialog shows all skills and attribute values as 0 Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses Bug #299: Crash in World::disable Bug #306: Non-existent ~/.config/openmw "crash" the launcher. Bug #307: False "Data Files" location make the launcher "crash" Feature #81: Spell Window Feature #85: Alchemy Window Feature #181: Support for x.y script syntax Feature #242: Weapon and Spell icons Feature #254: Ingame settings window Feature #293: Allow "stacking" game modes Feature #295: Class creation dialog tooltips Feature #296: Clicking on the HUD elements should show/hide the respective window Feature #301: Direction after using a Teleport Door Feature #303: Allow object selection in the console Feature #305: Allow the use of = as a synonym for == Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts Task #176: Restructure enabling/disabling of MW-references Task #283: Integrate ogre.cfg file in settings file Task #290: Auto-Close MW-reference related GUI windows 0.15.0 ------ Bug #5: Physics reimplementation (fixes various issues) Bug #258: Resizing arrow's background is not transparent Bug #268: Widening the stats window in X direction causes layout problems Bug #269: Topic pane in dialgoue window is too small for some longer topics Bug #271: Dialog choices are sorted incorrectly Bug #281: The single quote character is not rendered on dialog windows Bug #285: Terrain not handled properly in cells that are not predefined Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs Feature #15: Collision with Terrain Feature #17: Inventory-, Container- and Trade-Windows Feature #44: Floating Labels above Focussed Objects Feature #80: Tooltips Feature #83: Barter Dialogue Feature #90: Book and Scroll Windows Feature #156: Item Stacking in Containers Feature #213: Pulsating lights Feature #218: Feather & Burden Feature #256: Implement magic effect bookkeeping Feature #259: Add missing information to Stats window Feature #260: Correct case for dialogue topics Feature #280: GUI texture atlasing Feature #291: Ability to use GMST strings from GUI layout files Task #255: Make MWWorld::Environment into a singleton 0.14.0 ------ Bug #1: Meshes rendered with wrong orientation Bug #6/Task #220: Picking up small objects doesn't always work Bug #127: tcg doesn't work Bug #178: Compablity problems with Ogre 1.8.0 RC 1 Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI Bug #227: Terrain crashes when moving away from predefined cells Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces Bug #235: TGA texture loading problem Bug #246: wireframe mode does not work in water Feature #8/#232: Water Rendering Feature #13: Terrain Rendering Feature #37: Render Path Grid Feature #66: Factions Feature #77: Local Map Feature #78: Compass/Mini-Map Feature #97: Render Clothing/Armour Feature #121: Window Pinning Feature #205: Auto equip Feature #217: Contiainer should track changes to its content Feature #221: NPC Dialogue Window Enhancements Feature #233: Game settings manager Feature #240: Spell List and selected spell (no GUI yet) Feature #243: Draw State Task #113: Morrowind.ini Importer Task #215: Refactor the sound code Task #216: Update MyGUI 0.13.0 ------ Bug #145: Fixed sound problems after cell change Bug #179: Pressing space in console triggers activation Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux Bug #189: ASCII 16 character added to console on it's activation on Mac OS X Bug #190: Case Folding fails with music files Bug #192: Keypresses write Text into Console no matter which gui element is active Bug #196: Collision shapes out of place Bug #202: ESMTool doesn't not work with localised ESM files anymore Bug #203: Torch lights only visible on short distance Bug #207: Ogre.log not written Bug #209: Sounds do not play Bug #210: Ogre crash at Dren plantation Bug #214: Unsupported file format version Bug #222: Launcher is writing openmw.cfg file to wrong location Feature #9: NPC Dialogue Window Feature #16/42: New sky/weather implementation Feature #40: Fading Feature #48: NPC Dialogue System Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) Feature #161: Load REC_PGRD records Feature #195: Wireframe-mode Feature #198/199: Various sound effects Feature #206: Allow picking data path from launcher if non is set Task #108: Refactor window manager class Task #172: Sound Manager Cleanup Task #173: Create OpenEngine systems in the appropriate manager classes Task #184: Adjust MSVC and gcc warning levels Task #185: RefData rewrite Task #201: Workaround for transparency issues Task #208: silenced esm_reader.hpp warning 0.12.0 ------ Bug #154: FPS Drop Bug #169: Local scripts continue running if associated object is deleted Bug #174: OpenMW fails to start if the config directory doesn't exist Bug #187: Missing lighting Bug #188: Lights without a mesh are not rendered Bug #191: Taking screenshot causes crash when running installed Feature #28: Sort out the cell load problem Feature #31: Allow the player to move away from pre-defined cells Feature #35: Use alternate storage location for modified object position Feature #45: NPC animations Feature #46: Creature Animation Feature #89: Basic Journal Window Feature #110: Automatically pick up the path of existing MW-installations Feature #183: More FPS display settings Task #19: Refactor engine class Task #109/Feature #162: Automate Packaging Task #112: Catch exceptions thrown in input handling functions Task #128/#168: Cleanup Configuration File Handling Task #131: NPC Activation doesn't work properly Task #144: MWRender cleanup Task #155: cmake cleanup 0.11.1 ------ Bug #2: Resources loading doesn't work outside of bsa files Bug #3: GUI does not render non-English characters Bug #7: openmw.cfg location doesn't match Bug #124: The TCL alias for ToggleCollision is missing. Bug #125: Some command line options can't be used from a .cfg file Bug #126: Toggle-type script instructions are less verbose compared with original MW Bug #130: NPC-Record Loading fails for some NPCs Bug #167: Launcher sets invalid parameters in ogre config Feature #10: Journal Feature #12: Rendering Optimisations Feature #23: Change Launcher GUI to a tabbed interface Feature #24: Integrate the OGRE settings window into the launcher Feature #25: Determine openmw.cfg location (Launcher) Feature #26: Launcher Profiles Feature #79: MessageBox Feature #116: Tab-Completion in Console Feature #132: --data-local and multiple --data Feature #143: Non-Rendering Performance-Optimisations Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs Feature #157: Version Handling Task #14: Replace tabs with 4 spaces Task #18: Move components from global namespace into their own namespace Task #123: refactor header files in components/esm 0.10.0 ------ * NPC dialogue window (not functional yet) * Collisions with objects * Refactor the PlayerPos class * Adjust file locations * CMake files and test linking for Bullet * Replace Ogre raycasting test for activation with something more precise * Adjust player movement according to collision results * FPS display * Various Portability Improvements * Mac OS X support is back! 0.9.0 ----- * Exterior cells loading, unloading and management * Character Creation GUI * Character creation * Make cell names case insensitive when doing internal lookups * Music player * NPCs rendering 0.8.0 ----- * GUI * Complete and working script engine * In game console * Sky rendering * Sound and music * Tons of smaller stuff 0.7.0 ----- * This release is a complete rewrite in C++. * All D code has been culled, and all modules have been rewritten. * The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. 0.6.0 ----- * Coded a GUI system using MyGUI * Skinned MyGUI to look like Morrowind (work in progress) * Integrated the Monster script engine * Rewrote some functions into script code * Very early MyGUI < > Monster binding * Fixed Windows sound problems (replaced old openal32.dll) 0.5.0 ----- * Collision detection with Bullet * Experimental walk & fall character physics * New key bindings: * t toggle physics mode (walking, flying, ghost), * n night eye, brightens the scene * Fixed incompatability with DMD 1.032 and newer compilers * * (thanks to tomqyp) * Various minor changes and updates 0.4.0 ----- * Switched from Audiere to OpenAL * * (BIG thanks to Chris Robinson) * Added complete Makefile (again) as a alternative build tool * More realistic lighting (thanks again to Chris Robinson) * Various localization fixes tested with Russian and French versions * Temporary workaround for the Unicode issue: invalid UTF displayed as '?' * Added ns option to disable sound, for debugging * Various bug fixes * Cosmetic changes to placate gdc Wall 0.3.0 ----- * Built and tested on Windows XP * Partial support for FreeBSD (exceptions do not work) * You no longer have to download Monster separately * Made an alternative for building without DSSS (but DSSS still works) * Renamed main program from 'morro' to 'openmw' * Made the config system more robust * Added oc switch for showing Ogre config window on startup * Removed some config files, these are auto generated when missing. * Separated plugins.cfg into linux and windows versions. * Updated Makefile and sources for increased portability * confirmed to work against OIS 1.0.0 (Ubuntu repository package) 0.2.0 ----- * Compiles with gdc * Switched to DSSS for building D code * Includes the program esmtool 0.1.0 ----- first release openmw-openmw-0.48.0/CHANGELOG_PR.md000066400000000000000000000070541445372753700166540ustar00rootroot00000000000000*** PLEASE PUT YOUR ISSUE DESCRIPTION FOR DUMMIES HERE FOR REVIEW *** - I'm just a placeholder description (#1337) - I'm also just a placeholder description, but I'm a more recent one (#42) *** 0.47.0 ------ The OpenMW team is proud to announce the release of version 0.47.0! Grab it from our Downloads Page for all operating systems. ***short summary: XXX *** Check out the release video (***add link***) and the OpenMW-CS release video (***add link***) by the ***add flattering adjective*** Atahualpa, and see below for the full list of changes. Known Issues: - To use generic Linux binaries, Qt4 and libpng12 must be installed on your system - On macOS, launching OpenMW from OpenMW-CS requires OpenMW.app and OpenMW-CS.app to be siblings New Features: - Dialogue to split item stacks now displays the name of the trapped soul for stacks of soul gems (#5362) - Basics of Collada animations are now supported via osgAnimation plugin (#5456) New Editor Features: - Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171) Bug Fixes: - NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676) - Targetting non-unique actors in scripts is now supported (#2311) - Guards no longer ignore attacks of invisible players but rather initiate dialogue and flee if the player resists being arrested (#4774) - Changing the dialogue window without closing it no longer clears the dialogue history in order to allow, e.g., emulation of three-way dialogue via ForceGreeting (#5358) - Scripts which try to start a non-existent global script now skip that step and continue execution instead of breaking (#5364) - Selecting already equipped spells or magic items via hotkey no longer triggers the equip sound to play (#5367) - 'Scale' argument in levelled creature lists is now taken into account when spawning creatures from such lists (#5369) - Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370) Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) - Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) - Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Cell borders are now properly redrawn when undoing/redoing terrain changes (#5473) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) - Collada models render properly in the Editor (#5713) - Terrain-selection grid is now properly updated when undoing/redoing terrain changes (#6022) - Tool outline and select/edit actions in "Terrain land editing" mode now ignore references (#6023) - Primary-select and secondary-select actions in "Terrain land editing" mode now behave like in "Instance editing" mode (#6024) - Using the circle brush to select terrain in the "Terrain land editing" mode no longer selects vertices outside the circle (#6035) - Vertices at the NW and SE corners of a cell can now also be selected in "Terrain land editing" mode if the adjacent cells aren't loaded yet (#6036) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) - Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) openmw-openmw-0.48.0/CI/000077500000000000000000000000001445372753700147275ustar00rootroot00000000000000openmw-openmw-0.48.0/CI/ActivateMSVC.ps1000066400000000000000000000015701445372753700176100ustar00rootroot00000000000000& "${env:COMSPEC}" /c ActivateMSVC.bat "&&" set | ForEach-Object { if ($_.Contains("=")) { $name, $value = $_ -split '=', 2 Set-Content env:\"$name" $value } } $MissingTools = $false $tools = "cl", "link", "rc", "mt" $descriptions = "MSVC Compiler", "MSVC Linker", "MS Windows Resource Compiler", "MS Windows Manifest Tool" for ($i = 0; $i -lt $tools.Length; $i++) { $present = $true try { Get-Command $tools[$i] *>&1 | Out-Null $present = $present -and $? } catch { $present = $false } if (!$present) { Write-Warning "$($tools[$i]) ($($descriptions[$i])) missing." $MissingTools = $true } } if ($MissingTools) { Write-Error "Some build tools were unavailable after activating MSVC in the shell. It's likely that your Visual Studio $MSVC_DISPLAY_YEAR installation needs repairing." exit 1 }openmw-openmw-0.48.0/CI/activate_msvc.sh000066400000000000000000000031111445372753700201070ustar00rootroot00000000000000#!/bin/bash oldSettings=$- set -eu function restoreOldSettings { if [[ $oldSettings != *e* ]]; then set +e fi if [[ $oldSettings != *u* ]]; then set +u fi } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then echo "Error: Script not sourced." echo "You must source this script for it to work, i.e. " echo "source ./activate_msvc.sh" echo "or" echo ". ./activate_msvc.sh" restoreOldSettings exit 1 fi command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows { if command -v cygpath >/dev/null 2>&1; then cygpath -w $1 else echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g" fi } # capture CMD environment in a shell with MSVC activated cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" "bash" "-c" "declare -px > declared_env.sh" source ./declared_env.sh rm declared_env.sh MISSINGTOOLS=0 command -v cl >/dev/null 2>&1 || { echo "Error: cl (MSVC Compiler) missing."; MISSINGTOOLS=1; } command -v link >/dev/null 2>&1 || { echo "Error: link (MSVC Linker) missing."; MISSINGTOOLS=1; } command -v rc >/dev/null 2>&1 || { echo "Error: rc (MS Windows Resource Compiler) missing."; MISSINGTOOLS=1; } command -v mt >/dev/null 2>&1 || { echo "Error: mt (MS Windows Manifest Tool) missing."; MISSINGTOOLS=1; } if [ $MISSINGTOOLS -ne 0 ]; then echo "Some build tools were unavailable after activating MSVC in the shell. It's likely that your Visual Studio $MSVC_DISPLAY_YEAR installation needs repairing." restoreOldSettings return 1 fi restoreOldSettings openmw-openmw-0.48.0/CI/before_install.android.sh000077500000000000000000000004071445372753700216760ustar00rootroot00000000000000#!/bin/sh -ex curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20211114.zip -o ~/openmw-android-deps.zip unzip -o ~/openmw-android-deps -d /android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null openmw-openmw-0.48.0/CI/before_install.linux.sh000077500000000000000000000001731445372753700214150ustar00rootroot00000000000000#!/bin/bash -ex #sudo ln -sf /usr/bin/clang-6 /usr/local/bin/clang #sudo ln -sf /usr/bin/clang++-6 /usr/local/bin/clang++ openmw-openmw-0.48.0/CI/before_install.osx.sh000077500000000000000000000020711445372753700210660ustar00rootroot00000000000000#!/bin/sh -ex export HOMEBREW_NO_EMOJI=1 brew tap --repair brew update --quiet # workaround python issue on travis [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.8 || true [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.9 || true [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies qt@6 || true # Some of these tools can come from places other than brew, so check before installing [ -z "${TRAVIS}" ] && brew reinstall xquartz [ -z "${TRAVIS}" ] && brew reinstall fontconfig 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@5 brew install icu4c brew install yaml-cpp export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 ccache --version cmake --version qmake --version curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null openmw-openmw-0.48.0/CI/before_script.android.sh000077500000000000000000000027211445372753700215350ustar00rootroot00000000000000#!/bin/sh -ex # hack to work around: FFmpeg version is too old, 3.2 is required sed -i s/"NOT FFVER_OK"/"FALSE"/ CMakeLists.txt # Silence a git warning git config --global advice.detachedHead false mkdir -p build cd build # Build a version of ICU for the host so that it can use the tools during the cross-compilation mkdir -p icu-host-build cd icu-host-build if [ -r ../extern/fetched/icu/icu4c/source/configure ]; then ICU_SOURCE_DIR=../extern/fetched/icu/icu4c/source else wget https://github.com/unicode-org/icu/archive/refs/tags/release-70-1.zip unzip release-70-1.zip ICU_SOURCE_DIR=./icu-release-70-1/icu4c/source fi ${ICU_SOURCE_DIR}/configure --disable-tests --disable-samples --disable-icuio --disable-extras CC="ccache gcc" CXX="ccache g++" make -j $(nproc) cd .. cmake \ -DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ -DANDROID_LD=deprecated \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_INSTALL_PREFIX=install \ -DBUILD_BSATOOL=0 \ -DBUILD_NIFTEST=0 \ -DBUILD_ESMTOOL=0 \ -DBUILD_LAUNCHER=0 \ -DBUILD_MWINIIMPORTER=0 \ -DBUILD_ESSIMPORTER=0 \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ -DBUILD_NAVMESHTOOL=OFF \ -DBUILD_BULLETOBJECTTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ -DOPENMW_USE_SYSTEM_YAML_CPP=OFF \ -DOPENMW_USE_SYSTEM_ICU=OFF \ -DOPENMW_ICU_HOST_BUILD_DIR="$(pwd)/icu-host-build" \ .. openmw-openmw-0.48.0/CI/before_script.linux.sh000077500000000000000000000053021445372753700212520ustar00rootroot00000000000000#!/bin/bash set -xeo pipefail free -m # Silence a git warning git config --global advice.detachedHead false BUILD_UNITTESTS=OFF BUILD_BENCHMARKS=OFF if [[ "${BUILD_TESTS_ONLY}" ]]; then BUILD_UNITTESTS=ON BUILD_BENCHMARKS=ON fi # setup our basic cmake build options declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=install -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project ) if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then CMAKE_CONF_OPTS+=( -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" ) fi if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then CMAKE_CONF_OPTS+=( -DOPENMW_USE_SYSTEM_MYGUI=OFF -DOPENMW_USE_SYSTEM_OSG=OFF -DOPENMW_USE_SYSTEM_BULLET=OFF -DOPENMW_USE_SYSTEM_SQLITE3=OFF -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=OFF ) fi if [[ $CI_CLANG_TIDY ]]; then CMAKE_CONF_OPTS+=( -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" ) fi if [[ "${CMAKE_BUILD_TYPE}" ]]; then CMAKE_CONF_OPTS+=( -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ) else CMAKE_CONF_OPTS+=( -DCMAKE_BUILD_TYPE=RelWithDebInfo ) fi if [[ "${CMAKE_CXX_FLAGS_DEBUG}" ]]; then CMAKE_CONF_OPTS+=( -DCMAKE_CXX_FLAGS_DEBUG="${CMAKE_CXX_FLAGS_DEBUG}" ) fi if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then CMAKE_CONF_OPTS+=( -DBUILD_WITH_CODE_COVERAGE="${BUILD_WITH_CODE_COVERAGE}" ) fi mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then # flags specific to our test suite CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" if [[ "${CXX}" == 'clang++' ]]; then CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" fi CMAKE_CONF_OPTS+=( -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" ) ${ANALYZE} cmake \ "${CMAKE_CONF_OPTS[@]}" \ -DBUILD_OPENMW=OFF \ -DBUILD_BSATOOL=OFF \ -DBUILD_ESMTOOL=OFF \ -DBUILD_LAUNCHER=OFF \ -DBUILD_MWINIIMPORTER=OFF \ -DBUILD_ESSIMPORTER=OFF \ -DBUILD_OPENCS=OFF \ -DBUILD_WIZARD=OFF \ -DBUILD_NAVMESHTOOL=OFF \ -DBUILD_BULLETOBJECTTOOL=OFF \ -DBUILD_NIFTEST=OFF \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ .. else ${ANALYZE} cmake \ "${CMAKE_CONF_OPTS[@]}" \ .. fi openmw-openmw-0.48.0/CI/before_script.msvc.sh000066400000000000000000000732411445372753700210670ustar00rootroot00000000000000#!/bin/bash # set -x # turn-on for debugging function wrappedExit { if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then exit $1 else return $1 fi } MISSINGTOOLS=0 command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; } command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; } MISSINGPYTHON=0 if ! command -v python >/dev/null 2>&1; then echo "Warning: Python is not on the path, automatic Qt installation impossible." MISSINGPYTHON=1 elif ! python --version >/dev/null 2>&1; then echo "Warning: Python is (probably) fake stub Python that comes bundled with newer versions of Windows, automatic Qt installation impossible." echo "If you think you have Python installed, try changing the order of your PATH environment variable in Advanced System Settings." MISSINGPYTHON=1 fi if [ $MISSINGTOOLS -ne 0 ]; then wrappedExit 1 fi WORKINGDIR="$(pwd)" case "$WORKINGDIR" in *[[:space:]]*) echo "Error: Working directory contains spaces." wrappedExit 1 ;; esac set -euo pipefail function windowsPathAsUnix { if command -v cygpath >/dev/null 2>&1; then cygpath -u $1 else echo "$1" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1," fi } function unixPathAsWindows { if command -v cygpath >/dev/null 2>&1; then cygpath -w $1 else echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g" fi } APPVEYOR=${APPVEYOR:-} CI=${CI:-} STEP=${STEP:-} VERBOSE="" STRIP="" SKIP_DOWNLOAD="" SKIP_EXTRACT="" USE_CCACHE="" KEEP="" UNITY_BUILD="" VS_VERSION="" NMAKE="" NINJA="" PDBS="" PLATFORM="" CONFIGURATIONS=() TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." BUILD_BENCHMARKS="" OSG_MULTIVIEW_BUILD="" USE_WERROR="" USE_CLANG_TIDY="" ACTIVATE_MSVC="" SINGLE_CONFIG="" while [ $# -gt 0 ]; do ARGSTR=$1 shift if [ ${ARGSTR:0:1} != "-" ]; then echo "Unknown argument $ARGSTR" echo "Try '$0 -h'" wrappedExit 1 fi for (( i=1; i<${#ARGSTR}; i++ )); do ARG=${ARGSTR:$i:1} case $ARG in V ) VERBOSE=true ;; d ) SKIP_DOWNLOAD=true ;; e ) SKIP_EXTRACT=true ;; C ) USE_CCACHE=true ;; k ) KEEP=true ;; u ) UNITY_BUILD=true ;; v ) VS_VERSION=$1 shift ;; n ) NMAKE=true ;; N ) NINJA=true ;; p ) PLATFORM=$1 shift ;; P ) PDBS=true ;; c ) CONFIGURATIONS+=( $1 ) shift ;; t ) TEST_FRAMEWORK=true ;; i ) INSTALL_PREFIX=$(echo "$1" | sed 's;\\;/;g' | sed -E 's;/+;/;g') shift ;; b ) BUILD_BENCHMARKS=true ;; M ) OSG_MULTIVIEW_BUILD=true ;; E ) USE_WERROR=true ;; T ) USE_CLANG_TIDY=true ;; h ) cat < Set the configuration, can also be set with environment variable CONFIGURATION. For mutli-config generators, this is ignored, and all configurations are set up. For single-config generators, several configurations can be set up at once by specifying -c multiple times. -C Use ccache. -d Skip checking the downloads. -e Skip extracting dependencies. -h Show this message. -k Keep the old build directory, default is to delete it. -p Set the build platform, can also be set with environment variable PLATFORM. -t Build unit tests / Google test -u Configure for unity builds. -v <2019/2022> Choose the Visual Studio version to use. -n Produce NMake makefiles instead of a Visual Studio solution. Cannot be used with -N. -N Produce Ninja (multi-config if CMake is new enough to support it) files instead of a Visual Studio solution. Cannot be used with -n.. -P Download debug symbols where available -V Run verbosely -i CMake install prefix -b Build benchmarks -M Use a multiview build of OSG -E Use warnings as errors (/WX) -T Run clang-tidy EOF wrappedExit 0 ;; * ) echo "Unknown argument $ARG." echo "Try '$0 -h'" wrappedExit 1 ;; esac done done if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then if [ -n "$NMAKE" ] && [ -n "$NINJA" ]; then echo "Cannot run in NMake and Ninja mode at the same time." wrappedExit 1 fi ACTIVATE_MSVC=true fi if [ -z $VERBOSE ]; then STRIP="> /dev/null 2>&1" fi if [ -z $APPVEYOR ]; then echo "Running prebuild outside of Appveyor." DIR=$(windowsPathAsUnix "${BASH_SOURCE[0]}") cd $(dirname "$DIR")/.. else echo "Running prebuild in Appveyor." cd "$APPVEYOR_BUILD_FOLDER" fi run_cmd() { CMD="$1" shift if [ -z $VERBOSE ]; then RET=0 eval $CMD $@ > output.log 2>&1 || RET=$? if [ $RET -ne 0 ]; then if [ -z $APPVEYOR ]; then echo "Command $CMD failed, output can be found in $(real_pwd)/output.log" else echo echo "Command $CMD failed;" cat output.log fi else rm output.log fi return $RET else RET=0 eval $CMD $@ || RET=$? return $RET fi } download() { if [ $# -lt 3 ]; then echo "Invalid parameters to download." return 1 fi NAME=$1 shift echo "$NAME..." while [ $# -gt 1 ]; do URL=$1 FILE=$2 shift shift if ! [ -f $FILE ]; then printf " Downloading $FILE... " if [ -z $VERBOSE ]; then RET=0 curl --silent --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$? else RET=0 curl --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$? fi if [ $RET -ne 0 ]; then echo "Failed!" wrappedExit $RET else echo "Done." fi else echo " $FILE exists, skipping." fi done if [ $# -ne 0 ]; then echo "Missing parameter." fi } real_pwd() { if type cygpath >/dev/null 2>&1; then cygpath -am "$PWD" else pwd # not git bash, Cygwin or the like fi } CMAKE_OPTS="" add_cmake_opts() { CMAKE_OPTS="$CMAKE_OPTS $@" } declare -A RUNTIME_DLLS RUNTIME_DLLS["Release"]="" RUNTIME_DLLS["Debug"]="" RUNTIME_DLLS["RelWithDebInfo"]="" add_runtime_dlls() { local CONFIG=$1 shift RUNTIME_DLLS[$CONFIG]="${RUNTIME_DLLS[$CONFIG]} $@" } declare -A OSG_PLUGINS OSG_PLUGINS["Release"]="" OSG_PLUGINS["Debug"]="" OSG_PLUGINS["RelWithDebInfo"]="" add_osg_dlls() { local CONFIG=$1 shift OSG_PLUGINS[$CONFIG]="${OSG_PLUGINS[$CONFIG]} $@" } declare -A QT_PLATFORMS QT_PLATFORMS["Release"]="" QT_PLATFORMS["Debug"]="" QT_PLATFORMS["RelWithDebInfo"]="" add_qt_platform_dlls() { local CONFIG=$1 shift QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@" } declare -A QT_STYLES QT_STYLES["Release"]="" QT_STYLES["Debug"]="" QT_STYLES["RelWithDebInfo"]="" add_qt_style_dlls() { local CONFIG=$1 shift QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" } if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi if [ -z $VS_VERSION ]; then VS_VERSION="2019" fi case $VS_VERSION in 17|17.0|2022 ) GENERATOR="Visual Studio 17 2022" TOOLSET="vc143" MSVC_REAL_VER="17" MSVC_VER="14.3" MSVC_DISPLAY_YEAR="2022" OSG_MSVC_YEAR="2019" MYGUI_MSVC_YEAR="2019" LUA_MSVC_YEAR="2019" QT_MSVC_YEAR="2019" BULLET_MSVC_YEAR="2015" BOOST_VER="1.80.0" BOOST_VER_URL="1_80_0" BOOST_VER_SDK="108000" ;; 16|16.0|2019 ) GENERATOR="Visual Studio 16 2019" TOOLSET="vc142" MSVC_REAL_VER="16" MSVC_VER="14.2" MSVC_DISPLAY_YEAR="2019" OSG_MSVC_YEAR="2019" MYGUI_MSVC_YEAR="2019" LUA_MSVC_YEAR="2019" QT_MSVC_YEAR="2019" BULLET_MSVC_YEAR="2015" BOOST_VER="1.80.0" BOOST_VER_URL="1_80_0" BOOST_VER_SDK="108000" ;; 15|15.0|2017 ) echo "Visual Studio 2017 is no longer supported" wrappedExit 1 ;; 14|14.0|2015 ) echo "Visual Studio 2015 is no longer supported" wrappedExit 1 ;; 12|12.0|2013 ) echo "Visual Studio 2013 is no longer supported" wrappedExit 1 ;; esac case $PLATFORM in x64|x86_64|x86-64|win64|Win64 ) ARCHNAME="x86-64" ARCHSUFFIX="64" BITS="64" ;; x32|x86|i686|i386|win32|Win32 ) ARCHNAME="x86" ARCHSUFFIX="86" BITS="32" ;; * ) echo "Unknown platform $PLATFORM." wrappedExit 1 ;; esac if [ -n "$NMAKE" ]; then GENERATOR="NMake Makefiles" SINGLE_CONFIG=true fi if [ -n "$NINJA" ]; then GENERATOR="Ninja Multi-Config" if ! cmake -E capabilities | grep -F "$GENERATOR" > /dev/null; then SINGLE_CONFIG=true GENERATOR="Ninja" fi fi if [ -n "$SINGLE_CONFIG" ]; then if [ ${#CONFIGURATIONS[@]} -eq 0 ]; then if [ -n "${CONFIGURATION:-}" ]; then CONFIGURATIONS=("$CONFIGURATION") else CONFIGURATIONS=("Debug") fi elif [ ${#CONFIGURATIONS[@]} -ne 1 ]; then # It's simplest just to recursively call the script a few times. RECURSIVE_OPTIONS=() if [ -n "$VERBOSE" ]; then RECURSIVE_OPTIONS+=("-V") fi if [ -n "$SKIP_DOWNLOAD" ]; then RECURSIVE_OPTIONS+=("-d") fi if [ -n "$SKIP_EXTRACT" ]; then RECURSIVE_OPTIONS+=("-e") fi if [ -n "$KEEP" ]; then RECURSIVE_OPTIONS+=("-k") fi if [ -n "$UNITY_BUILD" ]; then RECURSIVE_OPTIONS+=("-u") fi if [ -n "$NMAKE" ]; then RECURSIVE_OPTIONS+=("-n") fi if [ -n "$NINJA" ]; then RECURSIVE_OPTIONS+=("-N") fi if [ -n "$PDBS" ]; then RECURSIVE_OPTIONS+=("-P") fi if [ -n "$TEST_FRAMEWORK" ]; then RECURSIVE_OPTIONS+=("-t") fi RECURSIVE_OPTIONS+=("-v $VS_VERSION") RECURSIVE_OPTIONS+=("-p $PLATFORM") RECURSIVE_OPTIONS+=("-i '$INSTALL_PREFIX'") for config in ${CONFIGURATIONS[@]}; do $0 ${RECURSIVE_OPTIONS[@]} -c $config done wrappedExit 1 fi else if [ ${#CONFIGURATIONS[@]} -ne 0 ]; then echo "Ignoring configurations argument - generator is multi-config" fi CONFIGURATIONS=("Release" "Debug" "RelWithDebInfo") fi for i in ${!CONFIGURATIONS[@]}; do case ${CONFIGURATIONS[$i]} in debug|Debug|DEBUG ) CONFIGURATIONS[$i]=Debug ;; release|Release|RELEASE ) CONFIGURATIONS[$i]=Release ;; relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) CONFIGURATIONS[$i]=RelWithDebInfo ;; esac done if [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then if [ $BITS -eq 64 ]; then add_cmake_opts "-G\"$GENERATOR\" -A x64" else add_cmake_opts "-G\"$GENERATOR\" -A Win32" fi else add_cmake_opts "-G\"$GENERATOR\"" fi if [ -n "$SINGLE_CONFIG" ]; then add_cmake_opts "-DCMAKE_BUILD_TYPE=${CONFIGURATIONS[0]}" fi if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi if ! [ -z $USE_CCACHE ]; then add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" fi # turn on LTO by default add_cmake_opts "-DOPENMW_LTO_BUILD=True" if ! [ -z "$USE_WERROR" ]; then add_cmake_opts "-DOPENMW_MSVC_WERROR=ON" fi if ! [ -z "$USE_CLANG_TIDY" ]; then add_cmake_opts "-DCMAKE_CXX_CLANG_TIDY=\"clang-tidy --warnings-as-errors=*\"" fi ICU_VER="70_1" OSG_ARCHIVE_NAME="OSGoS 3.6.5" OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" OSG_ARCHIVE_REPO_URL="https://gitlab.com/OpenMW/openmw-deps/-/raw/main" if ! [ -z $OSG_MULTIVIEW_BUILD ]; then OSG_ARCHIVE_NAME="OSG-3.6-multiview" OSG_ARCHIVE="OSG-3.6-multiview-d2ee5aa8-msvc${OSG_MSVC_YEAR}-win${BITS}" OSG_ARCHIVE_REPO_URL="https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview" fi echo echo "===================================" echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}" echo "===================================" echo # cd OpenMW/AppVeyor-test mkdir -p deps cd deps DEPS="$(pwd)" if [ -z $SKIP_DOWNLOAD ]; then echo "Downloading dependency packages." echo # Boost if [ -z $APPVEYOR ]; then download "Boost ${BOOST_VER}" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \ "boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe" fi # Bullet download "Bullet 2.89" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${BULLET_MSVC_YEAR}-win${BITS}-double.7z" \ "Bullet-2.89-msvc${BULLET_MSVC_YEAR}-win${BITS}-double.7z" # FFmpeg download "FFmpeg 4.2.2" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-win${BITS}.zip" \ "ffmpeg-4.2.2-win${BITS}.zip" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-dev-win${BITS}.zip" \ "ffmpeg-4.2.2-dev-win${BITS}.zip" # MyGUI download "MyGUI 3.4.1" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" \ "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" if [ -n "$PDBS" ]; then download "MyGUI symbols" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" \ "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" fi # OpenAL download "OpenAL-Soft 1.23.0" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.23.0.zip" \ "OpenAL-Soft-1.23.0.zip" # OSGoS download "${OSG_ARCHIVE_NAME}" \ "${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}.7z" \ "${OSG_ARCHIVE}.7z" if [ -n "$PDBS" ]; then download "${OSG_ARCHIVE_NAME} symbols" \ "${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}-sym.7z" \ "${OSG_ARCHIVE}-sym.7z" fi # SDL2 download "SDL 2.24.0" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-devel-2.24.0-VC.zip" \ "SDL2-devel-2.24.0-VC.zip" # LZ4 download "LZ4 1.9.2" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v1_9_2.7z" \ "lz4_win${BITS}_v1_9_2.7z" # LuaJIT download "LuaJIT 2.1.0-beta3" \ "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \ "LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" # ICU download "ICU ${ICU_VER/_/.}"\ "https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \ "icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" fi cd .. #/.. # Set up dependencies BUILD_DIR="MSVC${MSVC_DISPLAY_YEAR}_${BITS}" if [ -n "$NMAKE" ]; then BUILD_DIR="${BUILD_DIR}_NMake" elif [ -n "$NINJA" ]; then BUILD_DIR="${BUILD_DIR}_Ninja" fi if [ -n "$SINGLE_CONFIG" ]; then BUILD_DIR="${BUILD_DIR}_${CONFIGURATIONS[0]}" fi if [ -z $KEEP ]; then echo echo "(Re)Creating build directory." rm -rf "$BUILD_DIR" fi mkdir -p "${BUILD_DIR}/deps" cd "${BUILD_DIR}/deps" DEPS_INSTALL="$(pwd)" cd $DEPS echo echo "Extracting dependencies, this might take a while..." echo "---------------------------------------------------" echo # Boost if [ -z $APPVEYOR ]; then printf "Boost ${BOOST_VER}... " else printf "Boost ${BOOST_VER} AppVeyor... " fi { if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL BOOST_SDK="$(real_pwd)/Boost" # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names # We work around this by installing to root of the current working drive and then move it to our deps # get the current working drive's root, we'll install to that temporarily CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')Boost_temp" CWD_DRIVE_ROOT_BASH=$(windowsPathAsUnix "$CWD_DRIVE_ROOT") if [ -d CWD_DRIVE_ROOT_BASH ]; then printf "Cannot continue, ${CWD_DRIVE_ROOT_BASH} aka ${CWD_DRIVE_ROOT} already exists. Please remove before re-running. "; wrappedExit 1; fi if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION ${BOOST_VER_SDK}" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost CI_EXTRA_INNO_OPTIONS="" [ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'" "${DEPS}/boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}" fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. else # Appveyor has all the boost we need already BOOST_SDK="c:/Libraries/boost_${BOOST_VER_URL}" add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.1" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. fi } cd $DEPS echo # Bullet printf "Bullet 2.89... " { cd $DEPS_INSTALL if [ -d Bullet ]; then printf -- "Exists. (No version checking) " elif [ -z $SKIP_EXTRACT ]; then rm -rf Bullet eval 7z x -y "${DEPS}/Bullet-2.89-msvc${BULLET_MSVC_YEAR}-win${BITS}-double.7z" $STRIP mv "Bullet-2.89-msvc${BULLET_MSVC_YEAR}-win${BITS}-double" Bullet fi add_cmake_opts -DBULLET_ROOT="$(real_pwd)/Bullet" echo Done. } cd $DEPS echo # FFmpeg printf "FFmpeg 4.2.2... " { cd $DEPS_INSTALL if [ -d FFmpeg ] && grep "4.2.2" FFmpeg/README.txt > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf FFmpeg eval 7z x -y "${DEPS}/ffmpeg-4.2.2-win${BITS}.zip" $STRIP eval 7z x -y "${DEPS}/ffmpeg-4.2.2-dev-win${BITS}.zip" $STRIP mv "ffmpeg-4.2.2-win${BITS}-shared" FFmpeg cp -r "ffmpeg-4.2.2-win${BITS}-dev/"* FFmpeg/ rm -rf "ffmpeg-4.2.2-win${BITS}-dev" fi export FFMPEG_HOME="$(real_pwd)/FFmpeg" for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/FFmpeg/bin/"{avcodec-58,avformat-58,avutil-56,swresample-3,swscale-5}.dll done if [ $BITS -eq 32 ]; then add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" fi echo Done. } cd $DEPS echo # MyGUI printf "MyGUI 3.4.1... " { cd $DEPS_INSTALL if [ -d MyGUI ] && \ grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_MINOR 4" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_PATCH 1" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf MyGUI eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" $STRIP [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" $STRIP mv "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}" MyGUI fi export MYGUI_HOME="$(real_pwd)/MyGUI" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then SUFFIX="_d" MYGUI_CONFIGURATION="Debug" else SUFFIX="" MYGUI_CONFIGURATION="RelWithDebInfo" fi add_runtime_dlls $CONFIGURATION "$(pwd)/MyGUI/bin/${MYGUI_CONFIGURATION}/MyGUIEngine${SUFFIX}.dll" done echo Done. } cd $DEPS echo # OpenAL printf "OpenAL-Soft 1.23.0... " { if [ -d openal-soft-1.23.0-bin ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf openal-soft-1.23.0-bin eval 7z x -y OpenAL-Soft-1.23.0.zip $STRIP fi OPENAL_SDK="$(real_pwd)/openal-soft-1.23.0-bin" add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \ -DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib" for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/openal-soft-1.23.0-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" done echo Done. } cd $DEPS echo # OSGoS printf "${OSG_ARCHIVE_NAME}... " { cd $DEPS_INSTALL if [ -d OSG ] && \ grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ grep "OPENSCENEGRAPH_MINOR_VERSION 6" OSG/include/osg/Version > /dev/null && \ grep "OPENSCENEGRAPH_PATCH_VERSION 5" OSG/include/osg/Version > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf OSG eval 7z x -y "${DEPS}/${OSG_ARCHIVE}.7z" $STRIP [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/${OSG_ARCHIVE}-sym.7z" $STRIP mv "${OSG_ARCHIVE}" OSG fi OSG_SDK="$(real_pwd)/OSG" add_cmake_opts -DOSG_DIR="$OSG_SDK" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then SUFFIX="d" SUFFIX_UPCASE="D" else SUFFIX="" SUFFIX_UPCASE="" fi if ! [ -z $OSG_MULTIVIEW_BUILD ]; then add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,zlib,libpng16}${SUFFIX}.dll \ "$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll else add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,icuuc58,libpng16,zlib}${SUFFIX}.dll \ "$(pwd)/OSG/bin/libxml2"${SUFFIX_UPCASE}.dll \ "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/icudt58.dll" if [ $CONFIGURATION == "Debug" ]; then add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_filesystem-vc141-mt-gd-1_63,boost_system-vc141-mt-gd-1_63,collada-dom2.4-dp-vc141-mt-d}.dll else add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_filesystem-vc141-mt-1_63,boost_system-vc141-mt-1_63,collada-dom2.4-dp-vc141-mt}.dll fi fi add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dae,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll done echo Done. } cd $DEPS echo # Qt printf "Qt 5.15.2... " { if [ $BITS -eq 64 ]; then SUFFIX="_64" else SUFFIX="" fi cd $DEPS_INSTALL qt_version="5.15.2" QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${QT_MSVC_YEAR}${SUFFIX}" if [ -d "Qt/${qt_version}" ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then if [ $MISSINGPYTHON -ne 0 ]; then echo "Can't be automatically installed without Python." wrappedExit 1 fi pushd "$DEPS" > /dev/null if ! [ -d 'aqt-venv' ]; then echo " Creating Virtualenv for aqt..." run_cmd python -m venv aqt-venv fi if [ -d 'aqt-venv/bin' ]; then VENV_BIN_DIR='bin' elif [ -d 'aqt-venv/Scripts' ]; then VENV_BIN_DIR='Scripts' else echo "Error: Failed to create virtualenv in expected location." wrappedExit 1 fi # check version aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] if [ $? -eq 0 ]; then echo " Installing aqt wheel into virtualenv..." run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 fi popd > /dev/null rm -rf Qt mkdir Qt cd Qt run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${QT_MSVC_YEAR}${SUFFIX}" printf " Cleaning up extraneous data... " rm -rf Qt/{aqtinstall.log,Tools} echo Done. fi cd $QT_SDK add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then DLLSUFFIX="d" else DLLSUFFIX="" fi add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. } cd $DEPS echo # SDL2 printf "SDL 2.24.0... " { if [ -d SDL2-2.24.0 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf SDL2-2.24.0 eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP fi export SDL2DIR="$(real_pwd)/SDL2-2.24.0" for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/SDL2-2.24.0/lib/x${ARCHSUFFIX}/SDL2.dll" done echo Done. } cd $DEPS echo # LZ4 printf "LZ4 1.9.2... " { if [ -d LZ4_1.9.2 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf LZ4_1.9.2 eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o$(real_pwd)/LZ4_1.9.2 $STRIP fi export LZ4DIR="$(real_pwd)/LZ4_1.9.2" add_cmake_opts -DLZ4_INCLUDE_DIR="${LZ4DIR}/include" \ -DLZ4_LIBRARY="${LZ4DIR}/lib/liblz4.lib" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then LZ4_CONFIGURATION="Debug" else SUFFIX="" LZ4_CONFIGURATION="Release" fi add_runtime_dlls $CONFIGURATION "$(pwd)/LZ4_1.9.2/bin/${LZ4_CONFIGURATION}/liblz4.dll" done echo Done. } cd $DEPS echo # LuaJIT 2.1.0-beta3 printf "LuaJIT 2.1.0-beta3... " { if [ -d LuaJIT ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf LuaJIT eval 7z x -y LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z -o$(real_pwd)/LuaJIT $STRIP fi export LUAJIT_DIR="$(real_pwd)/LuaJIT" add_cmake_opts -DLuaJit_INCLUDE_DIR="${LUAJIT_DIR}/include" \ -DLuaJit_LIBRARY="${LUAJIT_DIR}/lib/lua51.lib" for CONFIGURATION in ${CONFIGURATIONS[@]}; do add_runtime_dlls $CONFIGURATION "$(pwd)/LuaJIT/bin/lua51.dll" done echo Done. } cd $DEPS echo # ICU printf "ICU ${ICU_VER/_/.}... " { if [ -d ICU-${ICU_VER} ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf ICU-${ICU_VER} eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU-${ICU_VER} $STRIP fi ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}" add_cmake_opts -DICU_ROOT="${ICU_ROOT}" \ -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ -DICU_I18N_LIBRARY="${ICU_ROOT}/lib${BITS}/icuin.lib " \ -DICU_UC_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ -DICU_DEBUG=ON for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icudt${ICU_VER/_*/}.dll" add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuin${ICU_VER/_*/}.dll" add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuuc${ICU_VER/_*/}.dll" done echo Done. } echo cd $DEPS_INSTALL/.. echo echo "Setting up OpenMW build..." add_cmake_opts -DOPENMW_MP_BUILD=on add_cmake_opts -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" add_cmake_opts -DOPENMW_USE_SYSTEM_SQLITE3=OFF add_cmake_opts -DOPENMW_USE_SYSTEM_YAML_CPP=OFF if [ ! -z $CI ]; then case $STEP in components ) echo " Building subproject: Components." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ -DBUILD_OPENCS=no \ -DBUILD_OPENMW=no \ -DBUILD_WIZARD=no ;; openmw ) echo " Building subproject: OpenMW." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ -DBUILD_OPENCS=no \ -DBUILD_WIZARD=no ;; opencs ) echo " Building subproject: OpenCS." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ -DBUILD_OPENMW=no \ -DBUILD_WIZARD=no ;; misc ) echo " Building subprojects: Misc." add_cmake_opts -DBUILD_OPENCS=no \ -DBUILD_OPENMW=no ;; esac fi # NOTE: Disable this when/if we want to run test cases #if [ -z $CI ]; then for CONFIGURATION in ${CONFIGURATIONS[@]}; do echo "- Copying Runtime DLLs for $CONFIGURATION..." DLL_PREFIX="" if [ -z $SINGLE_CONFIG ]; then mkdir -p $CONFIGURATION DLL_PREFIX="$CONFIGURATION/" fi for DLL in ${RUNTIME_DLLS[$CONFIGURATION]}; do TARGET="$(basename "$DLL")" if [[ "$DLL" == *":"* ]]; then originalIFS="$IFS" IFS=':'; SPLIT=( ${DLL} ); IFS=$originalIFS DLL=${SPLIT[0]} TARGET=${SPLIT[1]} fi echo " ${TARGET}." cp "$DLL" "${DLL_PREFIX}$TARGET" done echo echo "- OSG Plugin DLLs..." mkdir -p ${DLL_PREFIX}osgPlugins-3.6.5 for DLL in ${OSG_PLUGINS[$CONFIGURATION]}; do echo " $(basename $DLL)." cp "$DLL" ${DLL_PREFIX}osgPlugins-3.6.5 done echo echo "- Qt Platform DLLs..." mkdir -p ${DLL_PREFIX}platforms for DLL in ${QT_PLATFORMS[$CONFIGURATION]}; do echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}platforms" done echo echo "- Qt Style DLLs..." mkdir -p ${DLL_PREFIX}styles for DLL in ${QT_STYLES[$CONFIGURATION]}; do echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}styles" done echo done #fi if [ "${BUILD_BENCHMARKS}" ]; then add_cmake_opts -DBUILD_BENCHMARKS=ON fi if [ -n "${TEST_FRAMEWORK}" ]; then add_cmake_opts -DBUILD_UNITTESTS=ON fi if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } # There are so many arguments now that I'm going to document them: # * products: allow Visual Studio or standalone build tools # * version: obvious. Awk helps make a version range by adding one. # * property installationPath: only give the installation path. # * latest: return only one result if several candidates exist. Prefer the last installed/updated # * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64) if [ -z "$MSVC_INSTALLATION_PATH" ]; then echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR" wrappedExit 1 fi echo "@\"${MSVC_INSTALLATION_PATH}\Common7\Tools\VsDevCmd.bat\" -no_logo -arch=$([ $BITS -eq 64 ] && echo "amd64" || echo "x86") -host_arch=$([ $(uname -m) == 'x86_64' ] && echo "amd64" || echo "x86")" > ActivateMSVC.bat cp "../CI/activate_msvc.sh" . sed -i "s/\$MSVC_DISPLAY_YEAR/$MSVC_DISPLAY_YEAR/g" activate_msvc.sh source ./activate_msvc.sh cp "../CI/ActivateMSVC.ps1" . sed -i "s/\$MSVC_DISPLAY_YEAR/$MSVC_DISPLAY_YEAR/g" ActivateMSVC.ps1 echo "done." echo fi if [ -z $VERBOSE ]; then printf -- "- Configuring... " else echo "- cmake .. $CMAKE_OPTS" fi RET=0 run_cmd cmake .. $CMAKE_OPTS || RET=$? if [ -z $VERBOSE ]; then if [ $RET -eq 0 ]; then echo Done. else echo Failed. fi fi if [ $RET -ne 0 ]; then wrappedExit $RET fi echo "Script completed successfully." echo "You now have an OpenMW build system at $(unixPathAsWindows "$(pwd)")" if [ -n "$ACTIVATE_MSVC" ]; then echo echo "Note: you must manually activate MSVC for the shell in which you want to do the build." echo echo "Some scripts have been created in the build directory to do so in an existing shell." echo "Bash: source activate_msvc.sh" echo "CMD: ActivateMSVC.bat" echo "PowerShell: ActivateMSVC.ps1" echo echo "You may find options to launch a Development/Native Tools/Cross Tools shell in your start menu or Visual Studio." echo if [ $(uname -m) == 'x86_64' ]; then if [ $BITS -eq 64 ]; then inheritEnvironments=msvc_x64_x64 else inheritEnvironments=msvc_x64 fi else if [ $BITS -eq 64 ]; then inheritEnvironments=msvc_x86_x64 else inheritEnvironments=msvc_x86 fi fi echo "In Visual Studio 15.3 (2017 Update 3) or later, try setting '\"inheritEnvironments\": [ \"$inheritEnvironments\" ]' in CMakeSettings.json to build in the IDE." fi wrappedExit $RET openmw-openmw-0.48.0/CI/before_script.osx.sh000077500000000000000000000016101445372753700207220ustar00rootroot00000000000000#!/bin/sh -e export CXX=clang++ export CC=clang # Silence a git warning git config --global advice.detachedHead false DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" QT_PATH=$(brew --prefix qt@5) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build cmake \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="11" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_USE_SYSTEM_SQLITE3=OFF \ -D BUILD_OPENMW=TRUE \ -D BUILD_OPENCS=TRUE \ -D BUILD_ESMTOOL=TRUE \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ -D ICU_ROOT="/usr/local/opt/icu4c" \ -G"Unix Makefiles" \ .. openmw-openmw-0.48.0/CI/build.msvc.sh000066400000000000000000000031351445372753700173330ustar00rootroot00000000000000#!/bin/bash APPVEYOR="" CI="" PACKAGE="" PLATFORM="" CONFIGURATION="" VS_VERSION="" if [ -z $PLATFORM ]; then PLATFORM=`uname -m` fi if [ -z $CONFIGURATION ]; then CONFIGURATION="Debug" fi case $VS_VERSION in 14|14.0|2015 ) GENERATOR="Visual Studio 14 2015" MSVC_YEAR="2015" MSVC_VER="14.0" ;; # 12|2013| * ) GENERATOR="Visual Studio 12 2013" MSVC_YEAR="2013" MVSC_VER="12.0" ;; esac case $PLATFORM in x64|x86_64|x86-64|win64|Win64 ) BITS=64 ;; x32|x86|i686|i386|win32|Win32 ) BITS=32 ;; esac case $CONFIGURATION in debug|Debug|DEBUG ) CONFIGURATION=Debug ;; release|Release|RELEASE ) CONFIGURATION=Release ;; relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) CONFIGURATION=RelWithDebInfo ;; esac if [ -z $APPVEYOR ]; then echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build outside of Appveyor." DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") cd $(dirname "$DIR")/.. else echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build in Appveyor." cd $APPVEYOR_BUILD_FOLDER fi BUILD_DIR="MSVC${MSVC_YEAR}_${BITS}" cd ${BUILD_DIR} which msbuild > /dev/null if [ $? -ne 0 ]; then msbuild() { /c/Program\ Files\ \(x86\)/MSBuild/${MSVC_VER}/Bin/MSBuild.exe "$@" } fi if [ -z $APPVEYOR ]; then msbuild OpenMW.sln //t:Build //p:Configuration=${CONFIGURATION} //m:8 else msbuild OpenMW.sln //t:Build //p:Configuration=${CONFIGURATION} //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" fi RET=$? if [ $RET -eq 0 ] && [ ! -z $PACKAGE ]; then msbuild PACKAGE.vcxproj //t:Build //m:8 RET=$? fi exit $RET openmw-openmw-0.48.0/CI/check_package.osx.sh000077500000000000000000000011111445372753700206200ustar00rootroot00000000000000#!/usr/bin/env bash hdiutil attach ./*.dmg -mountpoint "${TRAVIS_BUILD_DIR}/openmw-package" > /dev/null || echo "hdutil has failed" EXPECTED_PACKAGE_FILES=('Applications' 'OpenMW-CS.app' 'OpenMW.app') PACKAGE_FILES=$(ls "${TRAVIS_BUILD_DIR}/openmw-package" | LC_ALL=C sort) DIFF=$(diff <(printf "%s\n" "${EXPECTED_PACKAGE_FILES[@]}") <(printf "%s\n" "${PACKAGE_FILES[@]}")) DIFF_STATUS=$? if [[ $DIFF_STATUS -ne 0 ]]; then echo "The package should only contain an Applications symlink and two applications, see the following diff for details." >&2 echo "$DIFF" >&2 exit 1 fi openmw-openmw-0.48.0/CI/check_tabs.sh000077500000000000000000000003011445372753700173460ustar00rootroot00000000000000#!/bin/bash OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} --exclude=ui_\* apps components) if [[ $OUTPUT ]] ; then echo "Error: Tab characters found!" echo $OUTPUT exit 1 fi openmw-openmw-0.48.0/CI/deploy.osx.sh000077500000000000000000000024501445372753700173730ustar00rootroot00000000000000#!/bin/sh # This script expect the following environment variables to be set: # - OSX_DEPLOY_KEY: private SSH key, must be encoded like this before adding it to Travis secrets: https://github.com/travis-ci/travis-ci/issues/7715#issuecomment-433301692 # - OSX_DEPLOY_HOST: string specifying SSH of the following format: ssh-user@ssh-host # - OSX_DEPLOY_PORT: SSH port, it can't be a part of the host string because scp doesn't accept hosts with ports # - OSX_DEPLOY_HOST_FINGERPRINT: fingerprint of the host, can be obtained by using ssh-keygen -F [host]:port & putting it in double quotes when adding to Travis secrets SSH_KEY_PATH="$HOME/.ssh/openmw_deploy" REMOTE_PATH="\$HOME/nightly" echo "$OSX_DEPLOY_KEY" > "$SSH_KEY_PATH" chmod 600 "$SSH_KEY_PATH" echo "$OSX_DEPLOY_HOST_FINGERPRINT" >> "$HOME/.ssh/known_hosts" cd build || exit 1 DATE=$(date +'%d%m%Y') SHORT_COMMIT=$(git rev-parse --short "${TRAVIS_COMMIT}") TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg" if ! ssh -p "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" "$OSX_DEPLOY_HOST" "ls \"$REMOTE_PATH\"" | grep "$SHORT_COMMIT" > /dev/null; then scp -P "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" ./*.dmg "$OSX_DEPLOY_HOST:$REMOTE_PATH/$TARGET_FILENAME" else echo "An existing nightly build for commit ${SHORT_COMMIT} has been found, skipping upload." fi openmw-openmw-0.48.0/CI/install_debian_deps.sh000077500000000000000000000055151445372753700212570ustar00rootroot00000000000000#!/bin/bash set -euo pipefail print_help() { echo "usage: $0 [group]..." echo echo " available groups: "${!GROUPED_DEPS[@]}"" } declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" [clang_ubuntu_20_04]="binutils clang make cmake ccache curl unzip git pkg-config" # Common dependencies for building OpenMW. [openmw-deps]=" libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev 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 " # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev" [clang-tidy]="clang-tidy" # Pre-requisites for building MyGUI and OSG for static linking. # # * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev # * OSG: libgl-dev # # Plugins: # * DAE: libcollada-dom-dev libboost-system-dev libboost-filesystem-dev # * JPEG: libjpeg-dev # * PNG: libpng-dev [openmw-deps-static]=" libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev " [openmw-coverage]="gcovr" [openmw-integration-tests]=" ca-certificates gdb git git-lfs libavcodec58 libavformat58 libavutil56 libboost-filesystem1.74.0 libboost-iostreams1.74.0 libboost-program-options1.74.0 libboost-system1.74.0 libbullet3.24 libcollada-dom2.5-dp0 libicu70 libjpeg8 libluajit-5.1-2 liblz4-1 libmyguiengine3debian1v5 libopenal1 libopenscenegraph161 libpng16-16 libqt5opengl5 librecast1 libsdl2-2.0-0 libsqlite3-0 libswresample3 libswscale5 libtinyxml2.6.2v5 libyaml-cpp0.7 python3-pip xvfb " [android]="binutils build-essential cmake ccache curl unzip git pkg-config" ) if [[ $# -eq 0 ]]; then >&2 print_help exit 1 fi deps=() for group in "$@"; do if [[ ! -v GROUPED_DEPS[$group] ]]; then >&2 echo "error: unknown group ${group}" exit 1 fi deps+=(${GROUPED_DEPS[$group]}) done export APT_CACHE_DIR="${PWD}/apt-cache" export DEBIAN_FRONTEND=noninteractive set -x mkdir -pv "$APT_CACHE_DIR" apt-get update -yqq apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null add-apt-repository -y ppa:openmw/openmw apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null openmw-openmw-0.48.0/CI/org.openmw.OpenMW.devel.yaml000066400000000000000000000147741445372753700221650ustar00rootroot00000000000000--- app-id: org.openmw.OpenMW.devel runtime: org.kde.Platform runtime-version: '5.15-21.08' sdk: org.kde.Sdk command: openmw-launcher rename-appdata-file: openmw.appdata.xml finish-args: - "--share=ipc" - "--socket=x11" - "--device=all" - "--filesystem=host" - "--socket=pulseaudio" build-options: cflags: "-O2 -g" cxxflags: "-O2 -g" cleanup: - "/include" - "/lib/pkgconfig" - "/lib/cmake" - "/share/pkgconfig" - "/share/aclocal" - "/share/doc" - "/man" - "/share/man" - "/share/gtk-doc" - "/share/vala" - "*.la" - "*.a" modules: - name: boost buildsystem: simple build-commands: - ./bootstrap.sh --prefix=/app --with-libraries=filesystem,iostreams,program_options,system - ./b2 headers - ./b2 install sources: - type: archive url: https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz sha256: aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a - name: collada-dom buildsystem: cmake-ninja config-opts: - "-DOPT_COLLADA14=1" - "-DOPT_COLLADA15=0" sources: - type: archive url: https://github.com/rdiankov/collada-dom/archive/c1e20b7d6ff806237030fe82f126cb86d661f063.zip sha256: 6c51cd068c7d6760b587391884942caaac8a515d138535041e42d00d3e5c9152 - name: ffmpeg config-opts: - "--disable-static" - "--enable-shared" - "--disable-programs" - "--disable-doc" - "--disable-avdevice" - "--disable-avfilter" - "--disable-postproc" - "--disable-encoders" - "--disable-muxers" - "--disable-protocols" - "--disable-indevs" - "--disable-devices" - "--disable-filters" sources: - type: archive url: http://ffmpeg.org/releases/ffmpeg-4.3.2.tar.xz sha256: 46e4e64f1dd0233cbc0934b9f1c0da676008cad34725113fb7f802cfa84ccddb cleanup: - "/share/ffmpeg" - name: openscenegraph buildsystem: cmake-ninja config-opts: - "-DBUILD_OSG_PLUGINS_BY_DEFAULT=0" - "-DBUILD_OSG_PLUGIN_OSG=1" - "-DBUILD_OSG_PLUGIN_DDS=1" - "-DBUILD_OSG_PLUGIN_DAE=1" - "-DBUILD_OSG_PLUGIN_TGA=1" - "-DBUILD_OSG_PLUGIN_BMP=1" - "-DBUILD_OSG_PLUGIN_JPEG=1" - "-DBUILD_OSG_PLUGIN_PNG=1" - "-DBUILD_OSG_DEPRECATED_SERIALIZERS=0" - "-DBUILD_OSG_APPLICATIONS=0" - "-DCMAKE_BUILD_TYPE=Release" build-options: env: COLLADA_DIR: /app/include/collada-dom2.5 sources: - type: archive url: https://github.com/openmw/osg/archive/76e061739610bc9a3420a59e7c9395e742ce2f97.zip sha256: fa1100362eae260192819d65d90b29ec0b88fdf80e30cee677730b7a0d68637e - name: bullet # The cmake + ninja buildsystem doesn't install the required binaries correctly buildsystem: cmake config-opts: - "-DBUILD_BULLET2_DEMOS=0" - "-DBUILD_BULLET3=0" - "-DBUILD_CPU_DEMOS=0" - "-DBUILD_EXTRAS=0" - "-DBUILD_OPENGL3_DEMOS=0" - "-DBUILD_UNIT_TESTS=0" - "-DCMAKE_BUILD_TYPE=Release" - "-DUSE_GLUT=0" - "-DUSE_GRAPHICAL_BENCHMARK=0" - "-DUSE_DOUBLE_PRECISION=on" - "-DBULLET2_MULTITHREADING=on" sources: - type: archive url: https://github.com/bulletphysics/bullet3/archive/93be7e644024e92df13b454a4a0b0fcd02b21b10.zip sha256: 82968fbf20a92c51bc71ac9ee8f6381ecf3420c7cbb881ffb7bb633fa13b27f9 - name: mygui buildsystem: cmake-ninja config-opts: - "-DCMAKE_BUILD_TYPE=Release" - "-DMYGUI_RENDERSYSTEM=1" - "-DMYGUI_BUILD_DEMOS=0" - "-DMYGUI_BUILD_TOOLS=0" - "-DMYGUI_BUILD_PLUGINS=0" sources: - type: archive url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.1.tar.gz sha256: bdf730bdeb4ad89e6b8223967db01aa5274d2b93adc2c0d6aa4842faeed4de1a - name: libunshield buildsystem: cmake-ninja config-opts: - "-DCMAKE_BUILD_TYPE=Release" sources: - type: archive url: https://github.com/twogood/unshield/archive/1.4.3.tar.gz sha256: aa8c978dc0eb1158d266eaddcd1852d6d71620ddfc82807fe4bf2e19022b7bab - name: lz4 buildsystem: simple build-commands: - "make lib" - "PREFIX=/app make install" sources: - type: archive url: https://github.com/lz4/lz4/archive/refs/tags/v1.9.3.tar.gz sha256: 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1 - name: recastnavigation buildsystem: cmake-ninja config-opts: - "-DCMAKE_BUILD_TYPE=Release" - "-DRECASTNAVIGATION_DEMO=no" - "-DRECASTNAVIGATION_TESTS=no" - "-DRECASTNAVIGATION_EXAMPLES=no" sources: - type: archive url: https://github.com/recastnavigation/recastnavigation/archive/c5cbd53024c8a9d8d097a4371215e3342d2fdc87.zip sha256: 53dacfd7bead4d3b0c9a04a648caed3e7c3900e0aba765c15dee26b50f6103c6 - name: yaml-cpp buildsystem: cmake-ninja sources: - type: archive url: https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip sha256: 4d5e664a7fb2d7445fc548cc8c0e1aa7b1a496540eb382d137e2cc263e6d3ef5 - name: LuaJIT buildsystem: simple build-commands: - make install PREFIX=/app sources: - type: archive url: https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.zip sha256: 2adbe397a5b6b8ab22fa8396507ce852a2495db50e50734b3daa1ffcadd9eeb4 - name: openmw builddir: true buildsystem: cmake-ninja config-opts: - "-DBUILD_BSATOOL=no" - "-DBUILD_ESMTOOL=no" - "-DCMAKE_BUILD_TYPE=Release" - "-DICONDIR=/app/share/icons" - "-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=yes" sources: - type: dir path: .. - type: shell commands: - "sed -i 's:/wiki:/old-wiki:' ./files/openmw.appdata.xml" - "sed -i 's:>org.openmw.launcher.desktop<:>org.openmw.OpenMW.devel.desktop<:' ./files/openmw.appdata.xml" - "sed -i 's:Icon=openmw:Icon=org.openmw.OpenMW.devel.png:' ./files/org.openmw.launcher.desktop" - "sed -i 's:Icon=openmw-cs:Icon=org.openmw.OpenMW.OpenCS.devel.png:' ./files/org.openmw.cs.desktop" post-install: - "mv /app/share/applications/org.openmw.launcher.desktop /app/share/applications/org.openmw.OpenMW.devel.desktop" - "mv /app/share/applications/org.openmw.cs.desktop /app/share/applications/org.openmw.OpenMW.OpenCS.devel.desktop" - "mv /app/share/icons/openmw.png /app/share/icons/org.openmw.OpenMW.devel.png" - "mv /app/share/icons/openmw-cs.png /app/share/icons/org.openmw.OpenMW.OpenCS.devel.png" openmw-openmw-0.48.0/CI/run_integration_tests.sh000077500000000000000000000006131445372753700217170ustar00rootroot00000000000000#!/bin/bash -ex git clone --depth=1 https://gitlab.com/OpenMW/example-suite.git xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \ scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/ ls integration_tests_output/*.osg_stats.log | while read v; do scripts/osg_stats.py --stats '.*' --regexp_match < "${v}" done openmw-openmw-0.48.0/CMakeLists.txt000066400000000000000000001233511445372753700172010ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) # for link time optimization, remove if cmake version is >= 3.9 if(POLICY CMP0069) # LTO cmake_policy(SET CMP0069 NEW) endif() # for position-independent executable, remove if cmake version is >= 3.14 if(POLICY CMP0083) cmake_policy(SET CMP0083 NEW) endif() # to link with freetype library if(POLICY CMP0079) cmake_policy(SET CMP0079 NEW) endif() # don't add /W3 flag by default for MSVC if(POLICY CMP0092) cmake_policy(SET CMP0092 NEW) endif() project(OpenMW) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(GNUInstallDirs) option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) endif() # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) option(BUILD_WIZARD "Build Installation Wizard" ON) option(BUILD_MWINIIMPORTER "Build MWiniImporter" ON) option(BUILD_OPENCS "Build OpenMW Construction Set" ON) option(BUILD_ESSIMPORTER "Build ESS (Morrowind save game) importer" ON) option(BUILD_BSATOOL "Build BSA extractor" ON) option(BUILD_ESMTOOL "Build ESM inspector" ON) option(BUILD_NIFTEST "Build nif file tester" ON) option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) set(USE_QT FALSE) else() set(USE_QT TRUE) endif() # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS None Debug Release RelWithDebInfo MinSizeRel) ENDIF() if (APPLE) set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") endif (APPLE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) if (ANDROID) set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") endif() # Version message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 48) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") set(OPENMW_VERSION_COMMITDATE "") set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/stable/") set(GIT_CHECKOUT FALSE) if(EXISTS ${PROJECT_SOURCE_DIR}/.git) find_package(Git) if(GIT_FOUND) set(GIT_CHECKOUT TRUE) else(GIT_FOUND) message(WARNING "Git executable not found") endif(GIT_FOUND) if(GIT_FOUND) execute_process ( COMMAND ${GIT_EXECUTABLE} log -1 --format='%aI' WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE EXITCODE3 OUTPUT_VARIABLE OPENMW_VERSION_COMMITDATE OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT EXITCODE3) string(SUBSTRING ${OPENMW_VERSION_COMMITDATE} 1 10 OPENMW_VERSION_COMMITDATE) endif(NOT EXITCODE3) endif(GIT_FOUND) endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) include(WholeArchive) # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp") option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) if(OPENMW_USE_SYSTEM_BULLET) set(_bullet_static_default OFF) else() set(_bullet_static_default ON) endif() option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default}) option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) if(OPENMW_USE_SYSTEM_OSG) set(_osg_static_default OFF) else() set(_osg_static_default ON) endif() option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" ${_osg_static_default}) option(OPENMW_USE_SYSTEM_MYGUI "Use system provided mygui library" ON) if(OPENMW_USE_SYSTEM_MYGUI) set(_mygui_static_default OFF) else() set(_mygui_static_default ON) endif() option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_static_default}) option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF) if(OPENMW_USE_SYSTEM_RECASTNAVIGATION) set(_recastnavigation_static_default OFF) find_package(RecastNavigation REQUIRED) else() set(_recastnavigation_static_default ON) endif() option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_recastnavigation_static_default}) option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON) option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF) option(OPENMW_USE_SYSTEM_GOOGLETEST "Use system Google Test library." OFF) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) # what is necessary to build documentation IF( BUILD_DOCS ) # Builds the documentation. FIND_PACKAGE( Sphinx REQUIRED ) FIND_PACKAGE( Doxygen REQUIRED ) ENDIF() # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) if (MSVC) option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF) if (OPENMW_MP_BUILD) add_compile_options(/MP) endif() # \bigobj is required: # 1) for OPENMW_UNITY_BUILD; # 2) to compile lua bindings in components, openmw and tests, because sol3 is heavily templated. # there should be no relevant downsides to having it on: # https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file add_compile_options(/bigobj) endif() # Set up common paths if (APPLE) set(OPENMW_RESOURCE_FILES "../Resources/resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") SET(DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}" CACHE PATH "Sets the root of data directories to a non-default location") SET(GLOBAL_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/games/" CACHE PATH "Set data path prefix") SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") IF("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr") SET(GLOBAL_CONFIG_PATH "/etc/" CACHE PATH "Set config dir prefix") ELSE() SET(GLOBAL_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/etc/" CACHE PATH "Set config dir prefix") ENDIF() SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files") endif(APPLE) if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() if(MSVC) add_compile_options("/utf-8") endif() # Dependencies find_package(OpenGL REQUIRED) find_package(LZ4 REQUIRED) if (USE_QT) find_package(Qt5Core 5.12 REQUIRED) find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) endif() set(USED_OSG_COMPONENTS osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow osgAnimation) set(USED_OSG_PLUGINS osgdb_bmp osgdb_dds osgdb_freetype osgdb_jpeg osgdb_osg osgdb_png osgdb_serializers_osg osgdb_tga) option(OPENMW_USE_SYSTEM_ICU "Use system ICU library instead of internal. If disabled, requires autotools" ON) if(OPENMW_USE_SYSTEM_ICU) find_package(ICU REQUIRED COMPONENTS uc i18n data) endif() option(OPENMW_USE_SYSTEM_YAML_CPP "Use system yaml-cpp library instead of internal." ON) if(OPENMW_USE_SYSTEM_YAML_CPP) set(_yaml_cpp_static_default OFF) else() set(_yaml_cpp_static_default ON) endif() option(YAML_CPP_STATIC "Link static build of yaml-cpp into the binaries" ${_yaml_cpp_static_default}) if (OPENMW_USE_SYSTEM_YAML_CPP) find_package(yaml-cpp REQUIRED) endif() add_subdirectory(extern) # Sound setup # Require at least ffmpeg 3.2 for now SET(FFVER_OK FALSE) find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) if(FFmpeg_FOUND) SET(FFVER_OK TRUE) # Can not detect FFmpeg version on Windows for now if (NOT WIN32) if(FFmpeg_AVFORMAT_VERSION VERSION_LESS "57.56.100") message(STATUS "libavformat is too old! (${FFmpeg_AVFORMAT_VERSION}, wanted 57.56.100)") set(FFVER_OK FALSE) endif() if(FFmpeg_AVCODEC_VERSION VERSION_LESS "57.64.100") message(STATUS "libavcodec is too old! (${FFmpeg_AVCODEC_VERSION}, wanted 57.64.100)") set(FFVER_OK FALSE) endif() if(FFmpeg_AVUTIL_VERSION VERSION_LESS "55.34.100") message(STATUS "libavutil is too old! (${FFmpeg_AVUTIL_VERSION}, wanted 55.34.100)") set(FFVER_OK FALSE) endif() if(FFmpeg_SWSCALE_VERSION VERSION_LESS "4.2.100") message(STATUS "libswscale is too old! (${FFmpeg_SWSCALE_VERSION}, wanted 4.2.100)") set(FFVER_OK FALSE) endif() if(FFmpeg_SWRESAMPLE_VERSION VERSION_LESS "2.3.100") message(STATUS "libswresample is too old! (${FFmpeg_SWRESAMPLE_VERSION}, wanted 2.3.100)") set(FFVER_OK FALSE) endif() endif() if(NOT FFVER_OK AND NOT APPLE) # unable to detect on version on MacOS < 11.0 message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" ) endif() endif() if(NOT FFmpeg_FOUND) message(FATAL_ERROR "FFmpeg was not found" ) endif() if(WIN32) message("Can not detect FFmpeg version, at least the 3.2 is required" ) endif() # Required for building the FFmpeg headers add_definitions(-D__STDC_CONSTANT_MACROS) # Reqiuired for unity build add_definitions(-DMYGUI_DONT_REPLACE_NULLPTR) # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) if (USE_SYSTEM_TINYXML) find_package(TinyXML REQUIRED) add_definitions (-DTIXML_USE_STL) include_directories(SYSTEM ${TinyXML_INCLUDE_DIRS}) endif() # Platform specific if (WIN32) if(NOT MINGW) set(Boost_USE_STATIC_LIBS ON) add_definitions(-DBOOST_ALL_NO_LIB) endif(NOT MINGW) # Suppress WinMain(), provided by SDL add_definitions(-DSDL_MAIN_HANDLED) # Get rid of useless crud from windows.h add_definitions( -DNOMINMAX # name conflict with std::min, std::max -DWIN32_LEAN_AND_MEAN -DNOMB # name conflict with MWGui::MessageBox -DNOGDI # name conflict with osgAnimation::MorphGeometry::RELATIVE ) endif() if(OPENMW_USE_SYSTEM_BULLET) set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR}) set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine endif() # First, try BulletConfig-float64.cmake which comes with Debian derivatives. # This file does not define the Bullet version in a CMake-friendly way. find_package(Bullet CONFIGS BulletConfig-float64.cmake QUIET COMPONENTS BulletCollision LinearMath) if (BULLET_FOUND) string(REPLACE "." "" _bullet_version_num ${BULLET_VERSION_STRING}) if (_bullet_version_num VERSION_LESS REQUIRED_BULLET_VERSION) message(FATAL_ERROR "System bullet version too old, OpenMW requires at least ${REQUIRED_BULLET_VERSION}, got ${_bullet_version_num}") endif() # Fix the relative include: set(BULLET_INCLUDE_DIRS "${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS}") include(FindPackageMessage) find_package_message(Bullet "Found Bullet: ${BULLET_LIBRARIES} ${BULLET_VERSION_STRING}" "${BULLET_VERSION_STRING}-float64") else() find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) endif() # Only link the Bullet libraries that we need: string(REGEX MATCHALL "((optimized|debug);)?[^;]*(BulletCollision|LinearMath)[^;]*" BULLET_LIBRARIES "${BULLET_LIBRARIES}") include(cmake/CheckBulletPrecision.cmake) if (HAS_DOUBLE_PRECISION_BULLET) message(STATUS "Bullet uses double precision") else() message(FATAL_ERROR "Bullet does not uses double precision") endif() endif() if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard set(OPENMW_USE_UNSHIELD TRUE) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) endif() # Look for stdint.h include(CheckIncludeFile) check_include_file(stdint.h HAVE_STDINT_H) if(NOT HAVE_STDINT_H) unset(HAVE_STDINT_H CACHE) message(FATAL_ERROR "stdint.h was not found" ) endif() if(OPENMW_USE_SYSTEM_OSG) find_package(OpenSceneGraph 3.6.5 REQUIRED ${USED_OSG_COMPONENTS}) # Bump to 3.6.6 when released if(OSG_STATIC) find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) endif() endif() include_directories(BEFORE SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) if(OSG_STATIC) add_definitions(-DOSG_LIBRARY_STATIC) endif() include(cmake/CheckOsgMultiview.cmake) if(HAVE_MULTIVIEW) add_definitions(-DOSG_HAS_MULTIVIEW) endif(HAVE_MULTIVIEW) set(BOOST_COMPONENTS system filesystem program_options iostreams) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) if(MSVC) # boost-zlib is not present (nor needed) in vcpkg version of boost. # there, it is part of boost-iostreams instead. set(BOOST_OPTIONAL_COMPONENTS zlib) endif(MSVC) endif(WIN32) IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() set(Boost_NO_BOOST_CMAKE ON) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0) find_package(Boost 1.77.0 REQUIRED COMPONENTS atomic) endif() if(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.4.1 REQUIRED) endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) option(USE_LUAJIT "Switch Lua/LuaJit (TRUE is highly recommended)" TRUE) if(USE_LUAJIT) find_package(LuaJit REQUIRED) set(LUA_INCLUDE_DIR ${LuaJit_INCLUDE_DIR}) set(LUA_LIBRARIES ${LuaJit_LIBRARIES}) else(USE_LUAJIT) find_package(Lua 5.1 EXACT REQUIRED) add_compile_definitions(NO_LUAJIT) endif(USE_LUAJIT) # C++ library binding to Lua set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) include_directories( BEFORE SYSTEM "." ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ${LUA_INCLUDE_DIR} ${SOL_INCLUDE_DIR} ${SOL_CONFIG_DIR} ${ICU_INCLUDE_DIRS} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) endif (MYGUI_STATIC) if (APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw-Info.plist.in "${APP_BUNDLE_DIR}/Contents/Info.plist") configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) endif (APPLE) if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt" set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR}) endif () add_subdirectory(files/) # Specify build paths if (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") if (OPENMW_OSX_DEPLOYMENT) SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) endif() else (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") endif (APPLE) # Other files pack_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg "${OpenMW_BINARY_DIR}" "defaults.bin") configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml "${OpenMW_BINARY_DIR}" "openmw.appdata.xml") if (NOT APPLE) configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local "${OpenMW_BINARY_DIR}" "openmw.cfg") configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}" "openmw.cfg.install") else () configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg") endif () pack_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg "${OpenMW_BINARY_DIR}" "defaults-cs.bin") # Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate. copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}" "resources/defaultfilters") configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt "${OpenMW_BINARY_DIR}" "gamecontrollerdb.txt") if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/org.openmw.launcher.desktop "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop") configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml "${OpenMW_BINARY_DIR}/openmw.appdata.xml") configure_file(${OpenMW_SOURCE_DIR}/files/org.openmw.cs.desktop "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop") endif() if(OPENMW_LTO_BUILD) if(NOT CMAKE_VERSION VERSION_LESS 3.9) include(CheckIPOSupported) check_ipo_supported(RESULT HAVE_IPO OUTPUT HAVE_IPO_OUTPUT) if(HAVE_IPO) message(STATUS "LTO enabled for Release configuration.") set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) else() message(WARNING "Requested option OPENMW_LTO_BUILD not supported by this compiler: ${HAVE_IPO_OUTPUT}") if(MSVC) message(STATUS "Note: Flags used to be set manually for this setting with MSVC. We now rely on CMake for this. Upgrade CMake to at least 3.13 to re-enable this setting.") endif() endif() else() message(WARNING "Requested option OPENMW_LTO_BUILD not supported by this cmake version: ${CMAKE_VERSION}. Upgrade CMake to at least 3.9 to enable support for certain compilers. Newer CMake versions support more compilers.") endif() endif() if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) 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(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-potentially-evaluated-expression") endif () endif() if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") endif() endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/Base64) if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() if (OPENMW_CXX_FLAGS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}") endif() # Components add_subdirectory (components) # Apps and tools if (BUILD_OPENMW) add_subdirectory( apps/openmw ) endif() if (BUILD_BSATOOL) add_subdirectory( apps/bsatool ) endif() if (BUILD_ESMTOOL) add_subdirectory( apps/esmtool ) endif() if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() if (BUILD_ESSIMPORTER) add_subdirectory (apps/essimporter ) endif() if (BUILD_OPENCS) add_subdirectory (apps/opencs) endif() if (BUILD_WIZARD) add_subdirectory(apps/wizard) endif() if (BUILD_NIFTEST) add_subdirectory(apps/niftest) endif(BUILD_NIFTEST) # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) endif() if (BUILD_BENCHMARKS) add_subdirectory(apps/benchmarks) endif() if (BUILD_NAVMESHTOOL) add_subdirectory(apps/navmeshtool) endif() if (BUILD_BULLETOBJECTTOOL) add_subdirectory( apps/bulletobjecttool ) endif() if (WIN32) if (MSVC) foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(ProjectDir)$(Configuration)" ) endforeach( OUTPUTCONFIG ) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$:_CONSOLE>) elseif (BUILD_OPENMW) # Turn off debug console, debug output will be written to visual studio output instead set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() if (BUILD_OPENMW) # Release builds don't use the debug console set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") 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 ) if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" ) set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 4866 # compiler may not enforce left-to-right evaluation order for call ) endif() if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.1" ) set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 4275 # non dll-interface class 'MyGUI::delegates::IDelegateUnlink' used as base for dll-interface class 'MyGUI::Widget' ) endif() foreach(d ${WARNINGS_DISABLE}) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) if(OPENMW_MSVC_WERROR) set(WARNINGS "${WARNINGS} /WX") endif() set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${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) set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_ESMTOOL) set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_ESSIMPORTER) set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_LAUNCHER) set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_MWINIIMPORTER) set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_OPENCS) set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_OPENMW) set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_WIZARD) set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_UNITTESTS) set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_BENCHMARKS) set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_NAVMESHTOOL) set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_BULLETOBJECTTOOL) set(WARNINGS "${WARNINGS} ${MT_BUILD}") set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file #set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") #set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") endif() if (BUILD_OPENMW AND APPLE) target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1) target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1) endif() # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE) if (CMAKE_VERSION VERSION_LESS 3.19) message(FATAL_ERROR "macOS packaging requires CMake 3.19 or higher to sign macOS app bundles.") endif () get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) get_filename_component(QT_COCOA_PLUGIN_NAME "${QT_COCOA_PLUGIN_PATH}" NAME) configure_file("${QT_COCOA_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY) if (BUILD_OPENCS) get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME) set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) set(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) set(INSTALLED_OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_NAME}") set(INSTALLED_OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${OPENCS_BUNDLE_NAME}") install(CODE " set(BU_CHMOD_BUNDLE_ITEMS ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) include(BundleUtilities) cmake_minimum_required(VERSION 3.1) " COMPONENT Runtime) set(ABSOLUTE_PLUGINS "") set(OSGPlugins_DONT_FIND_DEPENDENCIES 1) find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) foreach (PLUGIN_NAME ${USED_OSG_PLUGINS}) string(TOUPPER ${PLUGIN_NAME} PLUGIN_NAME_UC) if(${PLUGIN_NAME_UC}_LIBRARY_RELEASE) set(PLUGIN_ABS ${${PLUGIN_NAME_UC}_LIBRARY_RELEASE}) elseif(${PLUGIN_NAME_UC}_LIBRARY) set(PLUGIN_ABS ${${PLUGIN_NAME_UC}_LIBRARY}) else() message(FATAL_ERROR "Can't find library file for ${PLUGIN_NAME}") # We used to construct the path manually from OSGPlugins_LIB_DIR and the plugin name. # Maybe that could be restored as a fallback? endif() set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) endforeach () set(OSG_PLUGIN_PREFIX_DIR "osgPlugins-${OPENSCENEGRAPH_VERSION}") # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX}) # and returns list of install paths for all installed plugins function (install_plugins_for_bundle bundle_path plugins_var) set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${OSG_PLUGIN_PREFIX_DIR}") set(PLUGINS "") set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}") foreach (PLUGIN ${ABSOLUTE_PLUGINS}) get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME) get_filename_component(PLUGIN_RELATIVE_WE ${PLUGIN} NAME_WE) set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}") set(PLUGINS ${PLUGINS} "${PLUGIN_DYLIB_IN_BUNDLE}") install(CODE " copy_resolved_item_into_bundle(\"${PLUGIN}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\") " COMPONENT Runtime) endforeach () set(${plugins_var} ${PLUGINS} PARENT_SCOPE) endfunction (install_plugins_for_bundle) install_plugins_for_bundle("${APP_BUNDLE_NAME}" PLUGINS) install_plugins_for_bundle("${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") install(CODE " function(gp_item_default_embedded_path_override item default_embedded_path_var) if (\${item} MATCHES ${OSG_PLUGIN_PREFIX_DIR}) set(path \"@executable_path/../PlugIns/${OSG_PLUGIN_PREFIX_DIR}\") set(\${default_embedded_path_var} \"\${path}\" PARENT_SCOPE) endif() endfunction() fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\") fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") " COMPONENT Runtime) set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_SOURCE_DIR}/cmake/SignMacApplications.cmake) include(CPack) elseif(NOT APPLE) get_generator_is_multi_config(multi_config) if (multi_config) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") else () SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () if(WIN32) INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll" PATTERN "deps" EXCLUDE PATTERN "apps" EXCLUDE PATTERN "CMakeFiles" EXCLUDE PATTERN "components" EXCLUDE PATTERN "docs" EXCLUDE PATTERN "extern" EXCLUDE PATTERN "files" EXCLUDE PATTERN "Testing" EXCLUDE) INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb" PATTERN "deps" EXCLUDE PATTERN "apps" EXCLUDE PATTERN "CMakeFiles" EXCLUDE PATTERN "components" EXCLUDE PATTERN "docs" EXCLUDE PATTERN "extern" EXCLUDE PATTERN "files" EXCLUDE PATTERN "Testing" EXCLUDE) INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".") INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") SET(CPACK_PACKAGE_NAME "OpenMW") SET(CPACK_PACKAGE_VENDOR "OpenMW.org") SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW") IF(BUILD_LAUNCHER) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher") ENDIF(BUILD_LAUNCHER) IF(BUILD_OPENCS) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard") ENDIF(BUILD_WIZARD) SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\" ") SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}") SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe") SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe") SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") if(EXISTS ${VCREDIST32}) INSTALL(FILES ${VCREDIST32} DESTINATION "redist") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q /norestart'" ) endif(EXISTS ${VCREDIST32}) SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe") if(EXISTS ${VCREDIST64}) INSTALL(FILES ${VCREDIST64} DESTINATION "redist") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q /norestart'" ) endif(EXISTS ${VCREDIST64}) SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe") if(EXISTS ${OALREDIST}) INSTALL(FILES ${OALREDIST} DESTINATION "redist") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" ) endif(EXISTS ${OALREDIST}) if(CMAKE_CL_64) SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64") endif() include(CPack) else(WIN32) # Linux installation # Install binaries IF(BUILD_OPENMW) INSTALL(PROGRAMS "$" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENMW) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) IF(BUILD_BSATOOL) INSTALL(PROGRAMS "${INSTALL_SOURCE}/bsatool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BSATOOL) IF(BUILD_ESMTOOL) INSTALL(PROGRAMS "${INSTALL_SOURCE}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) IF(BUILD_NIFTEST) INSTALL(PROGRAMS "${INSTALL_SOURCE}/niftest" DESTINATION "${BINDIR}" ) ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) IF(BUILD_ESSIMPORTER) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-essimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-cs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) if(BUILD_NAVMESHTOOL) install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" ) endif() IF(BUILD_BULLETOBJECTTOOL) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-bulletobjecttool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BULLETOBJECTTOOL) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install global configuration files INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") endif(WIN32) endif(OPENMW_OSX_DEPLOYMENT AND APPLE) # Doxygen Target -- simply run 'make doc' or 'make doc_pages' # output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen" # output directory for 'make doc_pages' is "${DOXYGEN_PAGES_OUTPUT_DIR}" if defined # or "${OpenMW_BINARY_DIR}/docs/Pages" otherwise find_package(Doxygen) if (DOXYGEN_FOUND) # determine output directory for doc_pages if (NOT DEFINED DOXYGEN_PAGES_OUTPUT_DIR) set(DOXYGEN_PAGES_OUTPUT_DIR "${OpenMW_BINARY_DIR}/docs/Pages") endif () configure_file(${OpenMW_SOURCE_DIR}/docs/Doxyfile.cmake ${OpenMW_BINARY_DIR}/docs/Doxyfile @ONLY) configure_file(${OpenMW_SOURCE_DIR}/docs/DoxyfilePages.cmake ${OpenMW_BINARY_DIR}/docs/DoxyfilePages @ONLY) add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${OpenMW_BINARY_DIR}/docs/Doxyfile WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating Doxygen documentation at ${OpenMW_BINARY_DIR}/docs/Doxygen" VERBATIM) add_custom_target(doc_pages ${DOXYGEN_EXECUTABLE} ${OpenMW_BINARY_DIR}/docs/DoxyfilePages WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) endif () openmw-openmw-0.48.0/CONTRIBUTING.md000066400000000000000000000247421445372753700166760ustar00rootroot00000000000000How to contribute to OpenMW ======================= Not sure what to do with all your free time? Pick out a task from here: https://gitlab.com/OpenMW/openmw/issues Currently, we are focused on completing the MW game experience and general polishing. Features out of this scope may be approved in some cases, but you should probably start a discussion first. Note: * Tasks set to 'openmw-future' are usually out of the current scope of the project and can't be started yet. * Bugs that are not 'Confirmed' should be confirmed first. * Often, it's best to start a discussion about possible solutions before you jump into coding, especially for larger features. Aside from coding, you can also help by triaging the issues list. Check for bugs that are 'Unconfirmed' and try to confirm them on your end, working out any details that may be necessary. Check for bugs that do not conform to [Bug reporting guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) and improve them to do so! There are various [Tools](https://wiki.openmw.org/index.php?title=Tools) to facilitate testing/development. Pull Request Guidelines ======================= To facilitate the review process, your pull request description should include the following, if applicable: * A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://gitlab.com/OpenMW/openmw/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead. * Summary of the changes made. * Reasoning / motivation behind the change. * What testing you have carried out to verify the change. Furthermore, we advise to: * Avoid stuffing unrelated commits into one pull request. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. * Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. * If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). * Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway. * Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged. * When pulling changes from master, prefer rebase over merge. Consider using a merge if there are conflicts or for long-running PRs. Guidelines for original engine "fixes" ================================= From time to time you may be tempted to "fix" what you think was a "bug" in the original game engine. Unfortunately, the definition of what is a "bug" is not so clear. Consider that your "bug" is actually a feature unless proven otherwise: * We have no way of knowing what the original developers really intended (short of asking them, good luck with that). * What may seem like an illogical mechanic can actually be part of an attempt to balance the game. * Many people will actually like these "bugs" because that is what they remember the game for. * Exploits may be part of the fun of an open-world game - they reward knowledge with power. There are too many of them to plug them all, anyway. OpenMW, in its default configuration, is meant to be a faithful reimplementation of Morrowind, minus things like crash bugs, stability issues and severe design errors. However, we try to avoid touching anything that affects the core gameplay, the balancing of the game or introduces incompatibilities with existing mod content. That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be: * Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells). * Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder). * Bugs that were fixed in an official patch for Morrowind. Feature additions policy ===================== We get it, you have waited so long for feature XYZ to be available in Morrowind and now that OpenMW is here you can not wait to implement your ingenious idea and share it with the world. Unfortunately, since maintaining features comes at a cost and our resources are limited, we have to be a little selective in what features we allow into the main repository. Generally: * Features should be as generic and non-redundant as possible. * Any feature that is also possible with modding should be done as a mod instead. * In the future, OpenMW plans to expand the scope of what is possible with modding, e.g. by moving certain game logic into editable scripts. * Currently, modders can edit OpenMW's GUI skins and layout XML files, although there are still a few missing hooks (e.g. scripting support) in order to make this into a powerful way of modding. * If a feature introduces new game UI strings, that reduces its chance of being accepted because we do not currently have any way of localizing these to the user's Morrowind installation language. If you are in doubt of your feature being within our scope, it is probably best to start a forum discussion first. See the [settings documentation](https://openmw.readthedocs.io/en/stable/reference/modding/settings/index.html) and [Features list](https://wiki.openmw.org/index.php?title=Features) for some examples of features that were deemed acceptable. Reviewing pull requests ======================= We welcome any help in reviewing open PRs. You don't need to be a developer to comment on new features. We also encourage ["junior" developers to review senior's work](https://pagefault.blog/2018/04/08/why-junior-devs-should-review-seniors-commits/). This review process is divided into two sections because complaining about code or style issues hardly makes sense until the functionality of the PR is deemed OK. Anyone can help with the Functionality Review while most parts of the Code Review require you to have programming experience. In addition to the checklist below, make sure to check that the Pull Request Guidelines (first half of this document) were followed. First review ============ 1. Ask for missing information or clarifications. Compare against the project's design goals and roadmap. 2. Check if the automated tests are passing. If they are not, make the PR author aware of the issue and potentially link to the error line on Travis CI or Appveyor. If the error appears unrelated to the PR and/or the master branch is failing with the same error, our CI has broken and needs to be fixed independently of any open PRs. Raise this issue on the forums, bug tracker or with the relevant maintainer. The PR can be merged in this case as long as you've built it yourself to make sure it does build. 3. Make sure that the new code has been tested thoroughly, either by asking the author or, preferably, testing yourself. In a complex project like OpenMW, it is easy to make mistakes, typos, etc. Therefore, prefer testing all code changes, no matter how trivial they look. When you have tested a PR that no one has tested so far, post a comment letting us know. 4. On long running PRs, request the author to update its description with the current state or a checklist of things left to do. Code Review =========== 1. Carefully review each line for issues the author may not have thought of, paying special attention to 'special' cases. Often, people build their code with a particular mindset and forget about other configurations or unexpected interactions. 2. If any changes are workarounds for an issue in an upstream library, make sure the issue was reported upstream so we can eventually drop the workaround when the issue is fixed and the new version of that library is a build dependency. 3. Make sure PRs do not turn into arguments about hardly related issues. If the PR author disagrees with an established part of the project (e.g. supported build environments), they should open a forum discussion or bug report and in the meantime adjust the PR to adhere to the established way, rather than leaving the PR hanging on a dispute. 4. Check if the code matches our style guidelines. 5. Check to make sure the commit history is clean. Squashing should be considered if the review process has made the commit history particularly long. Commits that don't build should be avoided because they are a nuisance for ```git bisect```. Merging ======= To be able to merge PRs, commit privileges are required. If you do not have the privileges, just ping someone that does have them with a short comment like "Looks good to me @user". The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description. Dealing with regressions ======================== The master branch should always be in a working state that is not worse than the previous release in any way. If a regression is found, the first and foremost priority should be to get the regression fixed quickly, either by reverting the change that caused it or finding a better solution. Please avoid leaving the project in the 'broken' state for an extensive period of time while proper solutions are found. If the solution takes more than a day or so then it is usually better to revert the offending change first and reapply it later when fixed. Other resources =============== [GitHub blog - how to write the perfect pull request](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/) openmw-openmw-0.48.0/LICENSE000066400000000000000000001045111445372753700154430ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . openmw-openmw-0.48.0/README.md000066400000000000000000000167311445372753700157230ustar00rootroot00000000000000OpenMW ====== OpenMW is an open-source open-world RPG game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * Version: 0.48.0 * License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information) * Website: https://www.openmw.org * IRC: #openmw on irc.libera.chat * Discord: https://discord.gg/bWuqq2e Font Licenses: * DejaVuLGCSansMono.ttf: custom (see [files/data/fonts/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DejaVuFontLicense.txt) for more information) * DemonicLetters.ttf: SIL Open Font License (see [files/data/fonts/DemonicLettersFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DemonicLettersFontLicense.txt) for more information) * MysticCards.ttf: SIL Open Font License (see [files/data/fonts/MysticCardsFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/MysticCardsFontLicense.txt) for more information) Current Status -------------- The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. Getting Started --------------- * [Official forums](https://forum.openmw.org/) * [Installation instructions](https://openmw.readthedocs.io/en/latest/manuals/installation/index.html) * [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup) * [Testing the game](https://wiki.openmw.org/index.php?title=Testing) * [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted) * [Report a bug](https://gitlab.com/OpenMW/openmw/issues) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! * [Known issues](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=Bug) The data path ------------- The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install). Command line options -------------------- Syntax: openmw Allowed options: --help print help message --version print version information and quit --data arg (=data) set data directories (later directories have higher priority) --data-local arg set local data directory (highest priority) --fallback-archive arg (=fallback-archive) set fallback BSA archives (later archives have higher priority) --resources arg (=resources) set resources directory --start arg set initial cell --content arg content file(s): esm/esp, or omwgame/omwaddon --no-sound [=arg(=1)] (=0) disable all sounds --script-verbose [=arg(=1)] (=0) verbose script output --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scripts) at startup --script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup --script-console [=arg(=1)] (=0) enable console-only script functionality --script-run arg select a file containing a list of console commands that is executed on startup --script-warn [=arg(=1)] (=1) handling of warnings when compiling scripts 0 - ignore warning 1 - show warning but consider script as correctly compiled anyway 2 - treat warnings as errors --script-blacklist arg ignore the specified script (if the use of the blacklist is enabled) --script-blacklist-use [=arg(=1)] (=1) enable script blacklisting --load-savegame arg load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory) --skip-menu [=arg(=1)] (=0) skip main menu on game startup --new-game [=arg(=1)] (=0) run new game sequence (ignored if skip-menu=0) --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding) --encoding arg (=win1252) Character encoding used in OpenMW game messages: win1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages win1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages win1252 - Western European (Latin) alphabet, used by default --fallback arg fallback values --no-grab Don't grab mouse cursor --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG image and XML file in current directory --activate-dist arg (=-1) activation distance override --random-seed arg (=) seed value for random number generator openmw-openmw-0.48.0/apps/000077500000000000000000000000001445372753700153775ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/benchmarks/000077500000000000000000000000001445372753700175145ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/benchmarks/CMakeLists.txt000066400000000000000000000012661445372753700222610ustar00rootroot00000000000000if(OPENMW_USE_SYSTEM_BENCHMARK) find_package(benchmark REQUIRED) endif() openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp) target_compile_features(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE cxx_std_17) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components) if (UNIX AND NOT APPLE) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ) endif() openmw-openmw-0.48.0/apps/benchmarks/detournavigator/000077500000000000000000000000001445372753700227315ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/benchmarks/detournavigator/navmeshtilescache.cpp000066400000000000000000000236261445372753700271340ustar00rootroot00000000000000#include #include #include #include #include namespace { using namespace DetourNavigator; struct Key { AgentBounds mAgentBounds; TilePosition mTilePosition; RecastMesh mRecastMesh; }; struct Item { Key mKey; PreparedNavMeshData mValue; }; template osg::Vec2i generateVec2i(int max, Random& random) { std::uniform_int_distribution distribution(0, max); return osg::Vec2i(distribution(random), distribution(random)); } template osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random) { std::uniform_int_distribution distribution(min, max); return osg::Vec3f(distribution(random), distribution(random), distribution(random)); } template void generateVertices(OutputIterator out, std::size_t number, Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); }); } template void generateIndices(OutputIterator out, int max, std::size_t number, Random& random) { std::uniform_int_distribution distribution(0, max); std::generate_n(out, number - number % 3, [&] { return distribution(random); }); } AreaType toAreaType(int index) { switch (index) { case 0: return AreaType_null; case 1: return AreaType_water; case 2: return AreaType_door; case 3: return AreaType_pathgrid; case 4: return AreaType_ground; } return AreaType_null; } template AreaType generateAreaType(Random& random) { std::uniform_int_distribution distribution(0, 4); return toAreaType(distribution(random)); } template void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random) { std::generate_n(out, triangles, [&] { return generateAreaType(random); }); } template void generateWater(OutputIterator out, std::size_t count, Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, count, [&] { return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}}; }); } template Mesh generateMesh(std::size_t triangles, Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::vector vertices; std::vector indices; std::vector areaTypes; if (distribution(random) < 0.939) { generateVertices(std::back_inserter(vertices), triangles * 2.467, random); generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, vertices.size() * 1.279, random); generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); } return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); } template Heightfield generateHeightfield(Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); Heightfield result; result.mCellPosition = generateVec2i(1000, random); result.mCellSize = ESM::Land::REAL_SIZE; result.mMinHeight = distribution(random); result.mMaxHeight = result.mMinHeight + 1.0; result.mLength = static_cast(ESM::Land::LAND_SIZE); std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&] { return distribution(random); }); result.mOriginalSize = ESM::Land::LAND_SIZE; result.mMinX = 0; result.mMinY = 0; return result; } template FlatHeightfield generateFlatHeightfield(Random& random) { std::uniform_real_distribution distribution(0.0, 1.0); FlatHeightfield result; result.mCellPosition = generateVec2i(1000, random); result.mCellSize = ESM::Land::REAL_SIZE; result.mHeight = distribution(random); return result; } template Key generateKey(std::size_t triangles, Random& random) { const CollisionShapeType agentShapeType = CollisionShapeType::Aabb; const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); const TilePosition tilePosition = generateVec2i(10000, random); const std::size_t generation = std::uniform_int_distribution(0, 100)(random); const std::size_t revision = std::uniform_int_distribution(0, 10000)(random); Mesh mesh = generateMesh(triangles, random); std::vector water; generateWater(std::back_inserter(water), 1, random); RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), {generateHeightfield(random)}, {generateFlatHeightfield(random)}, {}); return Key {AgentBounds {agentShapeType, agentHalfExtents}, tilePosition, std::move(recastMesh)}; } constexpr std::size_t trianglesPerTile = 239; template void generateKeys(OutputIterator out, std::size_t count, Random& random) { std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); }); } template void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache) { std::size_t size = cache.getStats().mNavMeshCacheSize; while (true) { Key key = generateKey(trianglesPerTile, random); cache.set(key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); *out++ = std::move(key); const std::size_t newSize = cache.getStats().mNavMeshCacheSize; if (size >= newSize) break; size = newSize; } } template void getFromFilledCache(benchmark::State& state) { NavMeshTilesCache cache(maxCacheSize); std::minstd_rand random; std::vector keys; fillCache(std::back_inserter(keys), random, cache); generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); std::size_t n = 0; while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); benchmark::DoNotOptimize(result); } } void getFromFilledCache_1m_100hit(benchmark::State& state) { getFromFilledCache<1 * 1024 * 1024, 100>(state); } void getFromFilledCache_4m_100hit(benchmark::State& state) { getFromFilledCache<4 * 1024 * 1024, 100>(state); } void getFromFilledCache_16m_100hit(benchmark::State& state) { getFromFilledCache<16 * 1024 * 1024, 100>(state); } void getFromFilledCache_64m_100hit(benchmark::State& state) { getFromFilledCache<64 * 1024 * 1024, 100>(state); } void getFromFilledCache_1m_70hit(benchmark::State& state) { getFromFilledCache<1 * 1024 * 1024, 70>(state); } void getFromFilledCache_4m_70hit(benchmark::State& state) { getFromFilledCache<4 * 1024 * 1024, 70>(state); } void getFromFilledCache_16m_70hit(benchmark::State& state) { getFromFilledCache<16 * 1024 * 1024, 70>(state); } void getFromFilledCache_64m_70hit(benchmark::State& state) { getFromFilledCache<64 * 1024 * 1024, 70>(state); } template void setToBoundedNonEmptyCache(benchmark::State& state) { NavMeshTilesCache cache(maxCacheSize); std::minstd_rand random; std::vector keys; fillCache(std::back_inserter(keys), random, cache); generateKeys(std::back_inserter(keys), keys.size() * 2, random); std::reverse(keys.begin(), keys.end()); std::size_t n = 0; while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; const auto result = cache.set(key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); benchmark::DoNotOptimize(result); } } void setToBoundedNonEmptyCache_1m(benchmark::State& state) { setToBoundedNonEmptyCache<1 * 1024 * 1024>(state); } void setToBoundedNonEmptyCache_4m(benchmark::State& state) { setToBoundedNonEmptyCache<4 * 1024 * 1024>(state); } void setToBoundedNonEmptyCache_16m(benchmark::State& state) { setToBoundedNonEmptyCache<16 * 1024 * 1024>(state); } void setToBoundedNonEmptyCache_64m(benchmark::State& state) { setToBoundedNonEmptyCache<64 * 1024 * 1024>(state); } } // namespace BENCHMARK(getFromFilledCache_1m_100hit); BENCHMARK(getFromFilledCache_4m_100hit); BENCHMARK(getFromFilledCache_16m_100hit); BENCHMARK(getFromFilledCache_64m_100hit); BENCHMARK(getFromFilledCache_1m_70hit); BENCHMARK(getFromFilledCache_4m_70hit); BENCHMARK(getFromFilledCache_16m_70hit); BENCHMARK(getFromFilledCache_64m_70hit); BENCHMARK(setToBoundedNonEmptyCache_1m); BENCHMARK(setToBoundedNonEmptyCache_4m); BENCHMARK(setToBoundedNonEmptyCache_16m); BENCHMARK(setToBoundedNonEmptyCache_64m); BENCHMARK_MAIN(); openmw-openmw-0.48.0/apps/bsatool/000077500000000000000000000000001445372753700170425ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/bsatool/CMakeLists.txt000066400000000000000000000010161445372753700216000ustar00rootroot00000000000000set(BSATOOL bsatool.cpp ) source_group(apps\\bsatool FILES ${BSATOOL}) # Main executable openmw_add_executable(bsatool ${BSATOOL} ) target_link_libraries(bsatool ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(bsatool gcov) endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(bsatool PRIVATE ) endif() openmw-openmw-0.48.0/apps/bsatool/bsatool.cpp000066400000000000000000000232121445372753700212110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #define BSATOOL_VERSION 1.1 // Create local aliases for brevity namespace bpo = boost::program_options; namespace bfs = boost::filesystem; struct Arguments { std::string mode; std::string filename; std::string extractfile; std::string addfile; std::string outdir; bool longformat; bool fullpath; }; bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n" "Usages:\n" " bsatool list [-l] archivefile\n" " List the files presents in the input archive.\n\n" " bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n" " Extract a file from the input archive.\n\n" " bsatool extractall archivefile [output_directory]\n" " Extract all files from the input archive.\n\n" " bsatool add [-a] archivefile file_to_add\n" " Add a file to the input archive.\n\n" " bsatool create [-c] archivefile\n" " Create an archive.\n\n" "Allowed options"); desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") ("long,l", "Include extra information in archive listing.") ("full-path,f", "Create directory hierarchy on file extraction " "(always true for extractall).") ; // input-file is hidden and used as a positional argument bpo::options_description hidden("Hidden Options"); hidden.add_options() ( "mode,m", bpo::value(), "bsatool mode") ( "input-file,i", bpo::value< std::vector >(), "input file") ; bpo::positional_options_description p; p.add("mode", 1).add("input-file", 3); // there might be a better way to do this bpo::options_description all; all.add(desc).add(hidden); bpo::variables_map variables; try { bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(all).positional(p).run(); bpo::store(valid_opts, variables); } catch(std::exception &e) { std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } bpo::notify(variables); if (variables.count ("help")) { std::cout << desc << std::endl; return false; } if (variables.count ("version")) { std::cout << "BSATool version " << BSATOOL_VERSION << std::endl; return false; } if (!variables.count("mode")) { std::cout << "ERROR: no mode specified!\n\n" << desc << std::endl; return false; } info.mode = variables["mode"].as(); if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create")) { std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << std::endl; return false; } if (!variables.count("input-file")) { std::cout << "\nERROR: missing BSA archive\n\n" << desc << std::endl; return false; } info.filename = variables["input-file"].as< std::vector >()[0]; // Default output to the working directory info.outdir = "."; if (info.mode == "extract") { if (variables["input-file"].as< std::vector >().size() < 2) { std::cout << "\nERROR: file to extract unspecified\n\n" << desc << std::endl; return false; } if (variables["input-file"].as< std::vector >().size() > 1) info.extractfile = variables["input-file"].as< std::vector >()[1]; if (variables["input-file"].as< std::vector >().size() > 2) info.outdir = variables["input-file"].as< std::vector >()[2]; } else if (info.mode == "add") { if (variables["input-file"].as< std::vector >().size() < 1) { std::cout << "\nERROR: file to add unspecified\n\n" << desc << std::endl; return false; } if (variables["input-file"].as< std::vector >().size() > 1) info.addfile = variables["input-file"].as< std::vector >()[1]; } else if (variables["input-file"].as< std::vector >().size() > 1) info.outdir = variables["input-file"].as< std::vector >()[1]; info.longformat = variables.count("long") != 0; info.fullpath = variables.count("full-path") != 0; return true; } template int list(std::unique_ptr& bsa, Arguments& info) { // List all files const auto &files = bsa->getList(); for (const auto& file : files) { if(info.longformat) { // Long format std::ios::fmtflags f(std::cout.flags()); std::cout << std::setw(50) << std::left << file.name(); std::cout << std::setw(8) << std::left << std::dec << file.fileSize; std::cout << "@ 0x" << std::hex << file.offset << std::endl; std::cout.flags(f); } else std::cout << file.name() << std::endl; } return 0; } template int extract(std::unique_ptr& bsa, Arguments& info) { std::string archivePath = info.extractfile; Misc::StringUtils::replaceAll(archivePath, "/", "\\"); std::string extractPath = info.extractfile; Misc::StringUtils::replaceAll(extractPath, "\\", "/"); Files::IStreamPtr stream; // Get a stream for the file to extract for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it) { if (Misc::StringUtils::ciEqual(std::string(it->name()), archivePath)) { stream = bsa->getFile(&*it); break; } } if (!stream) { std::cout << "ERROR: file '" << archivePath << "' not found\n"; std::cout << "In archive: " << info.filename << std::endl; return 3; } // Get the target path (the path the file will be extracted to) bfs::path relPath (extractPath); bfs::path outdir (info.outdir); bfs::path target; if (info.fullpath) target = outdir / relPath; else target = outdir / relPath.filename(); // Create the directory hierarchy bfs::create_directories(target.parent_path()); bfs::file_status s = bfs::status(target.parent_path()); if (!bfs::is_directory(s)) { std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; return 3; } bfs::ofstream out(target, std::ios::binary); // Write the file to disk std::cout << "Extracting " << info.extractfile << " to " << target << std::endl; out << stream->rdbuf(); out.close(); return 0; } template int extractAll(std::unique_ptr& bsa, Arguments& info) { for (const auto &file : bsa->getList()) { std::string extractPath(file.name()); Misc::StringUtils::replaceAll(extractPath, "\\", "/"); // Get the target path (the path the file will be extracted to) bfs::path target (info.outdir); target /= extractPath; // Create the directory hierarchy bfs::create_directories(target.parent_path()); bfs::file_status s = bfs::status(target.parent_path()); if (!bfs::is_directory(s)) { std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; return 3; } // Get a stream for the file to extract Files::IStreamPtr data = bsa->getFile(&file); bfs::ofstream out(target, std::ios::binary); // Write the file to disk std::cout << "Extracting " << target << std::endl; out << data->rdbuf(); out.close(); } return 0; } template int add(std::unique_ptr& bsa, Arguments& info) { boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in); bsa->addFile(info.addfile, stream); return 0; } template int call(Arguments& info) { std::unique_ptr bsa = std::make_unique(); if (info.mode == "create") { bsa->open(info.filename); return 0; } bsa->open(info.filename); if (info.mode == "list") return list(bsa, info); else if (info.mode == "extract") return extract(bsa, info); else if (info.mode == "extractall") return extractAll(bsa, info); else if (info.mode == "add") return add(bsa, info); else { std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; return 1; } } int main(int argc, char** argv) { try { Arguments info; if (!parseOptions(argc, argv, info)) return 1; // Open file Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename); if (bsaVersion == Bsa::BSAVER_COMPRESSED) return call(info); else return call(info); } catch (std::exception& e) { std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; return 2; } } openmw-openmw-0.48.0/apps/bulletobjecttool/000077500000000000000000000000001445372753700207535ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/bulletobjecttool/CMakeLists.txt000066400000000000000000000012041445372753700235100ustar00rootroot00000000000000set(BULLETMESHTOOL main.cpp ) source_group(apps\\bulletobjecttool FILES ${BULLETMESHTOOL}) openmw_add_executable(openmw-bulletobjecttool ${BULLETMESHTOOL}) target_link_libraries(openmw-bulletobjecttool ${Boost_PROGRAM_OPTIONS_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions(--coverage) target_link_libraries(openmw-bulletobjecttool gcov) endif() if (WIN32) install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-bulletobjecttool PRIVATE ) endif() openmw-openmw-0.48.0/apps/bulletobjecttool/main.cpp000066400000000000000000000204731445372753700224110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { namespace bpo = boost::program_options; using StringsVector = std::vector; constexpr std::string_view applicationName = "BulletObjectTool"; bpo::options_description makeOptionsDescription() { using Fallback::FallbackMap; bpo::options_description result; result.add_options() ("help", "print help message") ("version", "print version information and quit") ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), "set resources directory") ("content", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") ("encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") ; Files::ConfigurationManager::addCommonOptions(result); return result; } struct WriteArray { const float (&mValue)[3]; friend std::ostream& operator <<(std::ostream& stream, const WriteArray& value) { for (std::size_t i = 0; i < 2; ++i) stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[i] << ", "; return stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[2]; } }; std::string toHex(std::string_view value) { std::string buffer(value.size() * 2, '0'); char* out = buffer.data(); for (const char v : value) { const std::ptrdiff_t space = static_cast(static_cast(v) <= 0xf); const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast(v), 16); if (ec != std::errc()) throw std::system_error(std::make_error_code(ec)); out += 2; } return buffer; } int runBulletObjectTool(int argc, char *argv[]) { Platform::init(); bpo::options_description desc = makeOptionsDescription(); bpo::parsed_options options = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); bpo::variables_map variables; bpo::store(options, variables); bpo::notify(variables); if (variables.find("help") != variables.end()) { getRawStdout() << desc << std::endl; return 0; } Files::ConfigurationManager config; config.readConfiguration(variables, desc); setupLogging(config.getLogPath().string(), applicationName); const std::string encoding(variables["encoding"].as()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); auto local = variables["data-local"].as(); if (!local.empty()) dataDirs.push_back(std::move(local)); config.filterOutNonExistingPaths(dataDirs); const auto fsStrict = variables["fs-strict"].as(); const auto resDir = variables["resources"].as(); Version::Version v = Version::getOpenmwVersion(resDir.string()); Log(Debug::Info) << v.describe(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const auto fileCollections = Files::Collections(dataDirs, !fsStrict); const auto archives = variables["fallback-archive"].as(); const auto contentFiles = variables["content"].as(); Fallback::Map::init(variables["fallback"].as().mMap); VFS::Manager vfs(fsStrict); VFS::registerArchives(&vfs, fileCollections, archives, true); Settings::Manager settings; settings.load(config); ESM::ReadersCache readers; EsmLoader::Query query; query.mLoadActivators = true; query.mLoadCells = true; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = true; query.mLoadLands = true; query.mLoadStatics = true; const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); Resource::ImageManager imageManager(&vfs); Resource::NifFileManager nifFileManager(&vfs); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); Resource::forEachBulletObject(readers, vfs, bulletShapeManager, esmData, [] (const ESM::Cell& cell, const Resource::BulletObject& object) { Log(Debug::Verbose) << "Found bullet object in " << (cell.isExterior() ? "exterior" : "interior") << " cell \"" << cell.getDescription() << "\":" << " fileName=\"" << object.mShape->mFileName << '"' << " fileHash=" << toHex(object.mShape->mFileHash) << " collisionShape=" << std::boolalpha << (object.mShape->mCollisionShape == nullptr) << " avoidCollisionShape=" << std::boolalpha << (object.mShape->mAvoidCollisionShape == nullptr) << " position=(" << WriteArray {object.mPosition.pos} << ')' << " rotation=(" << WriteArray {object.mPosition.rot} << ')' << " scale=" << std::setprecision(std::numeric_limits::max_exponent10) << object.mScale; }); Log(Debug::Info) << "Done"; return 0; } } int main(int argc, char *argv[]) { return wrapApplication(runBulletObjectTool, argc, argv, applicationName); } openmw-openmw-0.48.0/apps/doc.hpp000066400000000000000000000001151445372753700166520ustar00rootroot00000000000000// Note: This is not a regular source file. /// \defgroup apps Applications openmw-openmw-0.48.0/apps/esmtool/000077500000000000000000000000001445372753700170615ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/esmtool/.gitignore000066400000000000000000000000531445372753700210470ustar00rootroot00000000000000*.esp *.esm *.ess *_test esmtool *_raw.txt openmw-openmw-0.48.0/apps/esmtool/CMakeLists.txt000066400000000000000000000011101445372753700216120ustar00rootroot00000000000000set(ESMTOOL esmtool.cpp labels.hpp labels.cpp record.hpp record.cpp arguments.hpp tes4.hpp tes4.cpp ) source_group(apps\\esmtool FILES ${ESMTOOL}) # Main executable openmw_add_executable(esmtool ${ESMTOOL} ) target_link_libraries(esmtool ${Boost_PROGRAM_OPTIONS_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(esmtool gcov) endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(esmtool PRIVATE ) endif() openmw-openmw-0.48.0/apps/esmtool/arguments.hpp000066400000000000000000000010441445372753700215760ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_ARGUMENTS_H #define OPENMW_ESMTOOL_ARGUMENTS_H #include #include #include namespace EsmTool { struct Arguments { std::optional mRawFormat; bool quiet_given = false; bool loadcells_given = false; bool plain_given = false; std::string mode; std::string encoding; std::string filename; std::string outname; std::vector types; std::string name; }; } #endif openmw-openmw-0.48.0/apps/esmtool/esmtool.cpp000066400000000000000000000452101445372753700212510ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "record.hpp" #include "labels.hpp" #include "arguments.hpp" #include "tes4.hpp" namespace { using namespace EsmTool; constexpr unsigned majorVersion = 1; constexpr unsigned minorVersion = 3; // Create a local alias for brevity namespace bpo = boost::program_options; struct ESMData { ESM::Header mHeader; std::deque> mRecords; // Value: (Reference, Deleted flag) std::map > > mCellRefs; std::map mRecordStats; }; bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options"); desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") ("raw,r", bpo::value(), "Show an unformatted list of all records and subrecords of given format:\n" "\n\tTES3" "\n\tTES4") // The intention is that this option would interact better // with other modes including clone, dump, and raw. ("type,t", bpo::value< std::vector >(), "Show only records of this type (four character record code). May " "be specified multiple times. Only affects dump mode.") ("name,n", bpo::value(), "Show only the record with this name. Only affects dump mode.") ("plain,p", "Print contents of dialogs, books and scripts. " "(skipped by default)" "Only affects dump mode.") ("quiet,q", "Suppress all record information. Useful for speed tests.") ("loadcells,C", "Browse through contents of all cells.") ( "encoding,e", bpo::value(&(info.encoding))-> default_value("win1252"), "Character encoding used in ESMTool:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ; std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information."; // input-file is hidden and used as a positional argument bpo::options_description hidden("Hidden Options"); hidden.add_options() ( "mode,m", bpo::value(), "esmtool mode") ( "input-file,i", bpo::value< std::vector >(), "input file") ; bpo::positional_options_description p; p.add("mode", 1).add("input-file", 2); // there might be a better way to do this bpo::options_description all; all.add(desc).add(hidden); bpo::variables_map variables; try { bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(all).positional(p).run(); bpo::store(valid_opts, variables); } catch(std::exception &e) { std::cout << "ERROR parsing arguments: " << e.what() << std::endl; return false; } bpo::notify(variables); if (variables.count ("help")) { std::cout << desc << finalText << std::endl; return false; } if (variables.count ("version")) { std::cout << "ESMTool version " << majorVersion << '.' << minorVersion << std::endl; return false; } if (!variables.count("mode")) { std::cout << "No mode specified!\n\n" << desc << finalText << std::endl; return false; } if (variables.count("type") > 0) info.types = variables["type"].as< std::vector >(); if (variables.count("name") > 0) info.name = variables["name"].as(); info.mode = variables["mode"].as(); if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp")) { std::cout << "\nERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << finalText << std::endl; return false; } if ( !variables.count("input-file") ) { std::cout << "\nERROR: missing ES file\n\n"; std::cout << desc << finalText << std::endl; return false; } // handling gracefully the user adding multiple files /* if (variables["input-file"].as< std::vector >().size() > 1) { std::cout << "\nERROR: more than one ES file specified\n\n"; std::cout << desc << finalText << std::endl; return false; }*/ info.filename = variables["input-file"].as< std::vector >()[0]; if (variables["input-file"].as< std::vector >().size() > 1) info.outname = variables["input-file"].as< std::vector >()[1]; if (const auto it = variables.find("raw"); it != variables.end()) info.mRawFormat = ESM::parseFormat(it->second.as()); info.quiet_given = variables.count ("quiet") != 0; info.loadcells_given = variables.count ("loadcells") != 0; info.plain_given = variables.count("plain") != 0; // Font encoding settings info.encoding = variables["encoding"].as(); if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252") { std::cout << info.encoding << " is not a valid encoding option.\n"; info.encoding = "win1252"; } std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; return true; } void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data); int load(const Arguments& info, ESMData* data); int clone(const Arguments& info); int comp(const Arguments& info); } int main(int argc, char**argv) { try { Arguments info; if(!parseOptions (argc, argv, info)) return 1; if (info.mode == "dump") return load(info, nullptr); else if (info.mode == "clone") return clone(info); else if (info.mode == "comp") return comp(info); else { std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl; return 1; } } catch (std::exception& e) { std::cerr << "ERROR: " << e.what() << std::endl; return 1; } return 0; } namespace { void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data) { bool quiet = (info.quiet_given || info.mode == "clone"); bool save = (info.mode == "clone"); // Skip back to the beginning of the reference list // FIXME: Changes to the references backend required to support multiple plugins have // almost certainly broken this following line. I'll leave it as is for now, so that // the compiler does not complain. cell.restore(esm, 0); // Loop through all the references ESM::CellRef ref; if(!quiet) std::cout << " References:\n"; bool deleted = false; ESM::MovedCellRef movedCellRef; bool moved = false; while(cell.getNextRef(esm, ref, deleted, movedCellRef, moved)) { if (data != nullptr && save) data->mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); if(quiet) continue; std::cout << " - Refnum: " << ref.mRefNum.mIndex << '\n'; std::cout << " ID: " << ref.mRefID << '\n'; std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")\n"; if (ref.mScale != 1.f) std::cout << " Scale: " << ref.mScale << '\n'; if (!ref.mOwner.empty()) std::cout << " Owner: " << ref.mOwner << '\n'; if (!ref.mGlobalVariable.empty()) std::cout << " Global: " << ref.mGlobalVariable << '\n'; if (!ref.mFaction.empty()) std::cout << " Faction: " << ref.mFaction << '\n'; if (!ref.mFaction.empty() || ref.mFactionRank != -2) std::cout << " Faction rank: " << ref.mFactionRank << '\n'; std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n'; std::cout << " Uses/health: " << ref.mChargeInt << '\n'; std::cout << " Gold value: " << ref.mGoldValue << '\n'; std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << '\n'; std::cout << " Deleted: " << deleted << '\n'; if (!ref.mKey.empty()) std::cout << " Key: " << ref.mKey << '\n'; std::cout << " Lock level: " << ref.mLockLevel << '\n'; if (!ref.mTrap.empty()) std::cout << " Trap: " << ref.mTrap << '\n'; if (!ref.mSoul.empty()) std::cout << " Soul: " << ref.mSoul << '\n'; if (ref.mTeleport) { std::cout << " Destination position: (" << ref.mDoorDest.pos[0] << ", " << ref.mDoorDest.pos[1] << ", " << ref.mDoorDest.pos[2] << ")\n"; if (!ref.mDestCell.empty()) std::cout << " Destination cell: " << ref.mDestCell << '\n'; } std::cout << " Moved: " << std::boolalpha << moved << std::noboolalpha << '\n'; if (moved) { std::cout << " Moved refnum: " << movedCellRef.mRefNum.mIndex << '\n'; std::cout << " Moved content file: " << movedCellRef.mRefNum.mContentFile << '\n'; std::cout << " Target: " << movedCellRef.mTarget[0] << ", " << movedCellRef.mTarget[1] << '\n'; } } } void printRawTes3(std::string_view path) { std::cout << "TES3 RAW file listing: " << path << '\n'; ESM::ESMReader esm; esm.openRaw(path); while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); std::cout << "Record: " << n.toStringView() << '\n'; esm.getRecHeader(); while(esm.hasMoreSubs()) { size_t offs = esm.getFileOffset(); esm.getSubName(); esm.skipHSub(); n = esm.retSubName(); std::ios::fmtflags f(std::cout.flags()); std::cout << " " << n.toStringView() << " - " << esm.getSubSize() << " bytes @ 0x" << std::hex << offs << '\n'; std::cout.flags(f); } } } int loadTes3(const Arguments& info, std::unique_ptr&& stream, ESMData* data) { std::cout << "Loading TES3 file: " << info.filename << '\n'; ESM::ESMReader esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); std::unordered_set skipped; try { bool quiet = (info.quiet_given || info.mode == "clone"); bool loadCells = (info.loadcells_given || info.mode == "clone"); bool save = (info.mode == "clone"); esm.open(std::move(stream), info.filename); if (data != nullptr) data->mHeader = esm.getHeader(); if (!quiet) { std::cout << "Author: " << esm.getAuthor() << '\n' << "Description: " << esm.getDesc() << '\n' << "File format version: " << esm.getFVer() << '\n'; std::vector masterData = esm.getGameFiles(); if (!masterData.empty()) { std::cout << "Masters:" << '\n'; for(const auto& master : masterData) std::cout << " " << master.name << ", " << master.size << " bytes\n"; } } // Loop through all records while(esm.hasMoreRecs()) { const ESM::NAME n = esm.getRecName(); uint32_t flags; esm.getRecHeader(flags); auto record = EsmTool::RecordBase::create(n); if (record == nullptr) { if (!quiet && skipped.count(n.toInt()) == 0) { std::cout << "Skipping " << n.toStringView() << " records.\n"; skipped.emplace(n.toInt()); } esm.skipRecord(); if (quiet) break; std::cout << " Skipping\n"; continue; } record->setFlags(static_cast(flags)); record->setPrintPlain(info.plain_given); record->load(esm); // Is the user interested in this record type? bool interested = true; if (!info.types.empty() && std::find(info.types.begin(), info.types.end(), n.toStringView()) == info.types.end()) interested = false; if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) interested = false; if(!quiet && interested) { std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" << "Record flags: " << recordFlags(record->getFlags()) << '\n'; record->print(); } if (record->getType().toInt() == ESM::REC_CELL && loadCells && interested) { loadCell(info, record->cast()->get(), esm, data); } if (data != nullptr) { if (save) data->mRecords.push_back(std::move(record)); ++data->mRecordStats[n.toInt()]; } } } catch (const std::exception &e) { std::cout << "\nERROR:\n\n " << e.what() << std::endl; if (data != nullptr) data->mRecords.clear(); return 1; } return 0; } int load(const Arguments& info, ESMData* data) { if (info.mRawFormat.has_value() && info.mode == "dump") { switch (*info.mRawFormat) { case ESM::Format::Tes3: printRawTes3(info.filename); break; case ESM::Format::Tes4: std::cout << "Printing raw TES4 file is not supported: " << info.filename << "\n"; break; } return 0; } auto stream = Files::openBinaryInputFileStream(info.filename); if (!stream->is_open()) { std::cout << "Failed to open file: " << std::strerror(errno) << '\n'; return -1; } const ESM::Format format = ESM::readFormat(*stream); stream->seekg(0); switch (format) { case ESM::Format::Tes3: return loadTes3(info, std::move(stream), data); case ESM::Format::Tes4: if (data != nullptr) { std::cout << "Collecting data from esm file is not supported for TES4\n"; return -1; } return loadTes4(info, std::move(stream)); } std::cout << "Unsupported ESM format: " << ESM::NAME(format).toStringView() << '\n'; return -1; } int clone(const Arguments& info) { if (info.outname.empty()) { std::cout << "You need to specify an output name" << std::endl; return 1; } ESMData data; if (load(info, &data) != 0) { std::cout << "Failed to load, aborting." << std::endl; return 1; } size_t recordCount = data.mRecords.size(); int digitCount = 1; // For a nicer output if (recordCount > 0) digitCount = (int)std::log10(recordCount) + 1; std::cout << "Loaded " << recordCount << " records:\n\n"; int i = 0; for (std::pair stat : data.mRecordStats) { ESM::NAME name; name = stat.first; int amount = stat.second; std::cout << std::setw(digitCount) << amount << " " << name.toStringView() << " "; if (++i % 3 == 0) std::cout << '\n'; } if (i % 3 != 0) std::cout << '\n'; std::cout << "\nSaving records to: " << info.outname << "...\n"; ESM::ESMWriter esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); esm.setHeader(data.mHeader); esm.setVersion(ESM::VER_13); esm.setRecordCount (recordCount); boost::filesystem::fstream save(info.outname, boost::filesystem::fstream::out | boost::filesystem::fstream::binary); esm.save(save); int saved = 0; for (auto& record : data.mRecords) { if (i <= 0) break; const ESM::NAME typeName = record->getType(); esm.startRecord(typeName, record->getFlags()); record->save(esm); if (typeName.toInt() == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!data.mCellRefs[ptr].empty()) { for (std::pair &ref : data.mCellRefs[ptr]) ref.first.save(esm, ref.second); } } esm.endRecord(typeName); saved++; int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100); if (perc % 10 == 0) { std::cerr << "\r" << perc << "%"; } } std::cout << "\rDone!" << std::endl; esm.close(); save.close(); return 0; } int comp(const Arguments& info) { if (info.filename.empty() || info.outname.empty()) { std::cout << "You need to specify two input files" << std::endl; return 1; } Arguments fileOne; Arguments fileTwo; fileOne.mode = "clone"; fileTwo.mode = "clone"; fileOne.encoding = info.encoding; fileTwo.encoding = info.encoding; fileOne.filename = info.filename; fileTwo.filename = info.outname; ESMData dataOne; if (load(fileOne, &dataOne) != 0) { std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl; return 1; } ESMData dataTwo; if (load(fileTwo, &dataTwo) != 0) { std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl; return 1; } if (dataOne.mRecords.size() != dataTwo.mRecords.size()) { std::cout << "Not equal, different amount of records." << std::endl; return 1; } return 0; } } openmw-openmw-0.48.0/apps/esmtool/labels.cpp000066400000000000000000000654501445372753700210410ustar00rootroot00000000000000#include "labels.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include std::string bodyPartLabel(int idx) { if (idx >= 0 && idx <= 26) { static const char *bodyPartLabels[] = { "Head", "Hair", "Neck", "Cuirass", "Groin", "Skirt", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield", "Right Forearm", "Left Forearm", "Right Upperarm", "Left Upperarm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Leg", "Left Leg", "Right Shoulder", "Left Shoulder", "Weapon", "Tail" }; return bodyPartLabels[idx]; } else return "Invalid"; } std::string meshPartLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail) { static const char *meshPartLabels[] = { "Head", "Hair", "Neck", "Chest", "Groin", "Hand", "Wrist", "Forearm", "Upperarm", "Foot", "Ankle", "Knee", "Upper Leg", "Clavicle", "Tail" }; return meshPartLabels[idx]; } else return "Invalid"; } std::string meshTypeLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor) { static const char *meshTypeLabels[] = { "Skin", "Clothing", "Armor" }; return meshTypeLabels[idx]; } else return "Invalid"; } std::string clothingTypeLabel(int idx) { if (idx >= 0 && idx <= 9) { static const char *clothingTypeLabels[] = { "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", "Amulet" }; return clothingTypeLabels[idx]; } else return "Invalid"; } std::string armorTypeLabel(int idx) { if (idx >= 0 && idx <= 10) { static const char *armorTypeLabels[] = { "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet", "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer" }; return armorTypeLabels[idx]; } else return "Invalid"; } std::string dialogTypeLabel(int idx) { if (idx >= 0 && idx <= 4) { static const char *dialogTypeLabels[] = { "Topic", "Voice", "Greeting", "Persuasion", "Journal" }; return dialogTypeLabels[idx]; } else if (idx == -1) return "Deleted"; else return "Invalid"; } std::string questStatusLabel(int idx) { if (idx >= 0 && idx <= 4) { static const char *questStatusLabels[] = { "None", "Name", "Finished", "Restart", "Deleted" }; return questStatusLabels[idx]; } else return "Invalid"; } std::string creatureTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { static const char *creatureTypeLabels[] = { "Creature", "Daedra", "Undead", "Humanoid", }; return creatureTypeLabels[idx]; } else return "Invalid"; } std::string soundTypeLabel(int idx) { if (idx >= 0 && idx <= 7) { static const char *soundTypeLabels[] = { "Left Foot", "Right Foot", "Swim Left", "Swim Right", "Moan", "Roar", "Scream", "Land" }; return soundTypeLabels[idx]; } else return "Invalid"; } std::string weaponTypeLabel(int idx) { if (idx >= 0 && idx <= 13) { static const char *weaponTypeLabels[] = { "Short Blade One Hand", "Long Blade One Hand", "Long Blade Two Hand", "Blunt One Hand", "Blunt Two Close", "Blunt Two Wide", "Spear Two Wide", "Axe One Hand", "Axe Two Hand", "Marksman Bow", "Marksman Crossbow", "Marksman Thrown", "Arrow", "Bolt" }; return weaponTypeLabels[idx]; } else return "Invalid"; } std::string aiTypeLabel(int type) { if (type == ESM::AI_Wander) return "Wander"; else if (type == ESM::AI_Travel) return "Travel"; else if (type == ESM::AI_Follow) return "Follow"; else if (type == ESM::AI_Escort) return "Escort"; else if (type == ESM::AI_Activate) return "Activate"; else return "Invalid"; } std::string magicEffectLabel(int idx) { if (idx >= 0 && idx <= 142) { const char* magicEffectLabels [] = { "Water Breathing", "Swift Swim", "Water Walking", "Shield", "Fire Shield", "Lightning Shield", "Frost Shield", "Burden", "Feather", "Jump", "Levitate", "SlowFall", "Lock", "Open", "Fire Damage", "Shock Damage", "Frost Damage", "Drain Attribute", "Drain Health", "Drain Magicka", "Drain Fatigue", "Drain Skill", "Damage Attribute", "Damage Health", "Damage Magicka", "Damage Fatigue", "Damage Skill", "Poison", "Weakness to Fire", "Weakness to Frost", "Weakness to Shock", "Weakness to Magicka", "Weakness to Common Disease", "Weakness to Blight Disease", "Weakness to Corprus Disease", "Weakness to Poison", "Weakness to Normal Weapons", "Disintegrate Weapon", "Disintegrate Armor", "Invisibility", "Chameleon", "Light", "Sanctuary", "Night Eye", "Charm", "Paralyze", "Silence", "Blind", "Sound", "Calm Humanoid", "Calm Creature", "Frenzy Humanoid", "Frenzy Creature", "Demoralize Humanoid", "Demoralize Creature", "Rally Humanoid", "Rally Creature", "Dispel", "Soultrap", "Telekinesis", "Mark", "Recall", "Divine Intervention", "Almsivi Intervention", "Detect Animal", "Detect Enchantment", "Detect Key", "Spell Absorption", "Reflect", "Cure Common Disease", "Cure Blight Disease", "Cure Corprus Disease", "Cure Poison", "Cure Paralyzation", "Restore Attribute", "Restore Health", "Restore Magicka", "Restore Fatigue", "Restore Skill", "Fortify Attribute", "Fortify Health", "Fortify Magicka", "Fortify Fatigue", "Fortify Skill", "Fortify Maximum Magicka", "Absorb Attribute", "Absorb Health", "Absorb Magicka", "Absorb Fatigue", "Absorb Skill", "Resist Fire", "Resist Frost", "Resist Shock", "Resist Magicka", "Resist Common Disease", "Resist Blight Disease", "Resist Corprus Disease", "Resist Poison", "Resist Normal Weapons", "Resist Paralysis", "Remove Curse", "Turn Undead", "Summon Scamp", "Summon Clannfear", "Summon Daedroth", "Summon Dremora", "Summon Ancestral Ghost", "Summon Skeletal Minion", "Summon Bonewalker", "Summon Greater Bonewalker", "Summon Bonelord", "Summon Winged Twilight", "Summon Hunger", "Summon Golden Saint", "Summon Flame Atronach", "Summon Frost Atronach", "Summon Storm Atronach", "Fortify Attack", "Command Creature", "Command Humanoid", "Bound Dagger", "Bound Longsword", "Bound Mace", "Bound Battle Axe", "Bound Spear", "Bound Longbow", "EXTRA SPELL", "Bound Cuirass", "Bound Helm", "Bound Boots", "Bound Shield", "Bound Gloves", "Corprus", "Vampirism", "Summon Centurion Sphere", "Sun Damage", "Stunted Magicka", "Summon Fabricant", "sEffectSummonCreature01", "sEffectSummonCreature02", "sEffectSummonCreature03", "sEffectSummonCreature04", "sEffectSummonCreature05" }; return magicEffectLabels[idx]; } else return "Invalid"; } std::string attributeLabel(int idx) { if (idx >= 0 && idx <= 7) { const char* attributeLabels [] = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck" }; return attributeLabels[idx]; } else return "Invalid"; } std::string spellTypeLabel(int idx) { if (idx >= 0 && idx <= 5) { const char* spellTypeLabels [] = { "Spells", "Abilities", "Blight Disease", "Disease", "Curse", "Powers" }; return spellTypeLabels[idx]; } else return "Invalid"; } std::string specializationLabel(int idx) { if (idx >= 0 && idx <= 2) { const char* specializationLabels [] = { "Combat", "Magic", "Stealth" }; return specializationLabels[idx]; } else return "Invalid"; } std::string skillLabel(int idx) { if (idx >= 0 && idx <= 26) { const char* skillLabels [] = { "Block", "Armorer", "Medium Armor", "Heavy Armor", "Blunt Weapon", "Long Blade", "Axe", "Spear", "Athletics", "Enchant", "Destruction", "Alteration", "Illusion", "Conjuration", "Mysticism", "Restoration", "Alchemy", "Unarmored", "Security", "Sneak", "Acrobatics", "Light Armor", "Short Blade", "Marksman", "Mercantile", "Speechcraft", "Hand-to-hand" }; return skillLabels[idx]; } else return "Invalid"; } std::string apparatusTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { const char* apparatusTypeLabels [] = { "Mortar", "Alembic", "Calcinator", "Retort", }; return apparatusTypeLabels[idx]; } else return "Invalid"; } std::string rangeTypeLabel(int idx) { if (idx >= 0 && idx <= 2) { const char* rangeTypeLabels [] = { "Self", "Touch", "Target" }; return rangeTypeLabels[idx]; } else return "Invalid"; } std::string schoolLabel(int idx) { if (idx >= 0 && idx <= 5) { const char* schoolLabels [] = { "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", "Restoration" }; return schoolLabels[idx]; } else return "Invalid"; } std::string enchantTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { const char* enchantTypeLabels [] = { "Cast Once", "Cast When Strikes", "Cast When Used", "Constant Effect" }; return enchantTypeLabels[idx]; } else return "Invalid"; } std::string ruleFunction(int idx) { if (idx >= 0 && idx <= 72) { std::string ruleFunctions[] = { "Reaction Low", "Reaction High", "Rank Requirement", "NPC? Reputation", "Health Percent", "Player Reputation", "NPC Level", "Player Health Percent", "Player Magicka", "Player Fatigue", "Player Attribute Strength", "Player Skill Block", "Player Skill Armorer", "Player Skill Medium Armor", "Player Skill Heavy Armor", "Player Skill Blunt Weapon", "Player Skill Long Blade", "Player Skill Axe", "Player Skill Spear", "Player Skill Athletics", "Player Skill Enchant", "Player Skill Destruction", "Player Skill Alteration", "Player Skill Illusion", "Player Skill Conjuration", "Player Skill Mysticism", "Player SKill Restoration", "Player Skill Alchemy", "Player Skill Unarmored", "Player Skill Security", "Player Skill Sneak", "Player Skill Acrobatics", "Player Skill Light Armor", "Player Skill Short Blade", "Player Skill Marksman", "Player Skill Mercantile", "Player Skill Speechcraft", "Player Skill Hand to Hand", "Player Gender", "Player Expelled from Faction", "Player Diseased (Common)", "Player Diseased (Blight)", "Player Clothing Modifier", "Player Crime Level", "Player Same Sex", "Player Same Race", "Player Same Faction", "Faction Rank Difference", "Player Detected", "Alarmed", "Choice Selected", "Player Attribute Intelligence", "Player Attribute Willpower", "Player Attribute Agility", "Player Attribute Speed", "Player Attribute Endurance", "Player Attribute Personality", "Player Attribute Luck", "Player Diseased (Corprus)", "Weather", "Player is a Vampire", "Player Level", "Attacked", "NPC Talked to Player", "Player Health", "Creature Target", "Friend Hit", "Fight", "Hello", "Alarm", "Flee", "Should Attack", "Werewolf" }; return ruleFunctions[idx]; } else return "Invalid"; } // The "unused flag bits" should probably be defined alongside the // defined bits in the ESM component. The names of the flag bits are // very inconsistent. std::string bodyPartFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::BodyPart::BPF_Female) properties += "Female "; if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable "; int unused = (0xFFFFFFFF ^ (ESM::BodyPart::BPF_Female| ESM::BodyPart::BPF_NotPlayable)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string cellFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Cell::HasWater) properties += "HasWater "; if (flags & ESM::Cell::Interior) properties += "Interior "; if (flags & ESM::Cell::NoSleep) properties += "NoSleep "; if (flags & ESM::Cell::QuasiEx) properties += "QuasiEx "; // This used value is not in the ESM component. if (flags & 0x00000040) properties += "Unknown "; int unused = (0xFFFFFFFF ^ (ESM::Cell::HasWater| ESM::Cell::Interior| ESM::Cell::NoSleep| ESM::Cell::QuasiEx| 0x00000040)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string containerFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Container::Unknown) properties += "Unknown "; if (flags & ESM::Container::Organic) properties += "Organic "; if (flags & ESM::Container::Respawn) properties += "Respawn "; int unused = (0xFFFFFFFF ^ (ESM::Container::Unknown| ESM::Container::Organic| ESM::Container::Respawn)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string creatureFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Creature::Base) properties += "Base "; if (flags & ESM::Creature::Walks) properties += "Walks "; if (flags & ESM::Creature::Swims) properties += "Swims "; if (flags & ESM::Creature::Flies) properties += "Flies "; if (flags & ESM::Creature::Bipedal) properties += "Bipedal "; if (flags & ESM::Creature::Respawn) properties += "Respawn "; if (flags & ESM::Creature::Weapon) properties += "Weapon "; if (flags & ESM::Creature::Essential) properties += "Essential "; int unused = (0xFFFFFFFF ^ (ESM::Creature::Base| ESM::Creature::Walks| ESM::Creature::Swims| ESM::Creature::Flies| ESM::Creature::Bipedal| ESM::Creature::Respawn| ESM::Creature::Weapon| ESM::Creature::Essential)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } std::string enchantmentFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Enchantment::Autocalc) properties += "Autocalc "; if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string landFlags(int flags) { std::string properties; // The ESM component says that this first four bits are used, but // only the first three bits are used as far as I can tell. // There's also no enumeration of the bit in the ESM component. if (flags == 0) properties += "[None] "; if (flags & 0x00000001) properties += "Unknown1 "; if (flags & 0x00000004) properties += "Unknown3 "; if (flags & 0x00000002) properties += "Unknown2 "; if (flags & 0xFFFFFFF8) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string itemListFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels "; if (flags & ESM::ItemLevList::Each) properties += "Each "; int unused = (0xFFFFFFFF ^ (ESM::ItemLevList::AllLevels| ESM::ItemLevList::Each)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string creatureListFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels "; int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string lightFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Light::Dynamic) properties += "Dynamic "; if (flags & ESM::Light::Fire) properties += "Fire "; if (flags & ESM::Light::Carry) properties += "Carry "; if (flags & ESM::Light::Flicker) properties += "Flicker "; if (flags & ESM::Light::FlickerSlow) properties += "FlickerSlow "; if (flags & ESM::Light::Pulse) properties += "Pulse "; if (flags & ESM::Light::PulseSlow) properties += "PulseSlow "; if (flags & ESM::Light::Negative) properties += "Negative "; if (flags & ESM::Light::OffDefault) properties += "OffDefault "; int unused = (0xFFFFFFFF ^ (ESM::Light::Dynamic| ESM::Light::Fire| ESM::Light::Carry| ESM::Light::Flicker| ESM::Light::FlickerSlow| ESM::Light::Pulse| ESM::Light::PulseSlow| ESM::Light::Negative| ESM::Light::OffDefault)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string magicEffectFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::MagicEffect::TargetAttribute) properties += "TargetAttribute "; if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill "; if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration "; if (flags & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude "; if (flags & ESM::MagicEffect::Harmful) properties += "Harmful "; if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX "; if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf "; if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch "; if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget "; if (flags & ESM::MagicEffect::AppliedOnce) properties += "AppliedOnce "; if (flags & ESM::MagicEffect::Stealth) properties += "Stealth "; if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable "; if (flags & ESM::MagicEffect::IllegalDaedra) properties += "IllegalDaedra "; if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable "; if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked "; if (flags & ESM::MagicEffect::AllowSpellmaking) properties += "AllowSpellmaking "; if (flags & ESM::MagicEffect::AllowEnchanting) properties += "AllowEnchanting "; if (flags & ESM::MagicEffect::NegativeLight) properties += "NegativeLight "; if (flags & 0xFFFC0000) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string npcFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::NPC::Base) properties += "Base "; if (flags & ESM::NPC::Autocalc) properties += "Autocalc "; if (flags & ESM::NPC::Female) properties += "Female "; if (flags & ESM::NPC::Respawn) properties += "Respawn "; if (flags & ESM::NPC::Essential) properties += "Essential "; // Whether corpses persist is a bit that is unaccounted for, // however relatively few NPCs have this bit set. int unused = (0xFF ^ (ESM::NPC::Base| ESM::NPC::Autocalc| ESM::NPC::Female| ESM::NPC::Respawn| ESM::NPC::Essential)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } std::string raceFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; // All races have the playable flag set in Bethesda files. if (flags & ESM::Race::Playable) properties += "Playable "; if (flags & ESM::Race::Beast) properties += "Beast "; int unused = (0xFFFFFFFF ^ (ESM::Race::Playable| ESM::Race::Beast)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string spellFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::Spell::F_Autocalc) properties += "Autocalc "; if (flags & ESM::Spell::F_PCStart) properties += "PCStart "; if (flags & ESM::Spell::F_Always) properties += "Always "; int unused = (0xFFFFFFFF ^ (ESM::Spell::F_Autocalc| ESM::Spell::F_PCStart| ESM::Spell::F_Always)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string weaponFlags(int flags) { std::string properties; if (flags == 0) properties += "[None] "; // The interpretation of the flags are still unclear to me. // Apparently you can't be Silver without being Magical? Many of // the "Magical" weapons don't have enchantments of any sort. if (flags & ESM::Weapon::Magical) properties += "Magical "; if (flags & ESM::Weapon::Silver) properties += "Silver "; int unused = (0xFFFFFFFF ^ (ESM::Weapon::Magical| ESM::Weapon::Silver)); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } std::string recordFlags(uint32_t flags) { std::string properties; if (flags == 0) properties += "[None] "; if (flags & ESM::FLAG_Deleted) properties += "Deleted "; if (flags & ESM::FLAG_Persistent) properties += "Persistent "; if (flags & ESM::FLAG_Ignored) properties += "Ignored "; if (flags & ESM::FLAG_Blocked) properties += "Blocked "; int unused = ~(ESM::FLAG_Deleted | ESM::FLAG_Persistent | ESM::FLAG_Ignored | ESM::FLAG_Blocked); if (flags & unused) properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; }openmw-openmw-0.48.0/apps/esmtool/labels.hpp000066400000000000000000000045021445372753700210350ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_LABELS_H #define OPENMW_ESMTOOL_LABELS_H #include #include std::string bodyPartLabel(int idx); std::string meshPartLabel(int idx); std::string meshTypeLabel(int idx); std::string clothingTypeLabel(int idx); std::string armorTypeLabel(int idx); std::string dialogTypeLabel(int idx); std::string questStatusLabel(int idx); std::string creatureTypeLabel(int idx); std::string soundTypeLabel(int idx); std::string weaponTypeLabel(int idx); // This function's a bit different because the types are record types, // not consecutive values. std::string aiTypeLabel(int type); // This one's also a bit different, because it enumerates dialog // select rule functions, not types. Structurally, it still converts // indexes to strings for display. std::string ruleFunction(int idx); // The labels below here can all be loaded from GMSTs, but are not // currently because among other things, that requires loading the // GMSTs before dumping any of the records. // If the data format supported ordered lists of GMSTs (post 1.0), the // lists could define the valid values, their localization strings, // and the indexes for referencing the types in other records in the // database. Then a single label function could work for all types. std::string magicEffectLabel(int idx); std::string attributeLabel(int idx); std::string spellTypeLabel(int idx); std::string specializationLabel(int idx); std::string skillLabel(int idx); std::string apparatusTypeLabel(int idx); std::string rangeTypeLabel(int idx); std::string schoolLabel(int idx); std::string enchantTypeLabel(int idx); // The are the flag functions that convert a bitmask into a list of // human readble strings representing the set bits. std::string bodyPartFlags(int flags); std::string cellFlags(int flags); std::string containerFlags(int flags); std::string creatureFlags(int flags); std::string enchantmentFlags(int flags); std::string landFlags(int flags); std::string creatureListFlags(int flags); std::string itemListFlags(int flags); std::string lightFlags(int flags); std::string magicEffectFlags(int flags); std::string npcFlags(int flags); std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); std::string recordFlags(uint32_t flags); // Missing flags functions: // aiServicesFlags, possibly more #endif openmw-openmw-0.48.0/apps/esmtool/record.cpp000066400000000000000000001511451445372753700210520ustar00rootroot00000000000000#include "record.hpp" #include "labels.hpp" #include #include #include namespace { void printAIPackage(const ESM::AIPackage& p) { std::cout << " AI Type: " << aiTypeLabel(p.mType) << " (" << Misc::StringUtils::format("0x%08X", p.mType) << ")" << std::endl; if (p.mType == ESM::AI_Wander) { std::cout << " Distance: " << p.mWander.mDistance << std::endl; std::cout << " Duration: " << p.mWander.mDuration << std::endl; std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl; if (p.mWander.mShouldRepeat != 1) std::cout << " Should repeat: " << (bool)(p.mWander.mShouldRepeat != 0) << std::endl; std::cout << " Idle: "; for (int i = 0; i != 8; i++) std::cout << (int)p.mWander.mIdle[i] << " "; std::cout << std::endl; } else if (p.mType == ESM::AI_Travel) { std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) { std::cout << " Follow Coordinates: (" << p.mTarget.mX << "," << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; std::cout << " Duration: " << p.mTarget.mDuration << std::endl; std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Activate) { std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl; } else { std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; } if (!p.mCellName.empty()) std::cout << " Cell Name: " << p.mCellName << std::endl; } std::string ruleString(const ESM::DialInfo::SelectStruct& ss) { std::string rule = ss.mSelectRule; if (rule.length() < 5) return "INVALID"; char type = rule[1]; char indicator = rule[2]; std::string type_str = "INVALID"; std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); int func; std::istringstream iss(rule.substr(2,2)); iss >> func; switch(type) { case '1': type_str = "Function"; func_str = ruleFunction(func); break; case '2': if (indicator == 's') type_str = "Global short"; else if (indicator == 'l') type_str = "Global long"; else if (indicator == 'f') type_str = "Global float"; break; case '3': if (indicator == 's') type_str = "Local short"; else if (indicator == 'l') type_str = "Local long"; else if (indicator == 'f') type_str = "Local float"; break; case '4': if (indicator == 'J') type_str = "Journal"; break; case '5': if (indicator == 'I') type_str = "Item type"; break; case '6': if (indicator == 'D') type_str = "NPC Dead"; break; case '7': if (indicator == 'X') type_str = "Not ID"; break; case '8': if (indicator == 'F') type_str = "Not Faction"; break; case '9': if (indicator == 'C') type_str = "Not Class"; break; case 'A': if (indicator == 'R') type_str = "Not Race"; break; case 'B': if (indicator == 'L') type_str = "Not Cell"; break; case 'C': if (indicator == 's') type_str = "Not Local"; break; default: break; } // Append the variable name to the function string if any. if (type != '1') func_str = rule.substr(5); // In the previous switch, we assumed that the second char was X // for all types not qual to one. If this wasn't true, go back to // the error message. if (type != '1' && rule[3] != 'X') func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); char oper = rule[4]; std::string oper_str = "??"; switch (oper) { case '0': oper_str = "=="; break; case '1': oper_str = "!="; break; case '2': oper_str = "> "; break; case '3': oper_str = ">="; break; case '4': oper_str = "< "; break; case '5': oper_str = "<="; break; default: break; } std::ostringstream stream; stream << ss.mValue; std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); return result; } void printEffectList(const ESM::EffectList& effects) { int i = 0; for (const ESM::ENAMstruct& effect : effects.mList) { std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID << ")" << std::endl; if (effect.mSkill != -1) std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" << std::endl; if (effect.mAttribute != -1) std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute << ")" << std::endl; std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; // Area is always zero if range type is "Self" if (effect.mRange != ESM::RT_Self) std::cout << " Area: " << effect.mArea << std::endl; std::cout << " Duration: " << effect.mDuration << std::endl; std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; i++; } } void printTransport(const std::vector& transport) { for (const ESM::Transport::Dest& dest : transport) { std::cout << " Destination Position: " << Misc::StringUtils::format("%12.3f", dest.mPos.pos[0]) << "," << Misc::StringUtils::format("%12.3f", dest.mPos.pos[1]) << "," << Misc::StringUtils::format("%12.3f", dest.mPos.pos[2]) << ")" << std::endl; std::cout << " Destination Rotation: " << Misc::StringUtils::format("%9.6f", dest.mPos.rot[0]) << "," << Misc::StringUtils::format("%9.6f", dest.mPos.rot[1]) << "," << Misc::StringUtils::format("%9.6f", dest.mPos.rot[2]) << ")" << std::endl; if (!dest.mCellName.empty()) std::cout << " Destination Cell: " << dest.mCellName << std::endl; } } } namespace EsmTool { std::unique_ptr RecordBase::create(const ESM::NAME type) { std::unique_ptr record; switch (type.toInt()) { case ESM::REC_ACTI: { record = std::make_unique>(); break; } case ESM::REC_ALCH: { record = std::make_unique>(); break; } case ESM::REC_APPA: { record = std::make_unique>(); break; } case ESM::REC_ARMO: { record = std::make_unique>(); break; } case ESM::REC_BODY: { record = std::make_unique>(); break; } case ESM::REC_BOOK: { record = std::make_unique>(); break; } case ESM::REC_BSGN: { record = std::make_unique>(); break; } case ESM::REC_CELL: { record = std::make_unique>(); break; } case ESM::REC_CLAS: { record = std::make_unique>(); break; } case ESM::REC_CLOT: { record = std::make_unique>(); break; } case ESM::REC_CONT: { record = std::make_unique>(); break; } case ESM::REC_CREA: { record = std::make_unique>(); break; } case ESM::REC_DIAL: { record = std::make_unique>(); break; } case ESM::REC_DOOR: { record = std::make_unique>(); break; } case ESM::REC_ENCH: { record = std::make_unique>(); break; } case ESM::REC_FACT: { record = std::make_unique>(); break; } case ESM::REC_GLOB: { record = std::make_unique>(); break; } case ESM::REC_GMST: { record = std::make_unique>(); break; } case ESM::REC_INFO: { record = std::make_unique>(); break; } case ESM::REC_INGR: { record = std::make_unique>(); break; } case ESM::REC_LAND: { record = std::make_unique>(); break; } case ESM::REC_LEVI: { record = std::make_unique>(); break; } case ESM::REC_LEVC: { record = std::make_unique>(); break; } case ESM::REC_LIGH: { record = std::make_unique>(); break; } case ESM::REC_LOCK: { record = std::make_unique>(); break; } case ESM::REC_LTEX: { record = std::make_unique>(); break; } case ESM::REC_MISC: { record = std::make_unique>(); break; } case ESM::REC_MGEF: { record = std::make_unique>(); break; } case ESM::REC_NPC_: { record = std::make_unique>(); break; } case ESM::REC_PGRD: { record = std::make_unique>(); break; } case ESM::REC_PROB: { record = std::make_unique>(); break; } case ESM::REC_RACE: { record = std::make_unique>(); break; } case ESM::REC_REGN: { record = std::make_unique>(); break; } case ESM::REC_REPA: { record = std::make_unique>(); break; } case ESM::REC_SCPT: { record = std::make_unique>(); break; } case ESM::REC_SKIL: { record = std::make_unique>(); break; } case ESM::REC_SNDG: { record = std::make_unique>(); break; } case ESM::REC_SOUN: { record = std::make_unique>(); break; } case ESM::REC_SPEL: { record = std::make_unique>(); break; } case ESM::REC_STAT: { record = std::make_unique>(); break; } case ESM::REC_WEAP: { record = std::make_unique>(); break; } case ESM::REC_SSCR: { record = std::make_unique>(); break; } default: break; } if (record) { record->mType = type; } return record; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mEnchant.empty()) std::cout << " Enchantment: " << mData.mEnchant << std::endl; std::cout << " Type: " << armorTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Health: " << mData.mData.mHealth << std::endl; std::cout << " Armor: " << mData.mData.mArmor << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; for (const ESM::PartReference &part : mData.mParts.mParts) { std::cout << " Body Part: " << bodyPartLabel(part.mPart) << " (" << (int)(part.mPart) << ")" << std::endl; std::cout << " Male Name: " << part.mMale << std::endl; if (!part.mFemale.empty()) std::cout << " Female Name: " << part.mFemale << std::endl; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Race: " << mData.mRace << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Type: " << meshTypeLabel(mData.mData.mType) << " (" << (int)mData.mData.mType << ")" << std::endl; std::cout << " Flags: " << bodyPartFlags(mData.mData.mFlags) << std::endl; std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mEnchant.empty()) std::cout << " Enchantment: " << mData.mEnchant << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl; std::cout << " SkillId: " << mData.mData.mSkillId << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { std::cout << " Text:" << std::endl; std::cout << "START--------------------------------------" << std::endl; std::cout << mData.mText << std::endl; std::cout << "END----------------------------------------" << std::endl; } else { std::cout << " Text: [skipped]" << std::endl; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; for (const std::string &power : mData.mPowers.mList) std::cout << " Power: " << power << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { // None of the cells have names... if (!mData.mName.empty()) std::cout << " Name: " << mData.mName << std::endl; if (!mData.mRegion.empty()) std::cout << " Region: " << mData.mRegion << std::endl; std::cout << " Flags: " << cellFlags(mData.mData.mFlags) << std::endl; std::cout << " Coordinates: " << " (" << mData.getGridX() << "," << mData.getGridY() << ")" << std::endl; if (mData.mData.mFlags & ESM::Cell::Interior && !(mData.mData.mFlags & ESM::Cell::QuasiEx)) { if (mData.hasAmbient()) { // TODO: see if we can change the integer representation to something more sensible std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl; std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl; std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl; std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl; } else { std::cout << " No Ambient Information" << std::endl; } std::cout << " Water Level: " << mData.mWater << std::endl; } else std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Playable: " << mData.mData.mIsPlayable << std::endl; std::cout << " AutoCalc: " << mData.mData.mCalc << std::endl; std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) << " (" << mData.mData.mAttribute[0] << ")" << std::endl; std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) << " (" << mData.mData.mAttribute[1] << ")" << std::endl; std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) << " (" << mData.mData.mSpecialization << ")" << std::endl; for (int i = 0; i != 5; i++) std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][0]) << " (" << mData.mData.mSkills[i][0] << ")" << std::endl; for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mEnchant.empty()) std::cout << " Enchantment: " << mData.mEnchant << std::endl; std::cout << " Type: " << clothingTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; for (const ESM::PartReference &part : mData.mParts.mParts) { std::cout << " Body Part: " << bodyPartLabel(part.mPart) << " (" << (int)(part.mPart) << ")" << std::endl; std::cout << " Male Name: " << part.mMale << std::endl; if (!part.mFemale.empty()) std::cout << " Female Name: " << part.mFemale << std::endl; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl; std::cout << " Weight: " << mData.mWeight << std::endl; for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; std::cout << " Original: " << mData.mOriginal << std::endl; std::cout << " Scale: " << mData.mScale << std::endl; std::cout << " Type: " << creatureTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Level: " << mData.mData.mLevel << std::endl; std::cout << " Attributes:" << std::endl; std::cout << " Strength: " << mData.mData.mStrength << std::endl; std::cout << " Intelligence: " << mData.mData.mIntelligence << std::endl; std::cout << " Willpower: " << mData.mData.mWillpower << std::endl; std::cout << " Agility: " << mData.mData.mAgility << std::endl; std::cout << " Speed: " << mData.mData.mSpeed << std::endl; std::cout << " Endurance: " << mData.mData.mEndurance << std::endl; std::cout << " Personality: " << mData.mData.mPersonality << std::endl; std::cout << " Luck: " << mData.mData.mLuck << std::endl; std::cout << " Health: " << mData.mData.mHealth << std::endl; std::cout << " Magicka: " << mData.mData.mMana << std::endl; std::cout << " Fatigue: " << mData.mData.mFatigue << std::endl; std::cout << " Soul: " << mData.mData.mSoul << std::endl; std::cout << " Combat: " << mData.mData.mCombat << std::endl; std::cout << " Magic: " << mData.mData.mMagic << std::endl; std::cout << " Stealth: " << mData.mData.mStealth << std::endl; std::cout << " Attack1: " << mData.mData.mAttack[0] << "-" << mData.mData.mAttack[1] << std::endl; std::cout << " Attack2: " << mData.mData.mAttack[2] << "-" << mData.mData.mAttack[3] << std::endl; std::cout << " Attack3: " << mData.mData.mAttack[4] << "-" << mData.mData.mAttack[5] << std::endl; std::cout << " Gold: " << mData.mData.mGold << std::endl; for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem << std::endl; for (const std::string &spell : mData.mSpells.mList) std::cout << " Spell: " << spell << std::endl; printTransport(mData.getTransport()); std::cout << " Artificial Intelligence: " << std::endl; std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) printAIPackage(package); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? for (const ESM::DialInfo &info : mData.mInfo) std::cout << "INFO!" << info.mId << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Type: " << enchantTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " Flags: " << enchantmentFlags(mData.mData.mFlags) << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl; std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) << " (" << mData.mData.mAttribute[0] << ")" << std::endl; std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) << " (" << mData.mData.mAttribute[1] << ")" << std::endl; for (int skill : mData.mData.mSkills) if (skill != -1) std::cout << " Skill: " << skillLabel(skill) << " (" << skill << ")" << std::endl; for (int i = 0; i != 10; i++) if (!mData.mRanks[i].empty()) { std::cout << " Rank: " << mData.mRanks[i] << std::endl; std::cout << " Attribute1 Requirement: " << mData.mData.mRankData[i].mAttribute1 << std::endl; std::cout << " Attribute2 Requirement: " << mData.mData.mRankData[i].mAttribute2 << std::endl; std::cout << " One Skill at Level: " << mData.mData.mRankData[i].mPrimarySkill << std::endl; std::cout << " Two Skills at Level: " << mData.mData.mRankData[i].mFavouredSkill << std::endl; std::cout << " Faction Reaction: " << mData.mData.mRankData[i].mFactReaction << std::endl; } for (const auto &reaction : mData.mReactions) std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; } template<> void Record::print() { std::cout << " Id: " << mData.mId << std::endl; if (!mData.mPrev.empty()) std::cout << " Previous ID: " << mData.mPrev << std::endl; if (!mData.mNext.empty()) std::cout << " Next ID: " << mData.mNext << std::endl; std::cout << " Text: " << mData.mResponse << std::endl; if (!mData.mActor.empty()) std::cout << " Actor: " << mData.mActor << std::endl; if (!mData.mRace.empty()) std::cout << " Race: " << mData.mRace << std::endl; if (!mData.mClass.empty()) std::cout << " Class: " << mData.mClass << std::endl; std::cout << " Factionless: " << mData.mFactionLess << std::endl; if (!mData.mFaction.empty()) std::cout << " NPC Faction: " << mData.mFaction << std::endl; if (mData.mData.mRank != -1) std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; if (!mData.mPcFaction.empty()) std::cout << " PC Faction: " << mData.mPcFaction << std::endl; // CHANGE? non-standard capitalization mPCrank -> mPCRank (mPcRank?) if (mData.mData.mPCrank != -1) std::cout << " PC Rank: " << (int)mData.mData.mPCrank << std::endl; if (!mData.mCell.empty()) std::cout << " Cell: " << mData.mCell << std::endl; if (mData.mData.mDisposition > 0) std::cout << " Disposition/Journal index: " << mData.mData.mDisposition << std::endl; if (mData.mData.mGender != ESM::DialInfo::NA) std::cout << " Gender: " << mData.mData.mGender << std::endl; if (!mData.mSound.empty()) std::cout << " Sound File: " << mData.mSound << std::endl; std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" << std::endl; std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; for (const ESM::DialInfo::SelectStruct &rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; if (!mData.mResultScript.empty()) { if (mPrintPlain) { std::cout << " Result Script:" << std::endl; std::cout << "START--------------------------------------" << std::endl; std::cout << mData.mResultScript << std::endl; std::cout << "END----------------------------------------" << std::endl; } else { std::cout << " Result Script: [skipped]" << std::endl; } } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; for (int i = 0; i !=4; i++) { // A value of -1 means no effect if (mData.mData.mEffectID[i] == -1) continue; std::cout << " Effect: " << magicEffectLabel(mData.mData.mEffectID[i]) << " (" << mData.mData.mEffectID[i] << ")" << std::endl; std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) << " (" << mData.mData.mSkills[i] << ")" << std::endl; std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl; std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) { std::cout << " Height Offset: " << data->mHeightOffset << std::endl; // Lots of missing members. std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } mData.unloadData(); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; for (const ESM::LevelledListBase::LevelItem &item : mData.mList) std::cout << " Creature: Level: " << item.mLevel << " Creature: " << item.mId << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; for (const ESM::LevelledListBase::LevelItem &item : mData.mList) std::cout << " Inventory: Level: " << item.mLevel << " Item: " << item.mId << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { if (!mData.mName.empty()) std::cout << " Name: " << mData.mName << std::endl; if (!mData.mModel.empty()) std::cout << " Model: " << mData.mModel << std::endl; if (!mData.mIcon.empty()) std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Flags: " << lightFlags(mData.mData.mFlags) << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Index: " << magicEffectLabel(mData.mIndex) << " (" << mData.mIndex << ")" << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; std::cout << " Flags: " << magicEffectFlags(mData.mData.mFlags) << std::endl; std::cout << " Particle Texture: " << mData.mParticle << std::endl; if (!mData.mCasting.empty()) std::cout << " Casting Static: " << mData.mCasting << std::endl; if (!mData.mCastSound.empty()) std::cout << " Casting Sound: " << mData.mCastSound << std::endl; if (!mData.mBolt.empty()) std::cout << " Bolt Static: " << mData.mBolt << std::endl; if (!mData.mBoltSound.empty()) std::cout << " Bolt Sound: " << mData.mBoltSound << std::endl; if (!mData.mHit.empty()) std::cout << " Hit Static: " << mData.mHit << std::endl; if (!mData.mHitSound.empty()) std::cout << " Hit Sound: " << mData.mHitSound << std::endl; if (!mData.mArea.empty()) std::cout << " Area Static: " << mData.mArea << std::endl; if (!mData.mAreaSound.empty()) std::cout << " Area Sound: " << mData.mAreaSound << std::endl; std::cout << " School: " << schoolLabel(mData.mData.mSchool) << " (" << mData.mData.mSchool << ")" << std::endl; std::cout << " Base Cost: " << mData.mData.mBaseCost << std::endl; std::cout << " Unknown 1: " << mData.mData.mUnknown1 << std::endl; std::cout << " Speed: " << mData.mData.mSpeed << std::endl; std::cout << " Unknown 2: " << mData.mData.mUnknown2 << std::endl; std::cout << " RGB Color: " << "(" << mData.mData.mRed << "," << mData.mData.mGreen << "," << mData.mData.mBlue << ")" << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Animation: " << mData.mModel << std::endl; std::cout << " Hair Model: " << mData.mHair << std::endl; std::cout << " Head Model: " << mData.mHead << std::endl; std::cout << " Race: " << mData.mRace << std::endl; std::cout << " Class: " << mData.mClass << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mFaction.empty()) std::cout << " Faction: " << mData.mFaction << std::endl; std::cout << " Flags: " << npcFlags((int)mData.mFlags) << std::endl; if (mData.mBloodType != 0) std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; } else { std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; std::cout << " Attributes:" << std::endl; std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl; std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl; std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl; std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl; std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl; std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl; std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl; std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl; std::cout << " Skills:" << std::endl; for (int i = 0; i != ESM::Skill::Length; i++) std::cout << " " << skillLabel(i) << ": " << (int)(mData.mNpdt.mSkills[i]) << std::endl; std::cout << " Health: " << mData.mNpdt.mHealth << std::endl; std::cout << " Magicka: " << mData.mNpdt.mMana << std::endl; std::cout << " Fatigue: " << mData.mNpdt.mFatigue << std::endl; std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; } for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem << std::endl; for (const std::string &spell : mData.mSpells.mList) std::cout << " Spell: " << spell << std::endl; printTransport(mData.getTransport()); std::cout << " Artificial Intelligence: " << std::endl; std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) printAIPackage(package); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Cell: " << mData.mCell << std::endl; std::cout << " Coordinates: (" << mData.mData.mX << "," << mData.mData.mY << ")" << std::endl; std::cout << " Unknown S1: " << mData.mData.mS1 << std::endl; if ((unsigned int)mData.mData.mS2 != mData.mPoints.size()) std::cout << " Reported Point Count: " << mData.mData.mS2 << std::endl; std::cout << " Point Count: " << mData.mPoints.size() << std::endl; std::cout << " Edge Count: " << mData.mEdges.size() << std::endl; int i = 0; for (const ESM::Pathgrid::Point &point : mData.mPoints) { std::cout << " Point[" << i << "]:" << std::endl; std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; std::cout << " Unknown: " << point.mUnknown << std::endl; i++; } i = 0; for (const ESM::Pathgrid::Edge &edge : mData.mEdges) { std::cout << " Edge[" << i << "]: " << edge.mV0 << " -> " << edge.mV1 << std::endl; if (edge.mV0 >= mData.mData.mS2 || edge.mV1 >= mData.mData.mS2) std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { static const char *sAttributeNames[8] = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck" }; std::cout << " Name: " << mData.mName << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl; for (int i=0; i<2; ++i) { bool male = i==0; std::cout << (male ? " Male:" : " Female:") << std::endl; for (int j=0; j<8; ++j) std::cout << " " << sAttributeNames[j] << ": " << mData.mData.mAttributeValues[j].getValue (male) << std::endl; std::cout << " Height: " << mData.mData.mHeight.getValue (male) << std::endl; std::cout << " Weight: " << mData.mData.mWeight.getValue (male) << std::endl; } for (int i = 0; i != 7; i++) // Not all races have 7 skills. if (mData.mData.mBonus[i].mSkill != -1) std::cout << " Skill: " << skillLabel(mData.mData.mBonus[i].mSkill) << " (" << mData.mData.mBonus[i].mSkill << ") = " << mData.mData.mBonus[i].mBonus << std::endl; for (const std::string &power : mData.mPowers.mList) std::cout << " Power: " << power << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Weather:" << std::endl; std::cout << " Clear: " << (int)mData.mData.mClear << std::endl; std::cout << " Cloudy: " << (int)mData.mData.mCloudy << std::endl; std::cout << " Foggy: " << (int)mData.mData.mFoggy << std::endl; std::cout << " Overcast: " << (int)mData.mData.mOvercast << std::endl; std::cout << " Rain: " << (int)mData.mData.mOvercast << std::endl; std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl; std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl; std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl; std::cout << " Snow: " << (int)mData.mData.mSnow << std::endl; std::cout << " Blizzard: " << (int)mData.mData.mBlizzard << std::endl; std::cout << " Map Color: " << mData.mMapColor << std::endl; if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; for (const ESM::Region::SoundRef &soundref : mData.mSoundList) std::cout << " Sound: " << (int)soundref.mChance << " = " << soundref.mSound << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mId << std::endl; std::cout << " Num Shorts: " << mData.mData.mNumShorts << std::endl; std::cout << " Num Longs: " << mData.mData.mNumLongs << std::endl; std::cout << " Num Floats: " << mData.mData.mNumFloats << std::endl; std::cout << " Script Data Size: " << mData.mData.mScriptDataSize << std::endl; std::cout << " Table Size: " << mData.mData.mStringTableSize << std::endl; for (const std::string &variable : mData.mVarNames) std::cout << " Variable: " << variable << std::endl; std::cout << " ByteCode: "; for (const unsigned char &byte : mData.mScriptData) std::cout << Misc::StringUtils::format("%02X", (int)(byte)); std::cout << std::endl; if (mPrintPlain) { std::cout << " Script:" << std::endl; std::cout << "START--------------------------------------" << std::endl; std::cout << mData.mScriptText << std::endl; std::cout << "END----------------------------------------" << std::endl; } else { std::cout << " Script: [skipped]" << std::endl; } std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " ID: " << skillLabel(mData.mIndex) << " (" << mData.mIndex << ")" << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Governing Attribute: " << attributeLabel(mData.mData.mAttribute) << " (" << mData.mData.mAttribute << ")" << std::endl; std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) << " (" << mData.mData.mSpecialization << ")" << std::endl; for (int i = 0; i != 4; i++) std::cout << " UseValue[" << i << "]:" << mData.mData.mUseValue[i] << std::endl; } template<> void Record::print() { if (!mData.mCreature.empty()) std::cout << " Creature: " << mData.mCreature << std::endl; std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Volume: " << (int)mData.mData.mVolume << std::endl; if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Type: " << spellTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Start Script: " << mData.mId << std::endl; std::cout << " Start Data: " << mData.mData << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " Model: " << mData.mModel << std::endl; } template<> void Record::print() { // No names on VFX bolts if (!mData.mName.empty()) std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; // No icons on VFX bolts or magic bolts if (!mData.mIcon.empty()) std::cout << " Icon: " << mData.mIcon << std::endl; if (!mData.mScript.empty()) std::cout << " Script: " << mData.mScript << std::endl; if (!mData.mEnchant.empty()) std::cout << " Enchantment: " << mData.mEnchant << std::endl; std::cout << " Type: " << weaponTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Flags: " << weaponFlags(mData.mData.mFlags) << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Health: " << mData.mData.mHealth << std::endl; std::cout << " Speed: " << mData.mData.mSpeed << std::endl; std::cout << " Reach: " << mData.mData.mReach << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mData.mData.mChop[0] != 0 && mData.mData.mChop[1] != 0) std::cout << " Chop: " << (int)mData.mData.mChop[0] << "-" << (int)mData.mData.mChop[1] << std::endl; if (mData.mData.mSlash[0] != 0 && mData.mData.mSlash[1] != 0) std::cout << " Slash: " << (int)mData.mData.mSlash[0] << "-" << (int)mData.mData.mSlash[1] << std::endl; if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> std::string Record::getId() const { return mData.mName; } template<> std::string Record::getId() const { return std::string(); // No ID for Land record } template<> std::string Record::getId() const { return std::string(); // No ID for MagicEffect record } template<> std::string Record::getId() const { return std::string(); // No ID for Pathgrid record } template<> std::string Record::getId() const { return std::string(); // No ID for Skill record } } // end namespace openmw-openmw-0.48.0/apps/esmtool/record.hpp000066400000000000000000000103621445372753700210520ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_RECORD_H #define OPENMW_ESMTOOL_RECORD_H #include #include #include namespace ESM { class ESMReader; class ESMWriter; } namespace EsmTool { template class Record; class RecordBase { protected: std::string mId; uint32_t mFlags; ESM::NAME mType; bool mPrintPlain; public: RecordBase () : mFlags(0) , mPrintPlain(false) { } virtual ~RecordBase() {} virtual std::string getId() const = 0; uint32_t getFlags() const { return mFlags; } void setFlags(uint32_t flags) { mFlags = flags; } ESM::NAME getType() const { return mType; } void setPrintPlain(bool plain) { mPrintPlain = plain; } virtual void load(ESM::ESMReader &esm) = 0; virtual void save(ESM::ESMWriter &esm) = 0; virtual void print() = 0; static std::unique_ptr create(ESM::NAME type); // just make it a bit shorter template Record *cast() { return static_cast *>(this); } }; template class Record : public RecordBase { T mData; bool mIsDeleted; public: Record() : mIsDeleted(false) {} std::string getId() const override { return mData.mId; } T &get() { return mData; } void save(ESM::ESMWriter &esm) override { mData.save(esm, mIsDeleted); } void load(ESM::ESMReader &esm) override { mData.load(esm, mIsDeleted); } void print() override; }; template<> std::string Record::getId() const; template<> std::string Record::getId() const; template<> std::string Record::getId() const; template<> std::string Record::getId() const; template<> std::string Record::getId() const; template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); template<> void Record::print(); } #endif openmw-openmw-0.48.0/apps/esmtool/tes4.cpp000066400000000000000000000400721445372753700204470ustar00rootroot00000000000000#include "tes4.hpp" #include "arguments.hpp" #include "labels.hpp" #include #include #include #include #include namespace EsmTool { namespace { struct Params { const bool mQuite; explicit Params(const Arguments& info) : mQuite(info.quiet_given || info.mode == "clone") {} }; std::string toString(ESM4::GroupType type) { switch (type) { case ESM4::Grp_RecordType: return "RecordType"; case ESM4::Grp_WorldChild: return "WorldChild"; case ESM4::Grp_InteriorCell: return "InteriorCell"; case ESM4::Grp_InteriorSubCell: return "InteriorSubCell"; case ESM4::Grp_ExteriorCell: return "ExteriorCell"; case ESM4::Grp_ExteriorSubCell: return "ExteriorSubCell"; case ESM4::Grp_CellChild: return "CellChild"; case ESM4::Grp_TopicChild: return "TopicChild"; case ESM4::Grp_CellPersistentChild: return "CellPersistentChild"; case ESM4::Grp_CellTemporaryChild: return "CellTemporaryChild"; case ESM4::Grp_CellVisibleDistChild: return "CellVisibleDistChild"; } return "Unknown (" + std::to_string(type) + ")"; } template > struct HasFormId : std::false_type {}; template struct HasFormId> : std::true_type {}; template constexpr bool hasFormId = HasFormId::value; template > struct HasFlags : std::false_type {}; template struct HasFlags> : std::true_type {}; template constexpr bool hasFlags = HasFlags::value; template void readTypedRecord(const Params& params, ESM4::Reader& reader) { reader.getRecordData(); T value; value.load(reader); if (params.mQuite) return; std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); if constexpr (hasFormId) std::cout << ' ' << value.mFormId; if constexpr (hasFlags) std::cout << "\n Record flags: " << recordFlags(value.mFlags); std::cout << '\n'; } void readRecord(const Params& params, ESM4::Reader& reader) { switch (static_cast(reader.hdr().record.typeId)) { case ESM4::REC_AACT: break; case ESM4::REC_ACHR: return readTypedRecord(params, reader); case ESM4::REC_ACRE: return readTypedRecord(params, reader); case ESM4::REC_ACTI: return readTypedRecord(params, reader); case ESM4::REC_ADDN: break; case ESM4::REC_ALCH: return readTypedRecord(params, reader); case ESM4::REC_ALOC: return readTypedRecord(params, reader); case ESM4::REC_AMMO: return readTypedRecord(params, reader); case ESM4::REC_ANIO: return readTypedRecord(params, reader); case ESM4::REC_APPA: return readTypedRecord(params, reader); case ESM4::REC_ARMA: return readTypedRecord(params, reader); case ESM4::REC_ARMO: return readTypedRecord(params, reader); case ESM4::REC_ARTO: break; case ESM4::REC_ASPC: return readTypedRecord(params, reader); case ESM4::REC_ASTP: break; case ESM4::REC_AVIF: break; case ESM4::REC_BOOK: return readTypedRecord(params, reader); case ESM4::REC_BPTD: return readTypedRecord(params, reader); case ESM4::REC_CAMS: break; case ESM4::REC_CCRD: break; case ESM4::REC_CELL: return readTypedRecord(params, reader); case ESM4::REC_CLAS: return readTypedRecord(params, reader); case ESM4::REC_CLFM: return readTypedRecord(params, reader); case ESM4::REC_CLMT: break; case ESM4::REC_CLOT: return readTypedRecord(params, reader); case ESM4::REC_CMNY: break; case ESM4::REC_COBJ: break; case ESM4::REC_COLL: break; case ESM4::REC_CONT: return readTypedRecord(params, reader); case ESM4::REC_CPTH: break; case ESM4::REC_CREA: return readTypedRecord(params, reader); case ESM4::REC_CSTY: break; case ESM4::REC_DEBR: break; case ESM4::REC_DIAL: return readTypedRecord(params, reader); case ESM4::REC_DLBR: break; case ESM4::REC_DLVW: break; case ESM4::REC_DOBJ: return readTypedRecord(params, reader); case ESM4::REC_DOOR: return readTypedRecord(params, reader); case ESM4::REC_DUAL: break; case ESM4::REC_ECZN: break; case ESM4::REC_EFSH: break; case ESM4::REC_ENCH: break; case ESM4::REC_EQUP: break; case ESM4::REC_EXPL: break; case ESM4::REC_EYES: return readTypedRecord(params, reader); case ESM4::REC_FACT: break; case ESM4::REC_FLOR: return readTypedRecord(params, reader); case ESM4::REC_FLST: return readTypedRecord(params, reader); case ESM4::REC_FSTP: break; case ESM4::REC_FSTS: break; case ESM4::REC_FURN: return readTypedRecord(params, reader); case ESM4::REC_GLOB: return readTypedRecord(params, reader); case ESM4::REC_GMST: break; case ESM4::REC_GRAS: return readTypedRecord(params, reader); case ESM4::REC_GRUP: break; case ESM4::REC_HAIR: return readTypedRecord(params, reader); case ESM4::REC_HAZD: break; case ESM4::REC_HDPT: return readTypedRecord(params, reader); case ESM4::REC_IDLE: // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm // return readTypedRecord(params, reader); break; case ESM4::REC_IDLM: return readTypedRecord(params, reader); case ESM4::REC_IMAD: break; case ESM4::REC_IMGS: break; case ESM4::REC_IMOD: return readTypedRecord(params, reader); case ESM4::REC_INFO: return readTypedRecord(params, reader); case ESM4::REC_INGR: return readTypedRecord(params, reader); case ESM4::REC_IPCT: break; case ESM4::REC_IPDS: break; case ESM4::REC_KEYM: return readTypedRecord(params, reader); case ESM4::REC_KYWD: break; case ESM4::REC_LAND: return readTypedRecord(params, reader); case ESM4::REC_LCRT: break; case ESM4::REC_LCTN: break; case ESM4::REC_LGTM: return readTypedRecord(params, reader); case ESM4::REC_LIGH: return readTypedRecord(params, reader); case ESM4::REC_LSCR: break; case ESM4::REC_LTEX: return readTypedRecord(params, reader); case ESM4::REC_LVLC: return readTypedRecord(params, reader); case ESM4::REC_LVLI: return readTypedRecord(params, reader); case ESM4::REC_LVLN: return readTypedRecord(params, reader); case ESM4::REC_LVSP: break; case ESM4::REC_MATO: return readTypedRecord(params, reader); case ESM4::REC_MATT: break; case ESM4::REC_MESG: break; case ESM4::REC_MGEF: break; case ESM4::REC_MISC: return readTypedRecord(params, reader); case ESM4::REC_MOVT: break; case ESM4::REC_MSET: return readTypedRecord(params, reader); case ESM4::REC_MSTT: return readTypedRecord(params, reader); case ESM4::REC_MUSC: return readTypedRecord(params, reader); case ESM4::REC_MUST: break; case ESM4::REC_NAVI: return readTypedRecord(params, reader); case ESM4::REC_NAVM: return readTypedRecord(params, reader); case ESM4::REC_NOTE: return readTypedRecord(params, reader); case ESM4::REC_NPC_: return readTypedRecord(params, reader); case ESM4::REC_OTFT: return readTypedRecord(params, reader); case ESM4::REC_PACK: return readTypedRecord(params, reader); case ESM4::REC_PERK: break; case ESM4::REC_PGRD: return readTypedRecord(params, reader); case ESM4::REC_PGRE: return readTypedRecord(params, reader); case ESM4::REC_PHZD: break; case ESM4::REC_PROJ: break; case ESM4::REC_PWAT: return readTypedRecord(params, reader); case ESM4::REC_QUST: return readTypedRecord(params, reader); case ESM4::REC_RACE: return readTypedRecord(params, reader); case ESM4::REC_REFR: return readTypedRecord(params, reader); case ESM4::REC_REGN: return readTypedRecord(params, reader); case ESM4::REC_RELA: break; case ESM4::REC_REVB: break; case ESM4::REC_RFCT: break; case ESM4::REC_ROAD: return readTypedRecord(params, reader); case ESM4::REC_SBSP: return readTypedRecord(params, reader); case ESM4::REC_SCEN: break; case ESM4::REC_SCOL: return readTypedRecord(params, reader); case ESM4::REC_SCPT: return readTypedRecord(params, reader); case ESM4::REC_SCRL: return readTypedRecord(params, reader); case ESM4::REC_SGST: return readTypedRecord(params, reader); case ESM4::REC_SHOU: break; case ESM4::REC_SLGM: return readTypedRecord(params, reader); case ESM4::REC_SMBN: break; case ESM4::REC_SMEN: break; case ESM4::REC_SMQN: break; case ESM4::REC_SNCT: break; case ESM4::REC_SNDR: return readTypedRecord(params, reader); case ESM4::REC_SOPM: break; case ESM4::REC_SOUN: return readTypedRecord(params, reader); case ESM4::REC_SPEL: break; case ESM4::REC_SPGD: break; case ESM4::REC_STAT: return readTypedRecord(params, reader); case ESM4::REC_TACT: return readTypedRecord(params, reader); case ESM4::REC_TERM: return readTypedRecord(params, reader); case ESM4::REC_TES4: return readTypedRecord(params, reader); case ESM4::REC_TREE: return readTypedRecord(params, reader); case ESM4::REC_TXST: return readTypedRecord(params, reader); case ESM4::REC_VTYP: break; case ESM4::REC_WATR: break; case ESM4::REC_WEAP: return readTypedRecord(params, reader); case ESM4::REC_WOOP: break; case ESM4::REC_WRLD: return readTypedRecord(params, reader); case ESM4::REC_WTHR: break; } if (!params.mQuite) std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; reader.skipRecordData(); } bool readItem(const Params& params, ESM4::Reader& reader); bool readGroup(const Params& params, ESM4::Reader& reader) { const ESM4::RecordHeader& header = reader.hdr(); if (!params.mQuite) std::cout << "\nGroup: " << toString(static_cast(header.group.type)) << " " << ESM::NAME(header.group.typeId).toStringView() << '\n'; switch (static_cast(header.group.type)) { case ESM4::Grp_RecordType: case ESM4::Grp_InteriorCell: case ESM4::Grp_InteriorSubCell: case ESM4::Grp_ExteriorCell: case ESM4::Grp_ExteriorSubCell: reader.enterGroup(); return readItem(params, reader); case ESM4::Grp_WorldChild: case ESM4::Grp_CellChild: case ESM4::Grp_TopicChild: case ESM4::Grp_CellPersistentChild: case ESM4::Grp_CellTemporaryChild: case ESM4::Grp_CellVisibleDistChild: reader.adjustGRUPFormId(); reader.enterGroup(); if (!reader.hasMoreRecs()) return false; return readItem(params, reader); } reader.skipGroup(); return true; } bool readItem(const Params& params, ESM4::Reader& reader) { if (!reader.getRecordHeader() || !reader.hasMoreRecs()) return false; const ESM4::RecordHeader& header = reader.hdr(); if (header.record.typeId == ESM4::REC_GRUP) return readGroup(params, reader); readRecord(params, reader); return true; } } int loadTes4(const Arguments& info, std::unique_ptr&& stream) { std::cout << "Loading TES4 file: " << info.filename << '\n'; try { const ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); ESM4::Reader reader(std::move(stream), info.filename); reader.setEncoder(&encoder); const Params params(info); if (!params.mQuite) { std::cout << "Author: " << reader.getAuthor() << '\n' << "Description: " << reader.getDesc() << '\n' << "File format version: " << reader.esmVersion() << '\n'; if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) { std::cout << "Masters:" << '\n'; for (const auto& master : masterData) std::cout << " " << master.name << ", " << master.size << " bytes\n"; } } while (reader.hasMoreRecs()) { reader.exitGroupCheck(); if (!readItem(params, reader)) break; } } catch (const std::exception& e) { std::cout << "\nERROR:\n\n " << e.what() << std::endl; return -1; } return 0; } } openmw-openmw-0.48.0/apps/esmtool/tes4.hpp000066400000000000000000000004401445372753700204470ustar00rootroot00000000000000#ifndef OPENMW_ESMTOOL_TES4_H #define OPENMW_ESMTOOL_TES4_H #include #include #include namespace EsmTool { struct Arguments; int loadTes4(const Arguments& info, std::unique_ptr&& stream); } #endif openmw-openmw-0.48.0/apps/essimporter/000077500000000000000000000000001445372753700177535ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/essimporter/CMakeLists.txt000066400000000000000000000022601445372753700225130ustar00rootroot00000000000000set(ESSIMPORTER_FILES main.cpp importer.cpp importplayer.cpp importnpcc.cpp importcrec.cpp importcellref.cpp importinventory.cpp importklst.cpp importcntc.cpp importgame.cpp importinfo.cpp importdial.cpp importques.cpp importjour.cpp importscri.cpp importscpt.cpp importproj.cpp importsplm.cpp importercontext.cpp converter.cpp convertacdt.cpp convertnpcc.cpp convertinventory.cpp convertcrec.cpp convertcntc.cpp convertscri.cpp convertscpt.cpp convertplayer.cpp ) openmw_add_executable(openmw-essimporter ${ESSIMPORTER_FILES} ) target_link_libraries(openmw-essimporter ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-essimporter gcov) endif() if (WIN32) INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") endif(WIN32) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-essimporter PRIVATE ) endif() openmw-openmw-0.48.0/apps/essimporter/convertacdt.cpp000066400000000000000000000115651445372753700230030ustar00rootroot00000000000000#include #include #include #include #include "convertacdt.hpp" namespace ESSImport { int translateDynamicIndex(int mwIndex) { if (mwIndex == 1) return 2; else if (mwIndex == 2) return 1; return mwIndex; } void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats) { for (int i=0; i<3; ++i) { int writeIndex = translateDynamicIndex(i); cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1]; cStats.mDynamic[writeIndex].mMod = 0.f; cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0]; } for (int i=0; i<8; ++i) { cStats.mAttributes[i].mBase = acdt.mAttributes[i][1]; cStats.mAttributes[i].mMod = 0.f; cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0]; } cStats.mGoldPool = acdt.mGoldPool; cStats.mTalkedTo = (acdt.mFlags & TalkedToPlayer) != 0; cStats.mAttacked = (acdt.mFlags & Attacked) != 0; } void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats) { cStats.mDead = (acsc.mFlags & Dead) != 0; } void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats) { for (int i=0; i::max(); state.mScriptedAnims.push_back(scriptedAnim); } else // TODO: Handle 0xFF index, which seems to be used for finished animations. std::cerr << "unknown animation group index: " << static_cast(anis.mGroupIndex) << std::endl; } } openmw-openmw-0.48.0/apps/essimporter/convertacdt.hpp000066400000000000000000000013171445372753700230020ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTACDT_H #define OPENMW_ESSIMPORT_CONVERTACDT_H #include #include #include #include #include "importacdt.hpp" namespace ESSImport { // OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka int translateDynamicIndex(int mwIndex); void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats); void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); void convertANIS (const ANIS& anis, ESM::AnimationState& state); } #endif openmw-openmw-0.48.0/apps/essimporter/convertcntc.cpp000066400000000000000000000003431445372753700230070ustar00rootroot00000000000000#include "convertcntc.hpp" #include "convertinventory.hpp" namespace ESSImport { void convertCNTC(const CNTC &cntc, ESM::ContainerState &state) { convertInventory(cntc.mInventory, state.mInventory); } } openmw-openmw-0.48.0/apps/essimporter/convertcntc.hpp000066400000000000000000000003771445372753700230230ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTCNTC_H #define OPENMW_ESSIMPORT_CONVERTCNTC_H #include "importcntc.hpp" #include namespace ESSImport { void convertCNTC(const CNTC& cntc, ESM::ContainerState& state); } #endif openmw-openmw-0.48.0/apps/essimporter/convertcrec.cpp000066400000000000000000000003421445372753700227730ustar00rootroot00000000000000#include "convertcrec.hpp" #include "convertinventory.hpp" namespace ESSImport { void convertCREC(const CREC &crec, ESM::CreatureState &state) { convertInventory(crec.mInventory, state.mInventory); } } openmw-openmw-0.48.0/apps/essimporter/convertcrec.hpp000066400000000000000000000003751445372753700230060ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTCREC_H #define OPENMW_ESSIMPORT_CONVERTCREC_H #include "importcrec.hpp" #include namespace ESSImport { void convertCREC(const CREC& crec, ESM::CreatureState& state); } #endif openmw-openmw-0.48.0/apps/essimporter/converter.cpp000066400000000000000000000461261445372753700224770ustar00rootroot00000000000000#include "converter.hpp" #include #include #include #include #include #include #include "convertcrec.hpp" #include "convertcntc.hpp" #include "convertscri.hpp" namespace { void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out) { osg::ref_ptr image (new osg::Image); image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE); memcpy(image->data(), data, size); image->flipVertical(); osgDB::writeImageFile(*image, out); } void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate) { objstate.mEnabled = cellref.mEnabled; objstate.mPosition = cellref.mPos; objstate.mRef.mRefNum = cellref.mRefNum; if (cellref.mDeleted) objstate.mCount = 0; convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); if (cellref.mActorData.mHasANIS) convertANIS(cellref.mActorData.mANIS, objstate.mAnimationState); } bool isIndexedRefId(const std::string& indexedRefId) { if (indexedRefId.size() <= 8) return false; if (indexedRefId.find_first_not_of("0123456789") == std::string::npos) return false; // entirely numeric refid, this is a reference to // a dynamically created record e.g. player-enchanted weapon std::string index = indexedRefId.substr(indexedRefId.size()-8); return index.find_first_not_of("0123456789ABCDEF") == std::string::npos; } void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId) { std::stringstream stream; stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8); stream >> refIndex; refId = indexedRefId.substr(0,indexedRefId.size()-8); } int convertActorId(const std::string& indexedRefId, ESSImport::Context& context) { if (isIndexedRefId(indexedRefId)) { int refIndex = 0; std::string refId; splitIndexedRefId(indexedRefId, refIndex, refId); auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId)); if (it == context.mActorIdMap.end()) return -1; return it->second; } else if (indexedRefId == "PlayerSaveGame") { return context.mPlayer.mObject.mCreatureStats.mActorId; } return -1; } } namespace ESSImport { struct MAPH { unsigned int size; unsigned int value; }; void ConvertFMAP::read(ESM::ESMReader &esm) { MAPH maph; esm.getHNT(maph, "MAPH"); std::vector data; esm.getSubNameIs("MAPD"); esm.getSubHeader(); data.resize(esm.getSubSize()); esm.getExact(&data[0], data.size()); mGlobalMapImage = new osg::Image; mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE); memcpy(mGlobalMapImage->data(), &data[0], data.size()); // to match openmw size // FIXME: filtering? mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE); } void ConvertFMAP::write(ESM::ESMWriter &esm) { int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly // with the 512x512 map the game has by default int cellSize = mGlobalMapImage->s()/numcells; // Note the upper left corner of the (0,0) cell should be at (width/2, height/2) mContext->mGlobalMapState.mBounds.mMinX = -numcells/2; mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2; mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2; mContext->mGlobalMapState.mBounds.mMaxY = numcells/2; osg::ref_ptr image2 (new osg::Image); int width = cellSize*numcells; int height = cellSize*numcells; std::vector data; data.resize(width*height*4, 0); image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); memcpy(image2->data(), &data[0], data.size()); for (const auto & exploredCell : mContext->mExploredCells) { if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX || exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX || exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY || exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY) { // out of bounds, I think this could happen, since the original engine had a fixed-size map continue; } int imageLeftSrc = mGlobalMapImage->s()/2; int imageTopSrc = mGlobalMapImage->t()/2; imageLeftSrc += exploredCell.first * cellSize; imageTopSrc -= exploredCell.second * cellSize; int imageLeftDst = width/2; int imageTopDst = height/2; imageLeftDst += exploredCell.first * cellSize; imageTopDst -= exploredCell.second * cellSize; for (int x=0; xdata(imageLeftSrc+x, imageTopSrc+y, 0); *(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col; } } std::stringstream ostream; osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { std::cerr << "Error: can't write global map image, no png readerwriter found" << std::endl; return; } image2->flipVertical(); osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream); if (!result.success()) { std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() << std::endl; return; } std::string outData = ostream.str(); mContext->mGlobalMapState.mImageData = std::vector(outData.begin(), outData.end()); esm.startRecord(ESM::REC_GMAP); mContext->mGlobalMapState.save(esm); esm.endRecord(ESM::REC_GMAP); } void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; bool isDeleted = false; cell.load(esm, isDeleted, false); // I wonder what 0x40 does? if (cell.isExterior() && cell.mData.mFlags & 0x20) { mContext->mGlobalMapState.mMarkers.insert(std::make_pair(cell.mData.mX, cell.mData.mY)); } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position if (cell.mName == mContext->mPlayerCellName) { mContext->mPlayer.mCellId = cell.getCellId(); } Cell newcell; newcell.mCell = cell; // fog of war // seems to be a 1-bit pixel format, 16*16 pixels // TODO: add bleeding of FOW into neighbouring cells (openmw handles this by writing to the textures, // MW handles it when rendering only) unsigned char nam8[32]; // exterior has 1 NAM8, interior can have multiple ones, and have an extra 4 byte flag at the start // (probably offset of that specific fog texture?) while (esm.isNextSub("NAM8")) { if (cell.isExterior()) // TODO: NAM8 occasionally exists for cells that haven't been explored. // are there any flags marking explored cells? mContext->mExploredCells.insert(std::make_pair(cell.mData.mX, cell.mData.mY)); esm.getSubHeader(); if (esm.getSubSize() == 36) { // flag on interiors esm.skip(4); } esm.getExact(nam8, 32); newcell.mFogOfWar.reserve(16*16); for (int x=0; x<16; ++x) { for (int y=0; y<16; ++y) { size_t pos = x*16+y; size_t bytepos = pos/8; assert(bytepos<32); int bit = pos%8; newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff); } } if (cell.isExterior()) { std::ostringstream filename; filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga"; convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str()); } } // moved reference, not handled yet // NOTE: MVRF can also occur in within normal references (importcellref.cpp)? // this does not match the ESM file implementation, // verify if that can happen with ESM files too while (esm.isNextSub("MVRF")) { esm.skipHSub(); // skip MVRF esm.getSubName(); esm.skipHSub(); // skip CNDT } std::vector cellrefs; while (esm.hasMoreSubs() && esm.peekNextSub("FRMR")) { CellRef ref; ref.load (esm); cellrefs.push_back(ref); } while (esm.isNextSub("MPCD")) { float notepos[3]; esm.getHTSized<3 * sizeof(float)>(notepos); // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. // This seems to be the reason markers can't be placed everywhere in interior cells, // i.e. when the grid is exceeded. // Converting the interior markers correctly could be rather tricky, but is probably similar logic // as used for the FoW texture placement, which we need to figure out anyway notepos[1] += 31.f; notepos[0] += 0.5; notepos[1] += 0.5; notepos[0] = Constants::CellSizeInUnits * notepos[0] / 32.f; notepos[1] = Constants::CellSizeInUnits * notepos[1] / 32.f; if (cell.isExterior()) { notepos[0] += Constants::CellSizeInUnits * cell.mData.mX; notepos[1] += Constants::CellSizeInUnits * cell.mData.mY; } // TODO: what encoding is this in? std::string note = esm.getHNString("MPNT"); ESM::CustomMarker marker; marker.mWorldX = notepos[0]; marker.mWorldY = notepos[1]; marker.mNote = note; marker.mCell = cell.getCellId(); mMarkers.push_back(marker); } newcell.mRefs = cellrefs; if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else mIntCells[cell.mName] = newcell; } void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) { ESM::Cell esmcell = cell.mCell; esm.startRecord(ESM::REC_CSTA); ESM::CellState csta; csta.mHasFogOfWar = 0; csta.mLastRespawn.mDay = 0; csta.mLastRespawn.mHour = 0; csta.mId = esmcell.getCellId(); csta.mId.save(esm); // TODO csta.mLastRespawn; // shouldn't be needed if we respawn on global schedule like in original MW csta.mWaterLevel = esmcell.mWater; csta.save(esm); for (const auto & cellref : cell.mRefs) { ESM::CellRef out (cellref); // TODO: use mContext->mCreatures/mNpcs if (!isIndexedRefId(cellref.mIndexedRefId)) { // non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it // this could be any type of object really (even creatures/npcs too) out.mRefID = cellref.mIndexedRefId; std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); ESM::ObjectState objstate; objstate.blank(); objstate.mRef = out; objstate.mRef.mRefID = idLower; objstate.mHasCustomState = false; convertCellRef(cellref, objstate); esm.writeHNT ("OBJE", 0); objstate.save(esm); continue; } else { int refIndex = 0; splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID); std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); auto npccIt = mContext->mNpcChanges.find( std::make_pair(refIndex, out.mRefID)); if (npccIt != mContext->mNpcChanges.end()) { ESM::NpcState objstate; objstate.blank(); objstate.mRef = out; objstate.mRef.mRefID = idLower; // TODO: need more micromanagement here so we don't overwrite values // from the ESM with default values if (cellref.mActorData.mHasACDT) convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); else objstate.mCreatureStats.mMissingACDT = true; if (cellref.mActorData.mHasACSC) convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats); convertNpcData(cellref.mActorData, objstate.mNpcStats); convertNPCC(npccIt->second, objstate); convertCellRef(cellref, objstate); objstate.mCreatureStats.mActorId = mContext->generateActorId(); mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); esm.writeHNT ("OBJE", ESM::REC_NPC_); objstate.save(esm); continue; } auto cntcIt = mContext->mContainerChanges.find( std::make_pair(refIndex, out.mRefID)); if (cntcIt != mContext->mContainerChanges.end()) { ESM::ContainerState objstate; objstate.blank(); objstate.mRef = out; objstate.mRef.mRefID = idLower; convertCNTC(cntcIt->second, objstate); convertCellRef(cellref, objstate); esm.writeHNT ("OBJE", ESM::REC_CONT); objstate.save(esm); continue; } auto crecIt = mContext->mCreatureChanges.find( std::make_pair(refIndex, out.mRefID)); if (crecIt != mContext->mCreatureChanges.end()) { ESM::CreatureState objstate; objstate.blank(); objstate.mRef = out; objstate.mRef.mRefID = idLower; // TODO: need more micromanagement here so we don't overwrite values // from the ESM with default values if (cellref.mActorData.mHasACDT) convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); else objstate.mCreatureStats.mMissingACDT = true; if (cellref.mActorData.mHasACSC) convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats); convertCREC(crecIt->second, objstate); convertCellRef(cellref, objstate); objstate.mCreatureStats.mActorId = mContext->generateActorId(); mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); esm.writeHNT ("OBJE", ESM::REC_CREA); objstate.save(esm); continue; } std::stringstream error; error << "Can't find type for " << cellref.mIndexedRefId << std::endl; throw std::runtime_error(error.str()); } } esm.endRecord(ESM::REC_CSTA); } void ConvertCell::write(ESM::ESMWriter &esm) { for (const auto & cell : mIntCells) writeCell(cell.second, esm); for (const auto & cell : mExtCells) writeCell(cell.second, esm); for (const auto & marker : mMarkers) { esm.startRecord(ESM::REC_MARK); marker.save(esm); esm.endRecord(ESM::REC_MARK); } } void ConvertPROJ::read(ESM::ESMReader& esm) { mProj.load(esm); } void ConvertPROJ::write(ESM::ESMWriter& esm) { for (const PROJ::PNAM& pnam : mProj.mProjectiles) { if (!pnam.isMagic()) { ESM::ProjectileState out; convertBaseState(out, pnam); out.mBowId = pnam.mBowId.toString(); out.mVelocity = pnam.mVelocity; out.mAttackStrength = pnam.mAttackStrength; esm.startRecord(ESM::REC_PROJ); out.save(esm); esm.endRecord(ESM::REC_PROJ); } else { ESM::MagicBoltState out; convertBaseState(out, pnam); auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(), [&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; }); if (it == mContext->mActiveSpells.end()) { std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl; continue; } out.mSpellId = it->mSPDT.mId.toString(); out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from out.mSlot = 0; esm.startRecord(ESM::REC_MPRJ); out.save(esm); esm.endRecord(ESM::REC_MPRJ); } } } void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) { base.mId = pnam.mArrowId.toString(); base.mPosition = pnam.mPosition; osg::Quat orient; orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity); base.mOrientation = orient; base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext); } void ConvertSPLM::read(ESM::ESMReader& esm) { mSPLM.load(esm); mContext->mActiveSpells = mSPLM.mActiveSpells; } void ConvertSPLM::write(ESM::ESMWriter& esm) { std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl; } } openmw-openmw-0.48.0/apps/essimporter/converter.hpp000066400000000000000000000424501445372753700225000ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTER_H #define OPENMW_ESSIMPORT_CONVERTER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "importcrec.hpp" #include "importcntc.hpp" #include "importercontext.hpp" #include "importcellref.hpp" #include "importklst.hpp" #include "importgame.hpp" #include "importinfo.hpp" #include "importdial.hpp" #include "importques.hpp" #include "importjour.hpp" #include "importscpt.hpp" #include "importproj.h" #include "importsplm.h" #include "convertacdt.hpp" #include "convertnpcc.hpp" #include "convertscpt.hpp" #include "convertplayer.hpp" namespace ESSImport { class Converter { public: /// @return the order for writing this converter's records to the output file, in relation to other converters virtual int getStage() { return 1; } virtual ~Converter() {} void setContext(Context& context) { mContext = &context; } /// @note The load method of ESM records accept the deleted flag as a parameter. /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. virtual void read(ESM::ESMReader& esm) { } /// Called after the input file has been read in completely, which may be necessary /// if the conversion process relies on information in other records virtual void write(ESM::ESMWriter& esm) { } protected: Context* mContext; }; /// Default converter: simply reads the record and writes it unmodified to the output template class DefaultConverter : public Converter { public: int getStage() override { return 0; } void read(ESM::ESMReader& esm) override { T record; bool isDeleted = false; record.load(esm, isDeleted); mRecords[record.mId] = record; } void write(ESM::ESMWriter& esm) override { for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); it->second.save(esm); esm.endRecord(T::sRecordId); } } protected: std::map mRecords; }; class ConvertNPC : public Converter { public: void read(ESM::ESMReader &esm) override { ESM::NPC npc; bool isDeleted = false; npc.load(esm, isDeleted); if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here // it will apply to ALL instances of the class. seems to be the reason for the // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; } else { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; mContext->mPlayerBase = npc; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList; // Clear the list now that we've written it, this prevents issues cropping up with // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. mContext->mPlayerBase.mSpells.mList.clear(); // Same with inventory. Actually it's strange this would contain something, since there's already an // inventory list in NPCC. There seems to be a fair amount of redundancy in this format. mContext->mPlayerBase.mInventory.mList.clear(); } } }; class ConvertCREA : public Converter { public: void read(ESM::ESMReader &esm) override { // See comment in ConvertNPC ESM::Creature creature; bool isDeleted = false; creature.load(esm, isDeleted); mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; // Do we need ConvertCONT? // I've seen a CONT record in a certain save file, but the container contents in it // were identical to a corresponding CNTC record. See previous comment about redundancy... class ConvertGlobal : public DefaultConverter { public: void read(ESM::ESMReader &esm) override { ESM::Global global; bool isDeleted = false; global.load(esm, isDeleted); if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); mRecords[global.mId] = global; } }; class ConvertClass : public DefaultConverter { public: void read(ESM::ESMReader &esm) override { ESM::Class class_; bool isDeleted = false; class_.load(esm, isDeleted); if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; mRecords[class_.mId] = class_; } }; class ConvertBook : public DefaultConverter { public: void read(ESM::ESMReader &esm) override { ESM::Book book; bool isDeleted = false; book.load(esm, isDeleted); if (book.mData.mSkillId == -1) mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); mRecords[book.mId] = book; } }; class ConvertNPCC : public Converter { public: void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); NPCC npcc; npcc.load(esm); if (id == "PlayerSaveGame") { convertNPCC(npcc, mContext->mPlayer.mObject); } else { int index = npcc.mNPDT.mIndex; mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc)); } } }; class ConvertREFR : public Converter { public: void read(ESM::ESMReader &esm) override { CellRef refr; refr.load(esm); assert(refr.mIndexedRefId == "PlayerSaveGame"); mContext->mPlayer.mObject.mPosition = refr.mPos; ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats; convertACDT(refr.mActorData.mACDT, cStats); ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats; convertNpcData(refr.mActorData, npcStats); mSelectedSpell = refr.mActorData.mSelectedSpell; if (!refr.mActorData.mSelectedEnchantItem.empty()) { ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory; for (unsigned int i=0; imPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); } void write(ESM::ESMWriter &esm) override { esm.startRecord(ESM::REC_ENAB); esm.writeHNT("TELE", mTeleportingEnabled); esm.writeHNT("LEVT", mLevitationEnabled); esm.endRecord(ESM::REC_ENAB); esm.startRecord(ESM::REC_CAM_); esm.writeHNT("FIRS", mFirstPersonCam); esm.endRecord(ESM::REC_CAM_); } private: bool mFirstPersonCam; bool mTeleportingEnabled; bool mLevitationEnabled; }; class ConvertCNTC : public Converter { void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); CNTC cntc; cntc.load(esm); mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex,id), cntc)); } }; class ConvertCREC : public Converter { public: void read(ESM::ESMReader &esm) override { std::string id = esm.getHNString("NAME"); CREC crec; crec.load(esm); mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec)); } }; class ConvertFMAP : public Converter { public: void read(ESM::ESMReader &esm) override; void write(ESM::ESMWriter &esm) override; private: osg::ref_ptr mGlobalMapImage; }; class ConvertCell : public Converter { public: void read(ESM::ESMReader& esm) override; void write(ESM::ESMWriter& esm) override; private: struct Cell { ESM::Cell mCell; std::vector mRefs; std::vector mFogOfWar; }; std::map mIntCells; std::map, Cell> mExtCells; std::vector mMarkers; void writeCell(const Cell& cell, ESM::ESMWriter &esm); }; class ConvertKLST : public Converter { public: void read(ESM::ESMReader& esm) override { KLST klst; klst.load(esm); mKillCounter = klst.mKillCounter; mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; } void write(ESM::ESMWriter &esm) override { esm.startRecord(ESM::REC_DCOU); for (auto it = mKillCounter.begin(); it != mKillCounter.end(); ++it) { esm.writeHNString("ID__", it->first); esm.writeHNT ("COUN", it->second); } esm.endRecord(ESM::REC_DCOU); } private: std::map mKillCounter; }; class ConvertFACT : public Converter { public: void read(ESM::ESMReader& esm) override { ESM::Faction faction; bool isDeleted = false; faction.load(esm, isDeleted); std::string id = Misc::StringUtils::lowerCase(faction.mId); for (auto it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); } } }; /// Stolen items class ConvertSTLN : public Converter { public: void read(ESM::ESMReader &esm) override { std::string itemid = esm.getHNString("NAME"); Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { if (esm.retSubName().toString() == "FNAM") { std::string factionid = esm.getHString(); mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); } else { std::string ownerid = esm.getHString(); mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); } } } void write(ESM::ESMWriter &esm) override { ESM::StolenItems items; for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it) { std::map, int> owners; for (const auto & ownerIt : it->second) { owners.insert(std::make_pair(std::make_pair(ownerIt.first, ownerIt.second) // Since OpenMW doesn't suffer from the owner contamination bug, // it needs a count argument. But for legacy savegames, we don't know // this count, so must assume all items of that ID are stolen, // like vanilla MW did. ,std::numeric_limits::max())); } items.mStolenItems.insert(std::make_pair(it->first, owners)); } esm.startRecord(ESM::REC_STLN); items.write(esm); esm.endRecord(ESM::REC_STLN); } private: typedef std::pair Owner; // std::map > mStolenItems; }; /// Seen responses for a dialogue topic? /// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs /// Dialogue conversion problems: /// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID. /// - Seen dialogue responses only store the INFO id, rather than the fulltext. /// - Quest stages only store the INFO id, rather than the journal entry fulltext. class ConvertINFO : public Converter { public: void read(ESM::ESMReader& esm) override { INFO info; info.load(esm); } }; class ConvertDIAL : public Converter { public: void read(ESM::ESMReader& esm) override { std::string id = esm.getHNString("NAME"); DIAL dial; dial.load(esm); if (dial.mIndex > 0) mDials[id] = dial; } void write(ESM::ESMWriter &esm) override { for (auto it = mDials.begin(); it != mDials.end(); ++it) { esm.startRecord(ESM::REC_QUES); ESM::QuestState state; state.mFinished = 0; state.mState = it->second.mIndex; state.mTopic = Misc::StringUtils::lowerCase(it->first); state.save(esm); esm.endRecord(ESM::REC_QUES); } } private: std::map mDials; }; class ConvertQUES : public Converter { public: void read(ESM::ESMReader& esm) override { std::string id = esm.getHNString("NAME"); QUES quest; quest.load(esm); } }; class ConvertJOUR : public Converter { public: void read(ESM::ESMReader& esm) override { JOUR journal; journal.load(esm); } }; class ConvertGAME : public Converter { public: ConvertGAME() : mHasGame(false) { } void read(ESM::ESMReader &esm) override { mGame.load(esm); mHasGame = true; } int validateWeatherID(int weatherID) { if(weatherID >= -1 && weatherID < 10) { return weatherID; } else { throw std::runtime_error("Invalid weather ID: " + std::to_string(weatherID)); } } void write(ESM::ESMWriter &esm) override { if (!mHasGame) return; esm.startRecord(ESM::REC_WTHR); ESM::WeatherState weather; weather.mTimePassed = 0.0f; weather.mFastForward = false; weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); weather.mQueuedWeather = -1; // TODO: Determine how ModRegion modifiers are saved in Morrowind. weather.save(esm); esm.endRecord(ESM::REC_WTHR); } private: bool mHasGame; GAME mGame; }; /// Running global script class ConvertSCPT : public Converter { public: void read(ESM::ESMReader &esm) override { SCPT script; script.load(esm); ESM::GlobalScript out; convertSCPT(script, out); mScripts.push_back(out); } void write(ESM::ESMWriter &esm) override { for (const auto & script : mScripts) { esm.startRecord(ESM::REC_GSCR); script.save(esm); esm.endRecord(ESM::REC_GSCR); } } private: std::vector mScripts; }; /// Projectile converter class ConvertPROJ : public Converter { public: int getStage() override { return 2; } void read(ESM::ESMReader& esm) override; void write(ESM::ESMWriter& esm) override; private: void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); PROJ mProj; }; class ConvertSPLM : public Converter { public: void read(ESM::ESMReader& esm) override; void write(ESM::ESMWriter& esm) override; private: SPLM mSPLM; }; } #endif openmw-openmw-0.48.0/apps/essimporter/convertinventory.cpp000066400000000000000000000022571445372753700241230ustar00rootroot00000000000000#include "convertinventory.hpp" #include #include namespace ESSImport { void convertInventory(const Inventory &inventory, ESM::InventoryState &state) { int index = 0; for (const auto & item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = Misc::StringUtils::lowerCase(item.mId); objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile // openmw handles them differently, so no need to set any flags state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about // Not a big deal, OpenMW will auto-correct to a valid slot, the only problem is when // an item could be equipped in two different slots (e.g. equipped two rings) state.mEquipmentSlots[index] = item.mRelativeEquipmentSlot; ++index; } } } openmw-openmw-0.48.0/apps/essimporter/convertinventory.hpp000066400000000000000000000004361445372753700241250ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTINVENTORY_H #define OPENMW_ESSIMPORT_CONVERTINVENTORY_H #include "importinventory.hpp" #include namespace ESSImport { void convertInventory (const Inventory& inventory, ESM::InventoryState& state); } #endif openmw-openmw-0.48.0/apps/essimporter/convertnpcc.cpp000066400000000000000000000005471445372753700230110ustar00rootroot00000000000000#include "convertnpcc.hpp" #include "convertinventory.hpp" namespace ESSImport { void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState) { npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition; npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation; convertInventory(npcc.mInventory, npcState.mInventory); } } openmw-openmw-0.48.0/apps/essimporter/convertnpcc.hpp000066400000000000000000000003671445372753700230160ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTNPCC_H #define OPENMW_ESSIMPORT_CONVERTNPCC_H #include "importnpcc.hpp" #include namespace ESSImport { void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState); } #endif openmw-openmw-0.48.0/apps/essimporter/convertplayer.cpp000066400000000000000000000100371445372753700233550ustar00rootroot00000000000000#include "convertplayer.hpp" #include #include #include namespace ESSImport { void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls) { out.mObject.mPosition.rot[0] = -atan2(pcdt.mPNAM.mVerticalRotation.mData[2][1], pcdt.mPNAM.mVerticalRotation.mData[2][2]); out.mBirthsign = pcdt.mBirthsign; out.mObject.mNpcStats.mBounty = pcdt.mBounty; for (const auto & essFaction : pcdt.mFactions) { ESM::NpcStats::Faction faction; faction.mExpelled = (essFaction.mFlags & 0x2) != 0; faction.mRank = essFaction.mRank; faction.mReputation = essFaction.mReputation; out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(essFaction.mFactionName.toString())] = faction; } for (int i=0; i<3; ++i) out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i]; for (int i=0; i<8; ++i) out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; for (int i=0; i<27; ++i) out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i]; out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawn) out.mObject.mCreatureStats.mDrawState = 1; if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawn) out.mObject.mCreatureStats.mDrawState = 2; firstPersonCam = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ThirdPerson); teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled); levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled); for (const auto & knownDialogueTopic : pcdt.mKnownDialogueTopics) { outDialogueTopics.push_back(Misc::StringUtils::lowerCase(knownDialogueTopic)); } controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled; controls.mControlsDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ControlsDisabled; controls.mJumpingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_JumpingDisabled; controls.mLookingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LookingDisabled; controls.mVanityModeDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_VanityModeDisabled; controls.mWeaponDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawingDisabled; controls.mSpellDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawingDisabled; if (pcdt.mHasMark) { out.mHasMark = 1; const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation; ESM::CellId cell; cell.mWorldspace = ESM::CellId::sDefaultWorldspace; cell.mPaged = true; cell.mIndex.mX = mark.mCellX; cell.mIndex.mY = mark.mCellY; // TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell. if (mark.mCellX == 0 && mark.mCellY == 0) { cell.mWorldspace = pcdt.mMNAM; cell.mPaged = false; } out.mMarkedCell = cell; out.mMarkedPosition.pos[0] = mark.mX; out.mMarkedPosition.pos[1] = mark.mY; out.mMarkedPosition.pos[2] = mark.mZ; out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f; out.mMarkedPosition.rot[2] = mark.mRotZ; } if (pcdt.mHasENAM) { out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * Constants::CellSizeInUnits; out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * Constants::CellSizeInUnits; out.mLastKnownExteriorPosition[2] = 0.0f; } } } openmw-openmw-0.48.0/apps/essimporter/convertplayer.hpp000066400000000000000000000006641445372753700233670ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTPLAYER_H #define OPENMW_ESSIMPORT_CONVERTPLAYER_H #include "importplayer.hpp" #include #include namespace ESSImport { void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls); } #endif openmw-openmw-0.48.0/apps/essimporter/convertscpt.cpp000066400000000000000000000006731445372753700230370ustar00rootroot00000000000000#include "convertscpt.hpp" #include #include "convertscri.hpp" namespace ESSImport { void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out) { out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString()); out.mRunning = scpt.mRunning; out.mTargetRef.unset(); // TODO: convert target reference of global script convertSCRI(scpt.mSCRI, out.mLocals); } } openmw-openmw-0.48.0/apps/essimporter/convertscpt.hpp000066400000000000000000000003651445372753700230420ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H #define OPENMW_ESSIMPORT_CONVERTSCPT_H #include #include "importscpt.hpp" namespace ESSImport { void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out); } #endif openmw-openmw-0.48.0/apps/essimporter/convertscri.cpp000066400000000000000000000015341445372753700230230ustar00rootroot00000000000000#include "convertscri.hpp" namespace { template void storeVariables(const std::vector& variables, ESM::Locals& locals, const std::string& scriptname) { for (const auto& variable : variables) { ESM::Variant val(variable); val.setType(VariantType); locals.mVariables.emplace_back(std::string(), val); } } } namespace ESSImport { void convertSCRI(const SCRI &scri, ESM::Locals &locals) { // order *is* important, as we do not have variable names available in this format storeVariables (scri.mShorts, locals, scri.mScript); storeVariables (scri.mLongs, locals, scri.mScript); storeVariables (scri.mFloats, locals, scri.mScript); } } openmw-openmw-0.48.0/apps/essimporter/convertscri.hpp000066400000000000000000000004351445372753700230270ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONVERTSCRI_H #define OPENMW_ESSIMPORT_CONVERTSCRI_H #include "importscri.hpp" #include namespace ESSImport { /// Convert script variable assignments void convertSCRI (const SCRI& scri, ESM::Locals& locals); } #endif openmw-openmw-0.48.0/apps/essimporter/importacdt.hpp000066400000000000000000000042261445372753700226360ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_ACDT_H #define OPENMW_ESSIMPORT_ACDT_H #include #include "importscri.hpp" namespace ESM { class ESMReader; } namespace ESSImport { enum ACDTFlags { TalkedToPlayer = 0x4, Attacked = 0x100, Unknown = 0x200 }; enum ACSCFlags { Dead = 0x2 }; /// Actor data, shared by (at least) REFR and CellRef #pragma pack(push) #pragma pack(1) struct ACDT { // Note, not stored at *all*: // - Level changes are lost on reload, except for the player (there it's in the NPC record). unsigned char mUnknown[12]; unsigned int mFlags; float mBreathMeter; // Seconds left before drowning unsigned char mUnknown2[20]; float mDynamic[3][2]; unsigned char mUnknown3[16]; float mAttributes[8][2]; float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes unsigned char mUnknown4[4]; unsigned int mGoldPool; unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe // this one is for respawning? unsigned char mUnknown5[3]; }; struct ACSC { unsigned char mUnknown1[17]; unsigned char mFlags; // ACSCFlags unsigned char mUnknown2[22]; unsigned char mCorpseClearCountdown; // hours? unsigned char mUnknown3[71]; }; struct ANIS { unsigned char mGroupIndex; unsigned char mUnknown[3]; float mTime; }; #pragma pack(pop) struct ActorData { bool mHasACDT; ACDT mACDT; bool mHasACSC; ACSC mACSC; int mSkills[27][2]; // skills, base and modified // creature combat stats, base and modified // I think these can be ignored in the conversion, because it is not possible // to change them ingame int mCombatStats[3][2]; std::string mSelectedSpell; std::string mSelectedEnchantItem; SCRI mSCRI; bool mHasANIS; ANIS mANIS; // scripted animation state }; } #endif openmw-openmw-0.48.0/apps/essimporter/importcellref.cpp000066400000000000000000000124131445372753700233270ustar00rootroot00000000000000#include "importcellref.hpp" #include namespace ESSImport { void CellRef::load(ESM::ESMReader &esm) { blank(); esm.getHNT(mRefNum.mIndex, "FRMR"); // this is required since openmw supports more than 255 content files int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24; mRefNum.mContentFile = pluginIndex-1; mRefNum.mIndex &= 0x00ffffff; mIndexedRefId = esm.getHNString("NAME"); if (esm.isNextSub("ACTN")) { /* Activation flags: ActivationFlag_UseEnabled = 1 ActivationFlag_OnActivate = 2 ActivationFlag_OnDeath = 10h ActivationFlag_OnKnockout = 20h ActivationFlag_OnMurder = 40h ActivationFlag_DoorOpening = 100h ActivationFlag_DoorClosing = 200h ActivationFlag_DoorJammedOpening = 400h ActivationFlag_DoorJammedClosing = 800h */ esm.skipHSub(); } if (esm.isNextSub("STPR")) esm.skipHSub(); if (esm.isNextSub("MNAM")) esm.skipHSub(); bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); mActorData.mHasACDT = false; if (esm.isNextSub("ACDT")) { mActorData.mHasACDT = true; esm.getHT(mActorData.mACDT); } mActorData.mHasACSC = false; if (esm.isNextSub("ACSC")) { mActorData.mHasACSC = true; esm.getHT(mActorData.mACSC); } if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); if (esm.isNextSub("CSTN")) esm.skipHSub(); // "PlayerSaveGame", link to some object? if (esm.isNextSub("LSTN")) esm.skipHSub(); // "PlayerSaveGame", link to some object? // unsure at which point between LSTN and TGTN if (esm.isNextSub("CSHN")) esm.skipHSub(); // "PlayerSaveGame", link to some object? // unsure if before or after CSTN/LSTN if (esm.isNextSub("LSHN")) esm.skipHSub(); // "PlayerSaveGame", link to some object? while (esm.isNextSub("TGTN")) esm.skipHSub(); // "PlayerSaveGame", link to some object? while (esm.isNextSub("FGTN")) esm.getHString(); // fight target? // unsure at which point between TGTN and CRED if (esm.isNextSub("AADT")) { // occurred when a creature was in the middle of its attack, 44 bytes esm.skipHSub(); } // unsure at which point between FGTN and CHRD if (esm.isNextSub("PWPC")) esm.skipHSub(); if (esm.isNextSub("PWPS")) esm.skipHSub(); if (esm.isNextSub("WNAM")) { std::string id = esm.getHString(); if (esm.isNextSub("XNAM")) mActorData.mSelectedEnchantItem = esm.getHString(); else mActorData.mSelectedSpell = id; if (esm.isNextSub("YNAM")) esm.skipHSub(); // 4 byte, 0 } while (esm.isNextSub("APUD")) { // used power esm.getSubHeader(); std::string id = esm.getString(32); (void)id; // timestamp can't be used: this is the total hours passed, calculated by // timestamp = 24 * (365 * year + cumulativeDays[month] + day) // unfortunately cumulativeDays[month] is not clearly defined, // in the (non-MCP) vanilla version the first month was missing, but MCP added it. double timestamp; esm.getT(timestamp); } // FIXME: not all actors have this, add flag if (esm.isNextSub("CHRD")) // npc only esm.getHExact(mActorData.mSkills, 27*2*sizeof(int)); if (esm.isNextSub("CRED")) // creature only esm.getHExact(mActorData.mCombatStats, 3*2*sizeof(int)); mActorData.mSCRI.load(esm); if (esm.isNextSub("ND3D")) esm.skipHSub(); mActorData.mHasANIS = false; if (esm.isNextSub("ANIS")) { mActorData.mHasANIS = true; esm.getHT(mActorData.mANIS); } if (esm.isNextSub("LVCR")) { // occurs on levelled creature spawner references // probably some identifier for the creature that has been spawned? unsigned char lvcr; esm.getHT(lvcr); //std::cout << "LVCR: " << (int)lvcr << std::endl; } mEnabled = true; esm.getHNOT(mEnabled, "ZNAM"); // DATA should occur for all references, except levelled creature spawners // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess esm.getHNOTSized<24>(mPos, "DATA"); esm.getHNOTSized<24>(mPos, "DATA"); mDeleted = 0; if (esm.isNextSub("DELE")) { unsigned int deleted; esm.getHT(deleted); mDeleted = ((deleted >> 24) & 0x2) != 0; // the other 3 bytes seem to be uninitialized garbage } if (esm.isNextSub("MVRF")) { esm.skipHSub(); esm.getSubName(); esm.skipHSub(); } } } openmw-openmw-0.48.0/apps/essimporter/importcellref.hpp000066400000000000000000000007651445372753700233430ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CELLREF_H #define OPENMW_ESSIMPORT_CELLREF_H #include #include #include "importacdt.hpp" namespace ESM { class ESMReader; } namespace ESSImport { struct CellRef : public ESM::CellRef { std::string mIndexedRefId; std::string mScript; bool mEnabled; bool mDeleted; ActorData mActorData; void load(ESM::ESMReader& esm); ~CellRef() = default; }; } #endif openmw-openmw-0.48.0/apps/essimporter/importcntc.cpp000066400000000000000000000003531445372753700226420ustar00rootroot00000000000000#include "importcntc.hpp" #include namespace ESSImport { void CNTC::load(ESM::ESMReader &esm) { mIndex = 0; esm.getHNT(mIndex, "INDX"); mInventory.load(esm); } } openmw-openmw-0.48.0/apps/essimporter/importcntc.hpp000066400000000000000000000005221445372753700226450ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTCNTC_H #define OPENMW_ESSIMPORT_IMPORTCNTC_H #include "importinventory.hpp" namespace ESM { class ESMReader; } namespace ESSImport { /// Changed container contents struct CNTC { int mIndex; Inventory mInventory; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importcrec.cpp000066400000000000000000000011141445372753700226230ustar00rootroot00000000000000#include "importcrec.hpp" #include namespace ESSImport { void CREC::load(ESM::ESMReader &esm) { esm.getHNT(mIndex, "INDX"); // equivalent of ESM::Creature XSCL? probably don't have to convert this, // since the value can't be changed float scale; esm.getHNOT(scale, "XSCL"); while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) mAiPackages.add(esm); mInventory.load(esm); } } openmw-openmw-0.48.0/apps/essimporter/importcrec.hpp000066400000000000000000000006161445372753700226360ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CREC_H #define OPENMW_ESSIMPORT_CREC_H #include "importinventory.hpp" #include namespace ESM { class ESMReader; } namespace ESSImport { /// Creature changes struct CREC { int mIndex; Inventory mInventory; ESM::AIPackageList mAiPackages; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importdial.cpp000066400000000000000000000011201445372753700226150ustar00rootroot00000000000000#include "importdial.hpp" #include namespace ESSImport { void DIAL::load(ESM::ESMReader &esm) { // See ESM::Dialogue::Type enum, not sure why we would need this here though int type = 0; esm.getHNOT(type, "DATA"); // Deleted dialogue in a savefile. No clue what this means... int deleted = 0; esm.getHNOT(deleted, "DELE"); mIndex = 0; // *should* always occur except when the dialogue is deleted, but leaving it optional just in case... esm.getHNOT(mIndex, "XIDX"); } } openmw-openmw-0.48.0/apps/essimporter/importdial.hpp000066400000000000000000000004011445372753700226230ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H #define OPENMW_ESSIMPORT_IMPORTDIAL_H namespace ESM { class ESMReader; } namespace ESSImport { struct DIAL { int mIndex; // Journal index void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importer.cpp000066400000000000000000000403751445372753700223310ustar00rootroot00000000000000#include "importer.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "importercontext.hpp" #include "converter.hpp" namespace { void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out) { if (fileHeader.mSCRS.size() != 128*128*4) { std::cerr << "Error: unexpected screenshot size " << std::endl; return; } osg::ref_ptr image (new osg::Image); image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE); // need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise auto it = fileHeader.mSCRS.begin(); for (int y=0; y<128; ++y) { for (int x=0; x<128; ++x) { assert(image->data(x,y)); *(image->data(x,y)+2) = *it++; *(image->data(x,y)+1) = *it++; *image->data(x,y) = *it++; ++it; // skip alpha } } image->flipVertical(); std::stringstream ostream; osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { std::cerr << "Error: can't write screenshot: no jpg readerwriter found" << std::endl; return; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream); if (!result.success()) { std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status() << std::endl; return; } std::string data = ostream.str(); out.mScreenshot = std::vector(data.begin(), data.end()); } } namespace ESSImport { Importer::Importer(const std::string &essfile, const std::string &outfile, const std::string &encoding) : mEssFile(essfile) , mOutFile(outfile) , mEncoding(encoding) { } struct File { struct Subrecord { std::string mName; size_t mFileOffset; std::vector mData; }; struct Record { std::string mName; size_t mFileOffset; std::vector mSubrecords; }; std::vector mRecords; }; void read(const std::string& filename, File& file) { ESM::ESMReader esm; esm.open(filename); while (esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); esm.getRecHeader(); File::Record rec; rec.mName = n.toString(); rec.mFileOffset = esm.getFileOffset(); while (esm.hasMoreSubs()) { File::Subrecord sub; esm.getSubName(); esm.getSubHeader(); sub.mFileOffset = esm.getFileOffset(); sub.mName = esm.retSubName().toString(); sub.mData.resize(esm.getSubSize()); esm.getExact(&sub.mData[0], sub.mData.size()); rec.mSubrecords.push_back(sub); } file.mRecords.push_back(rec); } } void Importer::compare() { // data that always changes (and/or is already fully decoded) should be blacklisted std::set > blacklist; blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour blacklist.insert(std::make_pair("REFR", "DATA")); // player position blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes blacklist.insert(std::make_pair("CELL", "DELE")); // first 3 bytes are uninitialized // this changes way too often, name suggests some renderer internal data? blacklist.insert(std::make_pair("CELL", "ND3D")); blacklist.insert(std::make_pair("REFR", "ND3D")); File file1; read(mEssFile, file1); File file2; read(mOutFile, file2); // todo rename variable // FIXME: use max(size1, size2) for (unsigned int i=0; i= file2.mRecords.size()) { std::ios::fmtflags f(std::cout.flags()); std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; std::cout.flags(f); return; } File::Record rec2 = file2.mRecords[i]; if (rec.mName != rec2.mName) { std::ios::fmtflags f(std::cout.flags()); std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; std::cout.flags(f); return; // TODO: try to recover } // FIXME: use max(size1, size2) for (unsigned int j=0; j= rec2.mSubrecords.size()) { std::ios::fmtflags f(std::cout.flags()); std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; std::cout.flags(f); return; } File::Subrecord sub2 = rec2.mSubrecords[j]; if (sub.mName != sub2.mName) { std::ios::fmtflags f(std::cout.flags()); std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; std::cout.flags(f); break; // TODO: try to recover } if (sub.mData != sub2.mData) { if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) continue; std::ios::fmtflags f(std::cout.flags()); std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; std::cout << "Data 1:" << std::endl; for (unsigned int k=0; k= sub2.mData.size() || sub2.mData[k] != sub.mData[k]) different = true; if (different) std::cout << "\033[033m"; std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub.mData[k] << " "; if (different) std::cout << "\033[0m"; } std::cout << std::endl; std::cout << "Data 2:" << std::endl; for (unsigned int k=0; k= sub.mData.size() || sub.mData[k] != sub2.mData[k]) different = true; if (different) std::cout << "\033[033m"; std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub2.mData[k] << " "; if (different) std::cout << "\033[0m"; } std::cout << std::endl; std::cout.flags(f); } } } } void Importer::run() { ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding)); ESM::ESMReader esm; esm.open(mEssFile); esm.setEncoder(&encoder); Context context; const ESM::Header& header = esm.getHeader(); context.mPlayerCellName = header.mGameData.mCurrentCell.toString(); const unsigned int recREFR = ESM::fourCC("REFR"); const unsigned int recPCDT = ESM::fourCC("PCDT"); const unsigned int recFMAP = ESM::fourCC("FMAP"); const unsigned int recKLST = ESM::fourCC("KLST"); const unsigned int recSTLN = ESM::fourCC("STLN"); const unsigned int recGAME = ESM::fourCC("GAME"); const unsigned int recJOUR = ESM::fourCC("JOUR"); const unsigned int recSPLM = ESM::fourCC("SPLM"); std::map> converters; converters[ESM::REC_GLOB] = std::make_unique(); converters[ESM::REC_BOOK] = std::make_unique(); converters[ESM::REC_NPC_] = std::make_unique(); converters[ESM::REC_CREA] = std::make_unique(); converters[ESM::REC_NPCC] = std::make_unique(); converters[ESM::REC_CREC] = std::make_unique(); converters[recREFR ] = std::make_unique(); converters[recPCDT ] = std::make_unique(); converters[recFMAP ] = std::make_unique(); converters[recKLST ] = std::make_unique(); converters[recSTLN ] = std::make_unique(); converters[recGAME ] = std::make_unique(); converters[ESM::REC_CELL] = std::make_unique(); converters[ESM::REC_ALCH] = std::make_unique>(); converters[ESM::REC_CLAS] = std::make_unique(); converters[ESM::REC_SPEL] = std::make_unique>(); converters[ESM::REC_ARMO] = std::make_unique>(); converters[ESM::REC_WEAP] = std::make_unique>(); converters[ESM::REC_CLOT] = std::make_unique>(); converters[ESM::REC_ENCH] = std::make_unique>(); converters[ESM::REC_WEAP] = std::make_unique>(); converters[ESM::REC_LEVC] = std::make_unique>(); converters[ESM::REC_LEVI] = std::make_unique>(); converters[ESM::REC_CNTC] = std::make_unique(); converters[ESM::REC_FACT] = std::make_unique(); converters[ESM::REC_INFO] = std::make_unique(); converters[ESM::REC_DIAL] = std::make_unique(); converters[ESM::REC_QUES] = std::make_unique(); converters[recJOUR ] = std::make_unique(); converters[ESM::REC_SCPT] = std::make_unique(); converters[ESM::REC_PROJ] = std::make_unique(); converters[recSPLM] = std::make_unique(); // TODO: // - REGN (weather in certain regions?) // - VFXM // - SPLM (active spell effects) std::set unknownRecords; for (const auto & converter : converters) { converter.second->setContext(context); } while (esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); esm.getRecHeader(); auto it = converters.find(n.toInt()); if (it != converters.end()) { it->second->read(esm); } else { if (unknownRecords.insert(n.toInt()).second) { std::ios::fmtflags f(std::cerr.flags()); std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; std::cerr.flags(f); } esm.skipRecord(); } } ESM::ESMWriter writer; writer.setFormat (ESM::SavedGame::sCurrentFormat); boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary); // all unused writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); writer.setRecordCount (0); for (const auto & master : header.mMaster) writer.addMaster(master.name, 0); // not using the size information anyway -> use value of 0 writer.save (stream); ESM::SavedGame profile; for (const auto & master : header.mMaster) { profile.mContentFiles.push_back(master.name); } profile.mDescription = esm.getDesc(); profile.mInGameTime.mDay = context.mDay; profile.mInGameTime.mGameHour = context.mHour; profile.mInGameTime.mMonth = context.mMonth; profile.mInGameTime.mYear = context.mYear; profile.mTimePlayed = 0; profile.mPlayerCell = header.mGameData.mCurrentCell.toString(); if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN") profile.mPlayerClassName = context.mCustomPlayerClassName; else profile.mPlayerClassId = context.mPlayerBase.mClass; profile.mPlayerLevel = context.mPlayerBase.mNpdt.mLevel; profile.mPlayerName = header.mGameData.mPlayerName.toString(); writeScreenshot(header, profile); writer.startRecord (ESM::REC_SAVE); profile.save (writer); writer.endRecord (ESM::REC_SAVE); // Writing order should be Dynamic Store -> Cells -> Player, // so that references to dynamic records can be recognized when loading for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 0) continue; it->second->write(writer); } writer.startRecord(ESM::REC_NPC_); context.mPlayerBase.mId = "player"; context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 1) continue; it->second->write(writer); } writer.startRecord(ESM::REC_PLAY); if (context.mPlayer.mCellId.mPaged) { // exterior cell -> determine cell coordinates based on position int cellX = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits)); int cellY = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits)); context.mPlayer.mCellId.mIndex.mX = cellX; context.mPlayer.mCellId.mIndex.mY = cellY; } context.mPlayer.save(writer); writer.endRecord(ESM::REC_PLAY); writer.startRecord(ESM::REC_ACTC); writer.writeHNT("COUN", context.mNextActorId); writer.endRecord(ESM::REC_ACTC); // Stage 2 requires cell references to be written / actors IDs assigned for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 2) continue; it->second->write(writer); } writer.startRecord (ESM::REC_DIAS); context.mDialogueState.save(writer); writer.endRecord(ESM::REC_DIAS); writer.startRecord(ESM::REC_INPU); context.mControlsState.save(writer); writer.endRecord(ESM::REC_INPU); } } openmw-openmw-0.48.0/apps/essimporter/importer.hpp000066400000000000000000000006541445372753700223320ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORTER_IMPORTER_H #define OPENMW_ESSIMPORTER_IMPORTER_H #include namespace ESSImport { class Importer { public: Importer(const std::string& essfile, const std::string& outfile, const std::string& encoding); void run(); void compare(); private: std::string mEssFile; std::string mOutFile; std::string mEncoding; }; } #endif openmw-openmw-0.48.0/apps/essimporter/importercontext.cpp000066400000000000000000000000001445372753700237130ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/essimporter/importercontext.hpp000066400000000000000000000053211445372753700237330ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_CONTEXT_H #define OPENMW_ESSIMPORT_CONTEXT_H #include #include #include #include #include #include #include #include #include "importnpcc.hpp" #include "importcrec.hpp" #include "importcntc.hpp" #include "importplayer.hpp" #include "importsplm.h" namespace ESSImport { struct Context { // set from the TES3 header std::string mPlayerCellName; ESM::Player mPlayer; ESM::NPC mPlayerBase; std::string mCustomPlayerClassName; ESM::DialogueState mDialogueState; ESM::ControlsState mControlsState; // cells which should show an explored overlay on the global map std::set > mExploredCells; ESM::GlobalMap mGlobalMapState; int mDay, mMonth, mYear; float mHour; // key std::map, CREC> mCreatureChanges; std::map, NPCC> mNpcChanges; std::map, CNTC> mContainerChanges; std::map, int> mActorIdMap; int mNextActorId; std::map mCreatures; std::map mNpcs; std::vector mActiveSpells; Context() : mDay(0) , mMonth(0) , mYear(0) , mHour(0.f) , mNextActorId(0) { ESM::CellId playerCellId; playerCellId.mPaged = true; playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; mPlayer.mCellId = playerCellId; mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1] = mPlayer.mLastKnownExteriorPosition[2] = 0.0f; mPlayer.mHasMark = 0; mPlayer.mCurrentCrimeId = -1; // TODO mPlayer.mPaidCrimeId = -1; mPlayer.mObject.blank(); mPlayer.mObject.mEnabled = true; mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame mPlayer.mObject.mCreatureStats.mActorId = generateActorId(); mGlobalMapState.mBounds.mMinX = 0; mGlobalMapState.mBounds.mMaxX = 0; mGlobalMapState.mBounds.mMinY = 0; mGlobalMapState.mBounds.mMaxY = 0; mPlayerBase.blank(); } int generateActorId() { return mNextActorId++; } }; } #endif openmw-openmw-0.48.0/apps/essimporter/importgame.cpp000066400000000000000000000010751445372753700226260ustar00rootroot00000000000000#include "importgame.hpp" #include namespace ESSImport { void GAME::load(ESM::ESMReader &esm) { esm.getSubNameIs("GMDT"); esm.getSubHeader(); if (esm.getSubSize() == 92) { esm.getExact(&mGMDT, 92); mGMDT.mSecundaPhase = 0; } else if (esm.getSubSize() == 96) { esm.getT(mGMDT); } else esm.fail("unexpected subrecord size for GAME.GMDT"); mGMDT.mWeatherTransition &= (0x000000ff); mGMDT.mSecundaPhase &= (0x000000ff); mGMDT.mMasserPhase &= (0x000000ff); } } openmw-openmw-0.48.0/apps/essimporter/importgame.hpp000066400000000000000000000013401445372753700226260ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_GAME_H #define OPENMW_ESSIMPORT_GAME_H namespace ESM { class ESMReader; } namespace ESSImport { /// Weather data struct GAME { struct GMDT { char mCellName[64] {}; int mFogColour {0}; float mFogDensity {0.f}; int mCurrentWeather {0}, mNextWeather {0}; int mWeatherTransition {0}; // 0-100 transition between weathers, top 3 bytes may be garbage float mTimeOfNextTransition {0.f}; // weather changes when gamehour == timeOfNextTransition int mMasserPhase {0}, mSecundaPhase {0}; // top 3 bytes may be garbage }; GMDT mGMDT; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importinfo.cpp000066400000000000000000000003541445372753700226470ustar00rootroot00000000000000#include "importinfo.hpp" #include namespace ESSImport { void INFO::load(ESM::ESMReader &esm) { mInfo = esm.getHNString("INAM"); mActorRefId = esm.getHNString("ACDT"); } } openmw-openmw-0.48.0/apps/essimporter/importinfo.hpp000066400000000000000000000004541445372753700226550ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTINFO_H #define OPENMW_ESSIMPORT_IMPORTINFO_H #include namespace ESM { class ESMReader; } namespace ESSImport { struct INFO { std::string mInfo; std::string mActorRefId; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importinventory.cpp000066400000000000000000000043661445372753700237600ustar00rootroot00000000000000#include "importinventory.hpp" #include #include namespace ESSImport { void Inventory::load(ESM::ESMReader &esm) { while (esm.isNextSub("NPCO")) { ContItem contItem; esm.getHT(contItem); InventoryItem item; item.mId = contItem.mItem.toString(); item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; item.mLockLevel = 0; item.mRefNum.unset(); unsigned int itemCount = std::abs(item.mCount); bool separateStacks = false; for (unsigned int i=0;i= int(mItems.size())) esm.fail("equipment item index out of range"); // appears to be a relative index for only the *possible* slots this item can be equipped in, // i.e. 0 most of the time int slotIndex; esm.getT(slotIndex); mItems[itemIndex].mRelativeEquipmentSlot = slotIndex; } } } openmw-openmw-0.48.0/apps/essimporter/importinventory.hpp000066400000000000000000000012651445372753700237600ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H #define OPENMW_ESSIMPORT_IMPORTINVENTORY_H #include #include #include #include #include "importscri.hpp" namespace ESM { class ESMReader; } namespace ESSImport { struct ContItem { int mCount; ESM::NAME32 mItem; }; struct Inventory { struct InventoryItem : public ESM::CellRef { std::string mId; int mCount; int mRelativeEquipmentSlot; SCRI mSCRI; }; std::vector mItems; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importjour.cpp000066400000000000000000000002751445372753700226750ustar00rootroot00000000000000#include "importjour.hpp" #include namespace ESSImport { void JOUR::load(ESM::ESMReader &esm) { mText = esm.getHNString("NAME"); } } openmw-openmw-0.48.0/apps/essimporter/importjour.hpp000066400000000000000000000005021445372753700226730ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTJOUR_H #define OPENMW_ESSIMPORT_IMPORTJOUR_H #include namespace ESM { class ESMReader; } namespace ESSImport { /// Journal struct JOUR { // The entire journal, in HTML std::string mText; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importklst.cpp000066400000000000000000000006611445372753700226720ustar00rootroot00000000000000#include "importklst.hpp" #include namespace ESSImport { void KLST::load(ESM::ESMReader &esm) { while (esm.isNextSub("KNAM")) { std::string refId = esm.getHString(); int count; esm.getHNT(count, "CNAM"); mKillCounter[refId] = count; } mWerewolfKills = 0; esm.getHNOT(mWerewolfKills, "INTV"); } } openmw-openmw-0.48.0/apps/essimporter/importklst.hpp000066400000000000000000000005621445372753700226770ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_KLST_H #define OPENMW_ESSIMPORT_KLST_H #include #include namespace ESM { class ESMReader; } namespace ESSImport { /// Kill Stats struct KLST { void load(ESM::ESMReader& esm); /// RefId, kill count std::map mKillCounter; int mWerewolfKills; }; } #endif openmw-openmw-0.48.0/apps/essimporter/importnpcc.cpp000066400000000000000000000006221445372753700226350ustar00rootroot00000000000000#include "importnpcc.hpp" #include namespace ESSImport { void NPCC::load(ESM::ESMReader &esm) { esm.getHNT(mNPDT, "NPDT"); while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) mAiPackages.add(esm); mInventory.load(esm); } } openmw-openmw-0.48.0/apps/essimporter/importnpcc.hpp000066400000000000000000000011501445372753700226370ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_NPCC_H #define OPENMW_ESSIMPORT_NPCC_H #include #include #include "importinventory.hpp" namespace ESM { class ESMReader; } namespace ESSImport { struct NPCC { struct NPDT { unsigned char mDisposition; unsigned char unknown; unsigned char mReputation; unsigned char unknown2; int mIndex; } mNPDT; Inventory mInventory; ESM::AIPackageList mAiPackages; void load(ESM::ESMReader &esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importplayer.cpp000066400000000000000000000043761445372753700232200ustar00rootroot00000000000000#include "importplayer.hpp" #include namespace ESSImport { void PCDT::load(ESM::ESMReader &esm) { while (esm.isNextSub("DNAM")) { mKnownDialogueTopics.push_back(esm.getHString()); } mHasMark = false; if (esm.isNextSub("MNAM")) { mHasMark = true; mMNAM = esm.getHString(); } esm.getHNT(mPNAM, "PNAM"); if (esm.isNextSub("SNAM")) esm.skipHSub(); if (esm.isNextSub("NAM9")) esm.skipHSub(); // Rest state. You shouldn't even be able to save during rest, but skip just in case. if (esm.isNextSub("RNAM")) /* int hoursLeft; float x, y, z; // resting position */ esm.skipHSub(); // 16 bytes mBounty = 0; esm.getHNOT(mBounty, "CNAM"); mBirthsign = esm.getHNOString("BNAM"); // Holds the names of the last used Alchemy apparatus. Don't need to import this ATM, // because our GUI auto-selects the best apparatus. if (esm.isNextSub("NAM0")) esm.skipHSub(); if (esm.isNextSub("NAM1")) esm.skipHSub(); if (esm.isNextSub("NAM2")) esm.skipHSub(); if (esm.isNextSub("NAM3")) esm.skipHSub(); mHasENAM = false; if (esm.isNextSub("ENAM")) { mHasENAM = true; esm.getHT(mENAM); } if (esm.isNextSub("LNAM")) esm.skipHSub(); while (esm.isNextSub("FNAM")) { FNAM fnam; esm.getHT(fnam); mFactions.push_back(fnam); } mHasAADT = false; if (esm.isNextSub("AADT")) // Attack animation data? { mHasAADT = true; esm.getHT(mAADT); } if (esm.isNextSub("KNAM")) esm.skipHSub(); // assigned Quick Keys, I think if (esm.isNextSub("ANIS")) esm.skipHSub(); // 16 bytes if (esm.isNextSub("WERE")) { // some werewolf data, 152 bytes // maybe current skills and attributes for werewolf form esm.getSubHeader(); esm.skip(152); } } } openmw-openmw-0.48.0/apps/essimporter/importplayer.hpp000066400000000000000000000061141445372753700232150ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_PLAYER_H #define OPENMW_ESSIMPORT_PLAYER_H #include #include #include #include #include #include "importacdt.hpp" namespace ESM { class ESMReader; } namespace ESSImport { /// Other player data struct PCDT { int mBounty; std::string mBirthsign; std::vector mKnownDialogueTopics; enum PlayerFlags { PlayerFlags_ViewSwitchDisabled = 0x1, PlayerFlags_ControlsDisabled = 0x4, PlayerFlags_Sleeping = 0x10, PlayerFlags_Waiting = 0x40, PlayerFlags_WeaponDrawn = 0x80, PlayerFlags_SpellDrawn = 0x100, PlayerFlags_InJail = 0x200, PlayerFlags_JumpingDisabled = 0x1000, PlayerFlags_LookingDisabled = 0x2000, PlayerFlags_VanityModeDisabled = 0x4000, PlayerFlags_WeaponDrawingDisabled = 0x8000, PlayerFlags_SpellDrawingDisabled = 0x10000, PlayerFlags_ThirdPerson = 0x20000, PlayerFlags_TeleportingDisabled = 0x40000, PlayerFlags_LevitationDisabled = 0x80000 }; #pragma pack(push) #pragma pack(1) struct FNAM { unsigned char mRank; unsigned char mUnknown1[3]; int mReputation; unsigned char mFlags; // 0x1: unknown, 0x2: expelled unsigned char mUnknown2[3]; ESM::NAME32 mFactionName; }; struct PNAM { struct MarkLocation { float mX, mY, mZ; // worldspace position float mRotZ; // Z angle in radians int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) }; struct Rotation { float mData[3][3]; }; int mPlayerFlags; // controls, camera and draw state unsigned int mLevelProgress; float mSkillProgress[27]; // skill progress, non-uniform scaled unsigned char mSkillIncreases[8]; // number of skill increases for each attribute int mTelekinesisRangeBonus; // in units; seems redundant float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus int mDetectKeyMagnitude; // seems redundant int mDetectEnchantmentMagnitude; // seems redundant int mDetectAnimalMagnitude; // seems redundant MarkLocation mMarkLocation; unsigned char mUnknown3[4]; Rotation mVerticalRotation; unsigned char mSpecIncreases[3]; // number of skill increases for each specialization unsigned char mUnknown4; }; struct ENAM { int mCellX; int mCellY; }; struct AADT // 44 bytes { int animGroupIndex; // See convertANIS() for the mapping. unsigned char mUnknown5[40]; }; #pragma pack(pop) std::vector mFactions; PNAM mPNAM; bool mHasMark; std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name bool mHasENAM; ENAM mENAM; // last exterior cell bool mHasAADT; AADT mAADT; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importproj.cpp000066400000000000000000000004211445372753700226610ustar00rootroot00000000000000#include "importproj.h" #include namespace ESSImport { void ESSImport::PROJ::load(ESM::ESMReader& esm) { while (esm.isNextSub("PNAM")) { PNAM pnam; esm.getHT(pnam); mProjectiles.push_back(pnam); } } } openmw-openmw-0.48.0/apps/essimporter/importproj.h000066400000000000000000000017171445372753700223370ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTPROJ_H #define OPENMW_ESSIMPORT_IMPORTPROJ_H #include #include #include namespace ESM { class ESMReader; } namespace ESSImport { struct PROJ { #pragma pack(push) #pragma pack(1) struct PNAM // 184 bytes { float mAttackStrength; float mSpeed; unsigned char mUnknown[4*2]; float mFlightTime; int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) unsigned char mUnknown2[4]; ESM::Vector3 mVelocity; ESM::Vector3 mPosition; unsigned char mUnknown3[4*9]; ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame") ESM::NAME32 mArrowId; ESM::NAME32 mBowId; bool isMagic() const { return mSplmIndex != 0; } }; #pragma pack(pop) std::vector mProjectiles; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importques.cpp000066400000000000000000000003511445372753700226660ustar00rootroot00000000000000#include "importques.hpp" #include namespace ESSImport { void QUES::load(ESM::ESMReader &esm) { while (esm.isNextSub("DATA")) mInfo.push_back(esm.getHString()); } } openmw-openmw-0.48.0/apps/essimporter/importques.hpp000066400000000000000000000011151445372753700226720ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTQUES_H #define OPENMW_ESSIMPORT_IMPORTQUES_H #include #include namespace ESM { class ESMReader; } namespace ESSImport { /// State for a quest /// Presumably this record only exists when Tribunal is installed, /// since pre-Tribunal there weren't any quest names in the data files. struct QUES { std::string mName; // NAME, should be assigned from outside as usual std::vector mInfo; // list of journal entries for the quest void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importscpt.cpp000066400000000000000000000006101445372753700226600ustar00rootroot00000000000000#include "importscpt.hpp" #include namespace ESSImport { void SCPT::load(ESM::ESMReader &esm) { esm.getHNT(mSCHD, "SCHD"); mSCRI.load(esm); mRefNum = -1; if (esm.isNextSub("RNAM")) { mRunning = true; esm.getHT(mRefNum); } else mRunning = false; } } openmw-openmw-0.48.0/apps/essimporter/importscpt.hpp000066400000000000000000000011211445372753700226630ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTSCPT_H #define OPENMW_ESSIMPORT_IMPORTSCPT_H #include "importscri.hpp" #include namespace ESM { class ESMReader; } namespace ESSImport { struct SCHD { ESM::NAME32 mName; ESM::Script::SCHDstruct mData; }; // A running global script struct SCPT { SCHD mSCHD; // values of local variables SCRI mSCRI; bool mRunning; int mRefNum; // Targeted reference, -1: no reference void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importscri.cpp000066400000000000000000000024511445372753700226540ustar00rootroot00000000000000#include "importscri.hpp" #include namespace ESSImport { void SCRI::load(ESM::ESMReader &esm) { mScript = esm.getHNOString("SCRI"); int numShorts = 0, numLongs = 0, numFloats = 0; if (esm.isNextSub("SLCS")) { esm.getSubHeader(); esm.getT(numShorts); esm.getT(numLongs); esm.getT(numFloats); } if (esm.isNextSub("SLSD")) { esm.getSubHeader(); for (int i=0; i #include namespace ESM { class ESMReader; } namespace ESSImport { /// Local variable assignments for a running script struct SCRI { std::string mScript; std::vector mShorts; std::vector mLongs; std::vector mFloats; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/importsplm.cpp000066400000000000000000000020301445372753700226600ustar00rootroot00000000000000#include "importsplm.h" #include namespace ESSImport { void SPLM::load(ESM::ESMReader& esm) { while (esm.isNextSub("NAME")) { ActiveSpell spell; esm.getHT(spell.mIndex); esm.getHNT(spell.mSPDT, "SPDT"); spell.mTarget = esm.getHNOString("TNAM"); while (esm.isNextSub("NPDT")) { ActiveEffect effect; esm.getHT(effect.mNPDT); // Effect-specific subrecords can follow: // - INAM for disintegration and bound effects // - CNAM for summoning and command effects // - VNAM for vampirism // NOTE: There can be multiple INAMs per effect. // TODO: Needs more research. esm.skipHSubUntil("NAM0"); // sentinel esm.getSubName(); esm.skipHSub(); spell.mActiveEffects.push_back(effect); } unsigned char xnam; // sentinel esm.getHNT(xnam, "XNAM"); mActiveSpells.push_back(spell); } } } openmw-openmw-0.48.0/apps/essimporter/importsplm.h000066400000000000000000000030261445372753700223330ustar00rootroot00000000000000#ifndef OPENMW_ESSIMPORT_IMPORTSPLM_H #define OPENMW_ESSIMPORT_IMPORTSPLM_H #include #include #include namespace ESM { class ESMReader; } namespace ESSImport { struct SPLM { #pragma pack(push) #pragma pack(1) struct SPDT // 160 bytes { int mType; // 1 = spell, 2 = enchantment, 3 = potion ESM::NAME32 mId; // base ID of a spell/enchantment/potion unsigned char mUnknown[4*4]; ESM::NAME32 mCasterId; ESM::NAME32 mSourceId; // empty for spells unsigned char mUnknown2[4*11]; }; struct NPDT // 56 bytes { ESM::NAME32 mAffectedActorId; unsigned char mUnknown[4*2]; int mMagnitude; float mSecondsActive; unsigned char mUnknown2[4*2]; }; struct INAM // 40 bytes { int mUnknown; unsigned char mUnknown2; ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration }; struct CNAM // 36 bytes { int mUnknown; // seems to always be 0 ESM::NAME32 mSummonedOrCommandedActor[32]; }; struct VNAM // 4 bytes { int mUnknown; }; #pragma pack(pop) struct ActiveEffect { NPDT mNPDT; }; struct ActiveSpell { int mIndex; SPDT mSPDT; std::string mTarget; std::vector mActiveEffects; }; std::vector mActiveSpells; void load(ESM::ESMReader& esm); }; } #endif openmw-openmw-0.48.0/apps/essimporter/main.cpp000066400000000000000000000046511445372753700214110ustar00rootroot00000000000000#include #include #include #include #include "importer.hpp" namespace bpo = boost::program_options; namespace bfs = boost::filesystem; int main(int argc, char** argv) { try { bpo::options_description desc("Syntax: openmw-essimporter infile.ess outfile.omwsave\nAllowed options"); bpo::positional_options_description p_desc; desc.add_options() ("help,h", "produce help message") ("mwsave,m", bpo::value(), "morrowind .ess save file") ("output,o", bpo::value(), "output file (.omwsave)") ("compare,c", "compare two .ess files") ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") ; p_desc.add("mwsave", 1).add("output", 1); Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) .options(desc) .positional(p_desc) .run(); bpo::store(parsed, variables); if(variables.count("help") || !variables.count("mwsave") || !variables.count("output")) { std::cout << desc; return 0; } bpo::notify(variables); Files::ConfigurationManager cfgManager(true); cfgManager.readConfiguration(variables, desc); std::string essFile = variables["mwsave"].as(); std::string outputFile = variables["output"].as(); std::string encoding = variables["encoding"].as(); ESSImport::Importer importer(essFile, outputFile, encoding); if (variables.count("compare")) importer.compare(); else { const std::string& ext = ".omwsave"; if (bfs::exists(bfs::path(outputFile)) && (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext)) { throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?"); } importer.run(); } } catch (std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; } return 0; } openmw-openmw-0.48.0/apps/launcher/000077500000000000000000000000001445372753700172005ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/launcher/CMakeLists.txt000066400000000000000000000046161445372753700217470ustar00rootroot00000000000000set(LAUNCHER datafilespage.cpp graphicspage.cpp sdlinit.cpp main.cpp maindialog.cpp playpage.cpp textslotmsgbox.cpp settingspage.cpp advancedpage.cpp utils/cellnameloader.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp utils/openalutil.cpp ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc ) set(LAUNCHER_HEADER datafilespage.hpp graphicspage.hpp sdlinit.hpp maindialog.hpp playpage.hpp textslotmsgbox.hpp settingspage.hpp advancedpage.hpp utils/cellnameloader.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp utils/openalutil.hpp ) # Headers that must be pre-processed set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui ${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui ${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) set(QT_USE_QTGUI 1) # Set some platform specific settings if(WIN32) set(GUI_TYPE WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) include_directories(${LIBUNSHIELD_INCLUDE_DIR}) endif(NOT WIN32) # Main executable openmw_add_executable(openmw-launcher ${GUI_TYPE} ${LAUNCHER} ${LAUNCHER_HEADER} ${RCC_SRCS} ${MOC_SRCS} ${UI_HDRS} ) if (WIN32) INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".") endif (WIN32) target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} ${OPENAL_LIBRARY} components_qt ) target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-launcher gcov) endif() if(USE_QT) set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) endif(USE_QT) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-launcher PRIVATE ) endif() openmw-openmw-0.48.0/apps/launcher/advancedpage.cpp000066400000000000000000000572241445372753700223200ustar00rootroot00000000000000#include "advancedpage.hpp" #include #include #include #include #include #include #include #include #include #include #include "utils/openalutil.hpp" Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent) : QWidget(parent) , mGameSettings(gameSettings) { setObjectName ("AdvancedPage"); setupUi(this); for(const std::string& name : Launcher::enumerateOpenALDevices()) { audioDeviceSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); } for(const std::string& name : Launcher::enumerateOpenALDevicesHrtf()) { hrtfProfileSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); } loadSettings(); mCellNameCompleter.setModel(&mCellNameCompleterModel); startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); } void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) { // Update the list of suggestions for the "Start default character at" field mCellNameCompleterModel.setStringList(cellNames); mCellNameCompleter.setCompletionMode(QCompleter::PopupCompletion); mCellNameCompleter.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); } void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) { startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); startDefaultCharacterAtField->setEnabled(state == Qt::Checked); } void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() { QString scriptFile = QFileDialog::getOpenFileName( this, QObject::tr("Select script file"), QDir::currentPath(), QString(tr("Text file (*.txt)"))); if (scriptFile.isEmpty()) return; QFileInfo info(scriptFile); if (!info.exists() || !info.isReadable()) return; const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); runScriptAfterStartupField->setText(path); } namespace { constexpr double CellSizeInUnits = 8192; double convertToCells(double unitRadius) { return unitRadius / CellSizeInUnits; } int convertToUnits(double CellGridRadius) { return static_cast(CellSizeInUnits * CellGridRadius); } } bool Launcher::AdvancedPage::loadSettings() { // Game mechanics { loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); loadSettingBool(classicCalmSpellsCheckBox, "classic calm spells behavior", "Game"); loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); int unarmedFactorsStrengthIndex = Settings::Manager::getInt("strength influences hand to hand", "Game"); if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); int numPhysicsThreads = Settings::Manager::getInt("async num threads", "Physics"); if (numPhysicsThreads >= 0) physicsThreadsSpinBox->setValue(numPhysicsThreads); loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); loadSettingBool(unarmedCreatureAttacksDamageArmorCheckBox, "unarmed creature attacks damage armor", "Game"); const int actorCollisionShapeType = Settings::Manager::getInt("actor collision shape type", "Game"); if (0 <= actorCollisionShapeType && actorCollisionShapeType < actorCollisonShapeTypeComboBox->count()) actorCollisonShapeTypeComboBox->setCurrentIndex(actorCollisionShapeType); } // Visuals { loadSettingBool(autoUseObjectNormalMapsCheckBox, "auto use object normal maps", "Shaders"); loadSettingBool(autoUseObjectSpecularMapsCheckBox, "auto use object specular maps", "Shaders"); loadSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); if (Settings::Manager::getInt("antialiasing", "Video") == 0) { antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); } loadSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); if (animSourcesCheckBox->checkState() != Qt::Unchecked) { loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); } loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); if (distantTerrain && objectPaging) { distantLandCheckBox->setCheckState(Qt::Checked); } loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))); objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool))); loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("auto exposure speed", "Post Processing")); connect(skyBlendingCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSkyBlendingToggled(bool))); loadSettingBool(radialFogCheckBox, "radial fog", "Fog"); loadSettingBool(exponentialFogCheckBox, "exponential fog", "Fog"); loadSettingBool(skyBlendingCheckBox, "sky blending", "Fog"); skyBlendingStartComboBox->setValue(Settings::Manager::getDouble("sky blending start", "Fog")); } // Audio { std::string selectedAudioDevice = Settings::Manager::getString("device", "Sound"); if (selectedAudioDevice.empty() == false) { int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice)); if (audioDeviceIndex != -1) { audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); } } int hrtfEnabledIndex = Settings::Manager::getInt("hrtf enable", "Sound"); if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) { enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); } std::string selectedHRTFProfile = Settings::Manager::getString("hrtf", "Sound"); if (selectedHRTFProfile.empty() == false) { int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile)); if (hrtfProfileIndex != -1) { hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); } } } // Interface Changes { loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); int showOwnedIndex = Settings::Manager::getInt("show owned", "Game"); // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. if (showOwnedIndex >= 0 && showOwnedIndex <= 3) showOwnedComboBox->setCurrentIndex(showOwnedIndex); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); loadSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI")); fontSizeSpinBox->setValue(Settings::Manager::getInt("font size", "GUI")); } // Bug fixes { loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); } // Miscellaneous { // Saves loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); loadSettingInt(maximumQuicksavesComboBox,"max quicksaves", "Saves"); // Other Settings QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper(); if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) screenshotFormatComboBox->addItem(screenshotFormatString); screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); loadSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General"); } // Testing { loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); } startDefaultCharacterAtLabel->setEnabled(skipMenu); startDefaultCharacterAtField->setEnabled(skipMenu); startDefaultCharacterAtField->setText(mGameSettings.value("start")); runScriptAfterStartupField->setText(mGameSettings.value("script-run")); } return true; } void Launcher::AdvancedPage::saveSettings() { // Game mechanics { saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); saveSettingBool(classicCalmSpellsCheckBox, "classic calm spells behavior", "Game"); saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); saveSettingInt(unarmedFactorsStrengthComboBox, "strength influences hand to hand", "Game"); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics"); saveSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); saveSettingBool(unarmedCreatureAttacksDamageArmorCheckBox, "unarmed creature attacks damage armor", "Game"); saveSettingInt(actorCollisonShapeTypeComboBox, "actor collision shape type", "Game"); } // Visuals { saveSettingBool(autoUseObjectNormalMapsCheckBox, "auto use object normal maps", "Shaders"); saveSettingBool(autoUseObjectSpecularMapsCheckBox, "auto use object specular maps", "Shaders"); saveSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); saveSettingBool(radialFogCheckBox, "radial fog", "Fog"); saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); saveSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); const bool wantDistantLand = distantLandCheckBox->checkState(); if (wantDistantLand != (distantTerrain && objectPaging)) { Settings::Manager::setBool("distant terrain", "Terrain", wantDistantLand); Settings::Manager::setBool("object paging", "Terrain", wantDistantLand); } saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); int viewingDistance = convertToUnits(viewingDistanceComboBox->value()); if (viewingDistance != Settings::Manager::getInt("viewing distance", "Camera")) { Settings::Manager::setInt("viewing distance", "Camera", viewingDistance); } double objectPagingMinSize = objectPagingMinSizeComboBox->value(); if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); double hdrExposureTime = postprocessHDRTimeComboBox->value(); if (hdrExposureTime != Settings::Manager::getDouble("auto exposure speed", "Post Processing")) Settings::Manager::setDouble("auto exposure speed", "Post Processing", hdrExposureTime); saveSettingBool(radialFogCheckBox, "radial fog", "Fog"); saveSettingBool(exponentialFogCheckBox, "exponential fog", "Fog"); saveSettingBool(skyBlendingCheckBox, "sky blending", "Fog"); Settings::Manager::setDouble("sky blending start", "Fog", skyBlendingStartComboBox->value()); } // Audio { int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex(); std::string prevAudioDevice = Settings::Manager::getString("device", "Sound"); if (audioDeviceIndex != 0) { const std::string& newAudioDevice = audioDeviceSelectorComboBox->currentText().toUtf8().constData(); if (newAudioDevice != prevAudioDevice) Settings::Manager::setString("device", "Sound", newAudioDevice); } else if (!prevAudioDevice.empty()) { Settings::Manager::setString("device", "Sound", {}); } int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1; if (hrtfEnabledIndex != Settings::Manager::getInt("hrtf enable", "Sound")) { Settings::Manager::setInt("hrtf enable", "Sound", hrtfEnabledIndex); } int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex(); std::string prevHRTFProfile = Settings::Manager::getString("hrtf", "Sound"); if (selectedHRTFProfileIndex != 0) { const std::string& newHRTFProfile = hrtfProfileSelectorComboBox->currentText().toUtf8().constData(); if (newHRTFProfile != prevHRTFProfile) Settings::Manager::setString("hrtf", "Sound", newHRTFProfile); } else if (!prevHRTFProfile.empty()) { Settings::Manager::setString("hrtf", "Sound", {}); } } // Interface Changes { saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); saveSettingInt(showOwnedComboBox,"show owned", "Game"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); float uiScalingFactor = scalingSpinBox->value(); if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor); int fontSize = fontSizeSpinBox->value(); if (fontSize != Settings::Manager::getInt("font size", "GUI")) Settings::Manager::setInt("font size", "GUI", fontSize); } // Bug fixes { saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); } // Miscellaneous { // Saves Settings saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); saveSettingInt(maximumQuicksavesComboBox, "max quicksaves", "Saves"); // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General")) Settings::Manager::setString("screenshot format", "General", screenshotFormatString); saveSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General"); } // Testing { saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; if (skipMenu != mGameSettings.value("skip-menu").toInt()) mGameSettings.setValue("skip-menu", QString::number(skipMenu)); QString startCell = startDefaultCharacterAtField->text(); if (startCell != mGameSettings.value("start")) { mGameSettings.setValue("start", startCell); } QString scriptRun = runScriptAfterStartupField->text(); if (scriptRun != mGameSettings.value("script-run")) mGameSettings.setValue("script-run", scriptRun); } } void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { if (Settings::Manager::getBool(setting, group)) checkbox->setCheckState(Qt::Checked); } void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { bool cValue = checkbox->checkState(); if (cValue != Settings::Manager::getBool(setting, group)) Settings::Manager::setBool(setting, group, cValue); } void Launcher::AdvancedPage::loadSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group) { int currentIndex = Settings::Manager::getInt(setting, group); comboBox->setCurrentIndex(currentIndex); } void Launcher::AdvancedPage::saveSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group) { int currentIndex = comboBox->currentIndex(); if (currentIndex != Settings::Manager::getInt(setting, group)) Settings::Manager::setInt(setting, group, currentIndex); } void Launcher::AdvancedPage::loadSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group) { int value = Settings::Manager::getInt(setting, group); spinBox->setValue(value); } void Launcher::AdvancedPage::saveSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group) { int value = spinBox->value(); if (value != Settings::Manager::getInt(setting, group)) Settings::Manager::setInt(setting, group, value); } void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) { loadCellsForAutocomplete(cellNames); } void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked) { weaponSheathingCheckBox->setEnabled(checked); shieldSheathingCheckBox->setEnabled(checked); if (!checked) { weaponSheathingCheckBox->setCheckState(Qt::Unchecked); shieldSheathingCheckBox->setCheckState(Qt::Unchecked); } } void Launcher::AdvancedPage::slotPostProcessToggled(bool checked) { postprocessTransparentPostpassCheckBox->setEnabled(checked); postprocessHDRTimeComboBox->setEnabled(checked); postprocessHDRTimeLabel->setEnabled(checked); } void Launcher::AdvancedPage::slotSkyBlendingToggled(bool checked) { skyBlendingStartComboBox->setEnabled(checked); skyBlendingStartLabel->setEnabled(checked); } openmw-openmw-0.48.0/apps/launcher/advancedpage.hpp000066400000000000000000000036321445372753700223170ustar00rootroot00000000000000#ifndef ADVANCEDPAGE_H #define ADVANCEDPAGE_H #include #include #include "ui_advancedpage.h" #include namespace Config { class GameSettings; } namespace Launcher { class AdvancedPage : public QWidget, private Ui::AdvancedPage { Q_OBJECT public: explicit AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent = nullptr); bool loadSettings(); void saveSettings(); public slots: void slotLoadedCellsChanged(QStringList cellNames); private slots: void on_skipMenuCheckBox_stateChanged(int state); void on_runScriptAfterStartupBrowseButton_clicked(); void slotAnimSourcesToggled(bool checked); void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); private: Config::GameSettings &mGameSettings; QCompleter mCellNameCompleter; QStringListModel mCellNameCompleterModel; /** * Load the cells associated with the given content files for use in autocomplete * @param filePaths the file paths of the content files to be examined */ void loadCellsForAutocomplete(QStringList filePaths); static void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); static void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); static void loadSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group); static void saveSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group); static void loadSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group); static void saveSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group); }; } #endif openmw-openmw-0.48.0/apps/launcher/datafilespage.cpp000066400000000000000000000747421445372753700225130ustar00rootroot00000000000000#include "datafilespage.hpp" #include "maindialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" #include "ui_directorypicker.h" const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; namespace { void contentSubdirs(const QString& path, QStringList& dirs) { QStringList fileFilter {"*.esm", "*.esp", "*.omwaddon", "*.bsa"}; QStringList dirFilter {"bookart", "icons", "meshes", "music", "sound", "textures"}; QDir currentDir(path); if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() || !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty()) dirs.push_back(currentDir.canonicalPath()); for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) contentSubdirs(subdir.canonicalFilePath(), dirs); } } namespace Launcher { namespace { struct HandleNavMeshToolMessage { int mCellsCount; int mExpectedMaxProgress; int mMaxProgress; int mProgress; HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedCells&& message) const { return HandleNavMeshToolMessage { static_cast(message.mCount), mExpectedMaxProgress, static_cast(message.mCount) * 100, mProgress }; } HandleNavMeshToolMessage operator()(NavMeshTool::ProcessedCells&& message) const { return HandleNavMeshToolMessage { mCellsCount, mExpectedMaxProgress, mMaxProgress, std::max(mProgress, static_cast(message.mCount)) }; } HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedTiles&& message) const { const int expectedMaxProgress = mCellsCount + static_cast(message.mCount); return HandleNavMeshToolMessage { mCellsCount, expectedMaxProgress, std::max(mMaxProgress, expectedMaxProgress), mProgress }; } HandleNavMeshToolMessage operator()(NavMeshTool::GeneratedTiles&& message) const { int progress = mCellsCount + static_cast(message.mCount); if (mExpectedMaxProgress < mMaxProgress) progress += static_cast(std::round( (mMaxProgress - mExpectedMaxProgress) * (static_cast(progress) / static_cast(mExpectedMaxProgress)) )); return HandleNavMeshToolMessage { mCellsCount, mExpectedMaxProgress, mMaxProgress, std::max(mProgress, progress) }; } }; int getMaxNavMeshDbFileSizeMiB() { return static_cast(Settings::Manager::getInt64("max navmeshdb file size", "Navigator") / (1024 * 1024)); } } } Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent) : QWidget(parent) , mMainDialog(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) , mNavMeshToolInvoker(new Process::ProcessInvoker(this)) { ui.setupUi (this); setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/true); const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this); connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateNewProfileOkButton(QString))); connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateCloneProfileOkButton(QString))); connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [this]() { this->addSubdirectories(true); }); connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); }); connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); buildView(); loadSettings(); // Connect signal and slot after the settings have been loaded. We only care about the user changing // the addons and don't want to get signals of the system doing it during startup. connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)), this, SLOT(slotAddonDataChanged())); // Call manually to indicate all changes to addon data during startup. slotAddonDataChanged(); } void Launcher::DataFilesPage::buildView() { QToolButton * refreshButton = mSelector->refreshButton(); //tool buttons ui.newProfileButton->setToolTip ("Create a new Content List"); ui.cloneProfileButton->setToolTip ("Clone the current Content List"); ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); //combo box ui.profilesComboBox->addItem(mDefaultContentListName); ui.profilesComboBox->setPlaceholderText (QString("Select a Content List...")); ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String(mDefaultContentListName))); // Add the actions to the toolbuttons ui.newProfileButton->setDefaultAction (ui.newProfileAction); ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction); ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); refreshButton->setDefaultAction(ui.refreshDataFilesAction); //establish connections connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), this, SLOT (slotProfileChanged(int))); connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), this, SLOT (slotProfileRenamed(QString, QString))); connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), this, SLOT (slotProfileChangedByUser(QString, QString))); connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool())); connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool())); connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(readNavMeshToolStdout())); connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(readNavMeshToolStderr())); connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus))); } bool Launcher::DataFilesPage::loadSettings() { ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB()); QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); qDebug() << "The current profile is: " << currentProfile; for (const QString &item : profiles) addProfile (item, false); // Hack: also add the current profile if (!currentProfile.isEmpty()) addProfile(currentProfile, true); return true; } void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) { mSelector->clearFiles(); ui.archiveListWidget->clear(); ui.directoryListWidget->clear(); QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); if (directories.isEmpty()) directories = mGameSettings.getDataDirs(); mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) directories.insert(0, mDataLocal); const auto globalDataDir = QString(mGameSettings.getGlobalDataDir().c_str()); if (!globalDataDir.isEmpty()) directories.insert(0, globalDataDir); std::unordered_set visitedDirectories; for (const QString& currentDir : directories) { // normalize user supplied directories: resolve symlink, convert to native separator, make absolute const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath(); if (!visitedDirectories.insert(canonicalDirPath).second) continue; // add new achives files presents in current directory addArchivesFromDir(currentDir); QString tooltip; // add content files presents in current directory mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); // add current directory to list ui.directoryListWidget->addItem(currentDir); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); // Display new content with green background if (mNewDataDirs.contains(canonicalDirPath)) { tooltip += "Will be added to the current profile\n"; item->setBackground(Qt::green); item->setForeground(Qt::black); } // deactivate data-local and global data directory: they are always included if (currentDir == mDataLocal || currentDir == globalDataDir) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled)); } // Add a "data file" icon if the directory contains a content file if (mSelector->containsDataFiles(currentDir)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); tooltip += "Contains content file(s)"; } else { // Pad to correct vertical alignment QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size pixmap.fill(ui.directoryListWidget->palette().base().color()); auto emptyIcon = QIcon(pixmap); item->setIcon(emptyIcon); } item->setToolTip(tooltip); } mSelector->sortFiles(); QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); if (selectedArchives.isEmpty()) selectedArchives = mGameSettings.getArchiveList(); // sort and tick BSA according to profile int row = 0; for (const auto& archive : selectedArchives) { const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); if (match.isEmpty()) continue; const auto name = match[0]->text(); const auto oldrow = ui.archiveListWidget->row(match[0]); ui.archiveListWidget->takeItem(oldrow); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); row++; } PathIterator pathIterator(directories); mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator)); } QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, PathIterator& pathIterator) { QStringList files = mLauncherSettings.getContentListFiles(profileName); QStringList filepaths; for (const QString& file : files) { QString filepath = pathIterator.findFirstPath(file); if (!filepath.isEmpty()) filepaths << filepath; } return filepaths; } void Launcher::DataFilesPage::saveSettings(const QString &profile) { if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) Settings::Manager::setInt64("max navmeshdb file size", "Navigator", static_cast(value) * 1024 * 1024); QString profileName = profile; if (profileName.isEmpty()) profileName = ui.profilesComboBox->currentText(); //retrieve the data paths auto dirList = selectedDirectoriesPaths(); //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); //set the value of the current profile (not necessarily the profile being saved!) mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); QStringList fileNames; for (const ContentSelectorModel::EsmFile *item : items) { fileNames.append(item->fileName()); } mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); } QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const { QStringList dirList; for (int i = 0; i < ui.directoryListWidget->count(); ++i) { if (ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) dirList.append(ui.directoryListWidget->item(i)->text()); } return dirList; } QStringList Launcher::DataFilesPage::selectedArchivePaths(bool all) const { QStringList archiveList; for (int i = 0; i < ui.archiveListWidget->count(); ++i) { const auto* item = ui.archiveListWidget->item(i); const auto archive = ui.archiveListWidget->item(i)->text(); if (all ||item->checkState() == Qt::Checked) archiveList.append(item->text()); } return archiveList; } QStringList Launcher::DataFilesPage::selectedFilePaths() const { //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); QStringList filePaths; for (const ContentSelectorModel::EsmFile *item : items) { QFile file(item->filePath()); if(file.exists()) filePaths.append(item->filePath()); } return filePaths; } void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.removeContentList(profile); } QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const { return ui.profilesComboBox->model(); } int Launcher::DataFilesPage::profilesIndex() const { return ui.profilesComboBox->currentIndex(); } void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { if (index >= -1 && index < ui.profilesComboBox->count()) { QString previous = mPreviousProfile; QString current = ui.profilesComboBox->itemText(index); mPreviousProfile = current; setProfile (previous, current, savePrevious); } } void Launcher::DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) { //abort if no change (poss. duplicate signal) if (previous == current) return; if (!previous.isEmpty() && savePrevious) saveSettings (previous); ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); mNewDataDirs.clear(); mKnownArchives.clear(); populateFileViews(current); // save list of "old" bsa to be able to display "new" bsa in a different colour for (int i = 0; i < ui.archiveListWidget->count(); ++i) { auto* item = ui.archiveListWidget->item(i); mKnownArchives.push_back(item->text()); } checkForDefaultProfile(); } void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) { removeProfile (item); } void Launcher::DataFilesPage:: refreshDataFilesView () { QString currentProfile = ui.profilesComboBox->currentText(); saveSettings(currentProfile); populateFileViews(currentProfile); } void Launcher::DataFilesPage::slotRefreshButtonClicked () { refreshDataFilesView(); } void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { setProfile(previous, current, true); emit signalProfileChanged (ui.profilesComboBox->findText(current)); } void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) { if (previous.isEmpty()) return; // Save the new profile name saveSettings(); // Remove the old one removeProfile (previous); loadSettings(); } void Launcher::DataFilesPage::slotProfileChanged(int index) { // in case the event was triggered externally if (ui.profilesComboBox->currentIndex() != index) ui.profilesComboBox->setCurrentIndex(index); setProfile (index, true); } void Launcher::DataFilesPage::on_newProfileAction_triggered() { if (mNewProfileDialog->exec() != QDialog::Accepted) return; QString profile = mNewProfileDialog->lineEdit()->text(); if (profile.isEmpty()) return; saveSettings(); mLauncherSettings.setCurrentContentListName(profile); addProfile(profile, true); } void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) { if (profile.isEmpty()) return; if (ui.profilesComboBox->findText (profile) == -1) ui.profilesComboBox->addItem (profile); if (setAsCurrent) setProfile (ui.profilesComboBox->findText (profile), false); } void Launcher::DataFilesPage::on_cloneProfileAction_triggered() { if (mCloneProfileDialog->exec() != QDialog::Accepted) return; QString profile = mCloneProfileDialog->lineEdit()->text(); if (profile.isEmpty()) return; mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); addProfile(profile, true); } void Launcher::DataFilesPage::on_deleteProfileAction_triggered() { QString profile = ui.profilesComboBox->currentText(); if (profile.isEmpty()) return; if (!showDeleteMessageBox (profile)) return; // this should work since the Default profile can't be deleted and is always index 0 int next = ui.profilesComboBox->currentIndex()-1; // changing the profile forces a reload of plugin file views. ui.profilesComboBox->setCurrentIndex(next); removeProfile(profile); ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); checkForDefaultProfile(); } void Launcher::DataFilesPage::updateNewProfileOkButton(const QString &text) { // We do this here because we need the profiles combobox text mNewProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text) { // We do this here because we need the profiles combobox text mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } QString Launcher::DataFilesPage::selectDirectory() { QFileDialog fileDialog(this); fileDialog.setFileMode(QFileDialog::Directory); fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); if (fileDialog.exec() == QDialog::Rejected) return {}; return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); } void Launcher::DataFilesPage::addSubdirectories(bool append) { int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); if (selectedRow == -1) return; const auto rootDir = selectDirectory(); if (rootDir.isEmpty()) return; QStringList subdirs; contentSubdirs(rootDir, subdirs); if (subdirs.empty()) { // we didn't find anything that looks like a content directory, add directory selected by user if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) { ui.directoryListWidget->addItem(rootDir); mNewDataDirs.push_back(rootDir); refreshDataFilesView(); } return; } QDialog dialog; Ui::SelectSubdirs select; select.setupUi(&dialog); for (const auto& dir : subdirs) { if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty()) continue; const auto lastRow = select.dirListWidget->count(); select.dirListWidget->addItem(dir); select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked); } dialog.show(); if (dialog.exec() == QDialog::Rejected) return; for (int i = 0; i < select.dirListWidget->count(); ++i) { const auto* dir = select.dirListWidget->item(i); if (dir->checkState() == Qt::Checked) { ui.directoryListWidget->insertItem(selectedRow++, dir->text()); mNewDataDirs.push_back(dir->text()); } } refreshDataFilesView(); } void Launcher::DataFilesPage::sortDirectories() { // Ensure disabled entries (aka default directories) are always at the top. for (auto i = 1; i < ui.directoryListWidget->count(); ++i) { if (!(ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) && (ui.directoryListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) { const auto item = ui.directoryListWidget->takeItem(i); ui.directoryListWidget->insertItem(i - 1, item); ui.directoryListWidget->setCurrentRow(i); } } } void Launcher::DataFilesPage::moveDirectory(int step) { int selectedRow = ui.directoryListWidget->currentRow(); int newRow = selectedRow + step; if (selectedRow == -1 || newRow < 0 || newRow > ui.directoryListWidget->count() - 1) return; if (!(ui.directoryListWidget->item(newRow)->flags() & Qt::ItemIsEnabled)) return; const auto item = ui.directoryListWidget->takeItem(selectedRow); ui.directoryListWidget->insertItem(newRow, item); ui.directoryListWidget->setCurrentRow(newRow); } void Launcher::DataFilesPage::removeDirectory() { for (const auto& path : ui.directoryListWidget->selectedItems()) ui.directoryListWidget->takeItem(ui.directoryListWidget->row(path)); refreshDataFilesView(); } void Launcher::DataFilesPage::moveArchive(int step) { int selectedRow = ui.archiveListWidget->currentRow(); int newRow = selectedRow + step; if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) return; const auto* item = ui.archiveListWidget->takeItem(selectedRow); addArchive(item->text(), item->checkState(), newRow); ui.archiveListWidget->setCurrentRow(newRow); } void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) { if (row == -1) row = ui.archiveListWidget->count(); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(selected); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { ui.archiveListWidget->item(row)->setBackground(Qt::green); ui.archiveListWidget->item(row)->setForeground(Qt::black); } } void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) { QDir dir(path, "*.bsa"); for (const auto& fileinfo : dir.entryInfoList()) { const auto absPath = fileinfo.absoluteFilePath(); if (Bsa::CompressedBSAFile::detectVersion(absPath.toStdString()) == Bsa::BSAVER_UNKNOWN) continue; const auto fileName = fileinfo.fileName(); const auto currentList = selectedArchivePaths(true); if (!currentList.contains(fileName, Qt::CaseInsensitive)) addArchive(fileName, Qt::Unchecked); } } void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName); ui.deleteProfileAction->setEnabled (success); ui.profilesComboBox->setEditEnabled (success); } bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) { QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Delete Content List")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("Are you sure you want to delete %1?").arg(text)); QAbstractButton *deleteButton = msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); msgBox.exec(); return (msgBox.clickedButton() == deleteButton); } void Launcher::DataFilesPage::slotAddonDataChanged() { QStringList selectedFiles = selectedFilePaths(); if (previousSelectedFiles != selectedFiles) { previousSelectedFiles = selectedFiles; // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a // barely perceptible UI lag. Splitting into its own thread to alleviate that. std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles); loadCellsThread.detach(); } } // Mutex lock to run reloadCells synchronously. static std::mutex reloadCellsMutex; void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) { // Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time // Based on https://stackoverflow.com/a/5429695/531762 std::unique_lock lock(reloadCellsMutex); // The following code will run only if there is not another thread currently running it CellNameLoader cellNameLoader; #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QSet set = cellNameLoader.getCellNames(selectedFiles); QStringList cellNamesList(set.begin(), set.end()); #else QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); #endif std::sort(cellNamesList.begin(), cellNamesList.end()); emit signalLoadedCellsChanged(cellNamesList); } void Launcher::DataFilesPage::startNavMeshTool() { mMainDialog->writeSettings(); ui.navMeshLogPlainTextEdit->clear(); ui.navMeshProgressBar->setValue(0); ui.navMeshProgressBar->setMaximum(1); ui.navMeshProgressBar->resetFormat(); mNavMeshToolProgress = NavMeshToolProgress {}; QStringList arguments({"--write-binary-log"}); if (ui.navMeshRemoveUnusedTilesCheckBox->checkState() == Qt::Checked) arguments.append("--remove-unused-tiles"); if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), arguments)) return; ui.cancelNavMeshButton->setEnabled(true); ui.navMeshProgressBar->setEnabled(true); } void Launcher::DataFilesPage::killNavMeshTool() { mNavMeshToolInvoker->killProcess(); } void Launcher::DataFilesPage::readNavMeshToolStderr() { updateNavMeshProgress(4096); } void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize) { if (!mNavMeshToolProgress.mEnabled) return; QProcess& process = *mNavMeshToolInvoker->getProcess(); mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError()); if (mNavMeshToolProgress.mMessagesData.size() < minDataSize) return; const std::byte* const begin = reinterpret_cast(mNavMeshToolProgress.mMessagesData.constData()); const std::byte* const end = begin + mNavMeshToolProgress.mMessagesData.size(); const std::byte* position = begin; HandleNavMeshToolMessage handle { mNavMeshToolProgress.mCellsCount, mNavMeshToolProgress.mExpectedMaxProgress, ui.navMeshProgressBar->maximum(), ui.navMeshProgressBar->value(), }; try { while (true) { NavMeshTool::Message message; const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message); if (nextPosition == position) break; position = nextPosition; handle = std::visit(handle, NavMeshTool::decode(message)); } } catch (const std::exception& e) { Log(Debug::Error) << "Failed to deserialize navmeshtool message: " << e.what(); mNavMeshToolProgress.mEnabled = false; ui.navMeshProgressBar->setFormat("Failed to update progress: " + QString(e.what())); } if (position != begin) mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin); mNavMeshToolProgress.mCellsCount = handle.mCellsCount; mNavMeshToolProgress.mExpectedMaxProgress = handle.mExpectedMaxProgress; ui.navMeshProgressBar->setMaximum(handle.mMaxProgress); ui.navMeshProgressBar->setValue(handle.mProgress); } void Launcher::DataFilesPage::readNavMeshToolStdout() { QProcess& process = *mNavMeshToolInvoker->getProcess(); QByteArray& logData = mNavMeshToolProgress.mLogData; logData.append(process.readAllStandardOutput()); const int lineEnd = logData.lastIndexOf('\n'); if (lineEnd == -1) return; const int size = logData.size() >= lineEnd && logData[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd; ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(logData.data(), size)); logData = logData.mid(lineEnd + 1); } void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) { updateNavMeshProgress(0); ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput())); if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit) { ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum()); ui.navMeshProgressBar->resetFormat(); } ui.cancelNavMeshButton->setEnabled(false); ui.navMeshProgressBar->setEnabled(false); } openmw-openmw-0.48.0/apps/launcher/datafilespage.hpp000066400000000000000000000133251445372753700225060ustar00rootroot00000000000000#ifndef DATAFILESPAGE_H #define DATAFILESPAGE_H #include "ui_datafilespage.h" #include #include #include #include class QSortFilterProxyModel; class QAbstractItemModel; class QMenu; namespace Files { struct ConfigurationManager; } namespace ContentSelectorView { class ContentSelector; } namespace Config { class GameSettings; class LauncherSettings; } namespace Launcher { class MainDialog; class TextInputDialog; class ProfilesComboBox; class DataFilesPage : public QWidget { Q_OBJECT ContentSelectorView::ContentSelector *mSelector; Ui::DataFilesPage ui; public: explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); QAbstractItemModel* profilesModel() const; int profilesIndex() const; //void writeConfig(QString profile = QString()); void saveSettings(const QString &profile = ""); bool loadSettings(); signals: void signalProfileChanged (int index); void signalLoadedCellsChanged(QStringList selectedFiles); public slots: void slotProfileChanged (int index); private slots: void slotProfileChangedByUser(const QString &previous, const QString ¤t); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); void slotAddonDataChanged (); void slotRefreshButtonClicked (); void updateNewProfileOkButton(const QString &text); void updateCloneProfileOkButton(const QString &text); void addSubdirectories(bool append); void sortDirectories(); void removeDirectory(); void moveArchive(int step); void moveDirectory(int step); void on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); void startNavMeshTool(); void killNavMeshTool(); void readNavMeshToolStdout(); void readNavMeshToolStderr(); void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus); public: /// Content List that is always present const static char *mDefaultContentListName; private: struct NavMeshToolProgress { bool mEnabled = true; QByteArray mLogData; QByteArray mMessagesData; std::map mWorldspaces; int mCellsCount = 0; int mExpectedMaxProgress = 0; }; MainDialog *mMainDialog; TextInputDialog *mNewProfileDialog; TextInputDialog *mCloneProfileDialog; Files::ConfigurationManager &mCfgMgr; Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; QString mPreviousProfile; QStringList previousSelectedFiles; QString mDataLocal; QStringList mKnownArchives; QStringList mNewDataDirs; Process::ProcessInvoker* mNavMeshToolInvoker; NavMeshToolProgress mNavMeshToolProgress; void addArchive(const QString& name, Qt::CheckState selected, int row = -1); void addArchivesFromDir(const QString& dir); void buildView(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); void removeProfile (const QString &profile); bool showDeleteMessageBox (const QString &text); void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); void reloadCells(QStringList selectedFiles); void refreshDataFilesView (); void updateNavMeshProgress(int minDataSize); QString selectDirectory(); /** * Returns the file paths of all selected content files * @return the file paths of all selected content files */ QStringList selectedFilePaths() const; QStringList selectedArchivePaths(bool all=false) const; QStringList selectedDirectoriesPaths() const; class PathIterator { QStringList::ConstIterator mCitEnd; QStringList::ConstIterator mCitCurrent; QStringList::ConstIterator mCitBegin; QString mFile; QString mFilePath; public: PathIterator (const QStringList &list) { mCitBegin = list.constBegin(); mCitCurrent = mCitBegin; mCitEnd = list.constEnd(); } QString findFirstPath (const QString &file) { mCitCurrent = mCitBegin; mFile = file; return path(); } QString findNextPath () { return path(); } private: QString path () { bool success = false; QDir dir; QFileInfo file; while (!success) { if (mCitCurrent == mCitEnd) break; dir.setPath (*(mCitCurrent++)); file.setFile (dir.absoluteFilePath (mFile)); success = file.exists(); } if (success) return file.absoluteFilePath(); return ""; } }; QStringList filesInProfile(const QString& profileName, PathIterator& pathIterator); }; } #endif openmw-openmw-0.48.0/apps/launcher/graphicspage.cpp000066400000000000000000000367721445372753700223600ustar00rootroot00000000000000#include "graphicspage.hpp" #include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED #include #include #include QString getAspect(int x, int y) { int gcd = std::gcd (x, y); if (gcd == 0) return QString(); int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 if (xaspect == 8 && yaspect == 5) return QString("16:10"); return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } Launcher::GraphicsPage::GraphicsPage(QWidget *parent) : QWidget(parent) { setObjectName ("GraphicsPage"); setupUi(this); // Set the maximum res we can set in windowed mode QRect res = getMaximumResolution(); customWidthSpinBox->setMaximum(res.width()); customHeightSpinBox->setMaximum(res.height()); connect(windowModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFullScreenChanged(int))); connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool))); connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool))); connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool))); } bool Launcher::GraphicsPage::setupSDL() { bool sdlConnectSuccessful = initSDL(); if (!sdlConnectSuccessful) { return false; } int displays = SDL_GetNumVideoDisplays(); if (displays < 0) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error receiving number of screens")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
SDL_GetNumVideoDisplays failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return false; } screenComboBox->clear(); mResolutionsPerScreen.clear(); for (int i = 0; i < displays; i++) { mResolutionsPerScreen.append(getAvailableResolutions(i)); screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); } screenChanged(0); // Disconnect from SDL processes quitSDL(); return true; } bool Launcher::GraphicsPage::loadSettings() { if (!setupSDL()) return false; // Visuals if (Settings::Manager::getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); if (windowMode > static_cast(Settings::WindowMode::Windowed)) windowMode = 0; windowModeComboBox->setCurrentIndex(windowMode); if (Settings::Manager::getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); // aaValue is the actual value (0, 1, 2, 4, 8, 16) int aaValue = Settings::Manager::getInt("antialiasing", "Video"); // aaIndex is the index into the allowed values in the pull down. int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); int width = Settings::Manager::getInt("resolution x", "Video"); int height = Settings::Manager::getInt("resolution y", "Video"); QString resolution = QString::number(width) + QString(" x ") + QString::number(height); screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video")); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); if (resIndex != -1) { standardRadioButton->toggle(); resolutionComboBox->setCurrentIndex(resIndex); } else { customRadioButton->toggle(); customWidthSpinBox->setValue(width); customHeightSpinBox->setValue(height); } float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); if (fpsLimit != 0) { framerateLimitCheckBox->setCheckState(Qt::Checked); framerateLimitSpinBox->setValue(fpsLimit); } // Lighting int lightingMethod = 1; if (Settings::Manager::getString("lighting method", "Shaders") == "legacy") lightingMethod = 0; else if (Settings::Manager::getString("lighting method", "Shaders") == "shaders") lightingMethod = 2; lightingMethodComboBox->setCurrentIndex(lightingMethod); // Shadows if (Settings::Manager::getBool("actor shadows", "Shadows")) actorShadowsCheckBox->setCheckState(Qt::Checked); if (Settings::Manager::getBool("player shadows", "Shadows")) playerShadowsCheckBox->setCheckState(Qt::Checked); if (Settings::Manager::getBool("terrain shadows", "Shadows")) terrainShadowsCheckBox->setCheckState(Qt::Checked); if (Settings::Manager::getBool("object shadows", "Shadows")) objectShadowsCheckBox->setCheckState(Qt::Checked); if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) indoorShadowsCheckBox->setCheckState(Qt::Checked); shadowComputeSceneBoundsComboBox->setCurrentIndex( shadowComputeSceneBoundsComboBox->findText( QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); if (shadowDistLimit > 0) { shadowDistanceCheckBox->setCheckState(Qt::Checked); shadowDistanceSpinBox->setValue(shadowDistLimit); } float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows"); if (shadowFadeStart != 0) fadeStartSpinBox->setValue(shadowFadeStart); int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows"); int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); return true; } void Launcher::GraphicsPage::saveSettings() { // Visuals // Ensure we only set the new settings if they changed. This is to avoid cluttering the // user settings file (which by definition should only contain settings the user has touched) bool cVSync = vSyncCheckBox->checkState(); if (cVSync != Settings::Manager::getBool("vsync", "Video")) Settings::Manager::setBool("vsync", "Video", cVSync); int cWindowMode = windowModeComboBox->currentIndex(); if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) Settings::Manager::setInt("window mode", "Video", cWindowMode); bool cWindowBorder = windowBorderCheckBox->checkState(); if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) Settings::Manager::setBool("window border", "Video", cWindowBorder); int cAAValue = antiAliasingComboBox->currentText().toInt(); if (cAAValue != Settings::Manager::getInt("antialiasing", "Video")) Settings::Manager::setInt("antialiasing", "Video", cAAValue); int cWidth = 0; int cHeight = 0; if (standardRadioButton->isChecked()) { QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { cWidth = resolutionRe.cap(1).toInt(); cHeight = resolutionRe.cap(2).toInt(); } } else { cWidth = customWidthSpinBox->value(); cHeight = customHeightSpinBox->value(); } if (cWidth != Settings::Manager::getInt("resolution x", "Video")) Settings::Manager::setInt("resolution x", "Video", cWidth); if (cHeight != Settings::Manager::getInt("resolution y", "Video")) Settings::Manager::setInt("resolution y", "Video", cHeight); int cScreen = screenComboBox->currentIndex(); if (cScreen != Settings::Manager::getInt("screen", "Video")) Settings::Manager::setInt("screen", "Video", cScreen); if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { float cFpsLimit = framerateLimitSpinBox->value(); if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video")) Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit); } else if (Settings::Manager::getFloat("framerate limit", "Video") != 0) { Settings::Manager::setFloat("framerate limit", "Video", 0); } // Lighting static std::array lightingMethodMap = {"legacy", "shaders compatibility", "shaders"}; const std::string& cLightingMethod = lightingMethodMap[lightingMethodComboBox->currentIndex()]; if (cLightingMethod != Settings::Manager::getString("lighting method", "Shaders")) Settings::Manager::setString("lighting method", "Shaders", cLightingMethod); // Shadows int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist) Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist); float cFadeStart = fadeStartSpinBox->value(); if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart) Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart); bool cActorShadows = actorShadowsCheckBox->checkState(); bool cObjectShadows = objectShadowsCheckBox->checkState(); bool cTerrainShadows = terrainShadowsCheckBox->checkState(); bool cPlayerShadows = playerShadowsCheckBox->checkState(); if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { if (!Settings::Manager::getBool("enable shadows", "Shadows")) Settings::Manager::setBool("enable shadows", "Shadows", true); if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows) Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows); if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows) Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows); if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows) Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows); if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows) Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows); } else { if (Settings::Manager::getBool("enable shadows", "Shadows")) Settings::Manager::setBool("enable shadows", "Shadows", false); if (Settings::Manager::getBool("actor shadows", "Shadows")) Settings::Manager::setBool("actor shadows", "Shadows", false); if (Settings::Manager::getBool("player shadows", "Shadows")) Settings::Manager::setBool("player shadows", "Shadows", false); if (Settings::Manager::getBool("object shadows", "Shadows")) Settings::Manager::setBool("object shadows", "Shadows", false); if (Settings::Manager::getBool("terrain shadows", "Shadows")) Settings::Manager::setBool("terrain shadows", "Shadows", false); } bool cIndoorShadows = indoorShadowsCheckBox->checkState(); if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows) Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows); int cShadowRes = shadowResolutionComboBox->currentText().toInt(); if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows")) Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes); auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows")) Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) { QStringList result; SDL_DisplayMode mode; int modeIndex, modes = SDL_GetNumDisplayModes(screen); if (modes < 0) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } for (modeIndex = 0; modeIndex < modes; modeIndex++) { if (SDL_GetDisplayMode(screen, modeIndex, &mode) < 0) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
SDL_GetDisplayMode failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); QString aspect = getAspect(mode.w, mode.h); if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { resolution.append(tr("\t(Wide ") + aspect + ")"); } else if (aspect == QLatin1String("4:3")) { resolution.append(tr("\t(Standard 4:3)")); } result.append(resolution); } result.removeDuplicates(); return result; } QRect Launcher::GraphicsPage::getMaximumResolution() { QRect max; for (QScreen* screen : QGuiApplication::screens()) { QRect res = screen->geometry(); if (res.width() > max.width()) max.setWidth(res.width()); if (res.height() > max.height()) max.setHeight(res.height()); } return max; } void Launcher::GraphicsPage::screenChanged(int screen) { if (screen >= 0) { resolutionComboBox->clear(); resolutionComboBox->addItems(mResolutionsPerScreen[screen]); } } void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { if (mode == static_cast(Settings::WindowMode::Fullscreen) || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); windowBorderCheckBox->setEnabled(false); } else { customRadioButton->setEnabled(true); customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); windowBorderCheckBox->setEnabled(true); } } void Launcher::GraphicsPage::slotStandardToggled(bool checked) { if (checked) { resolutionComboBox->setEnabled(true); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); } else { resolutionComboBox->setEnabled(false); customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); } } void Launcher::GraphicsPage::slotFramerateLimitToggled(bool checked) { framerateLimitSpinBox->setEnabled(checked); } void Launcher::GraphicsPage::slotShadowDistLimitToggled(bool checked) { shadowDistanceSpinBox->setEnabled(checked); fadeStartSpinBox->setEnabled(checked); } openmw-openmw-0.48.0/apps/launcher/graphicspage.hpp000066400000000000000000000017061445372753700223520ustar00rootroot00000000000000#ifndef GRAPHICSPAGE_H #define GRAPHICSPAGE_H #include "ui_graphicspage.h" #include #include "sdlinit.hpp" namespace Files { struct ConfigurationManager; } namespace Launcher { class GraphicsSettings; class GraphicsPage : public QWidget, private Ui::GraphicsPage { Q_OBJECT public: explicit GraphicsPage(QWidget *parent = nullptr); void saveSettings(); bool loadSettings(); public slots: void screenChanged(int screen); private slots: void slotFullScreenChanged(int state); void slotStandardToggled(bool checked); void slotFramerateLimitToggled(bool checked); void slotShadowDistLimitToggled(bool checked); private: QVector mResolutionsPerScreen; static QStringList getAvailableResolutions(int screen); static QRect getMaximumResolution(); bool setupSDL(); }; } #endif openmw-openmw-0.48.0/apps/launcher/main.cpp000066400000000000000000000030671445372753700206360ustar00rootroot00000000000000#include #include #include #include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED #include "maindialog.hpp" int runLauncher(int argc, char *argv[]) { Platform::init(); try { QApplication app(argc, argv); // Internationalization QString locale = QLocale::system().name().section('_', 0, 0); QTranslator appTranslator; appTranslator.load(":/translations/" + locale + ".qm"); app.installTranslator(&appTranslator); // Now we make sure the current dir is set to application path QDir dir(QCoreApplication::applicationDirPath()); QDir::setCurrent(dir.absolutePath()); Launcher::MainDialog mainWin; Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); if (result == Launcher::FirstRunDialogResultFailure) return 0; if (result == Launcher::FirstRunDialogResultContinue) mainWin.show(); int exitCode = app.exec(); return exitCode; } catch (std::exception& e) { std::cerr << "ERROR: " << e.what() << std::endl; return 0; } } int main(int argc, char *argv[]) { return wrapApplication(runLauncher, argc, argv, "Launcher"); } openmw-openmw-0.48.0/apps/launcher/maindialog.cpp000066400000000000000000000505651445372753700220230ustar00rootroot00000000000000#include "maindialog.hpp" #include #include #include #include #include #include #include #include #include #include #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" #include "settingspage.hpp" #include "advancedpage.hpp" using namespace Process; void cfgError(const QString& title, const QString& msg) { QMessageBox msgBox; msgBox.setWindowTitle(title); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(msg); msgBox.exec(); } Launcher::MainDialog::MainDialog(QWidget *parent) : QMainWindow(parent), mGameSettings (mCfgMgr) { setupUi(this); mGameInvoker = new ProcessInvoker(); mWizardInvoker = new ProcessInvoker(); connect(mWizardInvoker->getProcess(), SIGNAL(started()), this, SLOT(wizardStarted())); connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(wizardFinished(int,QProcess::ExitStatus))); iconWidget->setViewMode(QListView::IconMode); iconWidget->setWrapping(false); iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure iconWidget->setIconSize(QSize(48, 48)); iconWidget->setMovement(QListView::Static); iconWidget->setSpacing(4); iconWidget->setCurrentRow(0); iconWidget->setFlow(QListView::LeftToRight); auto *helpButton = new QPushButton(tr("Help")); auto *playButton = new QPushButton(tr("Play")); buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); buttonBox->addButton(helpButton, QDialogButtonBox::HelpRole); buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(play())); connect(buttonBox, SIGNAL(helpRequested()), this, SLOT(help())); // Remove what's this? button setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); createIcons(); } Launcher::MainDialog::~MainDialog() { delete mGameInvoker; delete mWizardInvoker; } void Launcher::MainDialog::createIcons() { if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); auto *playButton = new QListWidgetItem(iconWidget); playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setText(tr("Play")); playButton->setTextAlignment(Qt::AlignCenter); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); auto *dataFilesButton = new QListWidgetItem(iconWidget); dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); dataFilesButton->setText(tr("Data Files")); dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); auto *graphicsButton = new QListWidgetItem(iconWidget); graphicsButton->setIcon(QIcon(":/images/preferences-video.png")); graphicsButton->setText(tr("Graphics")); graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); auto *settingsButton = new QListWidgetItem(iconWidget); settingsButton->setIcon(QIcon(":/images/preferences.png")); settingsButton->setText(tr("Settings")); settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); auto *advancedButton = new QListWidgetItem(iconWidget); advancedButton->setIcon(QIcon(":/images/preferences-advanced.png")); advancedButton->setText(tr("Advanced")); advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); connect(iconWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); } void Launcher::MainDialog::createPages() { // Avoid creating the widgets twice if (pagesWidget->count() != 0) return; mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mAdvancedPage = new AdvancedPage(mGameSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); pagesWidget->addWidget(mDataFilesPage); pagesWidget->addWidget(mGraphicsPage); pagesWidget->addWidget(mSettingsPage); pagesWidget->addWidget(mAdvancedPage); // Select the first page iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); } Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() { if (!setupLauncherSettings()) return FirstRunDialogResultFailure; // Dialog wizard and setup will fail if the config directory does not already exist QDir userConfigDir = QDir(QString::fromStdString(mCfgMgr.getUserConfigPath().string())); if ( ! userConfigDir.exists() ) { if ( ! userConfigDir.mkpath(".") ) { cfgError(tr("Error opening OpenMW configuration file"), tr("
Could not create directory %0

\ Please make sure you have the right permissions \ and try again.
").arg(userConfigDir.canonicalPath()) ); return FirstRunDialogResultFailure; } } if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) { QMessageBox msgBox; msgBox.setWindowTitle(tr("First run")); msgBox.setIcon(QMessageBox::Question); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText(tr("

Welcome to OpenMW!

\

It is recommended to run the Installation Wizard.

\

The Wizard will let you select an existing Morrowind installation, \ or install Morrowind for OpenMW to use.

")); QAbstractButton *wizardButton = msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! QAbstractButton *skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); msgBox.exec(); if (msgBox.clickedButton() == wizardButton) { if (mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) return FirstRunDialogResultWizard; } else if (msgBox.clickedButton() == skipButton) { // Don't bother setting up absent game data. if (setup()) return FirstRunDialogResultContinue; } return FirstRunDialogResultFailure; } if (!setup() || !setupGameData()) { return FirstRunDialogResultFailure; } return FirstRunDialogResultContinue; } void Launcher::MainDialog::setVersionLabel() { // Add version information to bottom of the window Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData()); QString revision(QString::fromUtf8(v.mCommitHash.c_str())); QString tag(QString::fromUtf8(v.mTagHash.c_str())); versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag)) versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str()))); else versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); // Add the compile date and time auto compileDate = QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy")); auto compileTime = QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), QLatin1String("hh:mm:ss")); versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale::system().toString(compileDate, QLocale::LongFormat), QLocale::system().toString(compileTime, QLocale::ShortFormat))); } bool Launcher::MainDialog::setup() { if (!setupGameSettings()) return false; setVersionLabel(); mLauncherSettings.setContentList(mGameSettings); if (!setupGraphicsSettings()) return false; // Now create the pages as they need the settings createPages(); // Call this so we can exit on SDL errors before mainwindow is shown if (!mGraphicsPage->loadSettings()) return false; loadSettings(); return true; } bool Launcher::MainDialog::reloadSettings() { if (!setupLauncherSettings()) return false; if (!setupGameSettings()) return false; mLauncherSettings.setContentList(mGameSettings); if (!setupGraphicsSettings()) return false; if (!mSettingsPage->loadSettings()) return false; if (!mDataFilesPage->loadSettings()) return false; if (!mGraphicsPage->loadSettings()) return false; if (!mAdvancedPage->loadSettings()) return false; return true; } void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) current = previous; int currentIndex = iconWidget->row(current); pagesWidget->setCurrentIndex(currentIndex); mSettingsPage->resetProgressBar(); } bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.clear(); mLauncherSettings.setMultiValueEnabled(true); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QStringList paths; paths.append(QString(Config::LauncherSettings::sLauncherConfigFileName)); paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); for (const QString &path : paths) { qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mLauncherSettings.readFile(stream); } file.close(); } return true; } bool Launcher::MainDialog::setupGameSettings() { mGameSettings.clear(); QString localPath = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); QFile file; auto loadFile = [&] (const QString& path, bool(Config::GameSettings::*reader)(QTextStream&, bool), bool ignoreContent = false) -> std::optional { qDebug() << "Loading config file:" << path.toUtf8().constData(); file.setFileName(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); return {}; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); (mGameSettings.*reader)(stream, ignoreContent); file.close(); return true; } return false; }; // Load the user config file first, separately // So we can write it properly, uncontaminated if(!loadFile(userPath + QLatin1String("openmw.cfg"), &Config::GameSettings::readUserFile)) return false; // Now the rest - priority: user > local > global if(auto result = loadFile(localPath + QString("openmw.cfg"), &Config::GameSettings::readFile, true)) { // Load global if local wasn't found if(!*result && !loadFile(globalPath + QString("openmw.cfg"), &Config::GameSettings::readFile, true)) return false; } else return false; if(!loadFile(userPath + QString("openmw.cfg"), &Config::GameSettings::readFile)) return false; return true; } bool Launcher::MainDialog::setupGameData() { QStringList dataDirs; // Check if the paths actually contain data files for (const QString& path3 : mGameSettings.getDataDirs()) { QDir dir(path3); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) dataDirs.append(path3); } if (dataDirs.isEmpty()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText(tr("
Could not find the Data Files location

\ The directory containing the data files was not found.")); QAbstractButton *wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); QAbstractButton *skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); Q_UNUSED(skipButton); // Suppress compiler unused warning msgBox.exec(); if (msgBox.clickedButton() == wizardButton) { if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) return false; } } return true; } bool Launcher::MainDialog::setupGraphicsSettings() { Settings::Manager::clear(); // Ensure to clear previous settings in case we had already loaded settings. try { boost::program_options::variables_map variables; boost::program_options::options_description desc; mCfgMgr.addCommonOptions(desc); mCfgMgr.readConfiguration(variables, desc, true); Settings::Manager::load(mCfgMgr); return true; } catch (std::exception& e) { cfgError(tr("Error reading OpenMW configuration files"), tr("
The problem may be due to an incomplete installation of OpenMW.
\ Reinstalling OpenMW may resolve the problem.
") + e.what()); return false; } } void Launcher::MainDialog::loadSettings() { int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); int posX = mLauncherSettings.value(QString("General/MainWindow/posx")).toInt(); int posY = mLauncherSettings.value(QString("General/MainWindow/posy")).toInt(); resize(width, height); move(posX, posY); } void Launcher::MainDialog::saveSettings() { QString width = QString::number(this->width()); QString height = QString::number(this->height()); mLauncherSettings.setValue(QString("General/MainWindow/width"), width); mLauncherSettings.setValue(QString("General/MainWindow/height"), height); QString posX = QString::number(this->pos().x()); QString posY = QString::number(this->pos().y()); mLauncherSettings.setValue(QString("General/MainWindow/posx"), posX); mLauncherSettings.setValue(QString("General/MainWindow/posy"), posY); mLauncherSettings.setValue(QString("General/firstrun"), QString("false")); } bool Launcher::MainDialog::writeSettings() { // Now write all config files saveSettings(); mDataFilesPage->saveSettings(); mGraphicsPage->saveSettings(); mSettingsPage->saveSettings(); mAdvancedPage->saveSettings(); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QDir dir(userPath); if (!dir.exists()) { if (!dir.mkpath(userPath)) { cfgError(tr("Error creating OpenMW configuration directory"), tr("
Could not create %0

\ Please make sure you have the right permissions \ and try again.
").arg(userPath)); return false; } } // Game settings QFile file(userPath + QString("openmw.cfg")); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created cfgError(tr("Error writing OpenMW configuration file"), tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); return false; } mGameSettings.writeFileWithComments(file); file.close(); // Graphics settings const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); try { Settings::Manager::saveUser(settingsPath); } catch (std::exception& e) { std::string msg = "
Error writing settings.cfg

" + settingsPath + "

" + e.what(); cfgError(tr("Error writing user settings file"), tr(msg.c_str())); return false; } // Launcher settings file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created cfgError(tr("Error writing Launcher configuration file"), tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mLauncherSettings.writeFile(stream); file.close(); return true; } void Launcher::MainDialog::closeEvent(QCloseEvent *event) { writeSettings(); event->accept(); } void Launcher::MainDialog::wizardStarted() { hide(); } void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) return qApp->quit(); // HACK: Ensure the pages are created, else segfault setup(); if (setupGameData() && reloadSettings()) show(); } void Launcher::MainDialog::play() { if (!writeSettings()) return qApp->quit(); if (!mGameSettings.hasMaster()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("No game file selected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
You do not have a game file selected.

\ OpenMW will not start without a game file selected.
")); msgBox.exec(); return; } // Launch the game detached if (mGameInvoker->startProcess(QLatin1String("openmw"), true)) return qApp->quit(); } void Launcher::MainDialog::help() { Misc::HelpViewer::openHelp("reference/index.html"); } openmw-openmw-0.48.0/apps/launcher/maindialog.hpp000066400000000000000000000045601445372753700220220ustar00rootroot00000000000000#ifndef MAINDIALOG_H #define MAINDIALOG_H #ifndef Q_MOC_RUN #include #include #include #include #endif #include "ui_mainwindow.h" class QListWidgetItem; class QStackedWidget; class QStringList; class QStringListModel; class QString; namespace Launcher { class PlayPage; class GraphicsPage; class DataFilesPage; class UnshieldThread; class SettingsPage; class AdvancedPage; enum FirstRunDialogResult { FirstRunDialogResultFailure, FirstRunDialogResultContinue, FirstRunDialogResultWizard }; #ifndef WIN32 bool expansions(Launcher::UnshieldThread& cd); #endif class MainDialog : public QMainWindow, private Ui::MainWindow { Q_OBJECT public: explicit MainDialog(QWidget *parent = nullptr); ~MainDialog() override; FirstRunDialogResult showFirstRunDialog(); bool reloadSettings(); bool writeSettings(); public slots: void changePage(QListWidgetItem *current, QListWidgetItem *previous); void play(); void help(); private slots: void wizardStarted(); void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); private: bool setup(); void createIcons(); void createPages(); bool setupLauncherSettings(); bool setupGameSettings(); bool setupGraphicsSettings(); bool setupGameData(); void setVersionLabel(); void loadSettings(); void saveSettings(); inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); void closeEvent(QCloseEvent *event) override; PlayPage *mPlayPage; GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; SettingsPage *mSettingsPage; AdvancedPage *mAdvancedPage; Process::ProcessInvoker *mGameInvoker; Process::ProcessInvoker *mWizardInvoker; Files::ConfigurationManager mCfgMgr; Config::GameSettings mGameSettings; Config::LauncherSettings mLauncherSettings; }; } #endif openmw-openmw-0.48.0/apps/launcher/playpage.cpp000066400000000000000000000012541445372753700215100ustar00rootroot00000000000000#include "playpage.hpp" #include Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { setObjectName ("PlayPage"); setupUi(this); profilesComboBox->setView(new QListView()); connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); } void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model) { profilesComboBox->setModel(model); } void Launcher::PlayPage::setProfilesIndex(int index) { profilesComboBox->setCurrentIndex(index); } void Launcher::PlayPage::slotPlayClicked() { emit playButtonClicked(); } openmw-openmw-0.48.0/apps/launcher/playpage.hpp000066400000000000000000000010741445372753700215150ustar00rootroot00000000000000#ifndef PLAYPAGE_H #define PLAYPAGE_H #include "ui_playpage.h" class QComboBox; class QPushButton; class QAbstractItemModel; namespace Launcher { class PlayPage : public QWidget, private Ui::PlayPage { Q_OBJECT public: PlayPage(QWidget *parent = nullptr); void setProfilesModel(QAbstractItemModel *model); signals: void signalProfileChanged(int index); void playButtonClicked(); public slots: void setProfilesIndex(int index); private slots: void slotPlayClicked(); }; } #endif openmw-openmw-0.48.0/apps/launcher/sdlinit.cpp000066400000000000000000000010341445372753700213500ustar00rootroot00000000000000#include #include bool initSDL() { SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); SDL_SetMainReady(); // Required for determining screen resolution and such on the Graphics tab if (SDL_Init(SDL_INIT_VIDEO) != 0) { return false; } signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. return true; } void quitSDL() { // Disconnect from SDL processes SDL_Quit(); } openmw-openmw-0.48.0/apps/launcher/sdlinit.hpp000066400000000000000000000001161445372753700213550ustar00rootroot00000000000000#ifndef SDLINIT_H #define SDLINIT_H bool initSDL(); void quitSDL(); #endif openmw-openmw-0.48.0/apps/launcher/settingspage.cpp000066400000000000000000000176271445372753700224160ustar00rootroot00000000000000#include "settingspage.hpp" #include #include #include #include #include "utils/textinputdialog.hpp" #include "datafilespage.hpp" using namespace Process; Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent) : QWidget(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) , mMain(parent) { setupUi(this); QStringList languages; languages << tr("English") << tr("French") << tr("German") << tr("Italian") << tr("Polish") << tr("Russian") << tr("Spanish"); languageComboBox->addItems(languages); mWizardInvoker = new ProcessInvoker(); mImporterInvoker = new ProcessInvoker(); resetProgressBar(); connect(mWizardInvoker->getProcess(), SIGNAL(started()), this, SLOT(wizardStarted())); connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(wizardFinished(int,QProcess::ExitStatus))); connect(mImporterInvoker->getProcess(), SIGNAL(started()), this, SLOT(importerStarted())); connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(importerFinished(int,QProcess::ExitStatus))); mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); // Detect Morrowind configuration files QStringList iniPaths; for (const QString &path : mGameSettings.getDataDirs()) { QDir dir(path); dir.setPath(dir.canonicalPath()); // Resolve symlinks if (dir.exists(QString("Morrowind.ini"))) iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); else { if (!dir.cdUp()) continue; // Cannot move from Data Files if (dir.exists(QString("Morrowind.ini"))) iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); } } if (!iniPaths.isEmpty()) { settingsComboBox->addItems(iniPaths); importerButton->setEnabled(true); } else { importerButton->setEnabled(false); } loadSettings(); } Launcher::SettingsPage::~SettingsPage() { delete mWizardInvoker; delete mImporterInvoker; } void Launcher::SettingsPage::on_wizardButton_clicked() { mMain->writeSettings(); if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) return; } void Launcher::SettingsPage::on_importerButton_clicked() { mMain->writeSettings(); // Create the file if it doesn't already exist, else the importer will fail QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); path.append(QLatin1String("openmw.cfg")); QFile file(path); if (!file.exists()) { if (!file.open(QIODevice::ReadWrite)) { // File cannot be created QMessageBox msgBox; msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not open or create %1 for writing

\

Please make sure you have the right permissions \ and try again.

").arg(file.fileName())); msgBox.exec(); return; } file.close(); } // Construct the arguments to run the importer QStringList arguments; if (addonsCheckBox->isChecked()) arguments.append(QString("--game-files")); if (fontsCheckBox->isChecked()) arguments.append(QString("--fonts")); arguments.append(QString("--encoding")); arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); arguments.append(QString("--ini")); arguments.append(settingsComboBox->currentText()); arguments.append(QString("--cfg")); arguments.append(path); qDebug() << "arguments " << arguments; // start the progress bar as a "bouncing ball" progressBar->setMaximum(0); progressBar->setValue(0); if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) { resetProgressBar(); } } void Launcher::SettingsPage::on_browseButton_clicked() { QString iniFile = QFileDialog::getOpenFileName( this, QObject::tr("Select configuration file"), QDir::currentPath(), QString(tr("Morrowind configuration file (*.ini)"))); if (iniFile.isEmpty()) return; QFileInfo info(iniFile); if (!info.exists() || !info.isReadable()) return; const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); if (settingsComboBox->findText(path) == -1) { settingsComboBox->addItem(path); settingsComboBox->setCurrentIndex(settingsComboBox->findText(path)); importerButton->setEnabled(true); } } void Launcher::SettingsPage::wizardStarted() { mMain->hide(); // Hide the launcher wizardButton->setEnabled(false); } void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) return qApp->quit(); mMain->reloadSettings(); wizardButton->setEnabled(true); mMain->show(); // Show the launcher again } void Launcher::SettingsPage::importerStarted() { importerButton->setEnabled(false); } void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) { resetProgressBar(); QMessageBox msgBox; msgBox.setWindowTitle(tr("Importer finished")); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Warning); msgBox.setText(tr("Failed to import settings from INI file.")); msgBox.exec(); } else { // indicate progress finished progressBar->setMaximum(1); progressBar->setValue(1); // Importer may have changed settings, so refresh mMain->reloadSettings(); } importerButton->setEnabled(true); } void Launcher::SettingsPage::resetProgressBar() { // set progress bar to 0 % progressBar->reset(); } void Launcher::SettingsPage::updateOkButton(const QString &text) { // We do this here because we need to access the profiles if (text.isEmpty()) { mProfileDialog->setOkButtonEnabled(false); return; } const QStringList profiles(mLauncherSettings.getContentLists()); (profiles.contains(text)) ? mProfileDialog->setOkButtonEnabled(false) : mProfileDialog->setOkButtonEnabled(true); } void Launcher::SettingsPage::saveSettings() { QString language(languageComboBox->currentText()); mLauncherSettings.setValue(QLatin1String("Settings/language"), language); if (language == QLatin1String("Polish")) { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); } else if (language == QLatin1String("Russian")) { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); } else { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); } } bool Launcher::SettingsPage::loadSettings() { QString language(mLauncherSettings.value(QLatin1String("Settings/language"))); int index = languageComboBox->findText(language); if (index != -1) languageComboBox->setCurrentIndex(index); return true; } openmw-openmw-0.48.0/apps/launcher/settingspage.hpp000066400000000000000000000030651445372753700224120ustar00rootroot00000000000000#ifndef SETTINGSPAGE_HPP #define SETTINGSPAGE_HPP #include #include "ui_settingspage.h" #include "maindialog.hpp" namespace Files { struct ConfigurationManager; } namespace Config { class GameSettings; class LauncherSettings; } namespace Launcher { class TextInputDialog; class SettingsPage : public QWidget, private Ui::SettingsPage { Q_OBJECT public: SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); ~SettingsPage() override; void saveSettings(); bool loadSettings(); /// set progress bar on page to 0% void resetProgressBar(); private slots: void on_wizardButton_clicked(); void on_importerButton_clicked(); void on_browseButton_clicked(); void wizardStarted(); void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); void importerStarted(); void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); void updateOkButton(const QString &text); private: Process::ProcessInvoker *mWizardInvoker; Process::ProcessInvoker *mImporterInvoker; Files::ConfigurationManager &mCfgMgr; Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; MainDialog *mMain; TextInputDialog *mProfileDialog; }; } #endif // SETTINGSPAGE_HPP openmw-openmw-0.48.0/apps/launcher/textslotmsgbox.cpp000066400000000000000000000001721445372753700230120ustar00rootroot00000000000000#include "textslotmsgbox.hpp" void Launcher::TextSlotMsgBox::setTextSlot(const QString& string) { setText(string); } openmw-openmw-0.48.0/apps/launcher/textslotmsgbox.hpp000066400000000000000000000003761445372753700230250ustar00rootroot00000000000000#ifndef TEXT_SLOT_MSG_BOX #define TEXT_SLOT_MSG_BOX #include namespace Launcher { class TextSlotMsgBox : public QMessageBox { Q_OBJECT public slots: void setTextSlot(const QString& string); }; } #endif openmw-openmw-0.48.0/apps/launcher/utils/000077500000000000000000000000001445372753700203405ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/launcher/utils/cellnameloader.cpp000066400000000000000000000025451445372753700240210ustar00rootroot00000000000000#include "cellnameloader.hpp" #include #include QSet CellNameLoader::getCellNames(QStringList &contentPaths) { QSet cellNames; ESM::ESMReader esmReader; // Loop through all content files for (auto &contentPath : contentPaths) { if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive)) continue; esmReader.open(contentPath.toStdString()); // Loop through all records while(esmReader.hasMoreRecs()) { ESM::NAME recordName = esmReader.getRecName(); esmReader.getRecHeader(); if (isCellRecord(recordName)) { QString cellName = getCellName(esmReader); if (!cellName.isEmpty()) { cellNames.insert(cellName); } } // Stop loading content for this record and continue to the next esmReader.skipRecord(); } } return cellNames; } bool CellNameLoader::isCellRecord(ESM::NAME &recordName) { return recordName.toInt() == ESM::REC_CELL; } QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) { ESM::Cell cell; bool isDeleted = false; cell.loadNameAndData(esmReader, isDeleted); return QString::fromStdString(cell.mName); } openmw-openmw-0.48.0/apps/launcher/utils/cellnameloader.hpp000066400000000000000000000020301445372753700240130ustar00rootroot00000000000000#ifndef OPENMW_CELLNAMELOADER_H #define OPENMW_CELLNAMELOADER_H #include #include #include namespace ESM {class ESMReader; struct Cell;} namespace ContentSelectorView {class ContentSelector;} class CellNameLoader { public: /** * Returns the names of all cells contained within the given content files * @param contentPaths the file paths of each content file to be examined * @return the names of all cells */ QSet getCellNames(QStringList &contentPaths); private: /** * Returns whether or not the given record is of type "Cell" * @param name The name associated with the record * @return whether or not the given record is of type "Cell" */ bool isCellRecord(ESM::NAME &name); /** * Returns the name of the cell * @param esmReader the reader currently pointed to a loaded cell * @return the name of the cell */ QString getCellName(ESM::ESMReader &esmReader); }; #endif //OPENMW_CELLNAMELOADER_H openmw-openmw-0.48.0/apps/launcher/utils/lineedit.cpp000066400000000000000000000020201445372753700226330ustar00rootroot00000000000000#include "lineedit.hpp" LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { setupClearButton(); } void LineEdit::setupClearButton() { mClearButton = new QToolButton(this); QPixmap pixmap(":images/clear.png"); mClearButton->setIcon(QIcon(pixmap)); mClearButton->setIconSize(pixmap.size()); mClearButton->setCursor(Qt::ArrowCursor); mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); mClearButton->hide(); connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); } void LineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); mClearButton->move(rect().right() - frameWidth - sz.width(), (rect().bottom() + 1 - sz.height())/2); } void LineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } openmw-openmw-0.48.0/apps/launcher/utils/lineedit.hpp000066400000000000000000000014651445372753700226540ustar00rootroot00000000000000/**************************************************************************** ** ** Copyright (c) 2007 Trolltech ASA ** ** Use, modification and distribution is allowed without limitation, ** warranty, liability or support of any kind. ** ****************************************************************************/ #ifndef LINEEDIT_H #define LINEEDIT_H #include #include #include class QToolButton; class LineEdit : public QLineEdit { Q_OBJECT QString mPlaceholderText; public: LineEdit(QWidget *parent = nullptr); protected: void resizeEvent(QResizeEvent *) override; private slots: void updateClearButton(const QString &text); protected: QToolButton *mClearButton; void setupClearButton(); }; #endif // LIENEDIT_H openmw-openmw-0.48.0/apps/launcher/utils/openalutil.cpp000066400000000000000000000031361445372753700232230ustar00rootroot00000000000000#include #include #include #include "openalutil.hpp" #ifndef ALC_ALL_DEVICES_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif std::vector Launcher::enumerateOpenALDevices() { std::vector devlist; const ALCchar *devnames; if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) { devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); } else { devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); } while(devnames && *devnames) { devlist.emplace_back(devnames); devnames += strlen(devnames)+1; } return devlist; } std::vector Launcher::enumerateOpenALDevicesHrtf() { std::vector ret; ALCdevice *device = alcOpenDevice(nullptr); if(device) { if(alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) { LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT"); memcpy(&alcGetStringiSOFT, &funcPtr, sizeof(funcPtr)); ALCint num_hrtf; alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); ret.reserve(num_hrtf); for(ALCint i = 0;i < num_hrtf;++i) { const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i); if(strcmp(entry, "") == 0) break; ret.emplace_back(entry); } } alcCloseDevice(device); } return ret; } openmw-openmw-0.48.0/apps/launcher/utils/openalutil.hpp000066400000000000000000000002561445372753700232300ustar00rootroot00000000000000#include #include namespace Launcher { std::vector enumerateOpenALDevices(); std::vector enumerateOpenALDevicesHrtf(); } openmw-openmw-0.48.0/apps/launcher/utils/profilescombobox.cpp000066400000000000000000000047611445372753700244300ustar00rootroot00000000000000#include #include #include "profilescombobox.hpp" ProfilesComboBox::ProfilesComboBox(QWidget *parent) : ContentSelectorView::ComboBox(parent) { connect(this, SIGNAL(activated(int)), this, SLOT(slotIndexChangedByUser(int))); setInsertPolicy(QComboBox::NoInsert); } void ProfilesComboBox::setEditEnabled(bool editable) { if (isEditable() == editable) return; if (!editable) { disconnect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished())); disconnect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); return setEditable(false); } // Reset the completer and validator setEditable(true); setValidator(mValidator); auto *edit = new ComboBoxLineEdit(this); setLineEdit(edit); setCompleter(nullptr); connect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished())); connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); connect (lineEdit(), SIGNAL(textChanged(QString)), this, SIGNAL (signalProfileTextChanged (QString))); } void ProfilesComboBox::slotTextChanged(const QString &text) { QPalette palette; palette.setColor(QPalette::Text,Qt::red); int index = findText(text); if (text.isEmpty() || (index != -1 && index != currentIndex())) { lineEdit()->setPalette(palette); } else { lineEdit()->setPalette(QApplication::palette()); } } void ProfilesComboBox::slotEditingFinished() { QString current = currentText(); QString previous = itemText(currentIndex()); if (currentIndex() == -1) return; if (current.isEmpty()) return; if (current == previous) return; if (findText(current) != -1) return; setItemText(currentIndex(), current); emit(profileRenamed(previous, current)); } void ProfilesComboBox::slotIndexChangedByUser(int index) { if (index == -1) return; emit (signalProfileChanged(mOldProfile, currentText())); mOldProfile = currentText(); } ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit (QWidget *parent) : LineEdit (parent) { int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); setObjectName(QString("ComboBoxLineEdit")); setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); } openmw-openmw-0.48.0/apps/launcher/utils/profilescombobox.hpp000066400000000000000000000021161445372753700244250ustar00rootroot00000000000000#ifndef PROFILESCOMBOBOX_HPP #define PROFILESCOMBOBOX_HPP #include "components/contentselector/view/combobox.hpp" #include "lineedit.hpp" #include class QString; class ProfilesComboBox : public ContentSelectorView::ComboBox { Q_OBJECT public: class ComboBoxLineEdit : public LineEdit { public: explicit ComboBoxLineEdit (QWidget *parent = nullptr); }; public: explicit ProfilesComboBox(QWidget *parent = nullptr); void setEditEnabled(bool editable); void setCurrentProfile(int index) { ComboBox::setCurrentIndex(index); mOldProfile = currentText(); } signals: void signalProfileTextChanged(const QString &item); void signalProfileChanged(const QString &previous, const QString ¤t); void signalProfileChanged(int index); void profileRenamed(const QString &oldName, const QString &newName); private slots: void slotEditingFinished(); void slotIndexChangedByUser(int index); void slotTextChanged(const QString &text); private: QString mOldProfile; }; #endif // PROFILESCOMBOBOX_HPP openmw-openmw-0.48.0/apps/launcher/utils/textinputdialog.cpp000066400000000000000000000036231445372753700242740ustar00rootroot00000000000000#include "textinputdialog.hpp" #include #include #include #include #include #include Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); auto *label = new QLabel(this); label->setText(text); // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore mLineEdit = new LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(nullptr); auto *dialogLayout = new QVBoxLayout(this); dialogLayout->addWidget(label); dialogLayout->addWidget(mLineEdit); dialogLayout->addWidget(mButtonBox); // Messageboxes on mac have no title #ifndef Q_OS_MAC setWindowTitle(title); #else Q_UNUSED(title); #endif setModal(true); connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } Launcher::TextInputDialog::~TextInputDialog() { } int Launcher::TextInputDialog::exec() { mLineEdit->clear(); mLineEdit->setFocus(); return QDialog::exec(); } void Launcher::TextInputDialog::setOkButtonEnabled(bool enabled) { QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(enabled); QPalette palette; palette.setColor(QPalette::Text, Qt::red); if (enabled) { mLineEdit->setPalette(QApplication::palette()); } else { // Existing profile name, make the text red mLineEdit->setPalette(palette); } } openmw-openmw-0.48.0/apps/launcher/utils/textinputdialog.hpp000066400000000000000000000011651445372753700243000ustar00rootroot00000000000000#ifndef TEXTINPUTDIALOG_HPP #define TEXTINPUTDIALOG_HPP #include #include "lineedit.hpp" class QDialogButtonBox; namespace Launcher { class TextInputDialog : public QDialog { Q_OBJECT public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = nullptr); ~TextInputDialog () override; inline LineEdit *lineEdit() { return mLineEdit; } void setOkButtonEnabled(bool enabled); int exec() override; private: QDialogButtonBox *mButtonBox; LineEdit *mLineEdit; }; } #endif // TEXTINPUTDIALOG_HPP openmw-openmw-0.48.0/apps/mwiniimporter/000077500000000000000000000000001445372753700203045ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/mwiniimporter/CMakeLists.txt000066400000000000000000000016021445372753700230430ustar00rootroot00000000000000set(MWINIIMPORT main.cpp importer.cpp ) set(MWINIIMPORT_HEADER importer.hpp ) source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) openmw_add_executable(openmw-iniimporter ${MWINIIMPORT} ) target_link_libraries(openmw-iniimporter ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} components ) if (WIN32) target_link_libraries(openmw-iniimporter ${Boost_LOCALE_LIBRARY}) INSTALL(TARGETS openmw-iniimporter RUNTIME DESTINATION ".") endif(WIN32) if (MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") endif() if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-iniimporter gcov) endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-iniimporter PRIVATE ) endif() openmw-openmw-0.48.0/apps/mwiniimporter/importer.cpp000066400000000000000000001054441445372753700226610ustar00rootroot00000000000000#include "importer.hpp" #include #include #include #include #include namespace bfs = boost::filesystem; MwIniImporter::MwIniImporter() : mVerbose(false) , mEncoding(ToUTF8::WINDOWS_1250) { const char *map[][2] = { { "no-sound", "General:Disable Audio" }, { 0, 0 } }; const char *fallback[] = { // light "LightAttenuation:UseConstant", "LightAttenuation:ConstantValue", "LightAttenuation:UseLinear", "LightAttenuation:LinearMethod", "LightAttenuation:LinearValue", "LightAttenuation:LinearRadiusMult", "LightAttenuation:UseQuadratic", "LightAttenuation:QuadraticMethod", "LightAttenuation:QuadraticValue", "LightAttenuation:QuadraticRadiusMult", "LightAttenuation:OutQuadInLin", // inventory "Inventory:DirectionalDiffuseR", "Inventory:DirectionalDiffuseG", "Inventory:DirectionalDiffuseB", "Inventory:DirectionalAmbientR", "Inventory:DirectionalAmbientG", "Inventory:DirectionalAmbientB", "Inventory:DirectionalRotationX", "Inventory:DirectionalRotationY", "Inventory:UniformScaling", // map "Map:Travel Siltstrider Red", "Map:Travel Siltstrider Green", "Map:Travel Siltstrider Blue", "Map:Travel Boat Red", "Map:Travel Boat Green", "Map:Travel Boat Blue", "Map:Travel Magic Red", "Map:Travel Magic Green", "Map:Travel Magic Blue", "Map:Show Travel Lines", // water "Water:Map Alpha", "Water:World Alpha", "Water:SurfaceTextureSize", "Water:SurfaceTileCount", "Water:SurfaceFPS", "Water:SurfaceTexture", "Water:SurfaceFrameCount", "Water:TileTextureDivisor", "Water:RippleTexture", "Water:RippleFrameCount", "Water:RippleLifetime", "Water:MaxNumberRipples", "Water:RippleScale", "Water:RippleRotSpeed", "Water:RippleAlphas", "Water:PSWaterReflectTerrain", "Water:PSWaterReflectUpdate", "Water:NearWaterRadius", "Water:NearWaterPoints", "Water:NearWaterUnderwaterFreq", "Water:NearWaterUnderwaterVolume", "Water:NearWaterIndoorTolerance", "Water:NearWaterOutdoorTolerance", "Water:NearWaterIndoorID", "Water:NearWaterOutdoorID", "Water:UnderwaterSunriseFog", "Water:UnderwaterDayFog", "Water:UnderwaterSunsetFog", "Water:UnderwaterNightFog", "Water:UnderwaterIndoorFog", "Water:UnderwaterColor", "Water:UnderwaterColorWeight", // pixelwater "PixelWater:SurfaceFPS", "PixelWater:TileCount", "PixelWater:Resolution", // fonts "Fonts:Font 0", "Fonts:Font 1", "Fonts:Font 2", // UI colors "FontColor:color_normal", "FontColor:color_normal_over", "FontColor:color_normal_pressed", "FontColor:color_active", "FontColor:color_active_over", "FontColor:color_active_pressed", "FontColor:color_disabled", "FontColor:color_disabled_over", "FontColor:color_disabled_pressed", "FontColor:color_link", "FontColor:color_link_over", "FontColor:color_link_pressed", "FontColor:color_journal_link", "FontColor:color_journal_link_over", "FontColor:color_journal_link_pressed", "FontColor:color_journal_topic", "FontColor:color_journal_topic_over", "FontColor:color_journal_topic_pressed", "FontColor:color_answer", "FontColor:color_answer_over", "FontColor:color_answer_pressed", "FontColor:color_header", "FontColor:color_notify", "FontColor:color_big_normal", "FontColor:color_big_normal_over", "FontColor:color_big_normal_pressed", "FontColor:color_big_link", "FontColor:color_big_link_over", "FontColor:color_big_link_pressed", "FontColor:color_big_answer", "FontColor:color_big_answer_over", "FontColor:color_big_answer_pressed", "FontColor:color_big_header", "FontColor:color_big_notify", "FontColor:color_background", "FontColor:color_focus", "FontColor:color_health", "FontColor:color_magic", "FontColor:color_fatigue", "FontColor:color_misc", "FontColor:color_weapon_fill", "FontColor:color_magic_fill", "FontColor:color_positive", "FontColor:color_negative", "FontColor:color_count", // level up messages "Level Up:Level2", "Level Up:Level3", "Level Up:Level4", "Level Up:Level5", "Level Up:Level6", "Level Up:Level7", "Level Up:Level8", "Level Up:Level9", "Level Up:Level10", "Level Up:Level11", "Level Up:Level12", "Level Up:Level13", "Level Up:Level14", "Level Up:Level15", "Level Up:Level16", "Level Up:Level17", "Level Up:Level18", "Level Up:Level19", "Level Up:Level20", "Level Up:Default", // character creation multiple choice test "Question 1:Question", "Question 1:AnswerOne", "Question 1:AnswerTwo", "Question 1:AnswerThree", "Question 1:Sound", "Question 2:Question", "Question 2:AnswerOne", "Question 2:AnswerTwo", "Question 2:AnswerThree", "Question 2:Sound", "Question 3:Question", "Question 3:AnswerOne", "Question 3:AnswerTwo", "Question 3:AnswerThree", "Question 3:Sound", "Question 4:Question", "Question 4:AnswerOne", "Question 4:AnswerTwo", "Question 4:AnswerThree", "Question 4:Sound", "Question 5:Question", "Question 5:AnswerOne", "Question 5:AnswerTwo", "Question 5:AnswerThree", "Question 5:Sound", "Question 6:Question", "Question 6:AnswerOne", "Question 6:AnswerTwo", "Question 6:AnswerThree", "Question 6:Sound", "Question 7:Question", "Question 7:AnswerOne", "Question 7:AnswerTwo", "Question 7:AnswerThree", "Question 7:Sound", "Question 8:Question", "Question 8:AnswerOne", "Question 8:AnswerTwo", "Question 8:AnswerThree", "Question 8:Sound", "Question 9:Question", "Question 9:AnswerOne", "Question 9:AnswerTwo", "Question 9:AnswerThree", "Question 9:Sound", "Question 10:Question", "Question 10:AnswerOne", "Question 10:AnswerTwo", "Question 10:AnswerThree", "Question 10:Sound", // blood textures and models "Blood:Model 0", "Blood:Model 1", "Blood:Model 2", "Blood:Texture 0", "Blood:Texture 1", "Blood:Texture 2", "Blood:Texture 3", "Blood:Texture 4", "Blood:Texture 5", "Blood:Texture 6", "Blood:Texture 7", "Blood:Texture Name 0", "Blood:Texture Name 1", "Blood:Texture Name 2", "Blood:Texture Name 3", "Blood:Texture Name 4", "Blood:Texture Name 5", "Blood:Texture Name 6", "Blood:Texture Name 7", // movies "Movies:Company Logo", "Movies:Morrowind Logo", "Movies:New Game", "Movies:Loading", "Movies:Options Menu", // weather related values "Weather Thunderstorm:Thunder Sound ID 0", "Weather Thunderstorm:Thunder Sound ID 1", "Weather Thunderstorm:Thunder Sound ID 2", "Weather Thunderstorm:Thunder Sound ID 3", "Weather:Sunrise Time", "Weather:Sunset Time", "Weather:Sunrise Duration", "Weather:Sunset Duration", "Weather:Hours Between Weather Changes", // AKA weather update time "Weather Thunderstorm:Thunder Frequency", "Weather Thunderstorm:Thunder Threshold", "Weather:EnvReduceColor", "Weather:LerpCloseColor", "Weather:BumpFadeColor", "Weather:AlphaReduce", "Weather:Minimum Time Between Environmental Sounds", "Weather:Maximum Time Between Environmental Sounds", "Weather:Sun Glare Fader Max", "Weather:Sun Glare Fader Angle Max", "Weather:Sun Glare Fader Color", "Weather:Timescale Clouds", "Weather:Precip Gravity", "Weather:Rain Ripples", "Weather:Rain Ripple Radius", "Weather:Rain Ripples Per Drop", "Weather:Rain Ripple Scale", "Weather:Rain Ripple Speed", "Weather:Fog Depth Change Speed", "Weather:Sky Pre-Sunrise Time", "Weather:Sky Post-Sunrise Time", "Weather:Sky Pre-Sunset Time", "Weather:Sky Post-Sunset Time", "Weather:Ambient Pre-Sunrise Time", "Weather:Ambient Post-Sunrise Time", "Weather:Ambient Pre-Sunset Time", "Weather:Ambient Post-Sunset Time", "Weather:Fog Pre-Sunrise Time", "Weather:Fog Post-Sunrise Time", "Weather:Fog Pre-Sunset Time", "Weather:Fog Post-Sunset Time", "Weather:Sun Pre-Sunrise Time", "Weather:Sun Post-Sunrise Time", "Weather:Sun Pre-Sunset Time", "Weather:Sun Post-Sunset Time", "Weather:Stars Post-Sunset Start", "Weather:Stars Pre-Sunrise Finish", "Weather:Stars Fading Duration", "Weather:Snow Ripples", "Weather:Snow Ripple Radius", "Weather:Snow Ripples Per Flake", "Weather:Snow Ripple Scale", "Weather:Snow Ripple Speed", "Weather:Snow Gravity Scale", "Weather:Snow High Kill", "Weather:Snow Low Kill", "Weather Clear:Cloud Texture", "Weather Clear:Clouds Maximum Percent", "Weather Clear:Transition Delta", "Weather Clear:Sky Sunrise Color", "Weather Clear:Sky Day Color", "Weather Clear:Sky Sunset Color", "Weather Clear:Sky Night Color", "Weather Clear:Fog Sunrise Color", "Weather Clear:Fog Day Color", "Weather Clear:Fog Sunset Color", "Weather Clear:Fog Night Color", "Weather Clear:Ambient Sunrise Color", "Weather Clear:Ambient Day Color", "Weather Clear:Ambient Sunset Color", "Weather Clear:Ambient Night Color", "Weather Clear:Sun Sunrise Color", "Weather Clear:Sun Day Color", "Weather Clear:Sun Sunset Color", "Weather Clear:Sun Night Color", "Weather Clear:Sun Disc Sunset Color", "Weather Clear:Land Fog Day Depth", "Weather Clear:Land Fog Night Depth", "Weather Clear:Wind Speed", "Weather Clear:Cloud Speed", "Weather Clear:Glare View", "Weather Clear:Ambient Loop Sound ID", "Weather Cloudy:Cloud Texture", "Weather Cloudy:Clouds Maximum Percent", "Weather Cloudy:Transition Delta", "Weather Cloudy:Sky Sunrise Color", "Weather Cloudy:Sky Day Color", "Weather Cloudy:Sky Sunset Color", "Weather Cloudy:Sky Night Color", "Weather Cloudy:Fog Sunrise Color", "Weather Cloudy:Fog Day Color", "Weather Cloudy:Fog Sunset Color", "Weather Cloudy:Fog Night Color", "Weather Cloudy:Ambient Sunrise Color", "Weather Cloudy:Ambient Day Color", "Weather Cloudy:Ambient Sunset Color", "Weather Cloudy:Ambient Night Color", "Weather Cloudy:Sun Sunrise Color", "Weather Cloudy:Sun Day Color", "Weather Cloudy:Sun Sunset Color", "Weather Cloudy:Sun Night Color", "Weather Cloudy:Sun Disc Sunset Color", "Weather Cloudy:Land Fog Day Depth", "Weather Cloudy:Land Fog Night Depth", "Weather Cloudy:Wind Speed", "Weather Cloudy:Cloud Speed", "Weather Cloudy:Glare View", "Weather Cloudy:Ambient Loop Sound ID", "Weather Foggy:Cloud Texture", "Weather Foggy:Clouds Maximum Percent", "Weather Foggy:Transition Delta", "Weather Foggy:Sky Sunrise Color", "Weather Foggy:Sky Day Color", "Weather Foggy:Sky Sunset Color", "Weather Foggy:Sky Night Color", "Weather Foggy:Fog Sunrise Color", "Weather Foggy:Fog Day Color", "Weather Foggy:Fog Sunset Color", "Weather Foggy:Fog Night Color", "Weather Foggy:Ambient Sunrise Color", "Weather Foggy:Ambient Day Color", "Weather Foggy:Ambient Sunset Color", "Weather Foggy:Ambient Night Color", "Weather Foggy:Sun Sunrise Color", "Weather Foggy:Sun Day Color", "Weather Foggy:Sun Sunset Color", "Weather Foggy:Sun Night Color", "Weather Foggy:Sun Disc Sunset Color", "Weather Foggy:Land Fog Day Depth", "Weather Foggy:Land Fog Night Depth", "Weather Foggy:Wind Speed", "Weather Foggy:Cloud Speed", "Weather Foggy:Glare View", "Weather Foggy:Ambient Loop Sound ID", "Weather Thunderstorm:Cloud Texture", "Weather Thunderstorm:Clouds Maximum Percent", "Weather Thunderstorm:Transition Delta", "Weather Thunderstorm:Sky Sunrise Color", "Weather Thunderstorm:Sky Day Color", "Weather Thunderstorm:Sky Sunset Color", "Weather Thunderstorm:Sky Night Color", "Weather Thunderstorm:Fog Sunrise Color", "Weather Thunderstorm:Fog Day Color", "Weather Thunderstorm:Fog Sunset Color", "Weather Thunderstorm:Fog Night Color", "Weather Thunderstorm:Ambient Sunrise Color", "Weather Thunderstorm:Ambient Day Color", "Weather Thunderstorm:Ambient Sunset Color", "Weather Thunderstorm:Ambient Night Color", "Weather Thunderstorm:Sun Sunrise Color", "Weather Thunderstorm:Sun Day Color", "Weather Thunderstorm:Sun Sunset Color", "Weather Thunderstorm:Sun Night Color", "Weather Thunderstorm:Sun Disc Sunset Color", "Weather Thunderstorm:Land Fog Day Depth", "Weather Thunderstorm:Land Fog Night Depth", "Weather Thunderstorm:Wind Speed", "Weather Thunderstorm:Cloud Speed", "Weather Thunderstorm:Glare View", "Weather Thunderstorm:Rain Loop Sound ID", "Weather Thunderstorm:Using Precip", "Weather Thunderstorm:Rain Diameter", "Weather Thunderstorm:Rain Height Min", "Weather Thunderstorm:Rain Height Max", "Weather Thunderstorm:Rain Threshold", "Weather Thunderstorm:Max Raindrops", "Weather Thunderstorm:Rain Entrance Speed", "Weather Thunderstorm:Ambient Loop Sound ID", "Weather Thunderstorm:Flash Decrement", "Weather Rain:Cloud Texture", "Weather Rain:Clouds Maximum Percent", "Weather Rain:Transition Delta", "Weather Rain:Sky Sunrise Color", "Weather Rain:Sky Day Color", "Weather Rain:Sky Sunset Color", "Weather Rain:Sky Night Color", "Weather Rain:Fog Sunrise Color", "Weather Rain:Fog Day Color", "Weather Rain:Fog Sunset Color", "Weather Rain:Fog Night Color", "Weather Rain:Ambient Sunrise Color", "Weather Rain:Ambient Day Color", "Weather Rain:Ambient Sunset Color", "Weather Rain:Ambient Night Color", "Weather Rain:Sun Sunrise Color", "Weather Rain:Sun Day Color", "Weather Rain:Sun Sunset Color", "Weather Rain:Sun Night Color", "Weather Rain:Sun Disc Sunset Color", "Weather Rain:Land Fog Day Depth", "Weather Rain:Land Fog Night Depth", "Weather Rain:Wind Speed", "Weather Rain:Cloud Speed", "Weather Rain:Glare View", "Weather Rain:Rain Loop Sound ID", "Weather Rain:Using Precip", "Weather Rain:Rain Diameter", "Weather Rain:Rain Height Min", "Weather Rain:Rain Height Max", "Weather Rain:Rain Threshold", "Weather Rain:Rain Entrance Speed", "Weather Rain:Ambient Loop Sound ID", "Weather Rain:Max Raindrops", "Weather Overcast:Cloud Texture", "Weather Overcast:Clouds Maximum Percent", "Weather Overcast:Transition Delta", "Weather Overcast:Sky Sunrise Color", "Weather Overcast:Sky Day Color", "Weather Overcast:Sky Sunset Color", "Weather Overcast:Sky Night Color", "Weather Overcast:Fog Sunrise Color", "Weather Overcast:Fog Day Color", "Weather Overcast:Fog Sunset Color", "Weather Overcast:Fog Night Color", "Weather Overcast:Ambient Sunrise Color", "Weather Overcast:Ambient Day Color", "Weather Overcast:Ambient Sunset Color", "Weather Overcast:Ambient Night Color", "Weather Overcast:Sun Sunrise Color", "Weather Overcast:Sun Day Color", "Weather Overcast:Sun Sunset Color", "Weather Overcast:Sun Night Color", "Weather Overcast:Sun Disc Sunset Color", "Weather Overcast:Land Fog Day Depth", "Weather Overcast:Land Fog Night Depth", "Weather Overcast:Wind Speed", "Weather Overcast:Cloud Speed", "Weather Overcast:Glare View", "Weather Overcast:Ambient Loop Sound ID", "Weather Ashstorm:Cloud Texture", "Weather Ashstorm:Clouds Maximum Percent", "Weather Ashstorm:Transition Delta", "Weather Ashstorm:Sky Sunrise Color", "Weather Ashstorm:Sky Day Color", "Weather Ashstorm:Sky Sunset Color", "Weather Ashstorm:Sky Night Color", "Weather Ashstorm:Fog Sunrise Color", "Weather Ashstorm:Fog Day Color", "Weather Ashstorm:Fog Sunset Color", "Weather Ashstorm:Fog Night Color", "Weather Ashstorm:Ambient Sunrise Color", "Weather Ashstorm:Ambient Day Color", "Weather Ashstorm:Ambient Sunset Color", "Weather Ashstorm:Ambient Night Color", "Weather Ashstorm:Sun Sunrise Color", "Weather Ashstorm:Sun Day Color", "Weather Ashstorm:Sun Sunset Color", "Weather Ashstorm:Sun Night Color", "Weather Ashstorm:Sun Disc Sunset Color", "Weather Ashstorm:Land Fog Day Depth", "Weather Ashstorm:Land Fog Night Depth", "Weather Ashstorm:Wind Speed", "Weather Ashstorm:Cloud Speed", "Weather Ashstorm:Glare View", "Weather Ashstorm:Ambient Loop Sound ID", "Weather Ashstorm:Storm Threshold", "Weather Blight:Cloud Texture", "Weather Blight:Clouds Maximum Percent", "Weather Blight:Transition Delta", "Weather Blight:Sky Sunrise Color", "Weather Blight:Sky Day Color", "Weather Blight:Sky Sunset Color", "Weather Blight:Sky Night Color", "Weather Blight:Fog Sunrise Color", "Weather Blight:Fog Day Color", "Weather Blight:Fog Sunset Color", "Weather Blight:Fog Night Color", "Weather Blight:Ambient Sunrise Color", "Weather Blight:Ambient Day Color", "Weather Blight:Ambient Sunset Color", "Weather Blight:Ambient Night Color", "Weather Blight:Sun Sunrise Color", "Weather Blight:Sun Day Color", "Weather Blight:Sun Sunset Color", "Weather Blight:Sun Night Color", "Weather Blight:Sun Disc Sunset Color", "Weather Blight:Land Fog Day Depth", "Weather Blight:Land Fog Night Depth", "Weather Blight:Wind Speed", "Weather Blight:Cloud Speed", "Weather Blight:Glare View", "Weather Blight:Ambient Loop Sound ID", "Weather Blight:Storm Threshold", "Weather Blight:Disease Chance", // for Bloodmoon "Weather Snow:Cloud Texture", "Weather Snow:Clouds Maximum Percent", "Weather Snow:Transition Delta", "Weather Snow:Sky Sunrise Color", "Weather Snow:Sky Day Color", "Weather Snow:Sky Sunset Color", "Weather Snow:Sky Night Color", "Weather Snow:Fog Sunrise Color", "Weather Snow:Fog Day Color", "Weather Snow:Fog Sunset Color", "Weather Snow:Fog Night Color", "Weather Snow:Ambient Sunrise Color", "Weather Snow:Ambient Day Color", "Weather Snow:Ambient Sunset Color", "Weather Snow:Ambient Night Color", "Weather Snow:Sun Sunrise Color", "Weather Snow:Sun Day Color", "Weather Snow:Sun Sunset Color", "Weather Snow:Sun Night Color", "Weather Snow:Sun Disc Sunset Color", "Weather Snow:Land Fog Day Depth", "Weather Snow:Land Fog Night Depth", "Weather Snow:Wind Speed", "Weather Snow:Cloud Speed", "Weather Snow:Glare View", "Weather Snow:Snow Diameter", "Weather Snow:Snow Height Min", "Weather Snow:Snow Height Max", "Weather Snow:Snow Entrance Speed", "Weather Snow:Max Snowflakes", "Weather Snow:Ambient Loop Sound ID", "Weather Snow:Snow Threshold", // for Bloodmoon "Weather Blizzard:Cloud Texture", "Weather Blizzard:Clouds Maximum Percent", "Weather Blizzard:Transition Delta", "Weather Blizzard:Sky Sunrise Color", "Weather Blizzard:Sky Day Color", "Weather Blizzard:Sky Sunset Color", "Weather Blizzard:Sky Night Color", "Weather Blizzard:Fog Sunrise Color", "Weather Blizzard:Fog Day Color", "Weather Blizzard:Fog Sunset Color", "Weather Blizzard:Fog Night Color", "Weather Blizzard:Ambient Sunrise Color", "Weather Blizzard:Ambient Day Color", "Weather Blizzard:Ambient Sunset Color", "Weather Blizzard:Ambient Night Color", "Weather Blizzard:Sun Sunrise Color", "Weather Blizzard:Sun Day Color", "Weather Blizzard:Sun Sunset Color", "Weather Blizzard:Sun Night Color", "Weather Blizzard:Sun Disc Sunset Color", "Weather Blizzard:Land Fog Day Depth", "Weather Blizzard:Land Fog Night Depth", "Weather Blizzard:Wind Speed", "Weather Blizzard:Cloud Speed", "Weather Blizzard:Glare View", "Weather Blizzard:Ambient Loop Sound ID", "Weather Blizzard:Storm Threshold", // moons "Moons:Secunda Size", "Moons:Secunda Axis Offset", "Moons:Secunda Speed", "Moons:Secunda Daily Increment", "Moons:Secunda Moon Shadow Early Fade Angle", "Moons:Secunda Fade Start Angle", "Moons:Secunda Fade End Angle", "Moons:Secunda Fade In Start", "Moons:Secunda Fade In Finish", "Moons:Secunda Fade Out Start", "Moons:Secunda Fade Out Finish", "Moons:Masser Size", "Moons:Masser Axis Offset", "Moons:Masser Speed", "Moons:Masser Daily Increment", "Moons:Masser Moon Shadow Early Fade Angle", "Moons:Masser Fade Start Angle", "Moons:Masser Fade End Angle", "Moons:Masser Fade In Start", "Moons:Masser Fade In Finish", "Moons:Masser Fade Out Start", "Moons:Masser Fade Out Finish", "Moons:Script Color", // werewolf (Bloodmoon) "General:Werewolf FOV", 0 }; for(int i=0; map[i][0]; i++) { mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); } for(int i=0; fallback[i]; i++) { mMergeFallback.emplace_back(fallback[i]); } } void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { std::cout << "load ini file: " << filename << std::endl; std::string section(""); MwIniImporter::multistrmap map; bfs::ifstream file((bfs::path(filename))); ToUTF8::Utf8Encoder encoder(mEncoding); std::string line; while (std::getline(file, line)) { std::string_view utf8 = encoder.getUtf8(line); // unify Unix-style and Windows file ending if (!(utf8.empty()) && (utf8[utf8.length()-1]) == '\r') { utf8 = utf8.substr(0, utf8.length()-1); } if(utf8.empty()) { continue; } if(utf8[0] == '[') { int pos = static_cast(utf8.find(']')); if(pos < 2) { std::cout << "Warning: ini file wrongly formatted (" << utf8 << "). Line ignored." << std::endl; continue; } section = utf8.substr(1, utf8.find(']')-1); continue; } int comment_pos = static_cast(utf8.find(';')); if(comment_pos > 0) { utf8 = utf8.substr(0,comment_pos); } int pos = static_cast(utf8.find('=')); if(pos < 1) { continue; } std::string key(section + ":" + std::string(utf8.substr(0, pos))); const std::string_view value(utf8.substr(pos+1)); if(value.empty()) { std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl; continue; } auto it = map.find(key); if (it == map.end()) it = map.emplace_hint(it, std::move(key), std::vector()); it->second.push_back(std::string(value)); } return map; } MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::path& filename) { std::cout << "load cfg file: " << filename << std::endl; MwIniImporter::multistrmap map; bfs::ifstream file((bfs::path(filename))); std::string line; while (std::getline(file, line)) { // we cant say comment by only looking at first char anymore int comment_pos = static_cast(line.find('#')); if(comment_pos > 0) { line = line.substr(0,comment_pos); } if(line.empty()) { continue; } int pos = static_cast(line.find('=')); if(pos < 1) { continue; } std::string key(line.substr(0,pos)); std::string value(line.substr(pos+1)); if(map.find(key) == map.end()) { map.insert( std::make_pair (key, std::vector() ) ); } map[key].push_back(value); } return map; } void MwIniImporter::merge(multistrmap &cfg, const multistrmap &ini) const { multistrmap::const_iterator iniIt; for(strmap::const_iterator it=mMergeMap.begin(); it!=mMergeMap.end(); ++it) { if((iniIt = ini.find(it->second)) != ini.end()) { for(std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { cfg.erase(it->first); insertMultistrmap(cfg, it->first, *vc); } } } } void MwIniImporter::mergeFallback(multistrmap &cfg, const multistrmap &ini) const { cfg.erase("fallback"); multistrmap::const_iterator iniIt; for(std::vector::const_iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); ++it) { if((iniIt = ini.find(*it)) != ini.end()) { for(std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { std::string value(*it); std::replace( value.begin(), value.end(), ' ', '_' ); std::replace( value.begin(), value.end(), ':', '_' ); value.append(",").append(vc->substr(0,vc->length())); insertMultistrmap(cfg, "fallback", value); } } } } void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) { const auto it = cfg.find(key); if(it == cfg.end()) { cfg.insert(std::make_pair (key, std::vector() )); } cfg[key].push_back(value); } void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) const { std::vector archives; std::string baseArchive("Archives:Archive "); std::string archive; // Search archives listed in ini file auto it = ini.begin(); for(int i=0; it != ini.end(); i++) { archive = baseArchive; archive.append(std::to_string(i)); it = ini.find(archive); if(it == ini.end()) { break; } for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { archives.push_back(*entry); } } cfg.erase("fallback-archive"); cfg.insert( std::make_pair > ("fallback-archive", std::vector())); // Add Morrowind.bsa by default, since Vanilla loads this archive even if it // does not appears in the ini file cfg["fallback-archive"].push_back("Morrowind.bsa"); for(auto iter=archives.begin(); iter!=archives.end(); ++iter) { cfg["fallback-archive"].push_back(*iter); } } void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result) { auto iter = std::find_if( source.begin(), source.end(), [&element](std::pair< std::string, std::vector >& sourceElement) { return sourceElement.first == element; } ); if (iter != source.end()) { auto foundElement = std::move(*iter); source.erase(iter); for (auto name : foundElement.second) { MwIniImporter::dependencySortStep(name, source, result); } result.push_back(std::move(foundElement.first)); } } std::vector MwIniImporter::dependencySort(MwIniImporter::dependencyList source) { std::vector result; while (!source.empty()) { MwIniImporter::dependencySortStep(source.begin()->first, source, result); } return result; } std::vector::iterator MwIniImporter::findString(std::vector& source, const std::string& string) { return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString) { return Misc::StringUtils::ciEqual(sourceString, string); }); } void MwIniImporter::addPaths(std::vector& output, std::vector input) { for (auto& path : input) { if (path.front() == '"') { // Drop first and last characters - quotation marks path = path.substr(1, path.size() - 2); } output.emplace_back(path); } } void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { std::vector> contentFiles; std::string baseGameFile("Game Files:GameFile"); std::time_t defaultTime = 0; ToUTF8::Utf8Encoder encoder(mEncoding); std::vector dataPaths; if (cfg.count("data")) addPaths(dataPaths, cfg["data"]); if (cfg.count("data-local")) addPaths(dataPaths, cfg["data-local"]); dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); auto it = ini.begin(); for (int i=0; it != ini.end(); i++) { std::string gameFile = baseGameFile; gameFile.append(std::to_string(i)); it = ini.find(gameFile); if(it == ini.end()) break; for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-3)); Misc::StringUtils::lowerCaseInPlace(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { bool found = false; for (auto & dataPath : dataPaths) { boost::filesystem::path path = dataPath / *entry; std::time_t time = lastWriteTime(path, defaultTime); if (time != defaultTime) { contentFiles.emplace_back(time, std::move(path)); found = true; break; } } if (!found) std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl; } } } cfg.erase("content"); cfg.insert( std::make_pair("content", std::vector() ) ); // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); MwIniImporter::dependencyList unsortedFiles; ESM::ESMReader reader; reader.setEncoder(&encoder); for (auto& file : contentFiles) { reader.open(file.second.string()); std::vector dependencies; for (auto& gameFile : reader.getGameFiles()) { dependencies.push_back(gameFile.name); } unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies); reader.close(); } auto sortedFiles = dependencySort(unsortedFiles); // hard-coded dependency Morrowind - Tribunal - Bloodmoon if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) { auto tribunalIter = findString(sortedFiles, "Tribunal.esm"); auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm"); if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end()) { size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter); size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter); if (bloodmoonIndex < tribunalIndex) tribunalIndex++; sortedFiles.insert(bloodmoonIter, *tribunalIter); sortedFiles.erase(sortedFiles.begin() + tribunalIndex); } } for (auto& file : sortedFiles) cfg["content"].push_back(file); } void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) { for(auto entry=it->second.begin(); entry != it->second.end(); ++entry) { out << (it->first) << "=" << (*entry) << std::endl; } } } void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding) { mEncoding = encoding; } std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime) { std::time_t writeTime(defaultTime); if (boost::filesystem::exists(filename)) { boost::filesystem::path resolved = boost::filesystem::canonical(filename); writeTime = boost::filesystem::last_write_time(resolved); // print timestamp const int size=1024; char timeStrBuffer[size]; if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0) std::cout << "content file: " << resolved << " timestamp = (" << writeTime << ") " << timeStrBuffer << std::endl; } return writeTime; } openmw-openmw-0.48.0/apps/mwiniimporter/importer.hpp000066400000000000000000000040451445372753700226610ustar00rootroot00000000000000#ifndef MWINIIMPORTER_IMPORTER #define MWINIIMPORTER_IMPORTER 1 #include #include #include #include #include #include #include class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; typedef std::vector< std::pair< std::string, std::vector > > dependencyList; MwIniImporter(); void setInputEncoding(const ToUTF8::FromType& encoding); void setVerbose(bool verbose); multistrmap loadIniFile(const boost::filesystem::path& filename) const; static multistrmap loadCfgFile(const boost::filesystem::path& filename); void merge(multistrmap &cfg, const multistrmap &ini) const; void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; void importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const; static void writeToFile(std::ostream &out, const multistrmap &cfg); static std::vector dependencySort(MwIniImporter::dependencyList source); private: static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result); static std::vector::iterator findString(std::vector& source, const std::string& string); static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); static void addPaths(std::vector& output, std::vector input); /// \return file's "last modified time", used in original MW to determine plug-in load order static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); bool mVerbose; strmap mMergeMap; std::vector mMergeFallback; ToUTF8::FromType mEncoding; }; #endif openmw-openmw-0.48.0/apps/mwiniimporter/main.cpp000066400000000000000000000113421445372753700217350ustar00rootroot00000000000000#include "importer.hpp" #include #include #include #include namespace bpo = boost::program_options; namespace bfs = boost::filesystem; #ifndef _WIN32 int main(int argc, char *argv[]) { #else // Include on Windows only #include class utf8argv { public: utf8argv(int argc, wchar_t *wargv[]) { args.reserve(argc); argv = new const char *[argc]; for (int i = 0; i < argc; ++i) { args.push_back(boost::locale::conv::utf_to_utf(wargv[i])); argv[i] = args.back().c_str(); } } ~utf8argv() { delete[] argv; } char **get() const { return const_cast(argv); } private: utf8argv(const utf8argv&); utf8argv& operator=(const utf8argv&); const char **argv; std::vector args; }; /* The only way to pass Unicode on Winodws with CLI is to use wide characters interface which presents UTF-16 encoding. The rest of OpenMW application stack assumes UTF-8 encoding, therefore this conversion. For boost::filesystem::path::imbue see components/files/windowspath.cpp */ int wmain(int argc, wchar_t *wargv[]) { utf8argv converter(argc, wargv); char **argv = converter.get(); boost::filesystem::path::imbue(boost::locale::generator().generate("")); #endif try { bpo::options_description desc("Syntax: openmw-iniimporter inifile configfile\nAllowed options"); bpo::positional_options_description p_desc; desc.add_options() ("help,h", "produce help message") ("verbose,v", "verbose output") ("ini,i", bpo::value(), "morrowind.ini file") ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ("game-files,g", "import esm and esp files") ("fonts,f", "import bitmap fonts") ("no-archives,A", "disable bsa archives import") ("encoding,e", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ; p_desc.add("ini", 1).add("cfg", 1); bpo::variables_map vm; bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) .options(desc) .positional(p_desc) .run(); bpo::store(parsed, vm); if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) { std::cout << desc; return 0; } bpo::notify(vm); boost::filesystem::path iniFile(vm["ini"].as()); boost::filesystem::path cfgFile(vm["cfg"].as()); // if no output is given, write back to cfg file std::string outputFile(vm["output"].as()); if(vm["output"].defaulted()) { outputFile = vm["cfg"].as(); } if(!boost::filesystem::exists(iniFile)) { std::cerr << "ini file does not exist" << std::endl; return -3; } if(!boost::filesystem::exists(cfgFile)) std::cerr << "cfg file does not exist" << std::endl; MwIniImporter importer; importer.setVerbose(vm.count("verbose") != 0); // Font encoding settings std::string encoding(vm["encoding"].as()); importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); if (!vm.count("fonts")) { ini.erase("Fonts:Font 0"); ini.erase("Fonts:Font 1"); ini.erase("Fonts:Font 2"); } importer.merge(cfg, ini); importer.mergeFallback(cfg, ini); if(vm.count("game-files")) { importer.importGameFiles(cfg, ini, iniFile); } if(!vm.count("no-archives")) { importer.importArchives(cfg, ini); } std::cout << "write to: " << outputFile << std::endl; bfs::ofstream file((bfs::path(outputFile))); importer.writeToFile(file, cfg); } catch (std::exception& e) { std::cerr << "ERROR: " << e.what() << std::endl; } return 0; } openmw-openmw-0.48.0/apps/navmeshtool/000077500000000000000000000000001445372753700177365ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/navmeshtool/CMakeLists.txt000066400000000000000000000012511445372753700224750ustar00rootroot00000000000000set(NAVMESHTOOL worldspacedata.cpp navmesh.cpp main.cpp ) source_group(apps\\navmeshtool FILES ${NAVMESHTOOL}) openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) target_link_libraries(openmw-navmeshtool ${Boost_PROGRAM_OPTIONS_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions(--coverage) target_link_libraries(openmw-navmeshtool gcov) endif() if (WIN32) install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-navmeshtool PRIVATE ) endif() openmw-openmw-0.48.0/apps/navmeshtool/main.cpp000066400000000000000000000251251445372753700213730ustar00rootroot00000000000000#include "worldspacedata.hpp" #include "navmesh.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #include #endif namespace NavMeshTool { namespace { namespace bpo = boost::program_options; using StringsVector = std::vector; constexpr std::string_view applicationName = "NavMeshTool"; bpo::options_description makeOptionsDescription() { using Fallback::FallbackMap; bpo::options_description result; result.add_options() ("help", "print help message") ("version", "print version information and quit") ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), "set resources directory") ("content", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") ("encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") ->multitoken()->composing(), "fallback values") ("threads", bpo::value()->default_value(std::max(std::thread::hardware_concurrency() - 1, 1)), "number of threads for parallel processing") ("process-interior-cells", bpo::value()->implicit_value(true) ->default_value(false), "build navmesh for interior cells") ("remove-unused-tiles", bpo::value()->implicit_value(true) ->default_value(false), "remove tiles from cache that will not be used with current content profile") ("write-binary-log", bpo::value()->implicit_value(true) ->default_value(false), "write progress in binary messages to be consumed by the launcher") ; Files::ConfigurationManager::addCommonOptions(result); return result; } int runNavMeshTool(int argc, char *argv[]) { Platform::init(); bpo::options_description desc = makeOptionsDescription(); bpo::parsed_options options = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); bpo::variables_map variables; bpo::store(options, variables); bpo::notify(variables); if (variables.find("help") != variables.end()) { getRawStdout() << desc << std::endl; return 0; } Files::ConfigurationManager config; config.readConfiguration(variables, desc); setupLogging(config.getLogPath().string(), applicationName); const std::string encoding(variables["encoding"].as()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); auto local = variables["data-local"].as(); if (!local.empty()) dataDirs.push_back(std::move(local)); config.filterOutNonExistingPaths(dataDirs); const auto fsStrict = variables["fs-strict"].as(); const auto resDir = variables["resources"].as(); Version::Version v = Version::getOpenmwVersion(resDir.string()); Log(Debug::Info) << v.describe(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const auto fileCollections = Files::Collections(dataDirs, !fsStrict); const auto archives = variables["fallback-archive"].as(); const auto contentFiles = variables["content"].as(); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) { std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1"; return -1; } const bool processInteriorCells = variables["process-interior-cells"].as(); const bool removeUnusedTiles = variables["remove-unused-tiles"].as(); const bool writeBinaryLog = variables["write-binary-log"].as(); #ifdef WIN32 if (writeBinaryLog) _setmode(_fileno(stderr), _O_BINARY); #endif Fallback::Map::init(variables["fallback"].as().mMap); VFS::Manager vfs(fsStrict); VFS::registerArchives(&vfs, fileCollections, archives, true); Settings::Manager settings; settings.load(config); const auto agentCollisionShape = DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game")); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); const DetourNavigator::AgentBounds agentBounds {agentCollisionShape, agentHalfExtents}; const std::uint64_t maxDbFileSize = static_cast(Settings::Manager::getInt64("max navmeshdb file size", "Navigator")); const std::string dbPath = (config.getUserDataPath() / "navmesh.db").string(); Log(Debug::Info) << "Using navmeshdb at " << dbPath; DetourNavigator::NavMeshDb db(dbPath, maxDbFileSize); ESM::ReadersCache readers; EsmLoader::Query query; query.mLoadActivators = true; query.mLoadCells = true; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = true; query.mLoadLands = true; query.mLoadStatics = true; const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); Resource::ImageManager imageManager(&vfs); Resource::NifFileManager nifFileManager(&vfs); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager, esmData, processInteriorCells, writeBinaryLog); const Status status = generateAllNavMeshTiles(agentBounds, navigatorSettings, threadsNumber, removeUnusedTiles, writeBinaryLog, cellsData, std::move(db)); switch (status) { case Status::Ok: Log(Debug::Info) << "Done"; break; case Status::Cancelled: Log(Debug::Warning) << "Cancelled"; break; case Status::NotEnoughSpace: Log(Debug::Warning) << "Navmesh generation is cancelled due to running out of disk space or limits " << "for navmesh db. Check disk space at the db location \"" << dbPath << "\". If there is enough space, adjust \"max navmeshdb file size\" setting (see " << "https://openmw.readthedocs.io/en/latest/reference/modding/settings/navigator.html?highlight=navmesh#max-navmeshdb-file-size)."; break; } return 0; } } } int main(int argc, char *argv[]) { return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, NavMeshTool::applicationName); } openmw-openmw-0.48.0/apps/navmeshtool/navmesh.cpp000066400000000000000000000305041445372753700221050ustar00rootroot00000000000000#include "navmesh.hpp" #include "worldspacedata.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace NavMeshTool { namespace { using DetourNavigator::AgentBounds; using DetourNavigator::GenerateNavMeshTile; using DetourNavigator::NavMeshDb; using DetourNavigator::NavMeshTileInfo; using DetourNavigator::PreparedNavMeshData; using DetourNavigator::RecastMeshProvider; using DetourNavigator::MeshSource; using DetourNavigator::Settings; using DetourNavigator::ShapeId; using DetourNavigator::TileId; using DetourNavigator::TilePosition; using DetourNavigator::TileVersion; using DetourNavigator::TilesPositionsRange; using Sqlite3::Transaction; void logGeneratedTiles(std::size_t provided, std::size_t expected) { Log(Debug::Info) << provided << "/" << expected << " (" << (static_cast(provided) / static_cast(expected) * 100) << "%) navmesh tiles are generated"; } template void serializeToStderr(const T& value) { const std::vector data = serialize(value); getLockedRawStderr()->write(reinterpret_cast(data.data()), static_cast(data.size())); } void logGeneratedTilesMessage(std::size_t number) { serializeToStderr(GeneratedTiles {static_cast(number)}); } struct LogGeneratedTiles { void operator()(std::size_t provided, std::size_t expected) const { logGeneratedTiles(provided, expected); } }; class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer { public: std::atomic_size_t mExpected {0}; explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles, bool writeBinaryLog) : mDb(std::move(db)) , mRemoveUnusedTiles(removeUnusedTiles) , mWriteBinaryLog(writeBinaryLog) , mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate)) , mNextTileId(mDb.getMaxTileId() + 1) , mNextShapeId(mDb.getMaxShapeId() + 1) {} std::size_t getProvided() const { return mProvided.load(); } std::size_t getInserted() const { return mInserted.load(); } std::size_t getUpdated() const { return mUpdated.load(); } std::size_t getDeleted() const { const std::lock_guard lock(mMutex); return mDeleted; } std::int64_t resolveMeshSource(const MeshSource& source) override { const std::lock_guard lock(mMutex); return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId); } std::optional find(std::string_view worldspace, const TilePosition &tilePosition, const std::vector &input) override { std::optional result; std::lock_guard lock(mMutex); if (const auto tile = mDb.findTile(worldspace, tilePosition, input)) { NavMeshTileInfo info; info.mTileId = tile->mTileId; info.mVersion = tile->mVersion; result.emplace(info); } return result; } void ignore(std::string_view worldspace, const TilePosition& tilePosition) override { if (mRemoveUnusedTiles) { std::lock_guard lock(mMutex); mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); } report(); } void identity(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId) override { if (mRemoveUnusedTiles) { std::lock_guard lock(mMutex); mDeleted += static_cast(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId})); } report(); } void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) override { { std::lock_guard lock(mMutex); if (mRemoveUnusedTiles) mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); data.mUserId = static_cast(mNextTileId); mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data)); ++mNextTileId; } ++mInserted; report(); } void update(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override { data.mUserId = static_cast(tileId); { std::lock_guard lock(mMutex); if (mRemoveUnusedTiles) mDeleted += static_cast(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId})); mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data)); } ++mUpdated; report(); } void cancel(std::string_view reason) override { std::unique_lock lock(mMutex); if (reason.find("database or disk is full") != std::string_view::npos) mStatus = Status::NotEnoughSpace; else mStatus = Status::Cancelled; mHasTile.notify_one(); } Status wait() { constexpr std::chrono::seconds transactionInterval(1); std::unique_lock lock(mMutex); auto start = std::chrono::steady_clock::now(); while (mProvided < mExpected && mStatus == Status::Ok) { mHasTile.wait(lock); const auto now = std::chrono::steady_clock::now(); if (now - start > transactionInterval) { mTransaction.commit(); mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); start = now; } } logGeneratedTiles(mProvided, mExpected); if (mWriteBinaryLog) logGeneratedTilesMessage(mProvided); return mStatus; } void commit() { const std::lock_guard lock(mMutex); mTransaction.commit(); } void vacuum() { const std::lock_guard lock(mMutex); mDb.vacuum(); } void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) { const std::lock_guard lock(mMutex); mTransaction.commit(); Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"..."; mDeleted += static_cast(mDb.deleteTilesOutsideRange(worldspace, range)); mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); } private: std::atomic_size_t mProvided {0}; std::atomic_size_t mInserted {0}; std::atomic_size_t mUpdated {0}; std::size_t mDeleted = 0; Status mStatus = Status::Ok; mutable std::mutex mMutex; NavMeshDb mDb; const bool mRemoveUnusedTiles; const bool mWriteBinaryLog; Transaction mTransaction; TileId mNextTileId; std::condition_variable mHasTile; Misc::ProgressReporter mReporter; ShapeId mNextShapeId; std::mutex mReportMutex; void report() { const std::size_t provided = mProvided.fetch_add(1, std::memory_order_relaxed) + 1; mReporter(provided, mExpected); mHasTile.notify_one(); if (mWriteBinaryLog) logGeneratedTilesMessage(provided); } }; } Status generateAllNavMeshTiles(const AgentBounds& agentBounds, const Settings& settings, std::size_t threadsNumber, bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& data, NavMeshDb&& db) { Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; SceneUtil::WorkQueue workQueue(threadsNumber); auto navMeshTileConsumer = std::make_shared(std::move(db), removeUnusedTiles, writeBinaryLog); std::size_t tiles = 0; std::mt19937_64 random; for (const std::unique_ptr& input : data.mNavMeshInputs) { const auto range = DetourNavigator::makeTilesPositionsRange( Misc::Convert::toOsgXY(input->mAabb.m_min), Misc::Convert::toOsgXY(input->mAabb.m_max), settings.mRecast ); if (removeUnusedTiles) navMeshTileConsumer->removeTilesOutsideRange(input->mWorldspace, range); std::vector worldspaceTiles; DetourNavigator::getTilesPositions(range, [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }); tiles += worldspaceTiles.size(); if (writeBinaryLog) serializeToStderr(ExpectedTiles {static_cast(tiles)}); navMeshTileConsumer->mExpected = tiles; std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); for (const TilePosition& tilePosition : worldspaceTiles) workQueue.addWorkItem(new GenerateNavMeshTile( input->mWorldspace, tilePosition, RecastMeshProvider(input->mTileCachedRecastMeshManager), agentBounds, settings, navMeshTileConsumer )); } const Status status = navMeshTileConsumer->wait(); if (status == Status::Ok) navMeshTileConsumer->commit(); const auto inserted = navMeshTileConsumer->getInserted(); const auto updated = navMeshTileConsumer->getUpdated(); const auto deleted = navMeshTileConsumer->getDeleted(); Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " << inserted << " are inserted, " << updated << " updated and " << deleted << " deleted"; if (inserted + updated + deleted > 0) { Log(Debug::Info) << "Vacuuming the database..."; navMeshTileConsumer->vacuum(); } return status; } } openmw-openmw-0.48.0/apps/navmeshtool/navmesh.hpp000066400000000000000000000012031445372753700221040ustar00rootroot00000000000000#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H #define OPENMW_NAVMESHTOOL_NAVMESH_H #include #include namespace DetourNavigator { class NavMeshDb; struct Settings; struct AgentBounds; } namespace NavMeshTool { struct WorldspaceData; enum class Status { Ok, Cancelled, NotEnoughSpace, }; Status generateAllNavMeshTiles(const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Settings& settings, std::size_t threadsNumber, bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); } #endif openmw-openmw-0.48.0/apps/navmeshtool/worldspacedata.cpp000066400000000000000000000412721445372753700234450ustar00rootroot00000000000000#include "worldspacedata.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace NavMeshTool { namespace { using DetourNavigator::CollisionShape; using DetourNavigator::HeightfieldPlane; using DetourNavigator::HeightfieldShape; using DetourNavigator::HeightfieldSurface; using DetourNavigator::ObjectId; using DetourNavigator::ObjectTransform; struct CellRef { ESM::RecNameInts mType; ESM::RefNum mRefNum; std::string mRefId; float mScale; ESM::Position mPos; CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos) : mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {} }; ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId) { const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), refId, EsmLoader::LessById {}); if (it == esmData.mRefIdTypes.end() || it->mId != refId) return {}; return it->mType; } std::vector loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, ESM::ReadersCache& readers) { std::vector> cellRefs; for (std::size_t i = 0; i < cell.mContextList.size(); i++) { ESM::ReadersCache::BusyItem reader = readers.get(static_cast(cell.mContextList[i].index)); cell.restore(*reader, static_cast(i)); ESM::CellRef cellRef; bool deleted = false; while (ESM::Cell::getNextRef(*reader, cellRef, deleted)) { Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); if (type == ESM::RecNameInts {}) continue; cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), cellRef.mScale, cellRef.mPos); } } Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; const auto getKey = [] (const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; std::vector result = prepareRecords(cellRefs, getKey); Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; return result; } template void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, ESM::ReadersCache& readers, F&& f) { std::vector cellRefs = loadCellRefs(cell, esmData, readers); Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; for (CellRef& cellRef : cellRefs) { std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); if (model.empty()) continue; if (cellRef.mType != ESM::REC_STAT) model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); osg::ref_ptr shape = [&] { try { return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model, &vfs)); } catch (const std::exception& e) { Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what(); return osg::ref_ptr(); } } (); if (shape == nullptr || shape->mCollisionShape == nullptr) continue; osg::ref_ptr shapeInstance(new Resource::BulletShapeInstance(std::move(shape))); switch (cellRef.mType) { case ESM::REC_ACTI: case ESM::REC_CONT: case ESM::REC_DOOR: case ESM::REC_STAT: f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale)); break; default: break; } } } struct GetXY { osg::Vec2i operator()(const ESM::Land& value) const { return osg::Vec2i(value.mX, value.mY); } }; struct LessByXY { bool operator ()(const ESM::Land& lhs, const ESM::Land& rhs) const { return GetXY {}(lhs) < GetXY {}(rhs); } bool operator ()(const ESM::Land& lhs, const osg::Vec2i& rhs) const { return GetXY {}(lhs) < rhs; } bool operator ()(const osg::Vec2i& lhs, const ESM::Land& rhs) const { return lhs < GetXY {}(rhs); } }; btAABB getAabb(const osg::Vec2i& cellPosition, btScalar minHeight, btScalar maxHeight) { btAABB aabb; aabb.m_min = btVector3( static_cast(cellPosition.x() * ESM::Land::REAL_SIZE), static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), minHeight ); aabb.m_max = btVector3( static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), maxHeight ); return aabb; } void mergeOrAssign(const btAABB& aabb, btAABB& target, bool& initialized) { if (initialized) return target.merge(aabb); target.m_min = aabb.m_min; target.m_max = aabb.m_max; initialized = true; } std::tuple makeHeightfieldShape(const std::optional& land, const osg::Vec2i& cellPosition, std::vector>& heightfields, std::vector>& landDatas) { 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}; ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique()); land->loadData(ESM::Land::DATA_VHGT, &landData); heightfields.push_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); HeightfieldSurface surface; surface.mHeights = heightfields.back().data(); surface.mMinHeight = landData.mMinHeight; surface.mMaxHeight = landData.mMaxHeight; surface.mSize = static_cast(ESM::Land::LAND_SIZE); return {surface, landData.mMinHeight, landData.mMaxHeight}; } template void serializeToStderr(const T& value) { const std::vector data = serialize(value); getRawStderr().write(reinterpret_cast(data.data()), static_cast(data.size())); } std::string toHex(std::string_view value) { std::string buffer(value.size() * 2, '0'); char* out = buffer.data(); for (const char v : value) { const std::ptrdiff_t space = static_cast(static_cast(v) <= 0xf); const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast(v), 16); if (ec != std::errc()) throw std::system_error(std::make_error_code(ec)); out += 2; } return buffer; } std::string makeAddObjectErrorMessage(ObjectId objectId, DetourNavigator::AreaType areaType, const CollisionShape& shape) { std::ostringstream stream; stream << "Failed to add object to recast mesh objectId=" << objectId.value() << " areaType=" << areaType << " fileName=" << shape.getInstance()->getSource()->mFileName << " fileHash=" << toHex(shape.getInstance()->getSource()->mFileHash); return stream.str(); } } WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings) : mWorldspace(std::move(worldspace)) , mTileCachedRecastMeshManager(settings) { mAabb.m_min = btVector3(0, 0, 0); mAabb.m_max = btVector3(0, 0, 0); } WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, bool processInteriorCells, bool writeBinaryLog) { Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; std::map> navMeshInputs; WorldspaceData data; std::size_t objectsCounter = 0; if (writeBinaryLog) serializeToStderr(ExpectedCells {static_cast(esmData.mCells.size())}); for (std::size_t i = 0; i < esmData.mCells.size(); ++i) { const ESM::Cell& cell = esmData.mCells[i]; const bool exterior = cell.isExterior(); if (!exterior && !processInteriorCells) { if (writeBinaryLog) serializeToStderr(ProcessedCells {static_cast(i + 1)}); Log(Debug::Info) << "Skipped interior" << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; continue; } Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY); const std::size_t cellObjectsBegin = data.mObjects.size(); WorldspaceNavMeshInput& navMeshInput = [&] () -> WorldspaceNavMeshInput& { auto it = navMeshInputs.find(cell.mCellId.mWorldspace); if (it == navMeshInputs.end()) { it = navMeshInputs.emplace(cell.mCellId.mWorldspace, std::make_unique(cell.mCellId.mWorldspace, settings.mRecast)).first; it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace); } return *it->second; } (); if (exterior) { const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {}); const auto [heightfieldShape, minHeight, maxHeight] = makeHeightfieldShape( it == esmData.mLands.end() ? std::optional() : *it, cellPosition, data.mHeightfields, data.mLandData ); mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight), navMeshInput.mAabb, navMeshInput.mAabbInitialized); navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape); navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1); } else { if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0) navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits::max(), cell.mWater); } forEachObject(cell, esmData, vfs, bulletShapeManager, readers, [&] (BulletObject object) { if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None) return; const btTransform& transform = object.getCollisionObject().getWorldTransform(); const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform); mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized); if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) navMeshInput.mAabb.merge(BulletHelpers::getAabb(*avoid, transform)); const ObjectId objectId(++objectsCounter); const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); if (!navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground, [] (const auto&) {})) throw std::logic_error(makeAddObjectErrorMessage(objectId, DetourNavigator::AreaType_ground, shape)); if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) { const ObjectId avoidObjectId(++objectsCounter); const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); if (!navMeshInput.mTileCachedRecastMeshManager.addObject(avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, [] (const auto&) {})) throw std::logic_error(makeAddObjectErrorMessage(avoidObjectId, DetourNavigator::AreaType_null, avoidShape)); } data.mObjects.emplace_back(std::move(object)); }); const auto cellDescription = cell.getDescription(); if (writeBinaryLog) serializeToStderr(ProcessedCells {static_cast(i + 1)}); Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cellDescription << " with " << (data.mObjects.size() - cellObjectsBegin) << " objects"; } data.mNavMeshInputs.reserve(navMeshInputs.size()); std::transform(navMeshInputs.begin(), navMeshInputs.end(), std::back_inserter(data.mNavMeshInputs), [] (auto& v) { return std::move(v.second); }); Log(Debug::Info) << "Processed " << esmData.mCells.size() << " cells, added " << data.mObjects.size() << " objects and " << data.mHeightfields.size() << " height fields"; return data; } } openmw-openmw-0.48.0/apps/navmeshtool/worldspacedata.hpp000066400000000000000000000057011445372753700234470ustar00rootroot00000000000000#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H #define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H #include #include #include #include #include #include #include #include #include #include namespace ESM { class ESMReader; class ReadersCache; } namespace VFS { class Manager; } namespace Resource { class BulletShapeManager; } namespace EsmLoader { struct EsmData; } namespace DetourNavigator { struct Settings; } namespace NavMeshTool { using DetourNavigator::TileCachedRecastMeshManager; using DetourNavigator::ObjectTransform; struct WorldspaceNavMeshInput { std::string mWorldspace; TileCachedRecastMeshManager mTileCachedRecastMeshManager; btAABB mAabb; bool mAabbInitialized = false; explicit WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings); }; class BulletObject { public: BulletObject(osg::ref_ptr&& shapeInstance, const ESM::Position& position, float localScaling) : mShapeInstance(std::move(shapeInstance)) , mObjectTransform {position, localScaling} , mCollisionObject(BulletHelpers::makeCollisionObject( mShapeInstance->mCollisionShape.get(), Misc::Convert::toBullet(position.asVec3()), Misc::Convert::toBullet(Misc::Convert::makeOsgQuat(position)) )) { mShapeInstance->setLocalScaling(btVector3(localScaling, localScaling, localScaling)); } const osg::ref_ptr& getShapeInstance() const noexcept { return mShapeInstance; } const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; } btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; } private: osg::ref_ptr mShapeInstance; DetourNavigator::ObjectTransform mObjectTransform; std::unique_ptr mCollisionObject; }; struct WorldspaceData { std::vector> mNavMeshInputs; std::vector mObjects; std::vector> mLandData; std::vector> mHeightfields; }; WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, bool processInteriorCells, bool writeBinaryLog); } #endif openmw-openmw-0.48.0/apps/niftest/000077500000000000000000000000001445372753700170535ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/niftest/CMakeLists.txt000066400000000000000000000007121445372753700216130ustar00rootroot00000000000000set(NIFTEST niftest.cpp ) source_group(components\\nif\\tests FILES ${NIFTEST}) # Main executable openmw_add_executable(niftest ${NIFTEST} ) target_link_libraries(niftest ${Boost_FILESYSTEM_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(niftest gcov) endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(niftest PRIVATE ) endif() openmw-openmw-0.48.0/apps/niftest/niftest.cpp000066400000000000000000000117101445372753700212330ustar00rootroot00000000000000///Program to test .nif files both on the FileSystem and in BSA archives. #include #include #include #include #include #include #include #include #include // Create local aliases for brevity namespace bpo = boost::program_options; namespace bfs = boost::filesystem; ///See if the file has the named extension bool hasExtension(std::string filename, std::string extensionToFind) { std::string extension = filename.substr(filename.find_last_of('.')+1); return Misc::StringUtils::ciEqual(extension, extensionToFind); } ///See if the file has the "nif" extension. bool isNIF(const std::string & filename) { return hasExtension(filename,"nif"); } ///See if the file has the "bsa" extension. bool isBSA(const std::string & filename) { return hasExtension(filename,"bsa"); } /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. void readVFS(std::unique_ptr&& anArchive, std::string archivePath = "") { VFS::Manager myManager(true); myManager.addArchive(std::move(anArchive)); myManager.buildIndex(); for(const auto& name : myManager.getRecursiveDirectoryIterator("")) { try{ if(isNIF(name)) { // std::cout << "Decoding: " << name << std::endl; Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); } else if(isBSA(name)) { if(!archivePath.empty() && !isBSA(archivePath)) { // std::cout << "Reading BSA File: " << name << std::endl; readVFS(std::make_unique(archivePath + name), archivePath + name + "/"); // std::cout << "Done with BSA File: " << name << std::endl; } } } catch (std::exception& e) { std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; } } } bool parseOptions (int argc, char** argv, std::vector& files) { bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" "Usages:\n" " niftool \n" " Scan the file or directories for nif errors.\n\n" "Allowed options"); desc.add_options() ("help,h", "print help message.") ("input-file", bpo::value< std::vector >(), "input file") ; //Default option if none provided bpo::positional_options_description p; p.add("input-file", -1); bpo::variables_map variables; try { bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). options(desc).positional(p).run(); bpo::store(valid_opts, variables); bpo::notify(variables); if (variables.count ("help")) { std::cout << desc << std::endl; return false; } if (variables.count("input-file")) { files = variables["input-file"].as< std::vector >(); return true; } } catch(std::exception &e) { std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } std::cout << "No input files or directories specified!" << std::endl; std::cout << desc << std::endl; return false; } int main(int argc, char **argv) { std::vector files; if(!parseOptions (argc, argv, files)) return 1; Nif::NIFFile::setLoadUnsupportedFiles(true); // std::cout << "Reading Files" << std::endl; for(auto it=files.begin(); it!=files.end(); ++it) { std::string name = *it; try { if(isNIF(name)) { //std::cout << "Decoding: " << name << std::endl; Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name), name); } else if(isBSA(name)) { // std::cout << "Reading BSA File: " << name << std::endl; readVFS(std::make_unique(name)); } else if(bfs::is_directory(bfs::path(name))) { // std::cout << "Reading All Files in: " << name << std::endl; readVFS(std::make_unique(name), name); } else { std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; } } catch (std::exception& e) { std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; } } return 0; } openmw-openmw-0.48.0/apps/opencs/000077500000000000000000000000001445372753700166665ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/CMakeLists.txt000066400000000000000000000207401445372753700214310ustar00rootroot00000000000000set (OPENCS_SRC main.cpp ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc ) opencs_units (. editor) opencs_units (model/doc document operation saving documentmanager loader runner operationholder ) opencs_units (model/doc stage savingstate savingstages blacklist messages ) opencs_hdrs (model/doc state ) opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel actoradapter idcollection ) opencs_units (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) opencs_hdrs (model/world columnimp idcollection collection info subcellcollection ) opencs_units (model/tools tools reportmodel mergeoperation ) opencs_units (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) opencs_hdrs (model/tools mergestate ) opencs_units (view/doc viewmanager view operations operation subview startup filedialog newgame filewidget adjusterwidget loader globaldebugprofilemenu runlogsubview sizehint ) opencs_units (view/doc subviewfactory ) opencs_hdrs (view/doc subviewfactoryimp ) opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator globalcreator cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler ) opencs_units (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate colordelegate dragdroputils ) opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton scenetooltoggle2 scenetooltexturebrush scenetoolshapebrush completerpopup coloreditor colorpickerpopup droplineedit ) 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 ) opencs_units (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) opencs_hdrs (view/render mask ) opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) opencs_units (view/tools subviews ) opencs_units (view/prefs dialogue pagebase page keybindingpage contextmenulist ) opencs_units (model/prefs state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting ) opencs_units (model/prefs category ) opencs_units (model/filter node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) opencs_units (view/filter filterbox recordfilterbox editwidget ) set (OPENCS_US ) set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc ) set (OPENCS_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui ) source_group (openmw-cs FILES ${OPENCS_SRC} ${OPENCS_HDR}) if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(APPLE) set (OPENCS_MAC_ICON "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns") set (OPENCS_CFG "${OpenMW_BINARY_DIR}/defaults-cs.bin") set (OPENCS_DEFAULT_FILTERS_FILE "${OpenMW_BINARY_DIR}/resources/defaultfilters") set (OPENCS_OPENMW_CFG "${OpenMW_BINARY_DIR}/openmw.cfg") else() set (OPENCS_MAC_ICON "") set (OPENCS_CFG "") set (OPENCS_DEFAULT_FILTERS_FILE "") set (OPENCS_OPENMW_CFG "") endif(APPLE) openmw_add_executable(openmw-cs MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} ${OPENCS_MAC_ICON} ${OPENCS_CFG} ${OPENCS_DEFAULT_FILTERS_FILE} ${OPENCS_OPENMW_CFG} ) if(APPLE) set(OPENCS_BUNDLE_NAME "OpenMW-CS") set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") set(OPENMW_RESOURCES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) set_target_properties(openmw-cs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" OUTPUT_NAME ${OPENCS_BUNDLE_NAME} MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" MACOSX_BUNDLE_BUNDLE_NAME "OpenMW-CS" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs-Info.plist.in" ) set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) set_source_files_properties(${OPENCS_CFG} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) set_source_files_properties(${OPENCS_DEFAULT_FILTERS_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/resources) set_source_files_properties(${OPENCS_OPENMW_CFG} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) add_custom_command(TARGET openmw-cs POST_BUILD COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${OPENCS_BUNDLE_RESOURCES_DIR}/resources") endif(APPLE) target_link_libraries(openmw-cs # CMake's built-in OSG finder does not use pkgconfig, so we have to # manually ensure the order is correct for inter-library dependencies. # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGVIEWER_LIBRARIES} ${OSGFX_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSG_LIBRARIES} ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} components_qt ) target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") get_generator_is_multi_config(multi_config) if (multi_config) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$") else () SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION ".") endif() if (MSVC) # Debug version needs increased number of sections beyond 2^16 if (CMAKE_CL_64) set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") endif (CMAKE_CL_64) endif (MSVC) if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) endif() if(USE_QT) set_property(TARGET openmw-cs PROPERTY AUTOMOC ON) endif(USE_QT) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw-cs PRIVATE ) endif() openmw-openmw-0.48.0/apps/opencs/editor.cpp000066400000000000000000000332201445372753700206600ustar00rootroot00000000000000#include "editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "model/doc/document.hpp" #include "model/world/data.hpp" #ifdef _WIN32 #include #endif using namespace Fallback; CS::Editor::Editor (int argc, char **argv) : mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mPid(""), mLock(), mMerge (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) { std::pair > config = readConfig(); mViewManager = new CSVDoc::ViewManager(mDocumentManager); if (argc > 1) { mFileToLoad = argv[1]; mDataDirs = config.first; } NifOsg::Loader::setShowMarkers(true); mDocumentManager.setFileData(mFsStrict, config.first, config.second); mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); mMerge.setLocalData (mLocal); connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), this, SLOT (documentAdded (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), this, SLOT (lastDocumentDeleted())); connect (mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); connect (mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)), this, SLOT(openFiles (const boost::filesystem::path&))); connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)), this, SLOT(createNewFile (const boost::filesystem::path&))); connect (&mFileDialog, SIGNAL (rejected()), this, SLOT (cancelFileDialog ())); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), this, SLOT (createNewGame (const boost::filesystem::path&))); connect (&mNewGame, SIGNAL (cancelCreateGame()), this, SLOT (cancelCreateGame ())); } CS::Editor::~Editor () { delete mViewManager; mPidFile.close(); if(mServer && boost::filesystem::exists(mPid)) static_cast ( // silence coverity warning remove(mPid.string().c_str())); // ignore any error } boost::program_options::variables_map CS::Editor::readConfiguration() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); desc.add_options() ("data", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing()) ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) ("resources", boost::program_options::value()->default_value(Files::MaybeQuotedPath(), "resources")) ("fallback-archive", boost::program_options::value>()-> default_value(std::vector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") ("script-blacklist", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); mCfgMgr.readConfiguration(variables, desc, false); Settings::Manager::load(mCfgMgr, true); setupLogging(mCfgMgr.getLogPath().string(), applicationName); return variables; } std::pair > CS::Editor::readConfig(bool quiet) { boost::program_options::variables_map& variables = mConfigVariables; Fallback::Map::init(variables["fallback"].as().mMap); mEncodingName = variables["encoding"].as(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); mDocumentManager.setResourceDir (mResources = variables["resources"].as()); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( variables["script-blacklist"].as>()); mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = asPathContainer(variables["data"].as()); } Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) { boost::filesystem::create_directories(local); dataLocal.push_back(local); } mCfgMgr.filterOutNonExistingPaths(dataDirs); mCfgMgr.filterOutNonExistingPaths(dataLocal); if (!dataLocal.empty()) mLocal = dataLocal[0]; else { QMessageBox messageBox; messageBox.setWindowTitle (tr ("No local data path available")); messageBox.setIcon (QMessageBox::Critical); messageBox.setStandardButtons (QMessageBox::Ok); messageBox.setText(tr("
OpenCS is unable to access the local data directory. This may indicate a faulty configuration or a broken install.")); messageBox.exec(); QApplication::exit (1); } dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); //iterate the data directories and add them to the file dialog for loading mFileDialog.addFiles(dataDirs); return std::make_pair (dataDirs, variables["fallback-archive"].as>()); } void CS::Editor::createGame() { mStartup.hide(); if (mNewGame.isHidden()) mNewGame.show(); mNewGame.raise(); mNewGame.activateWindow(); } void CS::Editor::cancelCreateGame() { if (!mDocumentManager.isEmpty()) return; mNewGame.hide(); if (mStartup.isHidden()) mStartup.show(); mStartup.raise(); mStartup.activateWindow(); } void CS::Editor::createAddon() { mStartup.hide(); mFileDialog.clearFiles(); readConfig(/*quiet*/true); mFileDialog.showDialog (CSVDoc::ContentAction_New); } void CS::Editor::cancelFileDialog() { if (!mDocumentManager.isEmpty()) return; mFileDialog.hide(); if (mStartup.isHidden()) mStartup.show(); mStartup.raise(); mStartup.activateWindow(); } void CS::Editor::loadDocument() { mStartup.hide(); mFileDialog.clearFiles(); readConfig(/*quiet*/true); mFileDialog.showDialog (CSVDoc::ContentAction_Edit); } void CS::Editor::openFiles (const boost::filesystem::path &savePath, const std::vector &discoveredFiles) { std::vector files; if(discoveredFiles.empty()) { for (const QString &path : mFileDialog.selectedFilePaths()) files.emplace_back(path.toUtf8().constData()); } else { files = discoveredFiles; } mDocumentManager.addDocument (files, savePath, false); mFileDialog.hide(); } void CS::Editor::createNewFile (const boost::filesystem::path &savePath) { std::vector files; for (const QString &path : mFileDialog.selectedFilePaths()) { files.emplace_back(path.toUtf8().constData()); } files.push_back (savePath); mDocumentManager.addDocument (files, savePath, true); mFileDialog.hide(); } void CS::Editor::createNewGame (const boost::filesystem::path& file) { std::vector files; files.push_back (file); mDocumentManager.addDocument (files, file, true); mNewGame.hide(); } void CS::Editor::showStartup() { if(mStartup.isHidden()) mStartup.show(); mStartup.raise(); mStartup.activateWindow(); } void CS::Editor::showSettings() { if (mSettings.isHidden()) mSettings.show(); mSettings.move (QCursor::pos()); mSettings.raise(); mSettings.activateWindow(); } bool CS::Editor::makeIPCServer() { try { mPid = boost::filesystem::temp_directory_path(); mPid /= "openmw-cs.pid"; bool pidExists = boost::filesystem::exists(mPid); mPidFile.open(mPid); mLock = boost::interprocess::file_lock(mPid.string().c_str()); if(!mLock.try_lock()) { Log(Debug::Error) << "Error: OpenMW-CS is already running."; return false; } #ifdef _WIN32 mPidFile << GetCurrentProcessId() << std::endl; #else mPidFile << getpid() << std::endl; #endif mServer = new QLocalServer(this); if(pidExists) { // hack to get the temp directory path mServer->listen("dummy"); QString fullPath = mServer->fullServerName(); mServer->close(); fullPath.remove(QRegExp("dummy$")); fullPath += mIpcServerName; if(boost::filesystem::exists(fullPath.toUtf8().constData())) { // TODO: compare pid of the current process with that in the file Log(Debug::Info) << "Detected unclean shutdown."; // delete the stale file if(remove(fullPath.toUtf8().constData())) Log(Debug::Error) << "Error: can not remove stale connection file."; } } } catch(const std::exception& e) { Log(Debug::Error) << "Error: " << e.what(); return false; } if(mServer->listen(mIpcServerName)) { connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup())); return true; } mServer->close(); mServer = nullptr; return false; } void CS::Editor::connectToIPCServer() { mClientSocket = new QLocalSocket(this); mClientSocket->connectToServer(mIpcServerName); mClientSocket->close(); } int CS::Editor::run() { if (mLocal.empty()) return 1; Misc::Rng::init(); QApplication::setQuitOnLastWindowClosed(true); if (mFileToLoad.empty()) { mStartup.show(); } else { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName)); fileReader.setEncoder(&encoder); fileReader.open(mFileToLoad.string()); std::vector discoveredFiles; for (std::vector::const_iterator itemIter = fileReader.getGameFiles().begin(); itemIter != fileReader.getGameFiles().end(); ++itemIter) { for (Files::PathContainer::const_iterator pathIter = mDataDirs.begin(); pathIter != mDataDirs.end(); ++pathIter) { const boost::filesystem::path masterPath = *pathIter / itemIter->name; if (boost::filesystem::exists(masterPath)) { discoveredFiles.push_back(masterPath); break; } } } discoveredFiles.push_back(mFileToLoad); QString extension = QString::fromStdString(mFileToLoad.extension().string()).toLower(); if (extension == ".esm") { mFileToLoad.replace_extension(".omwgame"); mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false); } else if (extension == ".esp") { mFileToLoad.replace_extension(".omwaddon"); mDocumentManager.addDocument(discoveredFiles, mFileToLoad, false); } else { openFiles(mFileToLoad, discoveredFiles); } } return QApplication::exec(); } void CS::Editor::documentAdded (CSMDoc::Document *document) { mViewManager->addView (document); } void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) { if (mMerge.getDocument()==document) mMerge.cancel(); } void CS::Editor::lastDocumentDeleted() { QApplication::quit(); } void CS::Editor::mergeDocument (CSMDoc::Document *document) { mMerge.configure (document); mMerge.show(); mMerge.raise(); mMerge.activateWindow(); } openmw-openmw-0.48.0/apps/opencs/editor.hpp000066400000000000000000000064701445372753700206740ustar00rootroot00000000000000#ifndef CS_EDITOR_H #define CS_EDITOR_H #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include "model/doc/documentmanager.hpp" #include "model/prefs/state.hpp" #include "view/doc/viewmanager.hpp" #include "view/doc/startup.hpp" #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" #include "view/prefs/dialogue.hpp" #include "view/tools/merge.hpp" namespace CSMDoc { class Document; } namespace CS { inline constexpr std::string_view applicationName = "OpenMW-CS"; class Editor : public QObject { Q_OBJECT Files::ConfigurationManager mCfgMgr; boost::program_options::variables_map mConfigVariables; CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; CSVPrefs::Dialogue mSettings; CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; boost::filesystem::path mPid; boost::interprocess::file_lock mLock; boost::filesystem::ofstream mPidFile; bool mFsStrict; CSVTools::Merge mMerge; CSVDoc::ViewManager* mViewManager; boost::filesystem::path mFileToLoad; Files::PathContainer mDataDirs; std::string mEncodingName; boost::program_options::variables_map readConfiguration(); ///< Calls mCfgMgr.readConfiguration; should be used before initialization of mSettingsState as it depends on the configuration. std::pair > readConfig(bool quiet=false); ///< \return data paths // not implemented Editor (const Editor&); Editor& operator= (const Editor&); public: Editor (int argc, char **argv); ~Editor (); bool makeIPCServer(); void connectToIPCServer(); int run(); ///< \return error status private slots: void createGame(); void createAddon(); void cancelCreateGame(); void cancelFileDialog(); void loadDocument(); void openFiles (const boost::filesystem::path &path, const std::vector &discoveredFiles = std::vector()); void createNewFile (const boost::filesystem::path& path); void createNewGame (const boost::filesystem::path& file); void showStartup(); void showSettings(); void documentAdded (CSMDoc::Document *document); void documentAboutToBeRemoved (CSMDoc::Document *document); void lastDocumentDeleted(); void mergeDocument (CSMDoc::Document *document); private: QString mIpcServerName; QLocalServer *mServer; QLocalSocket *mClientSocket; }; } #endif openmw-openmw-0.48.0/apps/opencs/main.cpp000066400000000000000000000034061445372753700203210ustar00rootroot00000000000000#include "editor.hpp" #include #include #include #include #include #include #include "model/doc/messages.hpp" #include "model/world/universalid.hpp" #ifdef Q_OS_MAC #include #endif Q_DECLARE_METATYPE (std::string) class Application : public QApplication { private: bool notify (QObject *receiver, QEvent *event) override { try { return QApplication::notify (receiver, event); } catch (const std::exception& exception) { Log(Debug::Error) << "An exception has been caught: " << exception.what(); } return false; } public: Application (int& argc, char *argv[]) : QApplication (argc, argv) {} }; int runApplication(int argc, char *argv[]) { Platform::init(); #ifdef Q_OS_MAC setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif Q_INIT_RESOURCE (resources); qRegisterMetaType ("std::string"); qRegisterMetaType ("CSMWorld::UniversalId"); qRegisterMetaType ("CSMDoc::Message"); Application application (argc, argv); #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); QDir::setCurrent(dir.absolutePath()); #endif application.setWindowIcon (QIcon (":./openmw-cs.png")); CS::Editor editor(argc, argv); #ifdef __linux__ setlocale(LC_NUMERIC,"C"); #endif if(!editor.makeIPCServer()) { editor.connectToIPCServer(); return 0; } return editor.run(); } int main(int argc, char *argv[]) { return wrapApplication(&runApplication, argc, argv, CS::applicationName); } openmw-openmw-0.48.0/apps/opencs/model/000077500000000000000000000000001445372753700177665ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/doc/000077500000000000000000000000001445372753700205335ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/doc/blacklist.cpp000066400000000000000000000015041445372753700232070ustar00rootroot00000000000000#include "blacklist.hpp" #include #include bool CSMDoc::Blacklist::isBlacklisted (const CSMWorld::UniversalId& id) const { std::map >::const_iterator iter = mIds.find (id.getType()); if (iter==mIds.end()) return false; return std::binary_search (iter->second.begin(), iter->second.end(), Misc::StringUtils::lowerCase (id.getId())); } void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type, const std::vector& ids) { std::vector& list = mIds[type]; size_t size = list.size(); list.resize (size+ids.size()); std::transform (ids.begin(), ids.end(), list.begin()+size, Misc::StringUtils::lowerCase); std::sort (list.begin(), list.end()); } openmw-openmw-0.48.0/apps/opencs/model/doc/blacklist.hpp000066400000000000000000000010231445372753700232100ustar00rootroot00000000000000#ifndef CSM_DOC_BLACKLIST_H #define CSM_DOC_BLACKLIST_H #include #include #include #include "../world/universalid.hpp" namespace CSMDoc { /// \brief ID blacklist sorted by UniversalId type class Blacklist { std::map > mIds; public: bool isBlacklisted (const CSMWorld::UniversalId& id) const; void add (CSMWorld::UniversalId::Type type, const std::vector& ids); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/document.cpp000066400000000000000000000335131445372753700230620ustar00rootroot00000000000000#include "document.hpp" #include #include #include #include #include "../world/defaultgmsts.hpp" #ifndef Q_MOC_RUN #include #endif #include void CSMDoc::Document::addGmsts() { for (size_t i=0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::Floats[i]; gmst.mValue.setType (ESM::VT_Float); gmst.mRecordFlags = 0; gmst.mValue.setFloat (CSMWorld::DefaultGmsts::FloatsDefaultValues[i]); getData().getGmsts().add (gmst); } for (size_t i=0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::Ints[i]; gmst.mValue.setType (ESM::VT_Int); gmst.mRecordFlags = 0; gmst.mValue.setInteger (CSMWorld::DefaultGmsts::IntsDefaultValues[i]); getData().getGmsts().add (gmst); } for (size_t i=0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::Strings[i]; gmst.mValue.setType (ESM::VT_String); gmst.mRecordFlags = 0; gmst.mValue.setString (""); getData().getGmsts().add (gmst); } } void CSMDoc::Document::addOptionalGmsts() { for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalFloatCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::OptionalFloats[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Float); addOptionalGmst (gmst); } for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalIntCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::OptionalInts[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_Int); addOptionalGmst (gmst); } for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalStringCount; ++i) { ESM::GameSetting gmst; gmst.mId = CSMWorld::DefaultGmsts::OptionalStrings[i]; gmst.blank(); gmst.mValue.setType (ESM::VT_String); gmst.mValue.setString (""); addOptionalGmst (gmst); } } void CSMDoc::Document::addOptionalGlobals() { static const char *sGlobals[] = { "DaysPassed", "PCWerewolf", "PCYear", 0 }; for (int i=0; sGlobals[i]; ++i) { ESM::Global global; global.mId = sGlobals[i]; global.blank(); global.mValue.setType (ESM::VT_Long); if (i==0) global.mValue.setInteger (1); // dayspassed starts counting at 1 addOptionalGlobal (global); } } void CSMDoc::Document::addOptionalMagicEffects() { for (int i=ESM::MagicEffect::SummonFabricant; i<=ESM::MagicEffect::SummonCreature05; ++i) { ESM::MagicEffect effect; effect.mIndex = i; effect.mId = ESM::MagicEffect::indexToId (i); effect.blank(); addOptionalMagicEffect (effect); } } void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst) { if (getData().getGmsts().searchId (gmst.mId)==-1) { auto record = std::make_unique>(); record->mBase = gmst; record->mState = CSMWorld::RecordBase::State_BaseOnly; getData().getGmsts().appendRecord (std::move(record)); } } void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global) { if (getData().getGlobals().searchId (global.mId)==-1) { auto record = std::make_unique>(); record->mBase = global; record->mState = CSMWorld::RecordBase::State_BaseOnly; getData().getGlobals().appendRecord (std::move(record)); } } void CSMDoc::Document::addOptionalMagicEffect (const ESM::MagicEffect& magicEffect) { if (getData().getMagicEffects().searchId (magicEffect.mId)==-1) { auto record = std::make_unique>(); record->mBase = magicEffect; record->mState = CSMWorld::RecordBase::State_BaseOnly; getData().getMagicEffects().appendRecord (std::move(record)); } } void CSMDoc::Document::createBase() { static const char *sGlobals[] = { "Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0 }; for (int i=0; sGlobals[i]; ++i) { ESM::Global record; record.mId = sGlobals[i]; record.mRecordFlags = 0; record.mValue.setType (i==2 ? ESM::VT_Float : ESM::VT_Long); if (i==0 || i==1) record.mValue.setInteger (1); getData().getGlobals().add (record); } addGmsts(); for (int i=0; i<27; ++i) { ESM::Skill record; record.mIndex = i; record.mId = ESM::Skill::indexToId (record.mIndex); record.blank(); getData().getSkills().add (record); } static const char *sVoice[] = { "Intruder", "Attack", "Hello", "Thief", "Alarm", "Idle", "Flee", "Hit", 0 }; for (int i=0; sVoice[i]; ++i) { ESM::Dialogue record; record.mId = sVoice[i]; record.mType = ESM::Dialogue::Voice; record.blank(); getData().getTopics().add (record); } static const char *sGreetings[] = { "Greeting 0", "Greeting 1", "Greeting 2", "Greeting 3", "Greeting 4", "Greeting 5", "Greeting 6", "Greeting 7", "Greeting 8", "Greeting 9", 0 }; for (int i=0; sGreetings[i]; ++i) { ESM::Dialogue record; record.mId = sGreetings[i]; record.mType = ESM::Dialogue::Greeting; record.blank(); getData().getTopics().add (record); } static const char *sPersuasion[] = { "Intimidate Success", "Intimidate Fail", "Service Refusal", "Admire Success", "Taunt Success", "Bribe Success", "Info Refusal", "Admire Fail", "Taunt Fail", "Bribe Fail", 0 }; for (int i=0; sPersuasion[i]; ++i) { ESM::Dialogue record; record.mId = sPersuasion[i]; record.mType = ESM::Dialogue::Persuasion; record.blank(); getData().getTopics().add (record); } for (int i=0; i& files,bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, ToUTF8::FromType encoding, const std::vector& blacklistedScripts, bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives) : mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, fsStrict, dataPaths, archives, resDir), mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), mResDir(resDir), mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); if (mNew || !boost::filesystem::exists (mProjectPath)) { boost::filesystem::path filtersPath (configuration.getUserDataPath() / "defaultfilters"); boost::filesystem::ofstream destination(mProjectPath, std::ios::out | std::ios::binary); if (!destination.is_open()) throw std::runtime_error("Can not create project file: " + mProjectPath.string()); destination.exceptions(std::ios::failbit | std::ios::badbit); if (!boost::filesystem::exists (filtersPath)) filtersPath = mResDir / "defaultfilters"; boost::filesystem::ifstream source(filtersPath, std::ios::in | std::ios::binary); if (!source.is_open()) throw std::runtime_error("Can not read filters file: " + filtersPath.string()); source.exceptions(std::ios::failbit | std::ios::badbit); destination << source.rdbuf(); } if (mNew) { if (mContentFiles.size()==1) createBase(); } mBlacklist.add (CSMWorld::UniversalId::Type_Script, blacklistedScripts); addOptionalGmsts(); addOptionalGlobals(); addOptionalMagicEffects(); connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect ( &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (reportMessage (const CSMDoc::Message&, int))); connect (&mRunner, SIGNAL (runStateChanged()), this, SLOT (runStateChanged())); } CSMDoc::Document::~Document() { } QUndoStack& CSMDoc::Document::getUndoStack() { return mUndoStack; } int CSMDoc::Document::getState() const { int state = 0; if (!mUndoStack.isClean() || mDirty) state |= State_Modified; if (mSaving.isRunning()) state |= State_Locked | State_Saving | State_Operation; if (mRunner.isRunning()) state |= State_Locked | State_Running; if (int operations = mTools.getRunningOperations()) state |= State_Locked | State_Operation | operations; return state; } const boost::filesystem::path& CSMDoc::Document::getResourceDir() const { return mResDir; } const boost::filesystem::path& CSMDoc::Document::getSavePath() const { return mSavePath; } const boost::filesystem::path& CSMDoc::Document::getProjectPath() const { return mProjectPath; } const std::vector& CSMDoc::Document::getContentFiles() const { return mContentFiles; } bool CSMDoc::Document::isNew() const { return mNew; } void CSMDoc::Document::save() { if (mSaving.isRunning()) throw std::logic_error ( "Failed to initiate save, because a save operation is already running."); mSaving.start(); emit stateChanged (getState(), this); } CSMWorld::UniversalId CSMDoc::Document::verify (const CSMWorld::UniversalId& reportId) { CSMWorld::UniversalId id = mTools.runVerifier (reportId); emit stateChanged (getState(), this); return id; } CSMWorld::UniversalId CSMDoc::Document::newSearch() { return mTools.newSearch(); } void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search) { mTools.runSearch (searchId, search); emit stateChanged (getState(), this); } void CSMDoc::Document::runMerge (std::unique_ptr target) { mTools.runMerge (std::move(target)); emit stateChanged (getState(), this); } void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) mSaving.abort(); else mTools.abortOperation (type); } void CSMDoc::Document::modificationStateChanged (bool clean) { emit stateChanged (getState(), this); } void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) { /// \todo find a better way to get these messages to the user. Log(Debug::Info) << message.mMessage; } void CSMDoc::Document::operationDone2 (int type, bool failed) { if (type==CSMDoc::State_Saving && !failed) mDirty = false; emit stateChanged (getState(), this); } const CSMWorld::Data& CSMDoc::Document::getData() const { return mData; } CSMWorld::Data& CSMDoc::Document::getData() { return mData; } CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& id) { return mTools.getReport (id); } bool CSMDoc::Document::isBlacklisted (const CSMWorld::UniversalId& id) const { return mBlacklist.isBlacklisted (id); } void CSMDoc::Document::startRunning (const std::string& profile, const std::string& startupInstruction) { std::vector contentFiles; for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) contentFiles.push_back (iter->filename().string()); mRunner.configure (getData().getDebugProfiles().getRecord (profile).get(), contentFiles, startupInstruction); int state = getState(); if (state & State_Modified) { // need to save first mRunner.start (true); new SaveWatcher (&mRunner, &mSaving); // no, that is not a memory leak. Qt is weird. if (!(state & State_Saving)) save(); } else mRunner.start(); } void CSMDoc::Document::stopRunning() { mRunner.stop(); } QTextDocument *CSMDoc::Document::getRunLog() { return mRunner.getLog(); } void CSMDoc::Document::runStateChanged() { emit stateChanged (getState(), this); } void CSMDoc::Document::progress (int current, int max, int type) { emit progress (current, max, type, 1, this); } CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() { return mIdCompletionManager; } void CSMDoc::Document::flagAsDirty() { mDirty = true; } openmw-openmw-0.48.0/apps/opencs/model/doc/document.hpp000066400000000000000000000121071445372753700230630ustar00rootroot00000000000000#ifndef CSM_DOC_DOCUMENT_H #define CSM_DOC_DOCUMENT_H #include #include #include #include #include #include #include "../world/data.hpp" #include "../world/idcompletionmanager.hpp" #include "../tools/tools.hpp" #include "state.hpp" #include "saving.hpp" #include "blacklist.hpp" #include "runner.hpp" #include "operationholder.hpp" class QAbstractItemModel; namespace Fallback { class Map; } namespace VFS { class Manager; } namespace ESM { struct GameSetting; struct Global; struct MagicEffect; } namespace Files { struct ConfigurationManager; } namespace CSMWorld { class ResourcesManager; } namespace CSMDoc { class Document : public QObject { Q_OBJECT private: boost::filesystem::path mSavePath; std::vector mContentFiles; bool mNew; CSMWorld::Data mData; CSMTools::Tools mTools; boost::filesystem::path mProjectPath; Saving mSavingOperation; OperationHolder mSaving; boost::filesystem::path mResDir; Blacklist mBlacklist; Runner mRunner; bool mDirty; CSMWorld::IdCompletionManager mIdCompletionManager; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. QUndoStack mUndoStack; // not implemented Document (const Document&); Document& operator= (const Document&); void createBase(); void addGmsts(); void addOptionalGmsts(); void addOptionalGlobals(); void addOptionalMagicEffects(); void addOptionalGmst (const ESM::GameSetting& gmst); void addOptionalGlobal (const ESM::Global& global); void addOptionalMagicEffect (const ESM::MagicEffect& effect); public: Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, ToUTF8::FromType encoding, const std::vector& blacklistedScripts, bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives); ~Document(); QUndoStack& getUndoStack(); int getState() const; const boost::filesystem::path& getResourceDir() const; const boost::filesystem::path& getSavePath() const; const boost::filesystem::path& getProjectPath() const; const std::vector& getContentFiles() const; ///< \attention The last element in this collection is the file that is being edited, /// but with its original path instead of the save path. bool isNew() const; ///< Is this a newly created content file? void save(); CSMWorld::UniversalId verify (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); void runMerge (std::unique_ptr target); void abortOperation (int type); const CSMWorld::Data& getData() const; CSMWorld::Data& getData(); CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); ///< The ownership of the returned report is not transferred. bool isBlacklisted (const CSMWorld::UniversalId& id) const; void startRunning (const std::string& profile, const std::string& startupInstruction = ""); void stopRunning(); QTextDocument *getRunLog(); CSMWorld::IdCompletionManager &getIdCompletionManager(); void flagAsDirty(); signals: void stateChanged (int state, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document); /// \attention When this signal is emitted, *this hands over the ownership of the /// document. This signal must be handled to avoid a leak. void mergeDone (CSMDoc::Document *document); void operationDone (int type, bool failed); private slots: void modificationStateChanged (bool clean); void reportMessage (const CSMDoc::Message& message, int type); void operationDone2 (int type, bool failed); void runStateChanged(); public slots: void progress (int current, int max, int type); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/documentmanager.cpp000066400000000000000000000103521445372753700244110ustar00rootroot00000000000000#include "documentmanager.hpp" #include #ifndef Q_MOC_RUN #include #endif #include "document.hpp" CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) : mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252), mFsStrict(false) { boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; if (!boost::filesystem::is_directory (projectPath)) boost::filesystem::create_directories (projectPath); mLoader.moveToThread (&mLoaderThread); mLoaderThread.start(); connect (&mLoader, SIGNAL (documentLoaded (Document *)), this, SLOT (documentLoaded (Document *))); connect (&mLoader, SIGNAL (documentNotLoaded (Document *, const std::string&)), this, SLOT (documentNotLoaded (Document *, const std::string&))); connect (this, SIGNAL (loadRequest (CSMDoc::Document *)), &mLoader, SLOT (loadDocument (CSMDoc::Document *))); connect (&mLoader, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), this, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int))); connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *, int)), this, SIGNAL (nextRecord (CSMDoc::Document *, int))); connect (this, SIGNAL (cancelLoading (CSMDoc::Document *)), &mLoader, SLOT (abortLoading (CSMDoc::Document *))); connect (&mLoader, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), this, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&))); } CSMDoc::DocumentManager::~DocumentManager() { mLoaderThread.quit(); mLoader.stop(); mLoader.hasThingsToDo().wakeAll(); mLoaderThread.wait(); for (std::vector::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) delete *iter; } bool CSMDoc::DocumentManager::isEmpty() { return mDocuments.empty(); } void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { Document *document = makeDocument (files, savePath, new_); insertDocument (document); } CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_) { return new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mBlacklistedScripts, mFsStrict, mDataPaths, mArchives); } void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) { mDocuments.push_back (document); connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), this, SLOT (insertDocument (CSMDoc::Document*))); emit loadRequest (document); mLoader.hasThingsToDo().wakeAll(); } void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) { std::vector::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document); if (iter==mDocuments.end()) throw std::runtime_error ("removing invalid document"); emit documentAboutToBeRemoved (document); mDocuments.erase (iter); document->deleteLater(); if (mDocuments.empty()) emit lastDocumentDeleted(); } void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) { mResDir = boost::filesystem::system_complete(parResDir); } void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) { mEncoding = encoding; } void CSMDoc::DocumentManager::setBlacklistedScripts (const std::vector& scriptIds) { mBlacklistedScripts = scriptIds; } void CSMDoc::DocumentManager::documentLoaded (Document *document) { emit documentAdded (document); emit loadingStopped (document, true, ""); } void CSMDoc::DocumentManager::documentNotLoaded (Document *document, const std::string& error) { emit loadingStopped (document, false, error); if (error.empty()) // do not remove the document yet, if we have an error removeDocument (document); } void CSMDoc::DocumentManager::setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives) { mFsStrict = strict; mDataPaths = dataPaths; mArchives = archives; } openmw-openmw-0.48.0/apps/opencs/model/doc/documentmanager.hpp000066400000000000000000000077411445372753700244260ustar00rootroot00000000000000#ifndef CSM_DOC_DOCUMENTMGR_H #define CSM_DOC_DOCUMENTMGR_H #include #include #include #include #include #include #include #include #include "loader.hpp" namespace VFS { class Manager; } namespace Files { struct ConfigurationManager; } namespace CSMDoc { class Document; class DocumentManager : public QObject { Q_OBJECT std::vector mDocuments; const Files::ConfigurationManager& mConfiguration; QThread mLoaderThread; Loader mLoader; ToUTF8::FromType mEncoding; std::vector mBlacklistedScripts; boost::filesystem::path mResDir; bool mFsStrict; Files::PathContainer mDataPaths; std::vector mArchives; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); public: DocumentManager (const Files::ConfigurationManager& configuration); ~DocumentManager(); void addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_); ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. /// Create a new document. The ownership of the created document is transferred to /// the calling function. The DocumentManager does not manage it. Loading has not /// taken place at the point when the document is returned. /// /// \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. Document *makeDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_); void setResourceDir (const boost::filesystem::path& parResDir); void setEncoding (ToUTF8::FromType encoding); void setBlacklistedScripts (const std::vector& scriptIds); /// Sets the file data that gets passed to newly created documents. void setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives); bool isEmpty(); private slots: void documentLoaded (Document *document); ///< The ownership of \a document is not transferred. void documentNotLoaded (Document *document, const std::string& error); ///< Document load has been interrupted either because of a call to abortLoading /// or a problem during loading). In the former case error will be an empty string. public slots: void removeDocument (CSMDoc::Document *document); ///< Emits the lastDocumentDeleted signal, if applicable. /// Hand over document to *this. The ownership is transferred. The DocumentManager /// will initiate the load procedure, if necessary void insertDocument (CSMDoc::Document *document); signals: void documentAdded (CSMDoc::Document *document); void documentAboutToBeRemoved (CSMDoc::Document *document); void loadRequest (CSMDoc::Document *document); void lastDocumentDeleted(); void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); void nextRecord (CSMDoc::Document *document, int records); void cancelLoading (CSMDoc::Document *document); void loadMessage (CSMDoc::Document *document, const std::string& message); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/loader.cpp000066400000000000000000000072771445372753700225220ustar00rootroot00000000000000#include "loader.hpp" #include #include "../tools/reportmodel.hpp" #include "document.hpp" CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (false) {} CSMDoc::Loader::Loader() : mShouldStop(false) { mTimer = new QTimer (this); connect (mTimer, SIGNAL (timeout()), this, SLOT (load())); mTimer->start(); } QWaitCondition& CSMDoc::Loader::hasThingsToDo() { return mThingsToDo; } void CSMDoc::Loader::stop() { mShouldStop = true; } void CSMDoc::Loader::load() { if (mDocuments.empty()) { mMutex.lock(); mThingsToDo.wait (&mMutex); mMutex.unlock(); if (mShouldStop) mTimer->stop(); return; } std::vector >::iterator iter = mDocuments.begin(); Document *document = iter->first; int size = static_cast (document->getContentFiles().size()); int editedIndex = size-1; // index of the file to be edited/created if (document->isNew()) --size; bool done = false; try { if (iter->second.mRecordsLeft) { Messages messages (Message::Severity_Error); const int batchingSize = 50; for (int i=0; igetData().continueLoading (messages)) { iter->second.mRecordsLeft = false; break; } else ++(iter->second.mRecordsLoaded); CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); { // silence a g++ warning for (CSMDoc::Messages::Iterator messageIter (messages.begin()); messageIter!=messages.end(); ++messageIter) { document->getReport (log)->add (*messageIter); emit loadMessage (document, messageIter->mMessage); } } emit nextRecord (document, iter->second.mRecordsLoaded); return; } if (iter->second.mFilegetContentFiles()[iter->second.mFile]; int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, /*project*/false); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; emit nextStage (document, path.filename().string(), steps); } else if (iter->second.mFile==size) // start loading the last (project) file { int steps = document->getData().startLoading (document->getProjectPath(), /*base*/false, true); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; emit nextStage (document, "Project File", steps); } else { done = true; } ++(iter->second.mFile); } catch (const std::exception& e) { mDocuments.erase (iter); emit documentNotLoaded (document, e.what()); return; } if (done) { mDocuments.erase (iter); emit documentLoaded (document); } } void CSMDoc::Loader::loadDocument (CSMDoc::Document *document) { mDocuments.emplace_back (document, Stage()); } void CSMDoc::Loader::abortLoading (CSMDoc::Document *document) { for (std::vector >::iterator iter = mDocuments.begin(); iter!=mDocuments.end(); ++iter) { if (iter->first==document) { mDocuments.erase (iter); emit documentNotLoaded (document, ""); break; } } } openmw-openmw-0.48.0/apps/opencs/model/doc/loader.hpp000066400000000000000000000042361445372753700225170ustar00rootroot00000000000000#ifndef CSM_DOC_LOADER_H #define CSM_DOC_LOADER_H #include #include #include #include #include namespace CSMDoc { class Document; class Loader : public QObject { Q_OBJECT struct Stage { int mFile; int mRecordsLoaded; bool mRecordsLeft; Stage(); }; QMutex mMutex; QWaitCondition mThingsToDo; std::vector > mDocuments; QTimer* mTimer; bool mShouldStop; public: Loader(); QWaitCondition& hasThingsToDo(); void stop(); private slots: void load(); public slots: void loadDocument (CSMDoc::Document *document); ///< The ownership of \a document is not transferred. void abortLoading (CSMDoc::Document *document); ///< Abort loading \a docuemnt (ignored if \a document has already finished being /// loaded). Will result in a documentNotLoaded signal, once the Loader has finished /// cleaning up. signals: void documentLoaded (Document *document); ///< The ownership of \a document is not transferred. void documentNotLoaded (Document *document, const std::string& error); ///< Document load has been interrupted either because of a call to abortLoading /// or a problem during loading). In the former case error will be an empty string. void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); void nextRecord (CSMDoc::Document *document, int records); ///< \note This signal is only given once per group of records. The group size is /// approximately the total number of records divided by the steps value of the /// previous nextStage signal. void loadMessage (CSMDoc::Document *document, const std::string& message); ///< Non-critical load error or warning }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/messages.cpp000066400000000000000000000024101445372753700230430ustar00rootroot00000000000000#include "messages.hpp" CSMDoc::Message::Message() : mSeverity(Severity_Default){} CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity) : mId (id), mMessage (message), mHint (hint), mSeverity (severity) {} std::string CSMDoc::Message::toString (Severity severity) { switch (severity) { case CSMDoc::Message::Severity_Info: return "Information"; case CSMDoc::Message::Severity_Warning: return "Warning"; case CSMDoc::Message::Severity_Error: return "Error"; case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; case CSMDoc::Message::Severity_Default: break; } return ""; } CSMDoc::Messages::Messages (Message::Severity default_) : mDefault (default_) {} void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Message::Severity severity) { if (severity==Message::Severity_Default) severity = mDefault; mMessages.push_back (Message (id, message, hint, severity)); } CSMDoc::Messages::Iterator CSMDoc::Messages::begin() const { return mMessages.begin(); } CSMDoc::Messages::Iterator CSMDoc::Messages::end() const { return mMessages.end(); } openmw-openmw-0.48.0/apps/opencs/model/doc/messages.hpp000066400000000000000000000031011445372753700230460ustar00rootroot00000000000000#ifndef CSM_DOC_MESSAGES_H #define CSM_DOC_MESSAGES_H #include #include #include "../world/universalid.hpp" namespace CSMDoc { struct Message { enum Severity { Severity_Info = 0, // no problem Severity_Warning = 1, // a potential problem, but we are probably fine Severity_Error = 2, // an error; we are not fine Severity_SeriousError = 3, // an error so bad we can't even be sure if we are // reporting it correctly Severity_Default = 4 }; CSMWorld::UniversalId mId; std::string mMessage; std::string mHint; Severity mSeverity; Message(); Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity); static std::string toString (Severity severity); }; class Messages { public: typedef std::vector Collection; typedef Collection::const_iterator Iterator; private: Collection mMessages; Message::Severity mDefault; public: Messages (Message::Severity default_); void add (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint = "", Message::Severity severity = Message::Severity_Default); Iterator begin() const; Iterator end() const; }; } Q_DECLARE_METATYPE (CSMDoc::Message) #endif openmw-openmw-0.48.0/apps/opencs/model/doc/operation.cpp000066400000000000000000000060511445372753700232410ustar00rootroot00000000000000#include "operation.hpp" #include #include #include #include "../world/universalid.hpp" #include "stage.hpp" void CSMDoc::Operation::prepareStages() { mCurrentStage = mStages.begin(); mCurrentStep = 0; mCurrentStepTotal = 0; mTotalSteps = 0; mError = false; for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) { iter->second = iter->first->setup(); mTotalSteps += iter->second; } } CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) : mType (type), mStages(std::vector >()), mCurrentStage(mStages.begin()), mCurrentStep(0), mCurrentStepTotal(0), mTotalSteps(0), mOrdered (ordered), mFinalAlways (finalAlways), mError(false), mConnected (false), mPrepared (false), mDefaultSeverity (Message::Severity_Error) { mTimer = new QTimer (this); } CSMDoc::Operation::~Operation() { for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) delete iter->first; } void CSMDoc::Operation::run() { mTimer->stop(); if (!mConnected) { connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); mConnected = true; } mPrepared = false; mTimer->start (0); } void CSMDoc::Operation::appendStage (Stage *stage) { mStages.emplace_back (stage, 0); } void CSMDoc::Operation::setDefaultSeverity (Message::Severity severity) { mDefaultSeverity = severity; } bool CSMDoc::Operation::hasError() const { return mError; } void CSMDoc::Operation::abort() { if (!mTimer->isActive()) return; mError = true; if (mFinalAlways) { if (mStages.begin()!=mStages.end() && mCurrentStage!=--mStages.end()) { mCurrentStep = 0; mCurrentStage = --mStages.end(); } } else mCurrentStage = mStages.end(); } void CSMDoc::Operation::executeStage() { if (!mPrepared) { prepareStages(); mPrepared = true; } Messages messages (mDefaultSeverity); while (mCurrentStage!=mStages.end()) { if (mCurrentStep>=mCurrentStage->second) { mCurrentStep = 0; ++mCurrentStage; } else { try { mCurrentStage->first->perform (mCurrentStep++, messages); } catch (const std::exception& e) { emit reportMessage (Message (CSMWorld::UniversalId(), e.what(), "", Message::Severity_SeriousError), mType); abort(); } ++mCurrentStepTotal; break; } } emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); for (Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) emit reportMessage (*iter, mType); if (mCurrentStage==mStages.end()) operationDone(); } void CSMDoc::Operation::operationDone() { mTimer->stop(); emit done (mType, mError); } openmw-openmw-0.48.0/apps/opencs/model/doc/operation.hpp000066400000000000000000000036511445372753700232510ustar00rootroot00000000000000#ifndef CSM_DOC_OPERATION_H #define CSM_DOC_OPERATION_H #include #include #include #include #include "messages.hpp" namespace CSMWorld { class UniversalId; } namespace CSMDoc { class Stage; class Operation : public QObject { Q_OBJECT int mType; std::vector > mStages; // stage, number of steps std::vector >::iterator mCurrentStage; int mCurrentStep; int mCurrentStepTotal; int mTotalSteps; int mOrdered; bool mFinalAlways; bool mError; bool mConnected; QTimer *mTimer; bool mPrepared; Message::Severity mDefaultSeverity; void prepareStages(); public: Operation (int type, bool ordered, bool finalAlways = false); ///< \param ordered Stages must be executed in the given order. /// \param finalAlways Execute last stage even if an error occurred during earlier stages. virtual ~Operation(); void appendStage (Stage *stage); ///< The ownership of \a stage is transferred to *this. /// /// \attention Do no call this function while this Operation is running. /// \attention Do no call this function while this Operation is running. void setDefaultSeverity (Message::Severity severity); bool hasError() const; signals: void progress (int current, int max, int type); void reportMessage (const CSMDoc::Message& message, int type); void done (int type, bool failed); public slots: void abort(); void run(); private slots: void executeStage(); protected slots: virtual void operationDone(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/operationholder.cpp000066400000000000000000000026041445372753700244370ustar00rootroot00000000000000#include "operationholder.hpp" #include "operation.hpp" CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mOperation(nullptr) , mRunning (false) { if (operation) setOperation (operation); } void CSMDoc::OperationHolder::setOperation (Operation *operation) { mOperation = operation; mOperation->moveToThread (&mThread); connect ( mOperation, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect ( mOperation, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SIGNAL (reportMessage (const CSMDoc::Message&, int))); connect ( mOperation, SIGNAL (done (int, bool)), this, SLOT (doneSlot (int, bool))); connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); } bool CSMDoc::OperationHolder::isRunning() const { return mRunning; } void CSMDoc::OperationHolder::start() { mRunning = true; mThread.start(); } void CSMDoc::OperationHolder::abort() { mRunning = false; emit abortSignal(); } void CSMDoc::OperationHolder::abortAndWait() { if (mRunning) { mThread.quit(); mThread.wait(); } } void CSMDoc::OperationHolder::doneSlot (int type, bool failed) { mRunning = false; mThread.quit(); emit done (type, failed); } openmw-openmw-0.48.0/apps/opencs/model/doc/operationholder.hpp000066400000000000000000000020351445372753700244420ustar00rootroot00000000000000#ifndef CSM_DOC_OPERATIONHOLDER_H #define CSM_DOC_OPERATIONHOLDER_H #include #include #include "messages.hpp" namespace CSMWorld { class UniversalId; } namespace CSMDoc { class Operation; class OperationHolder : public QObject { Q_OBJECT QThread mThread; Operation *mOperation; bool mRunning; public: OperationHolder (Operation *operation = nullptr); void setOperation (Operation *operation); bool isRunning() const; void start(); void abort(); // Abort and wait until thread has finished. void abortAndWait(); private slots: void doneSlot (int type, bool failed); signals: void progress (int current, int max, int type); void reportMessage (const CSMDoc::Message& message, int type); void done (int type, bool failed); void abortSignal(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/runner.cpp000066400000000000000000000075321445372753700225570ustar00rootroot00000000000000#include "runner.hpp" #include #include #include #include #include "operationholder.hpp" CSMDoc::Runner::Runner (const boost::filesystem::path& projectPath) : mRunning (false), mStartup (nullptr), mProjectPath (projectPath) { connect (&mProcess, SIGNAL (finished (int, QProcess::ExitStatus)), this, SLOT (finished (int, QProcess::ExitStatus))); connect (&mProcess, SIGNAL (readyReadStandardOutput()), this, SLOT (readyReadStandardOutput())); mProcess.setProcessChannelMode (QProcess::MergedChannels); mProfile.blank(); } CSMDoc::Runner::~Runner() { if (mRunning) { disconnect (&mProcess, nullptr, this, nullptr); mProcess.kill(); mProcess.waitForFinished(); } } void CSMDoc::Runner::start (bool delayed) { if (mStartup) { delete mStartup; mStartup = nullptr; } if (!delayed) { mLog.clear(); QString path = "openmw"; #ifdef Q_OS_WIN path.append(QString(".exe")); #elif defined(Q_OS_MAC) QDir dir(QCoreApplication::applicationDirPath()); dir.cdUp(); dir.cdUp(); dir.cdUp(); path = dir.absoluteFilePath(path.prepend("OpenMW.app/Contents/MacOS/")); #else path.prepend(QString("./")); #endif mStartup = new QTemporaryFile (this); mStartup->open(); { QTextStream stream (mStartup); if (!mStartupInstruction.empty()) stream << QString::fromUtf8 (mStartupInstruction.c_str()) << '\n'; stream << QString::fromUtf8 (mProfile.mScriptText.c_str()); } mStartup->close(); QStringList arguments; arguments << "--skip-menu"; if (mProfile.mFlags & ESM::DebugProfile::Flag_BypassNewGame) arguments << "--new-game=0"; else arguments << "--new-game=1"; arguments << ("--script-run="+mStartup->fileName()); arguments << QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); arguments << "--replace=content"; arguments << "--content=builtin.omwscripts"; for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) { arguments << QString::fromUtf8 (("--content="+*iter).c_str()); } arguments << QString::fromUtf8 (("--content="+mProjectPath.filename().string()).c_str()); mProcess.start (path, arguments); } mRunning = true; emit runStateChanged(); } void CSMDoc::Runner::stop() { delete mStartup; mStartup = nullptr; if (mProcess.state()==QProcess::NotRunning) { mRunning = false; emit runStateChanged(); } else mProcess.kill(); } bool CSMDoc::Runner::isRunning() const { return mRunning; } void CSMDoc::Runner::configure (const ESM::DebugProfile& profile, const std::vector& contentFiles, const std::string& startupInstruction) { mProfile = profile; mContentFiles = contentFiles; mStartupInstruction = startupInstruction; } void CSMDoc::Runner::finished (int exitCode, QProcess::ExitStatus exitStatus) { mRunning = false; emit runStateChanged(); } QTextDocument *CSMDoc::Runner::getLog() { return &mLog; } void CSMDoc::Runner::readyReadStandardOutput() { mLog.setPlainText ( mLog.toPlainText() + QString::fromUtf8 (mProcess.readAllStandardOutput())); } CSMDoc::SaveWatcher::SaveWatcher (Runner *runner, OperationHolder *operation) : QObject (runner), mRunner (runner) { connect (operation, SIGNAL (done (int, bool)), this, SLOT (saveDone (int, bool))); } void CSMDoc::SaveWatcher::saveDone (int type, bool failed) { if (failed) mRunner->stop(); else mRunner->start(); deleteLater(); } openmw-openmw-0.48.0/apps/opencs/model/doc/runner.hpp000066400000000000000000000040731445372753700225610ustar00rootroot00000000000000#ifndef CSM_DOC_RUNNER_H #define CSM_DOC_RUNNER_H #include #include #include #include #include #include #include class QTemporaryFile; namespace CSMDoc { class OperationHolder; class Runner : public QObject { Q_OBJECT QProcess mProcess; bool mRunning; ESM::DebugProfile mProfile; std::vector mContentFiles; std::string mStartupInstruction; QTemporaryFile *mStartup; QTextDocument mLog; boost::filesystem::path mProjectPath; public: Runner (const boost::filesystem::path& projectPath); ~Runner(); /// \param delayed Flag as running but do not start the OpenMW process yet (the /// process must be started by another call of start with delayed==false) void start (bool delayed = false); void stop(); /// \note Running state is entered when the start function is called. This /// is not necessarily identical to the moment the child process is started. bool isRunning() const; void configure (const ESM::DebugProfile& profile, const std::vector& contentFiles, const std::string& startupInstruction); QTextDocument *getLog(); signals: void runStateChanged(); private slots: void finished (int exitCode, QProcess::ExitStatus exitStatus); void readyReadStandardOutput(); }; class Operation; /// \brief Watch for end of save operation and restart or stop runner class SaveWatcher : public QObject { Q_OBJECT Runner *mRunner; public: /// *this attaches itself to runner SaveWatcher (Runner *runner, OperationHolder *operation); private slots: void saveDone (int type, bool failed); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/saving.cpp000066400000000000000000000103171445372753700225300ustar00rootroot00000000000000#include "saving.hpp" #include "../world/data.hpp" #include "../world/idcollection.hpp" #include "state.hpp" #include "savingstages.hpp" #include "document.hpp" CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath, ToUTF8::FromType encoding) : Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath, encoding) { // save project file appendStage (new OpenSaveStage (mDocument, mState, true)); appendStage (new WriteHeaderStage (mDocument, mState, true)); appendStage (new WriteCollectionStage > ( mDocument.getData().getFilters(), mState, CSMWorld::Scope_Project)); appendStage (new WriteCollectionStage > ( mDocument.getData().getDebugProfiles(), mState, CSMWorld::Scope_Project)); appendStage (new WriteCollectionStage > ( mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); appendStage (new CloseSaveStage (mState)); // save content file appendStage (new OpenSaveStage (mDocument, mState, false)); appendStage (new WriteHeaderStage (mDocument, mState, false)); appendStage (new WriteCollectionStage > (mDocument.getData().getGlobals(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getGmsts(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getSkills(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getClasses(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getFactions(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getRaces(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getSounds(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getScripts(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getRegions(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getBirthsigns(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getSpells(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getEnchantments(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getBodyParts(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getSoundGens(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getMagicEffects(), mState)); appendStage (new WriteCollectionStage > (mDocument.getData().getStartScripts(), mState)); appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState)); // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); appendStage (new WritePathgridCollectionStage (mDocument, mState)); appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); // references Land Textures appendStage (new WriteLandCollectionStage (mDocument, mState)); // close file and clean up appendStage (new CloseSaveStage (mState)); appendStage (new FinalSavingStage (mDocument, mState)); } openmw-openmw-0.48.0/apps/opencs/model/doc/saving.hpp000066400000000000000000000010061445372753700225300ustar00rootroot00000000000000#ifndef CSM_DOC_SAVING_H #define CSM_DOC_SAVING_H #include #include #include "operation.hpp" #include "savingstate.hpp" namespace CSMDoc { class Document; class Saving : public Operation { Q_OBJECT Document& mDocument; SavingState mState; public: Saving (Document& document, const boost::filesystem::path& projectPath, ToUTF8::FromType encoding); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/savingstages.cpp000066400000000000000000000435451445372753700237500ustar00rootroot00000000000000#include "savingstages.hpp" #include #include #include #include "../world/infocollection.hpp" #include "../world/cellcoordinates.hpp" #include "document.hpp" CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state, bool projectFile) : mDocument (document), mState (state), mProjectFile (projectFile) {} int CSMDoc::OpenSaveStage::setup() { return 1; } void CSMDoc::OpenSaveStage::perform (int stage, Messages& messages) { mState.start (mDocument, mProjectFile); mState.getStream().open ( mProjectFile ? mState.getPath() : mState.getTmpPath(), std::ios::binary); if (!mState.getStream().is_open()) throw std::runtime_error ("failed to open stream for saving"); } CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state, bool simple) : mDocument (document), mState (state), mSimple (simple) {} int CSMDoc::WriteHeaderStage::setup() { return 1; } void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) { mState.getWriter().setVersion(); mState.getWriter().clearMaster(); if (mSimple) { mState.getWriter().setAuthor (""); mState.getWriter().setDescription (""); mState.getWriter().setRecordCount (0); // ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs // we use the format `0` for compatibility with old versions. mState.getWriter().setFormat(0); } else { mDocument.getData().getMetaData().save (mState.getWriter()); mState.getWriter().setRecordCount ( mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); /// \todo refine dependency list (at least remove redundant dependencies) std::vector dependencies = mDocument.getContentFiles(); std::vector::const_iterator end (--dependencies.end()); for (std::vector::const_iterator iter (dependencies.begin()); iter!=end; ++iter) { std::string name = iter->filename().string(); uint64_t size = boost::filesystem::file_size (*iter); mState.getWriter().addMaster (name, size); } } mState.getWriter().save (mState.getStream()); } CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal) : mState (state), mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()), mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos()) {} int CSMDoc::WriteDialogueCollectionStage::setup() { return mTopics.getSize(); } void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& topic = mTopics.getRecord (stage); if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. ESM::Dialogue dialogue = topic.get(); writer.startRecord(dialogue.sRecordId); dialogue.save(writer, true); writer.endRecord(dialogue.sRecordId); return; } // Test, if we need to save anything associated info records. bool infoModified = false; CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; } } if (topic.isModified() || infoModified) { if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) { mState.getWriter().startRecord (topic.mBase.sRecordId); topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mBase.sRecordId); } else { mState.getWriter().startRecord (topic.mModified.sRecordId); topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mModified.sRecordId); } // write modified selected info records for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted) { ESM::DialInfo info = (*iter)->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); info.mPrev.clear(); if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; info.mPrev = (*prev)->get().mId.substr ((*prev)->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; info.mNext.clear(); if (next!=range.second) { info.mNext = (*next)->get().mId.substr ((*next)->get().mId.find_last_of ('#')+1); } writer.startRecord (info.sRecordId); info.save (writer, (*iter)->mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (info.sRecordId); } } } } CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::WriteRefIdCollectionStage::setup() { return mDocument.getData().getReferenceables().getSize(); } void CSMDoc::WriteRefIdCollectionStage::perform (int stage, Messages& messages) { mDocument.getData().getReferenceables().save (stage, mState.getWriter()); } CSMDoc::CollectionReferencesStage::CollectionReferencesStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::CollectionReferencesStage::setup() { mState.getSubRecords().clear(); int size = mDocument.getData().getReferences().getSize(); int steps = size/100; if (size%100) ++steps; return steps; } void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) { int size = mDocument.getData().getReferences().getSize(); for (int i=stage*100; i& record = mDocument.getData().getReferences().getRecord (i); if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { std::string cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; std::deque& indices = mState.getSubRecords()[Misc::StringUtils::lowerCase (cellId)]; // collect moved references at the end of the container bool interior = cellId.substr (0, 1)!="#"; std::ostringstream stream; if (!interior) { // recalculate the ref's cell location std::pair index = record.get().getCellIndex(); stream << "#" << index.first << " " << index.second; } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. if ((record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior && record.mState!=CSMWorld::RecordBase::State_ModifiedOnly && !record.get().mNew) indices.push_back (i); else indices.push_front (i); } } } CSMDoc::WriteCellCollectionStage::WriteCellCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::WriteCellCollectionStage::setup() { return mDocument.getData().getCells().getSize(); } void CSMDoc::WriteCellCollectionStage::writeReferences (const std::deque& references, bool interior, unsigned int& newRefNum) { ESM::ESMWriter& writer = mState.getWriter(); for (std::deque::const_iterator iter (references.begin()); iter!=references.end(); ++iter) { const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::CellRef refRecord = ref.get(); // Check for uninitialized content file if (!refRecord.mRefNum.hasContentFile()) refRecord.mRefNum.mContentFile = 0; // recalculate the ref's cell location std::ostringstream stream; if (!interior) { std::pair index = refRecord.getCellIndex(); stream << "#" << index.first << " " << index.second; } if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 || (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && refRecord.mCell!=stream.str())) { refRecord.mRefNum.mIndex = newRefNum++; } else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. ESM::MovedCellRef moved; moved.mRefNum = refRecord.mRefNum; // Need to fill mTarget with the ref's new position. std::istringstream istream (stream.str().c_str()); char ignore; istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; refRecord.mRefNum.save (writer, false, "MVRF"); writer.writeHNT ("CNDT", moved.mTarget); } refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); } } } void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); const CSMWorld::RefIdCollection& referenceables = mDocument.getData().getReferenceables(); const CSMWorld::RefIdData& refIdData = referenceables.getDataSet(); std::deque tempRefs; std::deque persistentRefs; std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); if (cell.isModified() || cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { CSMWorld::Cell cellRecord = cell.get(); bool interior = cellRecord.mId.substr (0, 1)!="#"; // count new references and adjust RefNumCount accordingsly unsigned int newRefNum = cellRecord.mRefNumCounter; if (references!=mState.getSubRecords().end()) { for (std::deque::const_iterator iter (references->second.begin()); iter!=references->second.end(); ++iter) { const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); CSMWorld::CellRef refRecord = ref.get(); CSMWorld::RefIdData::LocalIndex localIndex = refIdData.searchId(refRecord.mRefID); unsigned int recordFlags = refIdData.getRecordFlags(refRecord.mRefID); bool isPersistent = ((recordFlags & ESM::FLAG_Persistent) != 0) || refRecord.mTeleport || localIndex.second == CSMWorld::UniversalId::Type_Creature || localIndex.second == CSMWorld::UniversalId::Type_Npc; if (isPersistent) persistentRefs.push_back(*iter); else tempRefs.push_back(*iter); if (refRecord.mNew || (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && /// \todo consider worldspace CSMWorld::CellCoordinates (refRecord.getCellIndex()).getId("") != refRecord.mCell)) ++cellRecord.mRefNumCounter; if (refRecord.mRefNum.mIndex >= newRefNum) newRefNum = refRecord.mRefNum.mIndex + 1; } } // write cell data writer.startRecord (cellRecord.sRecordId); if (interior) cellRecord.mData.mFlags |= ESM::Cell::Interior; else { cellRecord.mData.mFlags &= ~ESM::Cell::Interior; std::istringstream stream (cellRecord.mId.c_str()); char ignore; stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references if (references!=mState.getSubRecords().end()) { writeReferences(persistentRefs, interior, newRefNum); cellRecord.saveTempMarker(writer, int(references->second.size()) - persistentRefs.size()); writeReferences(tempRefs, interior, newRefNum); } writer.endRecord (cellRecord.sRecordId); } } CSMDoc::WritePathgridCollectionStage::WritePathgridCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::WritePathgridCollectionStage::setup() { return mDocument.getData().getPathgrids().getSize(); } void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); if (record.mId.substr (0, 1)=="#") { std::istringstream stream (record.mId.c_str()); char ignore; stream >> ignore >> record.mData.mX >> record.mData.mY; } else record.mCell = record.mId; writer.startRecord (record.sRecordId); record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } CSMDoc::WriteLandCollectionStage::WriteLandCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::WriteLandCollectionStage::setup() { return mDocument.getData().getLand().getSize(); } void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Land record = land.get(); writer.startRecord (record.sRecordId); record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } CSMDoc::WriteLandTextureCollectionStage::WriteLandTextureCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::WriteLandTextureCollectionStage::setup() { return mDocument.getData().getLandTextures().getSize(); } void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); writer.startRecord (record.sRecordId); record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) : mState (state) {} int CSMDoc::CloseSaveStage::setup() { return 1; } void CSMDoc::CloseSaveStage::perform (int stage, Messages& messages) { mState.getStream().close(); if (!mState.getStream()) throw std::runtime_error ("saving failed"); } CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} int CSMDoc::FinalSavingStage::setup() { return 1; } void CSMDoc::FinalSavingStage::perform (int stage, Messages& messages) { if (mState.hasError()) { mState.getWriter().close(); mState.getStream().close(); if (boost::filesystem::exists (mState.getTmpPath())) boost::filesystem::remove (mState.getTmpPath()); } else if (!mState.isProjectFile()) { if (boost::filesystem::exists (mState.getPath())) boost::filesystem::remove (mState.getPath()); boost::filesystem::rename (mState.getTmpPath(), mState.getPath()); mDocument.getUndoStack().setClean(); } } openmw-openmw-0.48.0/apps/opencs/model/doc/savingstages.hpp000066400000000000000000000167341445372753700237550ustar00rootroot00000000000000#ifndef CSM_DOC_SAVINGSTAGES_H #define CSM_DOC_SAVINGSTAGES_H #include "stage.hpp" #include "../world/record.hpp" #include "../world/idcollection.hpp" #include "../world/scope.hpp" #include #include "savingstate.hpp" namespace ESM { struct Dialogue; } namespace CSMWorld { class InfoCollection; } namespace CSMDoc { class Document; class SavingState; class OpenSaveStage : public Stage { Document& mDocument; SavingState& mState; bool mProjectFile; public: OpenSaveStage (Document& document, SavingState& state, bool projectFile); ///< \param projectFile Saving the project file instead of the content file. int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WriteHeaderStage : public Stage { Document& mDocument; SavingState& mState; bool mSimple; public: WriteHeaderStage (Document& document, SavingState& state, bool simple); ///< \param simple Simplified header (used for project files). int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; template class WriteCollectionStage : public Stage { const CollectionT& mCollection; SavingState& mState; CSMWorld::Scope mScope; public: WriteCollectionStage (const CollectionT& collection, SavingState& state, CSMWorld::Scope scope = CSMWorld::Scope_Content); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; template WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, SavingState& state, CSMWorld::Scope scope) : mCollection (collection), mState (state), mScope (scope) {} template int WriteCollectionStage::setup() { return mCollection.getSize(); } template void WriteCollectionStage::perform (int stage, Messages& messages) { if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) return; ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); if (state == CSMWorld::RecordBase::State_Modified || state == CSMWorld::RecordBase::State_ModifiedOnly || state == CSMWorld::RecordBase::State_Deleted) { writer.startRecord (record.sRecordId, record.mRecordFlags); record.save (writer, state == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } class WriteDialogueCollectionStage : public Stage { SavingState& mState; const CSMWorld::IdCollection& mTopics; CSMWorld::InfoCollection& mInfos; public: WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WriteRefIdCollectionStage : public Stage { Document& mDocument; SavingState& mState; public: WriteRefIdCollectionStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class CollectionReferencesStage : public Stage { Document& mDocument; SavingState& mState; public: CollectionReferencesStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WriteCellCollectionStage : public Stage { Document& mDocument; SavingState& mState; void writeReferences (const std::deque& references, bool interior, unsigned int& newRefNum); public: WriteCellCollectionStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WritePathgridCollectionStage : public Stage { Document& mDocument; SavingState& mState; public: WritePathgridCollectionStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WriteLandCollectionStage : public Stage { Document& mDocument; SavingState& mState; public: WriteLandCollectionStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class WriteLandTextureCollectionStage : public Stage { Document& mDocument; SavingState& mState; public: WriteLandTextureCollectionStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class CloseSaveStage : public Stage { SavingState& mState; public: CloseSaveStage (SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class FinalSavingStage : public Stage { Document& mDocument; SavingState& mState; public: FinalSavingStage (Document& document, SavingState& state); int setup() override; ///< \return number of steps void perform (int stage, Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/savingstate.cpp000066400000000000000000000026311445372753700235710ustar00rootroot00000000000000#include "savingstate.hpp" #include #include "operation.hpp" #include "document.hpp" CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath, ToUTF8::FromType encoding) : mOperation (operation), mEncoder (encoding), mProjectPath (projectPath), mProjectFile (false) { mWriter.setEncoder (&mEncoder); } bool CSMDoc::SavingState::hasError() const { return mOperation.hasError(); } void CSMDoc::SavingState::start (Document& document, bool project) { mProjectFile = project; if (mStream.is_open()) mStream.close(); mStream.clear(); mSubRecords.clear(); if (project) mPath = mProjectPath; else mPath = document.getSavePath(); boost::filesystem::path file (mPath.filename().string() + ".tmp"); mTmpPath = mPath.parent_path(); mTmpPath /= file; } const boost::filesystem::path& CSMDoc::SavingState::getPath() const { return mPath; } const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const { return mTmpPath; } boost::filesystem::ofstream& CSMDoc::SavingState::getStream() { return mStream; } ESM::ESMWriter& CSMDoc::SavingState::getWriter() { return mWriter; } bool CSMDoc::SavingState::isProjectFile() const { return mProjectFile; } std::map >& CSMDoc::SavingState::getSubRecords() { return mSubRecords; } openmw-openmw-0.48.0/apps/opencs/model/doc/savingstate.hpp000066400000000000000000000027671445372753700236100ustar00rootroot00000000000000#ifndef CSM_DOC_SAVINGSTATE_H #define CSM_DOC_SAVINGSTATE_H #include #include #include #include #include #include namespace CSMDoc { class Operation; class Document; class SavingState { Operation& mOperation; boost::filesystem::path mPath; boost::filesystem::path mTmpPath; ToUTF8::Utf8Encoder mEncoder; boost::filesystem::ofstream mStream; ESM::ESMWriter mWriter; boost::filesystem::path mProjectPath; bool mProjectFile; std::map > mSubRecords; // record ID, list of subrecords public: SavingState (Operation& operation, const boost::filesystem::path& projectPath, ToUTF8::FromType encoding); bool hasError() const; void start (Document& document, bool project); ///< \param project Save project file instead of content file. const boost::filesystem::path& getPath() const; const boost::filesystem::path& getTmpPath() const; boost::filesystem::ofstream& getStream(); ESM::ESMWriter& getWriter(); bool isProjectFile() const; ///< Currently saving project file? (instead of content file) std::map >& getSubRecords(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/stage.cpp000066400000000000000000000000611445372753700223370ustar00rootroot00000000000000#include "stage.hpp" CSMDoc::Stage::~Stage() {} openmw-openmw-0.48.0/apps/opencs/model/doc/stage.hpp000066400000000000000000000007701445372753700223530ustar00rootroot00000000000000#ifndef CSM_DOC_STAGE_H #define CSM_DOC_STAGE_H #include #include #include "../world/universalid.hpp" #include "messages.hpp" class QString; namespace CSMDoc { class Stage { public: virtual ~Stage(); virtual int setup() = 0; ///< \return number of steps virtual void perform (int stage, Messages& messages) = 0; ///< Messages resulting from this stage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/doc/state.hpp000066400000000000000000000006501445372753700223650ustar00rootroot00000000000000#ifndef CSM_DOC_STATE_H #define CSM_DOC_STATE_H namespace CSMDoc { enum State { State_Modified = 1, State_Locked = 2, State_Operation = 4, State_Running = 8, State_Saving = 16, State_Verifying = 32, State_Merging = 64, State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/000077500000000000000000000000001445372753700212535ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/filter/andnode.cpp000066400000000000000000000006341445372753700233720ustar00rootroot00000000000000#include "andnode.hpp" CSMFilter::AndNode::AndNode (const std::vector >& nodes) : NAryNode (nodes, "and") {} bool CSMFilter::AndNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { int size = getSize(); for (int i=0; i >& nodes); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/booleannode.cpp000066400000000000000000000005471445372753700242520ustar00rootroot00000000000000#include "booleannode.hpp" CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} bool CSMFilter::BooleanNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { return mTrue; } std::string CSMFilter::BooleanNode::toString (bool numericColumns) const { return mTrue ? "true" : "false"; } openmw-openmw-0.48.0/apps/opencs/model/filter/booleannode.hpp000066400000000000000000000014121445372753700242470ustar00rootroot00000000000000#ifndef CSM_FILTER_BOOLEANNODE_H #define CSM_FILTER_BOOLEANNODE_H #include "leafnode.hpp" namespace CSMFilter { class BooleanNode : public LeafNode { bool mTrue; public: BooleanNode (bool true_); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping std::string toString (bool numericColumns) const override; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/leafnode.cpp000066400000000000000000000002001445372753700235240ustar00rootroot00000000000000#include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const { return std::vector(); } openmw-openmw-0.48.0/apps/opencs/model/filter/leafnode.hpp000066400000000000000000000006771445372753700235530ustar00rootroot00000000000000#ifndef CSM_FILTER_LEAFNODE_H #define CSM_FILTER_LEAFNODE_H #include #include "node.hpp" namespace CSMFilter { class LeafNode : public Node { public: std::vector getReferencedColumns() const override; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/narynode.cpp000066400000000000000000000022731445372753700236020ustar00rootroot00000000000000#include "narynode.hpp" #include CSMFilter::NAryNode::NAryNode (const std::vector >& nodes, const std::string& name) : mNodes (nodes), mName (name) {} int CSMFilter::NAryNode::getSize() const { return static_cast(mNodes.size()); } const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const { return *mNodes.at (index); } std::vector CSMFilter::NAryNode::getReferencedColumns() const { std::vector columns; for (std::vector >::const_iterator iter (mNodes.begin()); iter!=mNodes.end(); ++iter) { std::vector columns2 = (*iter)->getReferencedColumns(); columns.insert (columns.end(), columns2.begin(), columns2.end()); } return columns; } std::string CSMFilter::NAryNode::toString (bool numericColumns) const { std::ostringstream stream; stream << mName << " ("; bool first = true; int size = getSize(); for (int i=0; i #include #include "node.hpp" namespace CSMFilter { class NAryNode : public Node { std::vector > mNodes; std::string mName; public: NAryNode (const std::vector >& nodes, const std::string& name); int getSize() const; const Node& operator[] (int index) const; std::vector getReferencedColumns() const override; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. std::string toString (bool numericColumns) const override; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/node.cpp000066400000000000000000000001151445372753700227010ustar00rootroot00000000000000#include "node.hpp" CSMFilter::Node::Node() {} CSMFilter::Node::~Node() {} openmw-openmw-0.48.0/apps/opencs/model/filter/node.hpp000066400000000000000000000027101445372753700227110ustar00rootroot00000000000000#ifndef CSM_FILTER_NODE_H #define CSM_FILTER_NODE_H #include #include #include #include #include namespace CSMWorld { class IdTableBase; } namespace CSMFilter { /// \brief Root class for the filter node hierarchy /// /// \note When the function documentation for this class mentions "this node", this should be /// interpreted as "the node and all its children". class Node { // not implemented Node (const Node&); Node& operator= (const Node&); public: Node(); virtual ~Node(); virtual bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const = 0; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping virtual std::vector getReferencedColumns() const = 0; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. virtual std::string toString (bool numericColumns) const = 0; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } Q_DECLARE_METATYPE (std::shared_ptr) #endif openmw-openmw-0.48.0/apps/opencs/model/filter/notnode.cpp000066400000000000000000000004411445372753700234240ustar00rootroot00000000000000#include "notnode.hpp" CSMFilter::NotNode::NotNode (std::shared_ptr child) : UnaryNode (child, "not") {} bool CSMFilter::NotNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { return !getChild().test (table, row, columns); } openmw-openmw-0.48.0/apps/opencs/model/filter/notnode.hpp000066400000000000000000000010011445372753700234220ustar00rootroot00000000000000#ifndef CSM_FILTER_NOTNODE_H #define CSM_FILTER_NOTNODE_H #include "unarynode.hpp" namespace CSMFilter { class NotNode : public UnaryNode { public: NotNode (std::shared_ptr child); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/ornode.cpp000066400000000000000000000006261445372753700232510ustar00rootroot00000000000000#include "ornode.hpp" CSMFilter::OrNode::OrNode (const std::vector >& nodes) : NAryNode (nodes, "or") {} bool CSMFilter::OrNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { int size = getSize(); for (int i=0; i >& nodes); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/parser.cpp000066400000000000000000000336701445372753700232640ustar00rootroot00000000000000#include "parser.hpp" #include #include #include #include #include "../world/columns.hpp" #include "../world/data.hpp" #include "../world/idcollection.hpp" #include "booleannode.hpp" #include "ornode.hpp" #include "andnode.hpp" #include "notnode.hpp" #include "textnode.hpp" #include "valuenode.hpp" namespace { bool isAlpha(char c) { return std::isalpha(static_cast(c)); } bool isDigit(char c) { return std::isdigit(static_cast(c)); } } namespace CSMFilter { struct Token { enum Type { Type_EOS, Type_None, Type_String, Type_Number, Type_Open, Type_Close, Type_OpenSquare, Type_CloseSquare, Type_Comma, Type_OneShot, Type_Keyword_True, ///< \attention Keyword enums must be arranged continuously. Type_Keyword_False, Type_Keyword_And, Type_Keyword_Or, Type_Keyword_Not, Type_Keyword_Text, Type_Keyword_Value }; Type mType; std::string mString; double mNumber; Token (Type type = Type_None); Token (Type type, const std::string& string); ///< Non-string type that can also be interpreted as a string. Token (const std::string& string); Token (double number); operator bool() const; bool isString() const; }; Token::Token (Type type) : mType (type), mNumber(0.0) {} Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {} Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {} Token::Token (double number) : mType (Type_Number), mNumber (number) {} bool Token::isString() const { return mType==Type_String || mType>=Type_Keyword_True; } Token::operator bool() const { return mType!=Type_None; } bool operator== (const Token& left, const Token& right) { if (left.mType!=right.mType) return false; switch (left.mType) { case Token::Type_String: return left.mString==right.mString; case Token::Type_Number: return left.mNumber==right.mNumber; default: return true; } } } CSMFilter::Token CSMFilter::Parser::getStringToken() { std::string string; int size = static_cast (mInput.size()); for (; mIndex1) { ++mIndex; break; } }; if (!string.empty()) { if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) ) { error(); return Token (Token::Type_None); } if (string[0]!='"' && string[string.size()-1]=='"') { error(); return Token (Token::Type_None); } if (string[0]=='"') return string.substr (1, string.size()-2); } return checkKeywords (string); } CSMFilter::Token CSMFilter::Parser::getNumberToken() { std::string string; int size = static_cast (mInput.size()); bool hasDecimalPoint = false; bool hasDigit = false; for (; mIndex> value; return value; } CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token) { static const char *sKeywords[] = { "true", "false", "and", "or", "not", "string", "value", 0 }; std::string string = Misc::StringUtils::lowerCase (token.mString); for (int i=0; sKeywords[i]; ++i) if (sKeywords[i]==string || (string.size()==1 && sKeywords[i][0]==string[0])) return Token (static_cast (i+Token::Type_Keyword_True), token.mString); return token; } CSMFilter::Token CSMFilter::Parser::getNextToken() { int size = static_cast (mInput.size()); char c = 0; for (; mIndex=size) return Token (Token::Type_EOS); switch (c) { case '(': ++mIndex; return Token (Token::Type_Open); case ')': ++mIndex; return Token (Token::Type_Close); case '[': ++mIndex; return Token (Token::Type_OpenSquare); case ']': ++mIndex; return Token (Token::Type_CloseSquare); case ',': ++mIndex; return Token (Token::Type_Comma); case '!': ++mIndex; return Token (Token::Type_OneShot); } if (c=='"' || c=='_' || isAlpha(c) || c==':') return getStringToken(); if (c=='-' || c=='.' || isDigit(c)) return getNumberToken(); error(); return Token (Token::Type_None); } std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, bool ignoreOneShot) { if (Token token = getNextToken()) { if (token==Token (Token::Type_OneShot)) token = getNextToken(); if (token) switch (token.mType) { case Token::Type_Keyword_True: return std::make_shared(true); case Token::Type_Keyword_False: return std::make_shared(false); case Token::Type_Keyword_And: case Token::Type_Keyword_Or: return parseNAry (token); case Token::Type_Keyword_Not: { std::shared_ptr node = parseImp(); if (mError) return std::shared_ptr(); return std::make_shared(node); } case Token::Type_Keyword_Text: return parseText(); case Token::Type_Keyword_Value: return parseValue(); case Token::Type_EOS: if (!allowEmpty) error(); return std::shared_ptr(); default: error(); } } return std::shared_ptr(); } std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyword) { std::vector > nodes; Token token = getNextToken(); if (token.mType!=Token::Type_Open) { error(); return std::shared_ptr(); } for (;;) { std::shared_ptr node = parseImp(); if (mError) return std::shared_ptr(); nodes.push_back (node); token = getNextToken(); if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma)) { error(); return std::shared_ptr(); } if (token.mType==Token::Type_Close) break; } switch (keyword.mType) { case Token::Type_Keyword_And: return std::make_shared(nodes); case Token::Type_Keyword_Or: return std::make_shared(nodes); default: error(); return std::shared_ptr(); } } std::shared_ptr CSMFilter::Parser::parseText() { Token token = getNextToken(); if (token.mType!=Token::Type_Open) { error(); return std::shared_ptr(); } token = getNextToken(); if (!token) return std::shared_ptr(); // parse column ID int columnId = -1; if (token.mType==Token::Type_Number) { if (static_cast (token.mNumber)==token.mNumber) columnId = static_cast (token.mNumber); } else if (token.isString()) { columnId = CSMWorld::Columns::getId (token.mString); } if (columnId<0) { error(); return std::shared_ptr(); } token = getNextToken(); if (token.mType!=Token::Type_Comma) { error(); return std::shared_ptr(); } // parse text pattern token = getNextToken(); if (!token.isString()) { error(); return std::shared_ptr(); } std::string text = token.mString; token = getNextToken(); if (token.mType!=Token::Type_Close) { error(); return std::shared_ptr(); } return std::make_shared(columnId, text); } std::shared_ptr CSMFilter::Parser::parseValue() { Token token = getNextToken(); if (token.mType!=Token::Type_Open) { error(); return std::shared_ptr(); } token = getNextToken(); if (!token) return std::shared_ptr(); // parse column ID int columnId = -1; if (token.mType==Token::Type_Number) { if (static_cast (token.mNumber)==token.mNumber) columnId = static_cast (token.mNumber); } else if (token.isString()) { columnId = CSMWorld::Columns::getId (token.mString); } if (columnId<0) { error(); return std::shared_ptr(); } token = getNextToken(); if (token.mType!=Token::Type_Comma) { error(); return std::shared_ptr(); } // parse value double lower = 0; double upper = 0; ValueNode::Type lowerType = ValueNode::Type_Open; ValueNode::Type upperType = ValueNode::Type_Open; token = getNextToken(); if (token.mType==Token::Type_Number) { // single value lower = upper = token.mNumber; lowerType = upperType = ValueNode::Type_Closed; } else { // interval if (token.mType==Token::Type_OpenSquare) lowerType = ValueNode::Type_Closed; else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open) { error(); return std::shared_ptr(); } token = getNextToken(); if (token.mType==Token::Type_Number) { lower = token.mNumber; token = getNextToken(); if (token.mType!=Token::Type_Comma) { error(); return std::shared_ptr(); } } else if (token.mType==Token::Type_Comma) { lowerType = ValueNode::Type_Infinite; } else { error(); return std::shared_ptr(); } token = getNextToken(); if (token.mType==Token::Type_Number) { upper = token.mNumber; token = getNextToken(); } else upperType = ValueNode::Type_Infinite; if (token.mType==Token::Type_CloseSquare) { if (upperType!=ValueNode::Type_Infinite) upperType = ValueNode::Type_Closed; } else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close) { error(); return std::shared_ptr(); } } token = getNextToken(); if (token.mType!=Token::Type_Close) { error(); return std::shared_ptr(); } return std::make_shared(columnId, lowerType, upperType, lower, upper); } void CSMFilter::Parser::error() { mError = true; } CSMFilter::Parser::Parser (const CSMWorld::Data& data) : mIndex (0), mError (false), mData (data) {} bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) { // reset mFilter.reset(); mError = false; mInput = filter; mIndex = 0; Token token; if (allowPredefined) token = getNextToken(); if (allowPredefined && token==Token (Token::Type_EOS)) { mFilter.reset(); return true; } else if (!allowPredefined || token==Token (Token::Type_OneShot)) { std::shared_ptr node = parseImp (true, token!=Token (Token::Type_OneShot)); if (mError) return false; if (getNextToken()!=Token (Token::Type_EOS)) { error(); return false; } if (node) mFilter = node; else { // Empty filter string equals to filter "true". mFilter = std::make_shared(true); } return true; } // We do not use isString() here, because there could be a pre-defined filter with an ID that is // equal a filter keyword. else if (token.mType==Token::Type_String) { if (getNextToken()!=Token (Token::Type_EOS)) { error(); return false; } int index = mData.getFilters().searchId (token.mString); if (index==-1) { error(); return false; } const CSMWorld::Record& record = mData.getFilters().getRecord (index); if (record.isDeleted()) { error(); return false; } return parse (record.get().mFilter, false); } else { error(); return false; } } std::shared_ptr CSMFilter::Parser::getFilter() const { if (mError) throw std::logic_error ("No filter available"); return mFilter; } openmw-openmw-0.48.0/apps/opencs/model/filter/parser.hpp000066400000000000000000000025161445372753700232640ustar00rootroot00000000000000#ifndef CSM_FILTER_PARSER_H #define CSM_FILTER_PARSER_H #include "node.hpp" namespace CSMWorld { class Data; } namespace CSMFilter { struct Token; class Parser { std::shared_ptr mFilter; std::string mInput; int mIndex; bool mError; const CSMWorld::Data& mData; Token getStringToken(); Token getNumberToken(); Token getNextToken(); Token checkKeywords (const Token& token); ///< Turn string token into keyword token, if possible. std::shared_ptr parseImp (bool allowEmpty = false, bool ignoreOneShot = false); ///< Will return a null-pointer, if there is nothing more to parse. std::shared_ptr parseNAry (const Token& keyword); std::shared_ptr parseText(); std::shared_ptr parseValue(); void error(); public: Parser (const CSMWorld::Data& data); bool parse (const std::string& filter, bool allowPredefined = true); ///< Discards any previous calls to parse /// /// \return Success? std::shared_ptr getFilter() const; ///< Throws an exception if the last call to parse did not return true. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/textnode.cpp000066400000000000000000000042571445372753700236210ustar00rootroot00000000000000#include "textnode.hpp" #include #include #include #include "../world/columns.hpp" #include "../world/idtablebase.hpp" CSMFilter::TextNode::TextNode (int columnId, const std::string& text) : mColumnId (columnId), mText (text) {} bool CSMFilter::TextNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { const std::map::const_iterator iter = columns.find (mColumnId); if (iter==columns.end()) throw std::logic_error ("invalid column in text node test"); if (iter->second==-1) return true; QModelIndex index = table.index (row, iter->second); QVariant data = table.data (index); QString string; if (data.type()==QVariant::String) { string = data.toString(); } else if ((data.type()==QVariant::Int || data.type()==QVariant::UInt) && CSMWorld::Columns::hasEnums (static_cast (mColumnId))) { int value = data.toInt(); std::vector> enums = CSMWorld::Columns::getEnums (static_cast (mColumnId)); if (value>=0 && value (enums.size())) string = QString::fromUtf8 (enums[value].second.c_str()); } else if (data.type()==QVariant::Bool) { string = data.toBool() ? "true" : "false"; } else if (mText.empty() && !data.isValid()) return true; else return false; /// \todo make pattern syntax configurable QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive); return regExp.exactMatch (string); } std::vector CSMFilter::TextNode::getReferencedColumns() const { return std::vector (1, mColumnId); } std::string CSMFilter::TextNode::toString (bool numericColumns) const { std::ostringstream stream; stream << "text ("; if (numericColumns) stream << mColumnId; else stream << "\"" << CSMWorld::Columns::getName (static_cast (mColumnId)) << "\""; stream << ", \"" << mText << "\")"; return stream.str(); } openmw-openmw-0.48.0/apps/opencs/model/filter/textnode.hpp000066400000000000000000000020661445372753700236220ustar00rootroot00000000000000#ifndef CSM_FILTER_TEXTNODE_H #define CSM_FILTER_TEXTNODE_H #include "leafnode.hpp" namespace CSMFilter { class TextNode : public LeafNode { int mColumnId; std::string mText; public: TextNode (int columnId, const std::string& text); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping std::vector getReferencedColumns() const override; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. std::string toString (bool numericColumns) const override; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/unarynode.cpp000066400000000000000000000010611445372753700237610ustar00rootroot00000000000000#include "unarynode.hpp" CSMFilter::UnaryNode::UnaryNode (std::shared_ptr child, const std::string& name) : mChild (child), mName (name) {} const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const { return *mChild; } CSMFilter::Node& CSMFilter::UnaryNode::getChild() { return *mChild; } std::vector CSMFilter::UnaryNode::getReferencedColumns() const { return mChild->getReferencedColumns(); } std::string CSMFilter::UnaryNode::toString (bool numericColumns) const { return mName + " " + mChild->toString (numericColumns); } openmw-openmw-0.48.0/apps/opencs/model/filter/unarynode.hpp000066400000000000000000000016041445372753700237710ustar00rootroot00000000000000#ifndef CSM_FILTER_UNARYNODE_H #define CSM_FILTER_UNARYNODE_H #include "node.hpp" namespace CSMFilter { class UnaryNode : public Node { std::shared_ptr mChild; std::string mName; public: UnaryNode (std::shared_ptr child, const std::string& name); const Node& getChild() const; Node& getChild(); std::vector getReferencedColumns() const override; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. std::string toString (bool numericColumns) const override; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/filter/valuenode.cpp000066400000000000000000000051001445372753700237350ustar00rootroot00000000000000#include "valuenode.hpp" #include #include #include "../world/columns.hpp" #include "../world/idtablebase.hpp" CSMFilter::ValueNode::ValueNode (int columnId, Type lowerType, Type upperType, double lower, double upper) : mColumnId (columnId), mLower (lower), mUpper (upper), mLowerType (lowerType), mUpperType (upperType){} bool CSMFilter::ValueNode::test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { const std::map::const_iterator iter = columns.find (mColumnId); if (iter==columns.end()) throw std::logic_error ("invalid column in value node test"); if (iter->second==-1) return true; QModelIndex index = table.index (row, iter->second); 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 (QMetaType::Float)) return false; double value = data.toDouble(); switch (mLowerType) { case Type_Closed: if (valuemUpper) return false; break; case Type_Open: if (value>=mUpper) return false; break; case Type_Infinite: break; } return true; } std::vector CSMFilter::ValueNode::getReferencedColumns() const { return std::vector (1, mColumnId); } std::string CSMFilter::ValueNode::toString (bool numericColumns) const { std::ostringstream stream; stream << "value ("; if (numericColumns) stream << mColumnId; else stream << "\"" << CSMWorld::Columns::getName (static_cast (mColumnId)) << "\""; stream << ", "; if (mLower==mUpper && mLowerType!=Type_Infinite && mUpperType!=Type_Infinite) stream << mLower; else { switch (mLowerType) { case Type_Closed: stream << "[" << mLower; break; case Type_Open: stream << "(" << mLower; break; case Type_Infinite: stream << "("; break; } stream << ", "; switch (mUpperType) { case Type_Closed: stream << mUpper << "]"; break; case Type_Open: stream << mUpper << ")"; break; case Type_Infinite: stream << ")"; break; } } stream << ")"; return stream.str(); } openmw-openmw-0.48.0/apps/opencs/model/filter/valuenode.hpp000066400000000000000000000025321445372753700237500ustar00rootroot00000000000000#ifndef CSM_FILTER_VALUENODE_H #define CSM_FILTER_VALUENODE_H #include "leafnode.hpp" namespace CSMFilter { class ValueNode : public LeafNode { public: enum Type { Type_Closed, Type_Open, Type_Infinite }; private: int mColumnId; std::string mText; double mLower; double mUpper; Type mLowerType; Type mUpperType; public: ValueNode (int columnId, Type lowerType, Type upperType, double lower, double upper); bool test (const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; ///< \return Can the specified table row pass through to filter? /// \param columns column ID to column index mapping std::vector getReferencedColumns() const override; ///< Return a list of the IDs of the columns referenced by this node. The column mapping /// passed into test as columns must contain all columns listed here. std::string toString (bool numericColumns) const override; ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/000077500000000000000000000000001445372753700211055ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/prefs/boolsetting.cpp000066400000000000000000000027461445372753700241530ustar00rootroot00000000000000#include "boolsetting.hpp" #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, bool default_) : Setting (parent, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } std::pair CSMPrefs::BoolSetting::makeWidgets (QWidget *parent) { mWidget = new QCheckBox (QString::fromUtf8 (getLabel().c_str()), parent); mWidget->setCheckState (mDefault ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); mWidget->setToolTip (tooltip); } connect (mWidget, SIGNAL (stateChanged (int)), this, SLOT (valueChanged (int))); return std::make_pair (static_cast (nullptr), mWidget); } void CSMPrefs::BoolSetting::updateWidget() { if (mWidget) { mWidget->setCheckState(Settings::Manager::getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked); } } void CSMPrefs::BoolSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); Settings::Manager::setBool (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/boolsetting.hpp000066400000000000000000000014021445372753700241440ustar00rootroot00000000000000#ifndef CSM_PREFS_BOOLSETTING_H #define CSM_PREFS_BOOLSETTING_H #include "setting.hpp" class QCheckBox; namespace CSMPrefs { class BoolSetting : public Setting { Q_OBJECT std::string mTooltip; bool mDefault; QCheckBox* mWidget; public: BoolSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, bool default_); BoolSetting& setTooltip (const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void valueChanged (int value); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/category.cpp000066400000000000000000000020361445372753700234270ustar00rootroot00000000000000 #include "category.hpp" #include #include "setting.hpp" #include "state.hpp" CSMPrefs::Category::Category (State *parent, const std::string& key) : mParent (parent), mKey (key) {} const std::string& CSMPrefs::Category::getKey() const { return mKey; } CSMPrefs::State *CSMPrefs::Category::getState() const { return mParent; } void CSMPrefs::Category::addSetting (Setting *setting) { mSettings.push_back (setting); } CSMPrefs::Category::Iterator CSMPrefs::Category::begin() { return mSettings.begin(); } CSMPrefs::Category::Iterator CSMPrefs::Category::end() { return mSettings.end(); } CSMPrefs::Setting& CSMPrefs::Category::operator[] (const std::string& key) { for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) if ((*iter)->getKey()==key) return **iter; throw std::logic_error ("Invalid user setting: " + key); } void CSMPrefs::Category::update() { for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) mParent->update (**iter); } openmw-openmw-0.48.0/apps/opencs/model/prefs/category.hpp000066400000000000000000000014251445372753700234350ustar00rootroot00000000000000#ifndef CSM_PREFS_CATEGORY_H #define CSM_PREFS_CATEGORY_H #include #include namespace CSMPrefs { class State; class Setting; class Category { public: typedef std::vector Container; typedef Container::iterator Iterator; private: State *mParent; std::string mKey; Container mSettings; public: Category (State *parent, const std::string& key); const std::string& getKey() const; State *getState() const; void addSetting (Setting *setting); Iterator begin(); Iterator end(); Setting& operator[] (const std::string& key); void update(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/coloursetting.cpp000066400000000000000000000032221445372753700245110ustar00rootroot00000000000000#include "coloursetting.hpp" #include #include #include #include #include "../../view/widget/coloreditor.hpp" #include "category.hpp" #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, QColor default_) : Setting (parent, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } std::pair CSMPrefs::ColourSetting::makeWidgets (QWidget *parent) { QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); mWidget = new CSVWidget::ColorEditor (mDefault, parent); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); label->setToolTip (tooltip); mWidget->setToolTip (tooltip); } connect (mWidget, SIGNAL (pickingFinished()), this, SLOT (valueChanged())); return std::make_pair (label, mWidget); } void CSMPrefs::ColourSetting::updateWidget() { if (mWidget) { mWidget->setColor(QString::fromStdString (Settings::Manager::getString(getKey(), getParent()->getKey()))); } } void CSMPrefs::ColourSetting::valueChanged() { CSVWidget::ColorEditor& widget = dynamic_cast (*sender()); { QMutexLocker lock (getMutex()); Settings::Manager::setString (getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/coloursetting.hpp000066400000000000000000000015241445372753700245210ustar00rootroot00000000000000#ifndef CSM_PREFS_COLOURSETTING_H #define CSM_PREFS_COLOURSETTING_H #include "setting.hpp" #include namespace CSVWidget { class ColorEditor; } namespace CSMPrefs { class ColourSetting : public Setting { Q_OBJECT std::string mTooltip; QColor mDefault; CSVWidget::ColorEditor* mWidget; public: ColourSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, QColor default_); ColourSetting& setTooltip (const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void valueChanged(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/doublesetting.cpp000066400000000000000000000042001445372753700244550ustar00rootroot00000000000000 #include "doublesetting.hpp" #include #include #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, double default_) : Setting (parent, mutex, key, label), mPrecision(2), mMin (0), mMax (std::numeric_limits::max()), mDefault (default_), mWidget(nullptr) {} CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) { mPrecision = precision; return *this; } CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) { mMin = min; mMax = max; return *this; } CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMin (double min) { mMin = min; return *this; } CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMax (double max) { mMax = max; return *this; } CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *parent) { QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); mWidget = new QDoubleSpinBox (parent); mWidget->setDecimals(mPrecision); mWidget->setRange (mMin, mMax); mWidget->setValue (mDefault); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); label->setToolTip (tooltip); mWidget->setToolTip (tooltip); } connect (mWidget, SIGNAL (valueChanged (double)), this, SLOT (valueChanged (double))); return std::make_pair (label, mWidget); } void CSMPrefs::DoubleSetting::updateWidget() { if (mWidget) { mWidget->setValue(Settings::Manager::getFloat(getKey(), getParent()->getKey())); } } void CSMPrefs::DoubleSetting::valueChanged (double value) { { QMutexLocker lock (getMutex()); Settings::Manager::setFloat (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/doublesetting.hpp000066400000000000000000000022311445372753700244640ustar00rootroot00000000000000#ifndef CSM_PREFS_DOUBLESETTING_H #define CSM_PREFS_DOUBLESETTING_H #include "setting.hpp" class QDoubleSpinBox; namespace CSMPrefs { class DoubleSetting : public Setting { Q_OBJECT int mPrecision; double mMin; double mMax; std::string mTooltip; double mDefault; QDoubleSpinBox* mWidget; public: DoubleSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, double default_); DoubleSetting& setPrecision (int precision); // defaults to [0, std::numeric_limits::max()] DoubleSetting& setRange (double min, double max); DoubleSetting& setMin (double min); DoubleSetting& setMax (double max); DoubleSetting& setTooltip (const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void valueChanged (double value); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/enumsetting.cpp000066400000000000000000000061271445372753700241610ustar00rootroot00000000000000 #include "enumsetting.hpp" #include #include #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::EnumValue::EnumValue (const std::string& value, const std::string& tooltip) : mValue (value), mTooltip (tooltip) {} CSMPrefs::EnumValue::EnumValue (const char *value) : mValue (value) {} CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValues& values) { mValues.insert (mValues.end(), values.mValues.begin(), values.mValues.end()); return *this; } CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValue& value) { mValues.push_back (value); return *this; } CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const std::string& value, const std::string& tooltip) { mValues.emplace_back(value, tooltip); return *this; } CSMPrefs::EnumSetting::EnumSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) : Setting (parent, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues (const EnumValues& values) { mValues.add (values); return *this; } CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const EnumValue& value) { mValues.add (value); return *this; } CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const std::string& value, const std::string& tooltip) { mValues.add (value, tooltip); return *this; } std::pair CSMPrefs::EnumSetting::makeWidgets (QWidget *parent) { QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); mWidget = new QComboBox (parent); int index = 0; for (int i=0; i (mValues.mValues.size()); ++i) { if (mDefault.mValue==mValues.mValues[i].mValue) index = i; mWidget->addItem (QString::fromUtf8 (mValues.mValues[i].mValue.c_str())); if (!mValues.mValues[i].mTooltip.empty()) mWidget->setItemData (i, QString::fromUtf8 (mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); } mWidget->setCurrentIndex (index); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); label->setToolTip (tooltip); } connect (mWidget, SIGNAL (currentIndexChanged (int)), this, SLOT (valueChanged (int))); return std::make_pair (label, mWidget); } void CSMPrefs::EnumSetting::updateWidget() { if (mWidget) { int index = mWidget->findText(QString::fromStdString (Settings::Manager::getString(getKey(), getParent()->getKey()))); mWidget->setCurrentIndex(index); } } void CSMPrefs::EnumSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); Settings::Manager::setString (getKey(), getParent()->getKey(), mValues.mValues.at (value).mValue); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/enumsetting.hpp000066400000000000000000000030101445372753700241520ustar00rootroot00000000000000#ifndef CSM_PREFS_ENUMSETTING_H #define CSM_PREFS_ENUMSETTING_H #include #include "setting.hpp" class QComboBox; namespace CSMPrefs { struct EnumValue { std::string mValue; std::string mTooltip; EnumValue (const std::string& value, const std::string& tooltip = ""); EnumValue (const char *value); }; struct EnumValues { std::vector mValues; EnumValues& add (const EnumValues& values); EnumValues& add (const EnumValue& value); EnumValues& add (const std::string& value, const std::string& tooltip); }; class EnumSetting : public Setting { Q_OBJECT std::string mTooltip; EnumValue mDefault; EnumValues mValues; QComboBox* mWidget; public: EnumSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_); EnumSetting& setTooltip (const std::string& tooltip); EnumSetting& addValues (const EnumValues& values); EnumSetting& addValue (const EnumValue& value); EnumSetting& addValue (const std::string& value, const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void valueChanged (int value); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/intsetting.cpp000066400000000000000000000035631445372753700240100ustar00rootroot00000000000000 #include "intsetting.hpp" #include #include #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::IntSetting::IntSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, int default_) : Setting (parent, mutex, key, label), mMin (0), mMax (std::numeric_limits::max()), mDefault (default_), mWidget(nullptr) {} CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange (int min, int max) { mMin = min; mMax = max; return *this; } CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMin (int min) { mMin = min; return *this; } CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMax (int max) { mMax = max; return *this; } CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } std::pair CSMPrefs::IntSetting::makeWidgets (QWidget *parent) { QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); mWidget = new QSpinBox (parent); mWidget->setRange (mMin, mMax); mWidget->setValue (mDefault); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); label->setToolTip (tooltip); mWidget->setToolTip (tooltip); } connect (mWidget, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); return std::make_pair (label, mWidget); } void CSMPrefs::IntSetting::updateWidget() { if (mWidget) { mWidget->setValue(Settings::Manager::getInt(getKey(), getParent()->getKey())); } } void CSMPrefs::IntSetting::valueChanged (int value) { { QMutexLocker lock (getMutex()); Settings::Manager::setInt (getKey(), getParent()->getKey(), value); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/intsetting.hpp000066400000000000000000000017611445372753700240130ustar00rootroot00000000000000#ifndef CSM_PREFS_INTSETTING_H #define CSM_PREFS_INTSETTING_H #include "setting.hpp" class QSpinBox; namespace CSMPrefs { class IntSetting : public Setting { Q_OBJECT int mMin; int mMax; std::string mTooltip; int mDefault; QSpinBox* mWidget; public: IntSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, int default_); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange (int min, int max); IntSetting& setMin (int min); IntSetting& setMax (int max); IntSetting& setTooltip (const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void valueChanged (int value); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/modifiersetting.cpp000066400000000000000000000106271445372753700250130ustar00rootroot00000000000000#include "modifiersetting.hpp" #include #include #include #include #include #include #include #include "state.hpp" #include "shortcutmanager.hpp" namespace CSMPrefs { ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) { } std::pair ModifierSetting::makeWidgets(QWidget* parent) { int modifier = 0; State::get().getShortcutManager().getModifier(getKey(), modifier); QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); widget->installEventFilter(this); // right clicking on button sets shortcut to RMB, so context menu should not appear widget->setContextMenuPolicy(Qt::PreventContextMenu); mButton = widget; connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); return std::make_pair(label, widget); } void ModifierSetting::updateWidget() { if (mButton) { std::string shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); int modifier; State::get().getShortcutManager().convertFromString(shortcut, modifier); State::get().getShortcutManager().setModifier(getKey(), modifier); resetState(); } } bool ModifierSetting::eventFilter(QObject* target, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->isAutoRepeat()) return true; int mod = keyEvent->modifiers(); int key = keyEvent->key(); return handleEvent(target, mod, key); } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouseEvent = static_cast(event); int mod = mouseEvent->modifiers(); int button = mouseEvent->button(); return handleEvent(target, mod, button); } else if (event->type() == QEvent::FocusOut) { resetState(); } return false; } bool ModifierSetting::handleEvent(QObject* target, int mod, int value) { // For potential future exceptions const int Blacklist[] = { 0 }; const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); if (!mEditorActive) { if (value == Qt::RightButton) { // Clear modifier int modifier = 0; storeValue(modifier); resetState(); } return false; } // Handle blacklist for (size_t i = 0; i < BlacklistSize; ++i) { if (value == Blacklist[i]) return true; } // Update modifier int modifier = value; storeValue(modifier); resetState(); return true; } void ModifierSetting::storeValue(int modifier) { State::get().getShortcutManager().setModifier(getKey(), modifier); // Convert to string and assign std::string value = State::get().getShortcutManager().convertToString(modifier); { QMutexLocker lock(getMutex()); Settings::Manager::setString(getKey(), getParent()->getKey(), value); } getParent()->getState()->update(*this); } void ModifierSetting::resetState() { mButton->setChecked(false); mEditorActive = false; // Button text int modifier = 0; State::get().getShortcutManager().getModifier(getKey(), modifier); QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); mButton->setText(text); } void ModifierSetting::buttonToggled(bool checked) { if (checked) mButton->setText("Press keys or click here..."); mEditorActive = checked; } } openmw-openmw-0.48.0/apps/opencs/model/prefs/modifiersetting.hpp000066400000000000000000000016171445372753700250170ustar00rootroot00000000000000#ifndef CSM_PREFS_MODIFIERSETTING_H #define CSM_PREFS_MODIFIERSETTING_H #include #include "setting.hpp" class QEvent; class QPushButton; namespace CSMPrefs { class ModifierSetting : public Setting { Q_OBJECT public: ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); std::pair makeWidgets(QWidget* parent) override; void updateWidget() override; protected: bool eventFilter(QObject* target, QEvent* event) override; private: bool handleEvent(QObject* target, int mod, int value); void storeValue(int modifier); void resetState(); QPushButton* mButton; bool mEditorActive; private slots: void buttonToggled(bool checked); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/setting.cpp000066400000000000000000000041011445372753700232620ustar00rootroot00000000000000 #include "setting.hpp" #include #include #include #include "category.hpp" #include "state.hpp" QMutex *CSMPrefs::Setting::getMutex() { return mMutex; } CSMPrefs::Setting::Setting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label) : QObject (parent->getState()), mParent (parent), mMutex (mutex), mKey (key), mLabel (label) {} CSMPrefs::Setting:: ~Setting() {} std::pair CSMPrefs::Setting::makeWidgets (QWidget *parent) { return std::pair (0, 0); } void CSMPrefs::Setting::updateWidget() { } const CSMPrefs::Category *CSMPrefs::Setting::getParent() const { return mParent; } const std::string& CSMPrefs::Setting::getKey() const { return mKey; } const std::string& CSMPrefs::Setting::getLabel() const { return mLabel; } int CSMPrefs::Setting::toInt() const { QMutexLocker lock (mMutex); return Settings::Manager::getInt (mKey, mParent->getKey()); } double CSMPrefs::Setting::toDouble() const { QMutexLocker lock (mMutex); return Settings::Manager::getFloat (mKey, mParent->getKey()); } std::string CSMPrefs::Setting::toString() const { QMutexLocker lock (mMutex); return Settings::Manager::getString (mKey, mParent->getKey()); } bool CSMPrefs::Setting::isTrue() const { QMutexLocker lock (mMutex); return Settings::Manager::getBool (mKey, mParent->getKey()); } QColor CSMPrefs::Setting::toColor() const { // toString() handles lock return QColor (QString::fromUtf8 (toString().c_str())); } bool CSMPrefs::operator== (const Setting& setting, const std::string& key) { std::string fullKey = setting.getParent()->getKey() + "/" + setting.getKey(); return fullKey==key; } bool CSMPrefs::operator== (const std::string& key, const Setting& setting) { return setting==key; } bool CSMPrefs::operator!= (const Setting& setting, const std::string& key) { return !(setting==key); } bool CSMPrefs::operator!= (const std::string& key, const Setting& setting) { return !(key==setting); } openmw-openmw-0.48.0/apps/opencs/model/prefs/setting.hpp000066400000000000000000000034001445372753700232700ustar00rootroot00000000000000#ifndef CSM_PREFS_SETTING_H #define CSM_PREFS_SETTING_H #include #include #include class QWidget; class QColor; class QMutex; namespace CSMPrefs { class Category; class Setting : public QObject { Q_OBJECT Category *mParent; QMutex *mMutex; std::string mKey; std::string mLabel; protected: QMutex *getMutex(); public: Setting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label); virtual ~Setting(); /// Return label, input widget. /// /// \note first can be a 0-pointer, which means that the label is part of the input /// widget. virtual std::pair makeWidgets (QWidget *parent); /// Updates the widget returned by makeWidgets() to the current setting. /// /// \note If make_widgets() has not been called yet then nothing happens. virtual void updateWidget(); const Category *getParent() const; const std::string& getKey() const; const std::string& getLabel() const; int toInt() const; double toDouble() const; std::string toString() const; bool isTrue() const; QColor toColor() const; }; // note: fullKeys have the format categoryKey/settingKey bool operator== (const Setting& setting, const std::string& fullKey); bool operator== (const std::string& fullKey, const Setting& setting); bool operator!= (const Setting& setting, const std::string& fullKey); bool operator!= (const std::string& fullKey, const Setting& setting); } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcut.cpp000066400000000000000000000122321445372753700234640ustar00rootroot00000000000000#include "shortcut.hpp" #include #include #include #include #include "state.hpp" #include "shortcutmanager.hpp" namespace CSMPrefs { Shortcut::Shortcut(const std::string& name, QWidget* parent) : QObject(parent) , mEnabled(true) , mName(name) , mModName("") , mSecondaryMode(SM_Ignore) , mModifier(0) , mCurrentPos(0) , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) , mAction(nullptr) { assert (parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); } Shortcut::Shortcut(const std::string& name, const std::string& modName, QWidget* parent) : QObject(parent) , mEnabled(true) , mName(name) , mModName(modName) , mSecondaryMode(SM_Ignore) , mModifier(0) , mCurrentPos(0) , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) , mAction(nullptr) { assert (parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); State::get().getShortcutManager().getModifier(modName, mModifier); } Shortcut::Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent) : QObject(parent) , mEnabled(true) , mName(name) , mModName(modName) , mSecondaryMode(secMode) , mModifier(0) , mCurrentPos(0) , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) , mAction(nullptr) { assert (parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); State::get().getShortcutManager().getModifier(modName, mModifier); } Shortcut::~Shortcut() { try { State::get().getShortcutManager().removeShortcut(this); } catch(const std::exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } bool Shortcut::isEnabled() const { return mEnabled; } const std::string& Shortcut::getName() const { return mName; } const std::string& Shortcut::getModifierName() const { return mModName; } Shortcut::SecondaryMode Shortcut::getSecondaryMode() const { return mSecondaryMode; } const QKeySequence& Shortcut::getSequence() const { return mSequence; } int Shortcut::getModifier() const { return mModifier; } int Shortcut::getPosition() const { return mCurrentPos; } int Shortcut::getLastPosition() const { return mLastPos; } Shortcut::ActivationStatus Shortcut::getActivationStatus() const { return mActivationStatus; } bool Shortcut::getModifierStatus() const { return mModifierStatus; } void Shortcut::enable(bool state) { mEnabled = state; } void Shortcut::setSequence(const QKeySequence& sequence) { mSequence = sequence; mCurrentPos = 0; mLastPos = sequence.count() - 1; if (mAction) { mAction->setText(mActionText + "\t" + State::get().getShortcutManager().convertToString(mSequence).data()); } } void Shortcut::setModifier(int modifier) { mModifier = modifier; } void Shortcut::setPosition(int pos) { mCurrentPos = pos; } void Shortcut::setActivationStatus(ActivationStatus status) { mActivationStatus = status; } void Shortcut::setModifierStatus(bool status) { mModifierStatus = status; } void Shortcut::associateAction(QAction* action) { if (mAction) { mAction->setText(mActionText); disconnect(this, SIGNAL(activated()), mAction, SLOT(trigger())); disconnect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); } mAction = action; if (mAction) { mActionText = mAction->text(); mAction->setText(mActionText + "\t" + State::get().getShortcutManager().convertToString(mSequence).data()); connect(this, SIGNAL(activated()), mAction, SLOT(trigger())); connect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); } } void Shortcut::signalActivated(bool state) { emit activated(state); } void Shortcut::signalActivated() { emit activated(); } void Shortcut::signalSecondary(bool state) { emit secondary(state); } void Shortcut::signalSecondary() { emit secondary(); } QString Shortcut::toString() const { return QString(State::get().getShortcutManager().convertToString(mSequence, mModifier).data()); } void Shortcut::actionDeleted() { mAction = nullptr; } } openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcut.hpp000066400000000000000000000064501445372753700234760ustar00rootroot00000000000000#ifndef CSM_PREFS_SHORTCUT_H #define CSM_PREFS_SHORTCUT_H #include #include #include #include class QAction; class QWidget; namespace CSMPrefs { /// A class similar in purpose to QShortcut, but with the ability to use mouse buttons class Shortcut : public QObject { Q_OBJECT public: enum ActivationStatus { AS_Regular, AS_Secondary, AS_Inactive }; enum SecondaryMode { SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active SM_Detach, ///< The secondary signal is emitted independent of the regular signal, even if not active SM_Ignore ///< The secondary signal will not ever be emitted }; Shortcut(const std::string& name, QWidget* parent); Shortcut(const std::string& name, const std::string& modName, QWidget* parent); Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent); ~Shortcut(); bool isEnabled() const; const std::string& getName() const; const std::string& getModifierName() const; SecondaryMode getSecondaryMode() const; const QKeySequence& getSequence() const; int getModifier() const; /// The position in the sequence int getPosition() const; /// The position in the sequence int getLastPosition() const; ActivationStatus getActivationStatus() const; bool getModifierStatus() const; void enable(bool state); void setSequence(const QKeySequence& sequence); void setModifier(int modifier); /// The position in the sequence void setPosition(int pos); void setActivationStatus(ActivationStatus status); void setModifierStatus(bool status); /// Appends the sequence to the QAction text, also keeps it up to date void associateAction(QAction* action); // Workaround for Qt4 signals being "protected" void signalActivated(bool state); void signalActivated(); void signalSecondary(bool state); void signalSecondary(); QString toString() const; private: bool mEnabled; std::string mName; std::string mModName; SecondaryMode mSecondaryMode; QKeySequence mSequence; int mModifier; int mCurrentPos; int mLastPos; ActivationStatus mActivationStatus; bool mModifierStatus; QAction* mAction; QString mActionText; private slots: void actionDeleted(); signals: /// Triggered when the shortcut is activated or deactivated; can be determined from \p state void activated(bool state); /// Convenience signal. void activated(); /// Triggered depending on SecondaryMode void secondary(bool state); /// Convenience signal. void secondary(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcuteventhandler.cpp000066400000000000000000000270421445372753700260710ustar00rootroot00000000000000#include "shortcuteventhandler.hpp" #include #include #include #include #include #include #include "shortcut.hpp" namespace CSMPrefs { ShortcutEventHandler::ShortcutEventHandler(QObject* parent) : QObject(parent) { } void ShortcutEventHandler::addShortcut(Shortcut* shortcut) { // Enforced by shortcut class QWidget* widget = static_cast(shortcut->parent()); // Check if widget setup is needed ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); if (shortcutListIt == mWidgetShortcuts.end()) { // Create list shortcutListIt = mWidgetShortcuts.insert(std::make_pair(widget, ShortcutList())).first; // Check if widget has a parent with shortcuts, unfortunately it is not typically set yet updateParent(widget); // Intercept widget events widget->installEventFilter(this); connect(widget, SIGNAL(destroyed()), this, SLOT(widgetDestroyed())); } // Add to list shortcutListIt->second.push_back(shortcut); } void ShortcutEventHandler::removeShortcut(Shortcut* shortcut) { // Enforced by shortcut class QWidget* widget = static_cast(shortcut->parent()); ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); if (shortcutListIt != mWidgetShortcuts.end()) { shortcutListIt->second.erase(std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), shortcutListIt->second.end()); } } bool ShortcutEventHandler::eventFilter(QObject* watched, QEvent* event) { // Process event if (event->type() == QEvent::KeyPress) { QWidget* widget = static_cast(watched); QKeyEvent* keyEvent = static_cast(event); unsigned int mod = (unsigned int) keyEvent->modifiers(); unsigned int key = (unsigned int) keyEvent->key(); if (!keyEvent->isAutoRepeat()) return activate(widget, mod, key); } else if (event->type() == QEvent::KeyRelease) { QWidget* widget = static_cast(watched); QKeyEvent* keyEvent = static_cast(event); unsigned int mod = (unsigned int) keyEvent->modifiers(); unsigned int key = (unsigned int) keyEvent->key(); if (!keyEvent->isAutoRepeat()) return deactivate(widget, mod, key); } else if (event->type() == QEvent::MouseButtonPress) { QWidget* widget = static_cast(watched); QMouseEvent* mouseEvent = static_cast(event); unsigned int mod = (unsigned int) mouseEvent->modifiers(); unsigned int button = (unsigned int) mouseEvent->button(); return activate(widget, mod, button); } else if (event->type() == QEvent::MouseButtonRelease) { QWidget* widget = static_cast(watched); QMouseEvent* mouseEvent = static_cast(event); unsigned int mod = (unsigned int) mouseEvent->modifiers(); unsigned int button = (unsigned int) mouseEvent->button(); return deactivate(widget, mod, button); } else if (event->type() == QEvent::FocusOut) { QWidget* widget = static_cast(watched); ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); // Deactivate in case events are missed for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) { Shortcut* shortcut = *it; shortcut->setPosition(0); shortcut->setModifierStatus(false); if (shortcut->getActivationStatus() == Shortcut::AS_Regular) { shortcut->setActivationStatus(Shortcut::AS_Inactive); shortcut->signalActivated(false); } else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary) { shortcut->setActivationStatus(Shortcut::AS_Inactive); shortcut->signalSecondary(false); } } } else if (event->type() == QEvent::FocusIn) { QWidget* widget = static_cast(watched); updateParent(widget); } return false; } void ShortcutEventHandler::updateParent(QWidget* widget) { QWidget* parent = widget->parentWidget(); while (parent) { ShortcutMap::iterator parentIt = mWidgetShortcuts.find(parent); if (parentIt != mWidgetShortcuts.end()) { mChildParentRelations.insert(std::make_pair(widget, parent)); updateParent(parent); break; } // Check next parent = parent->parentWidget(); } } bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button) { std::vector > potentials; bool used = false; while (widget) { ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); assert(shortcutListIt != mWidgetShortcuts.end()); // Find potential activations for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) { Shortcut* shortcut = *it; if (!shortcut->isEnabled()) continue; if (checkModifier(mod, button, shortcut, true)) used = true; if (shortcut->getActivationStatus() != Shortcut::AS_Inactive) continue; int pos = shortcut->getPosition(); int lastPos = shortcut->getLastPosition(); MatchResult result = match(mod, button, shortcut->getSequence()[pos]); if (result == Matches_WithMod || result == Matches_NoMod) { if (pos < lastPos && (result == Matches_WithMod || pos > 0)) { shortcut->setPosition(pos+1); } else if (pos == lastPos) { potentials.emplace_back(result, shortcut); } } } // Move on to parent WidgetMap::iterator widgetIt = mChildParentRelations.find(widget); widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0; } // Only activate the best match; in exact conflicts, this will favor the first shortcut added. if (!potentials.empty()) { std::stable_sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort); Shortcut* shortcut = potentials.front().second; if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace) { shortcut->setActivationStatus(Shortcut::AS_Secondary); shortcut->signalSecondary(true); shortcut->signalSecondary(); } else { shortcut->setActivationStatus(Shortcut::AS_Regular); shortcut->signalActivated(true); shortcut->signalActivated(); } used = true; } return used; } bool ShortcutEventHandler::deactivate(QWidget* widget, unsigned int mod, unsigned int button) { const int KeyMask = 0x01FFFFFF; bool used = false; while (widget) { ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); assert(shortcutListIt != mWidgetShortcuts.end()); for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) { Shortcut* shortcut = *it; if (checkModifier(mod, button, shortcut, false)) used = true; int pos = shortcut->getPosition(); MatchResult result = match(0, button, shortcut->getSequence()[pos] & KeyMask); if (result != Matches_Not) { shortcut->setPosition(0); if (shortcut->getActivationStatus() == Shortcut::AS_Regular) { shortcut->setActivationStatus(Shortcut::AS_Inactive); shortcut->signalActivated(false); used = true; } else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary) { shortcut->setActivationStatus(Shortcut::AS_Inactive); shortcut->signalSecondary(false); used = true; } } } // Move on to parent WidgetMap::iterator widgetIt = mChildParentRelations.find(widget); widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0; } return used; } bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, 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()); bool used = false; if (result != Matches_Not) { shortcut->setModifierStatus(activate); if (shortcut->getSecondaryMode() == Shortcut::SM_Detach) { if (activate) { shortcut->signalSecondary(true); shortcut->signalSecondary(); } else { shortcut->signalSecondary(false); } } else if (!activate && shortcut->getActivationStatus() == Shortcut::AS_Secondary) { shortcut->setActivationStatus(Shortcut::AS_Inactive); shortcut->setPosition(0); shortcut->signalSecondary(false); used = true; } } return used; } ShortcutEventHandler::MatchResult ShortcutEventHandler::match(unsigned int mod, unsigned int button, unsigned int value) { if ((mod | button) == value) { return Matches_WithMod; } else if (button == value) { return Matches_NoMod; } else { return Matches_Not; } } bool ShortcutEventHandler::sort(const std::pair& left, const std::pair& right) { if (left.first == Matches_WithMod && right.first == Matches_NoMod) return true; else return left.second->getPosition() > right.second->getPosition(); } void ShortcutEventHandler::widgetDestroyed() { QWidget* widget = static_cast(sender()); mWidgetShortcuts.erase(widget); mChildParentRelations.erase(widget); } } openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcuteventhandler.hpp000066400000000000000000000034051445372753700260730ustar00rootroot00000000000000#ifndef CSM_PREFS_SHORTCUT_EVENT_HANDLER_H #define CSM_PREFS_SHORTCUT_EVENT_HANDLER_H #include #include #include class QEvent; class QWidget; namespace CSMPrefs { class Shortcut; /// Users of this class should install it as an event handler class ShortcutEventHandler : public QObject { Q_OBJECT public: ShortcutEventHandler(QObject* parent); void addShortcut(Shortcut* shortcut); void removeShortcut(Shortcut* shortcut); protected: bool eventFilter(QObject* watched, QEvent* event) override; private: typedef std::vector ShortcutList; // Child, Parent typedef std::map WidgetMap; typedef std::map ShortcutMap; enum MatchResult { Matches_WithMod, Matches_NoMod, Matches_Not }; void updateParent(QWidget* widget); bool activate(QWidget* widget, unsigned int mod, unsigned int button); bool deactivate(QWidget* widget, unsigned int mod, unsigned int button); bool checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate); MatchResult match(unsigned int mod, unsigned int button, unsigned int value); // Prefers Matches_WithMod and a larger number of buttons static bool sort(const std::pair& left, const std::pair& right); WidgetMap mChildParentRelations; ShortcutMap mWidgetShortcuts; private slots: void widgetDestroyed(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcutmanager.cpp000066400000000000000000001225141445372753700250240ustar00rootroot00000000000000#include "shortcutmanager.hpp" #include #include #include "shortcut.hpp" #include "shortcuteventhandler.hpp" namespace CSMPrefs { ShortcutManager::ShortcutManager() { createLookupTables(); mEventHandler = new ShortcutEventHandler(this); } void ShortcutManager::addShortcut(Shortcut* shortcut) { mShortcuts.insert(std::make_pair(shortcut->getName(), shortcut)); mShortcuts.insert(std::make_pair(shortcut->getModifierName(), shortcut)); mEventHandler->addShortcut(shortcut); } void ShortcutManager::removeShortcut(Shortcut* shortcut) { std::pair range = mShortcuts.equal_range(shortcut->getName()); for (ShortcutMap::iterator it = range.first; it != range.second;) { if (it->second == shortcut) { mShortcuts.erase(it++); } else { ++it; } } mEventHandler->removeShortcut(shortcut); } bool ShortcutManager::getSequence(const std::string& name, QKeySequence& sequence) const { SequenceMap::const_iterator item = mSequences.find(name); if (item != mSequences.end()) { sequence = item->second; return true; } else return false; } void ShortcutManager::setSequence(const std::string& name, const QKeySequence& sequence) { // Add to map/modify SequenceMap::iterator item = mSequences.find(name); if (item != mSequences.end()) { item->second = sequence; } else { mSequences.insert(std::make_pair(name, sequence)); } // Change active shortcuts std::pair rangeS = mShortcuts.equal_range(name); for (ShortcutMap::iterator it = rangeS.first; it != rangeS.second; ++it) { it->second->setSequence(sequence); } } bool ShortcutManager::getModifier(const std::string& name, int& modifier) const { ModifierMap::const_iterator item = mModifiers.find(name); if (item != mModifiers.end()) { modifier = item->second; return true; } else return false; } void ShortcutManager::setModifier(const std::string& name, int modifier) { // Add to map/modify ModifierMap::iterator item = mModifiers.find(name); if (item != mModifiers.end()) { item->second = modifier; } else { mModifiers.insert(std::make_pair(name, modifier)); } // Change active shortcuts std::pair rangeS = mShortcuts.equal_range(name); for (ShortcutMap::iterator it = rangeS.first; it != rangeS.second; ++it) { it->second->setModifier(modifier); } } 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) { int mods = sequence[i] & ModMask; int key = sequence[i] & MouseKeyMask; if (key) { NameMap::const_iterator searchResult = mNames.find(key); if (searchResult != mNames.end()) { if (mods && i == 0) { if (mods & Qt::ControlModifier) result.append("Ctrl+"); if (mods & Qt::ShiftModifier) result.append("Shift+"); if (mods & Qt::AltModifier) result.append("Alt+"); if (mods & Qt::MetaModifier) result.append("Meta+"); if (mods & Qt::KeypadModifier) result.append("Keypad+"); if (mods & Qt::GroupSwitchModifier) result.append("GroupSwitch+"); } else if (i > 0) { result.append("+"); } result.append(searchResult->second); } } } return result; } std::string ShortcutManager::convertToString(int modifier) const { NameMap::const_iterator searchResult = mNames.find(modifier); if (searchResult != mNames.end()) { return searchResult->second; } else return ""; } std::string ShortcutManager::convertToString(const QKeySequence& sequence, int modifier) const { std::string concat = convertToString(sequence) + ";" + convertToString(modifier); return concat; } void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence) const { const int MaxKeys = 4; // A limitation of QKeySequence size_t end = data.find(';'); size_t size = std::min(end, data.size()); std::string value = data.substr(0, size); size_t start = 0; int keyPos = 0; int mods = 0; int keys[MaxKeys] = {}; while (start < value.size()) { end = data.find('+', start); end = std::min(end, value.size()); std::string name = value.substr(start, end - start); if (name == "Ctrl") { mods |= Qt::ControlModifier; } else if (name == "Shift") { mods |= Qt::ShiftModifier; } else if (name == "Alt") { mods |= Qt::AltModifier; } else if (name == "Meta") { mods |= Qt::MetaModifier; } else if (name == "Keypad") { mods |= Qt::KeypadModifier; } else if (name == "GroupSwitch") { mods |= Qt::GroupSwitchModifier; } else { KeyMap::const_iterator searchResult = mKeys.find(name); if (searchResult != mKeys.end()) { keys[keyPos] = mods | searchResult->second; mods = 0; keyPos += 1; if (keyPos >= MaxKeys) break; } } start = end + 1; } sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]); } void ShortcutManager::convertFromString(const std::string& data, int& modifier) const { size_t start = data.find(';') + 1; start = std::min(start, data.size()); std::string name = data.substr(start); KeyMap::const_iterator searchResult = mKeys.find(name); if (searchResult != mKeys.end()) { modifier = searchResult->second; } else { modifier = 0; } } void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const { convertFromString(data, sequence); convertFromString(data, modifier); } void ShortcutManager::createLookupTables() { // Mouse buttons mNames.insert(std::make_pair(Qt::LeftButton, "LMB")); mNames.insert(std::make_pair(Qt::RightButton, "RMB")); mNames.insert(std::make_pair(Qt::MiddleButton, "MMB")); mNames.insert(std::make_pair(Qt::XButton1, "Mouse4")); mNames.insert(std::make_pair(Qt::XButton2, "Mouse5")); // Keyboard buttons for (size_t i = 0; QtKeys[i].first != 0; ++i) { mNames.insert(QtKeys[i]); } // Generate inverse map for (NameMap::const_iterator it = mNames.begin(); it != mNames.end(); ++it) { mKeys.insert(std::make_pair(it->second, it->first)); } } QString ShortcutManager::processToolTip(const QString& toolTip) const { const QChar SequenceStart = '{'; const QChar SequenceEnd = '}'; QStringList substrings; int prevIndex = 0; int startIndex = toolTip.indexOf(SequenceStart); int endIndex = (startIndex != -1) ? toolTip.indexOf(SequenceEnd, startIndex) : -1; // Process every valid shortcut escape sequence while (startIndex != -1 && endIndex != -1) { int count = startIndex - prevIndex; if (count > 0) { substrings.push_back(toolTip.mid(prevIndex, count)); } // Find sequence name startIndex += 1; // '{' character count = endIndex - startIndex; if (count > 0) { QString settingName = toolTip.mid(startIndex, count); QKeySequence sequence; int modifier; if (getSequence(settingName.toUtf8().data(), sequence)) { QString value = QString::fromUtf8(convertToString(sequence).c_str()); substrings.push_back(value); } else if (getModifier(settingName.toUtf8().data(), modifier)) { QString value = QString::fromUtf8(convertToString(modifier).c_str()); substrings.push_back(value); } prevIndex = endIndex + 1; // '}' character } startIndex = toolTip.indexOf(SequenceStart, endIndex); endIndex = (startIndex != -1) ? toolTip.indexOf(SequenceEnd, startIndex) : -1; } if (prevIndex < toolTip.size()) { substrings.push_back(toolTip.mid(prevIndex)); } return substrings.join(""); } const std::pair ShortcutManager::QtKeys[] = { std::make_pair((int)Qt::Key_Space , "Space"), std::make_pair((int)Qt::Key_Exclam , "Exclam"), std::make_pair((int)Qt::Key_QuoteDbl , "QuoteDbl"), std::make_pair((int)Qt::Key_NumberSign , "NumberSign"), std::make_pair((int)Qt::Key_Dollar , "Dollar"), std::make_pair((int)Qt::Key_Percent , "Percent"), std::make_pair((int)Qt::Key_Ampersand , "Ampersand"), std::make_pair((int)Qt::Key_Apostrophe , "Apostrophe"), std::make_pair((int)Qt::Key_ParenLeft , "ParenLeft"), std::make_pair((int)Qt::Key_ParenRight , "ParenRight"), std::make_pair((int)Qt::Key_Asterisk , "Asterisk"), std::make_pair((int)Qt::Key_Plus , "Plus"), std::make_pair((int)Qt::Key_Comma , "Comma"), std::make_pair((int)Qt::Key_Minus , "Minus"), std::make_pair((int)Qt::Key_Period , "Period"), std::make_pair((int)Qt::Key_Slash , "Slash"), std::make_pair((int)Qt::Key_0 , "0"), std::make_pair((int)Qt::Key_1 , "1"), std::make_pair((int)Qt::Key_2 , "2"), std::make_pair((int)Qt::Key_3 , "3"), std::make_pair((int)Qt::Key_4 , "4"), std::make_pair((int)Qt::Key_5 , "5"), std::make_pair((int)Qt::Key_6 , "6"), std::make_pair((int)Qt::Key_7 , "7"), std::make_pair((int)Qt::Key_8 , "8"), std::make_pair((int)Qt::Key_9 , "9"), std::make_pair((int)Qt::Key_Colon , "Colon"), std::make_pair((int)Qt::Key_Semicolon , "Semicolon"), std::make_pair((int)Qt::Key_Less , "Less"), std::make_pair((int)Qt::Key_Equal , "Equal"), std::make_pair((int)Qt::Key_Greater , "Greater"), std::make_pair((int)Qt::Key_Question , "Question"), std::make_pair((int)Qt::Key_At , "At"), std::make_pair((int)Qt::Key_A , "A"), std::make_pair((int)Qt::Key_B , "B"), std::make_pair((int)Qt::Key_C , "C"), std::make_pair((int)Qt::Key_D , "D"), std::make_pair((int)Qt::Key_E , "E"), std::make_pair((int)Qt::Key_F , "F"), std::make_pair((int)Qt::Key_G , "G"), std::make_pair((int)Qt::Key_H , "H"), std::make_pair((int)Qt::Key_I , "I"), std::make_pair((int)Qt::Key_J , "J"), std::make_pair((int)Qt::Key_K , "K"), std::make_pair((int)Qt::Key_L , "L"), std::make_pair((int)Qt::Key_M , "M"), std::make_pair((int)Qt::Key_N , "N"), std::make_pair((int)Qt::Key_O , "O"), std::make_pair((int)Qt::Key_P , "P"), std::make_pair((int)Qt::Key_Q , "Q"), std::make_pair((int)Qt::Key_R , "R"), std::make_pair((int)Qt::Key_S , "S"), std::make_pair((int)Qt::Key_T , "T"), std::make_pair((int)Qt::Key_U , "U"), std::make_pair((int)Qt::Key_V , "V"), std::make_pair((int)Qt::Key_W , "W"), std::make_pair((int)Qt::Key_X , "X"), std::make_pair((int)Qt::Key_Y , "Y"), std::make_pair((int)Qt::Key_Z , "Z"), std::make_pair((int)Qt::Key_BracketLeft , "BracketLeft"), std::make_pair((int)Qt::Key_Backslash , "Backslash"), std::make_pair((int)Qt::Key_BracketRight , "BracketRight"), std::make_pair((int)Qt::Key_AsciiCircum , "AsciiCircum"), std::make_pair((int)Qt::Key_Underscore , "Underscore"), std::make_pair((int)Qt::Key_QuoteLeft , "QuoteLeft"), std::make_pair((int)Qt::Key_BraceLeft , "BraceLeft"), std::make_pair((int)Qt::Key_Bar , "Bar"), std::make_pair((int)Qt::Key_BraceRight , "BraceRight"), std::make_pair((int)Qt::Key_AsciiTilde , "AsciiTilde"), std::make_pair((int)Qt::Key_nobreakspace , "nobreakspace"), std::make_pair((int)Qt::Key_exclamdown , "exclamdown"), std::make_pair((int)Qt::Key_cent , "cent"), std::make_pair((int)Qt::Key_sterling , "sterling"), std::make_pair((int)Qt::Key_currency , "currency"), std::make_pair((int)Qt::Key_yen , "yen"), std::make_pair((int)Qt::Key_brokenbar , "brokenbar"), std::make_pair((int)Qt::Key_section , "section"), std::make_pair((int)Qt::Key_diaeresis , "diaeresis"), std::make_pair((int)Qt::Key_copyright , "copyright"), std::make_pair((int)Qt::Key_ordfeminine , "ordfeminine"), std::make_pair((int)Qt::Key_guillemotleft , "guillemotleft"), std::make_pair((int)Qt::Key_notsign , "notsign"), std::make_pair((int)Qt::Key_hyphen , "hyphen"), std::make_pair((int)Qt::Key_registered , "registered"), std::make_pair((int)Qt::Key_macron , "macron"), std::make_pair((int)Qt::Key_degree , "degree"), std::make_pair((int)Qt::Key_plusminus , "plusminus"), std::make_pair((int)Qt::Key_twosuperior , "twosuperior"), std::make_pair((int)Qt::Key_threesuperior , "threesuperior"), std::make_pair((int)Qt::Key_acute , "acute"), std::make_pair((int)Qt::Key_mu , "mu"), std::make_pair((int)Qt::Key_paragraph , "paragraph"), std::make_pair((int)Qt::Key_periodcentered , "periodcentered"), std::make_pair((int)Qt::Key_cedilla , "cedilla"), std::make_pair((int)Qt::Key_onesuperior , "onesuperior"), std::make_pair((int)Qt::Key_masculine , "masculine"), std::make_pair((int)Qt::Key_guillemotright , "guillemotright"), std::make_pair((int)Qt::Key_onequarter , "onequarter"), std::make_pair((int)Qt::Key_onehalf , "onehalf"), std::make_pair((int)Qt::Key_threequarters , "threequarters"), std::make_pair((int)Qt::Key_questiondown , "questiondown"), std::make_pair((int)Qt::Key_Agrave , "Agrave"), std::make_pair((int)Qt::Key_Aacute , "Aacute"), std::make_pair((int)Qt::Key_Acircumflex , "Acircumflex"), std::make_pair((int)Qt::Key_Atilde , "Atilde"), std::make_pair((int)Qt::Key_Adiaeresis , "Adiaeresis"), std::make_pair((int)Qt::Key_Aring , "Aring"), std::make_pair((int)Qt::Key_AE , "AE"), std::make_pair((int)Qt::Key_Ccedilla , "Ccedilla"), std::make_pair((int)Qt::Key_Egrave , "Egrave"), std::make_pair((int)Qt::Key_Eacute , "Eacute"), std::make_pair((int)Qt::Key_Ecircumflex , "Ecircumflex"), std::make_pair((int)Qt::Key_Ediaeresis , "Ediaeresis"), std::make_pair((int)Qt::Key_Igrave , "Igrave"), std::make_pair((int)Qt::Key_Iacute , "Iacute"), std::make_pair((int)Qt::Key_Icircumflex , "Icircumflex"), std::make_pair((int)Qt::Key_Idiaeresis , "Idiaeresis"), std::make_pair((int)Qt::Key_ETH , "ETH"), std::make_pair((int)Qt::Key_Ntilde , "Ntilde"), std::make_pair((int)Qt::Key_Ograve , "Ograve"), std::make_pair((int)Qt::Key_Oacute , "Oacute"), std::make_pair((int)Qt::Key_Ocircumflex , "Ocircumflex"), std::make_pair((int)Qt::Key_Otilde , "Otilde"), std::make_pair((int)Qt::Key_Odiaeresis , "Odiaeresis"), std::make_pair((int)Qt::Key_multiply , "multiply"), std::make_pair((int)Qt::Key_Ooblique , "Ooblique"), std::make_pair((int)Qt::Key_Ugrave , "Ugrave"), std::make_pair((int)Qt::Key_Uacute , "Uacute"), std::make_pair((int)Qt::Key_Ucircumflex , "Ucircumflex"), std::make_pair((int)Qt::Key_Udiaeresis , "Udiaeresis"), std::make_pair((int)Qt::Key_Yacute , "Yacute"), std::make_pair((int)Qt::Key_THORN , "THORN"), std::make_pair((int)Qt::Key_ssharp , "ssharp"), std::make_pair((int)Qt::Key_division , "division"), std::make_pair((int)Qt::Key_ydiaeresis , "ydiaeresis"), std::make_pair((int)Qt::Key_Escape , "Escape"), std::make_pair((int)Qt::Key_Tab , "Tab"), std::make_pair((int)Qt::Key_Backtab , "Backtab"), std::make_pair((int)Qt::Key_Backspace , "Backspace"), std::make_pair((int)Qt::Key_Return , "Return"), std::make_pair((int)Qt::Key_Enter , "Enter"), std::make_pair((int)Qt::Key_Insert , "Insert"), std::make_pair((int)Qt::Key_Delete , "Delete"), std::make_pair((int)Qt::Key_Pause , "Pause"), std::make_pair((int)Qt::Key_Print , "Print"), std::make_pair((int)Qt::Key_SysReq , "SysReq"), std::make_pair((int)Qt::Key_Clear , "Clear"), std::make_pair((int)Qt::Key_Home , "Home"), std::make_pair((int)Qt::Key_End , "End"), std::make_pair((int)Qt::Key_Left , "Left"), std::make_pair((int)Qt::Key_Up , "Up"), std::make_pair((int)Qt::Key_Right , "Right"), std::make_pair((int)Qt::Key_Down , "Down"), std::make_pair((int)Qt::Key_PageUp , "PageUp"), std::make_pair((int)Qt::Key_PageDown , "PageDown"), std::make_pair((int)Qt::Key_Shift , "Shift"), std::make_pair((int)Qt::Key_Control , "Control"), std::make_pair((int)Qt::Key_Meta , "Meta"), std::make_pair((int)Qt::Key_Alt , "Alt"), std::make_pair((int)Qt::Key_CapsLock , "CapsLock"), std::make_pair((int)Qt::Key_NumLock , "NumLock"), std::make_pair((int)Qt::Key_ScrollLock , "ScrollLock"), std::make_pair((int)Qt::Key_F1 , "F1"), std::make_pair((int)Qt::Key_F2 , "F2"), std::make_pair((int)Qt::Key_F3 , "F3"), std::make_pair((int)Qt::Key_F4 , "F4"), std::make_pair((int)Qt::Key_F5 , "F5"), std::make_pair((int)Qt::Key_F6 , "F6"), std::make_pair((int)Qt::Key_F7 , "F7"), std::make_pair((int)Qt::Key_F8 , "F8"), std::make_pair((int)Qt::Key_F9 , "F9"), std::make_pair((int)Qt::Key_F10 , "F10"), std::make_pair((int)Qt::Key_F11 , "F11"), std::make_pair((int)Qt::Key_F12 , "F12"), std::make_pair((int)Qt::Key_F13 , "F13"), std::make_pair((int)Qt::Key_F14 , "F14"), std::make_pair((int)Qt::Key_F15 , "F15"), std::make_pair((int)Qt::Key_F16 , "F16"), std::make_pair((int)Qt::Key_F17 , "F17"), std::make_pair((int)Qt::Key_F18 , "F18"), std::make_pair((int)Qt::Key_F19 , "F19"), std::make_pair((int)Qt::Key_F20 , "F20"), std::make_pair((int)Qt::Key_F21 , "F21"), std::make_pair((int)Qt::Key_F22 , "F22"), std::make_pair((int)Qt::Key_F23 , "F23"), std::make_pair((int)Qt::Key_F24 , "F24"), std::make_pair((int)Qt::Key_F25 , "F25"), std::make_pair((int)Qt::Key_F26 , "F26"), std::make_pair((int)Qt::Key_F27 , "F27"), std::make_pair((int)Qt::Key_F28 , "F28"), std::make_pair((int)Qt::Key_F29 , "F29"), std::make_pair((int)Qt::Key_F30 , "F30"), std::make_pair((int)Qt::Key_F31 , "F31"), std::make_pair((int)Qt::Key_F32 , "F32"), std::make_pair((int)Qt::Key_F33 , "F33"), std::make_pair((int)Qt::Key_F34 , "F34"), std::make_pair((int)Qt::Key_F35 , "F35"), std::make_pair((int)Qt::Key_Super_L , "Super_L"), std::make_pair((int)Qt::Key_Super_R , "Super_R"), std::make_pair((int)Qt::Key_Menu , "Menu"), std::make_pair((int)Qt::Key_Hyper_L , "Hyper_L"), std::make_pair((int)Qt::Key_Hyper_R , "Hyper_R"), std::make_pair((int)Qt::Key_Help , "Help"), std::make_pair((int)Qt::Key_Direction_L , "Direction_L"), std::make_pair((int)Qt::Key_Direction_R , "Direction_R"), std::make_pair((int)Qt::Key_Back , "Back"), std::make_pair((int)Qt::Key_Forward , "Forward"), std::make_pair((int)Qt::Key_Stop , "Stop"), std::make_pair((int)Qt::Key_Refresh , "Refresh"), std::make_pair((int)Qt::Key_VolumeDown , "VolumeDown"), std::make_pair((int)Qt::Key_VolumeMute , "VolumeMute"), std::make_pair((int)Qt::Key_VolumeUp , "VolumeUp"), std::make_pair((int)Qt::Key_BassBoost , "BassBoost"), std::make_pair((int)Qt::Key_BassUp , "BassUp"), std::make_pair((int)Qt::Key_BassDown , "BassDown"), std::make_pair((int)Qt::Key_TrebleUp , "TrebleUp"), std::make_pair((int)Qt::Key_TrebleDown , "TrebleDown"), std::make_pair((int)Qt::Key_MediaPlay , "MediaPlay"), std::make_pair((int)Qt::Key_MediaStop , "MediaStop"), std::make_pair((int)Qt::Key_MediaPrevious , "MediaPrevious"), std::make_pair((int)Qt::Key_MediaNext , "MediaNext"), std::make_pair((int)Qt::Key_MediaRecord , "MediaRecord"), std::make_pair((int)Qt::Key_MediaPause , "MediaPause"), std::make_pair((int)Qt::Key_MediaTogglePlayPause , "MediaTogglePlayPause"), std::make_pair((int)Qt::Key_HomePage , "HomePage"), std::make_pair((int)Qt::Key_Favorites , "Favorites"), std::make_pair((int)Qt::Key_Search , "Search"), std::make_pair((int)Qt::Key_Standby , "Standby"), std::make_pair((int)Qt::Key_OpenUrl , "OpenUrl"), std::make_pair((int)Qt::Key_LaunchMail , "LaunchMail"), std::make_pair((int)Qt::Key_LaunchMedia , "LaunchMedia"), std::make_pair((int)Qt::Key_Launch0 , "Launch0"), std::make_pair((int)Qt::Key_Launch1 , "Launch1"), std::make_pair((int)Qt::Key_Launch2 , "Launch2"), std::make_pair((int)Qt::Key_Launch3 , "Launch3"), std::make_pair((int)Qt::Key_Launch4 , "Launch4"), std::make_pair((int)Qt::Key_Launch5 , "Launch5"), std::make_pair((int)Qt::Key_Launch6 , "Launch6"), std::make_pair((int)Qt::Key_Launch7 , "Launch7"), std::make_pair((int)Qt::Key_Launch8 , "Launch8"), std::make_pair((int)Qt::Key_Launch9 , "Launch9"), std::make_pair((int)Qt::Key_LaunchA , "LaunchA"), std::make_pair((int)Qt::Key_LaunchB , "LaunchB"), std::make_pair((int)Qt::Key_LaunchC , "LaunchC"), std::make_pair((int)Qt::Key_LaunchD , "LaunchD"), std::make_pair((int)Qt::Key_LaunchE , "LaunchE"), std::make_pair((int)Qt::Key_LaunchF , "LaunchF"), std::make_pair((int)Qt::Key_MonBrightnessUp , "MonBrightnessUp"), std::make_pair((int)Qt::Key_MonBrightnessDown , "MonBrightnessDown"), std::make_pair((int)Qt::Key_KeyboardLightOnOff , "KeyboardLightOnOff"), std::make_pair((int)Qt::Key_KeyboardBrightnessUp , "KeyboardBrightnessUp"), std::make_pair((int)Qt::Key_KeyboardBrightnessDown , "KeyboardBrightnessDown"), std::make_pair((int)Qt::Key_PowerOff , "PowerOff"), std::make_pair((int)Qt::Key_WakeUp , "WakeUp"), std::make_pair((int)Qt::Key_Eject , "Eject"), std::make_pair((int)Qt::Key_ScreenSaver , "ScreenSaver"), std::make_pair((int)Qt::Key_WWW , "WWW"), std::make_pair((int)Qt::Key_Memo , "Memo"), std::make_pair((int)Qt::Key_LightBulb , "LightBulb"), std::make_pair((int)Qt::Key_Shop , "Shop"), std::make_pair((int)Qt::Key_History , "History"), std::make_pair((int)Qt::Key_AddFavorite , "AddFavorite"), std::make_pair((int)Qt::Key_HotLinks , "HotLinks"), std::make_pair((int)Qt::Key_BrightnessAdjust , "BrightnessAdjust"), std::make_pair((int)Qt::Key_Finance , "Finance"), std::make_pair((int)Qt::Key_Community , "Community"), std::make_pair((int)Qt::Key_AudioRewind , "AudioRewind"), std::make_pair((int)Qt::Key_BackForward , "BackForward"), std::make_pair((int)Qt::Key_ApplicationLeft , "ApplicationLeft"), std::make_pair((int)Qt::Key_ApplicationRight , "ApplicationRight"), std::make_pair((int)Qt::Key_Book , "Book"), std::make_pair((int)Qt::Key_CD , "CD"), std::make_pair((int)Qt::Key_Calculator , "Calculator"), std::make_pair((int)Qt::Key_ToDoList , "ToDoList"), std::make_pair((int)Qt::Key_ClearGrab , "ClearGrab"), std::make_pair((int)Qt::Key_Close , "Close"), std::make_pair((int)Qt::Key_Copy , "Copy"), std::make_pair((int)Qt::Key_Cut , "Cut"), std::make_pair((int)Qt::Key_Display , "Display"), std::make_pair((int)Qt::Key_DOS , "DOS"), std::make_pair((int)Qt::Key_Documents , "Documents"), std::make_pair((int)Qt::Key_Excel , "Excel"), std::make_pair((int)Qt::Key_Explorer , "Explorer"), std::make_pair((int)Qt::Key_Game , "Game"), std::make_pair((int)Qt::Key_Go , "Go"), std::make_pair((int)Qt::Key_iTouch , "iTouch"), std::make_pair((int)Qt::Key_LogOff , "LogOff"), std::make_pair((int)Qt::Key_Market , "Market"), std::make_pair((int)Qt::Key_Meeting , "Meeting"), std::make_pair((int)Qt::Key_MenuKB , "MenuKB"), std::make_pair((int)Qt::Key_MenuPB , "MenuPB"), std::make_pair((int)Qt::Key_MySites , "MySites"), std::make_pair((int)Qt::Key_News , "News"), std::make_pair((int)Qt::Key_OfficeHome , "OfficeHome"), std::make_pair((int)Qt::Key_Option , "Option"), std::make_pair((int)Qt::Key_Paste , "Paste"), std::make_pair((int)Qt::Key_Phone , "Phone"), std::make_pair((int)Qt::Key_Calendar , "Calendar"), std::make_pair((int)Qt::Key_Reply , "Reply"), std::make_pair((int)Qt::Key_Reload , "Reload"), std::make_pair((int)Qt::Key_RotateWindows , "RotateWindows"), std::make_pair((int)Qt::Key_RotationPB , "RotationPB"), std::make_pair((int)Qt::Key_RotationKB , "RotationKB"), std::make_pair((int)Qt::Key_Save , "Save"), std::make_pair((int)Qt::Key_Send , "Send"), std::make_pair((int)Qt::Key_Spell , "Spell"), std::make_pair((int)Qt::Key_SplitScreen , "SplitScreen"), std::make_pair((int)Qt::Key_Support , "Support"), std::make_pair((int)Qt::Key_TaskPane , "TaskPane"), std::make_pair((int)Qt::Key_Terminal , "Terminal"), std::make_pair((int)Qt::Key_Tools , "Tools"), std::make_pair((int)Qt::Key_Travel , "Travel"), std::make_pair((int)Qt::Key_Video , "Video"), std::make_pair((int)Qt::Key_Word , "Word"), std::make_pair((int)Qt::Key_Xfer , "Xfer"), std::make_pair((int)Qt::Key_ZoomIn , "ZoomIn"), std::make_pair((int)Qt::Key_ZoomOut , "ZoomOut"), std::make_pair((int)Qt::Key_Away , "Away"), std::make_pair((int)Qt::Key_Messenger , "Messenger"), std::make_pair((int)Qt::Key_WebCam , "WebCam"), std::make_pair((int)Qt::Key_MailForward , "MailForward"), std::make_pair((int)Qt::Key_Pictures , "Pictures"), std::make_pair((int)Qt::Key_Music , "Music"), std::make_pair((int)Qt::Key_Battery , "Battery"), std::make_pair((int)Qt::Key_Bluetooth , "Bluetooth"), std::make_pair((int)Qt::Key_WLAN , "WLAN"), std::make_pair((int)Qt::Key_UWB , "UWB"), std::make_pair((int)Qt::Key_AudioForward , "AudioForward"), std::make_pair((int)Qt::Key_AudioRepeat , "AudioRepeat"), std::make_pair((int)Qt::Key_AudioRandomPlay , "AudioRandomPlay"), std::make_pair((int)Qt::Key_Subtitle , "Subtitle"), std::make_pair((int)Qt::Key_AudioCycleTrack , "AudioCycleTrack"), std::make_pair((int)Qt::Key_Time , "Time"), std::make_pair((int)Qt::Key_Hibernate , "Hibernate"), std::make_pair((int)Qt::Key_View , "View"), std::make_pair((int)Qt::Key_TopMenu , "TopMenu"), std::make_pair((int)Qt::Key_PowerDown , "PowerDown"), std::make_pair((int)Qt::Key_Suspend , "Suspend"), std::make_pair((int)Qt::Key_ContrastAdjust , "ContrastAdjust"), std::make_pair((int)Qt::Key_LaunchG , "LaunchG"), std::make_pair((int)Qt::Key_LaunchH , "LaunchH"), std::make_pair((int)Qt::Key_TouchpadToggle , "TouchpadToggle"), std::make_pair((int)Qt::Key_TouchpadOn , "TouchpadOn"), std::make_pair((int)Qt::Key_TouchpadOff , "TouchpadOff"), std::make_pair((int)Qt::Key_MicMute , "MicMute"), std::make_pair((int)Qt::Key_Red , "Red"), std::make_pair((int)Qt::Key_Green , "Green"), std::make_pair((int)Qt::Key_Yellow , "Yellow"), std::make_pair((int)Qt::Key_Blue , "Blue"), std::make_pair((int)Qt::Key_ChannelUp , "ChannelUp"), std::make_pair((int)Qt::Key_ChannelDown , "ChannelDown"), std::make_pair((int)Qt::Key_Guide , "Guide"), std::make_pair((int)Qt::Key_Info , "Info"), std::make_pair((int)Qt::Key_Settings , "Settings"), std::make_pair((int)Qt::Key_MicVolumeUp , "MicVolumeUp"), std::make_pair((int)Qt::Key_MicVolumeDown , "MicVolumeDown"), std::make_pair((int)Qt::Key_New , "New"), std::make_pair((int)Qt::Key_Open , "Open"), std::make_pair((int)Qt::Key_Find , "Find"), std::make_pair((int)Qt::Key_Undo , "Undo"), std::make_pair((int)Qt::Key_Redo , "Redo"), std::make_pair((int)Qt::Key_AltGr , "AltGr"), std::make_pair((int)Qt::Key_Multi_key , "Multi_key"), std::make_pair((int)Qt::Key_Kanji , "Kanji"), std::make_pair((int)Qt::Key_Muhenkan , "Muhenkan"), std::make_pair((int)Qt::Key_Henkan , "Henkan"), std::make_pair((int)Qt::Key_Romaji , "Romaji"), std::make_pair((int)Qt::Key_Hiragana , "Hiragana"), std::make_pair((int)Qt::Key_Katakana , "Katakana"), std::make_pair((int)Qt::Key_Hiragana_Katakana , "Hiragana_Katakana"), std::make_pair((int)Qt::Key_Zenkaku , "Zenkaku"), std::make_pair((int)Qt::Key_Hankaku , "Hankaku"), std::make_pair((int)Qt::Key_Zenkaku_Hankaku , "Zenkaku_Hankaku"), std::make_pair((int)Qt::Key_Touroku , "Touroku"), std::make_pair((int)Qt::Key_Massyo , "Massyo"), std::make_pair((int)Qt::Key_Kana_Lock , "Kana_Lock"), std::make_pair((int)Qt::Key_Kana_Shift , "Kana_Shift"), std::make_pair((int)Qt::Key_Eisu_Shift , "Eisu_Shift"), std::make_pair((int)Qt::Key_Eisu_toggle , "Eisu_toggle"), std::make_pair((int)Qt::Key_Hangul , "Hangul"), std::make_pair((int)Qt::Key_Hangul_Start , "Hangul_Start"), std::make_pair((int)Qt::Key_Hangul_End , "Hangul_End"), std::make_pair((int)Qt::Key_Hangul_Hanja , "Hangul_Hanja"), std::make_pair((int)Qt::Key_Hangul_Jamo , "Hangul_Jamo"), std::make_pair((int)Qt::Key_Hangul_Romaja , "Hangul_Romaja"), std::make_pair((int)Qt::Key_Codeinput , "Codeinput"), std::make_pair((int)Qt::Key_Hangul_Jeonja , "Hangul_Jeonja"), std::make_pair((int)Qt::Key_Hangul_Banja , "Hangul_Banja"), std::make_pair((int)Qt::Key_Hangul_PreHanja , "Hangul_PreHanja"), std::make_pair((int)Qt::Key_Hangul_PostHanja , "Hangul_PostHanja"), std::make_pair((int)Qt::Key_SingleCandidate , "SingleCandidate"), std::make_pair((int)Qt::Key_MultipleCandidate , "MultipleCandidate"), std::make_pair((int)Qt::Key_PreviousCandidate , "PreviousCandidate"), std::make_pair((int)Qt::Key_Hangul_Special , "Hangul_Special"), std::make_pair((int)Qt::Key_Mode_switch , "Mode_switch"), std::make_pair((int)Qt::Key_Dead_Grave , "Dead_Grave"), std::make_pair((int)Qt::Key_Dead_Acute , "Dead_Acute"), std::make_pair((int)Qt::Key_Dead_Circumflex , "Dead_Circumflex"), std::make_pair((int)Qt::Key_Dead_Tilde , "Dead_Tilde"), std::make_pair((int)Qt::Key_Dead_Macron , "Dead_Macron"), std::make_pair((int)Qt::Key_Dead_Breve , "Dead_Breve"), std::make_pair((int)Qt::Key_Dead_Abovedot , "Dead_Abovedot"), std::make_pair((int)Qt::Key_Dead_Diaeresis , "Dead_Diaeresis"), std::make_pair((int)Qt::Key_Dead_Abovering , "Dead_Abovering"), std::make_pair((int)Qt::Key_Dead_Doubleacute , "Dead_Doubleacute"), std::make_pair((int)Qt::Key_Dead_Caron , "Dead_Caron"), std::make_pair((int)Qt::Key_Dead_Cedilla , "Dead_Cedilla"), std::make_pair((int)Qt::Key_Dead_Ogonek , "Dead_Ogonek"), std::make_pair((int)Qt::Key_Dead_Iota , "Dead_Iota"), std::make_pair((int)Qt::Key_Dead_Voiced_Sound , "Dead_Voiced_Sound"), std::make_pair((int)Qt::Key_Dead_Semivoiced_Sound , "Dead_Semivoiced_Sound"), std::make_pair((int)Qt::Key_Dead_Belowdot , "Dead_Belowdot"), std::make_pair((int)Qt::Key_Dead_Hook , "Dead_Hook"), std::make_pair((int)Qt::Key_Dead_Horn , "Dead_Horn"), std::make_pair((int)Qt::Key_MediaLast , "MediaLast"), std::make_pair((int)Qt::Key_Select , "Select"), std::make_pair((int)Qt::Key_Yes , "Yes"), std::make_pair((int)Qt::Key_No , "No"), std::make_pair((int)Qt::Key_Cancel , "Cancel"), std::make_pair((int)Qt::Key_Printer , "Printer"), std::make_pair((int)Qt::Key_Execute , "Execute"), std::make_pair((int)Qt::Key_Sleep , "Sleep"), std::make_pair((int)Qt::Key_Play , "Play"), std::make_pair((int)Qt::Key_Zoom , "Zoom"), std::make_pair((int)Qt::Key_Exit , "Exit"), std::make_pair((int)Qt::Key_Context1 , "Context1"), std::make_pair((int)Qt::Key_Context2 , "Context2"), std::make_pair((int)Qt::Key_Context3 , "Context3"), std::make_pair((int)Qt::Key_Context4 , "Context4"), std::make_pair((int)Qt::Key_Call , "Call"), std::make_pair((int)Qt::Key_Hangup , "Hangup"), std::make_pair((int)Qt::Key_Flip , "Flip"), std::make_pair((int)Qt::Key_ToggleCallHangup , "ToggleCallHangup"), std::make_pair((int)Qt::Key_VoiceDial , "VoiceDial"), std::make_pair((int)Qt::Key_LastNumberRedial , "LastNumberRedial"), std::make_pair((int)Qt::Key_Camera , "Camera"), std::make_pair((int)Qt::Key_CameraFocus , "CameraFocus"), std::make_pair(0 , (const char*) nullptr) }; } openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcutmanager.hpp000066400000000000000000000044721445372753700250330ustar00rootroot00000000000000#ifndef CSM_PREFS_SHORTCUTMANAGER_H #define CSM_PREFS_SHORTCUTMANAGER_H #include #include #include #include namespace CSMPrefs { class Shortcut; class ShortcutEventHandler; /// Class used to track and update shortcuts/sequences class ShortcutManager : public QObject { Q_OBJECT public: ShortcutManager(); /// The shortcut class will do this automatically void addShortcut(Shortcut* shortcut); /// The shortcut class will do this automatically void removeShortcut(Shortcut* shortcut); bool getSequence(const std::string& name, QKeySequence& sequence) const; void setSequence(const std::string& name, const QKeySequence& sequence); bool getModifier(const std::string& name, int& modifier) const; void setModifier(const std::string& name, int modifier); std::string convertToString(const QKeySequence& sequence) const; std::string convertToString(int modifier) const; std::string convertToString(const QKeySequence& sequence, int modifier) const; void convertFromString(const std::string& data, QKeySequence& sequence) const; void convertFromString(const std::string& data, int& modifier) const; void convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const; /// Replaces "{sequence-name}" or "{modifier-name}" with the appropriate text QString processToolTip(const QString& toolTip) const; private: // Need a multimap in case multiple shortcuts share the same name typedef std::multimap ShortcutMap; typedef std::map SequenceMap; typedef std::map ModifierMap; typedef std::map NameMap; typedef std::map KeyMap; ShortcutMap mShortcuts; SequenceMap mSequences; ModifierMap mModifiers; NameMap mNames; KeyMap mKeys; ShortcutEventHandler* mEventHandler; void createLookupTables(); static const std::pair QtKeys[]; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcutsetting.cpp000066400000000000000000000136451445372753700250730ustar00rootroot00000000000000#include "shortcutsetting.hpp" #include #include #include #include #include #include #include #include #include "state.hpp" #include "shortcutmanager.hpp" namespace CSMPrefs { ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) , mEditorPos(0) { for (int i = 0; i < MaxKeys; ++i) { mEditorKeys[i] = 0; } } std::pair ShortcutSetting::makeWidgets(QWidget* parent) { QKeySequence sequence; State::get().getShortcutManager().getSequence(getKey(), sequence); QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); widget->installEventFilter(this); // right clicking on button sets shortcut to RMB, so context menu should not appear widget->setContextMenuPolicy(Qt::PreventContextMenu); mButton = widget; connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); return std::make_pair(label, widget); } void ShortcutSetting::updateWidget() { if (mButton) { std::string shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); QKeySequence sequence; State::get().getShortcutManager().convertFromString(shortcut, sequence); State::get().getShortcutManager().setSequence(getKey(), sequence); resetState(); } } bool ShortcutSetting::eventFilter(QObject* target, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->isAutoRepeat()) return true; int mod = keyEvent->modifiers(); int key = keyEvent->key(); return handleEvent(target, mod, key, true); } else if (event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->isAutoRepeat()) return true; int mod = keyEvent->modifiers(); int key = keyEvent->key(); return handleEvent(target, mod, key, false); } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouseEvent = static_cast(event); int mod = mouseEvent->modifiers(); int key = mouseEvent->button(); return handleEvent(target, mod, key, true); } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent* mouseEvent = static_cast(event); int mod = mouseEvent->modifiers(); int key = mouseEvent->button(); return handleEvent(target, mod, key, false); } else if (event->type() == QEvent::FocusOut) { resetState(); } return false; } bool ShortcutSetting::handleEvent(QObject* target, int mod, int value, bool active) { // Modifiers are handled differently const int Blacklist[] = { Qt::Key_Shift, Qt::Key_Control, Qt::Key_Meta, Qt::Key_Alt, Qt::Key_AltGr }; const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); if (!mEditorActive) { if (value == Qt::RightButton && !active) { // Clear sequence QKeySequence sequence = QKeySequence(0, 0, 0, 0); storeValue(sequence); resetState(); } return false; } // Handle blacklist for (size_t i = 0; i < BlacklistSize; ++i) { if (value == Blacklist[i]) return true; } if (!active || mEditorPos >= MaxKeys) { // Update key QKeySequence sequence = QKeySequence(mEditorKeys[0], mEditorKeys[1], mEditorKeys[2], mEditorKeys[3]); storeValue(sequence); resetState(); } else { if (mEditorPos == 0) { mEditorKeys[0] = mod | value; } else { mEditorKeys[mEditorPos] = value; } mEditorPos += 1; } return true; } void ShortcutSetting::storeValue(const QKeySequence& sequence) { State::get().getShortcutManager().setSequence(getKey(), sequence); // Convert to string and assign std::string value = State::get().getShortcutManager().convertToString(sequence); { QMutexLocker lock(getMutex()); Settings::Manager::setString(getKey(), getParent()->getKey(), value); } getParent()->getState()->update(*this); } void ShortcutSetting::resetState() { mButton->setChecked(false); mEditorActive = false; mEditorPos = 0; for (int i = 0; i < MaxKeys; ++i) { mEditorKeys[i] = 0; } // Button text QKeySequence sequence; State::get().getShortcutManager().getSequence(getKey(), sequence); QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); mButton->setText(text); } void ShortcutSetting::buttonToggled(bool checked) { if (checked) mButton->setText("Press keys or click here..."); mEditorActive = checked; } } openmw-openmw-0.48.0/apps/opencs/model/prefs/shortcutsetting.hpp000066400000000000000000000020551445372753700250710ustar00rootroot00000000000000#ifndef CSM_PREFS_SHORTCUTSETTING_H #define CSM_PREFS_SHORTCUTSETTING_H #include #include "setting.hpp" class QEvent; class QPushButton; namespace CSMPrefs { class ShortcutSetting : public Setting { Q_OBJECT public: ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); std::pair makeWidgets(QWidget* parent) override; void updateWidget() override; protected: bool eventFilter(QObject* target, QEvent* event) override; private: bool handleEvent(QObject* target, int mod, int value, bool active); void storeValue(const QKeySequence& sequence); void resetState(); static constexpr int MaxKeys = 4; QPushButton* mButton; bool mEditorActive; int mEditorPos; int mEditorKeys[MaxKeys]; private slots: void buttonToggled(bool checked); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/state.cpp000066400000000000000000001112021445372753700227260ustar00rootroot00000000000000 #include "state.hpp" #include #include #include #include #include "intsetting.hpp" #include "doublesetting.hpp" #include "boolsetting.hpp" #include "coloursetting.hpp" #include "shortcutsetting.hpp" #include "modifiersetting.hpp" CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::declare() { declareCategory ("Windows"); declareInt ("default-width", "Default window width", 800). setTooltip ("Newly opened top-level windows will open with this width."). setMin (80); declareInt ("default-height", "Default window height", 600). setTooltip ("Newly opened top-level windows will open with this height."). setMin (80); declareBool ("show-statusbar", "Show Status Bar", true). setTooltip ("If a newly open top level window is showing status bars or not. " " Note that this does not affect existing windows."); declareSeparator(); declareBool ("reuse", "Reuse Subviews", true). setTooltip ("When a new subview is requested and a matching subview already " " exist, do not open a new subview and use the existing one instead."); declareInt ("max-subviews", "Maximum number of subviews per top-level window", 256). setTooltip ("If the maximum number is reached and a new subview is opened " "it will be placed into a new top-level window."). setRange (1, 256); declareBool ("hide-subview", "Hide single subview", false). setTooltip ("When a view contains only a single subview, hide the subview title " "bar and if this subview is closed also close the view (unless it is the last " "view for this document)"); declareInt ("minimum-width", "Minimum subview width", 325). setTooltip ("Minimum width of subviews."). setRange (50, 10000); declareSeparator(); EnumValue scrollbarOnly ("Scrollbar Only", "Simple addition of scrollbars, the view window " "does not grow automatically."); declareEnum ("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly). addValue (scrollbarOnly). addValue ("Grow Only", "The view window grows as subviews are added. No scrollbars."). addValue ("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); declareBool ("grow-limit", "Grow Limit Screen", false). 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."); declareCategory ("Records"); EnumValue iconAndText ("Icon and Text"); EnumValues recordValues; recordValues.add (iconAndText).add ("Icon Only").add ("Text Only"); declareEnum ("status-format", "Modification status display format", iconAndText). addValues (recordValues); declareEnum ("type-format", "ID type display format", iconAndText). addValues (recordValues); declareCategory ("ID Tables"); EnumValue inPlaceEdit ("Edit in Place", "Edit the clicked cell"); EnumValue editRecord ("Edit Record", "Open a dialogue subview for the clicked record"); EnumValue view ("View", "Open a scene subview for the clicked record (not available everywhere)"); EnumValue editRecordAndClose ("Edit Record and Close"); EnumValues doubleClickValues; doubleClickValues.add (inPlaceEdit).add (editRecord).add (view).add ("Revert"). add ("Delete").add (editRecordAndClose). add ("View and Close", "Open a scene subview for the clicked record and close the table subview"); declareEnum ("double", "Double Click", inPlaceEdit).addValues (doubleClickValues); declareEnum ("double-s", "Shift Double Click", editRecord).addValues (doubleClickValues); declareEnum ("double-c", "Control Double Click", view).addValues (doubleClickValues); declareEnum ("double-sc", "Shift Control Double Click", editRecordAndClose).addValues (doubleClickValues); declareSeparator(); EnumValue jumpAndSelect ("Jump and Select", "Scroll new record into view and make it the selection"); declareEnum ("jump-to-added", "Action on adding or cloning a record", jumpAndSelect). addValue (jumpAndSelect). addValue ("Jump Only", "Scroll new record into view"). addValue ("No Jump", "No special action"); declareBool ("extended-config", "Manually specify affected record types for an extended delete/revert", false). setTooltip ("Delete and revert commands have an extended form that also affects " "associated records.\n\n" "If this option is enabled, types of affected records are selected " "manually before a command execution.\nOtherwise, all associated " "records are deleted/reverted immediately."); declareBool ("subview-new-window", "Open Record in new window", false) .setTooltip("When editing a record, open the view in a new window," " rather than docked in the main view."); declareCategory ("ID Dialogues"); declareBool ("toolbar", "Show toolbar", true); declareCategory ("Reports"); EnumValue actionNone ("None"); EnumValue actionEdit ("Edit", "Open a table or dialogue suitable for addressing the listed report"); EnumValue actionRemove ("Remove", "Remove the report from the report table"); EnumValue actionEditAndRemove ("Edit And Remove", "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table"); EnumValues reportValues; reportValues.add (actionNone).add (actionEdit).add (actionRemove).add (actionEditAndRemove); declareEnum ("double", "Double Click", actionEdit).addValues (reportValues); declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues); declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues); declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues); declareBool("ignore-base-records", "Ignore base records in verifier", false); declareCategory ("Search & Replace"); declareInt ("char-before", "Characters before search string", 10). setTooltip ("Maximum number of character to display in search result before the searched text"); declareInt ("char-after", "Characters after search string", 10). setTooltip ("Maximum number of character to display in search result after the searched text"); declareBool ("auto-delete", "Delete row from result table after a successful replace", true); declareCategory ("Scripts"); declareBool ("show-linenum", "Show Line Numbers", true). setTooltip ("Show line numbers to the left of the script editor window." "The current row and column numbers of the text cursor are shown at the bottom."); declareBool ("wrap-lines", "Wrap Lines", false). setTooltip ("Wrap lines longer than width of script editor."); declareBool ("mono-font", "Use monospace font", true); declareInt ("tab-width", "Tab Width", 4). setTooltip ("Number of characters for tab width"). setRange (1, 10); EnumValue warningsNormal ("Normal", "Report warnings as warning"); declareEnum ("warnings", "Warning Mode", warningsNormal). addValue ("Ignore", "Do not report warning"). addValue (warningsNormal). addValue ("Strict", "Promote warning to an error"); declareBool ("toolbar", "Show toolbar", true); declareInt ("compile-delay", "Delay between updating of source errors", 100). setTooltip ("Delay in milliseconds"). setRange (0, 10000); declareInt ("error-height", "Initial height of the error panel", 100). setRange (100, 10000); declareBool ("highlight-occurrences", "Highlight other occurrences of selected names", true); declareColour ("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareSeparator(); declareColour ("colour-int", "Highlight Colour: Integer Literals", QColor ("darkmagenta")); declareColour ("colour-float", "Highlight Colour: Float Literals", QColor ("magenta")); declareColour ("colour-name", "Highlight Colour: Names", QColor ("grey")); declareColour ("colour-keyword", "Highlight Colour: Keywords", QColor ("red")); declareColour ("colour-special", "Highlight Colour: Special Characters", QColor ("darkorange")); declareColour ("colour-comment", "Highlight Colour: Comments", QColor ("green")); declareColour ("colour-id", "Highlight Colour: IDs", QColor ("blue")); declareCategory ("General Input"); declareBool ("cycle", "Cyclic next/previous", false). setTooltip ("When using next/previous functions at the last/first item of a " "list go to the first/last item"); declareCategory ("3D Scene Input"); declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); declareSeparator(); declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); declareSeparator(); declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0); declareBool ("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); declareSeparator(); declareBool ("context-select", "Context Sensitive Selection", false); declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). setRange (0.001, 100.0); declareDouble ("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0). setRange (0.001, 100.0); declareDouble ("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0). setTooltip ("Acceleration factor during drag operations while holding down shift"). setRange (0.001, 100.0); declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory ("Rendering"); declareInt ("framerate-limit", "FPS limit", 60). setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\"."). setRange(0, 10000); declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170); declareBool ("camera-ortho", "Orthographic projection for camera", false); declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100). setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world."). setRange(10, 10000); declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); declareBool("scene-use-gradient", "Use Gradient Background", true); declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255)); declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)). setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if " "the gradient option is disabled."); declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255)); declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)). setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if " "the gradient option is disabled."); declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255)); declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). setMin (1); EnumValue createAndInsert ("Create cell and insert"); EnumValue showAndInsert ("Show cell and insert"); EnumValue dontInsert ("Discard"); EnumValue insertAnyway ("Insert anyway"); EnumValues insertOutsideCell; insertOutsideCell.add (createAndInsert).add (dontInsert).add (insertAnyway); EnumValues insertOutsideVisibleCell; insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); EnumValue createAndLandEdit ("Create cell and land, then edit"); EnumValue showAndLandEdit ("Show cell and edit"); EnumValue dontLandEdit ("Discard"); EnumValues landeditOutsideCell; landeditOutsideCell.add (createAndLandEdit).add (dontLandEdit); EnumValues landeditOutsideVisibleCell; landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit); EnumValue SelectOnly ("Select only"); EnumValue SelectAdd ("Add to selection"); EnumValue SelectRemove ("Remove from selection"); EnumValue selectInvert ("Invert selection"); EnumValues primarySelectAction; primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); EnumValues secondarySelectAction; secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); declareCategory ("3D Scene Editing"); declareDouble("gridsnap-movement", "Grid snap size", 16); declareDouble("gridsnap-rotation", "Angle snap size", 15); declareDouble("gridsnap-scale", "Scale snap size", 0.25); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); declareEnum ("outside-drop", "Handling drops outside of cells", createAndInsert). addValues (insertOutsideCell); declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). addValues (insertOutsideVisibleCell); declareEnum ("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit). setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."). addValues (landeditOutsideCell); declareEnum ("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit). setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."). addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); declareInt ("shapebrush-maximumsize", "Maximum height edit brush size", 100). setTooltip("Setting for the slider range of brush size in terrain height editing."). setMin (1); declareBool ("landedit-post-smoothpainting", "Smooth land after painting height", false). setTooltip("Raise and lower tools will leave bumpy finish without this option"); declareDouble ("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25). setTooltip("If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " "Negative values may be used to roughen instead of smooth."). setMin (-1). setMax (1); declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); declareEnum ("primary-select-action", "Action for primary select", SelectOnly). setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). addValues (primarySelectAction); declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd). setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). addValues (secondarySelectAction); declareCategory ("Key Bindings"); declareSubcategory ("Document"); declareShortcut ("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); declareShortcut ("document-file-newaddon", "New Addon", QKeySequence()); declareShortcut ("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); declareShortcut ("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); declareShortcut ("document-help-help", "Help", QKeySequence(Qt::Key_F1)); declareShortcut ("document-help-tutorial", "Tutorial", QKeySequence()); declareShortcut ("document-file-verify", "Verify", QKeySequence()); declareShortcut ("document-file-merge", "Merge", QKeySequence()); declareShortcut ("document-file-errorlog", "Open Load Error Log", QKeySequence()); declareShortcut ("document-file-metadata", "Meta Data", QKeySequence()); declareShortcut ("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); declareShortcut ("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); declareShortcut ("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); declareShortcut ("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); declareShortcut ("document-edit-preferences", "Open Preferences", QKeySequence()); declareShortcut ("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); declareShortcut ("document-view-newview", "New View", QKeySequence()); declareShortcut ("document-view-statusbar", "Toggle Status Bar", QKeySequence()); declareShortcut ("document-view-filters", "Open Filter List", QKeySequence()); declareShortcut ("document-world-regions", "Open Region List", QKeySequence()); declareShortcut ("document-world-cells", "Open Cell List", QKeySequence()); declareShortcut ("document-world-referencables", "Open Object List", QKeySequence()); declareShortcut ("document-world-references", "Open Instance List", QKeySequence()); declareShortcut ("document-world-lands", "Open Lands List", QKeySequence()); declareShortcut ("document-world-landtextures", "Open Land Textures List", QKeySequence()); declareShortcut ("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); declareShortcut ("document-world-regionmap", "Open Region Map", QKeySequence()); declareShortcut ("document-mechanics-globals", "Open Global List", QKeySequence()); declareShortcut ("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); declareShortcut ("document-mechanics-scripts", "Open Script List", QKeySequence()); declareShortcut ("document-mechanics-spells", "Open Spell List", QKeySequence()); declareShortcut ("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); declareShortcut ("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); declareShortcut ("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); declareShortcut ("document-character-skills", "Open Skill List", QKeySequence()); declareShortcut ("document-character-classes", "Open Class List", QKeySequence()); declareShortcut ("document-character-factions", "Open Faction List", QKeySequence()); declareShortcut ("document-character-races", "Open Race List", QKeySequence()); declareShortcut ("document-character-birthsigns", "Open Birthsign List", QKeySequence()); declareShortcut ("document-character-topics", "Open Topic List", QKeySequence()); declareShortcut ("document-character-journals", "Open Journal List", QKeySequence()); declareShortcut ("document-character-topicinfos", "Open Topic Info List", QKeySequence()); declareShortcut ("document-character-journalinfos", "Open Journal Info List", QKeySequence()); declareShortcut ("document-character-bodyparts", "Open Body Part List", QKeySequence()); declareShortcut ("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); declareShortcut ("document-assets-sounds", "Open Sound Asset List", QKeySequence()); declareShortcut ("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); declareShortcut ("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); declareShortcut ("document-assets-icons", "Open Icon Asset List", QKeySequence()); declareShortcut ("document-assets-music", "Open Music Asset List", QKeySequence()); declareShortcut ("document-assets-soundres", "Open Sound File List", QKeySequence()); declareShortcut ("document-assets-textures", "Open Texture Asset List", QKeySequence()); declareShortcut ("document-assets-videos", "Open Video Asset List", QKeySequence()); declareShortcut ("document-debug-run", "Run Debug", QKeySequence()); declareShortcut ("document-debug-shutdown", "Stop Debug", QKeySequence()); declareShortcut ("document-debug-profiles", "Debug Profiles", QKeySequence()); declareShortcut ("document-debug-runlog", "Open Run Log", QKeySequence()); declareSubcategory ("Table"); declareShortcut ("table-edit", "Edit Record", QKeySequence()); declareShortcut ("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); declareShortcut ("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); declareShortcut ("touch-record", "Touch Record", QKeySequence()); declareShortcut ("table-revert", "Revert Record", QKeySequence()); declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); declareShortcut ("table-moveup", "Move Record Up", QKeySequence()); declareShortcut ("table-movedown", "Move Record Down", QKeySequence()); declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence()); declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence()); declareSubcategory ("Report Table"); declareShortcut ("reporttable-show", "Show Report", QKeySequence()); declareShortcut ("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); declareShortcut ("reporttable-replace", "Replace Report", QKeySequence()); declareShortcut ("reporttable-refresh", "Refresh Report", QKeySequence()); declareSubcategory ("Scene"); declareShortcut ("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); declareShortcut ("scene-navi-secondary", "Camera Translation From Mouse Movement", QKeySequence(Qt::ControlModifier | (int)Qt::LeftButton)); declareShortcut ("scene-open-primary", "Primary Open", QKeySequence(Qt::ShiftModifier | (int)Qt::LeftButton)); declareShortcut ("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); declareShortcut ("scene-edit-secondary", "Secondary Edit", QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); declareShortcut ("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); declareShortcut ("scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); declareShortcut ("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); declareShortcut ("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); declareShortcut ("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); declareShortcut ("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); declareShortcut ("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); declareShortcut ("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); declareShortcut ("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); declareShortcut ("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); declareShortcut ("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut ("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); declareSubcategory ("1st/Free Camera"); declareShortcut ("free-forward", "Forward", QKeySequence(Qt::Key_W)); declareShortcut ("free-backward", "Backward", QKeySequence(Qt::Key_S)); declareShortcut ("free-left", "Left", QKeySequence(Qt::Key_A)); declareShortcut ("free-right", "Right", QKeySequence(Qt::Key_D)); declareShortcut ("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); declareShortcut ("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); declareShortcut ("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); declareSubcategory ("Orbit Camera"); declareShortcut ("orbit-up", "Up", QKeySequence(Qt::Key_W)); declareShortcut ("orbit-down", "Down", QKeySequence(Qt::Key_S)); declareShortcut ("orbit-left", "Left", QKeySequence(Qt::Key_A)); declareShortcut ("orbit-right", "Right", QKeySequence(Qt::Key_D)); declareShortcut ("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); declareShortcut ("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); declareShortcut ("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); declareShortcut ("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); declareSubcategory ("Script Editor"); declareShortcut ("script-editor-comment", "Comment Selection", QKeySequence()); declareShortcut ("script-editor-uncomment", "Uncomment Selection", QKeySequence()); declareCategory ("Models"); declareString ("baseanim", "base animations", "meshes/base_anim.nif"). setTooltip("3rd person base model with textkeys-data"); declareString ("baseanimkna", "base animations, kna", "meshes/base_animkna.nif"). setTooltip("3rd person beast race base model with textkeys-data"); declareString ("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif"). setTooltip("3rd person female base model with textkeys-data"); declareString ("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif"). setTooltip("3rd person werewolf skin"); } void CSMPrefs::State::declareCategory (const std::string& key) { std::map::iterator iter = mCategories.find (key); if (iter!=mCategories.end()) { mCurrentCategory = iter; } else { mCurrentCategory = mCategories.insert (std::make_pair (key, Category (this, key))).first; } } CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key, const std::string& label, int default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); setDefault(key, std::to_string(default_)); default_ = Settings::Manager::getInt (key, mCurrentCategory->second.getKey()); CSMPrefs::IntSetting *setting = new CSMPrefs::IntSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, const std::string& label, double default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); std::ostringstream stream; stream << default_; setDefault(key, stream.str()); default_ = Settings::Manager::getFloat (key, mCurrentCategory->second.getKey()); CSMPrefs::DoubleSetting *setting = new CSMPrefs::DoubleSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::BoolSetting& CSMPrefs::State::declareBool (const std::string& key, const std::string& label, bool default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); setDefault (key, default_ ? "true" : "false"); default_ = Settings::Manager::getBool (key, mCurrentCategory->second.getKey()); CSMPrefs::BoolSetting *setting = new CSMPrefs::BoolSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum (const std::string& key, const std::string& label, EnumValue default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); setDefault (key, default_.mValue); default_.mValue = Settings::Manager::getString (key, mCurrentCategory->second.getKey()); CSMPrefs::EnumSetting *setting = new CSMPrefs::EnumSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::ColourSetting& CSMPrefs::State::declareColour (const std::string& key, const std::string& label, QColor default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); setDefault (key, default_.name().toUtf8().data()); default_.setNamedColor (QString::fromUtf8 (Settings::Manager::getString (key, mCurrentCategory->second.getKey()).c_str())); CSMPrefs::ColourSetting *setting = new CSMPrefs::ColourSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut (const std::string& key, const std::string& label, const QKeySequence& default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); std::string seqStr = getShortcutManager().convertToString(default_); setDefault (key, seqStr); // Setup with actual data QKeySequence sequence; getShortcutManager().convertFromString(Settings::Manager::getString(key, mCurrentCategory->second.getKey()), sequence); getShortcutManager().setSequence(key, sequence); CSMPrefs::ShortcutSetting *setting = new CSMPrefs::ShortcutSetting (&mCurrentCategory->second, &mMutex, key, label); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::StringSetting& CSMPrefs::State::declareString (const std::string& key, const std::string& label, std::string default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); setDefault (key, default_); default_ = Settings::Manager::getString (key, mCurrentCategory->second.getKey()); CSMPrefs::StringSetting *setting = new CSMPrefs::StringSetting (&mCurrentCategory->second, &mMutex, key, label, default_); mCurrentCategory->second.addSetting (setting); return *setting; } CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const std::string& label, int default_) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); std::string modStr = getShortcutManager().convertToString(default_); setDefault (key, modStr); // Setup with actual data int modifier; getShortcutManager().convertFromString(Settings::Manager::getString(key, mCurrentCategory->second.getKey()), modifier); getShortcutManager().setModifier(key, modifier); CSMPrefs::ModifierSetting *setting = new CSMPrefs::ModifierSetting (&mCurrentCategory->second, &mMutex, key, label); mCurrentCategory->second.addSetting (setting); return *setting; } void CSMPrefs::State::declareSeparator() { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); CSMPrefs::Setting *setting = new CSMPrefs::Setting (&mCurrentCategory->second, &mMutex, "", ""); mCurrentCategory->second.addSetting (setting); } void CSMPrefs::State::declareSubcategory(const std::string& label) { if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); CSMPrefs::Setting *setting = new CSMPrefs::Setting (&mCurrentCategory->second, &mMutex, "", label); mCurrentCategory->second.addSetting (setting); } void CSMPrefs::State::setDefault (const std::string& key, const std::string& default_) { Settings::CategorySetting fullKey (mCurrentCategory->second.getKey(), key); Settings::CategorySettingValueMap::iterator iter = Settings::Manager::mDefaultSettings.find (fullKey); if (iter==Settings::Manager::mDefaultSettings.end()) Settings::Manager::mDefaultSettings.insert (std::make_pair (fullKey, default_)); } CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) : mConfigFile ("openmw-cs.cfg"), mDefaultConfigFile("defaults-cs.bin"), mConfigurationManager (configurationManager), mCurrentCategory (mCategories.end()) { if (sThis) throw std::logic_error ("An instance of CSMPRefs::State already exists"); sThis = this; declare(); } CSMPrefs::State::~State() { sThis = nullptr; } void CSMPrefs::State::save() { boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; Settings::Manager::saveUser (user.string()); } CSMPrefs::State::Iterator CSMPrefs::State::begin() { return mCategories.begin(); } CSMPrefs::State::Iterator CSMPrefs::State::end() { return mCategories.end(); } CSMPrefs::ShortcutManager& CSMPrefs::State::getShortcutManager() { return mShortcutManager; } CSMPrefs::Category& CSMPrefs::State::operator[] (const std::string& key) { Iterator iter = mCategories.find (key); if (iter==mCategories.end()) throw std::logic_error ("Invalid user settings category: " + key); return iter->second; } void CSMPrefs::State::update (const Setting& setting) { emit (settingChanged (&setting)); } CSMPrefs::State& CSMPrefs::State::get() { if (!sThis) throw std::logic_error ("No instance of CSMPrefs::State"); return *sThis; } void CSMPrefs::State::resetCategory(const std::string& category) { for (Settings::CategorySettingValueMap::iterator i = Settings::Manager::mUserSettings.begin(); i != Settings::Manager::mUserSettings.end(); ++i) { // if the category matches if (i->first.first == category) { // mark the setting as changed Settings::Manager::mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); // reset the value to the default i->second = Settings::Manager::mDefaultSettings[i->first]; } } Collection::iterator container = mCategories.find(category); if (container != mCategories.end()) { Category settings = container->second; for (Category::Iterator i = settings.begin(); i != settings.end(); ++i) { (*i)->updateWidget(); update(**i); } } } void CSMPrefs::State::resetAll() { for (Collection::iterator iter = mCategories.begin(); iter != mCategories.end(); ++iter) { resetCategory(iter->first); } } CSMPrefs::State& CSMPrefs::get() { return State::get(); } openmw-openmw-0.48.0/apps/opencs/model/prefs/state.hpp000066400000000000000000000062011445372753700227350ustar00rootroot00000000000000#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include "category.hpp" #include "setting.hpp" #include "enumsetting.hpp" #include "stringsetting.hpp" #include "shortcutmanager.hpp" class QColor; namespace CSMPrefs { class IntSetting; class DoubleSetting; class BoolSetting; class ColourSetting; class ShortcutSetting; class ModifierSetting; /// \brief User settings state /// /// \note Access to the user settings is thread-safe once all declarations and loading has /// been completed. class State : public QObject { Q_OBJECT static State *sThis; public: typedef std::map Collection; typedef Collection::iterator Iterator; private: const std::string mConfigFile; const std::string mDefaultConfigFile; const Files::ConfigurationManager& mConfigurationManager; ShortcutManager mShortcutManager; Collection mCategories; Iterator mCurrentCategory; QMutex mMutex; // not implemented State (const State&); State& operator= (const State&); private: void declare(); void declareCategory (const std::string& key); IntSetting& declareInt (const std::string& key, const std::string& label, int default_); DoubleSetting& declareDouble (const std::string& key, const std::string& label, double default_); BoolSetting& declareBool (const std::string& key, const std::string& label, bool default_); EnumSetting& declareEnum (const std::string& key, const std::string& label, EnumValue default_); ColourSetting& declareColour (const std::string& key, const std::string& label, QColor default_); ShortcutSetting& declareShortcut (const std::string& key, const std::string& label, const QKeySequence& default_); StringSetting& declareString (const std::string& key, const std::string& label, std::string default_); ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); void declareSeparator(); void declareSubcategory(const std::string& label); void setDefault (const std::string& key, const std::string& default_); public: State (const Files::ConfigurationManager& configurationManager); ~State(); void save(); Iterator begin(); Iterator end(); ShortcutManager& getShortcutManager(); Category& operator[](const std::string& key); void update (const Setting& setting); static State& get(); void resetCategory(const std::string& category); void resetAll(); signals: void settingChanged (const CSMPrefs::Setting *setting); }; // convenience function State& get(); } #endif openmw-openmw-0.48.0/apps/opencs/model/prefs/stringsetting.cpp000066400000000000000000000026631445372753700245240ustar00rootroot00000000000000 #include "stringsetting.hpp" #include #include #include #include "category.hpp" #include "state.hpp" CSMPrefs::StringSetting::StringSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, std::string default_) : Setting (parent, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip (const std::string& tooltip) { mTooltip = tooltip; return *this; } std::pair CSMPrefs::StringSetting::makeWidgets (QWidget *parent) { mWidget = new QLineEdit (QString::fromUtf8 (mDefault.c_str()), parent); if (!mTooltip.empty()) { QString tooltip = QString::fromUtf8 (mTooltip.c_str()); mWidget->setToolTip (tooltip); } connect (mWidget, SIGNAL (textChanged (QString)), this, SLOT (textChanged (QString))); return std::make_pair (static_cast (nullptr), mWidget); } void CSMPrefs::StringSetting::updateWidget() { if (mWidget) { mWidget->setText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); } } void CSMPrefs::StringSetting::textChanged (const QString& text) { { QMutexLocker lock (getMutex()); Settings::Manager::setString (getKey(), getParent()->getKey(), text.toStdString()); } getParent()->getState()->update (*this); } openmw-openmw-0.48.0/apps/opencs/model/prefs/stringsetting.hpp000066400000000000000000000014431445372753700245240ustar00rootroot00000000000000#ifndef CSM_PREFS_StringSetting_H #define CSM_PREFS_StringSetting_H #include "setting.hpp" class QLineEdit; namespace CSMPrefs { class StringSetting : public Setting { Q_OBJECT std::string mTooltip; std::string mDefault; QLineEdit* mWidget; public: StringSetting (Category *parent, QMutex *mutex, const std::string& key, const std::string& label, std::string default_); StringSetting& setTooltip (const std::string& tooltip); /// Return label, input widget. std::pair makeWidgets (QWidget *parent) override; void updateWidget() override; private slots: void textChanged (const QString& text); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/000077500000000000000000000000001445372753700211265ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/tools/birthsigncheck.cpp000066400000000000000000000035471445372753700246320ustar00rootroot00000000000000#include "birthsigncheck.hpp" #include #include "../prefs/state.hpp" #include "../world/universalid.hpp" CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection& birthsigns, const CSMWorld::Resources &textures) : mBirthsigns(birthsigns), mTextures(textures) { mIgnoreBaseRecords = false; } int CSMTools::BirthsignCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mBirthsigns.getSize(); } void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mBirthsigns.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::BirthSign& birthsign = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId); if (birthsign.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (birthsign.mDescription.empty()) messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); if (birthsign.mTexture.empty()) messages.add(id, "Image is missing", "", CSMDoc::Message::Severity_Error); else if (mTextures.searchId(birthsign.mTexture) == -1) { std::string ddsTexture = birthsign.mTexture; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && mTextures.searchId(ddsTexture) != -1)) messages.add(id, "Image '" + birthsign.mTexture + "' does not exist", "", CSMDoc::Message::Severity_Error); } /// \todo check data members that can't be edited in the table view } openmw-openmw-0.48.0/apps/opencs/model/tools/birthsigncheck.hpp000066400000000000000000000017131445372753700246300ustar00rootroot00000000000000#ifndef CSM_TOOLS_BIRTHSIGNCHECK_H #define CSM_TOOLS_BIRTHSIGNCHECK_H #include #include "../world/idcollection.hpp" #include "../world/resources.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that birthsign records are internally consistent class BirthsignCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection &mBirthsigns; const CSMWorld::Resources &mTextures; bool mIgnoreBaseRecords; public: BirthsignCheckStage (const CSMWorld::IdCollection &birthsigns, const CSMWorld::Resources &textures); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/bodypartcheck.cpp000066400000000000000000000040761445372753700244630ustar00rootroot00000000000000#include "bodypartcheck.hpp" #include "../prefs/state.hpp" CSMTools::BodyPartCheckStage::BodyPartCheckStage( const CSMWorld::IdCollection &bodyParts, const CSMWorld::Resources &meshes, const CSMWorld::IdCollection &races ) : mBodyParts(bodyParts), mMeshes(meshes), mRaces(races) { mIgnoreBaseRecords = false; } int CSMTools::BodyPartCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mBodyParts.getSize(); } void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &messages) { const CSMWorld::Record &record = mBodyParts.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::BodyPart &bodyPart = record.get(); CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId ); // Check BYDT if (bodyPart.mData.mPart >= ESM::BodyPart::MP_Count ) messages.add(id, "Invalid part", "", CSMDoc::Message::Severity_Error); if (bodyPart.mData.mType > ESM::BodyPart::MT_Armor ) messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); // Check MODL if ( bodyPart.mModel.empty() ) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) messages.add(id, "Model '" + bodyPart.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check FNAM for skin body parts (for non-skin body parts it's meaningless) if ( bodyPart.mData.mType == ESM::BodyPart::MT_Skin ) { if ( bodyPart.mRace.empty() ) messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/bodypartcheck.hpp000066400000000000000000000022011445372753700244540ustar00rootroot00000000000000#ifndef CSM_TOOLS_BODYPARTCHECK_H #define CSM_TOOLS_BODYPARTCHECK_H #include #include #include "../world/resources.hpp" #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that body part records are internally consistent class BodyPartCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection &mBodyParts; const CSMWorld::Resources &mMeshes; const CSMWorld::IdCollection &mRaces; bool mIgnoreBaseRecords; public: BodyPartCheckStage( const CSMWorld::IdCollection &bodyParts, const CSMWorld::Resources &meshes, const CSMWorld::IdCollection &races ); int setup() override; ///< \return number of steps void perform(int stage, CSMDoc::Messages &messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/classcheck.cpp000066400000000000000000000042411445372753700237360ustar00rootroot00000000000000#include "classcheck.hpp" #include #include #include #include "../prefs/state.hpp" CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection& classes) : mClasses (classes) { mIgnoreBaseRecords = false; } int CSMTools::ClassCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mClasses.getSize(); } void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mClasses.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Class& class_ = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId); // A class should have a name if (class_.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // A playable class should have a description if (class_.mData.mIsPlayable != 0 && class_.mDescription.empty()) messages.add(id, "Description of a playable class is missing", "", CSMDoc::Message::Severity_Warning); // test for invalid attributes for (int i=0; i<2; ++i) if (class_.mData.mAttribute[i]==-1) { messages.add(id, "Attribute #" + std::to_string(i) + " is not set", "", CSMDoc::Message::Severity_Error); } if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1) { messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); } // test for non-unique skill std::map skills; // ID, number of occurrences for (int i=0; i<5; ++i) for (int i2=0; i2<2; ++i2) ++skills[class_.mData.mSkills[i][i2]]; for (auto &skill : skills) if (skill.second>1) { messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " is listed more than once", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/classcheck.hpp000066400000000000000000000014171445372753700237450ustar00rootroot00000000000000#ifndef CSM_TOOLS_CLASSCHECK_H #define CSM_TOOLS_CLASSCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that class records are internally consistent class ClassCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mClasses; bool mIgnoreBaseRecords; public: ClassCheckStage (const CSMWorld::IdCollection& classes); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/enchantmentcheck.cpp000066400000000000000000000072001445372753700251330ustar00rootroot00000000000000#include "enchantmentcheck.hpp" #include "../prefs/state.hpp" #include "../world/universalid.hpp" CSMTools::EnchantmentCheckStage::EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments) : mEnchantments (enchantments) { mIgnoreBaseRecords = false; } int CSMTools::EnchantmentCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mEnchantments.getSize(); } void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mEnchantments.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Enchantment& enchantment = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Enchantment, enchantment.mId); if (enchantment.mData.mType < 0 || enchantment.mData.mType > 3) messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); if (enchantment.mData.mCost < 0) messages.add(id, "Cost is negative", "", CSMDoc::Message::Severity_Error); if (enchantment.mData.mCharge < 0) messages.add(id, "Charge is negative", "", CSMDoc::Message::Severity_Error); if (enchantment.mData.mCost > enchantment.mData.mCharge) messages.add(id, "Cost is higher than charge", "", CSMDoc::Message::Severity_Error); if (enchantment.mEffects.mList.empty()) { messages.add(id, "Enchantment doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning); } else { std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) { const std::string number = std::to_string(i); // At the time of writing this effects, attributes and skills are hardcoded if (effect->mEffectID < 0 || effect->mEffectID > 142) { messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error); ++effect; continue; } if (effect->mSkill < -1 || effect->mSkill > 26) messages.add(id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mAttribute < -1 || effect->mAttribute > 7) messages.add(id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mRange < 0 || effect->mRange > 2) messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mArea < 0) messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error); if (effect->mDuration < 0) messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMin < 0) messages.add(id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMax < 0) messages.add(id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMin > effect->mMagnMax) messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", CSMDoc::Message::Severity_Error); ++effect; } } } openmw-openmw-0.48.0/apps/opencs/model/tools/enchantmentcheck.hpp000066400000000000000000000014511445372753700251420ustar00rootroot00000000000000#ifndef CSM_TOOLS_ENCHANTMENTCHECK_H #define CSM_TOOLS_ENCHANTMENTCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief Make sure that enchantment records are correct class EnchantmentCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mEnchantments; bool mIgnoreBaseRecords; public: EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/factioncheck.cpp000066400000000000000000000034411445372753700242550ustar00rootroot00000000000000#include "factioncheck.hpp" #include #include #include "../prefs/state.hpp" CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection& factions) : mFactions (factions) { mIgnoreBaseRecords = false; } int CSMTools::FactionCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mFactions.getSize(); } void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mFactions.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Faction& faction = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Faction, faction.mId); // test for empty name if (faction.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // test for invalid attributes if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1) { messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); } // test for non-unique skill std::map skills; // ID, number of occurrences for (int i=0; i<7; ++i) if (faction.mData.mSkills[i]!=-1) ++skills[faction.mData.mSkills[i]]; for (auto &skill : skills) if (skill.second>1) { messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " is listed more than once", "", CSMDoc::Message::Severity_Error); } /// \todo check data members that can't be edited in the table view } openmw-openmw-0.48.0/apps/opencs/model/tools/factioncheck.hpp000066400000000000000000000014371445372753700242650ustar00rootroot00000000000000#ifndef CSM_TOOLS_FACTIONCHECK_H #define CSM_TOOLS_FACTIONCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that faction records are internally consistent class FactionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mFactions; bool mIgnoreBaseRecords; public: FactionCheckStage (const CSMWorld::IdCollection& factions); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/gmstcheck.cpp000066400000000000000000000115571445372753700236130ustar00rootroot00000000000000#include "gmstcheck.hpp" #include #include "../prefs/state.hpp" #include "../world/defaultgmsts.hpp" CSMTools::GmstCheckStage::GmstCheckStage(const CSMWorld::IdCollection& gameSettings) : mGameSettings(gameSettings) { mIgnoreBaseRecords = false; } int CSMTools::GmstCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mGameSettings.getSize(); } void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mGameSettings.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::GameSetting& gmst = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Gmst, gmst.mId); // Test for empty string if (gmst.mValue.getType() == ESM::VT_String && gmst.mValue.getString().empty()) messages.add(id, gmst.mId + " is an empty string", "", CSMDoc::Message::Severity_Warning); // Checking type and limits // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) if (gmst.mId[0] == 'f') { for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { if (gmst.mId == CSMWorld::DefaultGmsts::Floats[i]) { if (gmst.mValue.getType() != ESM::VT_Float) { std::ostringstream stream; stream << "Expected float type for " << gmst.mId << " but found " << varTypeToString(gmst.mValue.getType()) << " type"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } if (gmst.mValue.getFloat() < CSMWorld::DefaultGmsts::FloatLimits[i*2]) messages.add(id, gmst.mId + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); if (gmst.mValue.getFloat() > CSMWorld::DefaultGmsts::FloatLimits[i*2+1]) messages.add(id, gmst.mId + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); break; // for loop } } } else if (gmst.mId[0] == 'i') { for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { if (gmst.mId == CSMWorld::DefaultGmsts::Ints[i]) { if (gmst.mValue.getType() != ESM::VT_Int) { std::ostringstream stream; stream << "Expected int type for " << gmst.mId << " but found " << varTypeToString(gmst.mValue.getType()) << " type"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } if (gmst.mValue.getInteger() < CSMWorld::DefaultGmsts::IntLimits[i*2]) messages.add(id, gmst.mId + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); if (gmst.mValue.getInteger() > CSMWorld::DefaultGmsts::IntLimits[i*2+1]) messages.add(id, gmst.mId + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); break; // for loop } } } else if (gmst.mId[0] == 's') { for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { if (gmst.mId == CSMWorld::DefaultGmsts::Strings[i]) { ESM::VarType type = gmst.mValue.getType(); if (type != ESM::VT_String && type != ESM::VT_None) { std::ostringstream stream; stream << "Expected string or none type for " << gmst.mId << " but found " << varTypeToString(gmst.mValue.getType()) << " type"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } break; // for loop } } } } std::string CSMTools::GmstCheckStage::varTypeToString(ESM::VarType type) { switch (type) { case ESM::VT_Unknown: return "unknown"; case ESM::VT_None: return "none"; case ESM::VT_Short: return "short"; case ESM::VT_Int: return "int"; case ESM::VT_Long: return "long"; case ESM::VT_Float: return "float"; case ESM::VT_String: return "string"; default: return "unhandled"; } } openmw-openmw-0.48.0/apps/opencs/model/tools/gmstcheck.hpp000066400000000000000000000015341445372753700236120ustar00rootroot00000000000000#ifndef CSM_TOOLS_GMSTCHECK_H #define CSM_TOOLS_GMSTCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that GMSTs are alright class GmstCheckStage : public CSMDoc::Stage { public: GmstCheckStage(const CSMWorld::IdCollection& gameSettings); int setup() override; ///< \return number of steps void perform(int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages private: const CSMWorld::IdCollection& mGameSettings; bool mIgnoreBaseRecords; std::string varTypeToString(ESM::VarType); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/journalcheck.cpp000066400000000000000000000054451445372753700243120ustar00rootroot00000000000000#include "journalcheck.hpp" #include #include "../prefs/state.hpp" CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, const CSMWorld::InfoCollection& journalInfos) : mJournals(journals), mJournalInfos(journalInfos) { mIgnoreBaseRecords = false; } int CSMTools::JournalCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mJournals.getSize(); } void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && journalRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || journalRecord.isDeleted()) return; const ESM::Dialogue &journal = journalRecord.get(); int statusNamedCount = 0; int totalInfoCount = 0; std::set questIndices; CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) { const CSMWorld::Record infoRecord = (*it->get()); if (infoRecord.isDeleted()) continue; const CSMWorld::Info& journalInfo = infoRecord.get(); totalInfoCount += 1; if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) { statusNamedCount += 1; } // Skip "Base" records (setting!) if (mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly) continue; if (journalInfo.mResponse.empty()) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning); } std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); // Duplicate index if (!result.second) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "", CSMDoc::Message::Severity_Error); } } if (totalInfoCount == 0) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); messages.add(id, "No related journal entry", "", CSMDoc::Message::Severity_Warning); } else if (statusNamedCount > 1) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); messages.add(id, "Multiple entries with quest status 'Named'", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/journalcheck.hpp000066400000000000000000000016231445372753700243110ustar00rootroot00000000000000#ifndef CSM_TOOLS_JOURNALCHECK_H #define CSM_TOOLS_JOURNALCHECK_H #include #include "../world/idcollection.hpp" #include "../world/infocollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that journal infos are good class JournalCheckStage : public CSMDoc::Stage { public: JournalCheckStage(const CSMWorld::IdCollection& journals, const CSMWorld::InfoCollection& journalInfos); int setup() override; ///< \return number of steps void perform(int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages private: const CSMWorld::IdCollection& mJournals; const CSMWorld::InfoCollection& mJournalInfos; bool mIgnoreBaseRecords; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/magiceffectcheck.cpp000066400000000000000000000117321445372753700250710ustar00rootroot00000000000000#include "magiceffectcheck.hpp" #include #include "../prefs/state.hpp" std::string CSMTools::MagicEffectCheckStage::checkObject(const std::string &id, const CSMWorld::UniversalId &type, const std::string &column) const { CSMWorld::RefIdData::LocalIndex index = mObjects.getDataSet().searchId(id); if (index.first == -1) return (column + " '" + id + "' does not exist"); else if (index.second != type.getType()) return (column + " '" + id + "' does not have " + type.getTypeName() + " type"); return std::string(); } CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, const CSMWorld::IdCollection &sounds, const CSMWorld::RefIdCollection &objects, const CSMWorld::Resources &icons, const CSMWorld::Resources &textures) : mMagicEffects(effects), mSounds(sounds), mObjects(objects), mIcons(icons), mTextures(textures) { mIgnoreBaseRecords = false; } int CSMTools::MagicEffectCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mMagicEffects.getSize(); } void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) { const CSMWorld::Record &record = mMagicEffects.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; ESM::MagicEffect effect = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); if (effect.mDescription.empty()) { messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); } if (effect.mData.mBaseCost < 0.0f) { messages.add(id, "Base cost is negative", "", CSMDoc::Message::Severity_Error); } if (effect.mIcon.empty()) { messages.add(id, "Icon is missing", "", CSMDoc::Message::Severity_Error); } else { if (mIcons.searchId(effect.mIcon) == -1) { std::string ddsIcon = effect.mIcon; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) messages.add(id, "Icon '" + effect.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); } } if (!effect.mParticle.empty()) { if (mTextures.searchId(effect.mParticle) == -1) { std::string ddsParticle = effect.mParticle; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsParticle) && mTextures.searchId(ddsParticle) != -1)) messages.add(id, "Particle texture '" + effect.mParticle + "' does not exist", "", CSMDoc::Message::Severity_Error); } } if (!effect.mCasting.empty()) { const std::string error = checkObject(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting object"); if (!error.empty()) messages.add(id, error, "", CSMDoc::Message::Severity_Error); } if (!effect.mHit.empty()) { const std::string error = checkObject(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit object"); if (!error.empty()) messages.add(id, error, "", CSMDoc::Message::Severity_Error); } if (!effect.mArea.empty()) { const std::string error = checkObject(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area object"); if (!error.empty()) messages.add(id, error, "", CSMDoc::Message::Severity_Error); } if (!effect.mBolt.empty()) { const std::string error = checkObject(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt object"); if (!error.empty()) messages.add(id, error, "", CSMDoc::Message::Severity_Error); } if (!effect.mCastSound.empty() && mSounds.searchId(effect.mCastSound) == -1) messages.add(id, "Casting sound '" + effect.mCastSound + "' does not exist", "", CSMDoc::Message::Severity_Error); if (!effect.mHitSound.empty() && mSounds.searchId(effect.mHitSound) == -1) messages.add(id, "Hit sound '" + effect.mHitSound + "' does not exist", "", CSMDoc::Message::Severity_Error); if (!effect.mAreaSound.empty() && mSounds.searchId(effect.mAreaSound) == -1) messages.add(id, "Area sound '" + effect.mAreaSound + "' does not exist", "", CSMDoc::Message::Severity_Error); if (!effect.mBoltSound.empty() && mSounds.searchId(effect.mBoltSound) == -1) messages.add(id, "Bolt sound '" + effect.mBoltSound + "' does not exist", "", CSMDoc::Message::Severity_Error); } openmw-openmw-0.48.0/apps/opencs/model/tools/magiceffectcheck.hpp000066400000000000000000000031061445372753700250720ustar00rootroot00000000000000#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP #define CSM_TOOLS_MAGICEFFECTCHECK_HPP #include #include #include "../world/idcollection.hpp" #include "../world/refidcollection.hpp" #include "../world/resources.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that magic effect records are internally consistent class MagicEffectCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection &mMagicEffects; const CSMWorld::IdCollection &mSounds; const CSMWorld::RefIdCollection &mObjects; const CSMWorld::Resources &mIcons; const CSMWorld::Resources &mTextures; bool mIgnoreBaseRecords; private: std::string checkObject(const std::string &id, const CSMWorld::UniversalId &type, const std::string &column) const; public: MagicEffectCheckStage(const CSMWorld::IdCollection &effects, const CSMWorld::IdCollection &sounds, const CSMWorld::RefIdCollection &objects, const CSMWorld::Resources &icons, const CSMWorld::Resources &textures); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages &messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/mandatoryid.cpp000066400000000000000000000013321445372753700241440ustar00rootroot00000000000000#include "mandatoryid.hpp" #include "../world/collectionbase.hpp" #include "../world/record.hpp" CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, const std::vector& ids) : mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids) {} int CSMTools::MandatoryIdStage::setup() { return static_cast(mIds.size()); } void CSMTools::MandatoryIdStage::perform (int stage, CSMDoc::Messages& messages) { if (mIdCollection.searchId (mIds.at (stage))==-1 || mIdCollection.getRecord (mIds.at (stage)).isDeleted()) messages.add (mCollectionId, "Missing mandatory record: " + mIds.at (stage)); } openmw-openmw-0.48.0/apps/opencs/model/tools/mandatoryid.hpp000066400000000000000000000017071445372753700241570ustar00rootroot00000000000000#ifndef CSM_TOOLS_MANDATORYID_H #define CSM_TOOLS_MANDATORYID_H #include #include #include "../world/universalid.hpp" #include "../doc/stage.hpp" namespace CSMWorld { class CollectionBase; } namespace CSMTools { /// \brief Verify stage: make sure that records with specific IDs exist. class MandatoryIdStage : public CSMDoc::Stage { const CSMWorld::CollectionBase& mIdCollection; CSMWorld::UniversalId mCollectionId; std::vector mIds; public: MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, const std::vector& ids); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/mergeoperation.cpp000066400000000000000000000070431445372753700246560ustar00rootroot00000000000000 #include "mergeoperation.hpp" #include "../doc/state.hpp" #include "../doc/document.hpp" #include "mergestages.hpp" CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) : CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) { appendStage (new StartMergeStage (mState)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); appendStage (new MergeRefIdsStage (mState)); appendStage (new MergeReferencesStage (mState)); appendStage (new MergeReferencesStage (mState)); appendStage (new PopulateLandTexturesMergeStage (mState)); appendStage (new MergeLandStage (mState)); appendStage (new FixLandsAndLandTexturesMergeStage (mState)); appendStage (new CleanupLandTexturesMergeStage (mState)); appendStage (new FinishMergedDocumentStage (mState, encoding)); } void CSMTools::MergeOperation::setTarget (std::unique_ptr document) { mState.mTarget = std::move(document); } void CSMTools::MergeOperation::operationDone() { CSMDoc::Operation::operationDone(); if (mState.mCompleted) emit mergeDone (mState.mTarget.release()); } openmw-openmw-0.48.0/apps/opencs/model/tools/mergeoperation.hpp000066400000000000000000000016671445372753700246710ustar00rootroot00000000000000#ifndef CSM_TOOLS_MERGEOPERATION_H #define CSM_TOOLS_MERGEOPERATION_H #include #include #include "../doc/operation.hpp" #include "mergestate.hpp" namespace CSMDoc { class Document; } namespace CSMTools { class MergeOperation : public CSMDoc::Operation { Q_OBJECT MergeState mState; public: MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); /// \attention Do not call this function while a merge is running. void setTarget (std::unique_ptr document); protected slots: void operationDone() override; signals: /// \attention When this signal is emitted, *this hands over the ownership of the /// document. This signal must be handled to avoid a leak. void mergeDone (CSMDoc::Document *document); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/mergestages.cpp000066400000000000000000000142261445372753700241450ustar00rootroot00000000000000#include "mergestages.hpp" #include #include "mergestate.hpp" #include "../doc/document.hpp" #include "../world/commands.hpp" #include "../world/data.hpp" #include "../world/idtable.hpp" CSMTools::StartMergeStage::StartMergeStage (MergeState& state) : mState (state) {} int CSMTools::StartMergeStage::setup() { return 1; } void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) { mState.mCompleted = false; mState.mTextureIndices.clear(); } CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) : mState (state), mEncoder (encoding) {} int CSMTools::FinishMergedDocumentStage::setup() { return 1; } void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) { // We know that the content file list contains at least two entries and that the first one // does exist on disc (otherwise it would have been impossible to initiate a merge on that // document). boost::filesystem::path path = mState.mSource.getContentFiles()[0]; ESM::ESMReader reader; reader.setEncoder (&mEncoder); reader.open (path.string()); CSMWorld::MetaData source; source.mId = "sys::meta"; source.load (reader); CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); target.mAuthor = source.mAuthor; target.mDescription = source.mDescription; mState.mTarget->getData().setMetaData (target); mState.mCompleted = true; } CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} int CSMTools::MergeRefIdsStage::setup() { return mState.mSource.getData().getReferenceables().getSize(); } void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) { mState.mSource.getData().getReferenceables().copyTo ( stage, mState.mTarget->getData().getReferenceables()); } CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) : mState (state) {} int CSMTools::MergeReferencesStage::setup() { mIndex.clear(); return mState.mSource.getData().getReferences().getSize(); } void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mState.mSource.getData().getReferences().getRecord (stage); if (!record.isDeleted()) { CSMWorld::CellRef ref = record.get(); ref.mOriginalCell = ref.mCell; ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; ref.mRefNum.mContentFile = 0; ref.mNew = false; mState.mTarget->getData().getReferences().appendRecord ( std::make_unique >( CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref))); } } CSMTools::PopulateLandTexturesMergeStage::PopulateLandTexturesMergeStage (MergeState& state) : mState (state) { } int CSMTools::PopulateLandTexturesMergeStage::setup() { return mState.mSource.getData().getLandTextures().getSize(); } void CSMTools::PopulateLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mState.mSource.getData().getLandTextures().getRecord (stage); if (!record.isDeleted()) { mState.mTarget->getData().getLandTextures().appendRecord( std::make_unique >( CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } } CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) { } int CSMTools::MergeLandStage::setup() { return mState.mSource.getData().getLand().getSize(); } void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mState.mSource.getData().getLand().getRecord (stage); if (!record.isDeleted()) { mState.mTarget->getData().getLand().appendRecord ( std::make_unique >( CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } } CSMTools::FixLandsAndLandTexturesMergeStage::FixLandsAndLandTexturesMergeStage (MergeState& state) : mState (state) { } int CSMTools::FixLandsAndLandTexturesMergeStage::setup() { // We will have no more than the source return mState.mSource.getData().getLand().getSize(); } void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) { if (stage < mState.mTarget->getData().getLand().getSize()) { CSMWorld::IdTable& landTable = dynamic_cast( *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); CSMWorld::IdTable& ltexTable = dynamic_cast( *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); std::string id = mState.mTarget->getData().getLand().getId(stage); CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id); cmd.redo(); // Get rid of base data const CSMWorld::Record& oldRecord = mState.mTarget->getData().getLand().getRecord (stage); mState.mTarget->getData().getLand().setRecord (stage, std::make_unique >( CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &oldRecord.get()))); } } CSMTools::CleanupLandTexturesMergeStage::CleanupLandTexturesMergeStage (MergeState& state) : mState (state) { } int CSMTools::CleanupLandTexturesMergeStage::setup() { return 1; } void CSMTools::CleanupLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) { auto& landTextures = mState.mTarget->getData().getLandTextures(); for (int i = 0; i < landTextures.getSize(); ) { if (!landTextures.getRecord(i).isModified()) landTextures.removeRows(i, 1); else ++i; } } openmw-openmw-0.48.0/apps/opencs/model/tools/mergestages.hpp000066400000000000000000000131661445372753700241540ustar00rootroot00000000000000#ifndef CSM_TOOLS_MERGESTAGES_H #define CSM_TOOLS_MERGESTAGES_H #include #include #include #include #include "../doc/stage.hpp" #include "../world/data.hpp" #include "mergestate.hpp" namespace CSMTools { class StartMergeStage : public CSMDoc::Stage { MergeState& mState; public: StartMergeStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class FinishMergedDocumentStage : public CSMDoc::Stage { MergeState& mState; ToUTF8::Utf8Encoder mEncoder; public: FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; template > class MergeIdCollectionStage : public CSMDoc::Stage { MergeState& mState; Collection& (CSMWorld::Data::*mAccessor)(); public: MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; template MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) : mState (state), mAccessor (accessor) {} template int MergeIdCollectionStage::setup() { return (mState.mSource.getData().*mAccessor)().getSize(); } template void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) { const Collection& source = (mState.mSource.getData().*mAccessor)(); Collection& target = (mState.mTarget->getData().*mAccessor)(); const CSMWorld::Record& record = source.getRecord (stage); if (!record.isDeleted()) target.appendRecord (std::make_unique >( CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } class MergeRefIdsStage : public CSMDoc::Stage { MergeState& mState; public: MergeRefIdsStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class MergeReferencesStage : public CSMDoc::Stage { MergeState& mState; std::map mIndex; public: MergeReferencesStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; /// Adds all land texture records that could potentially be referenced when merging class PopulateLandTexturesMergeStage : public CSMDoc::Stage { MergeState& mState; public: PopulateLandTexturesMergeStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; class MergeLandStage : public CSMDoc::Stage { MergeState& mState; public: MergeLandStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; /// During this stage, the complex process of combining LandTextures from /// potentially multiple plugins is undertaken. class FixLandsAndLandTexturesMergeStage : public CSMDoc::Stage { MergeState& mState; public: FixLandsAndLandTexturesMergeStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; /// Removes base LandTexture records. This gets rid of the base records previously /// needed in FixLandsAndLandTexturesMergeStage. class CleanupLandTexturesMergeStage : public CSMDoc::Stage { MergeState& mState; public: CleanupLandTexturesMergeStage (MergeState& state); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/mergestate.hpp000066400000000000000000000010121445372753700237710ustar00rootroot00000000000000#ifndef CSM_TOOLS_MERGESTATE_H #define CSM_TOOLS_MERGESTATE_H #include #include #include #include "../doc/document.hpp" namespace CSMTools { struct MergeState { std::unique_ptr mTarget; CSMDoc::Document& mSource; bool mCompleted; std::map, int> mTextureIndices; // (texture, content file) -> new texture MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/pathgridcheck.cpp000066400000000000000000000123241445372753700244340ustar00rootroot00000000000000#include "pathgridcheck.hpp" #include #include #include "../prefs/state.hpp" #include "../world/universalid.hpp" #include "../world/idcollection.hpp" #include "../world/subcellcollection.hpp" #include "../world/pathgrid.hpp" CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection& pathgrids) : mPathgrids (pathgrids) { mIgnoreBaseRecords = false; } int CSMTools::PathgridCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mPathgrids.getSize(); } void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mPathgrids.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const CSMWorld::Pathgrid& pathgrid = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); // check the number of pathgrid points if (pathgrid.mData.mS2 < static_cast(pathgrid.mPoints.size())) messages.add (id, "Less points than expected", "", CSMDoc::Message::Severity_Error); else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) messages.add (id, "More points than expected", "", CSMDoc::Message::Severity_Error); std::vector pointList(pathgrid.mPoints.size()); std::vector duplList; for (unsigned int i = 0; i < pathgrid.mEdges.size(); ++i) { if (pathgrid.mEdges[i].mV0 < static_cast(pathgrid.mPoints.size()) && pathgrid.mEdges[i].mV0 >= 0) { pointList[pathgrid.mEdges[i].mV0].mConnectionNum++; // first check for duplicate edges unsigned int j = 0; for (; j < pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size(); ++j) { if (pointList[pathgrid.mEdges[i].mV0].mOtherIndex[j] == pathgrid.mEdges[i].mV1) { std::ostringstream ss; ss << "Duplicate edge between points #" << pathgrid.mEdges[i].mV0 << " and #" << pathgrid.mEdges[i].mV1; messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); break; } } // only add if not a duplicate if (j == pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size()) pointList[pathgrid.mEdges[i].mV0].mOtherIndex.push_back(pathgrid.mEdges[i].mV1); } else { std::ostringstream ss; ss << "An edge is connected to a non-existent point #" << pathgrid.mEdges[i].mV0; messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); } } for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) { // check that edges are bidirectional bool foundReverse = false; for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) { for (unsigned int k = 0; k < pointList[pointList[i].mOtherIndex[j]].mOtherIndex.size(); ++k) { if (pointList[pointList[i].mOtherIndex[j]].mOtherIndex[k] == static_cast(i)) { foundReverse = true; break; } } if (!foundReverse) { std::ostringstream ss; ss << "Missing edge between points #" << i << " and #" << pointList[i].mOtherIndex[j]; messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); } } // check duplicate points // FIXME: how to do this efficiently? for (unsigned int j = 0; j != i; ++j) { if (pathgrid.mPoints[i].mX == pathgrid.mPoints[j].mX && pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY && pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) { std::vector::const_iterator it = find(duplList.begin(), duplList.end(), static_cast(i)); if (it == duplList.end()) { std::ostringstream ss; ss << "Point #" << i << " duplicates point #" << j << " (" << pathgrid.mPoints[i].mX << ", " << pathgrid.mPoints[i].mY << ", " << pathgrid.mPoints[i].mZ << ")"; messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Warning); duplList.push_back(i); break; } } } } // check pathgrid points that are not connected to anything for (unsigned int i = 0; i < pointList.size(); ++i) { if (pointList[i].mConnectionNum == 0) { std::ostringstream ss; ss << "Point #" << i << " (" << pathgrid.mPoints[i].mX << ", " << pathgrid.mPoints[i].mY << ", " << pathgrid.mPoints[i].mZ << ") is disconnected from other points"; messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Warning); } } // TODO: check whether there are disconnected graphs } openmw-openmw-0.48.0/apps/opencs/model/tools/pathgridcheck.hpp000066400000000000000000000017051445372753700244420ustar00rootroot00000000000000#ifndef CSM_TOOLS_PATHGRIDCHECK_H #define CSM_TOOLS_PATHGRIDCHECK_H #include "../world/collection.hpp" #include "../doc/stage.hpp" namespace CSMWorld { struct Pathgrid; template class SubCellCollection; } namespace CSMTools { struct Point { unsigned char mConnectionNum; std::vector mOtherIndex; Point() : mConnectionNum(0), mOtherIndex(0) {} }; class PathgridCheckStage : public CSMDoc::Stage { const CSMWorld::SubCellCollection >& mPathgrids; bool mIgnoreBaseRecords; public: PathgridCheckStage (const CSMWorld::SubCellCollection >& pathgrids); int setup() override; void perform (int stage, CSMDoc::Messages& messages) override; }; } #endif // CSM_TOOLS_PATHGRIDCHECK_H openmw-openmw-0.48.0/apps/opencs/model/tools/racecheck.cpp000066400000000000000000000047141445372753700235500ustar00rootroot00000000000000#include "racecheck.hpp" #include "../prefs/state.hpp" #include "../world/universalid.hpp" void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mRaces.getRecord (stage); if (record.isDeleted()) return; const ESM::Race& race = record.get(); // Consider mPlayable flag even when "Base" records are ignored if (race.mData.mFlags & 0x1) mPlayable = true; // Skip "Base" records (setting!) if (mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) return; CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId); // test for empty name and description if (race.mName.empty()) messages.add(id, "Name is missing", "", (race.mData.mFlags & 0x1) ? CSMDoc::Message::Severity_Error : CSMDoc::Message::Severity_Warning); if (race.mDescription.empty()) messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); // test for positive height if (race.mData.mHeight.mMale<=0) messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error); if (race.mData.mHeight.mFemale<=0) messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error); // test for non-negative weight if (race.mData.mWeight.mMale<0) messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error); if (race.mData.mWeight.mFemale<0) messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view } void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); if (!mPlayable) messages.add(id, "No playable race", "", CSMDoc::Message::Severity_SeriousError); } CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection& races) : mRaces (races), mPlayable (false) { mIgnoreBaseRecords = false; } int CSMTools::RaceCheckStage::setup() { mPlayable = false; mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mRaces.getSize()+1; } void CSMTools::RaceCheckStage::perform (int stage, CSMDoc::Messages& messages) { if (stage==mRaces.getSize()) performFinal (messages); else performPerRecord (stage, messages); } openmw-openmw-0.48.0/apps/opencs/model/tools/racecheck.hpp000066400000000000000000000016511445372753700235520ustar00rootroot00000000000000#ifndef CSM_TOOLS_RACECHECK_H #define CSM_TOOLS_RACECHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that race records are internally consistent class RaceCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRaces; bool mPlayable; bool mIgnoreBaseRecords; void performPerRecord (int stage, CSMDoc::Messages& messages); void performFinal (CSMDoc::Messages& messages); public: RaceCheckStage (const CSMWorld::IdCollection& races); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/referenceablecheck.cpp000066400000000000000000001214651445372753700254230ustar00rootroot00000000000000#include "referenceablecheck.hpp" #include #include #include "../prefs/state.hpp" #include "../world/record.hpp" #include "../world/universalid.hpp" CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& faction, const CSMWorld::IdCollection& scripts, const CSMWorld::Resources& models, const CSMWorld::Resources& icons, const CSMWorld::IdCollection& bodyparts) :mReferencables(referenceable), mRaces(races), mClasses(classes), mFactions(faction), mScripts(scripts), mModels(models), mIcons(icons), mBodyParts(bodyparts), mPlayerPresent(false) { mIgnoreBaseRecords = false; } void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& messages) { //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. const int bookSize(mReferencables.getBooks().getSize()); if (stage < bookSize) { bookCheck(stage, mReferencables.getBooks(), messages); return; } stage -= bookSize; const int activatorSize(mReferencables.getActivators().getSize()); if (stage < activatorSize) { activatorCheck(stage, mReferencables.getActivators(), messages); return; } stage -= activatorSize; const int potionSize(mReferencables.getPotions().getSize()); if (stage < potionSize) { potionCheck(stage, mReferencables.getPotions(), messages); return; } stage -= potionSize; const int apparatusSize(mReferencables.getApparati().getSize()); if (stage < apparatusSize) { apparatusCheck(stage, mReferencables.getApparati(), messages); return; } stage -= apparatusSize; const int armorSize(mReferencables.getArmors().getSize()); if (stage < armorSize) { armorCheck(stage, mReferencables.getArmors(), messages); return; } stage -= armorSize; const int clothingSize(mReferencables.getClothing().getSize()); if (stage < clothingSize) { clothingCheck(stage, mReferencables.getClothing(), messages); return; } stage -= clothingSize; const int containerSize(mReferencables.getContainers().getSize()); if (stage < containerSize) { containerCheck(stage, mReferencables.getContainers(), messages); return; } stage -= containerSize; const int doorSize(mReferencables.getDoors().getSize()); if (stage < doorSize) { doorCheck(stage, mReferencables.getDoors(), messages); return; } stage -= doorSize; const int ingredientSize(mReferencables.getIngredients().getSize()); if (stage < ingredientSize) { ingredientCheck(stage, mReferencables.getIngredients(), messages); return; } stage -= ingredientSize; const int creatureLevListSize(mReferencables.getCreatureLevelledLists().getSize()); if (stage < creatureLevListSize) { creaturesLevListCheck(stage, mReferencables.getCreatureLevelledLists(), messages); return; } stage -= creatureLevListSize; const int itemLevelledListSize(mReferencables.getItemLevelledList().getSize()); if (stage < itemLevelledListSize) { itemLevelledListCheck(stage, mReferencables.getItemLevelledList(), messages); return; } stage -= itemLevelledListSize; const int lightSize(mReferencables.getLights().getSize()); if (stage < lightSize) { lightCheck(stage, mReferencables.getLights(), messages); return; } stage -= lightSize; const int lockpickSize(mReferencables.getLocpicks().getSize()); if (stage < lockpickSize) { lockpickCheck(stage, mReferencables.getLocpicks(), messages); return; } stage -= lockpickSize; const int miscSize(mReferencables.getMiscellaneous().getSize()); if (stage < miscSize) { miscCheck(stage, mReferencables.getMiscellaneous(), messages); return; } stage -= miscSize; const int npcSize(mReferencables.getNPCs().getSize()); if (stage < npcSize) { npcCheck(stage, mReferencables.getNPCs(), messages); return; } stage -= npcSize; const int weaponSize(mReferencables.getWeapons().getSize()); if (stage < weaponSize) { weaponCheck(stage, mReferencables.getWeapons(), messages); return; } stage -= weaponSize; const int probeSize(mReferencables.getProbes().getSize()); if (stage < probeSize) { probeCheck(stage, mReferencables.getProbes(), messages); return; } stage -= probeSize; const int repairSize(mReferencables.getRepairs().getSize()); if (stage < repairSize) { repairCheck(stage, mReferencables.getRepairs(), messages); return; } stage -= repairSize; const int staticSize(mReferencables.getStatics().getSize()); if (stage < staticSize) { staticCheck(stage, mReferencables.getStatics(), messages); return; } stage -= staticSize; const int creatureSize(mReferencables.getCreatures().getSize()); if (stage < creatureSize) { creatureCheck(stage, mReferencables.getCreatures(), messages); return; } // if we come that far, we are about to perform our last, final check. finalCheck(messages); return; } int CSMTools::ReferenceableCheckStage::setup() { mPlayerPresent = false; mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mReferencables.getSize() + 1; } void CSMTools::ReferenceableCheckStage::bookCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Book& book = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId); inventoryItemCheck(book, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(book, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::activatorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Activator& activator = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); if (activator.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(activator.mModel) == -1) messages.add(id, "Model '" + activator.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(activator, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::potionCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Potion >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Potion& potion = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); inventoryItemCheck(potion, messages, id.toString()); /// \todo Check magic effects for validity // Check that mentioned scripts exist scriptCheck(potion, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::apparatusCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Apparatus& apparatus = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId); inventoryItemCheck(apparatus, messages, id.toString()); toolCheck(apparatus, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(apparatus, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::armorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Armor >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Armor& armor = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId); inventoryItemCheck(armor, messages, id.toString(), true); // Armor should have positive armor class, but 0 class is not an error if (armor.mData.mArmor < 0) messages.add(id, "Armor class is negative", "", CSMDoc::Message::Severity_Error); // Armor durability must be a positive number if (armor.mData.mHealth <= 0) messages.add(id, "Durability is non-positive", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(armor, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::clothingCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Clothing >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Clothing& clothing = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId); inventoryItemCheck(clothing, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(clothing, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::containerCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Container >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Container& container = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); //checking for name if (container.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); //Checking for model if (container.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(container.mModel) == -1) messages.add(id, "Model '" + container.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); //Checking for capacity (weight) if (container.mWeight < 0) //0 is allowed messages.add(id, "Capacity is negative", "", CSMDoc::Message::Severity_Error); //checking contained items inventoryListCheck(container.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(container, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::creatureCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Creature >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Creature& creature = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Creature, creature.mId); if (creature.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (creature.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(creature.mModel) == -1) messages.add(id, "Model '" + creature.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); //stats checks if (creature.mData.mLevel <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mStrength < 0) messages.add(id, "Strength is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mIntelligence < 0) messages.add(id, "Intelligence is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mWillpower < 0) messages.add(id, "Willpower is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mAgility < 0) messages.add(id, "Agility is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mSpeed < 0) messages.add(id, "Speed is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mEndurance < 0) messages.add(id, "Endurance is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mPersonality < 0) messages.add(id, "Personality is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mLuck < 0) messages.add(id, "Luck is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mCombat < 0) messages.add(id, "Combat is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mMagic < 0) messages.add(id, "Magic is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mStealth < 0) messages.add(id, "Stealth is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mHealth < 0) messages.add(id, "Health is negative", "", CSMDoc::Message::Severity_Error); if (creature.mData.mMana < 0) messages.add(id, "Magicka is negative", "", CSMDoc::Message::Severity_Error); if (creature.mData.mFatigue < 0) messages.add(id, "Fatigue is negative", "", CSMDoc::Message::Severity_Error); if (creature.mData.mSoul < 0) messages.add(id, "Soul value is negative", "", CSMDoc::Message::Severity_Error); if (creature.mAiData.mAlarm > 100) messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning); if (creature.mAiData.mFight > 100) messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning); if (creature.mAiData.mFlee > 100) messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning); for (int i = 0; i < 6; ++i) { if (creature.mData.mAttack[i] < 0) messages.add(id, "Attack " + std::to_string(i/2 + 1) + " has negative" + (i % 2 == 0 ? " minimum " : " maximum ") + "damage", "", CSMDoc::Message::Severity_Error); if (i % 2 == 0 && creature.mData.mAttack[i] > creature.mData.mAttack[i+1]) messages.add(id, "Attack " + std::to_string(i/2 + 1) + " has minimum damage higher than maximum damage", "", CSMDoc::Message::Severity_Error); } if (creature.mData.mGold < 0) messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); if (creature.mScale == 0) messages.add(id, "Scale is equal to zero", "", CSMDoc::Message::Severity_Error); if (!creature.mOriginal.empty()) { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(creature.mOriginal); if (index.first == -1) messages.add(id, "Parent creature '" + creature.mOriginal + "' does not exist", "", CSMDoc::Message::Severity_Error); else if (index.second != CSMWorld::UniversalId::Type_Creature) messages.add(id, "'" + creature.mOriginal + "' is not a creature", "", CSMDoc::Message::Severity_Error); } // Check inventory inventoryListCheck(creature.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); /// \todo Check spells, teleport table, AI data and AI packages for validity } void CSMTools::ReferenceableCheckStage::doorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Door >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Door& door = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Door, door.mId); //usual, name or model if (door.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (door.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(door.mModel) == -1) messages.add(id, "Model '" + door.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(door, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::ingredientCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Ingredient& ingredient = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, ingredient.mId); inventoryItemCheck(ingredient, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(ingredient, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::CreatureLevList& CreatureLevList = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/ listCheck(CreatureLevList, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::ItemLevList& ItemLevList = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId); listCheck(ItemLevList, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::lightCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Light >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Light& light = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId); if (light.mData.mRadius < 0) messages.add(id, "Light radius is negative", "", CSMDoc::Message::Severity_Error); if (light.mData.mFlags & ESM::Light::Carry) inventoryItemCheck(light, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(light, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::lockpickCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Lockpick& lockpick = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId); inventoryItemCheck(lockpick, messages, id.toString()); toolCheck(lockpick, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(lockpick, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::miscCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Miscellaneous& miscellaneous = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId); inventoryItemCheck(miscellaneous, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(miscellaneous, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::npcCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::NPC >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) return; const ESM::NPC& npc = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId); //Detect if player is present if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? mPlayerPresent = true; // Skip "Base" records (setting!) if (mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) return; short level(npc.mNpdt.mLevel); int gold(npc.mNpdt.mGold); if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated { if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag { messages.add(id, "NPC with autocalculated stats doesn't have autocalc flag turned on", "", CSMDoc::Message::Severity_Error); //should not happen? return; } } else { if (npc.mNpdt.mStrength == 0) messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mIntelligence == 0) messages.add(id, "Intelligence is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mWillpower == 0) messages.add(id, "Willpower is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mAgility == 0) messages.add(id, "Agility is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mSpeed == 0) messages.add(id, "Speed is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mEndurance == 0) messages.add(id, "Endurance is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mPersonality == 0) messages.add(id, "Personality is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mLuck == 0) messages.add(id, "Luck is equal to zero", "", CSMDoc::Message::Severity_Warning); } if (level <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); if (npc.mAiData.mAlarm > 100) messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning); if (npc.mAiData.mFight > 100) messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning); if (npc.mAiData.mFlee > 100) messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning); if (gold < 0) messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); if (npc.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (npc.mClass.empty()) messages.add(id, "Class is missing", "", CSMDoc::Message::Severity_Error); else if (mClasses.searchId (npc.mClass) == -1) messages.add(id, "Class '" + npc.mClass + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mRace.empty()) messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); else if (mRaces.searchId (npc.mRace) == -1) messages.add(id, "Race '" + npc.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); if (!npc.mFaction.empty() && mFactions.searchId(npc.mFaction) == -1) messages.add(id, "Faction '" + npc.mFaction + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mHead.empty()) messages.add(id, "Head is missing", "", CSMDoc::Message::Severity_Error); else { if (mBodyParts.searchId(npc.mHead) == -1) messages.add(id, "Head body part '" + npc.mHead + "' does not exist", "", CSMDoc::Message::Severity_Error); /// \todo Check gender, race and other body parts stuff validity for the specific NPC } if (npc.mHair.empty()) messages.add(id, "Hair is missing", "", CSMDoc::Message::Severity_Error); else { if (mBodyParts.searchId(npc.mHair) == -1) messages.add(id, "Hair body part '" + npc.mHair + "' does not exist", "", CSMDoc::Message::Severity_Error); /// \todo Check gender, race and other body part stuff validity for the specific NPC } // Check inventory inventoryListCheck(npc.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(npc, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::weaponCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Weapon& weapon = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Weapon, weapon.mId); //TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present if ( //THOSE ARE HARDCODED! !(weapon.mId == "VFX_Hands" || weapon.mId == "VFX_Absorb" || weapon.mId == "VFX_Reflect" || weapon.mId == "VFX_DefaultBolt" || //TODO I don't know how to get full list of effects :/ //DANGER!, ACHTUNG! FIXME! The following is the list of the magical bolts, valid for Morrowind.esm. However those are not hardcoded. weapon.mId == "magic_bolt" || weapon.mId == "shock_bolt" || weapon.mId == "shield_bolt" || weapon.mId == "VFX_DestructBolt" || weapon.mId == "VFX_PoisonBolt" || weapon.mId == "VFX_RestoreBolt" || weapon.mId == "VFX_AlterationBolt" || weapon.mId == "VFX_ConjureBolt" || weapon.mId == "VFX_FrostBolt" || weapon.mId == "VFX_MysticismBolt" || weapon.mId == "VFX_IllusionBolt" || weapon.mId == "VFX_Multiple2" || weapon.mId == "VFX_Multiple3" || weapon.mId == "VFX_Multiple4" || weapon.mId == "VFX_Multiple5" || weapon.mId == "VFX_Multiple6" || weapon.mId == "VFX_Multiple7" || weapon.mId == "VFX_Multiple8" || weapon.mId == "VFX_Multiple9")) { inventoryItemCheck(weapon, messages, id.toString(), true); if (!(weapon.mData.mType == ESM::Weapon::MarksmanBow || weapon.mData.mType == ESM::Weapon::MarksmanCrossbow || weapon.mData.mType == ESM::Weapon::MarksmanThrown || weapon.mData.mType == ESM::Weapon::Arrow || weapon.mData.mType == ESM::Weapon::Bolt)) { if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1]) messages.add(id, "Minimum slash damage higher than maximum", "", CSMDoc::Message::Severity_Warning); if (weapon.mData.mThrust[0] > weapon.mData.mThrust[1]) messages.add(id, "Minimum thrust damage higher than maximum", "", CSMDoc::Message::Severity_Warning); } if (weapon.mData.mChop[0] > weapon.mData.mChop[1]) messages.add(id, "Minimum chop damage higher than maximum", "", CSMDoc::Message::Severity_Warning); if (!(weapon.mData.mType == ESM::Weapon::Arrow || weapon.mData.mType == ESM::Weapon::Bolt || weapon.mData.mType == ESM::Weapon::MarksmanThrown)) { //checking of health if (weapon.mData.mHealth == 0) messages.add(id, "Durability is equal to zero", "", CSMDoc::Message::Severity_Warning); if (weapon.mData.mReach < 0) messages.add(id, "Reach is negative", "", CSMDoc::Message::Severity_Error); } } // Check that mentioned scripts exist scriptCheck(weapon, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::probeCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Probe >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Probe& probe = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId); inventoryItemCheck(probe, messages, id.toString()); toolCheck(probe, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(probe, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::repairCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Repair >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Repair& repair = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Repair, repair.mId); inventoryItemCheck (repair, messages, id.toString()); toolCheck (repair, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(repair, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::staticCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Static >& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; const ESM::Static& staticElement = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Static, staticElement.mId); if (staticElement.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(staticElement.mModel) == -1) messages.add(id, "Model '" + staticElement.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); } //final check void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) { if (!mPlayerPresent) messages.add(CSMWorld::UniversalId::Type_Referenceables, "Player record is missing", "", CSMDoc::Message::Severity_SeriousError); } void CSMTools::ReferenceableCheckStage::inventoryListCheck( const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id) { for (size_t i = 0; i < itemList.size(); ++i) { std::string itemName = itemList[i].mItem; CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); if (localIndex.first == -1) messages.add(id, "Item '" + itemName + "' does not exist", "", CSMDoc::Message::Severity_Error); else { // Needs to accommodate containers, creatures, and NPCs switch (localIndex.second) { case CSMWorld::UniversalId::Type_Potion: case CSMWorld::UniversalId::Type_Apparatus: case CSMWorld::UniversalId::Type_Armor: case CSMWorld::UniversalId::Type_Book: case CSMWorld::UniversalId::Type_Clothing: case CSMWorld::UniversalId::Type_Ingredient: case CSMWorld::UniversalId::Type_Light: case CSMWorld::UniversalId::Type_Lockpick: case CSMWorld::UniversalId::Type_Miscellaneous: case CSMWorld::UniversalId::Type_Probe: case CSMWorld::UniversalId::Type_Repair: case CSMWorld::UniversalId::Type_Weapon: case CSMWorld::UniversalId::Type_ItemLevelledList: break; default: messages.add(id, "'" + itemName + "' is not an item", "", CSMDoc::Message::Severity_Error); } } } } //Templates begins here template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( const Item& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable) { if (someItem.mName.empty()) messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); //Checking for weight if (someItem.mData.mWeight < 0) messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); //Checking for value if (someItem.mData.mValue < 0) messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); //checking for model if (someItem.mModel.empty()) messages.add(someID, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(someItem.mModel) == -1) messages.add(someID, "Model '" + someItem.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); //checking for icon if (someItem.mIcon.empty()) messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); else if (mIcons.searchId(someItem.mIcon) == -1) { std::string ddsIcon = someItem.mIcon; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) messages.add(someID, "Icon '" + someItem.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); } if (enchantable && someItem.mData.mEnchant < 0) messages.add(someID, "Enchantment points number is negative", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( const Item& someItem, CSMDoc::Messages& messages, const std::string& someID) { if (someItem.mName.empty()) messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); //Checking for weight if (someItem.mData.mWeight < 0) messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); //Checking for value if (someItem.mData.mValue < 0) messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); //checking for model if (someItem.mModel.empty()) messages.add(someID, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(someItem.mModel) == -1) messages.add(someID, "Model '" + someItem.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); //checking for icon if (someItem.mIcon.empty()) messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); else if (mIcons.searchId(someItem.mIcon) == -1) { std::string ddsIcon = someItem.mIcon; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) messages.add(someID, "Icon '" + someItem.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); } } template void CSMTools::ReferenceableCheckStage::toolCheck ( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canBeBroken) { if (someTool.mData.mQuality <= 0) messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); if (canBeBroken && someTool.mData.mUses<=0) messages.add(someID, "Number of uses is non-positive", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::toolCheck ( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (someTool.mData.mQuality <= 0) messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::listCheck ( const List& someList, CSMDoc::Messages& messages, const std::string& someID) { if (someList.mChanceNone > 100) { messages.add(someID, "Chance that no object is used is over 100 percent", "", CSMDoc::Message::Severity_Warning); } for (unsigned i = 0; i < someList.mList.size(); ++i) { if (mReferencables.searchId(someList.mList[i].mId).first == -1) messages.add(someID, "Object '" + someList.mList[i].mId + "' does not exist", "", CSMDoc::Message::Severity_Error); if (someList.mList[i].mLevel < 1) messages.add(someID, "Level of item '" + someList.mList[i].mId + "' is non-positive", "", CSMDoc::Message::Severity_Error); } } template void CSMTools::ReferenceableCheckStage::scriptCheck ( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (!someTool.mScript.empty()) { if (mScripts.searchId(someTool.mScript) == -1) messages.add(someID, "Script '" + someTool.mScript + "' does not exist", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/referenceablecheck.hpp000066400000000000000000000140201445372753700254140ustar00rootroot00000000000000#ifndef REFERENCEABLECHECKSTAGE_H #define REFERENCEABLECHECKSTAGE_H #include "../world/universalid.hpp" #include "../doc/stage.hpp" #include "../world/data.hpp" #include "../world/refiddata.hpp" #include "../world/resources.hpp" namespace CSMTools { class ReferenceableCheckStage : public CSMDoc::Stage { public: ReferenceableCheckStage (const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& scripts, const CSMWorld::Resources& models, const CSMWorld::Resources& icons, const CSMWorld::IdCollection& bodyparts); void perform(int stage, CSMDoc::Messages& messages) override; int setup() override; private: //CONCRETE CHECKS void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, CSMDoc::Messages& messages); void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, CSMDoc::Messages& messages); void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); //FINAL CHECK void finalCheck (CSMDoc::Messages& messages); //Convenience functions void inventoryListCheck(const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id); template void inventoryItemCheck(const ITEM& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable); //for all enchantable items. template void inventoryItemCheck(const ITEM& someItem, CSMDoc::Messages& messages, const std::string& someID); //for non-enchantable items. template void toolCheck(const TOOL& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canbebroken); //for tools with uses. template void toolCheck(const TOOL& someTool, CSMDoc::Messages& messages, const std::string& someID); //for tools without uses. template void listCheck(const LIST& someList, CSMDoc::Messages& messages, const std::string& someID); template void scriptCheck(const TOOL& someTool, CSMDoc::Messages& messages, const std::string& someID); const CSMWorld::RefIdData& mReferencables; const CSMWorld::IdCollection& mRaces; const CSMWorld::IdCollection& mClasses; const CSMWorld::IdCollection& mFactions; const CSMWorld::IdCollection& mScripts; const CSMWorld::Resources& mModels; const CSMWorld::Resources& mIcons; const CSMWorld::IdCollection& mBodyParts; bool mPlayerPresent; bool mIgnoreBaseRecords; }; } #endif // REFERENCEABLECHECKSTAGE_H openmw-openmw-0.48.0/apps/opencs/model/tools/referencecheck.cpp000066400000000000000000000074411445372753700245740ustar00rootroot00000000000000#include "referencecheck.hpp" #include "../prefs/state.hpp" CSMTools::ReferenceCheckStage::ReferenceCheckStage( const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions) : mReferences(references), mObjects(referencables), mDataSet(referencables.getDataSet()), mCells(cells), mFactions(factions) { mIgnoreBaseRecords = false; } void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &messages) { const CSMWorld::Record& record = mReferences.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const CSMWorld::CellRef& cellRef = record.get(); const CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Reference, cellRef.mId); // Check reference id if (cellRef.mRefID.empty()) messages.add(id, "Instance is not based on an object", "", CSMDoc::Message::Severity_Error); else { // Check for non existing referenced object if (mObjects.searchId(cellRef.mRefID) == -1) messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID + "'", "", CSMDoc::Message::Severity_Error); else { // Check if reference charge is valid for it's proper referenced type CSMWorld::RefIdData::LocalIndex localIndex = mDataSet.searchId(cellRef.mRefID); bool isLight = localIndex.second == CSMWorld::UniversalId::Type_Light; if ((isLight && cellRef.mChargeFloat < -1) || (!isLight && cellRef.mChargeInt < -1)) messages.add(id, "Invalid charge", "", CSMDoc::Message::Severity_Error); } } // If object have owner, check if that owner reference is valid if (!cellRef.mOwner.empty() && mObjects.searchId(cellRef.mOwner) == -1) messages.add(id, "Owner object '" + cellRef.mOwner + "' does not exist", "", CSMDoc::Message::Severity_Error); // If object have creature soul trapped, check if that creature reference is valid if (!cellRef.mSoul.empty()) if (mObjects.searchId(cellRef.mSoul) == -1) messages.add(id, "Trapped soul object '" + cellRef.mSoul + "' does not exist", "", CSMDoc::Message::Severity_Error); if (cellRef.mFaction.empty()) { if (cellRef.mFactionRank != -2) messages.add(id, "Reference without a faction has a faction rank", "", CSMDoc::Message::Severity_Error); } else { if (mFactions.searchId(cellRef.mFaction) == -1) messages.add(id, "Faction '" + cellRef.mFaction + "' does not exist", "", CSMDoc::Message::Severity_Error); else if (cellRef.mFactionRank < -1) messages.add(id, "Invalid faction rank", "", CSMDoc::Message::Severity_Error); } if (!cellRef.mDestCell.empty() && mCells.searchId(cellRef.mDestCell) == -1) messages.add(id, "Destination cell '" + cellRef.mDestCell + "' does not exist", "", CSMDoc::Message::Severity_Error); if (cellRef.mScale < 0) messages.add(id, "Negative scale", "", CSMDoc::Message::Severity_Error); // Check if enchantement points aren't negative or are at full (-1) if (cellRef.mEnchantmentCharge < -1) messages.add(id, "Negative number of enchantment points", "", CSMDoc::Message::Severity_Error); // Check if gold value isn't negative if (cellRef.mGoldValue < 0) messages.add(id, "Negative gold value", "", CSMDoc::Message::Severity_Error); } int CSMTools::ReferenceCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mReferences.getSize(); } openmw-openmw-0.48.0/apps/opencs/model/tools/referencecheck.hpp000066400000000000000000000017631445372753700246020ustar00rootroot00000000000000#ifndef CSM_TOOLS_REFERENCECHECK_H #define CSM_TOOLS_REFERENCECHECK_H #include "../doc/state.hpp" #include "../doc/document.hpp" namespace CSMTools { class ReferenceCheckStage : public CSMDoc::Stage { public: ReferenceCheckStage (const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions); void perform(int stage, CSMDoc::Messages& messages) override; int setup() override; private: const CSMWorld::RefCollection& mReferences; const CSMWorld::RefIdCollection& mObjects; const CSMWorld::RefIdData& mDataSet; const CSMWorld::IdCollection& mCells; const CSMWorld::IdCollection& mFactions; bool mIgnoreBaseRecords; }; } #endif // CSM_TOOLS_REFERENCECHECK_H openmw-openmw-0.48.0/apps/opencs/model/tools/regioncheck.cpp000066400000000000000000000034501445372753700241150ustar00rootroot00000000000000#include "regioncheck.hpp" #include "../prefs/state.hpp" #include "../world/universalid.hpp" CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection& regions) : mRegions (regions) { mIgnoreBaseRecords = false; } int CSMTools::RegionCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mRegions.getSize(); } void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mRegions.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Region& region = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Region, region.mId); // test for empty name if (region.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); /// \todo test that the ID in mSleeplist exists // test that chances add up to 100 int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast + region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + region.mData.mSnow + region.mData.mBlizzard; if (chances != 100) messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); for (const ESM::Region::SoundRef& sound : region.mSoundList) { if (sound.mChance > 100) messages.add(id, "Chance of '" + sound.mSound + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning); } /// \todo check data members that can't be edited in the table view } openmw-openmw-0.48.0/apps/opencs/model/tools/regioncheck.hpp000066400000000000000000000014261445372753700241230ustar00rootroot00000000000000#ifndef CSM_TOOLS_REGIONCHECK_H #define CSM_TOOLS_REGIONCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that region records are internally consistent class RegionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRegions; bool mIgnoreBaseRecords; public: RegionCheckStage (const CSMWorld::IdCollection& regions); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/reportmodel.cpp000066400000000000000000000110561445372753700241710ustar00rootroot00000000000000#include "reportmodel.hpp" #include #include #include "../world/columns.hpp" CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) : mColumnField (-1), mColumnSeverity (-1) { int index = 3; if (severityColumn) mColumnSeverity = index++; if (fieldColumn) mColumnField = index++; mColumnDescription = index; } int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return static_cast(mRows.size()); } int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return mColumnDescription+1; } QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const { if (role!=Qt::DisplayRole && role!=Qt::UserRole) return QVariant(); switch (index.column()) { case Column_Type: if(role == Qt::UserRole) return QString::fromUtf8 ( mRows.at (index.row()).mId.getTypeName().c_str()); else return static_cast (mRows.at (index.row()).mId.getType()); case Column_Id: { CSMWorld::UniversalId id = mRows.at (index.row()).mId; if (id.getArgumentType()==CSMWorld::UniversalId::ArgumentType_Id) return QString::fromUtf8 (id.getId().c_str()); return QString ("-"); } case Column_Hint: return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); } if (index.column()==mColumnDescription) return QString::fromUtf8 (mRows.at (index.row()).mMessage.c_str()); if (index.column()==mColumnField) { std::string field; std::istringstream stream (mRows.at (index.row()).mHint); char type, ignore; int fieldIndex; if ((stream >> type >> ignore >> fieldIndex) && (type=='r' || type=='R')) { field = CSMWorld::Columns::getName ( static_cast (fieldIndex)); } return QString::fromUtf8 (field.c_str()); } if (index.column()==mColumnSeverity) { return QString::fromUtf8 ( CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); } return QVariant(); } QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const { if (role!=Qt::DisplayRole) return QVariant(); if (orientation==Qt::Vertical) return QVariant(); switch (section) { case Column_Type: return "Type"; case Column_Id: return "ID"; } if (section==mColumnDescription) return "Description"; if (section==mColumnField) return "Field"; if (section==mColumnSeverity) return "Severity"; return "-"; } bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) { if (parent.isValid()) return false; if (count>0) { beginRemoveRows (parent, row, row+count-1); mRows.erase (mRows.begin()+row, mRows.begin()+row+count); endRemoveRows(); } return true; } void CSMTools::ReportModel::add (const CSMDoc::Message& message) { beginInsertRows (QModelIndex(), static_cast(mRows.size()), static_cast(mRows.size())); mRows.push_back (message); endInsertRows(); } void CSMTools::ReportModel::flagAsReplaced (int index) { CSMDoc::Message& line = mRows.at (index); std::string hint = line.mHint; if (hint.empty() || hint[0]!='R') throw std::logic_error ("trying to flag message as replaced that is not replaceable"); hint[0] = 'r'; line.mHint = hint; emit dataChanged (this->index (index, 0), this->index (index, columnCount())); } const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const { return mRows.at (row).mId; } std::string CSMTools::ReportModel::getHint (int row) const { return mRows.at (row).mHint; } void CSMTools::ReportModel::clear() { if (!mRows.empty()) { beginRemoveRows (QModelIndex(), 0, static_cast(mRows.size())-1); mRows.clear(); endRemoveRows(); } } int CSMTools::ReportModel::countErrors() const { int count = 0; for (std::vector::const_iterator iter (mRows.begin()); iter!=mRows.end(); ++iter) if (iter->mSeverity==CSMDoc::Message::Severity_Error || iter->mSeverity==CSMDoc::Message::Severity_SeriousError) ++count; return count; } openmw-openmw-0.48.0/apps/opencs/model/tools/reportmodel.hpp000066400000000000000000000031661445372753700242010ustar00rootroot00000000000000#ifndef CSM_TOOLS_REPORTMODEL_H #define CSM_TOOLS_REPORTMODEL_H #include #include #include #include "../doc/messages.hpp" #include "../world/universalid.hpp" namespace CSMTools { class ReportModel : public QAbstractTableModel { Q_OBJECT std::vector mRows; // Fixed columns enum Columns { Column_Type = 0, Column_Id = 1, Column_Hint = 2 }; // Configurable columns int mColumnDescription; int mColumnField; int mColumnSeverity; public: ReportModel (bool fieldColumn = false, bool severityColumn = true); int rowCount (const QModelIndex & parent = QModelIndex()) const override; int columnCount (const QModelIndex & parent = QModelIndex()) const override; QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; void add (const CSMDoc::Message& message); void flagAsReplaced (int index); const CSMWorld::UniversalId& getUniversalId (int row) const; std::string getHint (int row) const; void clear(); // Return number of messages with Error or SeriousError severity. int countErrors() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/scriptcheck.cpp000066400000000000000000000075131445372753700241420ustar00rootroot00000000000000#include "scriptcheck.hpp" #include #include #include #include #include #include #include "../doc/document.hpp" #include "../world/data.hpp" #include "../prefs/state.hpp" CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity (Type type) { switch (type) { case WarningMessage: return CSMDoc::Message::Severity_Warning; case ErrorMessage: return CSMDoc::Message::Severity_Error; } return CSMDoc::Message::Severity_SeriousError; } void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream stream; CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); stream << message << " (" << loc.mLiteral << ")" << " @ line " << loc.mLine+1 << ", column " << loc.mColumn; std::ostringstream hintStream; hintStream << "l:" << loc.mLine+1 << " " << loc.mColumn; mMessages->add (id, stream.str(), hintStream.str(), getSeverity (type)); } void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; stream << message; mMessages->add (id, stream.str(), "", getSeverity (type)); } CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) : mDocument (document), mContext (document.getData()), mMessages (nullptr), mWarningMode (Mode_Ignore) { /// \todo add an option to configure warning mode setWarningsMode (0); Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); mIgnoreBaseRecords = false; } int CSMTools::ScriptCheckStage::setup() { std::string warnings = CSMPrefs::get()["Scripts"]["warnings"].toString(); if (warnings=="Ignore") mWarningMode = Mode_Ignore; else if (warnings=="Normal") mWarningMode = Mode_Normal; else if (warnings=="Strict") mWarningMode = Mode_Strict; mContext.clear(); mMessages = nullptr; mId.clear(); Compiler::ErrorHandler::reset(); mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mDocument.getData().getScripts().getSize(); } void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record &record = mDocument.getData().getScripts().getRecord(stage); mId = mDocument.getData().getScripts().getId (stage); if (mDocument.isBlacklisted ( CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) return; // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; mMessages = &messages; switch (mWarningMode) { case Mode_Ignore: setWarningsMode (0); break; case Mode_Normal: setWarningsMode (1); break; case Mode_Strict: setWarningsMode (2); break; } try { mFile = record.get().mId; std::istringstream input (record.get().mScriptText); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); Compiler::FileParser parser (*this, mContext); scanner.scan (parser); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; stream << error.what(); messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = nullptr; } openmw-openmw-0.48.0/apps/opencs/model/tools/scriptcheck.hpp000066400000000000000000000030371445372753700241440ustar00rootroot00000000000000#ifndef CSM_TOOLS_SCRIPTCHECK_H #define CSM_TOOLS_SCRIPTCHECK_H #include #include #include "../doc/stage.hpp" #include "../world/scriptcontext.hpp" namespace CSMDoc { class Document; } namespace CSMTools { /// \brief VerifyStage: make sure that scripts compile class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler { enum WarningMode { Mode_Ignore, Mode_Normal, Mode_Strict }; const CSMDoc::Document& mDocument; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::string mId; std::string mFile; CSMDoc::Messages *mMessages; WarningMode mWarningMode; bool mIgnoreBaseRecords; CSMDoc::Message::Severity getSeverity (Type type); void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; ///< Report error to the user. void report (const std::string& message, Type type) override; ///< Report a file related error public: ScriptCheckStage (const CSMDoc::Document& document); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/search.cpp000066400000000000000000000215751445372753700231110ustar00rootroot00000000000000#include "search.hpp" #include #include #include "../doc/messages.hpp" #include "../doc/document.hpp" #include "../world/idtablebase.hpp" #include "../world/columnbase.hpp" #include "../world/universalid.hpp" #include "../world/commands.hpp" void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { // using QString here for easier handling of case folding. QString search = QString::fromUtf8 (mText.c_str()); QString text = model->data (index).toString(); int pos = 0; Qt::CaseSensitivity caseSensitivity = mCase ? Qt::CaseSensitive : Qt::CaseInsensitive; while ((pos = text.indexOf (search, pos, caseSensitivity))!=-1) { std::ostringstream hint; hint << (writable ? 'R' : 'r') <<": " << model->getColumnId (index.column()) << " " << pos << " " << search.length(); messages.add (id, formatDescription (text, pos, search.length()).toUtf8().data(), hint.str()); pos += search.length(); } } void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { QString text = model->data (index).toString(); int pos = 0; while ((pos = mRegExp.indexIn (text, pos))!=-1) { int length = mRegExp.matchedLength(); std::ostringstream hint; hint << (writable ? 'R' : 'r') <<": " << model->getColumnId (index.column()) << " " << pos << " " << length; messages.add (id, formatDescription (text, pos, length).toUtf8().data(), hint.str()); pos += length; } } void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { if (writable) throw std::logic_error ("Record state can not be modified by search and replace"); int data = model->data (index).toInt(); if (data==mValue) { std::vector> states = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); const std::string hint = "r: " + std::to_string(model->getColumnId(index.column())); messages.add (id, states.at(data).second, hint); } } QString CSMTools::Search::formatDescription (const QString& description, int pos, int length) const { QString text (description); // split QString highlight = flatten (text.mid (pos, length)); QString before = flatten (mPaddingBefore>=pos ? text.mid (0, pos) : text.mid (pos-mPaddingBefore, mPaddingBefore)); QString after = flatten (text.mid (pos+length, mPaddingAfter)); // compensate for Windows nonsense text.remove ('\r'); // join text = before + "" + highlight + "" + after; // improve layout for single line display text.replace ("\n", "<CR>"); text.replace ('\t', ' '); return text; } QString CSMTools::Search::flatten (const QString& text) const { QString flat (text); flat.replace ("&", "&"); flat.replace ("<", "<"); return flat; } CSMTools::Search::Search() : mType (Type_None), mValue (0), mCase (false), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) {} CSMTools::Search::Search (Type type, bool caseSensitive, const std::string& value) : mType (type), mText (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_Text && type!=Type_Id) throw std::logic_error ("Invalid search parameter (string)"); } CSMTools::Search::Search (Type type, bool caseSensitive, const QRegExp& value) : mType (type), mRegExp (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { mRegExp.setCaseSensitivity(mCase ? Qt::CaseSensitive : Qt::CaseInsensitive); if (type!=Type_TextRegEx && type!=Type_IdRegEx) throw std::logic_error ("Invalid search parameter (RegExp)"); } CSMTools::Search::Search (Type type, bool caseSensitive, int value) : mType (type), mValue (value), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) { if (type!=Type_RecordState) throw std::logic_error ("invalid search parameter (int)"); } void CSMTools::Search::configure (const CSMWorld::IdTableBase *model) { mColumns.clear(); int columns = model->columnCount(); for (int i=0; i ( model->headerData ( i, Qt::Horizontal, static_cast (CSMWorld::ColumnBase::Role_Display)).toInt()); bool consider = false; switch (mType) { case Type_Text: case Type_TextRegEx: if (CSMWorld::ColumnBase::isText (display) || CSMWorld::ColumnBase::isScript (display)) { consider = true; } break; case Type_Id: case Type_IdRegEx: if (CSMWorld::ColumnBase::isId (display) || CSMWorld::ColumnBase::isScript (display)) { consider = true; } break; case Type_RecordState: if (display==CSMWorld::ColumnBase::Display_RecordState) consider = true; break; case Type_None: break; } if (consider) mColumns.insert (i); } mIdColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_Id); mTypeColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); } void CSMTools::Search::searchRow (const CSMWorld::IdTableBase *model, int row, CSMDoc::Messages& messages) const { for (std::set::const_iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) { QModelIndex index = model->index (row, *iter); CSMWorld::UniversalId::Type type = static_cast ( model->data (model->index (row, mTypeColumn)).toInt()); CSMWorld::UniversalId id ( type, model->data (model->index (row, mIdColumn)).toString().toUtf8().data()); bool writable = model->flags (index) & Qt::ItemIsEditable; switch (mType) { case Type_Text: case Type_Id: searchTextCell (model, index, id, writable, messages); break; case Type_TextRegEx: case Type_IdRegEx: searchRegExCell (model, index, id, writable, messages); break; case Type_RecordState: searchRecordStateCell (model, index, id, writable, messages); break; case Type_None: break; } } } void CSMTools::Search::setPadding (int before, int after) { mPaddingBefore = before; mPaddingAfter = after; } void CSMTools::Search::replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, const CSMWorld::UniversalId& id, const std::string& messageHint, const std::string& replaceText) const { std::istringstream stream (messageHint.c_str()); char hint, ignore; int columnId, pos, length; if (stream >> hint >> ignore >> columnId >> pos >> length) { int column = model->findColumnIndex (static_cast (columnId)); QModelIndex index = model->getModelIndex (id.getId(), column); std::string text = model->data (index).toString().toUtf8().constData(); std::string before = text.substr (0, pos); std::string after = text.substr (pos+length); std::string newText = before + replaceText + after; document.getUndoStack().push ( new CSMWorld::ModifyCommand (*model, index, QString::fromUtf8 (newText.c_str()))); } } bool CSMTools::Search::verify (CSMDoc::Document& document, CSMWorld::IdTableBase *model, const CSMWorld::UniversalId& id, const std::string& messageHint) const { CSMDoc::Messages messages (CSMDoc::Message::Severity_Info); int row = model->getModelIndex (id.getId(), model->findColumnIndex (CSMWorld::Columns::ColumnId_Id)).row(); searchRow (model, row, messages); for (CSMDoc::Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) if (iter->mHint==messageHint) return true; return false; } openmw-openmw-0.48.0/apps/opencs/model/tools/search.hpp000066400000000000000000000056621445372753700231150ustar00rootroot00000000000000#ifndef CSM_TOOLS_SEARCH_H #define CSM_TOOLS_SEARCH_H #include #include #include #include class QModelIndex; namespace CSMDoc { class Messages; class Document; } namespace CSMWorld { class IdTableBase; class UniversalId; } namespace CSMTools { class Search { public: enum Type { Type_Text = 0, Type_TextRegEx = 1, Type_Id = 2, Type_IdRegEx = 3, Type_RecordState = 4, Type_None }; private: Type mType; std::string mText; QRegExp mRegExp; int mValue; bool mCase; std::set mColumns; int mIdColumn; int mTypeColumn; int mPaddingBefore; int mPaddingAfter; void searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; void searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; void searchRecordStateCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; QString formatDescription (const QString& description, int pos, int length) const; QString flatten (const QString& text) const; public: Search(); Search (Type type, bool caseSensitive, const std::string& value); Search (Type type, bool caseSensitive, const QRegExp& value); Search (Type type, bool caseSensitive, int value); // Configure search for the specified model. void configure (const CSMWorld::IdTableBase *model); // Search row in \a model and store results in \a messages. // // \attention *this needs to be configured for \a model. void searchRow (const CSMWorld::IdTableBase *model, int row, CSMDoc::Messages& messages) const; void setPadding (int before, int after); // Configuring *this for the model is not necessary when calling this function. void replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, const CSMWorld::UniversalId& id, const std::string& messageHint, const std::string& replaceText) const; // Check if model still matches search results. bool verify (CSMDoc::Document& document, CSMWorld::IdTableBase *model, const CSMWorld::UniversalId& id, const std::string& messageHint) const; }; } Q_DECLARE_METATYPE (CSMTools::Search) #endif openmw-openmw-0.48.0/apps/opencs/model/tools/searchoperation.cpp000066400000000000000000000022141445372753700250170ustar00rootroot00000000000000#include "searchoperation.hpp" #include "../doc/state.hpp" #include "../doc/document.hpp" #include "../world/data.hpp" #include "../world/idtablebase.hpp" #include "searchstage.hpp" CSMTools::SearchOperation::SearchOperation (CSMDoc::Document& document) : CSMDoc::Operation (CSMDoc::State_Searching, false) { std::vector types = CSMWorld::UniversalId::listTypes ( CSMWorld::UniversalId::Class_RecordList | CSMWorld::UniversalId::Class_ResourceList ); for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) appendStage (new SearchStage (&dynamic_cast ( *document.getData().getTableModel (*iter)))); setDefaultSeverity (CSMDoc::Message::Severity_Info); } void CSMTools::SearchOperation::configure (const Search& search) { mSearch = search; } void CSMTools::SearchOperation::appendStage (SearchStage *stage) { CSMDoc::Operation::appendStage (stage); stage->setOperation (this); } const CSMTools::Search& CSMTools::SearchOperation::getSearch() const { return mSearch; } openmw-openmw-0.48.0/apps/opencs/model/tools/searchoperation.hpp000066400000000000000000000014701445372753700250270ustar00rootroot00000000000000#ifndef CSM_TOOLS_SEARCHOPERATION_H #define CSM_TOOLS_SEARCHOPERATION_H #include "../doc/operation.hpp" #include "search.hpp" namespace CSMDoc { class Document; } namespace CSMTools { class SearchStage; class SearchOperation : public CSMDoc::Operation { Search mSearch; public: SearchOperation (CSMDoc::Document& document); /// \attention Do not call this function while a search is running. void configure (const Search& search); void appendStage (SearchStage *stage); ///< The ownership of \a stage is transferred to *this. /// /// \attention Do no call this function while this Operation is running. const Search& getSearch() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/searchstage.cpp000066400000000000000000000011621445372753700241230ustar00rootroot00000000000000#include "searchstage.hpp" #include "../world/idtablebase.hpp" #include "searchoperation.hpp" CSMTools::SearchStage::SearchStage (const CSMWorld::IdTableBase *model) : mModel (model), mOperation (nullptr) {} int CSMTools::SearchStage::setup() { if (mOperation) mSearch = mOperation->getSearch(); mSearch.configure (mModel); return mModel->rowCount(); } void CSMTools::SearchStage::perform (int stage, CSMDoc::Messages& messages) { mSearch.searchRow (mModel, stage, messages); } void CSMTools::SearchStage::setOperation (const SearchOperation *operation) { mOperation = operation; } openmw-openmw-0.48.0/apps/opencs/model/tools/searchstage.hpp000066400000000000000000000014311445372753700241270ustar00rootroot00000000000000#ifndef CSM_TOOLS_SEARCHSTAGE_H #define CSM_TOOLS_SEARCHSTAGE_H #include "../doc/stage.hpp" #include "search.hpp" namespace CSMWorld { class IdTableBase; } namespace CSMTools { class SearchOperation; class SearchStage : public CSMDoc::Stage { const CSMWorld::IdTableBase *mModel; Search mSearch; const SearchOperation *mOperation; public: SearchStage (const CSMWorld::IdTableBase *model); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages. void setOperation (const SearchOperation *operation); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/skillcheck.cpp000066400000000000000000000022721445372753700237510ustar00rootroot00000000000000#include "skillcheck.hpp" #include "../prefs/state.hpp" #include "../world/universalid.hpp" CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection& skills) : mSkills (skills) { mIgnoreBaseRecords = false; } int CSMTools::SkillCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mSkills.getSize(); } void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSkills.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Skill& skill = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Skill, skill.mId); if (skill.mDescription.empty()) messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); for (int i=0; i<4; ++i) if (skill.mData.mUseValue[i]<0) { messages.add(id, "Use value #" + std::to_string(i) + " is negative", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/skillcheck.hpp000066400000000000000000000014151445372753700237540ustar00rootroot00000000000000#ifndef CSM_TOOLS_SKILLCHECK_H #define CSM_TOOLS_SKILLCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that skill records are internally consistent class SkillCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSkills; bool mIgnoreBaseRecords; public: SkillCheckStage (const CSMWorld::IdCollection& skills); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/soundcheck.cpp000066400000000000000000000027131445372753700237630ustar00rootroot00000000000000#include "soundcheck.hpp" #include "../prefs/state.hpp" #include "../world/universalid.hpp" CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection &sounds, const CSMWorld::Resources &soundfiles) : mSounds (sounds), mSoundFiles (soundfiles) { mIgnoreBaseRecords = false; } int CSMTools::SoundCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mSounds.getSize(); } void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSounds.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Sound& sound = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) { messages.add(id, "Minimum range is larger than maximum range", "", CSMDoc::Message::Severity_Warning); } if (sound.mSound.empty()) { messages.add(id, "Sound file is missing", "", CSMDoc::Message::Severity_Error); } else if (mSoundFiles.searchId(sound.mSound) == -1) { messages.add(id, "Sound file '" + sound.mSound + "' does not exist", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/soundcheck.hpp000066400000000000000000000016471445372753700237750ustar00rootroot00000000000000#ifndef CSM_TOOLS_SOUNDCHECK_H #define CSM_TOOLS_SOUNDCHECK_H #include #include "../world/resources.hpp" #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that sound records are internally consistent class SoundCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSounds; const CSMWorld::Resources &mSoundFiles; bool mIgnoreBaseRecords; public: SoundCheckStage (const CSMWorld::IdCollection& sounds, const CSMWorld::Resources &soundfiles); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/soundgencheck.cpp000066400000000000000000000040341445372753700244530ustar00rootroot00000000000000#include "soundgencheck.hpp" #include "../prefs/state.hpp" #include "../world/refiddata.hpp" #include "../world/universalid.hpp" CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, const CSMWorld::IdCollection &sounds, const CSMWorld::RefIdCollection &objects) : mSoundGens(soundGens), mSounds(sounds), mObjects(objects) { mIgnoreBaseRecords = false; } int CSMTools::SoundGenCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mSoundGens.getSize(); } void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages) { const CSMWorld::Record &record = mSoundGens.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::SoundGenerator& soundGen = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId); if (!soundGen.mCreature.empty()) { CSMWorld::RefIdData::LocalIndex creatureIndex = mObjects.getDataSet().searchId(soundGen.mCreature); if (creatureIndex.first == -1) { messages.add(id, "Creature '" + soundGen.mCreature + "' doesn't exist", "", CSMDoc::Message::Severity_Error); } else if (creatureIndex.second != CSMWorld::UniversalId::Type_Creature) { messages.add(id, "'" + soundGen.mCreature + "' is not a creature", "", CSMDoc::Message::Severity_Error); } } if (soundGen.mSound.empty()) { messages.add(id, "Sound is missing", "", CSMDoc::Message::Severity_Error); } else if (mSounds.searchId(soundGen.mSound) == -1) { messages.add(id, "Sound '" + soundGen.mSound + "' doesn't exist", "", CSMDoc::Message::Severity_Error); } } openmw-openmw-0.48.0/apps/opencs/model/tools/soundgencheck.hpp000066400000000000000000000020261445372753700244570ustar00rootroot00000000000000#ifndef CSM_TOOLS_SOUNDGENCHECK_HPP #define CSM_TOOLS_SOUNDGENCHECK_HPP #include "../world/data.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that sound gen records are internally consistent class SoundGenCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection &mSoundGens; const CSMWorld::IdCollection &mSounds; const CSMWorld::RefIdCollection &mObjects; bool mIgnoreBaseRecords; public: SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, const CSMWorld::IdCollection &sounds, const CSMWorld::RefIdCollection &objects); int setup() override; ///< \return number of steps void perform(int stage, CSMDoc::Messages &messages) override; ///< Messages resulting from this stage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/spellcheck.cpp000066400000000000000000000023361445372753700237530ustar00rootroot00000000000000#include "spellcheck.hpp" #include #include "../prefs/state.hpp" CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection& spells) : mSpells (spells) { mIgnoreBaseRecords = false; } int CSMTools::SpellCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mSpells.getSize(); } void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSpells.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Spell& spell = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId); // test for empty name if (spell.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // test for invalid cost values if (spell.mData.mCost<0) messages.add(id, "Spell cost is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view } openmw-openmw-0.48.0/apps/opencs/model/tools/spellcheck.hpp000066400000000000000000000014151445372753700237550ustar00rootroot00000000000000#ifndef CSM_TOOLS_SPELLCHECK_H #define CSM_TOOLS_SPELLCHECK_H #include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that spell records are internally consistent class SpellCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSpells; bool mIgnoreBaseRecords; public: SpellCheckStage (const CSMWorld::IdCollection& spells); int setup() override; ///< \return number of steps void perform (int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/startscriptcheck.cpp000066400000000000000000000023061445372753700252130ustar00rootroot00000000000000#include "startscriptcheck.hpp" #include "../prefs/state.hpp" #include CSMTools::StartScriptCheckStage::StartScriptCheckStage ( const CSMWorld::IdCollection& startScripts, const CSMWorld::IdCollection& scripts) : mStartScripts (startScripts), mScripts (scripts) { mIgnoreBaseRecords = false; } void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mStartScripts.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; std::string scriptId = record.get().mId; CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_StartScript, scriptId); if (mScripts.searchId (Misc::StringUtils::lowerCase (scriptId))==-1) messages.add(id, "Start script " + scriptId + " does not exist", "", CSMDoc::Message::Severity_Error); } int CSMTools::StartScriptCheckStage::setup() { mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mStartScripts.getSize(); } openmw-openmw-0.48.0/apps/opencs/model/tools/startscriptcheck.hpp000066400000000000000000000014301445372753700252150ustar00rootroot00000000000000#ifndef CSM_TOOLS_STARTSCRIPTCHECK_H #define CSM_TOOLS_STARTSCRIPTCHECK_H #include #include #include "../doc/stage.hpp" #include "../world/idcollection.hpp" namespace CSMTools { class StartScriptCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mStartScripts; const CSMWorld::IdCollection& mScripts; bool mIgnoreBaseRecords; public: StartScriptCheckStage (const CSMWorld::IdCollection& startScripts, const CSMWorld::IdCollection& scripts); void perform(int stage, CSMDoc::Messages& messages) override; int setup() override; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/tools.cpp000066400000000000000000000250711445372753700227770ustar00rootroot00000000000000#include "tools.hpp" #include "../doc/state.hpp" #include "../doc/operation.hpp" #include "../doc/document.hpp" #include "../world/data.hpp" #include "../world/universalid.hpp" #include "reportmodel.hpp" #include "mandatoryid.hpp" #include "skillcheck.hpp" #include "classcheck.hpp" #include "factioncheck.hpp" #include "racecheck.hpp" #include "soundcheck.hpp" #include "regioncheck.hpp" #include "birthsigncheck.hpp" #include "spellcheck.hpp" #include "referenceablecheck.hpp" #include "scriptcheck.hpp" #include "bodypartcheck.hpp" #include "referencecheck.hpp" #include "startscriptcheck.hpp" #include "searchoperation.hpp" #include "pathgridcheck.hpp" #include "soundgencheck.hpp" #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" #include "gmstcheck.hpp" #include "topicinfocheck.hpp" #include "journalcheck.hpp" #include "enchantmentcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { switch (type) { case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Searching: return &mSearch; case CSMDoc::State_Merging: return &mMerge; } return nullptr; } const CSMDoc::OperationHolder *CSMTools::Tools::get (int type) const { return const_cast (this)->get (type); } CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() { if (!mVerifierOperation) { mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); std::vector mandatoryIds {"Day", "DaysPassed", "GameHour", "Month", "PCRace"}; mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(), CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); mVerifierOperation->appendStage (new SkillCheckStage (mData.getSkills())); mVerifierOperation->appendStage (new ClassCheckStage (mData.getClasses())); mVerifierOperation->appendStage (new FactionCheckStage (mData.getFactions())); mVerifierOperation->appendStage (new RaceCheckStage (mData.getRaces())); mVerifierOperation->appendStage (new SoundCheckStage (mData.getSounds(), mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); mVerifierOperation->appendStage (new RegionCheckStage (mData.getRegions())); mVerifierOperation->appendStage (new BirthsignCheckStage (mData.getBirthsigns(), mData.getResources (CSMWorld::UniversalId::Type_Textures))); mVerifierOperation->appendStage (new SpellCheckStage (mData.getSpells())); mVerifierOperation->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions(), mData.getScripts(), mData.getResources (CSMWorld::UniversalId::Type_Meshes), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getBodyParts())); mVerifierOperation->appendStage (new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); mVerifierOperation->appendStage (new ScriptCheckStage (mDocument)); mVerifierOperation->appendStage (new StartScriptCheckStage (mData.getStartScripts(), mData.getScripts())); mVerifierOperation->appendStage( new BodyPartCheckStage( mData.getBodyParts(), mData.getResources( CSMWorld::UniversalId( CSMWorld::UniversalId::Type_Meshes )), mData.getRaces() )); mVerifierOperation->appendStage (new PathgridCheckStage (mData.getPathgrids())); mVerifierOperation->appendStage (new SoundGenCheckStage (mData.getSoundGens(), mData.getSounds(), mData.getReferenceables())); mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), mData.getSounds(), mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), mData.getCells(), mData.getClasses(), mData.getFactions(), mData.getGmsts(), mData.getGlobals(), mData.getJournals(), mData.getRaces(), mData.getRegions(), mData.getTopics(), mData.getReferenceables().getDataSet(), mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); mVerifierOperation->appendStage (new EnchantmentCheckStage(mData.getEnchantments())); mVerifier.setOperation (mVerifierOperation); } return &mVerifier; } CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) : mDocument (document), mData (document.getData()), mVerifierOperation (nullptr), mSearchOperation (nullptr), mMergeOperation (nullptr), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); mActiveReports.insert (std::make_pair (CSMDoc::State_Loading, 0)); connect (&mSearch, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); // don't need to connect report message, since there are no messages for merge } CSMTools::Tools::~Tools() { if (mVerifierOperation) { mVerifier.abortAndWait(); delete mVerifierOperation; } if (mSearchOperation) { mSearch.abortAndWait(); delete mSearchOperation; } if (mMergeOperation) { mMerge.abortAndWait(); delete mMergeOperation; } for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& reportId) { int reportNumber = reportId.getType()==CSMWorld::UniversalId::Type_VerificationResults ? reportId.getIndex() : mNextReportNumber++; if (mReports.find (reportNumber)==mReports.end()) mReports.insert (std::make_pair (reportNumber, new ReportModel)); mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, reportNumber); } CSMWorld::UniversalId CSMTools::Tools::newSearch() { mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel (true, false))); return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Search, mNextReportNumber-1); } void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Search& search) { mActiveReports[CSMDoc::State_Searching] = searchId.getIndex(); if (!mSearchOperation) { mSearchOperation = new SearchOperation (mDocument); mSearch.setOperation (mSearchOperation); } mSearchOperation->configure (search); mSearch.start(); } void CSMTools::Tools::runMerge (std::unique_ptr target) { // not setting an active report, because merge does not produce messages if (!mMergeOperation) { mMergeOperation = new MergeOperation (mDocument, mEncoding); mMerge.setOperation (mMergeOperation); connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), this, SIGNAL (mergeDone (CSMDoc::Document*))); } target->flagAsDirty(); mMergeOperation->setTarget (std::move(target)); mMerge.start(); } void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) operation->abort(); } int CSMTools::Tools::getRunningOperations() const { static const int sOperations[] = { CSMDoc::State_Verifying, CSMDoc::State_Searching, CSMDoc::State_Merging, -1 }; int result = 0; for (int i=0; sOperations[i]!=-1; ++i) if (const CSMDoc::OperationHolder *operation = get (sOperations[i])) if (operation->isRunning()) result |= sOperations[i]; return result; } CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) { if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults && id.getType()!=CSMWorld::UniversalId::Type_LoadErrorLog && id.getType()!=CSMWorld::UniversalId::Type_Search) throw std::logic_error ("invalid request for report model: " + id.toString()); return mReports.at (id.getIndex()); } void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) { std::map::iterator iter = mActiveReports.find (type); if (iter!=mActiveReports.end()) mReports[iter->second]->add (message); } openmw-openmw-0.48.0/apps/opencs/model/tools/tools.hpp000066400000000000000000000057231445372753700230060ustar00rootroot00000000000000#ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H #include #include #include #include #include #include "../doc/operationholder.hpp" namespace CSMWorld { class Data; class UniversalId; } namespace CSMDoc { class Operation; class Document; } namespace CSMTools { class ReportModel; class Search; class SearchOperation; class MergeOperation; class Tools : public QObject { Q_OBJECT CSMDoc::Document& mDocument; CSMWorld::Data& mData; CSMDoc::Operation *mVerifierOperation; CSMDoc::OperationHolder mVerifier; SearchOperation *mSearchOperation; CSMDoc::OperationHolder mSearch; MergeOperation *mMergeOperation; CSMDoc::OperationHolder mMerge; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number ToUTF8::FromType mEncoding; // not implemented Tools (const Tools&); Tools& operator= (const Tools&); CSMDoc::OperationHolder *getVerifier(); CSMDoc::OperationHolder *get (int type); ///< Returns a 0-pointer, if operation hasn't been used yet. const CSMDoc::OperationHolder *get (int type) const; ///< Returns a 0-pointer, if operation hasn't been used yet. public: Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); virtual ~Tools(); /// \param reportId If a valid VerificationResults ID, run verifier for the /// specified report instead of creating a new one. /// /// \return ID of the report for this verification run CSMWorld::UniversalId runVerifier (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); /// Return ID of the report for this search. CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); void runMerge (std::unique_ptr target); void abortOperation (int type); ///< \attention The operation is not aborted immediately. int getRunningOperations() const; ReportModel *getReport (const CSMWorld::UniversalId& id); ///< The ownership of the returned report is not transferred. private slots: void verifierMessage (const CSMDoc::Message& message, int type); signals: void progress (int current, int max, int type); void done (int type, bool failed); /// \attention When this signal is emitted, *this hands over the ownership of the /// document. This signal must be handled to avoid a leak. void mergeDone (CSMDoc::Document *document); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/tools/topicinfocheck.cpp000066400000000000000000000327251445372753700246330ustar00rootroot00000000000000#include "topicinfocheck.hpp" #include #include "../prefs/state.hpp" #include "../world/infoselectwrapper.hpp" CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( const CSMWorld::InfoCollection& topicInfos, const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& gmsts, const CSMWorld::IdCollection& globals, const CSMWorld::IdCollection& journals, const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& regions, const CSMWorld::IdCollection &topics, const CSMWorld::RefIdData& referencables, const CSMWorld::Resources& soundFiles) : mTopicInfos(topicInfos), mCells(cells), mClasses(classes), mFactions(factions), mGameSettings(gmsts), mGlobals(globals), mJournals(journals), mRaces(races), mRegions(regions), mTopics(topics), mReferencables(referencables), mSoundFiles(soundFiles) { mIgnoreBaseRecords = false; } int CSMTools::TopicInfoCheckStage::setup() { // Generate list of cell names for reference checking mCellNames.clear(); for (int i = 0; i < mCells.getSize(); ++i) { const CSMWorld::Record& cellRecord = mCells.getRecord(i); if (cellRecord.isDeleted()) continue; mCellNames.insert(cellRecord.get().mName); } // Cell names can also include region names for (int i = 0; i < mRegions.getSize(); ++i) { const CSMWorld::Record& regionRecord = mRegions.getRecord(i); if (regionRecord.isDeleted()) continue; mCellNames.insert(regionRecord.get().mName); } // Default cell name int index = mGameSettings.searchId("sDefaultCellname"); if (index != -1) { const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) { mCellNames.insert(gmstRecord.get().mValue.getString()); } } mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); return mTopicInfos.getSize(); } void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || infoRecord.isDeleted()) return; const CSMWorld::Info& topicInfo = infoRecord.get(); // There should always be a topic that matches int topicIndex = mTopics.searchId(topicInfo.mTopicId); const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); if (topicRecord.isDeleted()) return; const ESM::Dialogue& topic = topicRecord.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); // Check fields if (!topicInfo.mActor.empty()) { verifyActor(topicInfo.mActor, id, messages); } if (!topicInfo.mClass.empty()) { verifyId(topicInfo.mClass, mClasses, id, messages); } if (!topicInfo.mCell.empty()) { verifyCell(topicInfo.mCell, id, messages); } if (!topicInfo.mFaction.empty() && !topicInfo.mFactionLess) { if (verifyId(topicInfo.mFaction, mFactions, id, messages)) { verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); } } if (!topicInfo.mPcFaction.empty()) { if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) { verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); } } if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) { messages.add(id, "Gender is invalid", "", CSMDoc::Message::Severity_Error); } if (!topicInfo.mRace.empty()) { verifyId(topicInfo.mRace, mRaces, id, messages); } if (!topicInfo.mSound.empty()) { verifySound(topicInfo.mSound, id, messages); } if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) { messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); } // Check info conditions for (std::vector::const_iterator it = topicInfo.mSelects.begin(); it != topicInfo.mSelects.end(); ++it) { verifySelectStruct((*it), id, messages); } } // Verification functions bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); if (index.first == -1) { messages.add(id, "Actor '" + actor + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { messages.add(id, "Deleted actor '" + actor + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) { CSMWorld::UniversalId tempId(index.second, actor); std::ostringstream stream; stream << "Object '" << actor << "' has invalid type " << tempId.getTypeName() << " (an actor must be an NPC or a creature)"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } return true; } bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (mCellNames.find(cell) == mCellNames.end()) { messages.add(id, "Cell '" + cell + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } return true; } bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (rank < -1) { std::ostringstream stream; stream << "Faction rank is set to " << rank << ", but it should be set to -1 if there are no rank requirements"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); return false; } int index = mFactions.searchId(factionName); const ESM::Faction &faction = mFactions.getRecord(index).get(); int limit = 0; for (; limit < 10; ++limit) { if (faction.mRanks[limit].empty()) break; } if (rank >= limit) { std::ostringstream stream; stream << "Faction rank is set to " << rank << " which is more than the maximum of " << limit - 1 << " for the '" << factionName << "' faction"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } return true; } bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); if (index.first == -1) { messages.add(id, ("Item '" + item + "' does not exist"), "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { messages.add(id, ("Deleted item '" + item + "' is being referenced"), "", CSMDoc::Message::Severity_Error); return false; } else { switch (index.second) { case CSMWorld::UniversalId::Type_Potion: case CSMWorld::UniversalId::Type_Apparatus: case CSMWorld::UniversalId::Type_Armor: case CSMWorld::UniversalId::Type_Book: case CSMWorld::UniversalId::Type_Clothing: case CSMWorld::UniversalId::Type_Ingredient: case CSMWorld::UniversalId::Type_Light: case CSMWorld::UniversalId::Type_Lockpick: case CSMWorld::UniversalId::Type_Miscellaneous: case CSMWorld::UniversalId::Type_Probe: case CSMWorld::UniversalId::Type_Repair: case CSMWorld::UniversalId::Type_Weapon: case CSMWorld::UniversalId::Type_ItemLevelledList: break; default: { CSMWorld::UniversalId tempId(index.second, item); std::ostringstream stream; stream << "Object '" << item << "' has invalid type " << tempId.getTypeName() << " (an item can be a potion, an armor piece, a book and so on)"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } } } return true; } bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::ConstInfoSelectWrapper infoCondition(select); if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) { messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); return false; } else if (!infoCondition.variantTypeIsValid()) { std::ostringstream stream; stream << "Value of condition '" << infoCondition.toString() << "' has invalid "; switch (select.mValue.getType()) { case ESM::VT_None: stream << "None"; break; case ESM::VT_Short: stream << "Short"; break; case ESM::VT_Int: stream << "Int"; break; case ESM::VT_Long: stream << "Long"; break; case ESM::VT_Float: stream << "Float"; break; case ESM::VT_String: stream << "String"; break; default: stream << "unknown"; break; } stream << " type"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } else if (infoCondition.conditionIsAlwaysTrue()) { messages.add(id, "Condition '" + infoCondition.toString() + "' is always true", "", CSMDoc::Message::Severity_Warning); return false; } else if (infoCondition.conditionIsNeverTrue()) { messages.add(id, "Condition '" + infoCondition.toString() + "' is never true", "", CSMDoc::Message::Severity_Warning); return false; } // Id checks if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && !verifyItem(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && !verifyActor(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && !verifyActor(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && !verifyCell(infoCondition.getVariableName(), id, messages)) { return false; } return true; } bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (mSoundFiles.searchId(sound) == -1) { messages.add(id, "Sound file '" + sound + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } return true; } template bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { int index = collection.searchId(name); if (index == -1) { messages.add(id, std::string(T::getRecordType()) + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { messages.add(id, "Deleted " + std::string(T::getRecordType()) + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } return true; } openmw-openmw-0.48.0/apps/opencs/model/tools/topicinfocheck.hpp000066400000000000000000000067051445372753700246370ustar00rootroot00000000000000#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP #define CSM_TOOLS_TOPICINFOCHECK_HPP #include #include #include #include #include #include #include #include #include "../world/cell.hpp" #include "../world/idcollection.hpp" #include "../world/infocollection.hpp" #include "../world/refiddata.hpp" #include "../world/resources.hpp" #include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: check topics class TopicInfoCheckStage : public CSMDoc::Stage { public: TopicInfoCheckStage( const CSMWorld::InfoCollection& topicInfos, const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& gmsts, const CSMWorld::IdCollection& globals, const CSMWorld::IdCollection& journals, const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& regions, const CSMWorld::IdCollection& topics, const CSMWorld::RefIdData& referencables, const CSMWorld::Resources& soundFiles); int setup() override; ///< \return number of steps void perform(int step, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages private: const CSMWorld::InfoCollection& mTopicInfos; const CSMWorld::IdCollection& mCells; const CSMWorld::IdCollection& mClasses; const CSMWorld::IdCollection& mFactions; const CSMWorld::IdCollection& mGameSettings; const CSMWorld::IdCollection& mGlobals; const CSMWorld::IdCollection& mJournals; const CSMWorld::IdCollection& mRaces; const CSMWorld::IdCollection& mRegions; const CSMWorld::IdCollection& mTopics; const CSMWorld::RefIdData& mReferencables; const CSMWorld::Resources& mSoundFiles; std::set mCellNames; bool mIgnoreBaseRecords; // These return false when not successful and write an error bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); template bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/000077500000000000000000000000001445372753700211155ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/model/world/actoradapter.cpp000066400000000000000000000526211445372753700243000ustar00rootroot00000000000000#include "actoradapter.hpp" #include #include #include #include #include #include #include "data.hpp" #include #include namespace CSMWorld { const std::string& ActorAdapter::RaceData::getId() const { return mId; } bool ActorAdapter::RaceData::isBeast() const { return mIsBeast; } ActorAdapter::RaceData::RaceData() { mIsBeast = false; } bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const { switch (type) { case ESM::PRT_Skirt: case ESM::PRT_Shield: case ESM::PRT_RPauldron: case ESM::PRT_LPauldron: case ESM::PRT_Weapon: return false; default: return true; } } const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const { return mFemaleParts[ESM::getMeshPart(index)]; } const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const { return mMaleParts[ESM::getMeshPart(index)]; } bool ActorAdapter::RaceData::hasDependency(const std::string& id) const { return mDependencies.find(id) != mDependencies.end(); } void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId) { mFemaleParts[index] = partId; addOtherDependency(partId); } void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId) { mMaleParts[index] = partId; addOtherDependency(partId); } void ActorAdapter::RaceData::addOtherDependency(const std::string& id) { if (!id.empty()) mDependencies.emplace(id); } void ActorAdapter::RaceData::reset_data(const std::string& id, bool isBeast) { mId = id; mIsBeast = isBeast; for (auto& str : mFemaleParts) str.clear(); for (auto& str : mMaleParts) str.clear(); mDependencies.clear(); // Mark self as a dependency addOtherDependency(id); } ActorAdapter::ActorData::ActorData() { mCreature = false; mFemale = false; } const std::string& ActorAdapter::ActorData::getId() const { return mId; } bool ActorAdapter::ActorData::isCreature() const { return mCreature; } bool ActorAdapter::ActorData::isFemale() const { return mFemale; } std::string ActorAdapter::ActorData::getSkeleton() const { if (mCreature || !mSkeletonOverride.empty()) return "meshes\\" + mSkeletonOverride; bool firstPerson = false; bool beast = mRaceData ? mRaceData->isBeast() : false; bool werewolf = false; return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } std::string_view ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) { if (mRaceData && mRaceData->handlesPart(index)) { if (mFemale) { // Note: we should use male parts for females as fallback const std::string& femalePart = mRaceData->getFemalePart(index); if (!femalePart.empty()) return femalePart; } return mRaceData->getMalePart(index); } return {}; } return it->second.first; } bool ActorAdapter::ActorData::hasDependency(const std::string& id) const { return mDependencies.find(id) != mDependencies.end(); } void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId, int priority) { auto it = mParts.find(index); if (it != mParts.end()) { if (it->second.second >= priority) return; } mParts[index] = std::make_pair(partId, priority); addOtherDependency(partId); } void ActorAdapter::ActorData::addOtherDependency(const std::string& id) { if (!id.empty()) mDependencies.emplace(id); } void ActorAdapter::ActorData::reset_data(const std::string& id, const std::string& skeleton, bool isCreature, bool isFemale, RaceDataPtr raceData) { mId = id; mCreature = isCreature; mFemale = isFemale; mSkeletonOverride = skeleton; mRaceData = raceData; mParts.clear(); mDependencies.clear(); // Mark self and race as a dependency addOtherDependency(id); if (raceData) addOtherDependency(raceData->getId()); } ActorAdapter::ActorAdapter(Data& data) : mReferenceables(data.getReferenceables()) , mRaces(data.getRaces()) , mBodyParts(data.getBodyParts()) { // Setup qt slots and signals QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable); connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int))); connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&))); connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int))); QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race); connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&))); connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart); connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int))); connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int))); } ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) { // Return cached actor data if it exists ActorDataPtr data = mCachedActors.get(id); if (data) { return data; } // Create the actor data data = std::make_shared(); setupActor(id, data); mCachedActors.insert(id, data); return data; } void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top level are pertinent. Others are caught by dataChanged handler. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { std::string refId = mReferenceables.getId(row); markDirtyDependency(refId); } } // Update affected updateDirty(); } void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row(); // A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start; // Handle each record for (int row = start; row <= end; ++row) { std::string refId = mReferenceables.getId(row); markDirtyDependency(refId); } // Update affected updateDirty(); } void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only rows at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { std::string refId = mReferenceables.getId(row); markDirtyDependency(refId); } } } void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleReferenceablesAboutToBeRemoved updateDirty(); } void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { std::string raceId = mReferenceables.getId(row); markDirtyDependency(raceId); } } // Update affected updateDirty(); } void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row(); // A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start; for (int row = start; row <= end; ++row) { std::string raceId = mRaces.getId(row); markDirtyDependency(raceId); } // Update affected updateDirty(); } void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only changes at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { std::string raceId = mRaces.getId(row); markDirtyDependency(raceId); } } } void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleRacesAboutToBeRemoved updateDirty(); } void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { markDirtyDependency(record.get().mRace); } std::string partId = mBodyParts.getId(row); markDirtyDependency(partId); } } // Update affected updateDirty(); } void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row(); // A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start; for (int row = start; row <= end; ++row) { // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { markDirtyDependency(record.get().mRace); } // Update entries with a tracked dependency std::string partId = mBodyParts.getId(row); markDirtyDependency(partId); } // Update affected updateDirty(); } void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only changes at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { std::string partId = mBodyParts.getId(row); markDirtyDependency(partId); } } } void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleBodyPartsAboutToBeRemoved updateDirty(); } QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const { while (index.parent().isValid()) index = index.parent(); return index; } bool ActorAdapter::is1stPersonPart(const std::string& name) const { return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; } ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id) { // Return cached race data if it exists RaceDataPtr data = mCachedRaces.get(id); if (data) return data; // Create the race data data = std::make_shared(); setupRace(id, data); mCachedRaces.insert(id, data); return data; } void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data) { int index = mReferenceables.searchId(id); if (index == -1) { // Record does not exist data->reset_data(id); emit actorChanged(id); return; } auto& record = mReferenceables.getRecord(index); if (record.isDeleted()) { // Record is deleted and therefore not accessible data->reset_data(id); emit actorChanged(id); return; } const int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); int type = mReferenceables.getData(index, TypeColumn).toInt(); if (type == UniversalId::Type_Creature) { // Valid creature record setupCreature(id, data); emit actorChanged(id); } else if (type == UniversalId::Type_Npc) { // Valid npc record setupNpc(id, data); emit actorChanged(id); } else { // Wrong record type data->reset_data(id); emit actorChanged(id); } } void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) { int index = mRaces.searchId(id); if (index == -1) { // Record does not exist data->reset_data(id); return; } auto& raceRecord = mRaces.getRecord(index); if (raceRecord.isDeleted()) { // Record is deleted, so not accessible data->reset_data(id); return; } auto& race = raceRecord.get(); data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) { std::string partId = mBodyParts.getId(i); auto& partRecord = mBodyParts.getRecord(i); if (partRecord.isDeleted()) { // Record is deleted, so not accessible. continue; } auto& part = partRecord.get(); if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) { auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female; if (female) data->setFemalePart(type, part.mId); else data->setMalePart(type, part.mId); } } } void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data) { // Common setup, record is known to exist and is not deleted int index = mReferenceables.searchId(id); auto& npc = dynamic_cast&>(mReferenceables.getRecord(index)).get(); RaceDataPtr raceData = getRaceData(npc.mRace); data->reset_data(id, "", false, !npc.isMale(), raceData); // Add head and hair data->setPart(ESM::PRT_Head, npc.mHead, 0); data->setPart(ESM::PRT_Hair, npc.mHair, 0); // Add inventory items for (auto& item : npc.mInventory.mList) { if (item.mCount <= 0) continue; std::string itemId = item.mItem; addNpcItem(itemId, data); } } void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) { int index = mReferenceables.searchId(itemId); if (index == -1) { // Item does not exist yet data->addOtherDependency(itemId); return; } auto& record = mReferenceables.getRecord(index); if (record.isDeleted()) { // Item cannot be accessed yet data->addOtherDependency(itemId); return; } // Convenience function to add a parts list to actor data auto addParts = [&](const std::vector& list, int priority) { for (auto& part : list) { std::string partId; auto partType = (ESM::PartReferenceType) part.mPart; if (data->isFemale()) partId = part.mFemale; if (partId.empty()) partId = part.mMale; data->setPart(partType, partId, priority); // An another vanilla quirk: hide hairs if an item replaces Head part if (partType == ESM::PRT_Head) data->setPart(ESM::PRT_Hair, "", priority); } }; int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); int type = mReferenceables.getData(index, TypeColumn).toInt(); if (type == UniversalId::Type_Armor) { auto& armor = dynamic_cast&>(record).get(); addParts(armor.mParts.mParts, 1); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); } else if (type == UniversalId::Type_Clothing) { auto& clothing = dynamic_cast&>(record).get(); std::vector parts; if (clothing.mData.mType == ESM::Clothing::Robe) { parts = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; } else if (clothing.mData.mType == ESM::Clothing::Skirt) { parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg}; } std::vector reservedList; for (const auto& p : parts) { ESM::PartReference pr; pr.mPart = p; reservedList.emplace_back(pr); } int priority = parts.size(); addParts(clothing.mParts.mParts, priority); addParts(reservedList, priority); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); } } void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) { // Record is known to exist and is not deleted int index = mReferenceables.searchId(id); auto& creature = dynamic_cast&>(mReferenceables.getRecord(index)).get(); data->reset_data(id, creature.mModel, true); } void ActorAdapter::markDirtyDependency(const std::string& dep) { for (auto raceIt : mCachedRaces) { if (raceIt->hasDependency(dep)) mDirtyRaces.emplace(raceIt->getId()); } for (auto actorIt : mCachedActors) { if (actorIt->hasDependency(dep)) mDirtyActors.emplace(actorIt->getId()); } } void ActorAdapter::updateDirty() { // Handle races before actors, since actors are dependent on race for (auto& race : mDirtyRaces) { RaceDataPtr data = mCachedRaces.get(race); if (data) { setupRace(race, data); // Race was changed. Need to mark actor dependencies as dirty. // Cannot use markDirtyDependency because that would invalidate // the current iterator. for (auto actorIt : mCachedActors) { if (actorIt->hasDependency(race)) mDirtyActors.emplace(actorIt->getId()); } } } mDirtyRaces.clear(); for (auto& actor : mDirtyActors) { ActorDataPtr data = mCachedActors.get(actor); if (data) { setupActor(actor, data); } } mDirtyActors.clear(); } } openmw-openmw-0.48.0/apps/opencs/model/world/actoradapter.hpp000066400000000000000000000150421445372753700243010ustar00rootroot00000000000000#ifndef CSM_WOLRD_ACTORADAPTER_H #define CSM_WOLRD_ACTORADAPTER_H #include #include #include #include #include #include #include #include #include #include #include "refidcollection.hpp" #include "idcollection.hpp" namespace ESM { struct Race; } namespace CSMWorld { class Data; /// Adapts multiple collections to provide the data needed to render /// an npc or creature. class ActorAdapter : public QObject { Q_OBJECT public: /// A list indexed by ESM::PartReferenceType using ActorPartList = std::map>; /// A list indexed by ESM::BodyPart::MeshPart using RacePartList = std::array; /// Tracks unique strings using StringSet = std::unordered_set; /// Contains base race data shared between actors class RaceData { public: RaceData(); /// Retrieves the id of the race represented const std::string& getId() const; /// Checks if it's a beast race bool isBeast() const; /// Checks if a part could exist for the given type bool handlesPart(ESM::PartReferenceType type) const; /// Retrieves the associated body part const std::string& getFemalePart(ESM::PartReferenceType index) const; /// Retrieves the associated body part const std::string& getMalePart(ESM::PartReferenceType index) const; /// Checks if the race has a data dependency bool hasDependency(const std::string& id) const; /// Sets the associated part if it's empty and marks a dependency void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); /// Sets the associated part if it's empty and marks a dependency void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); /// Marks an additional dependency void addOtherDependency(const std::string& id); /// Clears parts and dependencies void reset_data(const std::string& raceId, bool isBeast=false); private: bool handles(ESM::PartReferenceType type) const; std::string mId; bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; StringSet mDependencies; }; using RaceDataPtr = std::shared_ptr; /// Contains all the data needed to render an actor. Tracks dependencies /// so that pertinent data changes can be checked. class ActorData { public: ActorData(); /// Retrieves the id of the actor represented const std::string& getId() const; /// Checks if the actor is a creature bool isCreature() const; /// Checks if the actor is female bool isFemale() const; /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part std::string_view getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency bool hasDependency(const std::string& id) const; /// Sets the actor part used and marks a dependency void setPart(ESM::PartReferenceType partIndex, const std::string& partId, int priority); /// Marks an additional dependency for the actor void addOtherDependency(const std::string& id); /// Clears race, parts, and dependencies void reset_data(const std::string& actorId, const std::string& skeleton="", bool isCreature=false, bool female=true, RaceDataPtr raceData=nullptr); private: std::string mId; bool mCreature; bool mFemale; std::string mSkeletonOverride; RaceDataPtr mRaceData; ActorPartList mParts; StringSet mDependencies; }; using ActorDataPtr = std::shared_ptr; ActorAdapter(Data& data); /// Obtains the shared data for a given actor ActorDataPtr getActorData(const std::string& refId); signals: void actorChanged(const std::string& refId); public slots: void handleReferenceablesInserted(const QModelIndex&, int, int); void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int); void handleReferenceablesRemoved(const QModelIndex&, int, int); void handleRacesInserted(const QModelIndex&, int, int); void handleRaceChanged(const QModelIndex&, const QModelIndex&); void handleRacesAboutToBeRemoved(const QModelIndex&, int, int); void handleRacesRemoved(const QModelIndex&, int, int); void handleBodyPartsInserted(const QModelIndex&, int, int); void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int); void handleBodyPartsRemoved(const QModelIndex&, int, int); private: ActorAdapter(const ActorAdapter&) = delete; ActorAdapter& operator=(const ActorAdapter&) = delete; QModelIndex getHighestIndex(QModelIndex) const; bool is1stPersonPart(const std::string& id) const; RaceDataPtr getRaceData(const std::string& raceId); void setupActor(const std::string& id, ActorDataPtr data); void setupRace(const std::string& id, RaceDataPtr data); void setupNpc(const std::string& id, ActorDataPtr data); void addNpcItem(const std::string& itemId, ActorDataPtr data); void setupCreature(const std::string& id, ActorDataPtr data); void markDirtyDependency(const std::string& dependency); void updateDirty(); RefIdCollection& mReferenceables; IdCollection& mRaces; IdCollection& mBodyParts; Misc::WeakCache mCachedActors; // Key: referenceable id Misc::WeakCache mCachedRaces; // Key: race id StringSet mDirtyActors; // Actors that need updating StringSet mDirtyRaces; // Races that need updating }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/cell.cpp000066400000000000000000000005041445372753700225370ustar00rootroot00000000000000#include "cell.hpp" #include void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) { ESM::Cell::load (esm, isDeleted, false); mId = mName; if (isExterior()) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; mId = stream.str(); } } openmw-openmw-0.48.0/apps/opencs/model/world/cell.hpp000066400000000000000000000007541445372753700225530ustar00rootroot00000000000000#ifndef CSM_WOLRD_CELL_H #define CSM_WOLRD_CELL_H #include #include #include namespace CSMWorld { /// \brief Wrapper for Cell record /// /// \attention The mData.mX and mData.mY fields of the ESM::Cell struct are not used. /// Exterior cell coordinates are encoded in the cell ID. struct Cell : public ESM::Cell { std::string mId; void load (ESM::ESMReader &esm, bool &isDeleted); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/cellcoordinates.cpp000066400000000000000000000114751445372753700250030ustar00rootroot00000000000000#include "cellcoordinates.hpp" #include #include #include #include #include CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} CSMWorld::CellCoordinates::CellCoordinates (const std::pair& coordinates) : mX (coordinates.first), mY (coordinates.second) {} int CSMWorld::CellCoordinates::getX() const { return mX; } int CSMWorld::CellCoordinates::getY() const { return mY; } CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const { return CellCoordinates (mX + x, mY + y); } std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const { // we ignore the worldspace for now, since there is only one (will change in 1.1) return generateId(mX, mY); } std::string CSMWorld::CellCoordinates::generateId (int x, int y) { std::string cellId = "#" + std::to_string(x) + " " + std::to_string(y); return cellId; } bool CSMWorld::CellCoordinates::isExteriorCell (const std::string& id) { return (!id.empty() && id[0]=='#'); } std::pair CSMWorld::CellCoordinates::fromId ( const std::string& id) { // no worldspace for now, needs to be changed for 1.1 if (isExteriorCell(id)) { int x, y; char ignore; std::istringstream stream (id); if (stream >> ignore >> x >> y) return std::make_pair (CellCoordinates (x, y), true); } return std::make_pair (CellCoordinates(), false); } std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, float y) { return std::make_pair (std::floor (x / Constants::CellSizeInUnits), std::floor (y / Constants::CellSizeInUnits)); } std::pair CSMWorld::CellCoordinates::toTextureCoords(const osg::Vec3d& worldPos) { const auto xd = static_cast(worldPos.x() * ESM::Land::LAND_TEXTURE_SIZE / ESM::Land::REAL_SIZE - 0.25f); const auto yd = static_cast(worldPos.y() * ESM::Land::LAND_TEXTURE_SIZE / ESM::Land::REAL_SIZE + 0.25f); const auto x = static_cast(std::floor(xd)); const auto y = static_cast(std::floor(yd)); return std::make_pair(x, y); } std::pair CSMWorld::CellCoordinates::toVertexCoords(const osg::Vec3d& worldPos) { const auto xd = static_cast(worldPos.x() * (ESM::Land::LAND_SIZE - 1) / ESM::Land::REAL_SIZE + 0.5f); const auto yd = static_cast(worldPos.y() * (ESM::Land::LAND_SIZE - 1) / ESM::Land::REAL_SIZE + 0.5f); const auto x = static_cast(std::floor(xd)); const auto y = static_cast(std::floor(yd)); return std::make_pair(x, y); } float CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(int textureGlobal) { return ESM::Land::REAL_SIZE * (static_cast(textureGlobal) + 0.25f) / ESM::Land::LAND_TEXTURE_SIZE; } float CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(int textureGlobal) { return ESM::Land::REAL_SIZE * (static_cast(textureGlobal) - 0.25f) / ESM::Land::LAND_TEXTURE_SIZE; } float CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(int vertexGlobal) { return ESM::Land::REAL_SIZE * static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1); } int CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(int vertexGlobal) { return static_cast(vertexGlobal - std::floor(static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1)) * (ESM::Land::LAND_SIZE - 1)); } std::string CSMWorld::CellCoordinates::textureGlobalToCellId(const std::pair& textureGlobal) { int x = std::floor(static_cast(textureGlobal.first) / ESM::Land::LAND_TEXTURE_SIZE); int y = std::floor(static_cast(textureGlobal.second) / ESM::Land::LAND_TEXTURE_SIZE); return generateId(x, y); } std::string CSMWorld::CellCoordinates::vertexGlobalToCellId(const std::pair& vertexGlobal) { int x = std::floor(static_cast(vertexGlobal.first) / (ESM::Land::LAND_SIZE - 1)); int y = std::floor(static_cast(vertexGlobal.second) / (ESM::Land::LAND_SIZE - 1)); return generateId(x, y); } bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) { return left.getX()==right.getX() && left.getY()==right.getY(); } bool CSMWorld::operator!= (const CellCoordinates& left, const CellCoordinates& right) { return !(left==right); } bool CSMWorld::operator< (const CellCoordinates& left, const CellCoordinates& right) { if (left.getX()right.getX()) return false; return left.getY() #include #include #include #include namespace CSMWorld { class CellCoordinates { int mX; int mY; public: CellCoordinates(); CellCoordinates (int x, int y); CellCoordinates (const std::pair& coordinates); int getX() const; int getY() const; CellCoordinates move (int x, int y) const; ///< Return a copy of *this, moved by the given offset. ///Generate cell id string from x and y coordinates static std::string generateId (int x, int y); std::string getId (const std::string& worldspace) const; ///< Return the ID for the cell at these coordinates. static bool isExteriorCell (const std::string& id); /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), /// second: is cell paged? /// /// \note The worldspace part of \a id is ignored static std::pair fromId (const std::string& id); /// \return cell coordinates such that given world coordinates are in it. static std::pair coordinatesToCellIndex (float x, float y); ///Converts worldspace coordinates to global texture selection, taking in account the texture offset. static std::pair toTextureCoords(const osg::Vec3d& worldPos); ///Converts worldspace coordinates to global vertex selection. static std::pair toVertexCoords(const osg::Vec3d& worldPos); ///Converts global texture coordinate X to worldspace coordinate, offset by 0.25f. static float textureGlobalXToWorldCoords(int textureGlobal); ///Converts global texture coordinate Y to worldspace coordinate, offset by 0.25f. static float textureGlobalYToWorldCoords(int textureGlobal); ///Converts global vertex coordinate to worldspace coordinate static float vertexGlobalToWorldCoords(int vertexGlobal); ///Converts global vertex coordinate to local cell's heightmap coordinates static int vertexGlobalToInCellCoords(int vertexGlobal); ///Converts global texture coordinates to cell id static std::string textureGlobalToCellId(const std::pair& textureGlobal); ///Converts global vertex coordinates to cell id static std::string vertexGlobalToCellId(const std::pair& vertexGlobal); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); bool operator!= (const CellCoordinates& left, const CellCoordinates& right); bool operator< (const CellCoordinates& left, const CellCoordinates& right); std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes); } Q_DECLARE_METATYPE (CSMWorld::CellCoordinates) #endif openmw-openmw-0.48.0/apps/opencs/model/world/cellselection.cpp000066400000000000000000000033341445372753700244510ustar00rootroot00000000000000#include "cellselection.hpp" #include #include #include CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const { return mCells.begin(); } CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const { return mCells.end(); } bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates) { return mCells.insert (coordinates).second; } void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates) { mCells.erase (coordinates); } bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const { return mCells.find (coordinates)!=end(); } int CSMWorld::CellSelection::getSize() const { return mCells.size(); } CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const { if (mCells.empty()) throw std::logic_error ("call of getCentre on empty cell selection"); double x = 0; double y = 0; for (Iterator iter = begin(); iter!=end(); ++iter) { x += iter->getX(); y += iter->getY(); } x /= mCells.size(); y /= mCells.size(); Iterator closest = begin(); double distance = std::numeric_limits::max(); for (Iterator iter (begin()); iter!=end(); ++iter) { double deltaX = x - iter->getX(); double deltaY = y - iter->getY(); double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY); if (deltamove (x, y)); mCells.swap (moved); } openmw-openmw-0.48.0/apps/opencs/model/world/cellselection.hpp000066400000000000000000000030531445372753700244540ustar00rootroot00000000000000#ifndef CSM_WOLRD_CELLSELECTION_H #define CSM_WOLRD_CELLSELECTION_H #include #include #include "cellcoordinates.hpp" namespace CSMWorld { /// \brief Selection of cells in a paged worldspace /// /// \note The CellSelection does not specify the worldspace it applies to. class CellSelection { public: typedef std::set Container; typedef Container::const_iterator Iterator; private: Container mCells; public: Iterator begin() const; Iterator end() const; bool add (const CellCoordinates& coordinates); ///< Ignored if the cell specified by \a coordinates is already part of the selection. /// /// \return Was a cell added to the collection? void remove (const CellCoordinates& coordinates); ///< ignored if the cell specified by \a coordinates is not part of the selection. bool has (const CellCoordinates& coordinates) const; ///< \return Is the cell specified by \a coordinates part of the selection? int getSize() const; ///< Return number of cells. CellCoordinates getCentre() const; ///< Return the selected cell that is closest to the geometric centre of the selection. /// /// \attention This function must not be called on selections that are empty. void move (int x, int y); }; } Q_DECLARE_METATYPE (CSMWorld::CellSelection) #endif openmw-openmw-0.48.0/apps/opencs/model/world/collection.hpp000066400000000000000000000472311445372753700237700ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLLECTION_H #define CSM_WOLRD_COLLECTION_H #include #include #include #include #include #include #include #include #include #include #include #include "columnbase.hpp" #include "collectionbase.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" namespace CSMWorld { /// \brief Access to ID field in records template struct IdAccessor { void setId(ESXRecordT& record, const std::string& id) const; const std::string getId (const ESXRecordT& record) const; }; template void IdAccessor::setId(ESXRecordT& record, const std::string& id) const { record.mId = id; } template const std::string IdAccessor::getId (const ESXRecordT& record) const { return record.mId; } template<> inline void IdAccessor::setId (Land& record, const std::string& id) const { int x=0, y=0; Land::parseUniqueRecordId(id, x, y); record.mX = x; record.mY = y; } template<> inline void IdAccessor::setId (LandTexture& record, const std::string& id) const { int plugin = 0; int index = 0; LandTexture::parseUniqueRecordId(id, plugin, index); record.mPluginIndex = plugin; record.mIndex = index; } template<> inline const std::string IdAccessor::getId (const Land& record) const { return Land::createUniqueRecordId(record.mX, record.mY); } template<> inline const std::string IdAccessor::getId (const LandTexture& record) const { return LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex); } /// \brief Single-type record collection template > class Collection : public CollectionBase { public: typedef ESXRecordT ESXRecord; private: std::vector > > mRecords; std::map mIndex; std::vector *> mColumns; // not implemented Collection (const Collection&); Collection& operator= (const Collection&); protected: const std::vector > >& getRecords() const; bool reorderRowsImp (int baseIndex, const std::vector& newOrder); ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). /// /// \return Success? int cloneRecordImp (const std::string& origin, const std::string& dest, UniversalId::Type type); ///< Returns the index of the clone. int touchRecordImp (const std::string& id); ///< Returns the index of the record on success, -1 on failure. public: Collection(); virtual ~Collection(); void add (const ESXRecordT& record); ///< Add a new record (modified) int getSize() const override; std::string getId (int index) const override; int getIndex (const std::string& id) const override; int getColumns() const override; QVariant getData (int index, int column) const override; void setData (int index, int column, const QVariant& data) override; const ColumnBase& getColumn (int column) const override; virtual void merge(); ///< Merge modified into base. virtual void purge(); ///< Remove records that are flagged as erased. void removeRows (int index, int count) override; void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None) override; ///< \param type Will be ignored, unless the collection supports multiple record types void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) override; bool touchRecord(const std::string& id) override; ///< Change the state of a record from base to modified, if it is not already. /// \return True if the record was changed. int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) void replace (int index, std::unique_ptr record) override; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; ///< If the record type does not match, an exception is thrown. ///< \param type Will be ignored, unless the collection supports multiple record types const Record& getRecord (const std::string& id) const override; const Record& getRecord (int index) const override; int getAppendIndex (const std::string& id, UniversalId::Type type = UniversalId::Type_None) const override; ///< \param type Will be ignored, unless the collection supports multiple record types std::vector getIds (bool listDeleted = true) const override; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list virtual void insertRecord (std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None); ///< Insert record before index. /// /// If the record type does not match, an exception is thrown. /// /// If the index is invalid either generally (by being out of range) or for the particular /// record, an exception is thrown. bool reorderRows (int baseIndex, const std::vector& newOrder) override; ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). /// /// \return Success? void addColumn (Column *column); void setRecord (int index, std::unique_ptr > record); ///< \attention This function must not change the ID. NestableColumn *getNestableColumn (int column) const; }; template const std::vector > >& Collection::getRecords() const { return mRecords; } template bool Collection::reorderRowsImp (int baseIndex, const std::vector& newOrder) { if (!newOrder.empty()) { int size = static_cast (newOrder.size()); // check that all indices are present std::vector test (newOrder); std::sort (test.begin(), test.end()); if (*test.begin()!=0 || *--test.end()!=size-1) return false; // reorder records std::vector > > buffer (size); for (int i=0; isetModified (buffer[newOrder[i]]->get()); } std::move (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); // adjust index for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter) if (iter->second>=baseIndex && iter->secondsecond = newOrder.at (iter->second-baseIndex)+baseIndex; } return true; } template int Collection::cloneRecordImp(const std::string& origin, const std::string& destination, UniversalId::Type type) { auto copy = std::make_unique>(); copy->mModified = getRecord(origin).get(); copy->mState = RecordBase::State_ModifiedOnly; IdAccessorT().setId(copy->get(), destination); if (type == UniversalId::Type_Reference) { CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©->mModified; ptr->mRefNum.mIndex = 0; } int index = getAppendIndex(destination, type); insertRecord(std::move(copy), getAppendIndex(destination, type)); return index; } template int Collection::touchRecordImp(const std::string& id) { int index = getIndex(id); Record& record = *mRecords.at(index); if (record.isDeleted()) { throw std::runtime_error("attempt to touch deleted record"); } if (!record.isModified()) { record.setModified(record.get()); return index; } return -1; } template void Collection::cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) { cloneRecordImp(origin, destination, type); } template<> inline void Collection >::cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) { int index = cloneRecordImp(origin, destination, type); mRecords.at(index)->get().setPlugin(0); } template bool Collection::touchRecord(const std::string& id) { return touchRecordImp(id) != -1; } template<> inline bool Collection >::touchRecord(const std::string& id) { int index = touchRecordImp(id); if (index >= 0) { mRecords.at(index)->get().setPlugin(0); return true; } return false; } template Collection::Collection() {} template Collection::~Collection() { for (typename std::vector *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) delete *iter; } template void Collection::add (const ESXRecordT& record) { std::string id = Misc::StringUtils::lowerCase (IdAccessorT().getId (record)); std::map::iterator iter = mIndex.find (id); if (iter==mIndex.end()) { auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; record2->mModified = record; insertRecord (std::move(record2), getAppendIndex (id)); } else { mRecords[iter->second]->setModified (record); } } template int Collection::getSize() const { return mRecords.size(); } template std::string Collection::getId (int index) const { return IdAccessorT().getId (mRecords.at (index)->get()); } template int Collection::getIndex (const std::string& id) const { int index = searchId (id); if (index==-1) throw std::runtime_error ("invalid ID: " + id); return index; } template int Collection::getColumns() const { return mColumns.size(); } template QVariant Collection::getData (int index, int column) const { return mColumns.at (column)->get (*mRecords.at (index)); } template void Collection::setData (int index, int column, const QVariant& data) { return mColumns.at (column)->set (*mRecords.at (index), data); } template const ColumnBase& Collection::getColumn (int column) const { return *mColumns.at (column); } template NestableColumn *Collection::getNestableColumn (int column) const { if (column < 0 || column >= static_cast(mColumns.size())) throw std::runtime_error("column index out of range"); return mColumns.at (column); } template void Collection::addColumn (Column *column) { mColumns.push_back (column); } template void Collection::merge() { for (typename std::vector > >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) (*iter)->merge(); purge(); } template void Collection::purge() { int i = 0; while (i (mRecords.size())) { if (mRecords[i]->isErased()) removeRows (i, 1); else ++i; } } template void Collection::removeRows (int index, int count) { mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count); typename std::map::iterator iter = mIndex.begin(); while (iter!=mIndex.end()) { if (iter->second>=index) { if (iter->second>=index+count) { iter->second -= count; ++iter; } else { mIndex.erase (iter++); } } else ++iter; } } template void Collection::appendBlankRecord (const std::string& id, UniversalId::Type type) { ESXRecordT record; IdAccessorT().setId(record, id); record.blank(); auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; record2->mModified = record; insertRecord (std::move(record2), getAppendIndex (id, type), type); } template int Collection::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase(id); std::map::const_iterator iter = mIndex.find (id2); if (iter==mIndex.end()) return -1; return iter->second; } template void Collection::replace (int index, std::unique_ptr record) { std::unique_ptr > tmp(static_cast*>(record.release())); mRecords.at (index) = std::move(tmp); } template void Collection::appendRecord (std::unique_ptr record, UniversalId::Type type) { int index = getAppendIndex(IdAccessorT().getId(static_cast*>(record.get())->get()), type); insertRecord (std::move(record), index, type); } template int Collection::getAppendIndex (const std::string& id, UniversalId::Type type) const { return static_cast (mRecords.size()); } template std::vector Collection::getIds (bool listDeleted) const { std::vector ids; for (typename std::map::const_iterator iter = mIndex.begin(); iter!=mIndex.end(); ++iter) { if (listDeleted || !mRecords[iter->second]->isDeleted()) ids.push_back (IdAccessorT().getId (mRecords[iter->second]->get())); } return ids; } template const Record& Collection::getRecord (const std::string& id) const { int index = getIndex (id); return *mRecords.at (index); } template const Record& Collection::getRecord (int index) const { return *mRecords.at (index); } template void Collection::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(mRecords.size()); if (index < 0 || index > size) throw std::runtime_error ("index out of range"); std::unique_ptr > record2(static_cast*>(record.release())); std::string lowerId = Misc::StringUtils::lowerCase(IdAccessorT().getId(record2->get())); if (index == size) mRecords.push_back (std::move(record2)); else mRecords.insert (mRecords.begin()+index, std::move(record2)); if (index < size-1) { for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter) { if (iter->second >= index) ++(iter->second); } } mIndex.insert (std::make_pair (lowerId, index)); } template void Collection::setRecord (int index, std::unique_ptr > record) { if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index)->get())) != Misc::StringUtils::lowerCase (IdAccessorT().getId (record->get()))) throw std::runtime_error ("attempt to change the ID of a record"); mRecords.at (index) = std::move(record); } template bool Collection::reorderRows (int baseIndex, const std::vector& newOrder) { return false; } } #endif openmw-openmw-0.48.0/apps/opencs/model/world/collectionbase.cpp000066400000000000000000000014131445372753700246060ustar00rootroot00000000000000#include "collectionbase.hpp" #include #include "columnbase.hpp" CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {} int CSMWorld::CollectionBase::getInsertIndex (const std::string& id, UniversalId::Type type, RecordBase *record) const { return getAppendIndex(id, type); } int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const { int columns = getColumns(); for (int i=0; i #include #include #include #include "universalid.hpp" #include "columns.hpp" class QVariant; namespace CSMWorld { struct ColumnBase; struct RecordBase; /// \brief Base class for record collections /// /// \attention Modifying records through the interface does not update connected views. /// Such modifications should be done through the table model interface instead unless no views /// are connected to the model or special precautions have been taken to send update signals /// manually. class CollectionBase { // not implemented CollectionBase (const CollectionBase&); CollectionBase& operator= (const CollectionBase&); public: CollectionBase(); virtual ~CollectionBase(); virtual int getSize() const = 0; virtual std::string getId (int index) const = 0; virtual int getIndex (const std::string& id) const = 0; virtual int getColumns() const = 0; virtual const ColumnBase& getColumn (int column) const = 0; virtual QVariant getData (int index, int column) const = 0; virtual void setData (int index, int column, const QVariant& data) = 0; // Not in use. Temporarily removed so that the implementation of RefIdCollection can continue without // these functions for now. // virtual void merge() = 0; ///< Merge modified into base. // virtual void purge() = 0; ///< Remove records that are flagged as erased. virtual void removeRows (int index, int count) = 0; virtual void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None) = 0; ///< \param type Will be ignored, unless the collection supports multiple record types virtual int searchId(std::string_view id) const = 0; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) virtual void replace (int index, std::unique_ptr record) = 0; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. ///< \param type Will be ignored, unless the collection supports multiple record types virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. virtual void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) = 0; virtual bool touchRecord(const std::string& id) = 0; virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (int index) const = 0; virtual int getAppendIndex (const std::string& id, UniversalId::Type type = UniversalId::Type_None) const = 0; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted = true) const = 0; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list virtual bool reorderRows (int baseIndex, const std::vector& newOrder) = 0; ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). /// /// \return Success? virtual int getInsertIndex (const std::string& id, UniversalId::Type type = UniversalId::Type_None, RecordBase *record = nullptr) const; ///< Works like getAppendIndex unless an overloaded method uses the record pointer /// to get additional info about the record that results in an alternative index. int searchColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, -1 is returned. int findColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, an exception is /// thrown. }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/columnbase.cpp000066400000000000000000000072651445372753700237630ustar00rootroot00000000000000#include "columnbase.hpp" #include "columns.hpp" CSMWorld::ColumnBase::ColumnBase (int columnId, Display displayType, int flags) : mColumnId (columnId), mFlags (flags), mDisplayType (displayType) {} CSMWorld::ColumnBase::~ColumnBase() {} bool CSMWorld::ColumnBase::isUserEditable() const { return isEditable(); } std::string CSMWorld::ColumnBase::getTitle() const { return Columns::getName (static_cast (mColumnId)); } int CSMWorld::ColumnBase::getId() const { return mColumnId; } bool CSMWorld::ColumnBase::isId (Display display) { static const Display ids[] = { Display_Skill, Display_Class, Display_Faction, Display_Rank, Display_Race, Display_Sound, Display_Region, Display_Birthsign, Display_Spell, Display_Cell, Display_Referenceable, Display_Activator, Display_Potion, Display_Apparatus, Display_Armor, Display_Book, Display_Clothing, Display_Container, Display_Creature, Display_Door, Display_Ingredient, Display_CreatureLevelledList, Display_ItemLevelledList, Display_Light, Display_Lockpick, Display_Miscellaneous, Display_Npc, Display_Probe, Display_Repair, Display_Static, Display_Weapon, Display_Reference, Display_Filter, Display_Topic, Display_Journal, Display_TopicInfo, Display_JournalInfo, Display_Scene, Display_GlobalVariable, Display_BodyPart, Display_Enchantment, Display_Script, Display_Mesh, Display_Icon, Display_Music, Display_SoundRes, Display_Texture, Display_Video, Display_Id, Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, Display_EffectSkill, Display_EffectAttribute, Display_IngredEffectId, Display_None }; for (int i=0; ids[i]!=Display_None; ++i) if (ids[i]==display) return true; return false; } bool CSMWorld::ColumnBase::isText (Display display) { return display==Display_String || display==Display_LongString || display==Display_String32 || display==Display_String64 || display==Display_LongString256; } bool CSMWorld::ColumnBase::isScript (Display display) { return display==Display_ScriptFile || display==Display_ScriptLines; } void CSMWorld::NestableColumn::addColumn(CSMWorld::NestableColumn *column) { mNestedColumns.push_back(column); } const CSMWorld::ColumnBase& CSMWorld::NestableColumn::nestedColumn(int subColumn) const { if (mNestedColumns.empty()) throw std::logic_error("Tried to access nested column of the non-nest column"); return *mNestedColumns.at(subColumn); } CSMWorld::NestableColumn::NestableColumn(int columnId, CSMWorld::ColumnBase::Display displayType, int flag) : CSMWorld::ColumnBase(columnId, displayType, flag) {} CSMWorld::NestableColumn::~NestableColumn() { for (unsigned int i = 0; i < mNestedColumns.size(); ++i) { delete mNestedColumns[i]; } } bool CSMWorld::NestableColumn::hasChildren() const { return !mNestedColumns.empty(); } CSMWorld::NestedChildColumn::NestedChildColumn (int id, CSMWorld::ColumnBase::Display display, int flags, bool isEditable) : NestableColumn (id, display, flags) , mIsEditable(isEditable) {} bool CSMWorld::NestedChildColumn::isEditable () const { return mIsEditable; } openmw-openmw-0.48.0/apps/opencs/model/world/columnbase.hpp000066400000000000000000000163331445372753700237640ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLUMNBASE_H #define CSM_WOLRD_COLUMNBASE_H #include #include #include #include #include "record.hpp" namespace CSMWorld { struct ColumnBase { enum TableEditModes { TableEdit_None, // no editing TableEdit_Full, // edit cells and add/remove rows TableEdit_FixedRows // edit cells only }; enum Roles { Role_Flags = Qt::UserRole, Role_Display = Qt::UserRole+1, Role_ColumnId = Qt::UserRole+2 }; enum Flags { Flag_Table = 1, // column should be displayed in table view Flag_Dialogue = 2, // column should be displayed in dialogue view Flag_Dialogue_List = 4, // column should be diaplyed in dialogue view Flag_Dialogue_Refresh = 8 // refresh dialogue view if this column is modified }; enum Display { Display_None, //Do not use Display_String, Display_LongString, //CONCRETE TYPES STARTS HERE (for drag and drop) Display_Skill, Display_Class, Display_Faction, Display_Rank, Display_Race, Display_Sound, Display_Region, Display_Birthsign, Display_Spell, Display_Cell, Display_Referenceable, Display_Activator, Display_Potion, Display_Apparatus, Display_Armor, Display_Book, Display_Clothing, Display_Container, Display_Creature, Display_Door, Display_Ingredient, Display_CreatureLevelledList, Display_ItemLevelledList, Display_Light, Display_Lockpick, Display_Miscellaneous, Display_Npc, Display_Probe, Display_Repair, Display_Static, Display_Weapon, Display_Reference, Display_Filter, Display_Topic, Display_Journal, Display_TopicInfo, Display_JournalInfo, Display_Scene, Display_GlobalVariable, Display_BodyPart, Display_Enchantment, //CONCRETE TYPES ENDS HERE Display_SignedInteger8, Display_SignedInteger16, Display_UnsignedInteger8, Display_UnsignedInteger16, Display_Integer, Display_Float, Display_Double, Display_Var, Display_GmstVarType, Display_GlobalVarType, Display_Specialisation, Display_Attribute, Display_Boolean, Display_SpellType, Display_Script, Display_ApparatusType, Display_ArmorType, Display_ClothingType, Display_CreatureType, Display_WeaponType, Display_RecordState, Display_RefRecordType, Display_DialogueType, Display_QuestStatusType, Display_EnchantmentType, Display_BodyPartType, Display_MeshType, Display_Gender, Display_Mesh, Display_Icon, Display_Music, Display_SoundRes, Display_Texture, Display_Video, Display_Colour, Display_ScriptFile, Display_ScriptLines, // console context Display_SoundGeneratorType, Display_School, Display_Id, Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, Display_String32, Display_String64, Display_LongString256, Display_BookType, Display_BloodType, Display_EmitterType, Display_EffectSkill, // must display at least one, unlike Display_Skill Display_EffectAttribute, // must display at least one, unlike Display_Attribute Display_IngredEffectId, // display none allowed, unlike Display_EffectId Display_GenderNpc, // must display at least one, unlike Display_Gender //top level columns that nest other columns Display_NestedHeader }; int mColumnId; int mFlags; Display mDisplayType; ColumnBase (int columnId, Display displayType, int flag); virtual ~ColumnBase(); virtual bool isEditable() const = 0; virtual bool isUserEditable() const; ///< Can this column be edited directly by the user? virtual std::string getTitle() const; virtual int getId() const; static bool isId (Display display); static bool isText (Display display); static bool isScript (Display display); }; class NestableColumn : public ColumnBase { std::vector mNestedColumns; public: NestableColumn(int columnId, Display displayType, int flag); ~NestableColumn(); void addColumn(CSMWorld::NestableColumn *column); const ColumnBase& nestedColumn(int subColumn) const; bool hasChildren() const; }; template struct Column : public NestableColumn { Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) : NestableColumn (columnId, displayType, flags) {} virtual QVariant get (const Record& record) const = 0; virtual void set (Record& record, const QVariant& data) { throw std::logic_error ("Column " + getTitle() + " is not editable"); } }; template struct NestedParentColumn : public Column { NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) {} void set (Record& record, const QVariant& data) override { // There is nothing to do here. // This prevents exceptions from parent's implementation } QVariant get (const Record& record) const override { // by default editable; also see IdTree::hasChildren() if (mFixedRows) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); else return QVariant::fromValue(ColumnBase::TableEdit_Full); } bool isEditable() const override { return true; } private: bool mFixedRows; }; struct NestedChildColumn : public NestableColumn { NestedChildColumn (int id, Display display, int flags = ColumnBase::Flag_Dialogue, bool isEditable = true); bool isEditable() const override; private: bool mIsEditable; }; } Q_DECLARE_METATYPE(CSMWorld::ColumnBase::TableEditModes) #endif openmw-openmw-0.48.0/apps/opencs/model/world/columnimp.cpp000066400000000000000000000167571445372753700236440ustar00rootroot00000000000000#include "columnimp.hpp" #include namespace CSMWorld { /* LandTextureNicknameColumn */ LandTextureNicknameColumn::LandTextureNicknameColumn() : Column(Columns::ColumnId_TextureNickname, ColumnBase::Display_String) { } QVariant LandTextureNicknameColumn::get(const Record& record) const { return QString::fromUtf8(record.get().mId.c_str()); } void LandTextureNicknameColumn::set(Record& record, const QVariant& data) { LandTexture copy = record.get(); copy.mId = data.toString().toUtf8().constData(); record.setModified(copy); } bool LandTextureNicknameColumn::isEditable() const { return true; } /* LandTextureIndexColumn */ LandTextureIndexColumn::LandTextureIndexColumn() : Column(Columns::ColumnId_TextureIndex, ColumnBase::Display_Integer) { } QVariant LandTextureIndexColumn::get(const Record& record) const { return record.get().mIndex; } bool LandTextureIndexColumn::isEditable() const { return false; } /* LandPluginIndexColumn */ LandPluginIndexColumn::LandPluginIndexColumn() : Column(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0) { } QVariant LandPluginIndexColumn::get(const Record& record) const { return record.get().getPlugin(); } bool LandPluginIndexColumn::isEditable() const { return false; } /* LandTexturePluginIndexColumn */ LandTexturePluginIndexColumn::LandTexturePluginIndexColumn() : Column(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0) { } QVariant LandTexturePluginIndexColumn::get(const Record& record) const { return record.get().mPluginIndex; } bool LandTexturePluginIndexColumn::isEditable() const { return false; } /* LandNormalsColumn */ LandNormalsColumn::LandNormalsColumn() : Column(Columns::ColumnId_LandNormalsIndex, ColumnBase::Display_String, 0) { } QVariant LandNormalsColumn::get(const Record& record) const { const int Size = Land::LAND_NUM_VERTS * 3; const Land& land = record.get(); DataType values(Size, 0); if (land.isDataLoaded(Land::DATA_VNML)) { for (int i = 0; i < Size; ++i) values[i] = land.getLandData()->mNormals[i]; } QVariant variant; variant.setValue(values); return variant; } void LandNormalsColumn::set(Record& record, const QVariant& data) { DataType values = data.value(); if (values.size() != Land::LAND_NUM_VERTS * 3) throw std::runtime_error("invalid land normals data"); Land copy = record.get(); copy.add(Land::DATA_VNML); for (int i = 0; i < values.size(); ++i) { copy.getLandData()->mNormals[i] = values[i]; } record.setModified(copy); } bool LandNormalsColumn::isEditable() const { return true; } /* LandHeightsColumn */ LandHeightsColumn::LandHeightsColumn() : Column(Columns::ColumnId_LandHeightsIndex, ColumnBase::Display_String, 0) { } QVariant LandHeightsColumn::get(const Record& record) const { const int Size = Land::LAND_NUM_VERTS; const Land& land = record.get(); DataType values(Size, 0); if (land.isDataLoaded(Land::DATA_VHGT)) { for (int i = 0; i < Size; ++i) values[i] = land.getLandData()->mHeights[i]; } QVariant variant; variant.setValue(values); return variant; } void LandHeightsColumn::set(Record& record, const QVariant& data) { DataType values = data.value(); if (values.size() != Land::LAND_NUM_VERTS) throw std::runtime_error("invalid land heights data"); Land copy = record.get(); copy.add(Land::DATA_VHGT); for (int i = 0; i < values.size(); ++i) { copy.getLandData()->mHeights[i] = values[i]; } record.setModified(copy); } bool LandHeightsColumn::isEditable() const { return true; } /* LandColoursColumn */ LandColoursColumn::LandColoursColumn() : Column(Columns::ColumnId_LandColoursIndex, ColumnBase::Display_String, 0) { } QVariant LandColoursColumn::get(const Record& record) const { const int Size = Land::LAND_NUM_VERTS * 3; const Land& land = record.get(); DataType values(Size, 0); if (land.isDataLoaded(Land::DATA_VCLR)) { for (int i = 0; i < Size; ++i) values[i] = land.getLandData()->mColours[i]; } QVariant variant; variant.setValue(values); return variant; } void LandColoursColumn::set(Record& record, const QVariant& data) { DataType values = data.value(); if (values.size() != Land::LAND_NUM_VERTS * 3) throw std::runtime_error("invalid land colours data"); Land copy = record.get(); copy.add(Land::DATA_VCLR); for (int i = 0; i < values.size(); ++i) { copy.getLandData()->mColours[i] = values[i]; } record.setModified(copy); } bool LandColoursColumn::isEditable() const { return true; } /* LandTexturesColumn */ LandTexturesColumn::LandTexturesColumn() : Column(Columns::ColumnId_LandTexturesIndex, ColumnBase::Display_String, 0) { } QVariant LandTexturesColumn::get(const Record& record) const { const int Size = Land::LAND_NUM_TEXTURES; const Land& land = record.get(); DataType values(Size, 0); if (land.isDataLoaded(Land::DATA_VTEX)) { for (int i = 0; i < Size; ++i) values[i] = land.getLandData()->mTextures[i]; } QVariant variant; variant.setValue(values); return variant; } void LandTexturesColumn::set(Record& record, const QVariant& data) { DataType values = data.value(); if (values.size() != Land::LAND_NUM_TEXTURES) throw std::runtime_error("invalid land textures data"); Land copy = record.get(); copy.add(Land::DATA_VTEX); for (int i = 0; i < values.size(); ++i) { copy.getLandData()->mTextures[i] = values[i]; } record.setModified(copy); } bool LandTexturesColumn::isEditable() const { return true; } /* BodyPartRaceColumn */ BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) : mMeshType(meshType) {} QVariant BodyPartRaceColumn::get(const Record &record) const { if (mMeshType != nullptr && mMeshType->get(record) == ESM::BodyPart::MT_Skin) { return QString::fromUtf8(record.get().mRace.c_str()); } return QVariant(QVariant::UserType); } void BodyPartRaceColumn::set(Record &record, const QVariant &data) { ESM::BodyPart record2 = record.get(); record2.mRace = data.toString().toUtf8().constData(); record.setModified(record2); } bool BodyPartRaceColumn::isEditable() const { return true; } } openmw-openmw-0.48.0/apps/opencs/model/world/columnimp.hpp000066400000000000000000002145761445372753700236500ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLUMNIMP_H #define CSM_WOLRD_COLUMNIMP_H #include #include #include #include #include #include #include #include #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" #include "land.hpp" #include "landtexture.hpp" namespace CSMWorld { /// \note Shares ID with VarValueColumn. A table can not have both. template struct FloatValueColumn : public Column { FloatValueColumn() : Column (Columns::ColumnId_Value, ColumnBase::Display_Float) {} QVariant get (const Record& record) const override { return record.get().mValue.getFloat(); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mValue.setFloat (data.toFloat()); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct StringIdColumn : public Column { StringIdColumn (bool hidden = false) : Column (Columns::ColumnId_Id, ColumnBase::Display_Id, hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mId.c_str()); } bool isEditable() const override { return false; } }; template<> inline QVariant StringIdColumn::get(const Record& record) const { const Land& land = record.get(); return QString::fromUtf8(Land::createUniqueRecordId(land.mX, land.mY).c_str()); } template<> inline QVariant StringIdColumn::get(const Record& record) const { const LandTexture& ltex = record.get(); return QString::fromUtf8(LandTexture::createUniqueRecordId(ltex.mPluginIndex, ltex.mIndex).c_str()); } template struct RecordStateColumn : public Column { RecordStateColumn() : Column (Columns::ColumnId_Modification, ColumnBase::Display_RecordState) {} QVariant get (const Record& record) const override { if (record.mState==Record::State_Erased) return static_cast (Record::State_Deleted); return static_cast (record.mState); } void set (Record& record, const QVariant& data) override { record.mState = static_cast (data.toInt()); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct FixedRecordTypeColumn : public Column { int mType; FixedRecordTypeColumn (int type) : Column (Columns::ColumnId_RecordType, ColumnBase::Display_Integer, 0), mType (type) {} QVariant get (const Record& record) const override { return mType; } bool isEditable() const override { return false; } }; /// \attention A var type column must be immediately followed by a suitable value column. template struct VarTypeColumn : public Column { VarTypeColumn (ColumnBase::Display display) : Column (Columns::ColumnId_ValueType, display, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) {} QVariant get (const Record& record) const override { return static_cast (record.get().mValue.getType()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mValue.setType (static_cast (data.toInt())); record.setModified (record2); } bool isEditable() const override { return true; } }; /// \note Shares ID with FloatValueColumn. A table can not have both. template struct VarValueColumn : public Column { VarValueColumn() : Column (Columns::ColumnId_Value, ColumnBase::Display_Var, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) {} QVariant get (const Record& record) const override { switch (record.get().mValue.getType()) { case ESM::VT_String: return QString::fromUtf8 (record.get().mValue.getString().c_str()); case ESM::VT_Int: case ESM::VT_Short: case ESM::VT_Long: return record.get().mValue.getInteger(); case ESM::VT_Float: return record.get().mValue.getFloat(); default: return QVariant(); } } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); switch (record2.mValue.getType()) { case ESM::VT_String: record2.mValue.setString (data.toString().toUtf8().constData()); break; case ESM::VT_Int: case ESM::VT_Short: case ESM::VT_Long: record2.mValue.setInteger (data.toInt()); break; case ESM::VT_Float: record2.mValue.setFloat (data.toFloat()); break; default: break; } record.setModified (record2); } bool isEditable() const override { return true; } }; template struct DescriptionColumn : public Column { DescriptionColumn() : Column (Columns::ColumnId_Description, ColumnBase::Display_LongString) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mDescription.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDescription = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SpecialisationColumn : public Column { SpecialisationColumn() : Column (Columns::ColumnId_Specialisation, ColumnBase::Display_Specialisation) {} QVariant get (const Record& record) const override { return record.get().mData.mSpecialization; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mSpecialization = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct UseValueColumn : public Column { int mIndex; UseValueColumn (int index) : Column (Columns::ColumnId_UseValue1 + index, ColumnBase::Display_Float), mIndex (index) {} QVariant get (const Record& record) const override { return record.get().mData.mUseValue[mIndex]; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mUseValue[mIndex] = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct AttributeColumn : public Column { AttributeColumn() : Column (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute) {} QVariant get (const Record& record) const override { return record.get().mData.mAttribute; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAttribute = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct NameColumn : public Column { NameColumn(ColumnBase::Display display = ColumnBase::Display_String) : Column (Columns::ColumnId_Name, display) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mName.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mName = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct AttributesColumn : public Column { int mIndex; AttributesColumn (int index) : Column (Columns::ColumnId_Attribute1 + index, ColumnBase::Display_Attribute), mIndex (index) {} QVariant get (const Record& record) const override { return record.get().mData.mAttribute[mIndex]; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAttribute[mIndex] = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SkillsColumn : public Column { int mIndex; bool mMajor; SkillsColumn (int index, bool typePrefix = false, bool major = false) : Column ((typePrefix ? ( major ? Columns::ColumnId_MajorSkill1 : Columns::ColumnId_MinorSkill1) : Columns::ColumnId_Skill1) + index, ColumnBase::Display_Skill), mIndex (index), mMajor (major) {} QVariant get (const Record& record) const override { int skill = record.get().mData.getSkill (mIndex, mMajor); return QString::fromUtf8 (ESM::Skill::indexToId (skill).c_str()); } void set (Record& record, const QVariant& data) override { std::istringstream stream (data.toString().toUtf8().constData()); int index = -1; char c; stream >> c >> index; if (index!=-1) { ESXRecordT record2 = record.get(); record2.mData.getSkill (mIndex, mMajor) = index; record.setModified (record2); } } bool isEditable() const override { return true; } }; template struct PlayableColumn : public Column { PlayableColumn() : Column (Columns::ColumnId_Playable, ColumnBase::Display_Boolean) {} QVariant get (const Record& record) const override { return record.get().mData.mIsPlayable!=0; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mIsPlayable = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct HiddenColumn : public Column { HiddenColumn() : Column (Columns::ColumnId_Hidden, ColumnBase::Display_Boolean) {} QVariant get (const Record& record) const override { return record.get().mData.mIsHidden!=0; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mIsHidden = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FlagColumn : public Column { int mMask; bool mInverted; FlagColumn (int columnId, int mask, int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, bool inverted = false) : Column (columnId, ColumnBase::Display_Boolean, flags), mMask (mask), mInverted (inverted) {} QVariant get (const Record& record) const override { bool flag = (record.get().mData.mFlags & mMask)!=0; if (mInverted) flag = !flag; return flag; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); int flags = record2.mData.mFlags & ~mMask; if ((data.toInt()!=0)!=mInverted) flags |= mMask; record2.mData.mFlags = flags; record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FlagColumn2 : public Column { int mMask; bool mInverted; FlagColumn2 (int columnId, int mask, bool inverted = false) : Column (columnId, ColumnBase::Display_Boolean), mMask (mask), mInverted (inverted) {} QVariant get (const Record& record) const override { bool flag = (record.get().mFlags & mMask)!=0; if (mInverted) flag = !flag; return flag; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); int flags = record2.mFlags & ~mMask; if ((data.toInt()!=0)!=mInverted) flags |= mMask; record2.mFlags = flags; record.setModified (record2); } bool isEditable() const override { return true; } }; template struct WeightHeightColumn : public Column { bool mMale; bool mWeight; WeightHeightColumn (bool male, bool weight) : Column (male ? (weight ? Columns::ColumnId_MaleWeight : Columns::ColumnId_MaleHeight) : (weight ? Columns::ColumnId_FemaleWeight : Columns::ColumnId_FemaleHeight), ColumnBase::Display_Float), mMale (male), mWeight (weight) {} QVariant get (const Record& record) const override { const ESM::Race::MaleFemaleF& value = mWeight ? record.get().mData.mWeight : record.get().mData.mHeight; return mMale ? value.mMale : value.mFemale; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); ESM::Race::MaleFemaleF& value = mWeight ? record2.mData.mWeight : record2.mData.mHeight; (mMale ? value.mMale : value.mFemale) = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SoundParamColumn : public Column { enum Type { Type_Volume, Type_MinRange, Type_MaxRange }; Type mType; SoundParamColumn (Type type) : Column (type==Type_Volume ? Columns::ColumnId_Volume : (type==Type_MinRange ? Columns::ColumnId_MinRange : Columns::ColumnId_MaxRange), ColumnBase::Display_Integer), mType (type) {} QVariant get (const Record& record) const override { int value = 0; switch (mType) { case Type_Volume: value = record.get().mData.mVolume; break; case Type_MinRange: value = record.get().mData.mMinRange; break; case Type_MaxRange: value = record.get().mData.mMaxRange; break; } return value; } void set (Record& record, const QVariant& data) override { int value = data.toInt(); if (value<0) value = 0; else if (value>255) value = 255; ESXRecordT record2 = record.get(); switch (mType) { case Type_Volume: record2.mData.mVolume = static_cast (value); break; case Type_MinRange: record2.mData.mMinRange = static_cast (value); break; case Type_MaxRange: record2.mData.mMaxRange = static_cast (value); break; } record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SoundFileColumn : public Column { SoundFileColumn() : Column (Columns::ColumnId_SoundFile, ColumnBase::Display_SoundRes) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mSound.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mSound = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct MapColourColumn : public Column { MapColourColumn() : Column (Columns::ColumnId_MapColour, ColumnBase::Display_Colour) {} QVariant get (const Record& record) const override { return record.get().mMapColor; } void set (Record& record, const QVariant& data) override { ESXRecordT copy = record.get(); copy.mMapColor = data.toInt(); record.setModified (copy); } bool isEditable() const override { return true; } }; template struct SleepListColumn : public Column { SleepListColumn() : Column (Columns::ColumnId_SleepEncounter, ColumnBase::Display_CreatureLevelledList) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mSleepList.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mSleepList = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct TextureColumn : public Column { TextureColumn() : Column (Columns::ColumnId_Texture, ColumnBase::Display_Texture) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mTexture.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTexture = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SpellTypeColumn : public Column { SpellTypeColumn() : Column (Columns::ColumnId_SpellType, ColumnBase::Display_SpellType) {} QVariant get (const Record& record) const override { return record.get().mData.mType; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct CostColumn : public Column { CostColumn() : Column (Columns::ColumnId_Cost, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mData.mCost; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mCost = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ScriptColumn : public Column { enum Type { Type_File, // regular script record Type_Lines, // console context Type_Info // dialogue context (not implemented yet) }; ScriptColumn (Type type) : Column (Columns::ColumnId_ScriptText, type==Type_File ? ColumnBase::Display_ScriptFile : ColumnBase::Display_ScriptLines, type==Type_File ? 0 : ColumnBase::Flag_Dialogue) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mScriptText.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mScriptText = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct RegionColumn : public Column { RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_Region) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mRegion.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRegion = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct CellColumn : public Column { bool mBlocked; /// \param blocked Do not allow user-modification CellColumn (bool blocked = false) : Column (Columns::ColumnId_Cell, ColumnBase::Display_Cell), mBlocked (blocked) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mCell.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mCell = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return !mBlocked; } }; template struct OriginalCellColumn : public Column { OriginalCellColumn() : Column (Columns::ColumnId_OriginalCell, ColumnBase::Display_Cell) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mOriginalCell.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mOriginalCell = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct IdColumn : public Column { IdColumn() : Column (Columns::ColumnId_ReferenceableId, ColumnBase::Display_Referenceable) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mRefID.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRefID = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ScaleColumn : public Column { ScaleColumn() : Column (Columns::ColumnId_Scale, ColumnBase::Display_Float) {} QVariant get (const Record& record) const override { return record.get().mScale; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mScale = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct OwnerColumn : public Column { OwnerColumn() : Column (Columns::ColumnId_Owner, ColumnBase::Display_Npc) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mOwner.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mOwner = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SoulColumn : public Column { SoulColumn() : Column (Columns::ColumnId_Soul, ColumnBase::Display_Creature) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mSoul.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mSoul = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FactionColumn : public Column { FactionColumn() : Column (Columns::ColumnId_Faction, ColumnBase::Display_Faction) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mFaction.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mFaction = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FactionIndexColumn : public Column { FactionIndexColumn() : Column (Columns::ColumnId_FactionIndex, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mFactionRank; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mFactionRank = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ChargesColumn : public Column { ChargesColumn() : Column (Columns::ColumnId_Charges, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mChargeInt; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mChargeInt = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct EnchantmentChargesColumn : public Column { EnchantmentChargesColumn() : Column (Columns::ColumnId_Enchantment, ColumnBase::Display_Float) {} QVariant get (const Record& record) const override { return record.get().mEnchantmentCharge; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mEnchantmentCharge = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct GoldValueColumn : public Column { GoldValueColumn() : Column (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mGoldValue; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mGoldValue = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct TeleportColumn : public Column { TeleportColumn() : Column (Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) {} QVariant get (const Record& record) const override { return record.get().mTeleport; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTeleport = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct TeleportCellColumn : public Column { TeleportCellColumn() : Column (Columns::ColumnId_TeleportCell, ColumnBase::Display_Cell) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mDestCell.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDestCell = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return true; } }; template struct LockLevelColumn : public Column { LockLevelColumn() : Column (Columns::ColumnId_LockLevel, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mLockLevel; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mLockLevel = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct KeyColumn : public Column { KeyColumn() : Column (Columns::ColumnId_Key, ColumnBase::Display_Miscellaneous) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mKey.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mKey = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct TrapColumn : public Column { TrapColumn() : Column (Columns::ColumnId_Trap, ColumnBase::Display_Spell) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mTrap.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTrap = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FilterColumn : public Column { FilterColumn() : Column (Columns::ColumnId_Filter, ColumnBase::Display_Filter) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mFilter.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mFilter = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct PosColumn : public Column { ESM::Position ESXRecordT::* mPosition; int mIndex; PosColumn (ESM::Position ESXRecordT::* position, int index, bool door) : Column ( (door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos)+index, ColumnBase::Display_Float), mPosition (position), mIndex (index) {} QVariant get (const Record& record) const override { const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); ESM::Position& position = record2.*mPosition; position.pos[mIndex] = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct RotColumn : public Column { ESM::Position ESXRecordT::* mPosition; int mIndex; RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) : Column ( (door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index, ColumnBase::Display_Double), mPosition (position), mIndex (index) {} QVariant get (const Record& record) const override { const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); ESM::Position& position = record2.*mPosition; position.rot[mIndex] = osg::DegreesToRadians(data.toFloat()); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct DialogueTypeColumn : public Column { DialogueTypeColumn (bool hidden = false) : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType, hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) {} QVariant get (const Record& record) const override { return static_cast (record.get().mType); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mType = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct QuestStatusTypeColumn : public Column { QuestStatusTypeColumn() : Column (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType) {} QVariant get (const Record& record) const override { return static_cast (record.get().mQuestStatus); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mQuestStatus = static_cast (data.toInt()); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct QuestDescriptionColumn : public Column { QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_LongString) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mResponse.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mResponse = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct QuestIndexColumn : public Column { QuestIndexColumn() : Column (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mData.mDisposition; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mDisposition = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct TopicColumn : public Column { TopicColumn (bool journal) : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mTopicId.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTopicId = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct ActorColumn : public Column { ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_Npc) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mActor.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mActor = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct RaceColumn : public Column { RaceColumn() : Column (Columns::ColumnId_Race, ColumnBase::Display_Race) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mRace.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRace = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ClassColumn : public Column { ClassColumn() : Column (Columns::ColumnId_Class, ColumnBase::Display_Class) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mClass.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mClass = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct PcFactionColumn : public Column { PcFactionColumn() : Column (Columns::ColumnId_PcFaction, ColumnBase::Display_Faction) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mPcFaction.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mPcFaction = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ResponseColumn : public Column { ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_LongString) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mResponse.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mResponse = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct DispositionColumn : public Column { DispositionColumn() : Column (Columns::ColumnId_Disposition, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mData.mDisposition; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mDisposition = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct RankColumn : public Column { RankColumn() : Column (Columns::ColumnId_Rank, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mRank); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mRank = static_cast (data.toInt()); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct PcRankColumn : public Column { PcRankColumn() : Column (Columns::ColumnId_PcRank, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mPCrank); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mPCrank = static_cast (data.toInt()); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct GenderColumn : public Column { GenderColumn() : Column (Columns::ColumnId_Gender, ColumnBase::Display_Gender) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mGender); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mGender = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct GenderNpcColumn : public Column { GenderNpcColumn() : Column(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc) {} QVariant get(const Record& record) const override { // Implemented this way to allow additional gender types in the future. if ((record.get().mData.mFlags & ESM::BodyPart::BPF_Female) == ESM::BodyPart::BPF_Female) return 1; return 0; } void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); // Implemented this way to allow additional gender types in the future. if (data.toInt() == 1) record2.mData.mFlags = (record2.mData.mFlags & ~ESM::BodyPart::BPF_Female) | ESM::BodyPart::BPF_Female; else record2.mData.mFlags = record2.mData.mFlags & ~ESM::BodyPart::BPF_Female; record.setModified(record2); } bool isEditable() const override { return true; } }; template struct EnchantmentTypeColumn : public Column { EnchantmentTypeColumn() : Column (Columns::ColumnId_EnchantmentType, ColumnBase::Display_EnchantmentType) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mType); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ChargesColumn2 : public Column { ChargesColumn2() : Column (Columns::ColumnId_Charges, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mData.mCharge; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mCharge = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct AutoCalcColumn : public Column { AutoCalcColumn() : Column (Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean) {} QVariant get (const Record& record) const override { return record.get().mData.mAutocalc!=0; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAutocalc = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct ModelColumn : public Column { ModelColumn() : Column (Columns::ColumnId_Model, ColumnBase::Display_Mesh) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mModel.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mModel = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct VampireColumn : public Column { VampireColumn() : Column (Columns::ColumnId_Vampire, ColumnBase::Display_Boolean) {} QVariant get (const Record& record) const override { return record.get().mData.mVampire!=0; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mVampire = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct BodyPartTypeColumn : public Column { BodyPartTypeColumn() : Column (Columns::ColumnId_BodyPartType, ColumnBase::Display_BodyPartType) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mPart); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mPart = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct MeshTypeColumn : public Column { MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) {} QVariant get (const Record& record) const override { return static_cast (record.get().mData.mType); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct OwnerGlobalColumn : public Column { OwnerGlobalColumn() : Column (Columns::ColumnId_OwnerGlobal, ColumnBase::Display_GlobalVariable) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mGlobalVariable.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mGlobalVariable = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct RefNumCounterColumn : public Column { RefNumCounterColumn() : Column (Columns::ColumnId_RefNumCounter, ColumnBase::Display_Integer, 0) {} QVariant get (const Record& record) const override { return static_cast (record.get().mRefNumCounter); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRefNumCounter = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct RefNumColumn : public Column { RefNumColumn() : Column (Columns::ColumnId_RefNum, ColumnBase::Display_Integer, 0) {} QVariant get (const Record& record) const override { return static_cast (record.get().mRefNum.mIndex); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRefNum.mIndex = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } bool isUserEditable() const override { return false; } }; template struct SoundColumn : public Column { SoundColumn() : Column (Columns::ColumnId_Sound, ColumnBase::Display_Sound) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mSound.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mSound = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct CreatureColumn : public Column { CreatureColumn() : Column (Columns::ColumnId_Creature, ColumnBase::Display_Creature) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mCreature.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mCreature = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SoundGeneratorTypeColumn : public Column { SoundGeneratorTypeColumn() : Column (Columns::ColumnId_SoundGeneratorType, ColumnBase::Display_SoundGeneratorType) {} QVariant get (const Record& record) const override { return static_cast (record.get().mType); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mType = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct BaseCostColumn : public Column { BaseCostColumn() : Column (Columns::ColumnId_BaseCost, ColumnBase::Display_Float) {} QVariant get (const Record& record) const override { return record.get().mData.mBaseCost; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mBaseCost = data.toFloat(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct SchoolColumn : public Column { SchoolColumn() : Column (Columns::ColumnId_School, ColumnBase::Display_School) {} QVariant get (const Record& record) const override { return record.get().mData.mSchool; } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mSchool = data.toInt(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct EffectTextureColumn : public Column { EffectTextureColumn (Columns::ColumnId columnId) : Column (columnId, columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture : ColumnBase::Display_Icon) { assert (this->mColumnId==Columns::ColumnId_Icon || this->mColumnId==Columns::ColumnId_Particle); } QVariant get (const Record& record) const override { return QString::fromUtf8 ( (this->mColumnId==Columns::ColumnId_Icon ? record.get().mIcon : record.get().mParticle).c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); (this->mColumnId==Columns::ColumnId_Icon ? record2.mIcon : record2.mParticle) = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct EffectObjectColumn : public Column { EffectObjectColumn (Columns::ColumnId columnId) : Column (columnId, columnId==Columns::ColumnId_BoltObject ? ColumnBase::Display_Weapon : ColumnBase::Display_Static) { assert (this->mColumnId==Columns::ColumnId_CastingObject || this->mColumnId==Columns::ColumnId_HitObject || this->mColumnId==Columns::ColumnId_AreaObject || this->mColumnId==Columns::ColumnId_BoltObject); } QVariant get (const Record& record) const override { const std::string *string = nullptr; switch (this->mColumnId) { case Columns::ColumnId_CastingObject: string = &record.get().mCasting; break; case Columns::ColumnId_HitObject: string = &record.get().mHit; break; case Columns::ColumnId_AreaObject: string = &record.get().mArea; break; case Columns::ColumnId_BoltObject: string = &record.get().mBolt; break; } if (!string) throw std::logic_error ("Unsupported column ID"); return QString::fromUtf8 (string->c_str()); } void set (Record& record, const QVariant& data) override { std::string *string = nullptr; ESXRecordT record2 = record.get(); switch (this->mColumnId) { case Columns::ColumnId_CastingObject: string = &record2.mCasting; break; case Columns::ColumnId_HitObject: string = &record2.mHit; break; case Columns::ColumnId_AreaObject: string = &record2.mArea; break; case Columns::ColumnId_BoltObject: string = &record2.mBolt; break; } if (!string) throw std::logic_error ("Unsupported column ID"); *string = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct EffectSoundColumn : public Column { EffectSoundColumn (Columns::ColumnId columnId) : Column (columnId, ColumnBase::Display_Sound) { assert (this->mColumnId==Columns::ColumnId_CastingSound || this->mColumnId==Columns::ColumnId_HitSound || this->mColumnId==Columns::ColumnId_AreaSound || this->mColumnId==Columns::ColumnId_BoltSound); } QVariant get (const Record& record) const override { const std::string *string = nullptr; switch (this->mColumnId) { case Columns::ColumnId_CastingSound: string = &record.get().mCastSound; break; case Columns::ColumnId_HitSound: string = &record.get().mHitSound; break; case Columns::ColumnId_AreaSound: string = &record.get().mAreaSound; break; case Columns::ColumnId_BoltSound: string = &record.get().mBoltSound; break; } if (!string) throw std::logic_error ("Unsupported column ID"); return QString::fromUtf8 (string->c_str()); } void set (Record& record, const QVariant& data) override { std::string *string = nullptr; ESXRecordT record2 = record.get(); switch (this->mColumnId) { case Columns::ColumnId_CastingSound: string = &record2.mCastSound; break; case Columns::ColumnId_HitSound: string = &record2.mHitSound; break; case Columns::ColumnId_AreaSound: string = &record2.mAreaSound; break; case Columns::ColumnId_BoltSound: string = &record2.mBoltSound; break; } if (!string) throw std::logic_error ("Unsupported column ID"); *string = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FormatColumn : public Column { FormatColumn() : Column (Columns::ColumnId_FileFormat, ColumnBase::Display_Integer) {} QVariant get (const Record& record) const override { return record.get().mFormat; } bool isEditable() const override { return false; } }; template struct AuthorColumn : public Column { AuthorColumn() : Column (Columns::ColumnId_Author, ColumnBase::Display_String32) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mAuthor.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mAuthor = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; template struct FileDescriptionColumn : public Column { FileDescriptionColumn() : Column (Columns::ColumnId_FileDescription, ColumnBase::Display_LongString256) {} QVariant get (const Record& record) const override { return QString::fromUtf8 (record.get().mDescription.c_str()); } void set (Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDescription = data.toString().toUtf8().constData(); record.setModified (record2); } bool isEditable() const override { return true; } }; struct LandTextureNicknameColumn : public Column { LandTextureNicknameColumn(); QVariant get(const Record& record) const override; void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; struct LandTextureIndexColumn : public Column { LandTextureIndexColumn(); QVariant get(const Record& record) const override; bool isEditable() const override; }; struct LandPluginIndexColumn : public Column { LandPluginIndexColumn(); QVariant get(const Record& record) const override; bool isEditable() const override; }; struct LandTexturePluginIndexColumn : public Column { LandTexturePluginIndexColumn(); QVariant get(const Record& record) const override; bool isEditable() const override; }; struct LandNormalsColumn : public Column { using DataType = QVector; LandNormalsColumn(); QVariant get(const Record& record) const override; void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; struct LandHeightsColumn : public Column { using DataType = QVector; LandHeightsColumn(); QVariant get(const Record& record) const override; void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; struct LandColoursColumn : public Column { using DataType = QVector; LandColoursColumn(); QVariant get(const Record& record) const override; void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; struct LandTexturesColumn : public Column { using DataType = QVector; LandTexturesColumn(); QVariant get(const Record& record) const override; void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; struct BodyPartRaceColumn : public RaceColumn { const MeshTypeColumn *mMeshType; BodyPartRaceColumn(const MeshTypeColumn *meshType); QVariant get(const Record &record) const override; void set(Record &record, const QVariant &data) override; bool isEditable() const override; }; } // This is required to access the type as a QVariant. Q_DECLARE_METATYPE(CSMWorld::LandNormalsColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandHeightsColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandColoursColumn::DataType) Q_DECLARE_METATYPE(CSMWorld::LandTexturesColumn::DataType) #endif openmw-openmw-0.48.0/apps/opencs/model/world/columns.cpp000066400000000000000000000662301445372753700233100ustar00rootroot00000000000000#include "columns.hpp" #include #include #include "universalid.hpp" #include "infoselectwrapper.hpp" namespace CSMWorld { namespace Columns { struct ColumnDesc { int mId; const char *mName; }; const ColumnDesc sNames[] = { { ColumnId_Value, "Value" }, { ColumnId_Id, "ID" }, { ColumnId_Modification, "Modified" }, { ColumnId_RecordType, "Record Type" }, { ColumnId_ValueType, "Value Type" }, { ColumnId_Description, "Description" }, { ColumnId_Specialisation, "Specialisation" }, { ColumnId_Attribute, "Attribute" }, { ColumnId_Name, "Name" }, { ColumnId_Playable, "Playable" }, { ColumnId_Hidden, "Hidden" }, { ColumnId_MaleWeight, "Male Weight" }, { ColumnId_FemaleWeight, "Female Weight" }, { ColumnId_MaleHeight, "Male Height" }, { ColumnId_FemaleHeight, "Female Height" }, { ColumnId_Volume, "Volume" }, { ColumnId_MinRange, "Min Range" }, { ColumnId_MaxRange, "Max Range" }, { ColumnId_MinMagnitude, "Min Magnitude" }, { ColumnId_MaxMagnitude, "Max Magnitude" }, { ColumnId_SoundFile, "Sound File" }, { ColumnId_MapColour, "Map Colour" }, { ColumnId_SleepEncounter, "Sleep Encounter" }, { ColumnId_Texture, "Texture" }, { ColumnId_SpellType, "Spell Type" }, { ColumnId_Cost, "Cost" }, { ColumnId_ScriptText, "Script Text" }, { ColumnId_Region, "Region" }, { ColumnId_Cell, "Cell" }, { ColumnId_Scale, "Scale" }, { ColumnId_Owner, "Owner" }, { ColumnId_Soul, "Soul" }, { ColumnId_Faction, "Faction" }, { ColumnId_FactionIndex, "Faction Index" }, { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, { ColumnId_CoinValue, "Coin Value" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_LockLevel, "Lock Level" }, { ColumnId_Key, "Key" }, { ColumnId_Trap, "Trap" }, { ColumnId_BeastRace, "Beast Race" }, { ColumnId_AutoCalc, "Auto Calc" }, { ColumnId_StarterSpell, "Starter Spell" }, { ColumnId_AlwaysSucceeds, "Always Succeeds" }, { ColumnId_SleepForbidden, "Sleep Forbidden" }, { ColumnId_InteriorWater, "Interior Water" }, { ColumnId_InteriorSky, "Interior Sky" }, { ColumnId_Model, "Model/Animation" }, { ColumnId_Script, "Script" }, { ColumnId_Icon, "Icon" }, { ColumnId_Weight, "Weight" }, { ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_Quality, "Quality" }, { ColumnId_AiHello, "AI Hello" }, { ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFight, "AI Fight" }, { ColumnId_AiAlarm, "AI Alarm" }, { ColumnId_BuysWeapons, "Buys Weapons" }, { ColumnId_BuysArmor, "Buys Armor" }, { ColumnId_BuysClothing, "Buys Clothing" }, { ColumnId_BuysBooks, "Buys Books" }, { ColumnId_BuysIngredients, "Buys Ingredients" }, { ColumnId_BuysLockpicks, "Buys Lockpicks" }, { ColumnId_BuysProbes, "Buys Probes" }, { ColumnId_BuysLights, "Buys Lights" }, { ColumnId_BuysApparati, "Buys Apparati" }, { ColumnId_BuysRepairItems, "Buys Repair Items" }, { ColumnId_BuysMiscItems, "Buys Misc Items" }, { ColumnId_BuysPotions, "Buys Potions" }, { ColumnId_BuysMagicItems, "Buys Magic Items" }, { ColumnId_SellsSpells, "Sells Spells" }, { ColumnId_Trainer, "Trainer" }, { ColumnId_Spellmaking, "Spellmaking" }, { ColumnId_EnchantingService, "Enchanting Service" }, { ColumnId_RepairService, "Repair Service" }, { ColumnId_ApparatusType, "Apparatus Type" }, { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, { ColumnId_ArmorValue, "Armor Value" }, { ColumnId_BookType, "Book Type" }, { ColumnId_ClothingType, "Clothing Type" }, { ColumnId_WeightCapacity, "Weight Capacity" }, { ColumnId_OrganicContainer, "Organic Container" }, { ColumnId_Respawn, "Respawn" }, { ColumnId_CreatureType, "Creature Type" }, { ColumnId_SoulPoints, "Soul Points" }, { ColumnId_ParentCreature, "Parent Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, { ColumnId_Swims, "Swims" }, { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, { ColumnId_Essential, "Essential" }, { ColumnId_BloodType, "Blood Type" }, { ColumnId_OpenSound, "Open Sound" }, { ColumnId_CloseSound, "Close Sound" }, { ColumnId_Duration, "Duration" }, { ColumnId_Radius, "Radius" }, { ColumnId_Colour, "Colour" }, { ColumnId_Sound, "Sound" }, { ColumnId_Dynamic, "Dynamic" }, { ColumnId_Portable, "Portable" }, { ColumnId_NegativeLight, "Negative Light" }, { ColumnId_EmitterType, "Emitter Type" }, { ColumnId_Fire, "Fire" }, { ColumnId_OffByDefault, "Off by default" }, { ColumnId_IsKey, "Is Key" }, { ColumnId_Race, "Race" }, { ColumnId_Class, "Class" }, { Columnid_Hair, "Hair" }, { ColumnId_Head, "Head" }, { ColumnId_Female, "Female" }, { ColumnId_WeaponType, "Weapon Type" }, { ColumnId_WeaponSpeed, "Weapon Speed" }, { ColumnId_WeaponReach, "Weapon Reach" }, { ColumnId_MinChop, "Min Chop" }, { ColumnId_MaxChip, "Max Chop" }, { Columnid_MinSlash, "Min Slash" }, { ColumnId_MaxSlash, "Max Slash" }, { ColumnId_MinThrust, "Min Thrust" }, { ColumnId_MaxThrust, "Max Thrust" }, { ColumnId_Magical, "Magical" }, { ColumnId_Silver, "Silver" }, { ColumnId_Filter, "Filter" }, { ColumnId_PositionXPos, "Pos X" }, { ColumnId_PositionYPos, "Pos Y" }, { ColumnId_PositionZPos, "Pos Z" }, { ColumnId_PositionXRot, "Rot X" }, { ColumnId_PositionYRot, "Rot Y" }, { ColumnId_PositionZRot, "Rot Z" }, { ColumnId_DoorPositionXPos, "Teleport Pos X" }, { ColumnId_DoorPositionYPos, "Teleport Pos Y" }, { ColumnId_DoorPositionZPos, "Teleport Pos Z" }, { ColumnId_DoorPositionXRot, "Teleport Rot X" }, { ColumnId_DoorPositionYRot, "Teleport Rot Y" }, { ColumnId_DoorPositionZRot, "Teleport Rot Z" }, { ColumnId_DialogueType, "Dialogue Type" }, { ColumnId_QuestIndex, "Quest Index" }, { ColumnId_QuestStatusType, "Quest Status" }, { ColumnId_QuestDescription, "Quest Description" }, { ColumnId_Topic, "Topic" }, { ColumnId_Journal, "Journal" }, { ColumnId_Actor, "Actor" }, { ColumnId_PcFaction, "PC Faction" }, { ColumnId_Response, "Response" }, { ColumnId_Disposition, "Disposition" }, { ColumnId_Rank, "Rank" }, { ColumnId_Gender, "Gender" }, { ColumnId_PcRank, "PC Rank" }, { ColumnId_ReferenceableId, "Object ID" }, { ColumnId_ContainerContent, "Content" }, { ColumnId_ItemCount, "Count" }, { ColumnId_InventoryItemId, "Item ID"}, { ColumnId_CombatState, "Combat" }, { ColumnId_MagicState, "Magic" }, { ColumnId_StealthState, "Stealth" }, { ColumnId_EnchantmentType, "Enchantment Type" }, { ColumnId_Vampire, "Vampire" }, { ColumnId_BodyPartType, "Bodypart Type" }, { ColumnId_MeshType, "Mesh Type" }, { ColumnId_ActorInventory, "Inventory" }, { ColumnId_SpellList, "Spells" }, { ColumnId_SpellId, "Spell ID"}, { ColumnId_NpcDestinations, "Destinations" }, { ColumnId_DestinationCell, "Dest Cell"}, { ColumnId_PosX, "Dest X"}, { ColumnId_PosY, "Dest Y"}, { ColumnId_PosZ, "Dest Z"}, { ColumnId_RotX, "Rotation X"}, { ColumnId_RotY, "Rotation Y"}, { ColumnId_RotZ, "Rotation Z"}, { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_BypassNewGame, "Bypass New Game" }, { ColumnId_GlobalProfile, "Global Profile" }, { ColumnId_RefNumCounter, "RefNum Counter" }, { ColumnId_RefNum, "RefNum" }, { ColumnId_Creature, "Creature" }, { ColumnId_SoundGeneratorType, "Sound Generator Type" }, { ColumnId_AllowSpellmaking, "Allow Spellmaking" }, { ColumnId_AllowEnchanting, "Allow Enchanting" }, { ColumnId_BaseCost, "Base Cost" }, { ColumnId_School, "School" }, { ColumnId_Particle, "Particle" }, { ColumnId_CastingObject, "Casting Object" }, { ColumnId_HitObject, "Hit Object" }, { ColumnId_AreaObject, "Area Object" }, { ColumnId_BoltObject, "Bolt Object" }, { ColumnId_CastingSound, "Casting Sound" }, { ColumnId_HitSound, "Hit Sound" }, { ColumnId_AreaSound, "Area Sound" }, { ColumnId_BoltSound, "Bolt Sound" }, { ColumnId_PathgridPoints, "Points" }, { ColumnId_PathgridIndex, "pIndex" }, { ColumnId_PathgridPosX, "X" }, { ColumnId_PathgridPosY, "Y" }, { ColumnId_PathgridPosZ, "Z" }, { ColumnId_PathgridEdges, "Edges" }, { ColumnId_PathgridEdgeIndex, "eIndex" }, { ColumnId_PathgridEdge0, "Point 0" }, { ColumnId_PathgridEdge1, "Point 1" }, { ColumnId_RegionSounds, "Sounds" }, { ColumnId_SoundName, "Sound Name" }, { ColumnId_SoundChance, "Chance" }, { ColumnId_FactionReactions, "Reactions" }, { ColumnId_FactionRanks, "Ranks" }, { ColumnId_FactionReaction, "Reaction" }, { ColumnId_FactionAttrib1, "Attrib 1" }, { ColumnId_FactionAttrib2, "Attrib 2" }, { ColumnId_FactionPrimSkill, "Prim Skill" }, { ColumnId_FactionFavSkill, "Fav Skill" }, { ColumnId_FactionRep, "Fact Rep" }, { ColumnId_RankName, "Rank Name" }, { ColumnId_EffectList, "Effects" }, { ColumnId_EffectId, "Effect" }, { ColumnId_EffectRange, "Range" }, { ColumnId_EffectArea, "Area" }, { ColumnId_AiPackageList, "Ai Packages" }, { ColumnId_AiPackageType, "Package" }, { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, { ColumnId_AiWanderRepeat, "Ai Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, { ColumnId_AiTargetCell, "Target Cell" }, { ColumnId_PartRefList, "Part Reference" }, { ColumnId_PartRefType, "Type" }, { ColumnId_PartRefMale, "Male Part" }, { ColumnId_PartRefFemale, "Female Part" }, { ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledItemId,"Levelled Item" }, { ColumnId_LevelledItemLevel,"PC Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item for each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, { ColumnId_Skill, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, { ColumnId_InfoCondVar, "Variable/Object" }, { ColumnId_InfoCondComp, "Relation" }, { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, { ColumnId_NpcAttributes, "NPC Attributes" }, { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, { ColumnId_NpcMisc, "NPC Misc" }, { ColumnId_Level, "Level" }, { ColumnId_Mana, "Mana" }, { ColumnId_Fatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, { ColumnId_NpcRank, "NPC Rank" }, { ColumnId_Gold, "Gold" }, { ColumnId_RaceAttributes, "Race Attributes" }, { ColumnId_Male, "Male" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, { ColumnId_Ambient, "Ambient" }, { ColumnId_Sunlight, "Sunlight" }, { ColumnId_Fog, "Fog" }, { ColumnId_FogDensity, "Fog Density" }, { ColumnId_WaterLevel, "Water Level" }, { ColumnId_MapColor, "Map Color" }, { ColumnId_FileFormat, "File Format" }, { ColumnId_FileDescription, "File Description" }, { ColumnId_Author, "Author" }, { ColumnId_CreatureAttributes, "Creature Attributes" }, { ColumnId_AttributeValue, "Attrib Value" }, { ColumnId_CreatureAttack, "Creature Attack" }, { ColumnId_MinAttack, "Min Attack" }, { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, { ColumnId_Idle1, "Idle 1" }, { ColumnId_Idle2, "Idle 2" }, { ColumnId_Idle3, "Idle 3" }, { ColumnId_Idle4, "Idle 4" }, { ColumnId_Idle5, "Idle 5" }, { ColumnId_Idle6, "Idle 6" }, { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, { ColumnId_RegionWeather, "Weather" }, { ColumnId_WeatherName, "Type" }, { ColumnId_WeatherChance, "Percent Chance" }, { ColumnId_Text, "Text" }, { ColumnId_TextureNickname, "Texture Nickname" }, { ColumnId_PluginIndex, "Plugin Index" }, { ColumnId_TextureIndex, "Texture Index" }, { ColumnId_LandMapLodIndex, "Land map height LOD" }, { ColumnId_LandNormalsIndex, "Land normals" }, { ColumnId_LandHeightsIndex, "Land heights" }, { ColumnId_LandColoursIndex, "Land colors" }, { ColumnId_LandTexturesIndex, "Land textures" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, { ColumnId_UseValue4, "Use value 4" }, { ColumnId_Attribute1, "Attribute 1" }, { ColumnId_Attribute2, "Attribute 2" }, { ColumnId_MajorSkill1, "Major Skill 1" }, { ColumnId_MajorSkill2, "Major Skill 2" }, { ColumnId_MajorSkill3, "Major Skill 3" }, { ColumnId_MajorSkill4, "Major Skill 4" }, { ColumnId_MajorSkill5, "Major Skill 5" }, { ColumnId_MinorSkill1, "Minor Skill 1" }, { ColumnId_MinorSkill2, "Minor Skill 2" }, { ColumnId_MinorSkill3, "Minor Skill 3" }, { ColumnId_MinorSkill4, "Minor Skill 4" }, { ColumnId_MinorSkill5, "Minor Skill 5" }, { ColumnId_Skill1, "Skill 1" }, { ColumnId_Skill2, "Skill 2" }, { ColumnId_Skill3, "Skill 3" }, { ColumnId_Skill4, "Skill 4" }, { ColumnId_Skill5, "Skill 5" }, { ColumnId_Skill6, "Skill 6" }, { ColumnId_Skill7, "Skill 7" }, { ColumnId_Persistent, "Persistent" }, { ColumnId_Blocked, "Blocked" }, { ColumnId_LevelledCreatureId,"Levelled Creature" }, { -1, 0 } // end marker }; } } std::string CSMWorld::Columns::getName (ColumnId column) { for (int i=0; sNames[i].mName; ++i) if (column==sNames[i].mId) return sNames[i].mName; return ""; } int CSMWorld::Columns::getId (const std::string& name) { std::string name2 = Misc::StringUtils::lowerCase (name); for (int i=0; sNames[i].mName; ++i) if (Misc::StringUtils::ciEqual(std::string_view(sNames[i].mName), name2)) return sNames[i].mId; return -1; } namespace { static const char *sSpecialisations[] = { "Combat", "Magic", "Stealth", 0 }; // see ESM::Attribute::AttributeID in static const char *sAttributes[] = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck", 0 }; static const char *sSpellTypes[] = { "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0 }; static const char *sApparatusTypes[] = { "Mortar & Pestle", "Alembic", "Calcinator", "Retort", 0 }; static const char *sArmorTypes[] = { "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet", "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0 }; static const char *sClothingTypes[] = { "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", "Amulet", 0 }; static const char *sCreatureTypes[] = { "Creature", "Daedra", "Undead", "Humanoid", 0 }; static const char *sWeaponTypes[] = { "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close", "Blunt 2H Wide", "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow", "Bolt", 0 }; static const char *sModificationEnums[] = { "Base", "Modified", "Added", "Deleted", "Deleted", 0 }; static const char *sVarTypeEnums[] = { "unknown", "none", "short", "integer", "long", "float", "string", 0 }; static const char *sDialogueTypeEnums[] = { "Topic", "Voice", "Greeting", "Persuasion", 0 }; static const char *sQuestStatusTypes[] = { "None", "Name", "Finished", "Restart", 0 }; static const char *sGenderEnums[] = { "Male", "Female", 0 }; static const char *sEnchantmentTypes[] = { "Cast Once", "When Strikes", "When Used", "Constant Effect", 0 }; static const char *sBodyPartTypes[] = { "Head", "Hair", "Neck", "Chest", "Groin", "Hand", "Wrist", "Forearm", "Upper Arm", "Foot", "Ankle", "Knee", "Upper Leg", "Clavicle", "Tail", 0 }; static const char *sMeshTypes[] = { "Skin", "Clothing", "Armour", 0 }; static const char *sSoundGeneratorType[] = { "Left Foot", "Right Foot", "Swim Left", "Swim Right", "Moan", "Roar", "Scream", "Land", 0 }; static const char *sSchools[] = { "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", "Restoration", 0 }; // impact from magic effects, see ESM::Skill::SkillEnum in static const char *sSkills[] = { "Block", "Armorer", "MediumArmor", "HeavyArmor", "BluntWeapon", "LongBlade", "Axe", "Spear", "Athletics", "Enchant", "Destruction", "Alteration", "Illusion", "Conjuration", "Mysticism", "Restoration", "Alchemy", "Unarmored", "Security", "Sneak", "Acrobatics", "LightArmor", "ShortBlade", "Marksman", "Mercantile", "Speechcraft", "HandToHand", 0 }; // range of magic effects, see ESM::RangeType in static const char *sEffectRange[] = { "Self", "Touch", "Target", 0 }; // magic effect names, see ESM::MagicEffect::Effects in static const char *sEffectId[] = { "WaterBreathing", "SwiftSwim", "WaterWalking", "Shield", "FireShield", "LightningShield", "FrostShield", "Burden", "Feather", "Jump", "Levitate", "SlowFall", "Lock", "Open", "FireDamage", "ShockDamage", "FrostDamage", "DrainAttribute", "DrainHealth", "DrainMagicka", "DrainFatigue", "DrainSkill", "DamageAttribute", "DamageHealth", "DamageMagicka", "DamageFatigue", "DamageSkill", "Poison", "WeaknessToFire", "WeaknessToFrost", "WeaknessToShock", "WeaknessToMagicka", "WeaknessToCommonDisease", "WeaknessToBlightDisease", "WeaknessToCorprusDisease", "WeaknessToPoison", "WeaknessToNormalWeapons", "DisintegrateWeapon", "DisintegrateArmor", "Invisibility", "Chameleon", "Light", "Sanctuary", "NightEye", "Charm", "Paralyze", "Silence", "Blind", "Sound", "CalmHumanoid", "CalmCreature", "FrenzyHumanoid", "FrenzyCreature", "DemoralizeHumanoid", "DemoralizeCreature", "RallyHumanoid", "RallyCreature", "Dispel", "Soultrap", "Telekinesis", "Mark", "Recall", "DivineIntervention", "AlmsiviIntervention", "DetectAnimal", "DetectEnchantment", "DetectKey", "SpellAbsorption", "Reflect", "CureCommonDisease", "CureBlightDisease", "CureCorprusDisease", "CurePoison", "CureParalyzation", "RestoreAttribute", "RestoreHealth", "RestoreMagicka", "RestoreFatigue", "RestoreSkill", "FortifyAttribute", "FortifyHealth", "FortifyMagicka", "FortifyFatigue", "FortifySkill", "FortifyMaximumMagicka", "AbsorbAttribute", "AbsorbHealth", "AbsorbMagicka", "AbsorbFatigue", "AbsorbSkill", "ResistFire", "ResistFrost", "ResistShock", "ResistMagicka", "ResistCommonDisease", "ResistBlightDisease", "ResistCorprusDisease", "ResistPoison", "ResistNormalWeapons", "ResistParalysis", "RemoveCurse", "TurnUndead", "SummonScamp", "SummonClannfear", "SummonDaedroth", "SummonDremora", "SummonAncestralGhost", "SummonSkeletalMinion", "SummonBonewalker", "SummonGreaterBonewalker", "SummonBonelord", "SummonWingedTwilight", "SummonHunger", "SummonGoldenSaint", "SummonFlameAtronach", "SummonFrostAtronach", "SummonStormAtronach", "FortifyAttack", "CommandCreature", "CommandHumanoid", "BoundDagger", "BoundLongsword", "BoundMace", "BoundBattleAxe", "BoundSpear", "BoundLongbow", "ExtraSpell", "BoundCuirass", "BoundHelm", "BoundBoots", "BoundShield", "BoundGloves", "Corprus", "Vampirism", "SummonCenturionSphere", "SunDamage", "StuntedMagicka", "SummonFabricant", "SummonWolf", "SummonBear", "SummonBonewolf", "SummonCreature04", "SummonCreature05", 0 }; // see ESM::PartReferenceType in static const char *sPartRefType[] = { "Head", "Hair", "Neck", "Cuirass", "Groin", "Skirt", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield", "Right Forearm", "Left Forearm", "Right Upperarm", "Left Upperarm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Leg", "Left Leg", "Right Pauldron", "Left Pauldron", "Weapon", "Tail", 0 }; // see the enums in static const char *sAiPackageType[] = { "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; static const char *sBookType[] = { "Book", "Scroll", 0 }; static const char *sEmitterType[] = { "", "Flickering", "Flickering (Slow)", "Pulsing", "Pulsing (Slow)", 0 }; const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) { case CSMWorld::Columns::ColumnId_Specialisation: return sSpecialisations; case CSMWorld::Columns::ColumnId_Attribute: return sAttributes; case CSMWorld::Columns::ColumnId_SpellType: return sSpellTypes; case CSMWorld::Columns::ColumnId_ApparatusType: return sApparatusTypes; case CSMWorld::Columns::ColumnId_ArmorType: return sArmorTypes; case CSMWorld::Columns::ColumnId_ClothingType: return sClothingTypes; case CSMWorld::Columns::ColumnId_CreatureType: return sCreatureTypes; case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes; case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums; case CSMWorld::Columns::ColumnId_EnchantmentType: return sEnchantmentTypes; case CSMWorld::Columns::ColumnId_BodyPartType: return sBodyPartTypes; case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; case CSMWorld::Columns::ColumnId_School: return sSchools; case CSMWorld::Columns::ColumnId_Skill: return sSkills; case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; case CSMWorld::Columns::ColumnId_BookType: return sBookType; case CSMWorld::Columns::ColumnId_EmitterType: return sEmitterType; default: return 0; } } } bool CSMWorld::Columns::hasEnums (ColumnId column) { return getEnumNames (column)!=0 || column==ColumnId_RecordType; } std::vector>CSMWorld::Columns::getEnums (ColumnId column) { std::vector> enums; if (const char **table = getEnumNames (column)) for (int i=0; table[i]; ++i) enums.emplace_back(i, table[i]); else if (column==ColumnId_BloodType) { for (int i=0; i<8; i++) { const std::string& bloodName = Fallback::Map::getString("Blood_Texture_Name_" + std::to_string(i)); if (!bloodName.empty()) enums.emplace_back(i, bloodName); } } else if (column==ColumnId_RecordType) { enums.emplace_back(UniversalId::Type_None, ""); // none for (int i=UniversalId::Type_None+1; i (i)).getTypeName()); } return enums; } openmw-openmw-0.48.0/apps/opencs/model/world/columns.hpp000066400000000000000000000337651445372753700233240ustar00rootroot00000000000000#ifndef CSM_WOLRD_COLUMNS_H #define CSM_WOLRD_COLUMNS_H #include #include #include "columnbase.hpp" namespace CSMWorld { namespace Columns { enum ColumnId { ColumnId_Value = 0, ColumnId_Id = 1, ColumnId_Modification = 2, ColumnId_RecordType = 3, ColumnId_ValueType = 4, ColumnId_Description = 5, ColumnId_Specialisation = 6, ColumnId_Attribute = 7, ColumnId_Name = 8, ColumnId_Playable = 9, ColumnId_Hidden = 10, ColumnId_MaleWeight = 11, ColumnId_FemaleWeight = 12, ColumnId_MaleHeight = 13, ColumnId_FemaleHeight = 14, ColumnId_Volume = 15, ColumnId_MinRange = 16, ColumnId_MaxRange = 17, ColumnId_SoundFile = 18, ColumnId_MapColour = 19, ColumnId_SleepEncounter = 20, ColumnId_Texture = 21, ColumnId_SpellType = 22, ColumnId_Cost = 23, ColumnId_ScriptText = 24, ColumnId_Region = 25, ColumnId_Cell = 26, ColumnId_Scale = 27, ColumnId_Owner = 28, ColumnId_Soul = 29, ColumnId_Faction = 30, ColumnId_FactionIndex = 31, ColumnId_Charges = 32, ColumnId_Enchantment = 33, ColumnId_CoinValue = 34, ColumnId_Teleport = 35, ColumnId_TeleportCell = 36, ColumnId_LockLevel = 37, ColumnId_Key = 38, ColumnId_Trap = 39, ColumnId_BeastRace = 40, ColumnId_AutoCalc = 41, ColumnId_StarterSpell = 42, ColumnId_AlwaysSucceeds = 43, ColumnId_SleepForbidden = 44, ColumnId_InteriorWater = 45, ColumnId_InteriorSky = 46, ColumnId_Model = 47, ColumnId_Script = 48, ColumnId_Icon = 49, ColumnId_Weight = 50, ColumnId_EnchantmentPoints = 51, ColumnId_Quality = 52, // unused ColumnId_AiHello = 54, ColumnId_AiFlee = 55, ColumnId_AiFight = 56, ColumnId_AiAlarm = 57, ColumnId_BuysWeapons = 58, ColumnId_BuysArmor = 59, ColumnId_BuysClothing = 60, ColumnId_BuysBooks = 61, ColumnId_BuysIngredients = 62, ColumnId_BuysLockpicks = 63, ColumnId_BuysProbes = 64, ColumnId_BuysLights = 65, ColumnId_BuysApparati = 66, ColumnId_BuysRepairItems = 67, ColumnId_BuysMiscItems = 68, ColumnId_BuysPotions = 69, ColumnId_BuysMagicItems = 70, ColumnId_SellsSpells = 71, ColumnId_Trainer = 72, ColumnId_Spellmaking = 73, ColumnId_EnchantingService = 74, ColumnId_RepairService = 75, ColumnId_ApparatusType = 76, ColumnId_ArmorType = 77, ColumnId_Health = 78, ColumnId_ArmorValue = 79, ColumnId_BookType = 80, ColumnId_ClothingType = 81, ColumnId_WeightCapacity = 82, ColumnId_OrganicContainer = 83, ColumnId_Respawn = 84, ColumnId_CreatureType = 85, ColumnId_SoulPoints = 86, ColumnId_ParentCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, // unused ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, ColumnId_Essential = 94, ColumnId_BloodType = 95, // unused ColumnId_OpenSound = 97, ColumnId_CloseSound = 98, ColumnId_Duration = 99, ColumnId_Radius = 100, ColumnId_Colour = 101, ColumnId_Sound = 102, ColumnId_Dynamic = 103, ColumnId_Portable = 104, ColumnId_NegativeLight = 105, ColumnId_EmitterType = 106, // unused (3x) ColumnId_Fire = 110, ColumnId_OffByDefault = 111, ColumnId_IsKey = 112, ColumnId_Race = 113, ColumnId_Class = 114, Columnid_Hair = 115, ColumnId_Head = 116, ColumnId_Female = 117, ColumnId_WeaponType = 118, ColumnId_WeaponSpeed = 119, ColumnId_WeaponReach = 120, ColumnId_MinChop = 121, ColumnId_MaxChip = 122, Columnid_MinSlash = 123, ColumnId_MaxSlash = 124, ColumnId_MinThrust = 125, ColumnId_MaxThrust = 126, ColumnId_Magical = 127, ColumnId_Silver = 128, ColumnId_Filter = 129, ColumnId_PositionXPos = 130, ColumnId_PositionYPos = 131, ColumnId_PositionZPos = 132, ColumnId_PositionXRot = 133, ColumnId_PositionYRot = 134, ColumnId_PositionZRot = 135, ColumnId_DoorPositionXPos = 136, ColumnId_DoorPositionYPos = 137, ColumnId_DoorPositionZPos = 138, ColumnId_DoorPositionXRot = 139, ColumnId_DoorPositionYRot = 140, ColumnId_DoorPositionZRot = 141, ColumnId_DialogueType = 142, ColumnId_QuestIndex = 143, ColumnId_QuestStatusType = 144, ColumnId_QuestDescription = 145, ColumnId_Topic = 146, ColumnId_Journal = 147, ColumnId_Actor = 148, ColumnId_PcFaction = 149, ColumnId_Response = 150, ColumnId_Disposition = 151, ColumnId_Rank = 152, ColumnId_Gender = 153, ColumnId_PcRank = 154, ColumnId_ReferenceableId = 155, ColumnId_ContainerContent = 156, ColumnId_ItemCount = 157, ColumnId_InventoryItemId = 158, ColumnId_CombatState = 159, ColumnId_MagicState = 160, ColumnId_StealthState = 161, ColumnId_EnchantmentType = 162, ColumnId_Vampire = 163, ColumnId_BodyPartType = 164, ColumnId_MeshType = 165, ColumnId_ActorInventory = 166, ColumnId_SpellList = 167, ColumnId_SpellId = 168, ColumnId_NpcDestinations = 169, ColumnId_DestinationCell = 170, ColumnId_PosX = 171, // these are float ColumnId_PosY = 172, // these are float ColumnId_PosZ = 173, // these are float ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, // unused ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, ColumnId_GlobalProfile = 181, ColumnId_RefNumCounter = 182, ColumnId_RefNum = 183, ColumnId_Creature = 184, ColumnId_SoundGeneratorType = 185, ColumnId_AllowSpellmaking = 186, ColumnId_AllowEnchanting = 187, ColumnId_BaseCost = 188, ColumnId_School = 189, ColumnId_Particle = 190, ColumnId_CastingObject = 191, ColumnId_HitObject = 192, ColumnId_AreaObject = 193, ColumnId_BoltObject = 194, ColumnId_CastingSound = 195, ColumnId_HitSound = 196, ColumnId_AreaSound = 197, ColumnId_BoltSound = 198, ColumnId_PathgridPoints = 199, ColumnId_PathgridIndex = 200, ColumnId_PathgridPosX = 201, // these are int ColumnId_PathgridPosY = 202, // these are int ColumnId_PathgridPosZ = 203, // these are int ColumnId_PathgridEdges = 204, ColumnId_PathgridEdgeIndex = 205, ColumnId_PathgridEdge0 = 206, ColumnId_PathgridEdge1 = 207, ColumnId_RegionSounds = 208, ColumnId_SoundName = 209, ColumnId_SoundChance = 210, ColumnId_FactionReactions = 211, ColumnId_FactionReaction = 213, ColumnId_EffectList = 214, ColumnId_EffectId = 215, ColumnId_EffectRange = 217, ColumnId_EffectArea = 218, ColumnId_AiPackageList = 219, ColumnId_AiPackageType = 220, ColumnId_AiWanderDist = 221, ColumnId_AiDuration = 222, ColumnId_AiWanderToD = 223, // unused ColumnId_AiWanderRepeat = 225, ColumnId_AiActivateName = 226, // use ColumnId_PosX, etc for AI destinations ColumnId_AiTargetId = 227, ColumnId_AiTargetCell = 228, ColumnId_PartRefList = 229, ColumnId_PartRefType = 230, ColumnId_PartRefMale = 231, ColumnId_PartRefFemale = 232, ColumnId_LevelledList = 233, ColumnId_LevelledItemId = 234, ColumnId_LevelledItemLevel = 235, ColumnId_LevelledItemType = 236, ColumnId_LevelledItemTypeEach = 237, ColumnId_LevelledItemChanceNone = 238, ColumnId_PowerList = 239, ColumnId_Skill = 240, ColumnId_InfoList = 241, ColumnId_InfoCondition = 242, ColumnId_InfoCondFunc = 243, ColumnId_InfoCondVar = 244, ColumnId_InfoCondComp = 245, ColumnId_InfoCondValue = 246, ColumnId_OriginalCell = 247, ColumnId_NpcAttributes = 248, ColumnId_NpcSkills = 249, ColumnId_UChar = 250, ColumnId_NpcMisc = 251, ColumnId_Level = 252, // unused ColumnId_Mana = 255, ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, ColumnId_Gold = 260, // unused ColumnId_RaceAttributes = 262, ColumnId_Male = 263, // unused ColumnId_RaceSkillBonus = 265, // unused ColumnId_RaceBonus = 267, ColumnId_Interior = 268, ColumnId_Ambient = 269, ColumnId_Sunlight = 270, ColumnId_Fog = 271, ColumnId_FogDensity = 272, ColumnId_WaterLevel = 273, ColumnId_MapColor = 274, ColumnId_FileFormat = 275, ColumnId_FileDescription = 276, ColumnId_Author = 277, ColumnId_MinMagnitude = 278, ColumnId_MaxMagnitude = 279, ColumnId_CreatureAttributes = 280, ColumnId_AttributeValue = 281, ColumnId_CreatureAttack = 282, ColumnId_MinAttack = 283, ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, ColumnId_Idle1 = 286, ColumnId_Idle2 = 287, ColumnId_Idle3 = 288, ColumnId_Idle4 = 289, ColumnId_Idle5 = 290, ColumnId_Idle6 = 291, ColumnId_Idle7 = 292, ColumnId_Idle8 = 293, ColumnId_RegionWeather = 294, ColumnId_WeatherName = 295, ColumnId_WeatherChance = 296, ColumnId_Text = 297, ColumnId_TextureNickname = 298, ColumnId_PluginIndex = 299, ColumnId_TextureIndex = 300, ColumnId_LandMapLodIndex = 301, ColumnId_LandNormalsIndex = 302, ColumnId_LandHeightsIndex = 303, ColumnId_LandColoursIndex = 304, ColumnId_LandTexturesIndex = 305, ColumnId_RankName = 306, ColumnId_FactionRanks = 307, ColumnId_FactionPrimSkill = 308, ColumnId_FactionFavSkill = 309, ColumnId_FactionRep = 310, ColumnId_FactionAttrib1 = 311, ColumnId_FactionAttrib2 = 312, ColumnId_Persistent = 313, ColumnId_Blocked = 314, ColumnId_LevelledCreatureId = 315, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, ColumnId_UseValue2 = 0x10001, ColumnId_UseValue3 = 0x10002, ColumnId_UseValue4 = 0x10003, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of attributes. Note that this is not the number of different // attributes, but the number of attributes that can be references from a record. ColumnId_Attribute1 = 0x20000, ColumnId_Attribute2 = 0x20001, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of skills. Note that this is not the number of different // skills, but the number of skills that can be references from a record. ColumnId_MajorSkill1 = 0x30000, ColumnId_MajorSkill2 = 0x30001, ColumnId_MajorSkill3 = 0x30002, ColumnId_MajorSkill4 = 0x30003, ColumnId_MajorSkill5 = 0x30004, ColumnId_MinorSkill1 = 0x40000, ColumnId_MinorSkill2 = 0x40001, ColumnId_MinorSkill3 = 0x40002, ColumnId_MinorSkill4 = 0x40003, ColumnId_MinorSkill5 = 0x40004, ColumnId_Skill1 = 0x50000, ColumnId_Skill2 = 0x50001, ColumnId_Skill3 = 0x50002, ColumnId_Skill4 = 0x50003, ColumnId_Skill5 = 0x50004, ColumnId_Skill6 = 0x50005, ColumnId_Skill7 = 0x50006 }; std::string getName (ColumnId column); int getId (const std::string& name); ///< Will return -1 for an invalid name. bool hasEnums (ColumnId column); std::vector> getEnums (ColumnId column); ///< Returns an empty vector, if \a column isn't an enum type column. } } #endif openmw-openmw-0.48.0/apps/opencs/model/world/commanddispatcher.cpp000066400000000000000000000234631445372753700253160ustar00rootroot00000000000000#include "commanddispatcher.hpp" #include #include #include #include #include "../doc/document.hpp" #include "idtable.hpp" #include "record.hpp" #include "commands.hpp" #include "idtableproxymodel.hpp" #include "commandmacro.hpp" std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const { std::vector result; IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); for (std::vector::const_iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { int row = model.getModelIndex (*iter, 0).row(); // check record state RecordBase::State state = static_cast ( model.data (model.index (row, stateColumnIndex)).toInt()); if (state==RecordBase::State_Deleted) continue; // check other columns (only relevant for a subset of the tables) int dialogueTypeIndex = model.searchColumnIndex (Columns::ColumnId_DialogueType); if (dialogueTypeIndex!=-1) { int type = model.data (model.index (row, dialogueTypeIndex)).toInt(); if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) continue; } result.push_back (*iter); } return result; } std::vector CSMWorld::CommandDispatcher::getRevertableRecords() const { std::vector result; IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); /// \todo Reverting temporarily disabled on tables that support reordering, because /// revert logic currently can not handle reordering. if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic) return result; int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); for (std::vector::const_iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { int row = model.getModelIndex (*iter, 0).row(); // check record state RecordBase::State state = static_cast ( model.data (model.index (row, stateColumnIndex)).toInt()); if (state==RecordBase::State_BaseOnly) continue; result.push_back (*iter); } return result; } CSMWorld::CommandDispatcher::CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, QObject *parent) : QObject (parent), mLocked (false), mDocument (document), mId (id) {} void CSMWorld::CommandDispatcher::setEditLock (bool locked) { mLocked = locked; } void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (mSelection.begin(), mSelection.end()); } void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) { mExtendedTypes = types; } bool CSMWorld::CommandDispatcher::canDelete() const { if (mLocked) return false; return getDeletableRecords().size()!=0; } bool CSMWorld::CommandDispatcher::canRevert() const { if (mLocked) return false; return getRevertableRecords().size()!=0; } std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const { std::vector tables; if (mId==UniversalId::Type_Cells) { tables.push_back (mId); tables.emplace_back(UniversalId::Type_References); /// \todo add other cell-specific types } return tables; } void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_) { if (mLocked) return; std::unique_ptr modifyCell; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) { const float oldPosition = model->data (index).toFloat(); // Modulate by cell size, update cell id if reference has been moved to a new cell if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits)) - std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f) { IdTableProxyModel *proxy = dynamic_cast (model); int row = proxy ? proxy->mapToSource (index).row() : index.row(); // This is not guaranteed to be the same as \a model, since a proxy could be used. IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); if (cellColumn!=-1) { QModelIndex cellIndex = model2.index (row, cellColumn); std::string cellId = model2.data (cellIndex).toString().toUtf8().data(); if (cellId.find ('#')!=std::string::npos) { // Need to recalculate the cell modifyCell = std::make_unique(model2, row); } } } } auto modifyData = std::make_unique(*model, index, new_); if (modifyCell.get()) { CommandMacro macro (mDocument.getUndoStack()); macro.push (modifyData.release()); macro.push (modifyCell.release()); } else mDocument.getUndoStack().push (modifyData.release()); } void CSMWorld::CommandDispatcher::executeDelete() { if (mLocked) return; std::vector rows = getDeletableRecords(); if (rows.empty()) return; IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Delete multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). toString().toUtf8().constData(); if (mId.getType() == UniversalId::Type_Referenceables) { macro.push (new CSMWorld::DeleteCommand (model, id, static_cast(model.data (model.index ( model.getModelIndex (id, columnIndex).row(), model.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType))).toInt()))); } else mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); } } void CSMWorld::CommandDispatcher::executeRevert() { if (mLocked) return; std::vector rows = getRevertableRecords(); if (rows.empty()) return; IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Revert multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). toString().toUtf8().constData(); macro.push (new CSMWorld::RevertCommand (model, id)); } } void CSMWorld::CommandDispatcher::executeExtendedDelete() { CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended delete of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) { if (*iter==mId) executeDelete(); else if (*iter==UniversalId::Type_References) { IdTable& model = dynamic_cast ( *mDocument.getData().getTableModel (*iter)); const RefCollection& collection = mDocument.getData().getReferences(); int size = collection.getSize(); for (int i=size-1; i>=0; --i) { const Record& record = collection.getRecord (i); if (record.mState==RecordBase::State_Deleted) continue; if (!std::binary_search (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCase (record.get().mCell))) continue; macro.push (new CSMWorld::DeleteCommand (model, record.get().mId)); } } } } void CSMWorld::CommandDispatcher::executeExtendedRevert() { CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended revert of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) { if (*iter==mId) executeRevert(); else if (*iter==UniversalId::Type_References) { IdTable& model = dynamic_cast ( *mDocument.getData().getTableModel (*iter)); const RefCollection& collection = mDocument.getData().getReferences(); int size = collection.getSize(); for (int i=size-1; i>=0; --i) { const Record& record = collection.getRecord (i); if (!std::binary_search (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCase (record.get().mCell))) continue; macro.push (new CSMWorld::RevertCommand (model, record.get().mId)); } } } } openmw-openmw-0.48.0/apps/opencs/model/world/commanddispatcher.hpp000066400000000000000000000043321445372753700253150ustar00rootroot00000000000000#ifndef CSM_WOLRD_COMMANDDISPATCHER_H #define CSM_WOLRD_COMMANDDISPATCHER_H #include #include #include "universalid.hpp" class QModelIndex; class QAbstractItemModel; namespace CSMDoc { class Document; } namespace CSMWorld { class CommandDispatcher : public QObject { Q_OBJECT bool mLocked; CSMDoc::Document& mDocument; UniversalId mId; std::vector mSelection; std::vector mExtendedTypes; std::vector getDeletableRecords() const; std::vector getRevertableRecords() const; public: CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, QObject *parent = nullptr); ///< \param id ID of the table the commands should operate on primarily. void setEditLock (bool locked); void setSelection (const std::vector& selection); void setExtendedTypes (const std::vector& types); ///< Set record lists selected by the user for extended operations. bool canDelete() const; bool canRevert() const; /// Return IDs of the record collection that can also be affected when /// operating on the record collection this dispatcher is used for. /// /// \note The returned collection contains the ID of the record collection this /// dispatcher is used for. However if that record collection does not support /// the extended mode, the returned vector will be empty instead. std::vector getExtendedTypes() const; /// Add a modify command to the undo stack. /// /// \attention model must either be a model for the table operated on by this /// dispatcher or a proxy of it. void executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_); public slots: void executeDelete(); void executeRevert(); void executeExtendedDelete(); void executeExtendedRevert(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/commandmacro.cpp000066400000000000000000000010761445372753700242650ustar00rootroot00000000000000 #include "commandmacro.hpp" #include #include CSMWorld::CommandMacro::CommandMacro (QUndoStack& undoStack, const QString& description) : mUndoStack (undoStack), mDescription (description), mStarted (false) {} CSMWorld::CommandMacro::~CommandMacro() { if (mStarted) mUndoStack.endMacro(); } void CSMWorld::CommandMacro::push (QUndoCommand *command) { if (!mStarted) { mUndoStack.beginMacro (mDescription.isEmpty() ? command->text() : mDescription); mStarted = true; } mUndoStack.push (command); } openmw-openmw-0.48.0/apps/opencs/model/world/commandmacro.hpp000066400000000000000000000013341445372753700242670ustar00rootroot00000000000000#ifndef CSM_WOLRD_COMMANDMACRO_H #define CSM_WOLRD_COMMANDMACRO_H class QUndoStack; class QUndoCommand; #include namespace CSMWorld { class CommandMacro { QUndoStack& mUndoStack; QString mDescription; bool mStarted; /// not implemented CommandMacro (const CommandMacro&); /// not implemented CommandMacro& operator= (const CommandMacro&); public: /// If \a description is empty, the description of the first command is used. CommandMacro (QUndoStack& undoStack, const QString& description = ""); ~CommandMacro(); void push (QUndoCommand *command); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/commands.cpp000066400000000000000000000403531445372753700234270ustar00rootroot00000000000000#include "commands.hpp" #include #include #include #include #include #include #include "cellcoordinates.hpp" #include "idcollection.hpp" #include "idtable.hpp" #include "idtree.hpp" #include "nestedtablewrapper.hpp" #include "pathgrid.hpp" CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUndoCommand* parent) : QUndoCommand(parent) , mTable(table) , mId(id) , mOld(nullptr) , mChanged(false) { setText(("Touch " + mId).c_str()); } void CSMWorld::TouchCommand::redo() { mOld.reset(mTable.getRecord(mId).clone().get()); mChanged = mTable.touchRecord(mId); } void CSMWorld::TouchCommand::undo() { if (mChanged) { mTable.setRecord(mId, std::move(mOld)); mChanged = false; } } CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, QUndoCommand* parent) : QUndoCommand(parent) , mLands(landTable) , mLtexs(ltexTable) , mOldState(0) { setText("Copy land textures to current plugin"); } void CSMWorld::ImportLandTexturesCommand::redo() { int pluginColumn = mLands.findColumnIndex(Columns::ColumnId_PluginIndex); int oldPlugin = mLands.data(mLands.getModelIndex(getOriginId(), pluginColumn)).toInt(); // Original data int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); mOld = mLands.data(mLands.getModelIndex(getOriginId(), textureColumn)).value(); // Need to make a copy so the old values can be looked up DataType copy(mOld); // Perform touch/copy/etc... onRedo(); // Find all indices used std::unordered_set texIndices; for (int i = 0; i < mOld.size(); ++i) { // All indices are offset by 1 for a default texture if (mOld[i] > 0) texIndices.insert(mOld[i] - 1); } std::vector oldTextures; oldTextures.reserve(texIndices.size()); for (int index : texIndices) { oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index)); } // Import the textures, replace old values LandTextureIdTable::ImportResults results = dynamic_cast(mLtexs).importTextures(oldTextures); mCreatedTextures = std::move(results.createdRecords); for (const auto& it : results.recordMapping) { int plugin = 0, newIndex = 0, oldIndex = 0; LandTexture::parseUniqueRecordId(it.first, plugin, oldIndex); LandTexture::parseUniqueRecordId(it.second, plugin, newIndex); if (newIndex != oldIndex) { for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i) { // All indices are offset by 1 for a default texture if (mOld[i] == oldIndex + 1) copy[i] = newIndex + 1; } } } // Apply modification int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); mOldState = mLands.data(mLands.getModelIndex(getDestinationId(), stateColumn)).toInt(); QVariant variant; variant.setValue(copy); mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant); } void CSMWorld::ImportLandTexturesCommand::undo() { // Restore to previous int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); QVariant variant; variant.setValue(mOld); mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant); int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); mLands.setData(mLands.getModelIndex(getDestinationId(), stateColumn), mOldState); // Undo copy/touch/etc... onUndo(); for (const std::string& id : mCreatedTextures) { int row = mLtexs.getModelIndex(id, 0).row(); mLtexs.removeRows(row, 1); } mCreatedTextures.clear(); } CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin, const std::string& dest, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mOriginId(origin) , mDestId(dest) { } const std::string& CSMWorld::CopyLandTexturesCommand::getOriginId() const { return mOriginId; } const std::string& CSMWorld::CopyLandTexturesCommand::getDestinationId() const { return mDestId; } CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mId(id) , mOld(nullptr) , mChanged(false) { setText(("Touch " + mId).c_str()); } const std::string& CSMWorld::TouchLandCommand::getOriginId() const { return mId; } const std::string& CSMWorld::TouchLandCommand::getDestinationId() const { return mId; } void CSMWorld::TouchLandCommand::onRedo() { mChanged = mLands.touchRecord(mId); if (mChanged) mOld.reset(mLands.getRecord(mId).clone().get()); } void CSMWorld::TouchLandCommand::onUndo() { if (mChanged) { mLands.setRecord(mId, std::move(mOld)); mChanged = false; } } CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) { if (QAbstractProxyModel *proxy = dynamic_cast (mModel)) { // Replace proxy with actual model mIndex = proxy->mapToSource (mIndex); mModel = proxy->sourceModel(); } } void CSMWorld::ModifyCommand::redo() { if (mIndex.parent().isValid()) { CSMWorld::IdTree* tree = &dynamic_cast(*mModel); setText ("Modify " + tree->nestedHeaderData ( mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } else { setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } // Remember record state before the modification if (CSMWorld::IdTable *table = dynamic_cast(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); int rowIndex = mIndex.row(); if (mIndex.parent().isValid()) { rowIndex = mIndex.parent().row(); } mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } mOld = mModel->data (mIndex, Qt::EditRole); mModel->setData (mIndex, mNew); } void CSMWorld::ModifyCommand::undo() { mModel->setData (mIndex, mOld); if (mHasRecordState) { mModel->setData(mRecordStateIndex, mOldRecordState); } } void CSMWorld::CreateCommand::applyModifications() { if (!mNestedValues.empty()) { CSMWorld::IdTree* tree = &dynamic_cast(mModel); std::map >::const_iterator current = mNestedValues.begin(); std::map >::const_iterator end = mNestedValues.end(); for (; current != end; ++current) { QModelIndex index = tree->index(0, current->second.first, tree->getNestedModelIndex(mId, current->first)); tree->setData(index, current->second.second); } } } CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) { setText (("Create record " + id).c_str()); } void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) { mValues[column] = value; } void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant &value) { mNestedValues[parentColumn] = std::make_pair(nestedColumn, value); } void CSMWorld::CreateCommand::setType (UniversalId::Type type) { mType = type; } void CSMWorld::CreateCommand::redo() { mModel.addRecordWithData (mId, mValues, mType); applyModifications(); } void CSMWorld::CreateCommand::undo() { mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr) { setText (("Revert record " + id).c_str()); } CSMWorld::RevertCommand::~RevertCommand() { } void CSMWorld::RevertCommand::redo() { mOld = mModel.getRecord (mId).clone(); int column = mModel.findColumnIndex (Columns::ColumnId_Modification); QModelIndex index = mModel.getModelIndex (mId, column); RecordBase::State state = static_cast (mModel.data (index).toInt()); if (state==RecordBase::State_ModifiedOnly) { mModel.removeRows (index.row(), 1); } else { mModel.setData (index, static_cast (RecordBase::State_BaseOnly)); } } void CSMWorld::RevertCommand::undo() { mModel.setRecord (mId, std::move(mOld)); } CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr), mType(type) { setText (("Delete record " + id).c_str()); } CSMWorld::DeleteCommand::~DeleteCommand() { } void CSMWorld::DeleteCommand::redo() { mOld = mModel.getRecord (mId).clone(); int column = mModel.findColumnIndex (Columns::ColumnId_Modification); QModelIndex index = mModel.getModelIndex (mId, column); RecordBase::State state = static_cast (mModel.data (index).toInt()); if (state==RecordBase::State_ModifiedOnly) { mModel.removeRows (index.row(), 1); } else { mModel.setData (index, static_cast (RecordBase::State_Deleted)); } } void CSMWorld::DeleteCommand::undo() { mModel.setRecord (mId, std::move(mOld), mType); } CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector& newOrder) : mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder) {} void CSMWorld::ReorderRowsCommand::redo() { mModel.reorderRows (mBaseIndex, mNewOrder); } void CSMWorld::ReorderRowsCommand::undo() { int size = static_cast (mNewOrder.size()); std::vector reverse (size); for (int i=0; i< size; ++i) reverse.at (mNewOrder[i]) = i; mModel.reorderRows (mBaseIndex, reverse); } CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model, const std::string& idOrigin, const std::string& idDestination, const CSMWorld::UniversalId::Type type, QUndoCommand* parent) : CreateCommand (model, idDestination, parent), mIdOrigin (idOrigin) { setType (type); setText ( ("Clone record " + idOrigin + " to the " + idDestination).c_str()); } void CSMWorld::CloneCommand::redo() { mModel.cloneRecord (mIdOrigin, mId, mType); applyModifications(); for (auto& value : mOverrideValues) { mModel.setData(mModel.getModelIndex (mId, value.first), value.second); } } void CSMWorld::CloneCommand::undo() { mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) { mOverrideValues.emplace_back(std::make_pair(column, value)); } CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) : CreateCommand(model, id, parent) { setType(UniversalId::Type_Pathgrid); } void CSMWorld::CreatePathgridCommand::redo() { CreateCommand::redo(); std::unique_ptr > record = std::make_unique >(static_cast& >(mModel.getRecord(mId))); record->get().blank(); record->get().mCell = mId; std::pair coords = CellCoordinates::fromId(mId); if (coords.second) { record->get().mData.mX = coords.first.getX(); record->get().mData.mY = coords.first.getY(); } mModel.setRecord(mId, std::move(record), mType); } CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) : QUndoCommand (parent), mModel (model), mRow (row) { setText ("Update cell ID"); } void CSMWorld::UpdateCellCommand::redo() { if (!mNew.isValid()) { int cellColumn = mModel.searchColumnIndex (Columns::ColumnId_Cell); mIndex = mModel.index (mRow, cellColumn); QModelIndex xIndex = mModel.index ( mRow, mModel.findColumnIndex (Columns::ColumnId_PositionXPos)); QModelIndex yIndex = mModel.index ( mRow, mModel.findColumnIndex (Columns::ColumnId_PositionYPos)); int x = std::floor (mModel.data (xIndex).toFloat() / Constants::CellSizeInUnits); int y = std::floor (mModel.data (yIndex).toFloat() / Constants::CellSizeInUnits); std::ostringstream stream; stream << "#" << x << " " << y; mNew = QString::fromUtf8 (stream.str().c_str()); } mModel.setData (mIndex, mNew); } void CSMWorld::UpdateCellCommand::undo() { mModel.setData (mIndex, mOld); } CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) : QUndoCommand(parent), NestedTableStoring(model, id, parentColumn), mModel(model), mId(id), mParentColumn(parentColumn), mNestedRow(nestedRow) { std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Delete row in " + title + " sub-table of " + mId).c_str()); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::DeleteNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand->redo(); mModel.removeRows (mNestedRow, 1, parentIndex); } void CSMWorld::DeleteNestedCommand::undo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); mModifyParentCommand->undo(); } CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) : QUndoCommand(parent), NestedTableStoring(model, id, parentColumn), mModel(model), mId(id), mNewRow(nestedRow), mParentColumn(parentColumn) { std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Add row in " + title + " sub-table of " + mId).c_str()); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::AddNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand->redo(); mModel.addNestedRow (parentIndex, mNewRow); } void CSMWorld::AddNestedCommand::undo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); mModifyParentCommand->undo(); } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) : mOld(model.nestedTable(model.getModelIndex(id, parentColumn))) {} CSMWorld::NestedTableStoring::~NestedTableStoring() { delete mOld; } const CSMWorld::NestedTableWrapperBase& CSMWorld::NestedTableStoring::getOld() const { return *mOld; } openmw-openmw-0.48.0/apps/opencs/model/world/commands.hpp000066400000000000000000000225601445372753700234340ustar00rootroot00000000000000#ifndef CSM_WOLRD_COMMANDS_H #define CSM_WOLRD_COMMANDS_H #include "record.hpp" #include #include #include #include #include #include #include #include "columnimp.hpp" #include "universalid.hpp" #include "nestedtablewrapper.hpp" class QAbstractItemModel; namespace CSMWorld { class IdTable; class IdTree; struct RecordBase; struct NestedTableWrapperBase; class TouchCommand : public QUndoCommand { public: TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent=nullptr); void redo() override; void undo() override; private: IdTable& mTable; std::string mId; std::unique_ptr mOld; bool mChanged; }; /// \brief Adds LandTexture records and modifies texture indices as needed. /// /// LandTexture records are different from other types of records, because /// they only effect the current plugin. Thus, when modifying or copying /// a Land record, all of the LandTexture records referenced need to be /// added to the current plugin. Since these newly added LandTextures could /// have indices that conflict with pre-existing LandTextures in the current /// plugin, the indices might have to be changed, both for the newly added /// LandRecord and within the Land record. class ImportLandTexturesCommand : public QUndoCommand { public: ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, QUndoCommand* parent); void redo() override; void undo() override; protected: using DataType = LandTexturesColumn::DataType; virtual const std::string& getOriginId() const = 0; virtual const std::string& getDestinationId() const = 0; virtual void onRedo() = 0; virtual void onUndo() = 0; IdTable& mLands; IdTable& mLtexs; DataType mOld; int mOldState; std::vector mCreatedTextures; }; /// \brief This command is used to fix LandTexture records and texture /// indices after cloning a Land. See ImportLandTexturesCommand for /// details. class CopyLandTexturesCommand : public ImportLandTexturesCommand { public: CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin, const std::string& dest, QUndoCommand* parent = nullptr); private: const std::string& getOriginId() const override; const std::string& getDestinationId() const override; void onRedo() override {} void onUndo() override {} std::string mOriginId; std::string mDestId; }; /// \brief This command brings a land record into the current plugin, adding /// LandTexture records and modifying texture indices as needed. /// \note See ImportLandTextures for more details. class TouchLandCommand : public ImportLandTexturesCommand { public: TouchLandCommand(IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent = nullptr); private: const std::string& getOriginId() const override; const std::string& getDestinationId() const override; void onRedo() override; void onUndo() override; std::string mId; std::unique_ptr mOld; bool mChanged; }; class ModifyCommand : public QUndoCommand { QAbstractItemModel *mModel; QModelIndex mIndex; QVariant mNew; QVariant mOld; bool mHasRecordState; QModelIndex mRecordStateIndex; CSMWorld::RecordBase::State mOldRecordState; public: ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand *parent = nullptr); void redo() override; void undo() override; }; class CreateCommand : public QUndoCommand { std::map mValues; std::map > mNestedValues; ///< Parameter order: a parent column, a nested column, a data. ///< A nested row has index of 0. protected: IdTable& mModel; std::string mId; UniversalId::Type mType; protected: /// Apply modifications set via addValue. void applyModifications(); public: CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); void setType (UniversalId::Type type); void addValue (int column, const QVariant& value); void addNestedValue(int parentColumn, int nestedColumn, const QVariant &value); void redo() override; void undo() override; }; class CloneCommand : public CreateCommand { std::string mIdOrigin; std::vector> mOverrideValues; public: CloneCommand (IdTable& model, const std::string& idOrigin, const std::string& IdDestination, const UniversalId::Type type, QUndoCommand* parent = nullptr); void redo() override; void undo() override; void setOverrideValue(int column, QVariant value); }; class RevertCommand : public QUndoCommand { IdTable& mModel; std::string mId; std::unique_ptr mOld; // not implemented RevertCommand (const RevertCommand&); RevertCommand& operator= (const RevertCommand&); public: RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); virtual ~RevertCommand(); void redo() override; void undo() override; }; class DeleteCommand : public QUndoCommand { IdTable& mModel; std::string mId; std::unique_ptr mOld; UniversalId::Type mType; // not implemented DeleteCommand (const DeleteCommand&); DeleteCommand& operator= (const DeleteCommand&); public: DeleteCommand (IdTable& model, const std::string& id, UniversalId::Type type = UniversalId::Type_None, QUndoCommand *parent = nullptr); virtual ~DeleteCommand(); void redo() override; void undo() override; }; class ReorderRowsCommand : public QUndoCommand { IdTable& mModel; int mBaseIndex; std::vector mNewOrder; public: ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector& newOrder); void redo() override; void undo() override; }; class CreatePathgridCommand : public CreateCommand { public: CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); void redo() override; }; /// \brief Update cell ID according to x/y-coordinates /// /// \note The new value will be calculated in the first call to redo instead of the /// constructor to accommodate multiple coordinate-affecting commands being executed /// in a macro. class UpdateCellCommand : public QUndoCommand { IdTable& mModel; int mRow; QModelIndex mIndex; QVariant mNew; // invalid, if new cell ID has not been calculated yet QVariant mOld; public: UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent = nullptr); void redo() override; void undo() override; }; class NestedTableStoring { NestedTableWrapperBase* mOld; public: NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn); ~NestedTableStoring(); protected: const NestedTableWrapperBase& getOld() const; }; class DeleteNestedCommand : public QUndoCommand, private NestedTableStoring { IdTree& mModel; std::string mId; int mParentColumn; int mNestedRow; // The command to redo/undo the Modified status of a record ModifyCommand *mModifyParentCommand; public: DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); void redo() override; void undo() override; }; class AddNestedCommand : public QUndoCommand, private NestedTableStoring { IdTree& mModel; std::string mId; int mNewRow; int mParentColumn; // The command to redo/undo the Modified status of a record ModifyCommand *mModifyParentCommand; public: AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); void redo() override; void undo() override; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/data.cpp000066400000000000000000001636221445372753700225440ustar00rootroot00000000000000#include "data.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" #include "regionmap.hpp" #include "columns.hpp" #include "resourcesmanager.hpp" #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { mModels.push_back (model); mModelIndex.insert (std::make_pair (type, model)); UniversalId::Type type2 = UniversalId::getParentType (type); if (type2!=UniversalId::Type_None) mModelIndex.insert (std::make_pair (type2, model)); if (update) { connect (model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&))); connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (rowsChanged (const QModelIndex&, int, int))); connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (rowsChanged (const QModelIndex&, int, int))); } } void CSMWorld::Data::appendIds (std::vector& ids, const CollectionBase& collection, bool listDeleted) { std::vector ids2 = collection.getIds (listDeleted); ids.insert (ids.end(), ids2.begin(), ids2.end()); } int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collection) { int number = 0; for (int i=0; i& archives, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mReader (nullptr), mDialogue (nullptr), mReaderIndex(1), mFsStrict(fsStrict), mDataPaths(dataPaths), mArchives(archives) { mVFS = std::make_unique(mFsStrict); VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); mResourcesManager.setVFS(mVFS.get()); mResourceSystem = std::make_unique(mVFS.get()); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); defines["forcePPL"] = "0"; // Don't force per-pixel lighting defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; defines["refraction_enabled"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); int index = 0; mGlobals.addColumn (new StringIdColumn); mGlobals.addColumn (new RecordStateColumn); mGlobals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Global)); mGlobals.addColumn (new VarTypeColumn (ColumnBase::Display_GlobalVarType)); mGlobals.addColumn (new VarValueColumn); mGmsts.addColumn (new StringIdColumn); mGmsts.addColumn (new RecordStateColumn); mGmsts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Gmst)); mGmsts.addColumn (new VarTypeColumn (ColumnBase::Display_GmstVarType)); mGmsts.addColumn (new VarValueColumn); mSkills.addColumn (new StringIdColumn); mSkills.addColumn (new RecordStateColumn); mSkills.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Skill)); mSkills.addColumn (new AttributeColumn); mSkills.addColumn (new SpecialisationColumn); for (int i=0; i<4; ++i) mSkills.addColumn (new UseValueColumn (i)); mSkills.addColumn (new DescriptionColumn); mClasses.addColumn (new StringIdColumn); mClasses.addColumn (new RecordStateColumn); mClasses.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Class)); mClasses.addColumn (new NameColumn); mClasses.addColumn (new AttributesColumn (0)); mClasses.addColumn (new AttributesColumn (1)); mClasses.addColumn (new SpecialisationColumn); for (int i=0; i<5; ++i) mClasses.addColumn (new SkillsColumn (i, true, true)); for (int i=0; i<5; ++i) mClasses.addColumn (new SkillsColumn (i, true, false)); mClasses.addColumn (new PlayableColumn); mClasses.addColumn (new DescriptionColumn); mFactions.addColumn (new StringIdColumn); mFactions.addColumn (new RecordStateColumn); mFactions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Faction)); mFactions.addColumn (new NameColumn(ColumnBase::Display_String32)); mFactions.addColumn (new AttributesColumn (0)); mFactions.addColumn (new AttributesColumn (1)); mFactions.addColumn (new HiddenColumn); for (int i=0; i<7; ++i) mFactions.addColumn (new SkillsColumn (i)); // Faction Reactions mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionReactions)); index = mFactions.getColumns()-1; mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionReactionsAdapter ())); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionReaction, ColumnBase::Display_Integer)); // Faction Ranks mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionRanks)); index = mFactions.getColumns()-1; mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionRanksAdapter ())); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RankName, ColumnBase::Display_Rank)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionAttrib1, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionAttrib2, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionPrimSkill, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionFavSkill, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionRep, ColumnBase::Display_Integer)); mRaces.addColumn (new StringIdColumn); mRaces.addColumn (new RecordStateColumn); mRaces.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Race)); mRaces.addColumn (new NameColumn); mRaces.addColumn (new DescriptionColumn); mRaces.addColumn (new FlagColumn (Columns::ColumnId_Playable, 0x1)); mRaces.addColumn (new FlagColumn (Columns::ColumnId_BeastRace, 0x2)); mRaces.addColumn (new WeightHeightColumn (true, true)); mRaces.addColumn (new WeightHeightColumn (true, false)); mRaces.addColumn (new WeightHeightColumn (false, true)); mRaces.addColumn (new WeightHeightColumn (false, false)); // Race spells mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_PowerList)); index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new SpellListAdapter ())); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); mSounds.addColumn (new StringIdColumn); mSounds.addColumn (new RecordStateColumn); mSounds.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Sound)); mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_Volume)); mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_MinRange)); mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_MaxRange)); mSounds.addColumn (new SoundFileColumn); mScripts.addColumn (new StringIdColumn); mScripts.addColumn (new RecordStateColumn); mScripts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Script)); mScripts.addColumn (new ScriptColumn (ScriptColumn::Type_File)); mRegions.addColumn (new StringIdColumn); mRegions.addColumn (new RecordStateColumn); mRegions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Region)); mRegions.addColumn (new NameColumn); mRegions.addColumn (new MapColourColumn); mRegions.addColumn (new SleepListColumn); // Region Weather mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionWeather)); index = mRegions.getColumns()-1; mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter ())); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionSounds)); index = mRegions.getColumns()-1; mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionSoundListAdapter ())); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); mBirthsigns.addColumn (new StringIdColumn); mBirthsigns.addColumn (new RecordStateColumn); mBirthsigns.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Birthsign)); mBirthsigns.addColumn (new NameColumn); mBirthsigns.addColumn (new TextureColumn); mBirthsigns.addColumn (new DescriptionColumn); // Birthsign spells mBirthsigns.addColumn (new NestedParentColumn (Columns::ColumnId_PowerList)); index = mBirthsigns.getColumns()-1; mBirthsigns.addAdapter (std::make_pair(&mBirthsigns.getColumn(index), new SpellListAdapter ())); mBirthsigns.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); mSpells.addColumn (new StringIdColumn); mSpells.addColumn (new RecordStateColumn); mSpells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Spell)); mSpells.addColumn (new NameColumn); mSpells.addColumn (new SpellTypeColumn); mSpells.addColumn (new CostColumn); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, 0x1)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AlwaysSucceeds, 0x4)); // Spell effects mSpells.addColumn (new NestedParentColumn (Columns::ColumnId_EffectList)); index = mSpells.getColumns()-1; mSpells.addAdapter (std::make_pair(&mSpells.getColumn(index), new EffectsListAdapter ())); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); mTopics.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Topic)); mTopics.addColumn (new DialogueTypeColumn); mJournals.addColumn (new StringIdColumn); mJournals.addColumn (new RecordStateColumn); mJournals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); mJournals.addColumn (new DialogueTypeColumn (true)); mTopicInfos.addColumn (new StringIdColumn (true)); mTopicInfos.addColumn (new RecordStateColumn); mTopicInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_TopicInfo)); mTopicInfos.addColumn (new TopicColumn (false)); mTopicInfos.addColumn (new ActorColumn); mTopicInfos.addColumn (new RaceColumn); mTopicInfos.addColumn (new ClassColumn); mTopicInfos.addColumn (new FactionColumn); mTopicInfos.addColumn (new CellColumn); mTopicInfos.addColumn (new DispositionColumn); mTopicInfos.addColumn (new RankColumn); mTopicInfos.addColumn (new GenderColumn); mTopicInfos.addColumn (new PcFactionColumn); mTopicInfos.addColumn (new PcRankColumn); mTopicInfos.addColumn (new SoundFileColumn); mTopicInfos.addColumn (new ResponseColumn); // Result script mTopicInfos.addColumn (new NestedParentColumn (Columns::ColumnId_InfoList, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); index = mTopicInfos.getColumns()-1; mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoListAdapter ())); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_ScriptText, ColumnBase::Display_ScriptLines)); // Special conditions mTopicInfos.addColumn (new NestedParentColumn (Columns::ColumnId_InfoCondition)); index = mTopicInfos.getColumns()-1; mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoConditionAdapter ())); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Value, ColumnBase::Display_Var)); mJournalInfos.addColumn (new StringIdColumn (true)); mJournalInfos.addColumn (new RecordStateColumn); mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_JournalInfo)); mJournalInfos.addColumn (new TopicColumn (true)); mJournalInfos.addColumn (new QuestStatusTypeColumn); mJournalInfos.addColumn (new QuestIndexColumn); mJournalInfos.addColumn (new QuestDescriptionColumn); mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); mCells.addColumn (new NameColumn(ColumnBase::Display_String64)); mCells.addColumn (new FlagColumn (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorSky, ESM::Cell::QuasiEx, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mCells.addColumn (new RegionColumn); mCells.addColumn (new RefNumCounterColumn); // Misc Cell data mCells.addColumn (new NestedParentColumn (Columns::ColumnId_Cell, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); index = mCells.getColumns()-1; mCells.addAdapter (std::make_pair(&mCells.getColumn(index), new CellListAdapter ())); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Interior, ColumnBase::Display_Boolean, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Ambient, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Sunlight, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Fog, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FogDensity, ColumnBase::Display_Float)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_WaterLevel, ColumnBase::Display_Float)); mCells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_MapColor, ColumnBase::Display_Colour)); mEnchantments.addColumn (new StringIdColumn); mEnchantments.addColumn (new RecordStateColumn); mEnchantments.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Enchantment)); mEnchantments.addColumn (new EnchantmentTypeColumn); mEnchantments.addColumn (new CostColumn); mEnchantments.addColumn (new ChargesColumn2); mEnchantments.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, ESM::Enchantment::Autocalc)); // Enchantment effects mEnchantments.addColumn (new NestedParentColumn (Columns::ColumnId_EffectList)); index = mEnchantments.getColumns()-1; mEnchantments.addAdapter (std::make_pair(&mEnchantments.getColumn(index), new EffectsListAdapter ())); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mBodyParts.addColumn (new StringIdColumn); mBodyParts.addColumn (new RecordStateColumn); mBodyParts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_BodyPart)); mBodyParts.addColumn (new BodyPartTypeColumn); mBodyParts.addColumn (new VampireColumn); mBodyParts.addColumn(new GenderNpcColumn); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); mBodyParts.addColumn (meshTypeColumn); mBodyParts.addColumn (new ModelColumn); mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); mSoundGens.addColumn (new StringIdColumn); mSoundGens.addColumn (new RecordStateColumn); mSoundGens.addColumn (new FixedRecordTypeColumn (UniversalId::Type_SoundGen)); mSoundGens.addColumn (new CreatureColumn); mSoundGens.addColumn (new SoundColumn); mSoundGens.addColumn (new SoundGeneratorTypeColumn); mMagicEffects.addColumn (new StringIdColumn); mMagicEffects.addColumn (new RecordStateColumn); mMagicEffects.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MagicEffect)); mMagicEffects.addColumn (new SchoolColumn); mMagicEffects.addColumn (new BaseCostColumn); mMagicEffects.addColumn (new EffectTextureColumn (Columns::ColumnId_Icon)); mMagicEffects.addColumn (new EffectTextureColumn (Columns::ColumnId_Particle)); mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_CastingObject)); mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_HitObject)); mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_AreaObject)); mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_BoltObject)); mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_CastingSound)); mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_HitSound)); mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_AreaSound)); mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_BoltSound)); mMagicEffects.addColumn (new FlagColumn ( Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); mMagicEffects.addColumn (new FlagColumn ( Columns::ColumnId_AllowEnchanting, ESM::MagicEffect::AllowEnchanting)); mMagicEffects.addColumn (new FlagColumn ( Columns::ColumnId_NegativeLight, ESM::MagicEffect::NegativeLight)); mMagicEffects.addColumn (new DescriptionColumn); mLand.addColumn (new StringIdColumn); mLand.addColumn (new RecordStateColumn); mLand.addColumn (new FixedRecordTypeColumn(UniversalId::Type_Land)); mLand.addColumn (new LandPluginIndexColumn); mLand.addColumn (new LandNormalsColumn); mLand.addColumn (new LandHeightsColumn); mLand.addColumn (new LandColoursColumn); mLand.addColumn (new LandTexturesColumn); mLandTextures.addColumn (new StringIdColumn(true)); mLandTextures.addColumn (new RecordStateColumn); mLandTextures.addColumn (new FixedRecordTypeColumn(UniversalId::Type_LandTexture)); mLandTextures.addColumn (new LandTextureNicknameColumn); mLandTextures.addColumn (new LandTexturePluginIndexColumn); mLandTextures.addColumn (new LandTextureIndexColumn); mLandTextures.addColumn (new TextureColumn); mPathgrids.addColumn (new StringIdColumn); mPathgrids.addColumn (new RecordStateColumn); mPathgrids.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Pathgrid)); // new object deleted in dtor of Collection mPathgrids.addColumn (new NestedParentColumn (Columns::ColumnId_PathgridPoints)); index = mPathgrids.getColumns()-1; // new object deleted in dtor of NestedCollection mPathgrids.addAdapter (std::make_pair(&mPathgrids.getColumn(index), new PathgridPointListAdapter ())); // new objects deleted in dtor of NestableColumn // WARNING: The order of the columns below are assumed in PathgridPointListAdapter mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridIndex, ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue, false)); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridPosX, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridPosY, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridPosZ, ColumnBase::Display_Integer)); mPathgrids.addColumn (new NestedParentColumn (Columns::ColumnId_PathgridEdges)); index = mPathgrids.getColumns()-1; mPathgrids.addAdapter (std::make_pair(&mPathgrids.getColumn(index), new PathgridEdgeListAdapter ())); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridEdgeIndex, ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue, false)); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridEdge0, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_PathgridEdge1, ColumnBase::Display_Integer)); mStartScripts.addColumn (new StringIdColumn); mStartScripts.addColumn (new RecordStateColumn); mStartScripts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_StartScript)); mRefs.addColumn (new StringIdColumn (true)); mRefs.addColumn (new RecordStateColumn); mRefs.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Reference)); mRefs.addColumn (new CellColumn (true)); mRefs.addColumn (new OriginalCellColumn); mRefs.addColumn (new IdColumn); mRefs.addColumn (new PosColumn (&CellRef::mPos, 0, false)); mRefs.addColumn (new PosColumn (&CellRef::mPos, 1, false)); mRefs.addColumn (new PosColumn (&CellRef::mPos, 2, false)); mRefs.addColumn (new RotColumn (&CellRef::mPos, 0, false)); mRefs.addColumn (new RotColumn (&CellRef::mPos, 1, false)); mRefs.addColumn (new RotColumn (&CellRef::mPos, 2, false)); mRefs.addColumn (new ScaleColumn); mRefs.addColumn (new OwnerColumn); mRefs.addColumn (new SoulColumn); mRefs.addColumn (new FactionColumn); mRefs.addColumn (new FactionIndexColumn); mRefs.addColumn (new ChargesColumn); mRefs.addColumn (new EnchantmentChargesColumn); mRefs.addColumn (new GoldValueColumn); mRefs.addColumn (new TeleportColumn); mRefs.addColumn (new TeleportCellColumn); mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 0, true)); mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 1, true)); mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 2, true)); mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 0, true)); mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 1, true)); mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 2, true)); mRefs.addColumn (new LockLevelColumn); mRefs.addColumn (new KeyColumn); mRefs.addColumn (new TrapColumn); mRefs.addColumn (new OwnerGlobalColumn); mRefs.addColumn (new RefNumColumn); mFilters.addColumn (new StringIdColumn); mFilters.addColumn (new RecordStateColumn); mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); mFilters.addColumn (new FilterColumn); mFilters.addColumn (new DescriptionColumn); mDebugProfiles.addColumn (new StringIdColumn); mDebugProfiles.addColumn (new RecordStateColumn); mDebugProfiles.addColumn (new FixedRecordTypeColumn (UniversalId::Type_DebugProfile)); mDebugProfiles.addColumn (new FlagColumn2 ( Columns::ColumnId_DefaultProfile, ESM::DebugProfile::Flag_Default)); mDebugProfiles.addColumn (new FlagColumn2 ( Columns::ColumnId_BypassNewGame, ESM::DebugProfile::Flag_BypassNewGame)); mDebugProfiles.addColumn (new FlagColumn2 ( Columns::ColumnId_GlobalProfile, ESM::DebugProfile::Flag_Global)); mDebugProfiles.addColumn (new DescriptionColumn); mDebugProfiles.addColumn (new ScriptColumn ( ScriptColumn::Type_Lines)); mMetaData.appendBlankRecord ("sys::meta"); mMetaData.addColumn (new StringIdColumn (true)); mMetaData.addColumn (new RecordStateColumn); mMetaData.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MetaData)); mMetaData.addColumn (new FormatColumn); mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); addModel (new IdTable (&mClasses), UniversalId::Type_Class); addModel (new IdTree (&mFactions, &mFactions), UniversalId::Type_Faction); addModel (new IdTree (&mRaces, &mRaces), UniversalId::Type_Race); addModel (new IdTable (&mSounds), UniversalId::Type_Sound); addModel (new IdTable (&mScripts), UniversalId::Type_Script); addModel (new IdTree (&mRegions, &mRegions), UniversalId::Type_Region); addModel (new IdTree (&mBirthsigns, &mBirthsigns), UniversalId::Type_Birthsign); addModel (new IdTree (&mSpells, &mSpells), UniversalId::Type_Spell); addModel (new IdTable (&mTopics), UniversalId::Type_Topic); addModel (new IdTable (&mJournals), UniversalId::Type_Journal); addModel (new IdTree (&mTopicInfos, &mTopicInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_TopicInfo); addModel (new IdTable (&mJournalInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_JournalInfo); addModel (new IdTree (&mCells, &mCells, IdTable::Feature_ViewId), UniversalId::Type_Cell); addModel (new IdTree (&mEnchantments, &mEnchantments), UniversalId::Type_Enchantment); addModel (new IdTable (&mBodyParts), UniversalId::Type_BodyPart); addModel (new IdTable (&mSoundGens), UniversalId::Type_SoundGen); addModel (new IdTable (&mMagicEffects), UniversalId::Type_MagicEffect); addModel (new IdTable (&mLand, IdTable::Feature_AllowTouch), UniversalId::Type_Land); addModel (new LandTextureIdTable (&mLandTextures), UniversalId::Type_LandTexture); addModel (new IdTree (&mPathgrids, &mPathgrids), UniversalId::Type_Pathgrid); addModel (new IdTable (&mStartScripts), UniversalId::Type_StartScript); addModel (new IdTree (&mReferenceables, &mReferenceables, IdTable::Feature_Preview), UniversalId::Type_Referenceable); addModel (new IdTable (&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); addModel (new IdTable (&mFilters), UniversalId::Type_Filter); addModel (new IdTable (&mDebugProfiles), UniversalId::Type_DebugProfile); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Meshes)), UniversalId::Type_Mesh); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Icons)), UniversalId::Type_Icon); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Musics)), UniversalId::Type_Music); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_SoundsRes)), UniversalId::Type_SoundRes); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Textures)), UniversalId::Type_Texture); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); mActorAdapter = std::make_unique(*this); mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; delete mReader; } std::shared_ptr CSMWorld::Data::getResourceSystem() { return mResourceSystem; } std::shared_ptr CSMWorld::Data::getResourceSystem() const { return mResourceSystem; } const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const { return mGlobals; } CSMWorld::IdCollection& CSMWorld::Data::getGlobals() { return mGlobals; } const CSMWorld::IdCollection& CSMWorld::Data::getGmsts() const { return mGmsts; } CSMWorld::IdCollection& CSMWorld::Data::getGmsts() { return mGmsts; } const CSMWorld::IdCollection& CSMWorld::Data::getSkills() const { return mSkills; } CSMWorld::IdCollection& CSMWorld::Data::getSkills() { return mSkills; } const CSMWorld::IdCollection& CSMWorld::Data::getClasses() const { return mClasses; } CSMWorld::IdCollection& CSMWorld::Data::getClasses() { return mClasses; } const CSMWorld::IdCollection& CSMWorld::Data::getFactions() const { return mFactions; } CSMWorld::IdCollection& CSMWorld::Data::getFactions() { return mFactions; } const CSMWorld::IdCollection& CSMWorld::Data::getRaces() const { return mRaces; } CSMWorld::IdCollection& CSMWorld::Data::getRaces() { return mRaces; } const CSMWorld::IdCollection& CSMWorld::Data::getSounds() const { return mSounds; } CSMWorld::IdCollection& CSMWorld::Data::getSounds() { return mSounds; } const CSMWorld::IdCollection& CSMWorld::Data::getScripts() const { return mScripts; } CSMWorld::IdCollection& CSMWorld::Data::getScripts() { return mScripts; } const CSMWorld::IdCollection& CSMWorld::Data::getRegions() const { return mRegions; } CSMWorld::IdCollection& CSMWorld::Data::getRegions() { return mRegions; } const CSMWorld::IdCollection& CSMWorld::Data::getBirthsigns() const { return mBirthsigns; } CSMWorld::IdCollection& CSMWorld::Data::getBirthsigns() { return mBirthsigns; } const CSMWorld::IdCollection& CSMWorld::Data::getSpells() const { return mSpells; } CSMWorld::IdCollection& CSMWorld::Data::getSpells() { return mSpells; } const CSMWorld::IdCollection& CSMWorld::Data::getTopics() const { return mTopics; } CSMWorld::IdCollection& CSMWorld::Data::getTopics() { return mTopics; } const CSMWorld::IdCollection& CSMWorld::Data::getJournals() const { return mJournals; } CSMWorld::IdCollection& CSMWorld::Data::getJournals() { return mJournals; } const CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() const { return mTopicInfos; } CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() { return mTopicInfos; } const CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() const { return mJournalInfos; } CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() { return mJournalInfos; } const CSMWorld::IdCollection& CSMWorld::Data::getCells() const { return mCells; } CSMWorld::IdCollection& CSMWorld::Data::getCells() { return mCells; } const CSMWorld::RefIdCollection& CSMWorld::Data::getReferenceables() const { return mReferenceables; } CSMWorld::RefIdCollection& CSMWorld::Data::getReferenceables() { return mReferenceables; } const CSMWorld::RefCollection& CSMWorld::Data::getReferences() const { return mRefs; } CSMWorld::RefCollection& CSMWorld::Data::getReferences() { return mRefs; } const CSMWorld::IdCollection& CSMWorld::Data::getFilters() const { return mFilters; } CSMWorld::IdCollection& CSMWorld::Data::getFilters() { return mFilters; } const CSMWorld::IdCollection& CSMWorld::Data::getEnchantments() const { return mEnchantments; } CSMWorld::IdCollection& CSMWorld::Data::getEnchantments() { return mEnchantments; } const CSMWorld::IdCollection& CSMWorld::Data::getBodyParts() const { return mBodyParts; } CSMWorld::IdCollection& CSMWorld::Data::getBodyParts() { return mBodyParts; } const CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() const { return mDebugProfiles; } CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() { return mDebugProfiles; } const CSMWorld::IdCollection& CSMWorld::Data::getLand() const { return mLand; } CSMWorld::IdCollection& CSMWorld::Data::getLand() { return mLand; } const CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() const { return mLandTextures; } CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() { return mLandTextures; } const CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() const { return mSoundGens; } CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() { return mSoundGens; } const CSMWorld::IdCollection& CSMWorld::Data::getMagicEffects() const { return mMagicEffects; } CSMWorld::IdCollection& CSMWorld::Data::getMagicEffects() { return mMagicEffects; } const CSMWorld::SubCellCollection& CSMWorld::Data::getPathgrids() const { return mPathgrids; } CSMWorld::SubCellCollection& CSMWorld::Data::getPathgrids() { return mPathgrids; } const CSMWorld::IdCollection& CSMWorld::Data::getStartScripts() const { return mStartScripts; } CSMWorld::IdCollection& CSMWorld::Data::getStartScripts() { return mStartScripts; } const CSMWorld::Resources& CSMWorld::Data::getResources (const UniversalId& id) const { return mResourcesManager.get (id.getType()); } const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const { return mMetaData.getRecord (0).get(); } void CSMWorld::Data::setMetaData (const MetaData& metaData) { mMetaData.setRecord (0, std::make_unique >( Record(RecordBase::State_ModifiedOnly, nullptr, &metaData))); } QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); if (iter==mModelIndex.end()) { // try creating missing (secondary) tables on the fly // // Note: We create these tables here so we don't have to deal with them during load/initial // construction of the ESX data where no update signals are available. if (id.getType()==UniversalId::Type_RegionMap) { RegionMap *table = nullptr; addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, false); return table; } throw std::logic_error ("No table model available for " + id.toString()); } return iter->second; } const CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() const { return mActorAdapter.get(); } CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() { return mActorAdapter.get(); } void CSMWorld::Data::merge() { mGlobals.merge(); } int CSMWorld::Data::getTotalRecords (const std::vector& files) { int records = 0; std::unique_ptr reader = std::make_unique(); for (unsigned int i = 0; i < files.size(); ++i) { if (!boost::filesystem::exists(files[i])) continue; reader->open(files[i].string()); records += reader->getRecordCount(); reader->close(); } return records; } int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project) { // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading std::shared_ptr ptr(mReader); mReaders.push_back(ptr); mReader = nullptr; mDialogue = nullptr; mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); mReader->setIndex((project || !base) ? 0 : mReaderIndex++); mReader->open (path.string()); mContentFileNames.insert(std::make_pair(path.filename().string(), mReader->getIndex())); mBase = base; mProject = project; if (!mProject && !mBase) { MetaData metaData; metaData.mId = "sys::meta"; metaData.load (*mReader); mMetaData.setRecord (0, std::make_unique >( Record (RecordBase::State_ModifiedOnly, nullptr, &metaData))); } return mReader->getRecordCount(); } void CSMWorld::Data::loadFallbackEntries() { // Load default marker definitions, if game files do not have them for some reason std::pair staticMarkers[] = { std::make_pair("DivineMarker", "marker_divine.nif"), std::make_pair("DoorMarker", "marker_arrow.nif"), std::make_pair("NorthMarker", "marker_north.nif"), std::make_pair("TempleMarker", "marker_temple.nif"), std::make_pair("TravelMarker", "marker_travel.nif") }; std::pair doorMarkers[] = { std::make_pair("PrisonMarker", "marker_prison.nif") }; for (const auto &marker : staticMarkers) { if (mReferenceables.searchId (marker.first)==-1) { ESM::Static newMarker; newMarker.mId = marker.first; newMarker.mModel = marker.second; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); record->mBase = newMarker; record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (std::move(record), CSMWorld::UniversalId::Type_Static); } } for (const auto &marker : doorMarkers) { if (mReferenceables.searchId (marker.first)==-1) { ESM::Door newMarker; newMarker.mId = marker.first; newMarker.mModel = marker.second; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); record->mBase = newMarker; record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (std::move(record), CSMWorld::UniversalId::Type_Door); } } } bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { if (!mReader) throw std::logic_error ("can't continue loading, because no load has been started"); if (!mReader->hasMoreRecs()) { if (mBase) { // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading. // We don't store non-base reader, because everything going into modified will be // fully loaded during the initial loading process. std::shared_ptr ptr(mReader); mReaders.push_back(ptr); } else delete mReader; mReader = nullptr; mDialogue = nullptr; loadFallbackEntries(); return true; } ESM::NAME n = mReader->getRecName(); mReader->getRecHeader(); bool unhandledRecord = false; switch (n.toInt()) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; case ESM::REC_SKIL: mSkills.load (*mReader, mBase); break; case ESM::REC_CLAS: mClasses.load (*mReader, mBase); break; case ESM::REC_FACT: mFactions.load (*mReader, mBase); break; case ESM::REC_RACE: mRaces.load (*mReader, mBase); break; case ESM::REC_SOUN: mSounds.load (*mReader, mBase); break; case ESM::REC_SCPT: mScripts.load (*mReader, mBase); break; case ESM::REC_REGN: mRegions.load (*mReader, mBase); break; case ESM::REC_BSGN: mBirthsigns.load (*mReader, mBase); break; case ESM::REC_SPEL: mSpells.load (*mReader, mBase); break; case ESM::REC_ENCH: mEnchantments.load (*mReader, mBase); break; case ESM::REC_BODY: mBodyParts.load (*mReader, mBase); break; case ESM::REC_SNDG: mSoundGens.load (*mReader, mBase); break; case ESM::REC_MGEF: mMagicEffects.load (*mReader, mBase); break; case ESM::REC_PGRD: mPathgrids.load (*mReader, mBase); break; case ESM::REC_SSCR: mStartScripts.load (*mReader, mBase); break; case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break; case ESM::REC_LAND: mLand.load(*mReader, mBase); break; case ESM::REC_CELL: { int index = mCells.load (*mReader, mBase); if (index < 0 || index >= mCells.getSize()) { // log an error and continue loading the refs to the last loaded cell CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_None); messages.add (id, "Logic error: cell index out of bounds", "", CSMDoc::Message::Severity_Error); index = mCells.getSize()-1; } std::string cellId = Misc::StringUtils::lowerCase (mCells.getId (index)); mRefs.load (*mReader, index, mBase, mRefLoadCache[cellId], messages); break; } case ESM::REC_ACTI: mReferenceables.load (*mReader, mBase, UniversalId::Type_Activator); break; case ESM::REC_ALCH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Potion); break; case ESM::REC_APPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Apparatus); break; case ESM::REC_ARMO: mReferenceables.load (*mReader, mBase, UniversalId::Type_Armor); break; case ESM::REC_BOOK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Book); break; case ESM::REC_CLOT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Clothing); break; case ESM::REC_CONT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Container); break; case ESM::REC_CREA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Creature); break; case ESM::REC_DOOR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Door); break; case ESM::REC_INGR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Ingredient); break; case ESM::REC_LEVC: mReferenceables.load (*mReader, mBase, UniversalId::Type_CreatureLevelledList); break; case ESM::REC_LEVI: mReferenceables.load (*mReader, mBase, UniversalId::Type_ItemLevelledList); break; case ESM::REC_LIGH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Light); break; case ESM::REC_LOCK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Lockpick); break; case ESM::REC_MISC: mReferenceables.load (*mReader, mBase, UniversalId::Type_Miscellaneous); break; case ESM::REC_NPC_: mReferenceables.load (*mReader, mBase, UniversalId::Type_Npc); break; case ESM::REC_PROB: mReferenceables.load (*mReader, mBase, UniversalId::Type_Probe); break; case ESM::REC_REPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Repair); break; case ESM::REC_STAT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Static); break; case ESM::REC_WEAP: mReferenceables.load (*mReader, mBase, UniversalId::Type_Weapon); break; case ESM::REC_DIAL: { ESM::Dialogue record; bool isDeleted = false; record.load (*mReader, isDeleted); if (isDeleted) { // record vector can be shuffled around which would make pointer to record invalid mDialogue = nullptr; if (mJournals.tryDelete (record.mId)) { mJournalInfos.removeDialogueInfos(record.mId); } else if (mTopics.tryDelete (record.mId)) { mTopicInfos.removeDialogueInfos(record.mId); } else { messages.add (UniversalId::Type_None, "Trying to delete dialogue record " + record.mId + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { if (record.mType == ESM::Dialogue::Journal) { mJournals.load (record, mBase); mDialogue = &mJournals.getRecord (record.mId).get(); } else { mTopics.load (record, mBase); mDialogue = &mTopics.getRecord (record.mId).get(); } } break; } case ESM::REC_INFO: { if (!mDialogue) { messages.add (UniversalId::Type_None, "Found info record not following a dialogue record", "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); break; } if (mDialogue->mType==ESM::Dialogue::Journal) mJournalInfos.load (*mReader, mBase, *mDialogue); else mTopicInfos.load (*mReader, mBase, *mDialogue); break; } case ESM::REC_FILT: if (!mProject) { unhandledRecord = true; break; } mFilters.load (*mReader, mBase); break; case ESM::REC_DBGP: if (!mProject) { unhandledRecord = true; break; } mDebugProfiles.load (*mReader, mBase); break; default: unhandledRecord = true; } if (unhandledRecord) { messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString(), "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); } return false; } bool CSMWorld::Data::hasId (const std::string& id) const { return getGlobals().searchId (id)!=-1 || getGmsts().searchId (id)!=-1 || getSkills().searchId (id)!=-1 || getClasses().searchId (id)!=-1 || getFactions().searchId (id)!=-1 || getRaces().searchId (id)!=-1 || getSounds().searchId (id)!=-1 || getScripts().searchId (id)!=-1 || getRegions().searchId (id)!=-1 || getBirthsigns().searchId (id)!=-1 || getSpells().searchId (id)!=-1 || getTopics().searchId (id)!=-1 || getJournals().searchId (id)!=-1 || getCells().searchId (id)!=-1 || getEnchantments().searchId (id)!=-1 || getBodyParts().searchId (id)!=-1 || getSoundGens().searchId (id)!=-1 || getMagicEffects().searchId (id)!=-1 || getReferenceables().searchId (id)!=-1; } int CSMWorld::Data::count (RecordBase::State state) const { return count (state, mGlobals) + count (state, mGmsts) + count (state, mSkills) + count (state, mClasses) + count (state, mFactions) + count (state, mRaces) + count (state, mSounds) + count (state, mScripts) + count (state, mRegions) + count (state, mBirthsigns) + count (state, mSpells) + count (state, mCells) + count (state, mEnchantments) + count (state, mBodyParts) + count (state, mLand) + count (state, mLandTextures) + count (state, mSoundGens) + count (state, mMagicEffects) + count (state, mReferenceables) + count (state, mPathgrids); } std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; appendIds (ids, mGlobals, listDeleted); appendIds (ids, mGmsts, listDeleted); appendIds (ids, mClasses, listDeleted); appendIds (ids, mFactions, listDeleted); appendIds (ids, mRaces, listDeleted); appendIds (ids, mSounds, listDeleted); appendIds (ids, mScripts, listDeleted); appendIds (ids, mRegions, listDeleted); appendIds (ids, mBirthsigns, listDeleted); appendIds (ids, mSpells, listDeleted); appendIds (ids, mTopics, listDeleted); appendIds (ids, mJournals, listDeleted); appendIds (ids, mCells, listDeleted); appendIds (ids, mEnchantments, listDeleted); appendIds (ids, mBodyParts, listDeleted); appendIds (ids, mSoundGens, listDeleted); appendIds (ids, mMagicEffects, listDeleted); appendIds (ids, mReferenceables, listDeleted); std::sort (ids.begin(), ids.end()); return ids; } void CSMWorld::Data::assetsChanged() { mVFS.get()->reset(); VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); const UniversalId assetTableIds[] = { UniversalId::Type_Meshes, UniversalId::Type_Icons, UniversalId::Type_Musics, UniversalId::Type_SoundsRes, UniversalId::Type_Textures, UniversalId::Type_Videos }; size_t numAssetTables = sizeof(assetTableIds) / sizeof(UniversalId); for (size_t i = 0; i < numAssetTables; ++i) { ResourceTable* table = static_cast(getTableModel(assetTableIds[i])); table->beginReset(); } // Trigger recreation mResourcesManager.recreateResources(); for (size_t i = 0; i < numAssetTables; ++i) { ResourceTable* table = static_cast(getTableModel(assetTableIds[i])); table->endReset(); } // Get rid of potentially old cached assets mResourceSystem->clearCache(); emit assetTablesChanged(); } void CSMWorld::Data::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (topLeft.column()<=0) emit idListChanged(); } void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); } const VFS::Manager* CSMWorld::Data::getVFS() const { return mVFS.get(); } openmw-openmw-0.48.0/apps/opencs/model/world/data.hpp000066400000000000000000000250121445372753700225370ustar00rootroot00000000000000#ifndef CSM_WOLRD_DATA_H #define CSM_WOLRD_DATA_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../doc/stage.hpp" #include "actoradapter.hpp" #include "idcollection.hpp" #include "nestedidcollection.hpp" #include "universalid.hpp" #include "cell.hpp" #include "land.hpp" #include "landtexture.hpp" #include "refidcollection.hpp" #include "refcollection.hpp" #include "infocollection.hpp" #include "nestedinfocollection.hpp" #include "pathgrid.hpp" #include "resourcesmanager.hpp" #include "metadata.hpp" #ifndef Q_MOC_RUN #include "subcellcollection.hpp" #endif class QAbstractItemModel; namespace VFS { class Manager; } namespace Fallback { class Map; } namespace ESM { class ESMReader; struct Dialogue; } namespace CSMWorld { class ResourcesManager; class Resources; class Data : public QObject { Q_OBJECT ToUTF8::Utf8Encoder mEncoder; IdCollection mGlobals; IdCollection mGmsts; IdCollection mSkills; IdCollection mClasses; NestedIdCollection mFactions; NestedIdCollection mRaces; IdCollection mSounds; IdCollection mScripts; NestedIdCollection mRegions; NestedIdCollection mBirthsigns; NestedIdCollection mSpells; IdCollection mTopics; IdCollection mJournals; NestedIdCollection mEnchantments; IdCollection mBodyParts; IdCollection mMagicEffects; IdCollection mDebugProfiles; IdCollection mSoundGens; IdCollection mStartScripts; NestedInfoCollection mTopicInfos; InfoCollection mJournalInfos; NestedIdCollection mCells; SubCellCollection mPathgrids; IdCollection mLandTextures; IdCollection mLand; RefIdCollection mReferenceables; RefCollection mRefs; IdCollection mFilters; Collection mMetaData; std::unique_ptr mActorAdapter; std::vector mModels; std::map mModelIndex; ESM::ESMReader *mReader; const ESM::Dialogue *mDialogue; // last loaded dialogue bool mBase; bool mProject; std::map > mRefLoadCache; int mReaderIndex; bool mFsStrict; Files::PathContainer mDataPaths; std::vector mArchives; std::unique_ptr mVFS; ResourcesManager mResourcesManager; std::shared_ptr mResourceSystem; std::vector > mReaders; std::map mContentFileNames; // not implemented Data (const Data&); Data& operator= (const Data&); void addModel (QAbstractItemModel *model, UniversalId::Type type, bool update = true); static void appendIds (std::vector& ids, const CollectionBase& collection, bool listDeleted); ///< Append all IDs from collection to \a ids. static int count (RecordBase::State state, const CollectionBase& collection); void loadFallbackEntries(); public: Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives, const boost::filesystem::path& resDir); ~Data() override; const VFS::Manager* getVFS() const; std::shared_ptr getResourceSystem(); std::shared_ptr getResourceSystem() const; const IdCollection& getGlobals() const; IdCollection& getGlobals(); const IdCollection& getGmsts() const; IdCollection& getGmsts(); const IdCollection& getSkills() const; IdCollection& getSkills(); const IdCollection& getClasses() const; IdCollection& getClasses(); const IdCollection& getFactions() const; IdCollection& getFactions(); const IdCollection& getRaces() const; IdCollection& getRaces(); const IdCollection& getSounds() const; IdCollection& getSounds(); const IdCollection& getScripts() const; IdCollection& getScripts(); const IdCollection& getRegions() const; IdCollection& getRegions(); const IdCollection& getBirthsigns() const; IdCollection& getBirthsigns(); const IdCollection& getSpells() const; IdCollection& getSpells(); const IdCollection& getTopics() const; IdCollection& getTopics(); const IdCollection& getJournals() const; IdCollection& getJournals(); const InfoCollection& getTopicInfos() const; InfoCollection& getTopicInfos(); const InfoCollection& getJournalInfos() const; InfoCollection& getJournalInfos(); const IdCollection& getCells() const; IdCollection& getCells(); const RefIdCollection& getReferenceables() const; RefIdCollection& getReferenceables(); const RefCollection& getReferences() const; RefCollection& getReferences(); const IdCollection& getFilters() const; IdCollection& getFilters(); const IdCollection& getEnchantments() const; IdCollection& getEnchantments(); const IdCollection& getBodyParts() const; IdCollection& getBodyParts(); const IdCollection& getDebugProfiles() const; IdCollection& getDebugProfiles(); const IdCollection& getLand() const; IdCollection& getLand(); const IdCollection& getLandTextures() const; IdCollection& getLandTextures(); const IdCollection& getSoundGens() const; IdCollection& getSoundGens(); const IdCollection& getMagicEffects() const; IdCollection& getMagicEffects(); const SubCellCollection& getPathgrids() const; SubCellCollection& getPathgrids(); const IdCollection& getStartScripts() const; IdCollection& getStartScripts(); /// Throws an exception, if \a id does not match a resources list. const Resources& getResources (const UniversalId& id) const; const MetaData& getMetaData() const; void setMetaData (const MetaData& metaData); QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// /// \note The returned table may either be the model for the ID itself or the model that /// contains the record specified by the ID. const ActorAdapter* getActorAdapter() const; ActorAdapter* getActorAdapter(); void merge(); ///< Merge modified into base. int getTotalRecords (const std::vector& files); // for better loading bar int startLoading (const boost::filesystem::path& path, bool base, bool project); ///< Begin merging content of a file into base or modified. /// /// \param project load project file instead of content file /// ///< \return estimated number of records bool continueLoading (CSMDoc::Messages& messages); ///< \return Finished? bool hasId (const std::string& id) const; std::vector getIds (bool listDeleted = true) const; ///< Return a sorted collection of all IDs that are not internal to the editor. /// /// \param listDeleted include deleted record in the list int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. signals: void idListChanged(); void assetTablesChanged(); private slots: void assetsChanged(); void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/defaultgmsts.cpp000066400000000000000000001773751445372753700243470ustar00rootroot00000000000000#include "defaultgmsts.hpp" #include const float FInf = std::numeric_limits::infinity(); const float FEps = std::numeric_limits::epsilon(); const int IMax = std::numeric_limits::max(); const int IMin = std::numeric_limits::min(); const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = { "fAIFleeFleeMult", "fAIFleeHealthMult", "fAIMagicSpellMult", "fAIMeleeArmorMult", "fAIMeleeSummWeaponMult", "fAIMeleeWeaponMult", "fAIRangeMagicSpellMult", "fAIRangeMeleeWeaponMult", "fAlarmRadius", "fAthleticsRunBonus", "fAudioDefaultMaxDistance", "fAudioDefaultMinDistance", "fAudioMaxDistanceMult", "fAudioMinDistanceMult", "fAudioVoiceDefaultMaxDistance", "fAudioVoiceDefaultMinDistance", "fAutoPCSpellChance", "fAutoSpellChance", "fBargainOfferBase", "fBargainOfferMulti", "fBarterGoldResetDelay", "fBaseRunMultiplier", "fBlockStillBonus", "fBribe1000Mod", "fBribe100Mod", "fBribe10Mod", "fCombatAngleXY", "fCombatAngleZ", "fCombatArmorMinMult", "fCombatBlockLeftAngle", "fCombatBlockRightAngle", "fCombatCriticalStrikeMult", "fCombatDelayCreature", "fCombatDelayNPC", "fCombatDistance", "fCombatDistanceWerewolfMod", "fCombatForceSideAngle", "fCombatInvisoMult", "fCombatKODamageMult", "fCombatTorsoSideAngle", "fCombatTorsoStartPercent", "fCombatTorsoStopPercent", "fConstantEffectMult", "fCorpseClearDelay", "fCorpseRespawnDelay", "fCrimeGoldDiscountMult", "fCrimeGoldTurnInMult", "fCrimeStealing", "fDamageStrengthBase", "fDamageStrengthMult", "fDifficultyMult", "fDiseaseXferChance", "fDispAttacking", "fDispBargainFailMod", "fDispBargainSuccessMod", "fDispCrimeMod", "fDispDiseaseMod", "fDispFactionMod", "fDispFactionRankBase", "fDispFactionRankMult", "fDispositionMod", "fDispPersonalityBase", "fDispPersonalityMult", "fDispPickPocketMod", "fDispRaceMod", "fDispStealing", "fDispWeaponDrawn", "fEffectCostMult", "fElementalShieldMult", "fEnchantmentChanceMult", "fEnchantmentConstantChanceMult", "fEnchantmentConstantDurationMult", "fEnchantmentMult", "fEnchantmentValueMult", "fEncumberedMoveEffect", "fEncumbranceStrMult", "fEndFatigueMult", "fFallAcroBase", "fFallAcroMult", "fFallDamageDistanceMin", "fFallDistanceBase", "fFallDistanceMult", "fFatigueAttackBase", "fFatigueAttackMult", "fFatigueBase", "fFatigueBlockBase", "fFatigueBlockMult", "fFatigueJumpBase", "fFatigueJumpMult", "fFatigueMult", "fFatigueReturnBase", "fFatigueReturnMult", "fFatigueRunBase", "fFatigueRunMult", "fFatigueSneakBase", "fFatigueSneakMult", "fFatigueSpellBase", "fFatigueSpellCostMult", "fFatigueSpellMult", "fFatigueSwimRunBase", "fFatigueSwimRunMult", "fFatigueSwimWalkBase", "fFatigueSwimWalkMult", "fFightDispMult", "fFightDistanceMultiplier", "fFightStealing", "fFleeDistance", "fGreetDistanceReset", "fHandtoHandHealthPer", "fHandToHandReach", "fHoldBreathEndMult", "fHoldBreathTime", "fIdleChanceMultiplier", "fIngredientMult", "fInteriorHeadTrackMult", "fJumpAcrobaticsBase", "fJumpAcroMultiplier", "fJumpEncumbranceBase", "fJumpEncumbranceMultiplier", "fJumpMoveBase", "fJumpMoveMult", "fJumpRunMultiplier", "fKnockDownMult", "fLevelMod", "fLevelUpHealthEndMult", "fLightMaxMod", "fLuckMod", "fMagesGuildTravel", "fMagicCreatureCastDelay", "fMagicDetectRefreshRate", "fMagicItemConstantMult", "fMagicItemCostMult", "fMagicItemOnceMult", "fMagicItemPriceMult", "fMagicItemRechargePerSecond", "fMagicItemStrikeMult", "fMagicItemUsedMult", "fMagicStartIconBlink", "fMagicSunBlockedMult", "fMajorSkillBonus", "fMaxFlySpeed", "fMaxHandToHandMult", "fMaxHeadTrackDistance", "fMaxWalkSpeed", "fMaxWalkSpeedCreature", "fMedMaxMod", "fMessageTimePerChar", "fMinFlySpeed", "fMinHandToHandMult", "fMinorSkillBonus", "fMinWalkSpeed", "fMinWalkSpeedCreature", "fMiscSkillBonus", "fNPCbaseMagickaMult", "fNPCHealthBarFade", "fNPCHealthBarTime", "fPCbaseMagickaMult", "fPerDieRollMult", "fPersonalityMod", "fPerTempMult", "fPickLockMult", "fPickPocketMod", "fPotionMinUsefulDuration", "fPotionStrengthMult", "fPotionT1DurMult", "fPotionT1MagMult", "fPotionT4BaseStrengthMult", "fPotionT4EquipStrengthMult", "fProjectileMaxSpeed", "fProjectileMinSpeed", "fProjectileThrownStoreChance", "fRepairAmountMult", "fRepairMult", "fReputationMod", "fRestMagicMult", "fSeriousWoundMult", "fSleepRandMod", "fSleepRestMod", "fSneakBootMult", "fSneakDistanceBase", "fSneakDistanceMultiplier", "fSneakNoViewMult", "fSneakSkillMult", "fSneakSpeedMultiplier", "fSneakUseDelay", "fSneakUseDist", "fSneakViewMult", "fSoulGemMult", "fSpecialSkillBonus", "fSpellMakingValueMult", "fSpellPriceMult", "fSpellValueMult", "fStromWalkMult", "fStromWindSpeed", "fSuffocationDamage", "fSwimHeightScale", "fSwimRunAthleticsMult", "fSwimRunBase", "fSwimWalkAthleticsMult", "fSwimWalkBase", "fSwingBlockBase", "fSwingBlockMult", "fTargetSpellMaxSpeed", "fThrownWeaponMaxSpeed", "fThrownWeaponMinSpeed", "fTrapCostMult", "fTravelMult", "fTravelTimeMult", "fUnarmoredBase1", "fUnarmoredBase2", "fVanityDelay", "fVoiceIdleOdds", "fWaterReflectUpdateAlways", "fWaterReflectUpdateSeldom", "fWeaponDamageMult", "fWeaponFatigueBlockMult", "fWeaponFatigueMult", "fWereWolfAcrobatics", "fWereWolfAgility", "fWereWolfAlchemy", "fWereWolfAlteration", "fWereWolfArmorer", "fWereWolfAthletics", "fWereWolfAxe", "fWereWolfBlock", "fWereWolfBluntWeapon", "fWereWolfConjuration", "fWereWolfDestruction", "fWereWolfEnchant", "fWereWolfEndurance", "fWereWolfFatigue", "fWereWolfHandtoHand", "fWereWolfHealth", "fWereWolfHeavyArmor", "fWereWolfIllusion", "fWereWolfIntellegence", "fWereWolfLightArmor", "fWereWolfLongBlade", "fWereWolfLuck", "fWereWolfMagicka", "fWereWolfMarksman", "fWereWolfMediumArmor", "fWereWolfMerchantile", "fWereWolfMysticism", "fWereWolfPersonality", "fWereWolfRestoration", "fWereWolfRunMult", "fWereWolfSecurity", "fWereWolfShortBlade", "fWereWolfSilverWeaponDamageMult", "fWereWolfSneak", "fWereWolfSpear", "fWereWolfSpeechcraft", "fWereWolfSpeed", "fWereWolfStrength", "fWereWolfUnarmored", "fWereWolfWillPower", "fWortChanceValue" }; const char * CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = { "i1stPersonSneakDelta", "iAlarmAttack", "iAlarmKilling", "iAlarmPickPocket", "iAlarmStealing", "iAlarmTresspass", "iAlchemyMod", "iAutoPCSpellMax", "iAutoRepFacMod", "iAutoRepLevMod", "iAutoSpellAlterationMax", "iAutoSpellAttSkillMin", "iAutoSpellConjurationMax", "iAutoSpellDestructionMax", "iAutoSpellIllusionMax", "iAutoSpellMysticismMax", "iAutoSpellRestorationMax", "iAutoSpellTimesCanCast", "iBarterFailDisposition", "iBarterSuccessDisposition", "iBaseArmorSkill", "iBlockMaxChance", "iBlockMinChance", "iBootsWeight", "iCrimeAttack", "iCrimeKilling", "iCrimePickPocket", "iCrimeThreshold", "iCrimeThresholdMultiplier", "iCrimeTresspass", "iCuirassWeight", "iDaysinPrisonMod", "iDispAttackMod", "iDispKilling", "iDispTresspass", "iFightAlarmMult", "iFightAttack", "iFightAttacking", "iFightDistanceBase", "iFightKilling", "iFightPickpocket", "iFightTrespass", "iFlee", "iGauntletWeight", "iGreavesWeight", "iGreetDistanceMultiplier", "iGreetDuration", "iHelmWeight", "iKnockDownOddsBase", "iKnockDownOddsMult", "iLevelUp01Mult", "iLevelUp02Mult", "iLevelUp03Mult", "iLevelUp04Mult", "iLevelUp05Mult", "iLevelUp06Mult", "iLevelUp07Mult", "iLevelUp08Mult", "iLevelUp09Mult", "iLevelUp10Mult", "iLevelupMajorMult", "iLevelupMajorMultAttribute", "iLevelupMinorMult", "iLevelupMinorMultAttribute", "iLevelupMiscMultAttriubte", "iLevelupSpecialization", "iLevelupTotal", "iMagicItemChargeConst", "iMagicItemChargeOnce", "iMagicItemChargeStrike", "iMagicItemChargeUse", "iMaxActivateDist", "iMaxInfoDist", "iMonthsToRespawn", "iNumberCreatures", "iPauldronWeight", "iPerMinChance", "iPerMinChange", "iPickMaxChance", "iPickMinChance", "iShieldWeight", "iSoulAmountForConstantEffect", "iTrainingMod", "iVoiceAttackOdds", "iVoiceHitOdds", "iWereWolfBounty", "iWereWolfFightMod", "iWereWolfFleeMod", "iWereWolfLevelToAttack" }; const char * CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount] = { "s3dAudio", "s3dHardware", "s3dSoftware", "sAbsorb", "sAcrobat", "sActivate", "sActivateXbox", "sActorInCombat", "sAdmire", "sAdmireFail", "sAdmireSuccess", "sAgent", "sAgiDesc", "sAIDistance", "sAlembic", "sAllTab", "sAlways", "sAlways_Run", "sand", "sApparatus", "sApparelTab", "sArcher", "sArea", "sAreaDes", "sArmor", "sArmorRating", "sAsk", "sAssassin", "sAt", "sAttack", "sAttributeAgility", "sAttributeEndurance", "sAttributeIntelligence", "sAttributeListTitle", "sAttributeLuck", "sAttributePersonality", "sAttributesMenu1", "sAttributeSpeed", "sAttributeStrength", "sAttributeWillpower", "sAudio", "sAuto_Run", "sBack", "sBackspace", "sBackXbox", "sBarbarian", "sBard", "sBarter", "sBarterDialog1", "sBarterDialog10", "sBarterDialog11", "sBarterDialog12", "sBarterDialog2", "sBarterDialog3", "sBarterDialog4", "sBarterDialog5", "sBarterDialog6", "sBarterDialog7", "sBarterDialog8", "sBarterDialog9", "sBattlemage", "sBestAttack", "sBirthSign", "sBirthsignmenu1", "sBirthsignmenu2", "sBlocks", "sBonusSkillTitle", "sBookPageOne", "sBookPageTwo", "sBookSkillMessage", "sBounty", "sBreath", "sBribe 10 Gold", "sBribe 100 Gold", "sBribe 1000 Gold", "sBribeFail", "sBribeSuccess", "sBuy", "sBye", "sCalcinator", "sCancel", "sCantEquipWeapWarning", "sCastCost", "sCaughtStealingMessage", "sCenter", "sChangedMastersMsg", "sCharges", "sChooseClassMenu1", "sChooseClassMenu2", "sChooseClassMenu3", "sChooseClassMenu4", "sChop", "sClass", "sClassChoiceMenu1", "sClassChoiceMenu2", "sClassChoiceMenu3", "sClose", "sCompanionShare", "sCompanionWarningButtonOne", "sCompanionWarningButtonTwo", "sCompanionWarningMessage", "sCondition", "sConsoleTitle", "sContainer", "sContentsMessage1", "sContentsMessage2", "sContentsMessage3", "sControlerVibration", "sControls", "sControlsMenu1", "sControlsMenu2", "sControlsMenu3", "sControlsMenu4", "sControlsMenu5", "sControlsMenu6", "sCostChance", "sCostCharge", "sCreate", "sCreateClassMenu1", "sCreateClassMenu2", "sCreateClassMenu3", "sCreateClassMenuHelp1", "sCreateClassMenuHelp2", "sCreateClassMenuWarning", "sCreatedEffects", "sCrimeHelp", "sCrimeMessage", "sCrouch_Sneak", "sCrouchXbox", "sCrusader", "sCursorOff", "sCustom", "sCustomClassName", "sDamage", "sDark_Gamma", "sDay", "sDefaultCellname", "sDelete", "sDeleteGame", "sDeleteNote", "sDeleteSpell", "sDeleteSpellError", "sDetail_Level", "sDialogMenu1", "sDialogText1Xbox", "sDialogText2Xbox", "sDialogText3Xbox", "sDifficulty", "sDisposeCorpseFail", "sDisposeofCorpse", "sDone", "sDoYouWantTo", "sDrain", "sDrop", "sDuration", "sDurationDes", "sEasy", "sEditNote", "sEffectAbsorbAttribute", "sEffectAbsorbFatigue", "sEffectAbsorbHealth", "sEffectAbsorbSkill", "sEffectAbsorbSpellPoints", "sEffectAlmsiviIntervention", "sEffectBlind", "sEffectBoundBattleAxe", "sEffectBoundBoots", "sEffectBoundCuirass", "sEffectBoundDagger", "sEffectBoundGloves", "sEffectBoundHelm", "sEffectBoundLongbow", "sEffectBoundLongsword", "sEffectBoundMace", "sEffectBoundShield", "sEffectBoundSpear", "sEffectBurden", "sEffectCalmCreature", "sEffectCalmHumanoid", "sEffectChameleon", "sEffectCharm", "sEffectCommandCreatures", "sEffectCommandHumanoids", "sEffectCorpus", "sEffectCureBlightDisease", "sEffectCureCommonDisease", "sEffectCureCorprusDisease", "sEffectCureParalyzation", "sEffectCurePoison", "sEffectDamageAttribute", "sEffectDamageFatigue", "sEffectDamageHealth", "sEffectDamageMagicka", "sEffectDamageSkill", "sEffectDemoralizeCreature", "sEffectDemoralizeHumanoid", "sEffectDetectAnimal", "sEffectDetectEnchantment", "sEffectDetectKey", "sEffectDisintegrateArmor", "sEffectDisintegrateWeapon", "sEffectDispel", "sEffectDivineIntervention", "sEffectDrainAttribute", "sEffectDrainFatigue", "sEffectDrainHealth", "sEffectDrainSkill", "sEffectDrainSpellpoints", "sEffectExtraSpell", "sEffectFeather", "sEffectFireDamage", "sEffectFireShield", "sEffectFortifyAttackBonus", "sEffectFortifyAttribute", "sEffectFortifyFatigue", "sEffectFortifyHealth", "sEffectFortifyMagickaMultiplier", "sEffectFortifySkill", "sEffectFortifySpellpoints", "sEffectFrenzyCreature", "sEffectFrenzyHumanoid", "sEffectFrostDamage", "sEffectFrostShield", "sEffectInvisibility", "sEffectJump", "sEffectLevitate", "sEffectLight", "sEffectLightningShield", "sEffectLock", "sEffectMark", "sEffectNightEye", "sEffectOpen", "sEffectParalyze", "sEffectPoison", "sEffectRallyCreature", "sEffectRallyHumanoid", "sEffectRecall", "sEffectReflect", "sEffectRemoveCurse", "sEffectResistBlightDisease", "sEffectResistCommonDisease", "sEffectResistCorprusDisease", "sEffectResistFire", "sEffectResistFrost", "sEffectResistMagicka", "sEffectResistNormalWeapons", "sEffectResistParalysis", "sEffectResistPoison", "sEffectResistShock", "sEffectRestoreAttribute", "sEffectRestoreFatigue", "sEffectRestoreHealth", "sEffectRestoreSkill", "sEffectRestoreSpellPoints", "sEffects", "sEffectSanctuary", "sEffectShield", "sEffectShockDamage", "sEffectSilence", "sEffectSlowFall", "sEffectSoultrap", "sEffectSound", "sEffectSpellAbsorption", "sEffectStuntedMagicka", "sEffectSummonAncestralGhost", "sEffectSummonBonelord", "sEffectSummonCenturionSphere", "sEffectSummonClannfear", "sEffectSummonCreature01", "sEffectSummonCreature02", "sEffectSummonCreature03", "sEffectSummonCreature04", "sEffectSummonCreature05", "sEffectSummonDaedroth", "sEffectSummonDremora", "sEffectSummonFabricant", "sEffectSummonFlameAtronach", "sEffectSummonFrostAtronach", "sEffectSummonGoldensaint", "sEffectSummonGreaterBonewalker", "sEffectSummonHunger", "sEffectSummonLeastBonewalker", "sEffectSummonScamp", "sEffectSummonSkeletalMinion", "sEffectSummonStormAtronach", "sEffectSummonWingedTwilight", "sEffectSunDamage", "sEffectSwiftSwim", "sEffectTelekinesis", "sEffectTurnUndead", "sEffectVampirism", "sEffectWaterBreathing", "sEffectWaterWalking", "sEffectWeaknessToBlightDisease", "sEffectWeaknessToCommonDisease", "sEffectWeaknessToCorprusDisease", "sEffectWeaknessToFire", "sEffectWeaknessToFrost", "sEffectWeaknessToMagicka", "sEffectWeaknessToNormalWeapons", "sEffectWeaknessToPoison", "sEffectWeaknessToShock", "sEnableJoystick", "sEnchanting", "sEnchantItems", "sEnchantmentHelp1", "sEnchantmentHelp10", "sEnchantmentHelp2", "sEnchantmentHelp3", "sEnchantmentHelp4", "sEnchantmentHelp5", "sEnchantmentHelp6", "sEnchantmentHelp7", "sEnchantmentHelp8", "sEnchantmentHelp9", "sEnchantmentMenu1", "sEnchantmentMenu10", "sEnchantmentMenu11", "sEnchantmentMenu12", "sEnchantmentMenu2", "sEnchantmentMenu3", "sEnchantmentMenu4", "sEnchantmentMenu5", "sEnchantmentMenu6", "sEnchantmentMenu7", "sEnchantmentMenu8", "sEnchantmentMenu9", "sEncumbrance", "sEndDesc", "sEquip", "sExitGame", "sExpelled", "sExpelledMessage", "sFace", "sFaction", "sFar", "sFast", "sFatDesc", "sFatigue", "sFavoriteSkills", "sfeet", "sFileSize", "sfootarea", "sFootsteps", "sfor", "sFortify", "sForward", "sForwardXbox", "sFull", "sGame", "sGameWithoutLauncherXbox", "sGamma_Correction", "sGeneralMastPlugMismatchMsg", "sGold", "sGoodbye", "sGoverningAttribute", "sgp", "sHair", "sHard", "sHeal", "sHealer", "sHealth", "sHealthDesc", "sHealthPerHourOfRest", "sHealthPerLevel", "sHeavy", "sHigh", "sin", "sInfo", "sInfoRefusal", "sIngredients", "sInPrisonTitle", "sInputMenu1", "sIntDesc", "sIntimidate", "sIntimidateFail", "sIntimidateSuccess", "sInvalidSaveGameMsg", "sInvalidSaveGameMsgXBOX", "sInventory", "sInventoryMenu1", "sInventoryMessage1", "sInventoryMessage2", "sInventoryMessage3", "sInventoryMessage4", "sInventoryMessage5", "sInventorySelectNoIngredients", "sInventorySelectNoItems", "sInventorySelectNoSoul", "sItem", "sItemCastConstant", "sItemCastOnce", "sItemCastWhenStrikes", "sItemCastWhenUsed", "sItemName", "sJournal", "sJournalCmd", "sJournalEntry", "sJournalXbox", "sJoystickHatShort", "sJoystickNotFound", "sJoystickShort", "sJump", "sJumpXbox", "sKeyName_00", "sKeyName_01", "sKeyName_02", "sKeyName_03", "sKeyName_04", "sKeyName_05", "sKeyName_06", "sKeyName_07", "sKeyName_08", "sKeyName_09", "sKeyName_0A", "sKeyName_0B", "sKeyName_0C", "sKeyName_0D", "sKeyName_0E", "sKeyName_0F", "sKeyName_10", "sKeyName_11", "sKeyName_12", "sKeyName_13", "sKeyName_14", "sKeyName_15", "sKeyName_16", "sKeyName_17", "sKeyName_18", "sKeyName_19", "sKeyName_1A", "sKeyName_1B", "sKeyName_1C", "sKeyName_1D", "sKeyName_1E", "sKeyName_1F", "sKeyName_20", "sKeyName_21", "sKeyName_22", "sKeyName_23", "sKeyName_24", "sKeyName_25", "sKeyName_26", "sKeyName_27", "sKeyName_28", "sKeyName_29", "sKeyName_2A", "sKeyName_2B", "sKeyName_2C", "sKeyName_2D", "sKeyName_2E", "sKeyName_2F", "sKeyName_30", "sKeyName_31", "sKeyName_32", "sKeyName_33", "sKeyName_34", "sKeyName_35", "sKeyName_36", "sKeyName_37", "sKeyName_38", "sKeyName_39", "sKeyName_3A", "sKeyName_3B", "sKeyName_3C", "sKeyName_3D", "sKeyName_3E", "sKeyName_3F", "sKeyName_40", "sKeyName_41", "sKeyName_42", "sKeyName_43", "sKeyName_44", "sKeyName_45", "sKeyName_46", "sKeyName_47", "sKeyName_48", "sKeyName_49", "sKeyName_4A", "sKeyName_4B", "sKeyName_4C", "sKeyName_4D", "sKeyName_4E", "sKeyName_4F", "sKeyName_50", "sKeyName_51", "sKeyName_52", "sKeyName_53", "sKeyName_54", "sKeyName_55", "sKeyName_56", "sKeyName_57", "sKeyName_58", "sKeyName_59", "sKeyName_5A", "sKeyName_5B", "sKeyName_5C", "sKeyName_5D", "sKeyName_5E", "sKeyName_5F", "sKeyName_60", "sKeyName_61", "sKeyName_62", "sKeyName_63", "sKeyName_64", "sKeyName_65", "sKeyName_66", "sKeyName_67", "sKeyName_68", "sKeyName_69", "sKeyName_6A", "sKeyName_6B", "sKeyName_6C", "sKeyName_6D", "sKeyName_6E", "sKeyName_6F", "sKeyName_70", "sKeyName_71", "sKeyName_72", "sKeyName_73", "sKeyName_74", "sKeyName_75", "sKeyName_76", "sKeyName_77", "sKeyName_78", "sKeyName_79", "sKeyName_7A", "sKeyName_7B", "sKeyName_7C", "sKeyName_7D", "sKeyName_7E", "sKeyName_7F", "sKeyName_80", "sKeyName_81", "sKeyName_82", "sKeyName_83", "sKeyName_84", "sKeyName_85", "sKeyName_86", "sKeyName_87", "sKeyName_88", "sKeyName_89", "sKeyName_8A", "sKeyName_8B", "sKeyName_8C", "sKeyName_8D", "sKeyName_8E", "sKeyName_8F", "sKeyName_90", "sKeyName_91", "sKeyName_92", "sKeyName_93", "sKeyName_94", "sKeyName_95", "sKeyName_96", "sKeyName_97", "sKeyName_98", "sKeyName_99", "sKeyName_9A", "sKeyName_9B", "sKeyName_9C", "sKeyName_9D", "sKeyName_9E", "sKeyName_9F", "sKeyName_A0", "sKeyName_A1", "sKeyName_A2", "sKeyName_A3", "sKeyName_A4", "sKeyName_A5", "sKeyName_A6", "sKeyName_A7", "sKeyName_A8", "sKeyName_A9", "sKeyName_AA", "sKeyName_AB", "sKeyName_AC", "sKeyName_AD", "sKeyName_AE", "sKeyName_AF", "sKeyName_B0", "sKeyName_B1", "sKeyName_B2", "sKeyName_B3", "sKeyName_B4", "sKeyName_B5", "sKeyName_B6", "sKeyName_B7", "sKeyName_B8", "sKeyName_B9", "sKeyName_BA", "sKeyName_BB", "sKeyName_BC", "sKeyName_BD", "sKeyName_BE", "sKeyName_BF", "sKeyName_C0", "sKeyName_C1", "sKeyName_C2", "sKeyName_C3", "sKeyName_C4", "sKeyName_C5", "sKeyName_C6", "sKeyName_C7", "sKeyName_C8", "sKeyName_C9", "sKeyName_CA", "sKeyName_CB", "sKeyName_CC", "sKeyName_CD", "sKeyName_CE", "sKeyName_CF", "sKeyName_D0", "sKeyName_D1", "sKeyName_D2", "sKeyName_D3", "sKeyName_D4", "sKeyName_D5", "sKeyName_D6", "sKeyName_D7", "sKeyName_D8", "sKeyName_D9", "sKeyName_DA", "sKeyName_DB", "sKeyName_DC", "sKeyName_DD", "sKeyName_DE", "sKeyName_DF", "sKeyName_E0", "sKeyName_E1", "sKeyName_E2", "sKeyName_E3", "sKeyName_E4", "sKeyName_E5", "sKeyName_E6", "sKeyName_E7", "sKeyName_E8", "sKeyName_E9", "sKeyName_EA", "sKeyName_EB", "sKeyName_EC", "sKeyName_ED", "sKeyName_EE", "sKeyName_EF", "sKeyName_F0", "sKeyName_F1", "sKeyName_F2", "sKeyName_F3", "sKeyName_F4", "sKeyName_F5", "sKeyName_F6", "sKeyName_F7", "sKeyName_F8", "sKeyName_F9", "sKeyName_FA", "sKeyName_FB", "sKeyName_FC", "sKeyName_FD", "sKeyName_FE", "sKeyName_FF", "sKeyUsed", "sKilledEssential", "sKnight", "sLeft", "sLess", "sLevel", "sLevelProgress", "sLevels", "sLevelUp", "sLevelUpMenu1", "sLevelUpMenu2", "sLevelUpMenu3", "sLevelUpMenu4", "sLevelUpMsg", "sLevitateDisabled", "sLight", "sLight_Gamma", "sLoadFailedMessage", "sLoadGame", "sLoadingErrorsMsg", "sLoadingMessage1", "sLoadingMessage14", "sLoadingMessage15", "sLoadingMessage2", "sLoadingMessage3", "sLoadingMessage4", "sLoadingMessage5", "sLoadingMessage9", "sLoadLastSaveMsg", "sLocal", "sLockFail", "sLockImpossible", "sLockLevel", "sLockSuccess", "sLookDownXbox", "sLookUpXbox", "sLow", "sLucDesc", "sMagDesc", "sMage", "sMagic", "sMagicAncestralGhostID", "sMagicBonelordID", "sMagicBoundBattleAxeID", "sMagicBoundBootsID", "sMagicBoundCuirassID", "sMagicBoundDaggerID", "sMagicBoundHelmID", "sMagicBoundLeftGauntletID", "sMagicBoundLongbowID", "sMagicBoundLongswordID", "sMagicBoundMaceID", "sMagicBoundRightGauntletID", "sMagicBoundShieldID", "sMagicBoundSpearID", "sMagicCannotRecast", "sMagicCenturionSphereID", "sMagicClannfearID", "sMagicContractDisease", "sMagicCorprusWorsens", "sMagicCreature01ID", "sMagicCreature02ID", "sMagicCreature03ID", "sMagicCreature04ID", "sMagicCreature05ID", "sMagicDaedrothID", "sMagicDremoraID", "sMagicEffects", "sMagicFabricantID", "sMagicFlameAtronachID", "sMagicFrostAtronachID", "sMagicGoldenSaintID", "sMagicGreaterBonewalkerID", "sMagicHungerID", "sMagicInsufficientCharge", "sMagicInsufficientSP", "sMagicInvalidEffect", "sMagicInvalidTarget", "sMagicItem", "sMagicLeastBonewalkerID", "sMagicLockSuccess", "sMagicMenu", "sMagicOpenSuccess", "sMagicPCResisted", "sMagicScampID", "sMagicSelectTitle", "sMagicSkeletalMinionID", "sMagicSkillFail", "sMagicStormAtronachID", "sMagicTab", "sMagicTargetResisted", "sMagicTargetResistsWeapons", "sMagicWingedTwilightID", "sMagnitude", "sMagnitudeDes", "sMake Enchantment", "sMap", "sMaster", "sMastPlugMismatchMsg", "sMaximumSaveGameMessage", "sMaxSale", "sMedium", "sMenu_Help_Delay", "sMenu_Mode", "sMenuModeXbox", "sMenuNextXbox", "sMenuPrevXbox", "sMenus", "sMessage1", "sMessage2", "sMessage3", "sMessage4", "sMessage5", "sMessageQuestionAnswer1", "sMessageQuestionAnswer2", "sMessageQuestionAnswer3", "sMiscTab", "sMissingMastersMsg", "sMonk", "sMonthEveningstar", "sMonthFirstseed", "sMonthFrostfall", "sMonthHeartfire", "sMonthLastseed", "sMonthMidyear", "sMonthMorningstar", "sMonthRainshand", "sMonthSecondseed", "sMonthSunsdawn", "sMonthSunsdusk", "sMonthSunsheight", "sMore", "sMortar", "sMouse", "sMouseFlip", "sMouseWheelDownShort", "sMouseWheelUpShort", "sMove", "sMoveDownXbox", "sMoveUpXbox", "sMusic", "sName", "sNameTitle", "sNear", "sNeedOneSkill", "sNeedTwoSkills", "sNewGame", "sNext", "sNextRank", "sNextSpell", "sNextSpellXbox", "sNextWeapon", "sNextWeaponXbox", "sNightblade", "sNo", "sNoName", "sNone", "sNotifyMessage1", "sNotifyMessage10", "sNotifyMessage11", "sNotifyMessage12", "sNotifyMessage13", "sNotifyMessage14", "sNotifyMessage15", "sNotifyMessage16", "sNotifyMessage16_a", "sNotifyMessage17", "sNotifyMessage18", "sNotifyMessage19", "sNotifyMessage2", "sNotifyMessage20", "sNotifyMessage21", "sNotifyMessage22", "sNotifyMessage23", "sNotifyMessage24", "sNotifyMessage25", "sNotifyMessage26", "sNotifyMessage27", "sNotifyMessage28", "sNotifyMessage29", "sNotifyMessage3", "sNotifyMessage30", "sNotifyMessage31", "sNotifyMessage32", "sNotifyMessage33", "sNotifyMessage34", "sNotifyMessage35", "sNotifyMessage36", "sNotifyMessage37", "sNotifyMessage38", "sNotifyMessage39", "sNotifyMessage4", "sNotifyMessage40", "sNotifyMessage41", "sNotifyMessage42", "sNotifyMessage43", "sNotifyMessage44", "sNotifyMessage45", "sNotifyMessage46", "sNotifyMessage47", "sNotifyMessage48", "sNotifyMessage49", "sNotifyMessage4XBOX", "sNotifyMessage5", "sNotifyMessage50", "sNotifyMessage51", "sNotifyMessage52", "sNotifyMessage53", "sNotifyMessage54", "sNotifyMessage55", "sNotifyMessage56", "sNotifyMessage57", "sNotifyMessage58", "sNotifyMessage59", "sNotifyMessage6", "sNotifyMessage60", "sNotifyMessage61", "sNotifyMessage62", "sNotifyMessage63", "sNotifyMessage64", "sNotifyMessage65", "sNotifyMessage66", "sNotifyMessage67", "sNotifyMessage6a", "sNotifyMessage7", "sNotifyMessage8", "sNotifyMessage9", "sOff", "sOffer", "sOfferMenuTitle", "sOK", "sOn", "sOnce", "sOneHanded", "sOnetypeEffectMessage", "sonword", "sOptions", "sOptionsMenuXbox", "spercent", "sPerDesc", "sPersuasion", "sPersuasionMenuTitle", "sPickUp", "sPilgrim", "spoint", "spoints", "sPotionSuccess", "sPowerAlreadyUsed", "sPowers", "sPreferences", "sPrefs", "sPrev", "sPrevSpell", "sPrevSpellXbox", "sPrevWeapon", "sPrevWeaponXbox", "sProfitValue", "sQuality", "sQuanityMenuMessage01", "sQuanityMenuMessage02", "sQuestionDeleteSpell", "sQuestionMark", "sQuick0Xbox", "sQuick10Cmd", "sQuick1Cmd", "sQuick2Cmd", "sQuick3Cmd", "sQuick4Cmd", "sQuick4Xbox", "sQuick5Cmd", "sQuick5Xbox", "sQuick6Cmd", "sQuick6Xbox", "sQuick7Cmd", "sQuick7Xbox", "sQuick8Cmd", "sQuick8Xbox", "sQuick9Cmd", "sQuick9Xbox", "sQuick_Save", "sQuickLoadCmd", "sQuickLoadXbox", "sQuickMenu", "sQuickMenu1", "sQuickMenu2", "sQuickMenu3", "sQuickMenu4", "sQuickMenu5", "sQuickMenu6", "sQuickMenuInstruc", "sQuickMenuTitle", "sQuickSaveCmd", "sQuickSaveXbox", "sRace", "sRaceMenu1", "sRaceMenu2", "sRaceMenu3", "sRaceMenu4", "sRaceMenu5", "sRaceMenu6", "sRaceMenu7", "sRacialTraits", "sRange", "sRangeDes", "sRangeSelf", "sRangeTarget", "sRangeTouch", "sReady_Magic", "sReady_Weapon", "sReadyItemXbox", "sReadyMagicXbox", "sRechargeEnchantment", "sRender_Distance", "sRepair", "sRepairFailed", "sRepairServiceTitle", "sRepairSuccess", "sReputation", "sResChangeWarning", "sRest", "sRestIllegal", "sRestKey", "sRestMenu1", "sRestMenu2", "sRestMenu3", "sRestMenu4", "sRestMenuXbox", "sRestore", "sRetort", "sReturnToGame", "sRight", "sRogue", "sRun", "sRunXbox", "sSave", "sSaveGame", "sSaveGameDenied", "sSaveGameFailed", "sSaveGameNoMemory", "sSaveGameTooBig", "sSaveMenu1", "sSaveMenuHelp01", "sSaveMenuHelp02", "sSaveMenuHelp03", "sSaveMenuHelp04", "sSaveMenuHelp05", "sSaveMenuHelp06", "sSchool", "sSchoolAlteration", "sSchoolConjuration", "sSchoolDestruction", "sSchoolIllusion", "sSchoolMysticism", "sSchoolRestoration", "sScout", "sScrolldown", "sScrollup", "ssecond", "sseconds", "sSeldom", "sSelect", "sSell", "sSellerGold", "sService", "sServiceRefusal", "sServiceRepairTitle", "sServiceSpellsTitle", "sServiceTrainingTitle", "sServiceTrainingWords", "sServiceTravelTitle", "sSetValueMessage01", "sSex", "sShadows", "sShadowText", "sShift", "sSkill", "sSkillAcrobatics", "sSkillAlchemy", "sSkillAlteration", "sSkillArmorer", "sSkillAthletics", "sSkillAxe", "sSkillBlock", "sSkillBluntweapon", "sSkillClassMajor", "sSkillClassMinor", "sSkillClassMisc", "sSkillConjuration", "sSkillDestruction", "sSkillEnchant", "sSkillHandtohand", "sSkillHeavyarmor", "sSkillIllusion", "sSkillLightarmor", "sSkillLongblade", "sSkillMarksman", "sSkillMaxReached", "sSkillMediumarmor", "sSkillMercantile", "sSkillMysticism", "sSkillProgress", "sSkillRestoration", "sSkillSecurity", "sSkillShortblade", "sSkillsMenu1", "sSkillsMenuReputationHelp", "sSkillSneak", "sSkillSpear", "sSkillSpeechcraft", "sSkillUnarmored", "sSlash", "sSleepInterrupt", "sSlideLeftXbox", "sSlideRightXbox", "sSlow", "sSorceror", "sSoulGem", "sSoulGemsWithSouls", "sSoultrapSuccess", "sSpace", "sSpdDesc", "sSpecialization", "sSpecializationCombat", "sSpecializationMagic", "sSpecializationMenu1", "sSpecializationStealth", "sSpellmaking", "sSpellmakingHelp1", "sSpellmakingHelp2", "sSpellmakingHelp3", "sSpellmakingHelp4", "sSpellmakingHelp5", "sSpellmakingHelp6", "sSpellmakingMenu1", "sSpellmakingMenuTitle", "sSpells", "sSpellServiceTitle", "sSpellsword", "sStartCell", "sStartCellError", "sStartError", "sStats", "sStrafe", "sStrDesc", "sStrip", "sSubtitles", "sSystemMenuXbox", "sTake", "sTakeAll", "sTargetCriticalStrike", "sTaunt", "sTauntFail", "sTauntSuccess", "sTeleportDisabled", "sThief", "sThrust", "sTo", "sTogglePOVCmd", "sTogglePOVXbox", "sToggleRunXbox", "sTopics", "sTotalCost", "sTotalSold", "sTraining", "sTrainingServiceTitle", "sTraits", "sTransparency_Menu", "sTrapFail", "sTrapImpossible", "sTrapped", "sTrapSuccess", "sTravel", "sTravelServiceTitle", "sTurn", "sTurnLeftXbox", "sTurnRightXbox", "sTwoHanded", "sType", "sTypeAbility", "sTypeBlightDisease", "sTypeCurse", "sTypeDisease", "sTypePower", "sTypeSpell", "sUnequip", "sUnlocked", "sUntilHealed", "sUse", "sUserDefinedClass", "sUses", "sUseXbox", "sValue", "sVideo", "sVideoWarning", "sVoice", "sWait", "sWarrior", "sWaterReflectUpdate", "sWaterTerrainReflect", "sWeaponTab", "sWeight", "sWerewolfAlarmMessage", "sWerewolfPopup", "sWerewolfRefusal", "sWerewolfRestMessage", "sWilDesc", "sWitchhunter", "sWorld", "sWornTab", "sXStrafe", "sXTimes", "sXTimesINT", "sYes", "sYourGold" }; const char * CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::OptionalFloatCount] = { "fCombatDistanceWerewolfMod", "fFleeDistance", "fWereWolfAcrobatics", "fWereWolfAgility", "fWereWolfAlchemy", "fWereWolfAlteration", "fWereWolfArmorer", "fWereWolfAthletics", "fWereWolfAxe", "fWereWolfBlock", "fWereWolfBluntWeapon", "fWereWolfConjuration", "fWereWolfDestruction", "fWereWolfEnchant", "fWereWolfEndurance", "fWereWolfFatigue", "fWereWolfHandtoHand", "fWereWolfHealth", "fWereWolfHeavyArmor", "fWereWolfIllusion", "fWereWolfIntellegence", "fWereWolfLightArmor", "fWereWolfLongBlade", "fWereWolfLuck", "fWereWolfMagicka", "fWereWolfMarksman", "fWereWolfMediumArmor", "fWereWolfMerchantile", "fWereWolfMysticism", "fWereWolfPersonality", "fWereWolfRestoration", "fWereWolfRunMult", "fWereWolfSecurity", "fWereWolfShortBlade", "fWereWolfSilverWeaponDamageMult", "fWereWolfSneak", "fWereWolfSpear", "fWereWolfSpeechcraft", "fWereWolfSpeed", "fWereWolfStrength", "fWereWolfUnarmored", "fWereWolfWillPower" }; const char * CSMWorld::DefaultGmsts::OptionalInts[CSMWorld::DefaultGmsts::OptionalIntCount] = { "iWereWolfBounty", "iWereWolfFightMod", "iWereWolfFleeMod", "iWereWolfLevelToAttack" }; const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::OptionalStringCount] = { "sCompanionShare", "sCompanionWarningButtonOne", "sCompanionWarningButtonTwo", "sCompanionWarningMessage", "sDeleteNote", "sEditNote", "sEffectSummonCreature01", "sEffectSummonCreature02", "sEffectSummonCreature03", "sEffectSummonCreature04", "sEffectSummonCreature05", "sEffectSummonFabricant", "sLevitateDisabled", "sMagicCreature01ID", "sMagicCreature02ID", "sMagicCreature03ID", "sMagicCreature04ID", "sMagicCreature05ID", "sMagicFabricantID", "sMaxSale", "sProfitValue", "sTeleportDisabled", "sWerewolfAlarmMessage", "sWerewolfPopup", "sWerewolfRefusal", "sWerewolfRestMessage" }; const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = { 0.3f, // fAIFleeFleeMult 7.0f, // fAIFleeHealthMult 3.0f, // fAIMagicSpellMult 1.0f, // fAIMeleeArmorMult 1.0f, // fAIMeleeSummWeaponMult 2.0f, // fAIMeleeWeaponMult 5.0f, // fAIRangeMagicSpellMult 5.0f, // fAIRangeMeleeWeaponMult 2000.0f, // fAlarmRadius 1.0f, // fAthleticsRunBonus 40.0f, // fAudioDefaultMaxDistance 5.0f, // fAudioDefaultMinDistance 50.0f, // fAudioMaxDistanceMult 20.0f, // fAudioMinDistanceMult 60.0f, // fAudioVoiceDefaultMaxDistance 10.0f, // fAudioVoiceDefaultMinDistance 50.0f, // fAutoPCSpellChance 80.0f, // fAutoSpellChance 50.0f, // fBargainOfferBase -4.0f, // fBargainOfferMulti 24.0f, // fBarterGoldResetDelay 1.75f, // fBaseRunMultiplier 1.25f, // fBlockStillBonus 150.0f, // fBribe1000Mod 75.0f, // fBribe100Mod 35.0f, // fBribe10Mod 60.0f, // fCombatAngleXY 60.0f, // fCombatAngleZ 0.25f, // fCombatArmorMinMult -90.0f, // fCombatBlockLeftAngle 30.0f, // fCombatBlockRightAngle 4.0f, // fCombatCriticalStrikeMult 0.1f, // fCombatDelayCreature 0.1f, // fCombatDelayNPC 128.0f, // fCombatDistance 0.3f, // fCombatDistanceWerewolfMod 30.0f, // fCombatForceSideAngle 0.2f, // fCombatInvisoMult 1.5f, // fCombatKODamageMult 45.0f, // fCombatTorsoSideAngle 0.3f, // fCombatTorsoStartPercent 0.8f, // fCombatTorsoStopPercent 15.0f, // fConstantEffectMult 72.0f, // fCorpseClearDelay 72.0f, // fCorpseRespawnDelay 0.5f, // fCrimeGoldDiscountMult 0.9f, // fCrimeGoldTurnInMult 1.0f, // fCrimeStealing 0.5f, // fDamageStrengthBase 0.1f, // fDamageStrengthMult 5.0f, // fDifficultyMult 2.5f, // fDiseaseXferChance -10.0f, // fDispAttacking -1.0f, // fDispBargainFailMod 1.0f, // fDispBargainSuccessMod 0.0f, // fDispCrimeMod -10.0f, // fDispDiseaseMod 3.0f, // fDispFactionMod 1.0f, // fDispFactionRankBase 0.5f, // fDispFactionRankMult 1.0f, // fDispositionMod 50.0f, // fDispPersonalityBase 0.5f, // fDispPersonalityMult -25.0f, // fDispPickPocketMod 5.0f, // fDispRaceMod -0.5f, // fDispStealing -5.0f, // fDispWeaponDrawn 0.5f, // fEffectCostMult 0.1f, // fElementalShieldMult 3.0f, // fEnchantmentChanceMult 0.5f, // fEnchantmentConstantChanceMult 100.0f, // fEnchantmentConstantDurationMult 0.1f, // fEnchantmentMult 1000.0f, // fEnchantmentValueMult 0.3f, // fEncumberedMoveEffect 5.0f, // fEncumbranceStrMult 0.04f, // fEndFatigueMult 0.25f, // fFallAcroBase 0.01f, // fFallAcroMult 400.0f, // fFallDamageDistanceMin 0.0f, // fFallDistanceBase 0.07f, // fFallDistanceMult 2.0f, // fFatigueAttackBase 0.0f, // fFatigueAttackMult 1.25f, // fFatigueBase 4.0f, // fFatigueBlockBase 0.0f, // fFatigueBlockMult 5.0f, // fFatigueJumpBase 0.0f, // fFatigueJumpMult 0.5f, // fFatigueMult 2.5f, // fFatigueReturnBase 0.02f, // fFatigueReturnMult 5.0f, // fFatigueRunBase 2.0f, // fFatigueRunMult 1.5f, // fFatigueSneakBase 1.5f, // fFatigueSneakMult 0.0f, // fFatigueSpellBase 0.0f, // fFatigueSpellCostMult 0.0f, // fFatigueSpellMult 7.0f, // fFatigueSwimRunBase 0.0f, // fFatigueSwimRunMult 2.5f, // fFatigueSwimWalkBase 0.0f, // fFatigueSwimWalkMult 0.2f, // fFightDispMult 0.005f, // fFightDistanceMultiplier 50.0f, // fFightStealing 3000.0f, // fFleeDistance 512.0f, // fGreetDistanceReset 0.1f, // fHandtoHandHealthPer 1.0f, // fHandToHandReach 0.5f, // fHoldBreathEndMult 20.0f, // fHoldBreathTime 0.75f, // fIdleChanceMultiplier 1.0f, // fIngredientMult 0.5f, // fInteriorHeadTrackMult 128.0f, // fJumpAcrobaticsBase 4.0f, // fJumpAcroMultiplier 0.5f, // fJumpEncumbranceBase 1.0f, // fJumpEncumbranceMultiplier 0.5f, // fJumpMoveBase 0.5f, // fJumpMoveMult 1.0f, // fJumpRunMultiplier 0.5f, // fKnockDownMult 5.0f, // fLevelMod 0.1f, // fLevelUpHealthEndMult 0.6f, // fLightMaxMod 10.0f, // fLuckMod 10.0f, // fMagesGuildTravel 1.5f, // fMagicCreatureCastDelay 0.0167f, // fMagicDetectRefreshRate 1.0f, // fMagicItemConstantMult 1.0f, // fMagicItemCostMult 1.0f, // fMagicItemOnceMult 1.0f, // fMagicItemPriceMult 0.05f, // fMagicItemRechargePerSecond 1.0f, // fMagicItemStrikeMult 1.0f, // fMagicItemUsedMult 3.0f, // fMagicStartIconBlink 0.5f, // fMagicSunBlockedMult 0.75f, // fMajorSkillBonus 300.0f, // fMaxFlySpeed 0.5f, // fMaxHandToHandMult 400.0f, // fMaxHeadTrackDistance 200.0f, // fMaxWalkSpeed 300.0f, // fMaxWalkSpeedCreature 0.9f, // fMedMaxMod 0.1f, // fMessageTimePerChar 5.0f, // fMinFlySpeed 0.1f, // fMinHandToHandMult 1.0f, // fMinorSkillBonus 100.0f, // fMinWalkSpeed 5.0f, // fMinWalkSpeedCreature 1.25f, // fMiscSkillBonus 2.0f, // fNPCbaseMagickaMult 0.5f, // fNPCHealthBarFade 3.0f, // fNPCHealthBarTime 1.0f, // fPCbaseMagickaMult 0.3f, // fPerDieRollMult 5.0f, // fPersonalityMod 1.0f, // fPerTempMult -1.0f, // fPickLockMult 0.3f, // fPickPocketMod 20.0f, // fPotionMinUsefulDuration 0.5f, // fPotionStrengthMult 0.5f, // fPotionT1DurMult 1.5f, // fPotionT1MagMult 20.0f, // fPotionT4BaseStrengthMult 12.0f, // fPotionT4EquipStrengthMult 3000.0f, // fProjectileMaxSpeed 400.0f, // fProjectileMinSpeed 25.0f, // fProjectileThrownStoreChance 3.0f, // fRepairAmountMult 1.0f, // fRepairMult 1.0f, // fReputationMod 0.15f, // fRestMagicMult 0.0f, // fSeriousWoundMult 0.25f, // fSleepRandMod 0.3f, // fSleepRestMod -1.0f, // fSneakBootMult 0.5f, // fSneakDistanceBase 0.002f, // fSneakDistanceMultiplier 0.5f, // fSneakNoViewMult 1.0f, // fSneakSkillMult 0.75f, // fSneakSpeedMultiplier 1.0f, // fSneakUseDelay 500.0f, // fSneakUseDist 1.5f, // fSneakViewMult 3.0f, // fSoulGemMult 0.8f, // fSpecialSkillBonus 7.0f, // fSpellMakingValueMult 2.0f, // fSpellPriceMult 10.0f, // fSpellValueMult 0.25f, // fStromWalkMult 0.7f, // fStromWindSpeed 3.0f, // fSuffocationDamage 0.9f, // fSwimHeightScale 0.1f, // fSwimRunAthleticsMult 0.5f, // fSwimRunBase 0.02f, // fSwimWalkAthleticsMult 0.5f, // fSwimWalkBase 1.0f, // fSwingBlockBase 1.0f, // fSwingBlockMult 1000.0f, // fTargetSpellMaxSpeed 1000.0f, // fThrownWeaponMaxSpeed 300.0f, // fThrownWeaponMinSpeed 0.0f, // fTrapCostMult 4000.0f, // fTravelMult 16000.0f,// fTravelTimeMult 0.1f, // fUnarmoredBase1 0.065f, // fUnarmoredBase2 30.0f, // fVanityDelay 10.0f, // fVoiceIdleOdds 0.0f, // fWaterReflectUpdateAlways 10.0f, // fWaterReflectUpdateSeldom 0.1f, // fWeaponDamageMult 1.0f, // fWeaponFatigueBlockMult 0.25f, // fWeaponFatigueMult 150.0f, // fWereWolfAcrobatics 150.0f, // fWereWolfAgility 1.0f, // fWereWolfAlchemy 1.0f, // fWereWolfAlteration 1.0f, // fWereWolfArmorer 150.0f, // fWereWolfAthletics 1.0f, // fWereWolfAxe 1.0f, // fWereWolfBlock 1.0f, // fWereWolfBluntWeapon 1.0f, // fWereWolfConjuration 1.0f, // fWereWolfDestruction 1.0f, // fWereWolfEnchant 150.0f, // fWereWolfEndurance 400.0f, // fWereWolfFatigue 100.0f, // fWereWolfHandtoHand 2.0f, // fWereWolfHealth 1.0f, // fWereWolfHeavyArmor 1.0f, // fWereWolfIllusion 1.0f, // fWereWolfIntellegence 1.0f, // fWereWolfLightArmor 1.0f, // fWereWolfLongBlade 1.0f, // fWereWolfLuck 100.0f, // fWereWolfMagicka 1.0f, // fWereWolfMarksman 1.0f, // fWereWolfMediumArmor 1.0f, // fWereWolfMerchantile 1.0f, // fWereWolfMysticism 1.0f, // fWereWolfPersonality 1.0f, // fWereWolfRestoration 1.5f, // fWereWolfRunMult 1.0f, // fWereWolfSecurity 1.0f, // fWereWolfShortBlade 1.5f, // fWereWolfSilverWeaponDamageMult 1.0f, // fWereWolfSneak 1.0f, // fWereWolfSpear 1.0f, // fWereWolfSpeechcraft 150.0f, // fWereWolfSpeed 150.0f, // fWereWolfStrength 100.0f, // fWereWolfUnarmored 1.0f, // fWereWolfWillPower 15.0f // fWortChanceValue }; const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = { 10, // i1stPersonSneakDelta 50, // iAlarmAttack 90, // iAlarmKilling 20, // iAlarmPickPocket 1, // iAlarmStealing 5, // iAlarmTresspass 2, // iAlchemyMod 100, // iAutoPCSpellMax 2, // iAutoRepFacMod 0, // iAutoRepLevMod 5, // iAutoSpellAlterationMax 70, // iAutoSpellAttSkillMin 2, // iAutoSpellConjurationMax 5, // iAutoSpellDestructionMax 5, // iAutoSpellIllusionMax 5, // iAutoSpellMysticismMax 5, // iAutoSpellRestorationMax 3, // iAutoSpellTimesCanCast -1, // iBarterFailDisposition 1, // iBarterSuccessDisposition 30, // iBaseArmorSkill 50, // iBlockMaxChance 10, // iBlockMinChance 20, // iBootsWeight 40, // iCrimeAttack 1000, // iCrimeKilling 25, // iCrimePickPocket 1000, // iCrimeThreshold 10, // iCrimeThresholdMultiplier 5, // iCrimeTresspass 30, // iCuirassWeight 100, // iDaysinPrisonMod -50, // iDispAttackMod -50, // iDispKilling -20, // iDispTresspass 1, // iFightAlarmMult 100, // iFightAttack 50, // iFightAttacking 20, // iFightDistanceBase 50, // iFightKilling 25, // iFightPickpocket 25, // iFightTrespass 0, // iFlee 5, // iGauntletWeight 15, // iGreavesWeight 6, // iGreetDistanceMultiplier 4, // iGreetDuration 5, // iHelmWeight 50, // iKnockDownOddsBase 50, // iKnockDownOddsMult 2, // iLevelUp01Mult 2, // iLevelUp02Mult 2, // iLevelUp03Mult 2, // iLevelUp04Mult 3, // iLevelUp05Mult 3, // iLevelUp06Mult 3, // iLevelUp07Mult 4, // iLevelUp08Mult 4, // iLevelUp09Mult 5, // iLevelUp10Mult 1, // iLevelupMajorMult 1, // iLevelupMajorMultAttribute 1, // iLevelupMinorMult 1, // iLevelupMinorMultAttribute 1, // iLevelupMiscMultAttriubte 1, // iLevelupSpecialization 10, // iLevelupTotal 10, // iMagicItemChargeConst 1, // iMagicItemChargeOnce 10, // iMagicItemChargeStrike 5, // iMagicItemChargeUse 192, // iMaxActivateDist 192, // iMaxInfoDist 4, // iMonthsToRespawn 1, // iNumberCreatures 10, // iPauldronWeight 5, // iPerMinChance 10, // iPerMinChange 75, // iPickMaxChance 5, // iPickMinChance 15, // iShieldWeight 400, // iSoulAmountForConstantEffect 10, // iTrainingMod 10, // iVoiceAttackOdds 30, // iVoiceHitOdds 10000, // iWereWolfBounty 100, // iWereWolfFightMod 100, // iWereWolfFleeMod 20 // iWereWolfLevelToAttack }; const float CSMWorld::DefaultGmsts::FloatLimits[CSMWorld::DefaultGmsts::FloatCount * 2] = { -FInf, FInf, // fAIFleeFleeMult -FInf, FInf, // fAIFleeHealthMult -FInf, FInf, // fAIMagicSpellMult -FInf, FInf, // fAIMeleeArmorMult -FInf, FInf, // fAIMeleeSummWeaponMult -FInf, FInf, // fAIMeleeWeaponMult -FInf, FInf, // fAIRangeMagicSpellMult -FInf, FInf, // fAIRangeMeleeWeaponMult 0, FInf, // fAlarmRadius -FInf, FInf, // fAthleticsRunBonus 0, FInf, // fAudioDefaultMaxDistance 0, FInf, // fAudioDefaultMinDistance 0, FInf, // fAudioMaxDistanceMult 0, FInf, // fAudioMinDistanceMult 0, FInf, // fAudioVoiceDefaultMaxDistance 0, FInf, // fAudioVoiceDefaultMinDistance 0, FInf, // fAutoPCSpellChance 0, FInf, // fAutoSpellChance -FInf, FInf, // fBargainOfferBase -FInf, 0, // fBargainOfferMulti -FInf, FInf, // fBarterGoldResetDelay 0, FInf, // fBaseRunMultiplier -FInf, FInf, // fBlockStillBonus 0, FInf, // fBribe1000Mod 0, FInf, // fBribe100Mod 0, FInf, // fBribe10Mod 0, FInf, // fCombatAngleXY 0, FInf, // fCombatAngleZ 0, 1, // fCombatArmorMinMult -180, 0, // fCombatBlockLeftAngle 0, 180, // fCombatBlockRightAngle 0, FInf, // fCombatCriticalStrikeMult 0, FInf, // fCombatDelayCreature 0, FInf, // fCombatDelayNPC 0, FInf, // fCombatDistance -FInf, FInf, // fCombatDistanceWerewolfMod -FInf, FInf, // fCombatForceSideAngle 0, FInf, // fCombatInvisoMult 0, FInf, // fCombatKODamageMult -FInf, FInf, // fCombatTorsoSideAngle -FInf, FInf, // fCombatTorsoStartPercent -FInf, FInf, // fCombatTorsoStopPercent -FInf, FInf, // fConstantEffectMult -FInf, FInf, // fCorpseClearDelay -FInf, FInf, // fCorpseRespawnDelay 0, 1, // fCrimeGoldDiscountMult 0, FInf, // fCrimeGoldTurnInMult 0, FInf, // fCrimeStealing 0, FInf, // fDamageStrengthBase 0, FInf, // fDamageStrengthMult -FInf, FInf, // fDifficultyMult 0, FInf, // fDiseaseXferChance -FInf, 0, // fDispAttacking -FInf, FInf, // fDispBargainFailMod -FInf, FInf, // fDispBargainSuccessMod -FInf, 0, // fDispCrimeMod -FInf, 0, // fDispDiseaseMod 0, FInf, // fDispFactionMod 0, FInf, // fDispFactionRankBase 0, FInf, // fDispFactionRankMult 0, FInf, // fDispositionMod 0, FInf, // fDispPersonalityBase 0, FInf, // fDispPersonalityMult -FInf, 0, // fDispPickPocketMod 0, FInf, // fDispRaceMod -FInf, 0, // fDispStealing -FInf, 0, // fDispWeaponDrawn 0, FInf, // fEffectCostMult 0, FInf, // fElementalShieldMult FEps, FInf, // fEnchantmentChanceMult 0, FInf, // fEnchantmentConstantChanceMult 0, FInf, // fEnchantmentConstantDurationMult 0, FInf, // fEnchantmentMult 0, FInf, // fEnchantmentValueMult 0, FInf, // fEncumberedMoveEffect 0, FInf, // fEncumbranceStrMult 0, FInf, // fEndFatigueMult -FInf, FInf, // fFallAcroBase 0, FInf, // fFallAcroMult 0, FInf, // fFallDamageDistanceMin -FInf, FInf, // fFallDistanceBase 0, FInf, // fFallDistanceMult -FInf, FInf, // fFatigueAttackBase 0, FInf, // fFatigueAttackMult 0, FInf, // fFatigueBase 0, FInf, // fFatigueBlockBase 0, FInf, // fFatigueBlockMult 0, FInf, // fFatigueJumpBase 0, FInf, // fFatigueJumpMult 0, FInf, // fFatigueMult -FInf, FInf, // fFatigueReturnBase 0, FInf, // fFatigueReturnMult -FInf, FInf, // fFatigueRunBase 0, FInf, // fFatigueRunMult -FInf, FInf, // fFatigueSneakBase 0, FInf, // fFatigueSneakMult -FInf, FInf, // fFatigueSpellBase -FInf, FInf, // fFatigueSpellCostMult 0, FInf, // fFatigueSpellMult -FInf, FInf, // fFatigueSwimRunBase 0, FInf, // fFatigueSwimRunMult -FInf, FInf, // fFatigueSwimWalkBase 0, FInf, // fFatigueSwimWalkMult -FInf, FInf, // fFightDispMult -FInf, FInf, // fFightDistanceMultiplier -FInf, FInf, // fFightStealing -FInf, FInf, // fFleeDistance -FInf, FInf, // fGreetDistanceReset 0, FInf, // fHandtoHandHealthPer 0, FInf, // fHandToHandReach -FInf, FInf, // fHoldBreathEndMult 0, FInf, // fHoldBreathTime 0, FInf, // fIdleChanceMultiplier -FInf, FInf, // fIngredientMult 0, FInf, // fInteriorHeadTrackMult -FInf, FInf, // fJumpAcrobaticsBase 0, FInf, // fJumpAcroMultiplier -FInf, FInf, // fJumpEncumbranceBase 0, FInf, // fJumpEncumbranceMultiplier -FInf, FInf, // fJumpMoveBase 0, FInf, // fJumpMoveMult 0, FInf, // fJumpRunMultiplier -FInf, FInf, // fKnockDownMult 0, FInf, // fLevelMod 0, FInf, // fLevelUpHealthEndMult 0, FInf, // fLightMaxMod 0, FInf, // fLuckMod 0, FInf, // fMagesGuildTravel -FInf, FInf, // fMagicCreatureCastDelay -FInf, FInf, // fMagicDetectRefreshRate -FInf, FInf, // fMagicItemConstantMult -FInf, FInf, // fMagicItemCostMult -FInf, FInf, // fMagicItemOnceMult -FInf, FInf, // fMagicItemPriceMult 0, FInf, // fMagicItemRechargePerSecond -FInf, FInf, // fMagicItemStrikeMult -FInf, FInf, // fMagicItemUsedMult 0, FInf, // fMagicStartIconBlink 0, FInf, // fMagicSunBlockedMult FEps, FInf, // fMajorSkillBonus 0, FInf, // fMaxFlySpeed 0, FInf, // fMaxHandToHandMult 0, FInf, // fMaxHeadTrackDistance 0, FInf, // fMaxWalkSpeed 0, FInf, // fMaxWalkSpeedCreature 0, FInf, // fMedMaxMod 0, FInf, // fMessageTimePerChar 0, FInf, // fMinFlySpeed 0, FInf, // fMinHandToHandMult FEps, FInf, // fMinorSkillBonus 0, FInf, // fMinWalkSpeed 0, FInf, // fMinWalkSpeedCreature FEps, FInf, // fMiscSkillBonus 0, FInf, // fNPCbaseMagickaMult 0, FInf, // fNPCHealthBarFade 0, FInf, // fNPCHealthBarTime 0, FInf, // fPCbaseMagickaMult 0, FInf, // fPerDieRollMult 0, FInf, // fPersonalityMod 0, FInf, // fPerTempMult -FInf, 0, // fPickLockMult 0, FInf, // fPickPocketMod -FInf, FInf, // fPotionMinUsefulDuration 0, FInf, // fPotionStrengthMult FEps, FInf, // fPotionT1DurMult FEps, FInf, // fPotionT1MagMult -FInf, FInf, // fPotionT4BaseStrengthMult -FInf, FInf, // fPotionT4EquipStrengthMult 0, FInf, // fProjectileMaxSpeed 0, FInf, // fProjectileMinSpeed 0, FInf, // fProjectileThrownStoreChance 0, FInf, // fRepairAmountMult 0, FInf, // fRepairMult 0, FInf, // fReputationMod 0, FInf, // fRestMagicMult -FInf, FInf, // fSeriousWoundMult 0, FInf, // fSleepRandMod 0, FInf, // fSleepRestMod -FInf, 0, // fSneakBootMult -FInf, FInf, // fSneakDistanceBase 0, FInf, // fSneakDistanceMultiplier 0, FInf, // fSneakNoViewMult 0, FInf, // fSneakSkillMult 0, FInf, // fSneakSpeedMultiplier 0, FInf, // fSneakUseDelay 0, FInf, // fSneakUseDist 0, FInf, // fSneakViewMult 0, FInf, // fSoulGemMult 0, FInf, // fSpecialSkillBonus 0, FInf, // fSpellMakingValueMult -FInf, FInf, // fSpellPriceMult 0, FInf, // fSpellValueMult 0, FInf, // fStromWalkMult 0, FInf, // fStromWindSpeed 0, FInf, // fSuffocationDamage 0, FInf, // fSwimHeightScale 0, FInf, // fSwimRunAthleticsMult 0, FInf, // fSwimRunBase -FInf, FInf, // fSwimWalkAthleticsMult -FInf, FInf, // fSwimWalkBase 0, FInf, // fSwingBlockBase 0, FInf, // fSwingBlockMult 0, FInf, // fTargetSpellMaxSpeed 0, FInf, // fThrownWeaponMaxSpeed 0, FInf, // fThrownWeaponMinSpeed 0, FInf, // fTrapCostMult 0, FInf, // fTravelMult 0, FInf, // fTravelTimeMult 0, FInf, // fUnarmoredBase1 0, FInf, // fUnarmoredBase2 0, FInf, // fVanityDelay 0, FInf, // fVoiceIdleOdds -FInf, FInf, // fWaterReflectUpdateAlways -FInf, FInf, // fWaterReflectUpdateSeldom 0, FInf, // fWeaponDamageMult 0, FInf, // fWeaponFatigueBlockMult 0, FInf, // fWeaponFatigueMult 0, FInf, // fWereWolfAcrobatics -FInf, FInf, // fWereWolfAgility -FInf, FInf, // fWereWolfAlchemy -FInf, FInf, // fWereWolfAlteration -FInf, FInf, // fWereWolfArmorer -FInf, FInf, // fWereWolfAthletics -FInf, FInf, // fWereWolfAxe -FInf, FInf, // fWereWolfBlock -FInf, FInf, // fWereWolfBluntWeapon -FInf, FInf, // fWereWolfConjuration -FInf, FInf, // fWereWolfDestruction -FInf, FInf, // fWereWolfEnchant -FInf, FInf, // fWereWolfEndurance -FInf, FInf, // fWereWolfFatigue -FInf, FInf, // fWereWolfHandtoHand -FInf, FInf, // fWereWolfHealth -FInf, FInf, // fWereWolfHeavyArmor -FInf, FInf, // fWereWolfIllusion -FInf, FInf, // fWereWolfIntellegence -FInf, FInf, // fWereWolfLightArmor -FInf, FInf, // fWereWolfLongBlade -FInf, FInf, // fWereWolfLuck -FInf, FInf, // fWereWolfMagicka -FInf, FInf, // fWereWolfMarksman -FInf, FInf, // fWereWolfMediumArmor -FInf, FInf, // fWereWolfMerchantile -FInf, FInf, // fWereWolfMysticism -FInf, FInf, // fWereWolfPersonality -FInf, FInf, // fWereWolfRestoration 0, FInf, // fWereWolfRunMult -FInf, FInf, // fWereWolfSecurity -FInf, FInf, // fWereWolfShortBlade -FInf, FInf, // fWereWolfSilverWeaponDamageMult -FInf, FInf, // fWereWolfSneak -FInf, FInf, // fWereWolfSpear -FInf, FInf, // fWereWolfSpeechcraft -FInf, FInf, // fWereWolfSpeed -FInf, FInf, // fWereWolfStrength -FInf, FInf, // fWereWolfUnarmored -FInf, FInf, // fWereWolfWillPower 0, FInf // fWortChanceValue }; const int CSMWorld::DefaultGmsts::IntLimits[CSMWorld::DefaultGmsts::IntCount * 2] = { IMin, IMax, // i1stPersonSneakDelta IMin, IMax, // iAlarmAttack IMin, IMax, // iAlarmKilling IMin, IMax, // iAlarmPickPocket IMin, IMax, // iAlarmStealing IMin, IMax, // iAlarmTresspass IMin, IMax, // iAlchemyMod 0, IMax, // iAutoPCSpellMax IMin, IMax, // iAutoRepFacMod IMin, IMax, // iAutoRepLevMod IMin, IMax, // iAutoSpellAlterationMax 0, IMax, // iAutoSpellAttSkillMin IMin, IMax, // iAutoSpellConjurationMax IMin, IMax, // iAutoSpellDestructionMax IMin, IMax, // iAutoSpellIllusionMax IMin, IMax, // iAutoSpellMysticismMax IMin, IMax, // iAutoSpellRestorationMax 0, IMax, // iAutoSpellTimesCanCast IMin, 0, // iBarterFailDisposition 0, IMax, // iBarterSuccessDisposition 1, IMax, // iBaseArmorSkill 0, IMax, // iBlockMaxChance 0, IMax, // iBlockMinChance 0, IMax, // iBootsWeight IMin, IMax, // iCrimeAttack IMin, IMax, // iCrimeKilling IMin, IMax, // iCrimePickPocket 0, IMax, // iCrimeThreshold 0, IMax, // iCrimeThresholdMultiplier IMin, IMax, // iCrimeTresspass 0, IMax, // iCuirassWeight 1, IMax, // iDaysinPrisonMod IMin, 0, // iDispAttackMod IMin, 0, // iDispKilling IMin, 0, // iDispTresspass IMin, IMax, // iFightAlarmMult IMin, IMax, // iFightAttack IMin, IMax, // iFightAttacking 0, IMax, // iFightDistanceBase IMin, IMax, // iFightKilling IMin, IMax, // iFightPickpocket IMin, IMax, // iFightTrespass IMin, IMax, // iFlee 0, IMax, // iGauntletWeight 0, IMax, // iGreavesWeight 0, IMax, // iGreetDistanceMultiplier 0, IMax, // iGreetDuration 0, IMax, // iHelmWeight IMin, IMax, // iKnockDownOddsBase IMin, IMax, // iKnockDownOddsMult IMin, IMax, // iLevelUp01Mult IMin, IMax, // iLevelUp02Mult IMin, IMax, // iLevelUp03Mult IMin, IMax, // iLevelUp04Mult IMin, IMax, // iLevelUp05Mult IMin, IMax, // iLevelUp06Mult IMin, IMax, // iLevelUp07Mult IMin, IMax, // iLevelUp08Mult IMin, IMax, // iLevelUp09Mult IMin, IMax, // iLevelUp10Mult IMin, IMax, // iLevelupMajorMult IMin, IMax, // iLevelupMajorMultAttribute IMin, IMax, // iLevelupMinorMult IMin, IMax, // iLevelupMinorMultAttribute IMin, IMax, // iLevelupMiscMultAttriubte IMin, IMax, // iLevelupSpecialization IMin, IMax, // iLevelupTotal IMin, IMax, // iMagicItemChargeConst IMin, IMax, // iMagicItemChargeOnce IMin, IMax, // iMagicItemChargeStrike IMin, IMax, // iMagicItemChargeUse IMin, IMax, // iMaxActivateDist IMin, IMax, // iMaxInfoDist 0, IMax, // iMonthsToRespawn 0, IMax, // iNumberCreatures 0, IMax, // iPauldronWeight 0, IMax, // iPerMinChance 0, IMax, // iPerMinChange 0, IMax, // iPickMaxChance 0, IMax, // iPickMinChance 0, IMax, // iShieldWeight 0, IMax, // iSoulAmountForConstantEffect 0, IMax, // iTrainingMod 0, IMax, // iVoiceAttackOdds 0, IMax, // iVoiceHitOdds IMin, IMax, // iWereWolfBounty IMin, IMax, // iWereWolfFightMod IMin, IMax, // iWereWolfFleeMod IMin, IMax // iWereWolfLevelToAttack }; openmw-openmw-0.48.0/apps/opencs/model/world/defaultgmsts.hpp000066400000000000000000000015451445372753700243350ustar00rootroot00000000000000#ifndef CSM_WORLD_DEFAULTGMSTS_H #define CSM_WORLD_DEFAULTGMSTS_H #include namespace CSMWorld { namespace DefaultGmsts { const size_t FloatCount = 258; const size_t IntCount = 89; const size_t StringCount = 1174; const size_t OptionalFloatCount = 42; const size_t OptionalIntCount = 4; const size_t OptionalStringCount = 26; extern const char* Floats[]; extern const char * Ints[]; extern const char * Strings[]; extern const char * OptionalFloats[]; extern const char * OptionalInts[]; extern const char * OptionalStrings[]; extern const float FloatsDefaultValues[]; extern const int IntsDefaultValues[]; extern const float FloatLimits[]; extern const int IntLimits[]; } } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idcollection.cpp000066400000000000000000000022071445372753700242720ustar00rootroot00000000000000#include "idcollection.hpp" namespace CSMWorld { template<> int IdCollection >::load (ESM::ESMReader& reader, bool base) { Pathgrid record; bool isDeleted = false; loadRecord (record, reader, isDeleted); std::string id = IdAccessor().getId (record); int index = this->searchId (id); if (record.mPoints.empty() || record.mEdges.empty()) isDeleted = true; if (isDeleted) { if (index==-1) { // deleting a record that does not exist // ignore it for now /// \todo report the problem to the user return -1; } if (base) { this->removeRows (index, 1); return -1; } auto baseRecord = std::make_unique>(this->getRecord(index)); baseRecord->mState = RecordBase::State_Deleted; this->setRecord(index, std::move(baseRecord)); return index; } return load (record, base, index); } } openmw-openmw-0.48.0/apps/opencs/model/world/idcollection.hpp000066400000000000000000000122111445372753700242730ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDCOLLECTION_H #define CSM_WOLRD_IDCOLLECTION_H #include #include "collection.hpp" #include "land.hpp" #include "pathgrid.hpp" namespace CSMWorld { /// \brief Single type collection of top level records template > class IdCollection : public Collection { virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: /// \return Index of loaded record (-1 if no record was loaded) int load (ESM::ESMReader& reader, bool base); /// \param index Index at which the record can be found. /// Special values: -2 index unknown, -1 record does not exist yet and therefore /// does not have an index /// /// \return index int load (const ESXRecordT& record, bool base, int index = -2); bool tryDelete (const std::string& id); ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. /// /// \return Has the ID been deleted? }; template void IdCollection::loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) { record.load (reader, isDeleted); } template<> inline void IdCollection >::loadRecord (Land& record, ESM::ESMReader& reader, bool& isDeleted) { record.load (reader, isDeleted); // Load all land data for now. A future optimisation may only load non-base data // if a suitable mechanism for avoiding race conditions can be established. int flags = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; record.loadData (flags); // Prevent data from being reloaded. record.mContext.filename.clear(); } template int IdCollection::load (ESM::ESMReader& reader, bool base) { ESXRecordT record; bool isDeleted = false; loadRecord (record, reader, isDeleted); std::string id = IdAccessorT().getId (record); int index = this->searchId (id); if (isDeleted) { if (index==-1) { // deleting a record that does not exist // ignore it for now /// \todo report the problem to the user return -1; } if (base) { this->removeRows (index, 1); return -1; } auto baseRecord = std::make_unique>(this->getRecord(index)); baseRecord->mState = RecordBase::State_Deleted; this->setRecord(index, std::move(baseRecord)); return index; } return load (record, base, index); } template int IdCollection::load (const ESXRecordT& record, bool base, int index) { if (index==-2) // index unknown index = this->searchId (IdAccessorT().getId (record)); if (index==-1) { // new record auto record2 = std::make_unique>(); record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2->mBase : record2->mModified) = record; index = this->getSize(); this->appendRecord(std::move(record2)); } else { // old record auto record2 = std::make_unique>(Collection::getRecord(index)); if (base) record2->mBase = record; else record2->setModified(record); this->setRecord(index, std::move(record2)); } return index; } template bool IdCollection::tryDelete (const std::string& id) { int index = this->searchId (id); if (index==-1) return false; const Record& record = Collection::getRecord (index); if (record.isDeleted()) return false; if (record.mState==RecordBase::State_ModifiedOnly) { Collection::removeRows (index, 1); } else { auto record2 = std::make_unique>(Collection::getRecord(index)); record2->mState = RecordBase::State_Deleted; this->setRecord(index, std::move(record2)); } return true; } template<> int IdCollection >::load(ESM::ESMReader& reader, bool base); } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idcompletionmanager.cpp000066400000000000000000000130011445372753700256350ustar00rootroot00000000000000#include "idcompletionmanager.hpp" #include #include "../../view/widget/completerpopup.hpp" #include "data.hpp" #include "idtablebase.hpp" namespace { std::map generateModelTypes() { std::map types; types[CSMWorld::ColumnBase::Display_BodyPart ] = CSMWorld::UniversalId::Type_BodyPart; types[CSMWorld::ColumnBase::Display_Cell ] = CSMWorld::UniversalId::Type_Cell; types[CSMWorld::ColumnBase::Display_Class ] = CSMWorld::UniversalId::Type_Class; types[CSMWorld::ColumnBase::Display_CreatureLevelledList] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Creature ] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Enchantment ] = CSMWorld::UniversalId::Type_Enchantment; types[CSMWorld::ColumnBase::Display_Faction ] = CSMWorld::UniversalId::Type_Faction; types[CSMWorld::ColumnBase::Display_GlobalVariable ] = CSMWorld::UniversalId::Type_Global; types[CSMWorld::ColumnBase::Display_Icon ] = CSMWorld::UniversalId::Type_Icon; types[CSMWorld::ColumnBase::Display_Journal ] = CSMWorld::UniversalId::Type_Journal; types[CSMWorld::ColumnBase::Display_Mesh ] = CSMWorld::UniversalId::Type_Mesh; types[CSMWorld::ColumnBase::Display_Miscellaneous ] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Npc ] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Race ] = CSMWorld::UniversalId::Type_Race; types[CSMWorld::ColumnBase::Display_Region ] = CSMWorld::UniversalId::Type_Region; types[CSMWorld::ColumnBase::Display_Referenceable ] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Script ] = CSMWorld::UniversalId::Type_Script; types[CSMWorld::ColumnBase::Display_Skill ] = CSMWorld::UniversalId::Type_Skill; types[CSMWorld::ColumnBase::Display_Sound ] = CSMWorld::UniversalId::Type_Sound; types[CSMWorld::ColumnBase::Display_SoundRes ] = CSMWorld::UniversalId::Type_SoundRes; types[CSMWorld::ColumnBase::Display_Spell ] = CSMWorld::UniversalId::Type_Spell; types[CSMWorld::ColumnBase::Display_Static ] = CSMWorld::UniversalId::Type_Referenceable; types[CSMWorld::ColumnBase::Display_Texture ] = CSMWorld::UniversalId::Type_Texture; types[CSMWorld::ColumnBase::Display_Topic ] = CSMWorld::UniversalId::Type_Topic; types[CSMWorld::ColumnBase::Display_Weapon ] = CSMWorld::UniversalId::Type_Referenceable; return types; } typedef std::map::const_iterator ModelTypeConstIterator; } const std::map CSMWorld::IdCompletionManager::sCompleterModelTypes = generateModelTypes(); std::vector CSMWorld::IdCompletionManager::getDisplayTypes() { std::vector types; ModelTypeConstIterator current = sCompleterModelTypes.begin(); ModelTypeConstIterator end = sCompleterModelTypes.end(); for (; current != end; ++current) { types.push_back(current->first); } // Hack for Display_InfoCondVar types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); return types; } CSMWorld::IdCompletionManager::IdCompletionManager(CSMWorld::Data &data) { generateCompleters(data); } bool CSMWorld::IdCompletionManager::hasCompleterFor(CSMWorld::ColumnBase::Display display) const { return mCompleters.find(display) != mCompleters.end(); } std::shared_ptr CSMWorld::IdCompletionManager::getCompleter(CSMWorld::ColumnBase::Display display) { if (!hasCompleterFor(display)) { throw std::logic_error("This column doesn't have an ID completer"); } return mCompleters[display]; } void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) { ModelTypeConstIterator current = sCompleterModelTypes.begin(); ModelTypeConstIterator end = sCompleterModelTypes.end(); for (; current != end; ++current) { QAbstractItemModel *model = data.getTableModel(current->second); CSMWorld::IdTableBase *table = dynamic_cast(model); if (table != nullptr) { int idColumn = table->searchColumnIndex(CSMWorld::Columns::ColumnId_Id); if (idColumn != -1) { std::shared_ptr completer = std::make_shared(table); completer->setCompletionColumn(idColumn); // The completion role must be Qt::DisplayRole to get the ID values from the model completer->setCompletionRole(Qt::DisplayRole); completer->setCaseSensitivity(Qt::CaseInsensitive); QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); mCompleters[current->first] = completer; } } } } openmw-openmw-0.48.0/apps/opencs/model/world/idcompletionmanager.hpp000066400000000000000000000020251445372753700256460ustar00rootroot00000000000000#ifndef CSM_WORLD_IDCOMPLETIONMANAGER_HPP #define CSM_WORLD_IDCOMPLETIONMANAGER_HPP #include #include #include #include "columnbase.hpp" #include "universalid.hpp" class QCompleter; namespace CSMWorld { class Data; /// \brief Creates and stores all ID completers class IdCompletionManager { static const std::map sCompleterModelTypes; std::map > mCompleters; // Don't allow copying IdCompletionManager(const IdCompletionManager &); IdCompletionManager &operator = (const IdCompletionManager &); void generateCompleters(Data &data); public: static std::vector getDisplayTypes(); IdCompletionManager(Data &data); bool hasCompleterFor(ColumnBase::Display display) const; std::shared_ptr getCompleter(ColumnBase::Display display); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idtable.cpp000066400000000000000000000300261445372753700232260ustar00rootroot00000000000000#include "idtable.hpp" #include #include #include #include #include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" #include "landtexture.hpp" CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) : IdTableBase (features), mIdCollection (idCollection) {} CSMWorld::IdTable::~IdTable() {} int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return mIdCollection->getSize(); } int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return mIdCollection->getColumns(); } QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const { if (index.row() < 0 || index.column() < 0) return QVariant(); if (role==ColumnBase::Role_Display) return QVariant(mIdCollection->getColumn(index.column()).mDisplayType); if (role==ColumnBase::Role_ColumnId) return QVariant (getColumnId (index.column())); if ((role!=Qt::DisplayRole && role!=Qt::EditRole)) return QVariant(); if (role==Qt::EditRole && !mIdCollection->getColumn (index.column()).isEditable()) return QVariant(); return mIdCollection->getData (index.row(), index.column()); } QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation, int role) const { if (orientation==Qt::Vertical) return QVariant(); if (orientation != Qt::Horizontal) throw std::logic_error("Unknown header orientation specified"); if (role==Qt::DisplayRole) return tr (mIdCollection->getColumn (section).getTitle().c_str()); if (role==ColumnBase::Role_Flags) return mIdCollection->getColumn (section).mFlags; if (role==ColumnBase::Role_Display) return mIdCollection->getColumn (section).mDisplayType; if (role==ColumnBase::Role_ColumnId) return getColumnId (section); return QVariant(); } bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value, int role) { if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); if (stateColumn != -1) { if (index.column() == stateColumn) { // modifying the state column can modify other values. we need to tell // views that the whole row has changed. emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount(index.parent()) - 1)); } else { emit dataChanged(index, index); // Modifying a value can also change the Modified status of a record. QModelIndex stateIndex = this->index(index.row(), stateColumn); emit dataChanged(stateIndex, stateIndex); } } else emit dataChanged(index, index); return true; } return false; } Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const { if (!index.isValid()) return Qt::ItemFlags(); Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (mIdCollection->getColumn (index.column()).isUserEditable()) flags |= Qt::ItemIsEditable; int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked); if (blockedColumn != -1 && blockedColumn != index.column()) { bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt(); if (isBlocked) flags = Qt::ItemIsSelectable; // not enabled (to grey out) } return flags; } bool CSMWorld::IdTable::removeRows (int row, int count, const QModelIndex& parent) { if (parent.isValid()) return false; beginRemoveRows (parent, row, row+count-1); mIdCollection->removeRows (row, count); endRemoveRows(); return true; } QModelIndex CSMWorld::IdTable::index (int row, int column, const QModelIndex& parent) const { if (parent.isValid()) return QModelIndex(); if (row<0 || row>=mIdCollection->getSize()) return QModelIndex(); if (column<0 || column>=mIdCollection->getColumns()) return QModelIndex(); return createIndex (row, column); } QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const { return QModelIndex(); } void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) { int index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); mIdCollection->appendBlankRecord (id, type); endInsertRows(); } void CSMWorld::IdTable::addRecordWithData (const std::string& id, const std::map& data, UniversalId::Type type) { int index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); mIdCollection->appendBlankRecord (id, type); for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) { mIdCollection->setData(index, iter->first, iter->second); } endInsertRows(); } void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) { int index = mIdCollection->getAppendIndex (destination, type); beginInsertRows (QModelIndex(), index, index); mIdCollection->cloneRecord(origin, destination, type); endInsertRows(); } bool CSMWorld::IdTable::touchRecord(const std::string& id) { bool changed = mIdCollection->touchRecord(id); int row = mIdCollection->getIndex(id); int column = mIdCollection->searchColumnIndex(Columns::ColumnId_RecordType); if (changed && column != -1) { QModelIndex modelIndex = index(row, column); emit dataChanged(modelIndex, modelIndex); } return changed; } std::string CSMWorld::IdTable::getId(int row) const { return mIdCollection->getId(row); } ///This method can return only indexes to the top level table cells QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { int row = mIdCollection->searchId (id); if (row != -1) return index(row, column); return QModelIndex(); } void CSMWorld::IdTable::setRecord (const std::string& id, std::unique_ptr record, CSMWorld::UniversalId::Type type) { int index = mIdCollection->searchId (id); if (index==-1) { // For info records, appendRecord may use a different index than the one returned by // getAppendIndex (because of prev/next links). This can result in the display not // updating correctly after an undo // // Use an alternative method to get the correct index. For non-Info records the // record pointer is ignored and internally calls getAppendIndex. int index2 = mIdCollection->getInsertIndex (id, type, record.get()); beginInsertRows (QModelIndex(), index2, index2); mIdCollection->appendRecord (std::move(record), type); endInsertRows(); } else { mIdCollection->replace (index, std::move(record)); emit dataChanged (CSMWorld::IdTable::index (index, 0), CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); } } const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const { return mIdCollection->getRecord (id); } int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const { return mIdCollection->searchColumnIndex (id); } int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const { return mIdCollection->findColumnIndex (id); } void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newOrder) { if (!newOrder.empty()) if (mIdCollection->reorderRows (baseIndex, newOrder)) emit dataChanged (index (baseIndex, 0), index (baseIndex+static_cast(newOrder.size())-1, mIdCollection->getColumns()-1)); } std::pair CSMWorld::IdTable::view (int row) const { std::string id; std::string hint; if (getFeatures() & Feature_ViewCell) { int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); if (cellColumn!=-1 && idColumn!=-1) { id = mIdCollection->getData (row, cellColumn).toString().toUtf8().constData(); hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); } } else if (getFeatures() & Feature_ViewId) { int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); if (column!=-1) { id = mIdCollection->getData (row, column).toString().toUtf8().constData(); hint = "c:" + id; } } if (id.empty()) return std::make_pair (UniversalId::Type_None, ""); if (id[0]=='#') id = ESM::CellId::sDefaultWorldspace; return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); } ///For top level data/columns bool CSMWorld::IdTable::isDeleted (const std::string& id) const { return getRecord (id).isDeleted(); } int CSMWorld::IdTable::getColumnId(int column) const { return mIdCollection->getColumn(column).getId(); } CSMWorld::CollectionBase *CSMWorld::IdTable::idCollection() const { return mIdCollection; } CSMWorld::LandTextureIdTable::LandTextureIdTable(CollectionBase* idCollection, unsigned int features) : IdTable(idCollection, features) { } CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures(const std::vector& ids) { ImportResults results; // Map existing textures to ids std::map reverseLookupMap; for (int i = 0; i < idCollection()->getSize(); ++i) { auto& record = static_cast&>(idCollection()->getRecord(i)); std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture); if (record.isModified()) reverseLookupMap.emplace(texture, idCollection()->getId(i)); } for (const std::string& id : ids) { int plugin, index; LandTexture::parseUniqueRecordId(id, plugin, index); int oldRow = idCollection()->searchId(id); // If it does not exist or it is in the current plugin, it can be skipped. if (oldRow < 0 || plugin == 0) { results.recordMapping.emplace_back(id, id); continue; } // Look for a pre-existing record auto& record = static_cast&>(idCollection()->getRecord(oldRow)); std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture); auto searchIt = reverseLookupMap.find(texture); if (searchIt != reverseLookupMap.end()) { results.recordMapping.emplace_back(id, searchIt->second); continue; } // Iterate until an unused index or found, or the index has completely wrapped around. int startIndex = index; do { std::string newId = LandTexture::createUniqueRecordId(0, index); int newRow = idCollection()->searchId(newId); if (newRow < 0) { // Id not taken, clone it cloneRecord(id, newId, UniversalId::Type_LandTexture); results.createdRecords.push_back(newId); results.recordMapping.emplace_back(id, newId); reverseLookupMap.emplace(texture, newId); break; } const size_t MaxIndex = std::numeric_limits::max() - 1; index = (index + 1) % MaxIndex; } while (index != startIndex); } return results; } openmw-openmw-0.48.0/apps/opencs/model/world/idtable.hpp000066400000000000000000000113511445372753700232330ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H #include #include #include "idtablebase.hpp" #include "universalid.hpp" #include "columns.hpp" namespace CSMWorld { class CollectionBase; struct RecordBase; class IdTable : public IdTableBase { Q_OBJECT private: CollectionBase *mIdCollection; // not implemented IdTable (const IdTable&); IdTable& operator= (const IdTable&); public: IdTable (CollectionBase *idCollection, unsigned int features = 0); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); int rowCount (const QModelIndex & parent = QModelIndex()) const override; int columnCount (const QModelIndex & parent = QModelIndex()) const override; QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags (const QModelIndex & index) const override; bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent (const QModelIndex& index) const override; void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types void addRecordWithData (const std::string& id, const std::map& data, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types void cloneRecord(const std::string& origin, const std::string& destination, UniversalId::Type type = UniversalId::Type_None); bool touchRecord(const std::string& id); ///< Will change the record state to modified, if it is not already. std::string getId(int row) const; QModelIndex getModelIndex (const std::string& id, int column) const override; void setRecord (const std::string& id, std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); ///< Add record or overwrite existing record. const RecordBase& getRecord (const std::string& id) const; int searchColumnIndex (Columns::ColumnId id) const override; ///< Return index of column with the given \a id. If no such column exists, -1 is returned. int findColumnIndex (Columns::ColumnId id) const override; ///< Return index of column with the given \a id. If no such column exists, an exception is /// thrown. void reorderRows (int baseIndex, const std::vector& newOrder); ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). std::pair view (int row) const override; ///< Return the UniversalId and the hint for viewing \a row. If viewing is not /// supported by this table, return (UniversalId::Type_None, ""). /// Is \a id flagged as deleted? bool isDeleted (const std::string& id) const override; int getColumnId(int column) const override; protected: virtual CollectionBase *idCollection() const; }; /// An IdTable customized to handle the more unique needs of LandTextureId's which behave /// differently from other records. The major difference is that base records cannot be /// modified. class LandTextureIdTable : public IdTable { public: struct ImportResults { using StringPair = std::pair; /// The newly added records std::vector createdRecords; /// The 1st string is the original id, the 2nd is the mapped id std::vector recordMapping; }; LandTextureIdTable(CollectionBase* idCollection, unsigned int features=0); /// Finds and maps/recreates the specified ids. ImportResults importTextures(const std::vector& ids); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idtablebase.cpp000066400000000000000000000003041445372753700240550ustar00rootroot00000000000000#include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} unsigned int CSMWorld::IdTableBase::getFeatures() const { return mFeatures; } openmw-openmw-0.48.0/apps/opencs/model/world/idtablebase.hpp000066400000000000000000000041301445372753700240630ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDTABLEBASE_H #define CSM_WOLRD_IDTABLEBASE_H #include #include "columns.hpp" namespace CSMWorld { class UniversalId; class IdTableBase : public QAbstractItemModel { Q_OBJECT public: enum Features { Feature_ReorderWithinTopic = 1, /// Use ID column to generate view request (ID is transformed into /// worldspace and original ID is passed as hint with c: prefix). Feature_ViewId = 2, /// Use cell column to generate view request (cell ID is transformed /// into worldspace and record ID is passed as hint with r: prefix). Feature_ViewCell = 4, Feature_View = Feature_ViewId | Feature_ViewCell, Feature_Preview = 8, /// Table can not be modified through ordinary means. Feature_Constant = 16, Feature_AllowTouch = 32 }; private: unsigned int mFeatures; public: IdTableBase (unsigned int features); virtual QModelIndex getModelIndex (const std::string& id, int column) const = 0; /// Return index of column with the given \a id. If no such column exists, -1 is /// returned. virtual int searchColumnIndex (Columns::ColumnId id) const = 0; /// Return index of column with the given \a id. If no such column exists, an /// exception is thrown. virtual int findColumnIndex (Columns::ColumnId id) const = 0; /// Return the UniversalId and the hint for viewing \a row. If viewing is not /// supported by this table, return (UniversalId::Type_None, ""). virtual std::pair view (int row) const = 0; /// Is \a id flagged as deleted? virtual bool isDeleted (const std::string& id) const = 0; virtual int getColumnId (int column) const = 0; unsigned int getFeatures() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idtableproxymodel.cpp000066400000000000000000000107741445372753700253610ustar00rootroot00000000000000#include "idtableproxymodel.hpp" #include #include "idtablebase.hpp" namespace { std::string getEnumValue(const std::vector> &values, int index) { if (index < 0 || index >= static_cast(values.size())) { return ""; } return values[index].second; } } void CSMWorld::IdTableProxyModel::updateColumnMap() { Q_ASSERT(mSourceModel != nullptr); mColumnMap.clear(); if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) mColumnMap.insert (std::make_pair (*iter, mSourceModel->searchColumnIndex (static_cast (*iter)))); } } bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { Q_ASSERT(mSourceModel != nullptr); // It is not possible to use filterAcceptsColumn() and check for // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can // be rejected. Workaround by disallowing tree branches (nested columns), which are not meant // to be visible, from the filter. if (sourceParent.isValid()) return false; if (!mFilter) return true; return mFilter->test (*mSourceModel, sourceRow, mColumnMap); } CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) : QSortFilterProxyModel (parent), mSourceModel(nullptr) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { Q_ASSERT(mSourceModel != nullptr); return mapFromSource(mSourceModel->getModelIndex (id, column)); } void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) { QSortFilterProxyModel::setSourceModel(model); mSourceModel = dynamic_cast(sourceModel()); connect(mSourceModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(sourceRowsInserted(const QModelIndex &, int, int))); connect(mSourceModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); connect(mSourceModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); } void CSMWorld::IdTableProxyModel::setFilter (const std::shared_ptr& filter) { beginResetModel(); mFilter = filter; updateColumnMap(); endResetModel(); } bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { Columns::ColumnId id = static_cast(left.data(ColumnBase::Role_ColumnId).toInt()); EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); if (valuesIt == mEnumColumnCache.end()) { if (Columns::hasEnums(id)) { valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first; } } if (valuesIt != mEnumColumnCache.end()) { std::string first = getEnumValue(valuesIt->second, left.data().toInt()); std::string second = getEnumValue(valuesIt->second, right.data().toInt()); return first < second; } return QSortFilterProxyModel::lessThan(left, right); } QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const { Q_ASSERT(mSourceModel != nullptr); int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); } void CSMWorld::IdTableProxyModel::refreshFilter() { if (mFilter) { updateColumnMap(); invalidateFilter(); } } void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) { refreshFilter(); if (!parent.isValid()) { emit rowAdded(getRecordId(end).toUtf8().constData()); } } void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) { refreshFilter(); } void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) { refreshFilter(); } openmw-openmw-0.48.0/apps/opencs/model/world/idtableproxymodel.hpp000066400000000000000000000035711445372753700253630ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H #define CSM_WOLRD_IDTABLEPROXYMODEL_H #include #include #include #include "../filter/node.hpp" #include "columns.hpp" namespace CSMWorld { class IdTableProxyModel : public QSortFilterProxyModel { Q_OBJECT std::shared_ptr mFilter; std::map mColumnMap; // column ID, column index in this model (or -1) // Cache of enum values for enum columns (e.g. Modified, Record Type). // Used to speed up comparisons during the sort by such columns. typedef std::map> > EnumColumnCache; mutable EnumColumnCache mEnumColumnCache; protected: IdTableBase *mSourceModel; private: void updateColumnMap(); public: IdTableProxyModel (QObject *parent = nullptr); virtual QModelIndex getModelIndex (const std::string& id, int column) const; void setSourceModel(QAbstractItemModel *model) override; void setFilter (const std::shared_ptr& filter); void refreshFilter(); protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override; QString getRecordId(int sourceRow) const; protected slots: virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); signals: void rowAdded(const std::string &id); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/idtree.cpp000066400000000000000000000215351445372753700231030ustar00rootroot00000000000000#include "idtree.hpp" #include "nestedtablewrapper.hpp" #include "collectionbase.hpp" #include "nestedcollection.hpp" #include "columnbase.hpp" // NOTE: parent class still needs idCollection CSMWorld::IdTree::IdTree (NestedCollection *nestedCollection, CollectionBase *idCollection, unsigned int features) : IdTable (idCollection, features), mNestedCollection (nestedCollection) {} CSMWorld::IdTree::~IdTree() {} int CSMWorld::IdTree::rowCount (const QModelIndex & parent) const { if (hasChildren(parent)) return mNestedCollection->getNestedRowsCount(parent.row(), parent.column()); return IdTable::rowCount(parent); } int CSMWorld::IdTree::columnCount (const QModelIndex & parent) const { if (hasChildren(parent)) return mNestedCollection->getNestedColumnsCount(parent.row(), parent.column()); return IdTable::columnCount(parent); } QVariant CSMWorld::IdTree::data (const QModelIndex & index, int role) const { if (!index.isValid()) return QVariant(); if (index.internalId() != 0) { std::pair parentAddress(unfoldIndexAddress(index.internalId())); const NestableColumn *parentColumn = mNestedCollection->getNestableColumn(parentAddress.second); if (role == ColumnBase::Role_Display) return parentColumn->nestedColumn(index.column()).mDisplayType; if (role == ColumnBase::Role_ColumnId) return parentColumn->nestedColumn(index.column()).mColumnId; if (role == Qt::EditRole && !parentColumn->nestedColumn(index.column()).isEditable()) return QVariant(); if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); return mNestedCollection->getNestedData(parentAddress.first, parentAddress.second, index.row(), index.column()); } else { return IdTable::data(index, role); } } QVariant CSMWorld::IdTree::nestedHeaderData(int section, int subSection, Qt::Orientation orientation, int role) const { if (section < 0 || section >= idCollection()->getColumns()) return QVariant(); const NestableColumn *parentColumn = mNestedCollection->getNestableColumn(section); if (orientation==Qt::Vertical) return QVariant(); if (role==Qt::DisplayRole) return tr(parentColumn->nestedColumn(subSection).getTitle().c_str()); if (role==ColumnBase::Role_Flags) return parentColumn->nestedColumn(subSection).mFlags; if (role==ColumnBase::Role_Display) return parentColumn->nestedColumn(subSection).mDisplayType; if (role==ColumnBase::Role_ColumnId) return parentColumn->nestedColumn(subSection).mColumnId; return QVariant(); } bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, int role) { if (index.internalId() != 0) { if (idCollection()->getColumn(parent(index).column()).isEditable() && role==Qt::EditRole) { const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); emit dataChanged(index, index); // Modifying a value can also change the Modified status of a record. int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); if (stateColumn != -1) { QModelIndex stateIndex = this->index(index.parent().row(), stateColumn); emit dataChanged(stateIndex, stateIndex); } return true; } else return false; } return IdTable::setData(index, value, role); } Qt::ItemFlags CSMWorld::IdTree::flags (const QModelIndex & index) const { if (!index.isValid()) return Qt::ItemFlags(); if (index.internalId() != 0) { std::pair parentAddress(unfoldIndexAddress(index.internalId())); Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (mNestedCollection->getNestableColumn(parentAddress.second)->nestedColumn(index.column()).isEditable()) flags |= Qt::ItemIsEditable; return flags; } else return IdTable::flags(index); } bool CSMWorld::IdTree::removeRows (int row, int count, const QModelIndex& parent) { if (parent.isValid()) { beginRemoveRows (parent, row, row+count-1); for (int i = 0; i < count; ++i) { mNestedCollection->removeNestedRows(parent.row(), parent.column(), row+i); } endRemoveRows(); emit dataChanged (CSMWorld::IdTree::index (parent.row(), 0), CSMWorld::IdTree::index (parent.row(), idCollection()->getColumns()-1)); return true; } else return IdTable::removeRows(row, count, parent); } void CSMWorld::IdTree::addNestedRow(const QModelIndex& parent, int position) { if (!hasChildren(parent)) throw std::logic_error("Tried to set nested table, but index has no children"); int row = parent.row(); beginInsertRows(parent, position, position); mNestedCollection->addNestedRow(row, parent.column(), position); endInsertRows(); emit dataChanged (CSMWorld::IdTree::index (row, 0), CSMWorld::IdTree::index (row, idCollection()->getColumns()-1)); } QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& parent) const { unsigned int encodedId = 0; if (parent.isValid()) { encodedId = this->foldIndexAddress(parent); } if (row < 0 || row >= rowCount(parent)) return QModelIndex(); if (column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, encodedId); // store internal id } QModelIndex CSMWorld::IdTree::getNestedModelIndex (const std::string& id, int column) const { return CSMWorld::IdTable::index(idCollection()->getIndex (id), column); } QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const { if (index.internalId() == 0) // 0 is used for indexs with invalid parent (top level data) return QModelIndex(); unsigned int id = index.internalId(); const std::pair& address(unfoldIndexAddress(id)); if (address.first >= this->rowCount() || address.second >= this->columnCount()) throw std::logic_error("Parent index is not present in the model"); return createIndex(address.first, address.second); } unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const { unsigned int out = index.row() * this->columnCount(); out += index.column(); return ++out; } std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) const { if (id == 0) throw std::runtime_error("Attempt to unfold index id of the top level data cell"); --id; int row = id / this->columnCount(); int column = id - row * this->columnCount(); return std::make_pair (row, column); } // FIXME: Not sure why this check is also needed? // // index.data().isValid() requires RefIdAdapter::getData() to return a valid QVariant for // nested columns (refidadapterimp.hpp) // // Also see comments in refidadapter.hpp and refidadapterimp.hpp. bool CSMWorld::IdTree::hasChildren(const QModelIndex& index) const { return (index.isValid() && index.internalId() == 0 && mNestedCollection->getNestableColumn(index.column())->hasChildren() && index.data().isValid()); } void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld::NestedTableWrapperBase& nestedTable) { if (!hasChildren(index)) throw std::logic_error("Tried to set nested table, but index has no children"); bool removeRowsMode = false; if (nestedTable.size() != this->nestedTable(index)->size()) { emit resetStart(this->index(index.row(), 0).data().toString()); removeRowsMode = true; } mNestedCollection->setNestedTable(index.row(), index.column(), nestedTable); emit dataChanged (CSMWorld::IdTree::index (index.row(), 0), CSMWorld::IdTree::index (index.row(), idCollection()->getColumns()-1)); if (removeRowsMode) { emit resetEnd(this->index(index.row(), 0).data().toString()); } } CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelIndex& index) const { if (!hasChildren(index)) throw std::logic_error("Tried to retrieve nested table, but index has no children"); return mNestedCollection->nestedTable(index.row(), index.column()); } int CSMWorld::IdTree::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) { return mNestedCollection->searchNestedColumnIndex(parentColumn, id); } int CSMWorld::IdTree::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) { return mNestedCollection->findNestedColumnIndex(parentColumn, id); } openmw-openmw-0.48.0/apps/opencs/model/world/idtree.hpp000066400000000000000000000065661445372753700231170ustar00rootroot00000000000000#ifndef CSM_WOLRD_IDTREE_H #define CSM_WOLRD_IDTREE_H #include "idtable.hpp" #include "universalid.hpp" #include "columns.hpp" /*! \brief * Class for holding the model. Uses typical qt table abstraction/interface for granting access * to the individiual fields of the records, Some records are holding nested data (for instance * inventory list of the npc). In cases like this, table model offers interface to access * nested data in the qt way - that is specify parent. Since some of those nested data require * multiple columns to represent information, single int (default way to index model in the * qmodelindex) is not sufficiant. Therefore tablemodelindex class can hold two ints for the * sake of indexing two dimensions of the table. This model does not support multiple levels of * the nested data. Vast majority of methods makes sense only for the top level data. */ namespace CSMWorld { class NestedCollection; struct RecordBase; struct NestedTableWrapperBase; class IdTree : public IdTable { Q_OBJECT private: NestedCollection *mNestedCollection; // not implemented IdTree (const IdTree&); IdTree& operator= (const IdTree&); unsigned int foldIndexAddress(const QModelIndex& index) const; std::pair unfoldIndexAddress(unsigned int id) const; public: IdTree (NestedCollection *nestedCollection, CollectionBase *idCollection, unsigned int features = 0); ///< The ownerships of \a nestedCollecton and \a idCollection are not transferred. ~IdTree() override; int rowCount (const QModelIndex & parent = QModelIndex()) const override; int columnCount (const QModelIndex & parent = QModelIndex()) const override; QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags (const QModelIndex & index) const override; bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent (const QModelIndex& index) const override; QModelIndex getNestedModelIndex (const std::string& id, int column) const; QVariant nestedHeaderData(int section, int subSection, Qt::Orientation orientation, int role = Qt::DisplayRole) const; NestedTableWrapperBase* nestedTable(const QModelIndex &index) const; void setNestedTable(const QModelIndex &index, const NestedTableWrapperBase& nestedTable); void addNestedRow (const QModelIndex& parent, int position); bool hasChildren (const QModelIndex& index) const override; virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); ///< \return the column index or -1 if the requested column wasn't found. virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); ///< \return the column index or throws an exception if the requested column wasn't found. signals: void resetStart(const QString& id); void resetEnd(const QString& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/info.hpp000066400000000000000000000003151445372753700225600ustar00rootroot00000000000000#ifndef CSM_WOLRD_INFO_H #define CSM_WOLRD_INFO_H #include namespace CSMWorld { struct Info : public ESM::DialInfo { std::string mTopicId; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/infocollection.cpp000066400000000000000000000306241445372753700246350ustar00rootroot00000000000000#include "infocollection.hpp" #include #include #include #include #include #include namespace CSMWorld { template<> void Collection >::removeRows (int index, int count) { mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count); // index map is updated in InfoCollection::removeRows() } template<> void Collection >::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(mRecords.size()); if (index < 0 || index > size) throw std::runtime_error("index out of range"); std::unique_ptr > record2(static_cast*>(record.release())); if (index == size) mRecords.push_back(std::move(record2)); else mRecords.insert(mRecords.begin()+index, std::move(record2)); // index map is updated in InfoCollection::insertRecord() } template<> bool Collection >::reorderRowsImp (int baseIndex, const std::vector& newOrder) { if (!newOrder.empty()) { int size = static_cast(newOrder.size()); // check that all indices are present std::vector test(newOrder); std::sort(test.begin(), test.end()); if (*test.begin() != 0 || *--test.end() != size-1) return false; // reorder records std::vector > > buffer(size); // FIXME: BUG: undo does not remove modified flag for (int i = 0; i < size; ++i) { buffer[newOrder[i]] = std::move(mRecords[baseIndex+i]); if (buffer[newOrder[i]]) buffer[newOrder[i]]->setModified(buffer[newOrder[i]]->get()); } std::move(buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); // index map is updated in InfoCollection::reorderRows() } return true; } } void CSMWorld::InfoCollection::load (const Info& record, bool base) { int index = searchId (record.mId); if (index==-1) { // new record auto record2 = std::make_unique>(); record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2->mBase : record2->mModified) = record; appendRecord(std::move(record2)); } else { // old record auto record2 = std::make_unique>(getRecord(index)); if (base) record2->mBase = record; else record2->setModified (record); setRecord (index, std::move(record2)); } } int CSMWorld::InfoCollection::getInfoIndex(std::string_view id, std::string_view topic) const { // find the topic first std::unordered_map > >::const_iterator iter = mInfoIndex.find(Misc::StringUtils::lowerCase(topic)); if (iter == mInfoIndex.end()) return -1; // brute force loop for (std::vector >::const_iterator it = iter->second.begin(); it != iter->second.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, id)) return it->second; } return -1; } // Calling insertRecord() using index from getInsertIndex() needs to take into account of // prev/next records; an example is deleting a record then undo int CSMWorld::InfoCollection::getInsertIndex (const std::string& id, UniversalId::Type type, RecordBase *record) const { if (record == nullptr) { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) throw std::runtime_error("invalid info ID: " + id); std::pair range = getTopicRange(id.substr(0, separator)); if (range.first == range.second) return Collection >::getAppendIndex(id, type); return std::distance(getRecords().begin(), range.second); } int index = -1; const Info& info = static_cast*>(record)->get(); std::string topic = info.mTopicId; // if the record has a prev, find its index value if (!info.mPrev.empty()) { index = getInfoIndex(info.mPrev, topic); if (index != -1) ++index; // if prev exists, set current index to one above prev } // if prev doesn't exist or not found and the record has a next, find its index value if (index == -1 && !info.mNext.empty()) { // if next exists, use its index as the current index index = getInfoIndex(info.mNext, topic); } // if next doesn't exist or not found (i.e. neither exist yet) then start a new one if (index == -1) { Range range = getTopicRange(topic); // getTopicRange converts topic to lower case first index = std::distance(getRecords().begin(), range.second); } return index; } bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector& newOrder) { // check if the range is valid int lastIndex = baseIndex + newOrder.size() -1; if (lastIndex>=getSize()) return false; // Check that topics match if (!Misc::StringUtils::ciEqual(getRecord(baseIndex).get().mTopicId, getRecord(lastIndex).get().mTopicId)) return false; // reorder if (!Collection >::reorderRowsImp(baseIndex, newOrder)) return false; // adjust index int size = static_cast(newOrder.size()); for (auto& [hash, infos] : mInfoIndex) for (auto& [a, b] : infos) if (b >= baseIndex && b < baseIndex + size) b = newOrder.at(b - baseIndex) + baseIndex; return true; } void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { Info info; bool isDeleted = false; info.load (reader, isDeleted); std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + info.mId; if (isDeleted) { int index = searchId (id); if (index==-1) { // deleting a record that does not exist // ignore it for now /// \todo report the problem to the user } else if (base) { removeRows (index, 1); } else { auto record = std::make_unique>(getRecord(index)); record->mState = RecordBase::State_Deleted; setRecord (index, std::move(record)); } } else { info.mTopicId = dialogue.mId; info.mId = id; load (info, base); } } CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) const { std::string lowerTopic = Misc::StringUtils::lowerCase (topic); // find the topic std::unordered_map > >::const_iterator iter = mInfoIndex.find(lowerTopic); if (iter == mInfoIndex.end()) return Range (getRecords().end(), getRecords().end()); // topic found, find the starting index int low = INT_MAX; for (std::vector >::const_iterator it = iter->second.begin(); it != iter->second.end(); ++it) { low = std::min(low, it->second); } RecordConstIterator begin = getRecords().begin() + low; // Find end (one past the range) RecordConstIterator end = begin + iter->second.size(); assert(static_cast(std::distance(begin, end)) == iter->second.size()); return Range (begin, end); } void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) { std::vector erasedRecords; Range range = getTopicRange(dialogueId); // getTopicRange converts dialogueId to lower case first for (; range.first != range.second; ++range.first) { const Record& record = **range.first; if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) { if (record.mState == RecordBase::State_ModifiedOnly) { erasedRecords.push_back(range.first - getRecords().begin()); } else { auto record2 = std::make_unique>(record); record2->mState = RecordBase::State_Deleted; setRecord(range.first - getRecords().begin(), std::move(record2)); } } else { break; } } while (!erasedRecords.empty()) { removeRows(erasedRecords.back(), 1); erasedRecords.pop_back(); } } // FIXME: removing a record should adjust prev/next and mark those records as modified // accordingly (also consider undo) void CSMWorld::InfoCollection::removeRows (int index, int count) { Collection >::removeRows(index, count); // erase records only for (std::unordered_map > >::iterator iter = mInfoIndex.begin(); iter != mInfoIndex.end();) { for (std::vector >::iterator it = iter->second.begin(); it != iter->second.end();) { if (it->second >= index) { if (it->second >= index+count) { it->second -= count; ++it; } else it = iter->second.erase(it); } else ++it; } // check for an empty vector if (iter->second.empty()) mInfoIndex.erase(iter++); else ++iter; } } void CSMWorld::InfoCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) { auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; record2->mModified.blank(); record2->get().mId = id; insertRecord(std::move(record2), getInsertIndex(id, type, nullptr), type); // call InfoCollection::insertRecord() } int CSMWorld::InfoCollection::searchId(std::string_view id) const { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) throw std::runtime_error("invalid info ID: " + std::string(id)); return getInfoIndex(id.substr(separator+1), id.substr(0, separator)); } void CSMWorld::InfoCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) { int index = getInsertIndex(static_cast*>(record.get())->get().mId, type, record.get()); insertRecord(std::move(record), index, type); } void CSMWorld::InfoCollection::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(getRecords().size()); std::string id = static_cast*>(record.get())->get().mId; std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) throw std::runtime_error("invalid info ID: " + id); Collection >::insertRecord(std::move(record), index, type); // add records only // adjust index if (index < size-1) { for (std::unordered_map > >::iterator iter = mInfoIndex.begin(); iter != mInfoIndex.end(); ++iter) { for (std::vector >::iterator it = iter->second.begin(); it != iter->second.end(); ++it) { if (it->second >= index) ++(it->second); } } } // get iterator for existing topic or a new topic std::string lowerId = Misc::StringUtils::lowerCase(id); std::pair > >::iterator, bool> res = mInfoIndex.insert( std::make_pair(lowerId.substr(0, separator), std::vector >())); // empty vector // insert info and index res.first->second.push_back(std::make_pair(lowerId.substr(separator+1), index)); } openmw-openmw-0.48.0/apps/opencs/model/world/infocollection.hpp000066400000000000000000000074541445372753700246470ustar00rootroot00000000000000#ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H #include #include #include "collection.hpp" #include "info.hpp" namespace ESM { struct Dialogue; } namespace CSMWorld { template<> void Collection >::removeRows (int index, int count); template<> void Collection >::insertRecord (std::unique_ptr record, int index, UniversalId::Type type); template<> bool Collection >::reorderRowsImp (int baseIndex, const std::vector& newOrder); class InfoCollection : public Collection > { public: typedef std::vector > >::const_iterator RecordConstIterator; typedef std::pair Range; private: // The general strategy is to keep the records in Collection kept in order (within // a topic group) while the index lookup maps are not ordered. It is assumed that // each topic has a small number of infos, which allows the use of vectors for // iterating through them without too much penalty. // // NOTE: topic string as well as id string are stored in lower case. std::unordered_map > > mInfoIndex; void load (const Info& record, bool base); int getInfoIndex(std::string_view id, std::string_view topic) const; ///< Return index for record \a id or -1 (if not present; deleted records are considered) /// /// \param id info ID without topic prefix // /// \attention id and topic are assumed to be in lower case public: int getInsertIndex (const std::string& id, UniversalId::Type type = UniversalId::Type_None, RecordBase *record = nullptr) const override; ///< \param type Will be ignored, unless the collection supports multiple record types /// /// Works like getAppendIndex unless an overloaded method uses the record pointer /// to get additional info about the record that results in an alternative index. int getAppendIndex(const std::string& id, UniversalId::Type type) const override { return getInsertIndex(id, type); } bool reorderRows (int baseIndex, const std::vector& newOrder) override; ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). /// /// \return Success? void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. void removeDialogueInfos(const std::string& dialogueId); void removeRows (int index, int count) override; void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None) override; int searchId(std::string_view id) const override; void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; void insertRecord (std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/infoselectwrapper.cpp000066400000000000000000000621771445372753700253720ustar00rootroot00000000000000#include "infoselectwrapper.hpp" #include #include #include const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { "Rank Low", "Rank High", "Rank Requirement", "Reputation", "Health Percent", "PC Reputation", "PC Level", "PC Health Percent", "PC Magicka", "PC Fatigue", "PC Strength", "PC Block", "PC Armorer", "PC Medium Armor", "PC Heavy Armor", "PC Blunt Weapon", "PC Long Blade", "PC Axe", "PC Spear", "PC Athletics", "PC Enchant", "PC Detruction", "PC Alteration", "PC Illusion", "PC Conjuration", "PC Mysticism", "PC Restoration", "PC Alchemy", "PC Unarmored", "PC Security", "PC Sneak", "PC Acrobatics", "PC Light Armor", "PC Short Blade", "PC Marksman", "PC Merchantile", "PC Speechcraft", "PC Hand to Hand", "PC Sex", "PC Expelled", "PC Common Disease", "PC Blight Disease", "PC Clothing Modifier", "PC Crime Level", "Same Sex", "Same Race", "Same Faction", "Faction Rank Difference", "Detected", "Alarmed", "Choice", "PC Intelligence", "PC Willpower", "PC Agility", "PC Speed", "PC Endurance", "PC Personality", "PC Luck", "PC Corpus", "Weather", "PC Vampire", "Level", "Attacked", "Talked to PC", "PC Health", "Creature Target", "Friend Hit", "Fight", "Hello", "Alarm", "Flee", "Should Attack", "Werewolf", "PC Werewolf Kills", "Global", "Local", "Journal", "Item", "Dead", "Not Id", "Not Faction", "Not Class", "Not Race", "Not Cell", "Not Local", 0 }; const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = { "=", "!=", ">", ">=", "<", "<=", 0 }; const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { "Boolean", "Integer", "Numeric", 0 }; // static functions std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) { if (name < Function_None) return FunctionEnumStrings[name]; else return "(Invalid Data: Function)"; } std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) { if (type < Relation_None) return RelationEnumStrings[type]; else return "(Invalid Data: Relation)"; } std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) { if (type < Comparison_None) return ComparisonEnumStrings[type]; else return "(Invalid Data: Comparison)"; } // ConstInfoSelectWrapper CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) : mConstSelect(select) { readRule(); } CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const { return mFunctionName; } CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const { return mRelationType; } CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const { return mComparisonType; } bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const { return mHasVariable; } const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const { return mVariableName; } bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const { if (!variantTypeIsValid()) return false; if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { if (mConstSelect.mValue.getType() == ESM::VT_Float) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { if (mConstSelect.mValue.getType() == ESM::VT_Float) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); } return false; } bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const { if (!variantTypeIsValid()) return false; if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { if (mConstSelect.mValue.getType() == ESM::VT_Float) return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { if (mConstSelect.mValue.getType() == ESM::VT_Float) return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); } return false; } bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const { return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); } const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const { return mConstSelect.mValue; } std::string CSMWorld::ConstInfoSelectWrapper::toString() const { std::ostringstream stream; stream << convertToString(mFunctionName) << " "; if (mHasVariable) stream << mVariableName << " "; stream << convertToString(mRelationType) << " "; switch (mConstSelect.mValue.getType()) { case ESM::VT_Int: stream << mConstSelect.mValue.getInteger(); break; case ESM::VT_Float: stream << mConstSelect.mValue.getFloat(); break; default: stream << "(Invalid value type)"; break; } return stream.str(); } void CSMWorld::ConstInfoSelectWrapper::readRule() { if (mConstSelect.mSelectRule.size() < RuleMinSize) throw std::runtime_error("InfoSelectWrapper: rule is to small"); readFunctionName(); readRelationType(); readVariableName(); updateHasVariable(); updateComparisonType(); } void CSMWorld::ConstInfoSelectWrapper::readFunctionName() { char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); int convertedIndex = -1; // Read in function index, form ## from 00 .. 73, skip leading zero if (functionIndex[0] == '0') functionIndex = functionIndex[1]; std::stringstream stream; stream << functionIndex; stream >> convertedIndex; switch (functionPrefix) { case '1': if (convertedIndex >= 0 && convertedIndex <= 73) mFunctionName = static_cast(convertedIndex); else mFunctionName = Function_None; break; case '2': mFunctionName = Function_Global; break; case '3': mFunctionName = Function_Local; break; case '4': mFunctionName = Function_Journal; break; case '5': mFunctionName = Function_Item; break; case '6': mFunctionName = Function_Dead; break; case '7': mFunctionName = Function_NotId; break; case '8': mFunctionName = Function_NotFaction; break; case '9': mFunctionName = Function_NotClass; break; case 'A': mFunctionName = Function_NotRace; break; case 'B': mFunctionName = Function_NotCell; break; case 'C': mFunctionName = Function_NotLocal; break; default: mFunctionName = Function_None; break; } } void CSMWorld::ConstInfoSelectWrapper::readRelationType() { char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; switch (relationIndex) { case '0': mRelationType = Relation_Equal; break; case '1': mRelationType = Relation_NotEqual; break; case '2': mRelationType = Relation_Greater; break; case '3': mRelationType = Relation_GreaterOrEqual; break; case '4': mRelationType = Relation_Less; break; case '5': mRelationType = Relation_LessOrEqual; break; default: mRelationType = Relation_None; } } void CSMWorld::ConstInfoSelectWrapper::readVariableName() { if (mConstSelect.mSelectRule.size() >= VarNameOffset) mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); else mVariableName.clear(); } void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() { switch (mFunctionName) { case Function_Global: case Function_Local: case Function_Journal: case Function_Item: case Function_Dead: case Function_NotId: case Function_NotFaction: case Function_NotClass: case Function_NotRace: case Function_NotCell: case Function_NotLocal: mHasVariable = true; break; default: mHasVariable = false; break; } } void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() { switch (mFunctionName) { // Boolean case Function_NotId: case Function_NotFaction: case Function_NotClass: case Function_NotRace: case Function_NotCell: case Function_PcExpelled: case Function_PcCommonDisease: case Function_PcBlightDisease: case Function_SameSex: case Function_SameRace: case Function_SameFaction: case Function_Detected: case Function_Alarmed: case Function_PcCorpus: case Function_PcVampire: case Function_Attacked: case Function_TalkedToPc: case Function_ShouldAttack: case Function_Werewolf: mComparisonType = Comparison_Boolean; break; // Integer case Function_Journal: case Function_Item: case Function_Dead: case Function_RankLow: case Function_RankHigh: case Function_RankRequirement: case Function_Reputation: case Function_PcReputation: case Function_PcLevel: case Function_PcStrength: case Function_PcBlock: case Function_PcArmorer: case Function_PcMediumArmor: case Function_PcHeavyArmor: case Function_PcBluntWeapon: case Function_PcLongBlade: case Function_PcAxe: case Function_PcSpear: case Function_PcAthletics: case Function_PcEnchant: case Function_PcDestruction: case Function_PcAlteration: case Function_PcIllusion: case Function_PcConjuration: case Function_PcMysticism: case Function_PcRestoration: case Function_PcAlchemy: case Function_PcUnarmored: case Function_PcSecurity: case Function_PcSneak: case Function_PcAcrobatics: case Function_PcLightArmor: case Function_PcShortBlade: case Function_PcMarksman: case Function_PcMerchantile: case Function_PcSpeechcraft: case Function_PcHandToHand: case Function_PcGender: case Function_PcClothingModifier: case Function_PcCrimeLevel: case Function_FactionRankDifference: case Function_Choice: case Function_PcIntelligence: case Function_PcWillpower: case Function_PcAgility: case Function_PcSpeed: case Function_PcEndurance: case Function_PcPersonality: case Function_PcLuck: case Function_Weather: case Function_Level: case Function_CreatureTarget: case Function_FriendHit: case Function_Fight: case Function_Hello: case Function_Alarm: case Function_Flee: case Function_PcWerewolfKills: mComparisonType = Comparison_Integer; break; // Numeric case Function_Global: case Function_Local: case Function_NotLocal: case Function_Health_Percent: case Function_PcHealthPercent: case Function_PcMagicka: case Function_PcFatigue: case Function_PcHealth: mComparisonType = Comparison_Numeric; break; default: mComparisonType = Comparison_None; break; } } std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const { const int IntMax = std::numeric_limits::max(); const int IntMin = std::numeric_limits::min(); const std::pair InvalidRange(IntMax, IntMin); int value = mConstSelect.mValue.getInteger(); switch (mRelationType) { case Relation_Equal: case Relation_NotEqual: return std::pair(value, value); case Relation_Greater: if (value == IntMax) { return InvalidRange; } else { return std::pair(value + 1, IntMax); } break; case Relation_GreaterOrEqual: return std::pair(value, IntMax); case Relation_Less: if (value == IntMin) { return InvalidRange; } else { return std::pair(IntMin, value - 1); } case Relation_LessOrEqual: return std::pair(IntMin, value); default: throw std::logic_error("InfoSelectWrapper: relation does not have a range"); } } std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const { const float FloatMax = std::numeric_limits::infinity(); const float FloatMin = -std::numeric_limits::infinity(); const float Epsilon = std::numeric_limits::epsilon(); float value = mConstSelect.mValue.getFloat(); switch (mRelationType) { case Relation_Equal: case Relation_NotEqual: return std::pair(value, value); case Relation_Greater: return std::pair(value + Epsilon, FloatMax); case Relation_GreaterOrEqual: return std::pair(value, FloatMax); case Relation_Less: return std::pair(FloatMin, value - Epsilon); case Relation_LessOrEqual: return std::pair(FloatMin, value); default: throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); } } std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const { const int IntMax = std::numeric_limits::max(); const int IntMin = std::numeric_limits::min(); switch (mFunctionName) { // Boolean case Function_NotId: case Function_NotFaction: case Function_NotClass: case Function_NotRace: case Function_NotCell: case Function_PcExpelled: case Function_PcCommonDisease: case Function_PcBlightDisease: case Function_SameSex: case Function_SameRace: case Function_SameFaction: case Function_Detected: case Function_Alarmed: case Function_PcCorpus: case Function_PcVampire: case Function_Attacked: case Function_TalkedToPc: case Function_ShouldAttack: case Function_Werewolf: return std::pair(0, 1); // Integer case Function_RankLow: case Function_RankHigh: case Function_Reputation: case Function_PcReputation: case Function_Journal: return std::pair(IntMin, IntMax); case Function_Item: case Function_Dead: case Function_PcLevel: case Function_PcStrength: case Function_PcBlock: case Function_PcArmorer: case Function_PcMediumArmor: case Function_PcHeavyArmor: case Function_PcBluntWeapon: case Function_PcLongBlade: case Function_PcAxe: case Function_PcSpear: case Function_PcAthletics: case Function_PcEnchant: case Function_PcDestruction: case Function_PcAlteration: case Function_PcIllusion: case Function_PcConjuration: case Function_PcMysticism: case Function_PcRestoration: case Function_PcAlchemy: case Function_PcUnarmored: case Function_PcSecurity: case Function_PcSneak: case Function_PcAcrobatics: case Function_PcLightArmor: case Function_PcShortBlade: case Function_PcMarksman: case Function_PcMerchantile: case Function_PcSpeechcraft: case Function_PcHandToHand: case Function_PcClothingModifier: case Function_PcCrimeLevel: case Function_Choice: case Function_PcIntelligence: case Function_PcWillpower: case Function_PcAgility: case Function_PcSpeed: case Function_PcEndurance: case Function_PcPersonality: case Function_PcLuck: case Function_Level: case Function_PcWerewolfKills: return std::pair(0, IntMax); case Function_Fight: case Function_Hello: case Function_Alarm: case Function_Flee: return std::pair(0, 100); case Function_Weather: return std::pair(0, 9); case Function_FriendHit: return std::pair(0, 4); case Function_RankRequirement: return std::pair(0, 3); case Function_CreatureTarget: return std::pair(0, 2); case Function_PcGender: return std::pair(0, 1); case Function_FactionRankDifference: return std::pair(-9, 9); // Numeric case Function_Global: case Function_Local: case Function_NotLocal: return std::pair(IntMin, IntMax); case Function_PcMagicka: case Function_PcFatigue: case Function_PcHealth: return std::pair(0, IntMax); case Function_Health_Percent: case Function_PcHealthPercent: return std::pair(0, 100); default: throw std::runtime_error("InfoSelectWrapper: function does not exist"); } } std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const { const float FloatMax = std::numeric_limits::infinity(); const float FloatMin = -std::numeric_limits::infinity(); switch (mFunctionName) { // Numeric case Function_Global: case Function_Local: case Function_NotLocal: return std::pair(FloatMin, FloatMax); case Function_PcMagicka: case Function_PcFatigue: case Function_PcHealth: return std::pair(0, FloatMax); case Function_Health_Percent: case Function_PcHealthPercent: return std::pair(0, 100); default: throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); } } template bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const { return (value >= range.first && value <= range.second); } template bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, std::pair testRange) const { return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); } template bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const { // One of the bounds of either range should fall within the other range return (range1.first <= range2.first && range2.first <= range1.second) || (range1.first <= range2.second && range2.second <= range1.second) || (range2.first <= range1.first && range1.first <= range2.second) || (range2.first <= range1.second && range1.second <= range2.second); } template bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const { return (range1.first == range2.first && range1.second == range2.second); } template bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const { switch (mRelationType) { case Relation_Equal: return false; case Relation_NotEqual: // If value is not within range, it will always be true return !rangeContains(conditionRange.first, validRange); case Relation_Greater: case Relation_GreaterOrEqual: case Relation_Less: case Relation_LessOrEqual: // If the valid range is completely within the condition range, it will always be true return rangeFullyContains(conditionRange, validRange); default: throw std::logic_error("InfoCondition: operator can not be used to compare"); } return false; } template bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const { switch (mRelationType) { case Relation_Equal: return !rangeContains(conditionRange.first, validRange); case Relation_NotEqual: return false; case Relation_Greater: case Relation_GreaterOrEqual: case Relation_Less: case Relation_LessOrEqual: // If ranges do not overlap, it will never be true return !rangesOverlap(conditionRange, validRange); default: throw std::logic_error("InfoCondition: operator can not be used to compare"); } return false; } // InfoSelectWrapper CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) { } void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) { mFunctionName = name; updateHasVariable(); updateComparisonType(); } void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) { mRelationType = type; } void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) { mVariableName = name; } void CSMWorld::InfoSelectWrapper::setDefaults() { if (!variantTypeIsValid()) mSelect.mValue.setType(ESM::VT_Int); switch (mComparisonType) { case Comparison_Boolean: setRelationType(Relation_Equal); mSelect.mValue.setInteger(1); break; case Comparison_Integer: case Comparison_Numeric: setRelationType(Relation_Greater); mSelect.mValue.setInteger(0); break; default: // Do nothing break; } update(); } void CSMWorld::InfoSelectWrapper::update() { std::ostringstream stream; // Leading 0 stream << '0'; // Write Function bool writeIndex = false; size_t functionIndex = static_cast(mFunctionName); switch (mFunctionName) { case Function_None: stream << '0'; break; case Function_Global: stream << '2'; break; case Function_Local: stream << '3'; break; case Function_Journal: stream << '4'; break; case Function_Item: stream << '5'; break; case Function_Dead: stream << '6'; break; case Function_NotId: stream << '7'; break; case Function_NotFaction: stream << '8'; break; case Function_NotClass: stream << '9'; break; case Function_NotRace: stream << 'A'; break; case Function_NotCell: stream << 'B'; break; case Function_NotLocal: stream << 'C'; break; default: stream << '1'; writeIndex = true; break; } if (writeIndex && functionIndex < 10) // leading 0 stream << '0' << functionIndex; else if (writeIndex) stream << functionIndex; else stream << "00"; // Write Relation switch (mRelationType) { case Relation_Equal: stream << '0'; break; case Relation_NotEqual: stream << '1'; break; case Relation_Greater: stream << '2'; break; case Relation_GreaterOrEqual: stream << '3'; break; case Relation_Less: stream << '4'; break; case Relation_LessOrEqual: stream << '5'; break; default: stream << '0'; break; } if (mHasVariable) stream << mVariableName; mSelect.mSelectRule = stream.str(); } ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() { return mSelect.mValue; } openmw-openmw-0.48.0/apps/opencs/model/world/infoselectwrapper.hpp000066400000000000000000000165041445372753700253700ustar00rootroot00000000000000#ifndef CSM_WORLD_INFOSELECTWRAPPER_H #define CSM_WORLD_INFOSELECTWRAPPER_H #include namespace CSMWorld { // ESM::DialInfo::SelectStruct.mSelectRule // 012345... // ^^^ ^^ // ||| || // ||| |+------------- condition variable string // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc // ||+---------------- function index (encoded, where function == '1') // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc // +------------------ unknown // // Wrapper for DialInfo::SelectStruct class ConstInfoSelectWrapper { public: // Order matters enum FunctionName { Function_RankLow=0, Function_RankHigh, Function_RankRequirement, Function_Reputation, Function_Health_Percent, Function_PcReputation, Function_PcLevel, Function_PcHealthPercent, Function_PcMagicka, Function_PcFatigue, Function_PcStrength, Function_PcBlock, Function_PcArmorer, Function_PcMediumArmor, Function_PcHeavyArmor, Function_PcBluntWeapon, Function_PcLongBlade, Function_PcAxe, Function_PcSpear, Function_PcAthletics, Function_PcEnchant, Function_PcDestruction, Function_PcAlteration, Function_PcIllusion, Function_PcConjuration, Function_PcMysticism, Function_PcRestoration, Function_PcAlchemy, Function_PcUnarmored, Function_PcSecurity, Function_PcSneak, Function_PcAcrobatics, Function_PcLightArmor, Function_PcShortBlade, Function_PcMarksman, Function_PcMerchantile, Function_PcSpeechcraft, Function_PcHandToHand, Function_PcGender, Function_PcExpelled, Function_PcCommonDisease, Function_PcBlightDisease, Function_PcClothingModifier, Function_PcCrimeLevel, Function_SameSex, Function_SameRace, Function_SameFaction, Function_FactionRankDifference, Function_Detected, Function_Alarmed, Function_Choice, Function_PcIntelligence, Function_PcWillpower, Function_PcAgility, Function_PcSpeed, Function_PcEndurance, Function_PcPersonality, Function_PcLuck, Function_PcCorpus, Function_Weather, Function_PcVampire, Function_Level, Function_Attacked, Function_TalkedToPc, Function_PcHealth, Function_CreatureTarget, Function_FriendHit, Function_Fight, Function_Hello, Function_Alarm, Function_Flee, Function_ShouldAttack, Function_Werewolf, Function_PcWerewolfKills=73, Function_Global, Function_Local, Function_Journal, Function_Item, Function_Dead, Function_NotId, Function_NotFaction, Function_NotClass, Function_NotRace, Function_NotCell, Function_NotLocal, Function_None }; enum RelationType { Relation_Equal, Relation_NotEqual, Relation_Greater, Relation_GreaterOrEqual, Relation_Less, Relation_LessOrEqual, Relation_None }; enum ComparisonType { Comparison_Boolean, Comparison_Integer, Comparison_Numeric, Comparison_None }; static const size_t RuleMinSize; static const size_t FunctionPrefixOffset; static const size_t FunctionIndexOffset; static const size_t RelationIndexOffset; static const size_t VarNameOffset; static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; static const char* ComparisonEnumStrings[]; static std::string convertToString(FunctionName name); static std::string convertToString(RelationType type); static std::string convertToString(ComparisonType type); ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); FunctionName getFunctionName() const; RelationType getRelationType() const; ComparisonType getComparisonType() const; bool hasVariable() const; const std::string& getVariableName() const; bool conditionIsAlwaysTrue() const; bool conditionIsNeverTrue() const; bool variantTypeIsValid() const; const ESM::Variant& getVariant() const; std::string toString() const; protected: void readRule(); void readFunctionName(); void readRelationType(); void readVariableName(); void updateHasVariable(); void updateComparisonType(); std::pair getConditionIntRange() const; std::pair getConditionFloatRange() const; std::pair getValidIntRange() const; std::pair getValidFloatRange() const; template bool rangeContains(Type1 value, std::pair range) const; template bool rangesOverlap(std::pair range1, std::pair range2) const; template bool rangeFullyContains(std::pair containing, std::pair test) const; template bool rangesMatch(std::pair range1, std::pair range2) const; template bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; template bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; FunctionName mFunctionName; RelationType mRelationType; ComparisonType mComparisonType; bool mHasVariable; std::string mVariableName; private: const ESM::DialInfo::SelectStruct& mConstSelect; }; // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct class InfoSelectWrapper : public ConstInfoSelectWrapper { public: InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); // Wrapped SelectStruct will not be modified until update() is called void setFunctionName(FunctionName name); void setRelationType(RelationType type); void setVariableName(const std::string& name); // Modified wrapped SelectStruct void update(); // This sets properties based on the function name to its defaults and updates the wrapped object void setDefaults(); ESM::Variant& getVariant(); private: ESM::DialInfo::SelectStruct& mSelect; void writeRule(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/infotableproxymodel.cpp000066400000000000000000000071621445372753700257150ustar00rootroot00000000000000#include "infotableproxymodel.hpp" #include #include "idtablebase.hpp" #include "columns.hpp" namespace { QString toLower(const QString &str) { return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); } } CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) : IdTableProxyModel(parent), mType(type), mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : Columns::ColumnId_Journal), mInfoColumnIndex(-1), mLastAddedSourceRow(-1) { Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); } void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { IdTableProxyModel::setSourceModel(sourceModel); if (mSourceModel != nullptr) { mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); mFirstRowCache.clear(); } } bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { Q_ASSERT(mSourceModel != nullptr); QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); // If both indexes are belonged to the same Topic/Journal, compare their original rows only if (first.row() == second.row()) { return sortOrder() == Qt::AscendingOrder ? left.row() < right.row() : right.row() < left.row(); } return IdTableProxyModel::lessThan(first, second); } int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const { Q_ASSERT(mSourceModel != nullptr); int row = currentRow; int column = mInfoColumnIndex; QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); if (mFirstRowCache.contains(info)) { return mFirstRowCache[info]; } while (--row >= 0 && toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()) == info); ++row; mFirstRowCache[info] = row; return row; } void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) { refreshFilter(); mFirstRowCache.clear(); } void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) { refreshFilter(); if (!parent.isValid()) { mFirstRowCache.clear(); // We can't re-sort the model here, because the topic of the added row isn't set yet. // Store the row index for using in the first dataChanged() after this row insertion. mLastAddedSourceRow = end; } } void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { refreshFilter(); if (mLastAddedSourceRow != -1 && topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) { // Now the topic of the last added row is set, // so we can re-sort the model to ensure the corrent position of this row int column = sortColumn(); Qt::SortOrder order = sortOrder(); sort(mInfoColumnIndex); // Restore the correct position of an added row sort(column, order); // Restore the original sort order emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion mLastAddedSourceRow = -1; } } openmw-openmw-0.48.0/apps/opencs/model/world/infotableproxymodel.hpp000066400000000000000000000026051445372753700257170ustar00rootroot00000000000000#ifndef CSM_WORLD_INFOTABLEPROXYMODEL_HPP #define CSM_WORLD_INFOTABLEPROXYMODEL_HPP #include #include "idtableproxymodel.hpp" #include "columns.hpp" #include "universalid.hpp" namespace CSMWorld { class IdTableBase; class InfoTableProxyModel : public IdTableProxyModel { Q_OBJECT UniversalId::Type mType; Columns::ColumnId mInfoColumnId; ///< Contains ID for Topic or Journal ID int mInfoColumnIndex; int mLastAddedSourceRow; mutable QHash mFirstRowCache; int getFirstInfoRow(int currentRow) const; ///< Finds the first row with the same topic (journal entry) as in \a currentRow ///< \a currentRow is a row of the source model. public: InfoTableProxyModel(UniversalId::Type type, QObject *parent = nullptr); void setSourceModel(QAbstractItemModel *sourceModel) override; protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; protected slots: void sourceRowsInserted(const QModelIndex &parent, int start, int end) override; void sourceRowsRemoved(const QModelIndex &parent, int start, int end) override; void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/land.cpp000066400000000000000000000012661445372753700225440ustar00rootroot00000000000000#include "land.hpp" #include #include namespace CSMWorld { void Land::load(ESM::ESMReader &esm, bool &isDeleted) { ESM::Land::load(esm, isDeleted); } std::string Land::createUniqueRecordId(int x, int y) { std::ostringstream stream; stream << "#" << x << " " << y; return stream.str(); } void Land::parseUniqueRecordId(const std::string& id, int& x, int& y) { size_t mid = id.find(' '); if (mid == std::string::npos || id[0] != '#') throw std::runtime_error("Invalid Land ID"); x = std::stoi(id.substr(1, mid - 1)); y = std::stoi(id.substr(mid + 1)); } } openmw-openmw-0.48.0/apps/opencs/model/world/land.hpp000066400000000000000000000010751445372753700225470ustar00rootroot00000000000000#ifndef CSM_WORLD_LAND_H #define CSM_WORLD_LAND_H #include #include namespace CSMWorld { /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// /// \todo Add worldspace support to the Land record. struct Land : public ESM::Land { /// Loads the metadata and ID void load (ESM::ESMReader &esm, bool &isDeleted); static std::string createUniqueRecordId(int x, int y); static void parseUniqueRecordId(const std::string& id, int& x, int& y); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/landtexture.cpp000066400000000000000000000015201445372753700241560ustar00rootroot00000000000000#include "landtexture.hpp" #include #include #include namespace CSMWorld { void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { ESM::LandTexture::load(esm, isDeleted); mPluginIndex = esm.getIndex(); } std::string LandTexture::createUniqueRecordId(int plugin, int index) { std::stringstream ss; ss << 'L' << plugin << '#' << index; return ss.str(); } void LandTexture::parseUniqueRecordId(const std::string& id, int& plugin, int& index) { size_t middle = id.find('#'); if (middle == std::string::npos || id[0] != 'L') throw std::runtime_error("Invalid LandTexture ID"); plugin = std::stoi(id.substr(1,middle-1)); index = std::stoi(id.substr(middle+1)); } } openmw-openmw-0.48.0/apps/opencs/model/world/landtexture.hpp000066400000000000000000000013301445372753700241620ustar00rootroot00000000000000#ifndef CSM_WORLD_LANDTEXTURE_H #define CSM_WORLD_LANDTEXTURE_H #include #include namespace CSMWorld { /// \brief Wrapper for LandTexture record, providing info which plugin the LandTexture was loaded from. struct LandTexture : public ESM::LandTexture { int mPluginIndex; void load (ESM::ESMReader &esm, bool &isDeleted); /// Returns a string identifier that will be unique to any LandTexture. static std::string createUniqueRecordId(int plugin, int index); /// Deconstructs a unique string identifier into plugin and index. static void parseUniqueRecordId(const std::string& id, int& plugin, int& index); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/landtexturetableproxymodel.cpp000066400000000000000000000006351445372753700273170ustar00rootroot00000000000000#include "landtexturetableproxymodel.hpp" #include "idtable.hpp" namespace CSMWorld { LandTextureTableProxyModel::LandTextureTableProxyModel(QObject* parent) : IdTableProxyModel(parent) { } bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent); } } openmw-openmw-0.48.0/apps/opencs/model/world/landtexturetableproxymodel.hpp000066400000000000000000000010111445372753700273110ustar00rootroot00000000000000#ifndef CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H #define CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H #include "idtableproxymodel.hpp" namespace CSMWorld { /// \brief Removes base records from filtered results. class LandTextureTableProxyModel : public IdTableProxyModel { Q_OBJECT public: LandTextureTableProxyModel(QObject* parent = nullptr); protected: bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/metadata.cpp000066400000000000000000000013601445372753700234010ustar00rootroot00000000000000#include "metadata.hpp" #include #include #include void CSMWorld::MetaData::blank() { // ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs // we use the format `0` for compatibility with old versions. mFormat = 0; mAuthor.clear(); mDescription.clear(); } void CSMWorld::MetaData::load (ESM::ESMReader& esm) { mFormat = esm.getHeader().mFormat; mAuthor = esm.getHeader().mData.author; mDescription = esm.getHeader().mData.desc; } void CSMWorld::MetaData::save (ESM::ESMWriter& esm) const { esm.setFormat (mFormat); esm.setAuthor (mAuthor); esm.setDescription (mDescription); } openmw-openmw-0.48.0/apps/opencs/model/world/metadata.hpp000066400000000000000000000006511445372753700234100ustar00rootroot00000000000000#ifndef CSM_WOLRD_METADATA_H #define CSM_WOLRD_METADATA_H #include namespace ESM { class ESMReader; class ESMWriter; } namespace CSMWorld { struct MetaData { std::string mId; int mFormat; std::string mAuthor; std::string mDescription; void blank(); void load (ESM::ESMReader& esm); void save (ESM::ESMWriter& esm) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/nestedcoladapterimp.cpp000066400000000000000000001214061445372753700256540ustar00rootroot00000000000000#include "nestedcoladapterimp.hpp" #include #include #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" #include "infoselectwrapper.hpp" namespace CSMWorld { PathgridPointListAdapter::PathgridPointListAdapter () {} void PathgridPointListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::PointList& points = pathgrid.mPoints; // blank row ESM::Pathgrid::Point point; point.mX = 0; point.mY = 0; point.mZ = 0; point.mAutogenerated = 0; point.mConnectionNum = 0; point.mUnknown = 0; points.insert(points.begin()+position, point); pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } void PathgridPointListAdapter::removeRow(Record& record, int rowToRemove) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::PointList& points = pathgrid.mPoints; if (rowToRemove < 0 || rowToRemove >= static_cast (points.size())) throw std::runtime_error ("index out of range"); // Do not remove dangling edges, does not work with current undo mechanism // Do not automatically adjust indices, what would be done with dangling edges? points.erase(points.begin()+rowToRemove); pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } void PathgridPointListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); pathgrid.mPoints = static_cast &>(nestedTable).mNestedTable; pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } NestedTableWrapperBase* PathgridPointListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring return new NestedTableWrapper(record.get().mPoints); } QVariant PathgridPointListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Pathgrid::Point point = record.get().mPoints[subRowIndex]; switch (subColIndex) { case 0: return subRowIndex; case 1: return point.mX; case 2: return point.mY; case 3: return point.mZ; default: throw std::runtime_error("Pathgrid point subcolumn index out of range"); } } void PathgridPointListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::Point point = pathgrid.mPoints[subRowIndex]; switch (subColIndex) { case 0: return; // return without saving case 1: point.mX = value.toInt(); break; case 2: point.mY = value.toInt(); break; case 3: point.mZ = value.toInt(); break; default: throw std::runtime_error("Pathgrid point subcolumn index out of range"); } pathgrid.mPoints[subRowIndex] = point; record.setModified (pathgrid); } int PathgridPointListAdapter::getColumnsCount(const Record& record) const { return 4; } int PathgridPointListAdapter::getRowsCount(const Record& record) const { return static_cast(record.get().mPoints.size()); } PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::EdgeList& edges = pathgrid.mEdges; // blank row ESM::Pathgrid::Edge edge; edge.mV0 = 0; edge.mV1 = 0; // NOTE: inserting a blank edge does not really make sense, perhaps this should be a // logic_error exception // // Currently the code assumes that the end user to know what he/she is doing. // e.g. Edges come in pairs, from points a->b and b->a edges.insert(edges.begin()+position, edge); record.setModified (pathgrid); } void PathgridEdgeListAdapter::removeRow(Record& record, int rowToRemove) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::EdgeList& edges = pathgrid.mEdges; if (rowToRemove < 0 || rowToRemove >= static_cast (edges.size())) throw std::runtime_error ("index out of range"); edges.erase(edges.begin()+rowToRemove); record.setModified (pathgrid); } void PathgridEdgeListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); pathgrid.mEdges = static_cast &>(nestedTable).mNestedTable; record.setModified (pathgrid); } NestedTableWrapperBase* PathgridEdgeListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring return new NestedTableWrapper(record.get().mEdges); } QVariant PathgridEdgeListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast (pathgrid.mEdges.size())) throw std::runtime_error ("index out of range"); ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex]; switch (subColIndex) { case 0: return subRowIndex; case 1: return edge.mV0; case 2: return edge.mV1; default: throw std::runtime_error("Pathgrid edge subcolumn index out of range"); } } void PathgridEdgeListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast (pathgrid.mEdges.size())) throw std::runtime_error ("index out of range"); ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex]; switch (subColIndex) { case 0: return; // return without saving case 1: edge.mV0 = value.toInt(); break; case 2: edge.mV1 = value.toInt(); break; default: throw std::runtime_error("Pathgrid edge subcolumn index out of range"); } pathgrid.mEdges[subRowIndex] = edge; record.setModified (pathgrid); } int PathgridEdgeListAdapter::getColumnsCount(const Record& record) const { return 3; } int PathgridEdgeListAdapter::getRowsCount(const Record& record) const { return static_cast(record.get().mEdges.size()); } FactionReactionsAdapter::FactionReactionsAdapter () {} void FactionReactionsAdapter::addRow(Record& record, int position) const { ESM::Faction faction = record.get(); std::map& reactions = faction.mReactions; // blank row reactions.insert(std::make_pair("", 0)); record.setModified (faction); } void FactionReactionsAdapter::removeRow(Record& record, int rowToRemove) const { ESM::Faction faction = record.get(); std::map& reactions = faction.mReactions; if (rowToRemove < 0 || rowToRemove >= static_cast (reactions.size())) throw std::runtime_error ("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < rowToRemove; ++i) ++iter; reactions.erase(iter); record.setModified (faction); } void FactionReactionsAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Faction faction = record.get(); faction.mReactions = static_cast >&>(nestedTable).mNestedTable; record.setModified (faction); } NestedTableWrapperBase* FactionReactionsAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mReactions); } QVariant FactionReactionsAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); std::map& reactions = faction.mReactions; if (subRowIndex < 0 || subRowIndex >= static_cast (reactions.size())) throw std::runtime_error ("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map std::map::const_iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) ++iter; switch (subColIndex) { case 0: return QString((*iter).first.c_str()); case 1: return (*iter).second; default: throw std::runtime_error("Faction reactions subcolumn index out of range"); } } void FactionReactionsAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); std::map& reactions = faction.mReactions; if (subRowIndex < 0 || subRowIndex >= static_cast (reactions.size())) throw std::runtime_error ("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map std::map::iterator iter = reactions.begin(); for(int i = 0; i < subRowIndex; ++i) ++iter; std::string factionId = (*iter).first; int reaction = (*iter).second; switch (subColIndex) { case 0: { reactions.erase(iter); reactions.insert(std::make_pair(value.toString().toUtf8().constData(), reaction)); break; } case 1: { reactions[factionId] = value.toInt(); break; } default: throw std::runtime_error("Faction reactions subcolumn index out of range"); } record.setModified (faction); } int FactionReactionsAdapter::getColumnsCount(const Record& record) const { return 2; } int FactionReactionsAdapter::getRowsCount(const Record& record) const { return static_cast(record.get().mReactions.size()); } RegionSoundListAdapter::RegionSoundListAdapter () {} void RegionSoundListAdapter::addRow(Record& record, int position) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; // blank row ESM::Region::SoundRef soundRef; soundRef.mSound.assign(""); soundRef.mChance = 0; soundList.insert(soundList.begin()+position, soundRef); record.setModified (region); } void RegionSoundListAdapter::removeRow(Record& record, int rowToRemove) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; if (rowToRemove < 0 || rowToRemove >= static_cast (soundList.size())) throw std::runtime_error ("index out of range"); soundList.erase(soundList.begin()+rowToRemove); record.setModified (region); } void RegionSoundListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Region region = record.get(); region.mSoundList = static_cast >&>(nestedTable).mNestedTable; record.setModified (region); } NestedTableWrapperBase* RegionSoundListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mSoundList); } QVariant RegionSoundListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; if (subRowIndex < 0 || subRowIndex >= static_cast (soundList.size())) throw std::runtime_error ("index out of range"); ESM::Region::SoundRef soundRef = soundList[subRowIndex]; switch (subColIndex) { case 0: return QString(soundRef.mSound.c_str()); case 1: return soundRef.mChance; default: throw std::runtime_error("Region sounds subcolumn index out of range"); } } void RegionSoundListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; if (subRowIndex < 0 || subRowIndex >= static_cast (soundList.size())) throw std::runtime_error ("index out of range"); ESM::Region::SoundRef soundRef = soundList[subRowIndex]; switch (subColIndex) { case 0: soundRef.mSound.assign(value.toString().toUtf8().constData()); break; case 1: soundRef.mChance = static_cast(value.toInt()); break; default: throw std::runtime_error("Region sounds subcolumn index out of range"); } region.mSoundList[subRowIndex] = soundRef; record.setModified (region); } int RegionSoundListAdapter::getColumnsCount(const Record& record) const { return 2; } int RegionSoundListAdapter::getRowsCount(const Record& record) const { return static_cast(record.get().mSoundList.size()); } InfoListAdapter::InfoListAdapter () {} void InfoListAdapter::addRow(Record& record, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void InfoListAdapter::removeRow(Record& record, int rowToRemove) const { throw std::logic_error ("cannot remove a row to a fixed table"); } void InfoListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } NestedTableWrapperBase* InfoListAdapter::table(const Record& record) const { throw std::logic_error ("table operation not supported"); } QVariant InfoListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); if (subColIndex == 0) return QString(info.mResultScript.c_str()); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); } void InfoListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Info info = record.get(); if (subColIndex == 0) info.mResultScript = value.toString().toStdString(); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); record.setModified (info); } int InfoListAdapter::getColumnsCount(const Record& record) const { return 1; } int InfoListAdapter::getRowsCount(const Record& record) const { return 1; // fixed at size 1 } InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const { Info info = record.get(); std::vector& conditions = info.mSelects; // default row ESM::DialInfo::SelectStruct condStruct; condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); record.setModified (info); } void InfoConditionAdapter::removeRow(Record& record, int rowToRemove) const { Info info = record.get(); std::vector& conditions = info.mSelects; if (rowToRemove < 0 || rowToRemove >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); conditions.erase(conditions.begin()+rowToRemove); record.setModified (info); } void InfoConditionAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Info info = record.get(); info.mSelects = static_cast >&>(nestedTable).mNestedTable; record.setModified (info); } NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mSelects); } QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); std::vector& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); switch (subColIndex) { case 0: { return infoSelectWrapper.getFunctionName(); } case 1: { if (infoSelectWrapper.hasVariable()) return QString(infoSelectWrapper.getVariableName().c_str()); else return ""; } case 2: { return infoSelectWrapper.getRelationType(); } case 3: { switch (infoSelectWrapper.getVariant().getType()) { case ESM::VT_Int: { return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } } default: throw std::runtime_error("Info condition subcolumn index out of range"); } } void InfoConditionAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Info info = record.get(); std::vector& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); bool conversionResult = false; switch (subColIndex) { case 0: // Function { infoSelectWrapper.setFunctionName(static_cast(value.toInt())); if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); } infoSelectWrapper.update(); break; } case 1: // Variable { infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); infoSelectWrapper.update(); break; } case 2: // Relation { infoSelectWrapper.setRelationType(static_cast(value.toInt())); infoSelectWrapper.update(); break; } case 3: // Value { switch (infoSelectWrapper.getComparisonType()) { case ConstInfoSelectWrapper::Comparison_Numeric: { // QVariant seems to have issues converting 0 if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.getVariant().setInteger(value.toInt()); } else if (value.toFloat(&conversionResult) && conversionResult) { infoSelectWrapper.getVariant().setType(ESM::VT_Float); infoSelectWrapper.getVariant().setFloat(value.toFloat()); } break; } case ConstInfoSelectWrapper::Comparison_Boolean: case ConstInfoSelectWrapper::Comparison_Integer: { if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.getVariant().setInteger(value.toInt()); } break; } default: break; } break; } default: throw std::runtime_error("Info condition subcolumn index out of range"); } record.setModified (info); } int InfoConditionAdapter::getColumnsCount(const Record& record) const { return 4; } int InfoConditionAdapter::getRowsCount(const Record& record) const { return static_cast(record.get().mSelects.size()); } RaceAttributeAdapter::RaceAttributeAdapter () {} void RaceAttributeAdapter::addRow(Record& record, int position) const { // Do nothing, this table cannot be changed by the user } void RaceAttributeAdapter::removeRow(Record& record, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void RaceAttributeAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Race race = record.get(); race.mData = static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (race); } NestedTableWrapperBase* RaceAttributeAdapter::table(const Record& record) const { std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant RaceAttributeAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) throw std::runtime_error ("index out of range"); switch (subColIndex) { case 0: return subRowIndex; case 1: return race.mData.mAttributeValues[subRowIndex].mMale; case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); } } void RaceAttributeAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) throw std::runtime_error ("index out of range"); switch (subColIndex) { case 0: return; // throw an exception here? case 1: race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); break; case 2: race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); break; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); } record.setModified (race); } int RaceAttributeAdapter::getColumnsCount(const Record& record) const { return 3; // attrib, male, female } int RaceAttributeAdapter::getRowsCount(const Record& record) const { return ESM::Attribute::Length; // there are 8 attributes } RaceSkillsBonusAdapter::RaceSkillsBonusAdapter () {} void RaceSkillsBonusAdapter::addRow(Record& record, int position) const { // Do nothing, this table cannot be changed by the user } void RaceSkillsBonusAdapter::removeRow(Record& record, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void RaceSkillsBonusAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Race race = record.get(); race.mData = static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (race); } NestedTableWrapperBase* RaceSkillsBonusAdapter::table(const Record& record) const { std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant RaceSkillsBonusAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0]))) throw std::runtime_error ("index out of range"); switch (subColIndex) { case 0: return race.mData.mBonus[subRowIndex].mSkill; // can be -1 case 1: return race.mData.mBonus[subRowIndex].mBonus; default: throw std::runtime_error("Race skill bonus subcolumn index out of range"); } } void RaceSkillsBonusAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0]))) throw std::runtime_error ("index out of range"); switch (subColIndex) { case 0: race.mData.mBonus[subRowIndex].mSkill = value.toInt(); break; // can be -1 case 1: race.mData.mBonus[subRowIndex].mBonus = value.toInt(); break; default: throw std::runtime_error("Race skill bonus subcolumn index out of range"); } record.setModified (race); } int RaceSkillsBonusAdapter::getColumnsCount(const Record& record) const { return 2; // skill, bonus } int RaceSkillsBonusAdapter::getRowsCount(const Record& record) const { // there are 7 skill bonuses return static_cast(sizeof(record.get().mData.mBonus)/sizeof(record.get().mData.mBonus[0])); } CellListAdapter::CellListAdapter () {} void CellListAdapter::addRow(Record& record, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void CellListAdapter::removeRow(Record& record, int rowToRemove) const { throw std::logic_error ("cannot remove a row to a fixed table"); } void CellListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } NestedTableWrapperBase* CellListAdapter::table(const Record& record) const { throw std::logic_error ("table operation not supported"); } QVariant CellListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { CSMWorld::Cell cell = record.get(); bool isInterior = (cell.mData.mFlags & ESM::Cell::Interior) != 0; bool behaveLikeExterior = (cell.mData.mFlags & ESM::Cell::QuasiEx) != 0; bool interiorWater = (cell.mData.mFlags & ESM::Cell::HasWater) != 0; switch (subColIndex) { case 0: return isInterior; // While the ambient information is not necessarily valid if the subrecord wasn't loaded, // the user should still be allowed to edit it case 1: return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mAmbient : QVariant(QVariant::UserType); case 2: return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mSunlight : QVariant(QVariant::UserType); case 3: return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mFog : QVariant(QVariant::UserType); case 4: return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); case 5: { if (isInterior && interiorWater) return cell.mWater; else return QVariant(QVariant::UserType); } case 6: return isInterior ? QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? //case 7: return isInterior ? //behaveLikeExterior : QVariant(QVariant::UserType); default: throw std::runtime_error("Cell subcolumn index out of range"); } } void CellListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { CSMWorld::Cell cell = record.get(); bool isInterior = (cell.mData.mFlags & ESM::Cell::Interior) != 0; bool behaveLikeExterior = (cell.mData.mFlags & ESM::Cell::QuasiEx) != 0; bool interiorWater = (cell.mData.mFlags & ESM::Cell::HasWater) != 0; switch (subColIndex) { case 0: { if (value.toBool()) cell.mData.mFlags |= ESM::Cell::Interior; else cell.mData.mFlags &= ~ESM::Cell::Interior; break; } case 1: { if (isInterior && !behaveLikeExterior) { cell.mAmbi.mAmbient = static_cast(value.toInt()); cell.setHasAmbient(true); } else return; // return without saving break; } case 2: { if (isInterior && !behaveLikeExterior) { cell.mAmbi.mSunlight = static_cast(value.toInt()); cell.setHasAmbient(true); } else return; // return without saving break; } case 3: { if (isInterior && !behaveLikeExterior) { cell.mAmbi.mFog = static_cast(value.toInt()); cell.setHasAmbient(true); } else return; // return without saving break; } case 4: { if (isInterior && !behaveLikeExterior) { cell.mAmbi.mFogDensity = value.toFloat(); cell.setHasAmbient(true); } else return; // return without saving break; } case 5: { if (isInterior && interiorWater) cell.mWater = value.toFloat(); else return; // return without saving break; } case 6: { if (!isInterior) cell.mMapColor = value.toInt(); else return; // return without saving break; } #if 0 // redundant since this flag is shown in the main table as "Interior Sky" // keep here for documenting the logic based on vanilla case 7: { if (isInterior) { if (value.toBool()) cell.mData.mFlags |= ESM::Cell::QuasiEx; else cell.mData.mFlags &= ~ESM::Cell::QuasiEx; } else return; // return without saving break; } #endif default: throw std::runtime_error("Cell subcolumn index out of range"); } record.setModified (cell); } int CellListAdapter::getColumnsCount(const Record& record) const { return 7; } int CellListAdapter::getRowsCount(const Record& record) const { return 1; // fixed at size 1 } RegionWeatherAdapter::RegionWeatherAdapter () {} void RegionWeatherAdapter::addRow(Record& record, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void RegionWeatherAdapter::removeRow(Record& record, int rowToRemove) const { throw std::logic_error ("cannot remove a row from a fixed table"); } void RegionWeatherAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } NestedTableWrapperBase* RegionWeatherAdapter::table(const Record& record) const { throw std::logic_error ("table operation not supported"); } QVariant RegionWeatherAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { const char* WeatherNames[] = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; const ESM::Region& region = record.get(); if (subColIndex == 0 && subRowIndex >= 0 && subRowIndex < 10) { return WeatherNames[subRowIndex]; } else if (subColIndex == 1) { switch (subRowIndex) { case 0: return region.mData.mClear; case 1: return region.mData.mCloudy; case 2: return region.mData.mFoggy; case 3: return region.mData.mOvercast; case 4: return region.mData.mRain; case 5: return region.mData.mThunder; case 6: return region.mData.mAsh; case 7: return region.mData.mBlight; case 8: return region.mData.mSnow; case 9: return region.mData.mBlizzard; default: break; } } throw std::runtime_error("index out of range"); } void RegionWeatherAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); unsigned char chance = static_cast(value.toInt()); if (subColIndex == 1) { switch (subRowIndex) { case 0: region.mData.mClear = chance; break; case 1: region.mData.mCloudy = chance; break; case 2: region.mData.mFoggy = chance; break; case 3: region.mData.mOvercast = chance; break; case 4: region.mData.mRain = chance; break; case 5: region.mData.mThunder = chance; break; case 6: region.mData.mAsh = chance; break; case 7: region.mData.mBlight = chance; break; case 8: region.mData.mSnow = chance; break; case 9: region.mData.mBlizzard = chance; break; default: throw std::runtime_error("index out of range"); } record.setModified (region); } } int RegionWeatherAdapter::getColumnsCount(const Record& record) const { return 2; } int RegionWeatherAdapter::getRowsCount(const Record& record) const { return 10; } FactionRanksAdapter::FactionRanksAdapter () {} void FactionRanksAdapter::addRow(Record& record, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void FactionRanksAdapter::removeRow(Record& record, int rowToRemove) const { throw std::logic_error ("cannot remove a row from a fixed table"); } void FactionRanksAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } NestedTableWrapperBase* FactionRanksAdapter::table(const Record& record) const { throw std::logic_error ("table operation not supported"); } QVariant FactionRanksAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) throw std::runtime_error ("index out of range"); auto& rankData = faction.mData.mRankData[subRowIndex]; switch (subColIndex) { case 0: return QString(faction.mRanks[subRowIndex].c_str()); case 1: return rankData.mAttribute1; case 2: return rankData.mAttribute2; case 3: return rankData.mPrimarySkill; case 4: return rankData.mFavouredSkill; case 5: return rankData.mFactReaction; default: throw std::runtime_error("Rank subcolumn index out of range"); } } void FactionRanksAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) throw std::runtime_error ("index out of range"); auto& rankData = faction.mData.mRankData[subRowIndex]; switch (subColIndex) { case 0: faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); break; case 1: rankData.mAttribute1 = value.toInt(); break; case 2: rankData.mAttribute2 = value.toInt(); break; case 3: rankData.mPrimarySkill = value.toInt(); break; case 4: rankData.mFavouredSkill = value.toInt(); break; case 5: rankData.mFactReaction = value.toInt(); break; default: throw std::runtime_error("Rank index out of range"); } record.setModified (faction); } int FactionRanksAdapter::getColumnsCount(const Record& record) const { return 6; } int FactionRanksAdapter::getRowsCount(const Record& record) const { return 10; } } openmw-openmw-0.48.0/apps/opencs/model/world/nestedcoladapterimp.hpp000066400000000000000000000505531445372753700256650ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDCOLADAPTERIMP_H #define CSM_WOLRD_NESTEDCOLADAPTERIMP_H #include #include #include #include // for converting magic effect id to string & back #include // for converting skill names #include // for converting attributes #include #include "nestedcolumnadapter.hpp" #include "nestedtablewrapper.hpp" #include "cell.hpp" namespace ESM { struct Faction; struct Region; } namespace CSMWorld { struct Pathgrid; struct Info; class PathgridPointListAdapter : public NestedColumnAdapter { public: PathgridPointListAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class PathgridEdgeListAdapter : public NestedColumnAdapter { public: PathgridEdgeListAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class FactionReactionsAdapter : public NestedColumnAdapter { public: FactionReactionsAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class FactionRanksAdapter : public NestedColumnAdapter { public: FactionRanksAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class RegionSoundListAdapter : public NestedColumnAdapter { public: RegionSoundListAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; template class SpellListAdapter : public NestedColumnAdapter { public: SpellListAdapter () {} void addRow(Record& record, int position) const override { ESXRecordT raceOrBthSgn = record.get(); std::vector& spells = raceOrBthSgn.mPowers.mList; // blank row std::string spell; spells.insert(spells.begin()+position, spell); record.setModified (raceOrBthSgn); } void removeRow(Record& record, int rowToRemove) const override { ESXRecordT raceOrBthSgn = record.get(); std::vector& spells = raceOrBthSgn.mPowers.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); spells.erase(spells.begin()+rowToRemove); record.setModified (raceOrBthSgn); } void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override { ESXRecordT raceOrBthSgn = record.get(); raceOrBthSgn.mPowers.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (raceOrBthSgn); } NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mPowers.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT raceOrBthSgn = record.get(); std::vector& spells = raceOrBthSgn.mPowers.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); std::string spell = spells[subRowIndex]; switch (subColIndex) { case 0: return QString(spell.c_str()); default: throw std::runtime_error("Spells subcolumn index out of range"); } } void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override { ESXRecordT raceOrBthSgn = record.get(); std::vector& spells = raceOrBthSgn.mPowers.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); std::string spell = spells[subRowIndex]; switch (subColIndex) { case 0: spell = value.toString().toUtf8().constData(); break; default: throw std::runtime_error("Spells subcolumn index out of range"); } raceOrBthSgn.mPowers.mList[subRowIndex] = spell; record.setModified (raceOrBthSgn); } int getColumnsCount(const Record& record) const override { return 1; } int getRowsCount(const Record& record) const override { return static_cast(record.get().mPowers.mList.size()); } }; template class EffectsListAdapter : public NestedColumnAdapter { public: EffectsListAdapter () {} void addRow(Record& record, int position) const override { ESXRecordT magic = record.get(); std::vector& effectsList = magic.mEffects.mList; // blank row ESM::ENAMstruct effect; effect.mEffectID = 0; effect.mSkill = -1; effect.mAttribute = -1; effect.mRange = 0; effect.mArea = 0; effect.mDuration = 0; effect.mMagnMin = 0; effect.mMagnMax = 0; effectsList.insert(effectsList.begin()+position, effect); record.setModified (magic); } void removeRow(Record& record, int rowToRemove) const override { ESXRecordT magic = record.get(); std::vector& effectsList = magic.mEffects.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (effectsList.size())) throw std::runtime_error ("index out of range"); effectsList.erase(effectsList.begin()+rowToRemove); record.setModified (magic); } void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override { ESXRecordT magic = record.get(); magic.mEffects.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (magic); } NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mEffects.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (effectsList.size())) throw std::runtime_error ("index out of range"); ESM::ENAMstruct effect = effectsList[subRowIndex]; switch (subColIndex) { case 0: { if (effect.mEffectID >=0 && effect.mEffectID < ESM::MagicEffect::Length) return effect.mEffectID; else throw std::runtime_error("Magic effects ID unexpected value"); } case 1: { switch (effect.mEffectID) { case ESM::MagicEffect::DrainSkill: case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::RestoreSkill: case ESM::MagicEffect::FortifySkill: case ESM::MagicEffect::AbsorbSkill: return effect.mSkill; default: return QVariant(); } } case 2: { switch (effect.mEffectID) { case ESM::MagicEffect::DrainAttribute: case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::RestoreAttribute: case ESM::MagicEffect::FortifyAttribute: case ESM::MagicEffect::AbsorbAttribute: return effect.mAttribute; default: return QVariant(); } } case 3: { if (effect.mRange >=0 && effect.mRange <=2) return effect.mRange; else throw std::runtime_error("Magic effects range unexpected value"); } case 4: return effect.mArea; case 5: return effect.mDuration; case 6: return effect.mMagnMin; case 7: return effect.mMagnMax; default: throw std::runtime_error("Magic Effects subcolumn index out of range"); } } void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (effectsList.size())) throw std::runtime_error ("index out of range"); ESM::ENAMstruct effect = effectsList[subRowIndex]; switch (subColIndex) { case 0: { effect.mEffectID = static_cast(value.toInt()); break; } case 1: { effect.mSkill = static_cast(value.toInt()); break; } case 2: { effect.mAttribute = static_cast(value.toInt()); break; } case 3: { effect.mRange = value.toInt(); break; } case 4: effect.mArea = value.toInt(); break; case 5: effect.mDuration = value.toInt(); break; case 6: effect.mMagnMin = value.toInt(); break; case 7: effect.mMagnMax = value.toInt(); break; default: throw std::runtime_error("Magic Effects subcolumn index out of range"); } magic.mEffects.mList[subRowIndex] = effect; record.setModified (magic); } int getColumnsCount(const Record& record) const override { return 8; } int getRowsCount(const Record& record) const override { return static_cast(record.get().mEffects.mList.size()); } }; class InfoListAdapter : public NestedColumnAdapter { public: InfoListAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class InfoConditionAdapter : public NestedColumnAdapter { public: InfoConditionAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class RaceAttributeAdapter : public NestedColumnAdapter { public: RaceAttributeAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class RaceSkillsBonusAdapter : public NestedColumnAdapter { public: RaceSkillsBonusAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class CellListAdapter : public NestedColumnAdapter { public: CellListAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; class RegionWeatherAdapter : public NestedColumnAdapter { public: RegionWeatherAdapter (); void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; } #endif // CSM_WOLRD_NESTEDCOLADAPTERIMP_H openmw-openmw-0.48.0/apps/opencs/model/world/nestedcollection.cpp000066400000000000000000000021061445372753700251560ustar00rootroot00000000000000#include "nestedcollection.hpp" CSMWorld::NestedCollection::NestedCollection() {} CSMWorld::NestedCollection::~NestedCollection() {} int CSMWorld::NestedCollection::getNestedRowsCount(int row, int column) const { return 0; } int CSMWorld::NestedCollection::getNestedColumnsCount(int row, int column) const { return 0; } int CSMWorld::NestedCollection::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) { // Assumed that the parentColumn is always a valid index const NestableColumn *parent = getNestableColumn(parentColumn); int nestedColumnCount = getNestedColumnsCount(0, parentColumn); for (int i = 0; i < nestedColumnCount; ++i) { if (parent->nestedColumn(i).mColumnId == id) { return i; } } return -1; } int CSMWorld::NestedCollection::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) { int index = searchNestedColumnIndex(parentColumn, id); if (index == -1) { throw std::logic_error("CSMWorld::NestedCollection: No such nested column"); } return index; } openmw-openmw-0.48.0/apps/opencs/model/world/nestedcollection.hpp000066400000000000000000000026741445372753700251750ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDCOLLECTION_H #define CSM_WOLRD_NESTEDCOLLECTION_H #include "columns.hpp" class QVariant; namespace CSMWorld { class NestableColumn; struct NestedTableWrapperBase; class NestedCollection { public: NestedCollection(); virtual ~NestedCollection(); virtual void addNestedRow(int row, int col, int position) = 0; virtual void removeNestedRows(int row, int column, int subRow) = 0; virtual QVariant getNestedData(int row, int column, int subRow, int subColumn) const = 0; virtual void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) = 0; virtual NestedTableWrapperBase* nestedTable(int row, int column) const = 0; virtual void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) = 0; virtual int getNestedRowsCount(int row, int column) const; virtual int getNestedColumnsCount(int row, int column) const; virtual NestableColumn *getNestableColumn(int column) = 0; virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); ///< \return the column index or -1 if the requested column wasn't found. virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); ///< \return the column index or throws an exception if the requested column wasn't found. }; } #endif // CSM_WOLRD_NESTEDCOLLECTION_H openmw-openmw-0.48.0/apps/opencs/model/world/nestedcolumnadapter.hpp000066400000000000000000000022541445372753700256720ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDCOLUMNADAPTER_H #define CSM_WOLRD_NESTEDCOLUMNADAPTER_H class QVariant; namespace CSMWorld { struct NestedTableWrapperBase; template struct Record; template class NestedColumnAdapter { public: NestedColumnAdapter() {} virtual ~NestedColumnAdapter() {} virtual void addRow(Record& record, int position) const = 0; virtual void removeRow(Record& record, int rowToRemove) const = 0; virtual void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const = 0; virtual NestedTableWrapperBase* table(const Record& record) const = 0; virtual QVariant getData(const Record& record, int subRowIndex, int subColIndex) const = 0; virtual void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const = 0; virtual int getColumnsCount(const Record& record) const = 0; virtual int getRowsCount(const Record& record) const = 0; }; } #endif // CSM_WOLRD_NESTEDCOLUMNADAPTER_H openmw-openmw-0.48.0/apps/opencs/model/world/nestedidcollection.hpp000066400000000000000000000163401445372753700255050ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDIDCOLLECTION_H #define CSM_WOLRD_NESTEDIDCOLLECTION_H #include #include #include "nestedcollection.hpp" #include "nestedcoladapterimp.hpp" namespace ESM { class ESMReader; } namespace CSMWorld { struct NestedTableWrapperBase; struct Cell; template class IdCollection; template > class NestedIdCollection : public IdCollection, public NestedCollection { std::map* > mAdapters; const NestedColumnAdapter& getAdapter(const ColumnBase &column) const; public: NestedIdCollection (); ~NestedIdCollection() override; void addNestedRow(int row, int column, int position) override; void removeNestedRows(int row, int column, int subRow) override; QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; NestedTableWrapperBase* nestedTable(int row, int column) const override; void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; int getNestedRowsCount(int row, int column) const override; int getNestedColumnsCount(int row, int column) const override; // this method is inherited from NestedCollection, not from Collection NestableColumn *getNestableColumn(int column) override; void addAdapter(std::pair* > adapter); }; template NestedIdCollection::NestedIdCollection () {} template NestedIdCollection::~NestedIdCollection() { for (typename std::map* >::iterator iter (mAdapters.begin()); iter!=mAdapters.end(); ++iter) { delete (*iter).second; } } template void NestedIdCollection::addAdapter(std::pair* > adapter) { mAdapters.insert(adapter); } template const NestedColumnAdapter& NestedIdCollection::getAdapter(const ColumnBase &column) const { typename std::map* >::const_iterator iter = mAdapters.find (&column); if (iter==mAdapters.end()) throw std::logic_error("No such column in the nestedidadapter"); return *iter->second; } template void NestedIdCollection::addNestedRow(int row, int column, int position) { auto record = std::make_unique>(); record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).addRow(*record, position); Collection::setRecord(row, std::move(record)); } template void NestedIdCollection::removeNestedRows(int row, int column, int subRow) { auto record = std::make_unique>(); record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).removeRow(*record, subRow); Collection::setRecord(row, std::move(record)); } template QVariant NestedIdCollection::getNestedData (int row, int column, int subRow, int subColumn) const { return getAdapter(Collection::getColumn(column)).getData( Collection::getRecord(row), subRow, subColumn); } template void NestedIdCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { auto record = std::make_unique>(); record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setData( *record, data, subRow, subColumn); Collection::setRecord(row, std::move(record)); } template CSMWorld::NestedTableWrapperBase* NestedIdCollection::nestedTable(int row, int column) const { return getAdapter(Collection::getColumn(column)).table( Collection::getRecord(row)); } template void NestedIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { auto record = std::make_unique>(); record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setTable( *record, nestedTable); Collection::setRecord(row, std::move(record)); } template int NestedIdCollection::getNestedRowsCount(int row, int column) const { return getAdapter(Collection::getColumn(column)).getRowsCount( Collection::getRecord(row)); } template int NestedIdCollection::getNestedColumnsCount(int row, int column) const { const ColumnBase &nestedColumn = Collection::getColumn(column); int numRecords = Collection::getSize(); if (row >= 0 && row < numRecords) { const Record& record = Collection::getRecord(row); return getAdapter(nestedColumn).getColumnsCount(record); } else { // If the row is invalid (or there no records), retrieve the column count using a blank record const Record record; return getAdapter(nestedColumn).getColumnsCount(record); } } template CSMWorld::NestableColumn *NestedIdCollection::getNestableColumn(int column) { return Collection::getNestableColumn(column); } } #endif // CSM_WOLRD_NESTEDIDCOLLECTION_H openmw-openmw-0.48.0/apps/opencs/model/world/nestedinfocollection.cpp000066400000000000000000000077311445372753700260430ustar00rootroot00000000000000#include "nestedinfocollection.hpp" #include "nestedcoladapterimp.hpp" namespace CSMWorld { NestedInfoCollection::NestedInfoCollection () {} NestedInfoCollection::~NestedInfoCollection() { for (std::map* >::iterator iter (mAdapters.begin()); iter!=mAdapters.end(); ++iter) { delete (*iter).second; } } void NestedInfoCollection::addAdapter(std::pair* > adapter) { mAdapters.insert(adapter); } const NestedColumnAdapter& NestedInfoCollection::getAdapter(const ColumnBase &column) const { std::map* >::const_iterator iter = mAdapters.find (&column); if (iter==mAdapters.end()) throw std::logic_error("No such column in the nestedidadapter"); return *iter->second; } void NestedInfoCollection::addNestedRow(int row, int column, int position) { auto record = std::make_unique>(); record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).addRow(*record, position); Collection >::setRecord(row, std::move(record)); } void NestedInfoCollection::removeNestedRows(int row, int column, int subRow) { auto record = std::make_unique>(); record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).removeRow(*record, subRow); Collection >::setRecord(row, std::move(record)); } QVariant NestedInfoCollection::getNestedData (int row, int column, int subRow, int subColumn) const { return getAdapter(Collection >::getColumn(column)).getData( Collection >::getRecord(row), subRow, subColumn); } void NestedInfoCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { auto record = std::make_unique>(); record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setData( *record, data, subRow, subColumn); Collection >::setRecord(row, std::move(record)); } CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row, int column) const { return getAdapter(Collection >::getColumn(column)).table( Collection >::getRecord(row)); } void NestedInfoCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { auto record = std::make_unique>(); record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setTable( *record, nestedTable); Collection >::setRecord(row, std::move(record)); } int NestedInfoCollection::getNestedRowsCount(int row, int column) const { return getAdapter(Collection >::getColumn(column)).getRowsCount( Collection >::getRecord(row)); } int NestedInfoCollection::getNestedColumnsCount(int row, int column) const { return getAdapter(Collection >::getColumn(column)).getColumnsCount( Collection >::getRecord(row)); } CSMWorld::NestableColumn *NestedInfoCollection::getNestableColumn(int column) { return Collection >::getNestableColumn(column); } } openmw-openmw-0.48.0/apps/opencs/model/world/nestedinfocollection.hpp000066400000000000000000000032151445372753700260410ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDINFOCOLLECTION_H #define CSM_WOLRD_NESTEDINFOCOLLECTION_H #include #include "infocollection.hpp" #include "nestedcollection.hpp" namespace CSMWorld { struct NestedTableWrapperBase; template class NestedColumnAdapter; class NestedInfoCollection : public InfoCollection, public NestedCollection { std::map* > mAdapters; const NestedColumnAdapter& getAdapter(const ColumnBase &column) const; public: NestedInfoCollection (); ~NestedInfoCollection() override; void addNestedRow(int row, int column, int position) override; void removeNestedRows(int row, int column, int subRow) override; QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; NestedTableWrapperBase* nestedTable(int row, int column) const override; void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; int getNestedRowsCount(int row, int column) const override; int getNestedColumnsCount(int row, int column) const override; // this method is inherited from NestedCollection, not from Collection > NestableColumn *getNestableColumn(int column) override; void addAdapter(std::pair* > adapter); }; } #endif // CSM_WOLRD_NESTEDINFOCOLLECTION_H openmw-openmw-0.48.0/apps/opencs/model/world/nestedtableproxymodel.cpp000066400000000000000000000144441445372753700262450ustar00rootroot00000000000000#include "nestedtableproxymodel.hpp" #include #include "idtree.hpp" CSMWorld::NestedTableProxyModel::NestedTableProxyModel(const QModelIndex& parent, ColumnBase::Display columnId, CSMWorld::IdTree* parentModel) : mParentColumn(parent.column()), mMainModel(parentModel) { const int parentRow = parent.row(); mId = std::string(parentModel->index(parentRow, 0).data().toString().toUtf8()); QAbstractProxyModel::setSourceModel(parentModel); connect(mMainModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), this, SLOT(forwardRowsAboutToInserted(const QModelIndex &, int, int))); connect(mMainModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(forwardRowsInserted(const QModelIndex &, int, int))); connect(mMainModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), this, SLOT(forwardRowsAboutToRemoved(const QModelIndex &, int, int))); connect(mMainModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(forwardRowsRemoved(const QModelIndex &, int, int))); connect(mMainModel, SIGNAL(resetStart(const QString&)), this, SLOT(forwardResetStart(const QString&))); connect(mMainModel, SIGNAL(resetEnd(const QString&)), this, SLOT(forwardResetEnd(const QString&))); connect(mMainModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(forwardDataChanged(const QModelIndex &, const QModelIndex &))); } QModelIndex CSMWorld::NestedTableProxyModel::mapFromSource(const QModelIndex& sourceIndex) const { const QModelIndex& testedParent = mMainModel->parent(sourceIndex); const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); if (testedParent == parent) { return createIndex(sourceIndex.row(), sourceIndex.column()); } else { return QModelIndex(); } } QModelIndex CSMWorld::NestedTableProxyModel::mapToSource(const QModelIndex& proxyIndex) const { const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); return mMainModel->index(proxyIndex.row(), proxyIndex.column(), parent); } int CSMWorld::NestedTableProxyModel::rowCount(const QModelIndex& index) const { assert (!index.isValid()); return mMainModel->rowCount(mMainModel->getModelIndex(mId, mParentColumn)); } int CSMWorld::NestedTableProxyModel::columnCount(const QModelIndex& parent) const { assert (!parent.isValid()); return mMainModel->columnCount(mMainModel->getModelIndex(mId, mParentColumn)); } QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QModelIndex& parent) const { assert (!parent.isValid()); int numRows = rowCount(parent); int numColumns = columnCount(parent); if (row < 0 || row >= numRows || column < 0 || column >= numColumns) return QModelIndex(); return createIndex(row, column); } QModelIndex CSMWorld::NestedTableProxyModel::parent(const QModelIndex& index) const { return QModelIndex(); } QVariant CSMWorld::NestedTableProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { return mMainModel->nestedHeaderData(mParentColumn, section, orientation, role); } QVariant CSMWorld::NestedTableProxyModel::data(const QModelIndex& index, int role) const { return mMainModel->data(mapToSource(index), role); } // NOTE: Due to mapToSouce(index) the dataChanged() signal resulting from setData() will have the // source model's index values. The indicies need to be converted to the proxy space values. // See forwardDataChanged() bool CSMWorld::NestedTableProxyModel::setData (const QModelIndex & index, const QVariant & value, int role) { return mMainModel->setData(mapToSource(index), value, role); } Qt::ItemFlags CSMWorld::NestedTableProxyModel::flags(const QModelIndex& index) const { return mMainModel->flags(mapToSource(index)); } std::string CSMWorld::NestedTableProxyModel::getParentId() const { return mId; } int CSMWorld::NestedTableProxyModel::getParentColumn() const { return mParentColumn; } CSMWorld::IdTree* CSMWorld::NestedTableProxyModel::model() const { return mMainModel; } void CSMWorld::NestedTableProxyModel::forwardRowsAboutToInserted(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { beginInsertRows(QModelIndex(), first, last); } } void CSMWorld::NestedTableProxyModel::forwardRowsInserted(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { endInsertRows(); } } bool CSMWorld::NestedTableProxyModel::indexIsParent(const QModelIndex& index) { return (index.isValid() && index.column() == mParentColumn && mMainModel->data(mMainModel->index(index.row(), 0)).toString().toUtf8().constData() == mId); } void CSMWorld::NestedTableProxyModel::forwardRowsAboutToRemoved(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { beginRemoveRows(QModelIndex(), first, last); } } void CSMWorld::NestedTableProxyModel::forwardRowsRemoved(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { endRemoveRows(); } } void CSMWorld::NestedTableProxyModel::forwardResetStart(const QString& id) { if (id.toUtf8() == mId.c_str()) beginResetModel(); } void CSMWorld::NestedTableProxyModel::forwardResetEnd(const QString& id) { if (id.toUtf8() == mId.c_str()) endResetModel(); } void CSMWorld::NestedTableProxyModel::forwardDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); if (topLeft.column() <= parent.column() && bottomRight.column() >= parent.column()) { emit dataChanged(index(0,0), index(mMainModel->rowCount(parent)-1, mMainModel->columnCount(parent)-1)); } else if (topLeft.parent() == parent && bottomRight.parent() == parent) { emit dataChanged(index(topLeft.row(), topLeft.column()), index(bottomRight.row(), bottomRight.column())); } } openmw-openmw-0.48.0/apps/opencs/model/world/nestedtableproxymodel.hpp000066400000000000000000000047561445372753700262570ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDTABLEPROXYMODEL_H #define CSM_WOLRD_NESTEDTABLEPROXYMODEL_H #include #include #include "universalid.hpp" #include "columns.hpp" #include "columnbase.hpp" /*! \brief * Proxy model used to connect view in the dialogue into the nested columns of the main model. */ namespace CSMWorld { class CollectionBase; struct RecordBase; class IdTree; class NestedTableProxyModel : public QAbstractProxyModel { Q_OBJECT const int mParentColumn; IdTree* mMainModel; std::string mId; public: NestedTableProxyModel(const QModelIndex& parent, ColumnBase::Display displayType, IdTree* parentModel); //parent is the parent of columns to work with. Columnid provides information about the column std::string getParentId() const; int getParentColumn() const; CSMWorld::IdTree* model() const; QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; int rowCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& index) const override; QVariant headerData (int section, Qt::Orientation orientation, int role) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; private: void setupHeaderVectors(ColumnBase::Display columnId); bool indexIsParent(const QModelIndex& index); private slots: void forwardRowsAboutToInserted(const QModelIndex & parent, int first, int last); void forwardRowsInserted(const QModelIndex & parent, int first, int last); void forwardRowsAboutToRemoved(const QModelIndex & parent, int first, int last); void forwardRowsRemoved(const QModelIndex & parent, int first, int last); void forwardResetStart(const QString& id); void forwardResetEnd(const QString& id); void forwardDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/nestedtablewrapper.cpp000066400000000000000000000003501445372753700255120ustar00rootroot00000000000000#include "nestedtablewrapper.hpp" CSMWorld::NestedTableWrapperBase::NestedTableWrapperBase() {} CSMWorld::NestedTableWrapperBase::~NestedTableWrapperBase() {} int CSMWorld::NestedTableWrapperBase::size() const { return -5; } openmw-openmw-0.48.0/apps/opencs/model/world/nestedtablewrapper.hpp000066400000000000000000000013051445372753700255200ustar00rootroot00000000000000#ifndef CSM_WOLRD_NESTEDTABLEWRAPPER_H #define CSM_WOLRD_NESTEDTABLEWRAPPER_H namespace CSMWorld { struct NestedTableWrapperBase { virtual ~NestedTableWrapperBase(); virtual int size() const; NestedTableWrapperBase(); }; template struct NestedTableWrapper : public NestedTableWrapperBase { NestedTable mNestedTable; NestedTableWrapper(const NestedTable& nestedTable) : mNestedTable(nestedTable) {} ~NestedTableWrapper() override {} int size() const override { return mNestedTable.size(); //i hope that this will be enough } }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/pathgrid.cpp000066400000000000000000000013251445372753700234240ustar00rootroot00000000000000#include "cell.hpp" #include "idcollection.hpp" #include "pathgrid.hpp" #include void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) { load (esm, isDeleted); // correct ID if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; mId = stream.str(); } } void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) { ESM::Pathgrid::load (esm, isDeleted); mId = mCell; if (mCell.empty()) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; mId = stream.str(); } } openmw-openmw-0.48.0/apps/opencs/model/world/pathgrid.hpp000066400000000000000000000013051445372753700234270ustar00rootroot00000000000000#ifndef CSM_WOLRD_PATHGRID_H #define CSM_WOLRD_PATHGRID_H #include #include #include namespace CSMWorld { struct Cell; template class IdCollection; /// \brief Wrapper for Pathgrid record /// /// \attention The mData.mX and mData.mY fields of the ESM::Pathgrid struct are not used. /// Exterior cell coordinates are encoded in the pathgrid ID. struct Pathgrid : public ESM::Pathgrid { std::string mId; void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection >& cells); void load (ESM::ESMReader &esm, bool &isDeleted); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/record.cpp000066400000000000000000000005611445372753700231010ustar00rootroot00000000000000#include "record.hpp" CSMWorld::RecordBase::~RecordBase() {} bool CSMWorld::RecordBase::isDeleted() const { return mState==State_Deleted || mState==State_Erased; } bool CSMWorld::RecordBase::isErased() const { return mState==State_Erased; } bool CSMWorld::RecordBase::isModified() const { return mState==State_Modified || mState==State_ModifiedOnly; }openmw-openmw-0.48.0/apps/opencs/model/world/record.hpp000066400000000000000000000105331445372753700231060ustar00rootroot00000000000000#ifndef CSM_WOLRD_RECORD_H #define CSM_WOLRD_RECORD_H #include #include namespace CSMWorld { struct RecordBase { enum State { State_BaseOnly = 0, // defined in base only State_Modified = 1, // exists in base, but has been modified State_ModifiedOnly = 2, // newly created in modified State_Deleted = 3, // exists in base, but has been deleted State_Erased = 4 // does not exist at all (we mostly treat that the same way as deleted) }; State mState; virtual ~RecordBase(); virtual std::unique_ptr clone() const = 0; virtual std::unique_ptr modifiedCopy() const = 0; virtual void assign (const RecordBase& record) = 0; ///< Will throw an exception if the types don't match. bool isDeleted() const; bool isErased() const; bool isModified() const; }; template struct Record : public RecordBase { ESXRecordT mBase; ESXRecordT mModified; Record(); Record(State state, const ESXRecordT *base = 0, const ESXRecordT *modified = 0); std::unique_ptr clone() const override; std::unique_ptr modifiedCopy() const override; void assign (const RecordBase& record) override; const ESXRecordT& get() const; ///< Throws an exception, if the record is deleted. ESXRecordT& get(); ///< Throws an exception, if the record is deleted. const ESXRecordT& getBase() const; ///< Throws an exception, if the record is deleted. Returns modified, if there is no base. void setModified (const ESXRecordT& modified); ///< Throws an exception, if the record is deleted. void merge(); ///< Merge modified into base. }; template Record::Record() : mBase(), mModified() { } template Record::Record(State state, const ESXRecordT *base, const ESXRecordT *modified) { if(base) mBase = *base; if(modified) mModified = *modified; this->mState = state; } template std::unique_ptr Record::modifiedCopy() const { return std::make_unique >( Record(State_ModifiedOnly, nullptr, &(this->get()))); } template std::unique_ptr Record::clone() const { return std::make_unique >(Record(*this)); } template void Record::assign (const RecordBase& record) { *this = dynamic_cast& > (record); } template const ESXRecordT& Record::get() const { if (mState==State_Erased) throw std::logic_error ("attempt to access a deleted record"); return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified; } template ESXRecordT& Record::get() { if (mState==State_Erased) throw std::logic_error ("attempt to access a deleted record"); return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified; } template const ESXRecordT& Record::getBase() const { if (mState==State_Erased) throw std::logic_error ("attempt to access a deleted record"); return mState==State_ModifiedOnly ? mModified : mBase; } template void Record::setModified (const ESXRecordT& modified) { if (mState==State_Erased) throw std::logic_error ("attempt to modify a deleted record"); mModified = modified; if (mState!=State_ModifiedOnly) mState = State_Modified; } template void Record::merge() { if (isModified()) { mBase = mModified; mState = State_BaseOnly; } else if (mState==State_Deleted) { mState = State_Erased; } } } #endif openmw-openmw-0.48.0/apps/opencs/model/world/ref.cpp000066400000000000000000000004641445372753700224010ustar00rootroot00000000000000#include "ref.hpp" #include "cellcoordinates.hpp" CSMWorld::CellRef::CellRef() : mNew (true), mIdNum(0) { mRefNum.mIndex = 0; mRefNum.mContentFile = 0; } std::pair CSMWorld::CellRef::getCellIndex() const { return CellCoordinates::coordinatesToCellIndex (mPos.pos[0], mPos.pos[1]); } openmw-openmw-0.48.0/apps/opencs/model/world/ref.hpp000066400000000000000000000010721445372753700224020ustar00rootroot00000000000000#ifndef CSM_WOLRD_REF_H #define CSM_WOLRD_REF_H #include #include namespace CSMWorld { /// \brief Wrapper for CellRef sub record struct CellRef : public ESM::CellRef { std::string mId; std::string mCell; std::string mOriginalCell; bool mNew; // new reference, not counted yet, ref num not assigned yet unsigned int mIdNum; CellRef(); /// Calculate cell index based on coordinates (x and y) std::pair getCellIndex() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/refcollection.cpp000066400000000000000000000271421445372753700244570ustar00rootroot00000000000000#include "refcollection.hpp" #include #include "ref.hpp" #include "cell.hpp" #include "universalid.hpp" #include "record.hpp" #include namespace CSMWorld { template<> void Collection >::removeRows (int index, int count) { mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count); // index map is updated in RefCollection::removeRows() } template<> void Collection >::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(mRecords.size()); if (index < 0 || index > size) throw std::runtime_error("index out of range"); std::unique_ptr > record2(static_cast*>(record.release())); if (index == size) mRecords.push_back(std::move(record2)); else mRecords.insert(mRecords.begin()+index, std::move(record2)); // index map is updated in RefCollection::insertRecord() } } void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, std::map& cache, CSMDoc::Messages& messages) { Record cell = mCells.getRecord (cellIndex); Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; ref.mNew = false; ESM::MovedCellRef mref; mref.mRefNum.mIndex = 0; bool isDeleted = false; bool isMoved = false; while (ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). ref.mOriginalCell = base ? cell2.mId : ""; if (cell.get().isExterior()) { // Autocalculate the cell index from coordinates first std::pair index = ref.getCellIndex(); ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); // Handle non-base moved references if (!base && isMoved) { // Moved references must have a link back to their original cell // See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; // Some mods may move references outside of the bounds, which often happens they are deleted. // This results in nonsensical autocalculated cell IDs, so we must use the record target cell. // Log a warning if the record target cell is different if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { std::string indexCell = ref.mCell; ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")" " does not match the target cell (" + ref.mCell + ")", std::string(), CSMDoc::Message::Severity_Warning); } } } else ref.mCell = cell2.mId; if (ref.mRefNum.mContentFile != -1 && !base) { ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; ref.mRefNum.mIndex &= 0x00ffffff; } unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) | (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; std::map::iterator iter = cache.find(refNum); if (isMoved) { if (iter == cache.end()) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); messages.add(id, "Attempt to move a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + std::to_string(ref.mRefNum.mContentFile), /*hint*/"", CSMDoc::Message::Severity_Warning); continue; } int index = getIntIndex(iter->second); // ensure we have the same record id for setRecord() ref.mId = getRecord(index).get().mId; ref.mIdNum = extractIdNum(ref.mId); auto record = std::make_unique>(); // TODO: check whether a base record be moved record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record->mBase : record->mModified) = std::move(ref); // overwrite original record setRecord(index, std::move(record)); continue; // NOTE: assumed moved references are not deleted at the same time } if (isDeleted) { if (iter==cache.end()) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + std::to_string(ref.mRefNum.mContentFile), /*hint*/"", CSMDoc::Message::Severity_Warning); continue; } int index = getIntIndex (iter->second); if (base) { removeRows (index, 1); cache.erase (iter); } else { auto record = std::make_unique>(getRecord(index)); record->mState = RecordBase::State_Deleted; setRecord(index, std::move(record)); } continue; } if (iter==cache.end()) { // new reference ref.mIdNum = mNextId; // FIXME: fragile ref.mId = getNewId(); cache.emplace(refNum, ref.mIdNum); auto record = std::make_unique>(); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record->mBase : record->mModified) = std::move(ref); appendRecord(std::move(record)); } else { // old reference -> merge int index = getIntIndex(iter->second); #if 0 // ref.mRefNum.mIndex : the key // iter->second : previously cached idNum for the key // index : position of the record for that idNum // getRecord(index).get() : record in the index position assert(iter->second != getRecord(index).get().mIdNum); // sanity check // check if the plugin used the same RefNum index for a different record if (ref.mRefID != getRecord(index).get().mRefID) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); messages.add(id, "RefNum renamed from RefID \"" + getRecord(index).get().mRefID + "\" to \"" + ref.mRefID + "\" (RefNum index " + std::to_string(ref.mRefNum.mIndex) + ")", /*hint*/"", CSMDoc::Message::Severity_Info); } #endif ref.mId = getRecord(index).get().mId; ref.mIdNum = extractIdNum(ref.mId); auto record = std::make_unique>(getRecord(index)); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; (base ? record->mBase : record->mModified) = std::move(ref); setRecord(index, std::move(record)); } } } std::string CSMWorld::RefCollection::getNewId() { return "ref#" + std::to_string(mNextId++); } unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) throw std::runtime_error("invalid ref ID: " + std::string(id)); return static_cast(std::stoi(std::string(id.substr(separator+1)))); } int CSMWorld::RefCollection::getIntIndex (unsigned int id) const { int index = searchId(id); if (index == -1) throw std::runtime_error("invalid RefNum: " + std::to_string(id)); return index; } int CSMWorld::RefCollection::searchId (unsigned int id) const { std::map::const_iterator iter = mRefIndex.find(id); if (iter == mRefIndex.end()) return -1; return iter->second; } void CSMWorld::RefCollection::removeRows (int index, int count) { Collection >::removeRows(index, count); // erase records only std::map::iterator iter = mRefIndex.begin(); while (iter != mRefIndex.end()) { if (iter->second>=index) { if (iter->second >= index+count) { iter->second -= count; ++iter; } else mRefIndex.erase(iter++); } else ++iter; } } void CSMWorld::RefCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) { auto record = std::make_unique>(); record->mState = Record::State_ModifiedOnly; record->mModified.blank(); record->get().mId = id; record->get().mIdNum = extractIdNum(id); Collection >::appendRecord(std::move(record)); } void CSMWorld::RefCollection::cloneRecord (const std::string& origin, const std::string& destination, const UniversalId::Type type) { auto copy = std::make_unique>(); copy->mModified = getRecord(origin).get(); copy->mState = RecordBase::State_ModifiedOnly; copy->get().mId = destination; copy->get().mIdNum = extractIdNum(destination); insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() } int CSMWorld::RefCollection::searchId(std::string_view id) const { return searchId(extractIdNum(id)); } void CSMWorld::RefCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) { int index = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored mRefIndex.insert(std::make_pair(static_cast*>(record.get())->get().mIdNum, index)); Collection >::insertRecord(std::move(record), index, type); // add records only } void CSMWorld::RefCollection::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored unsigned int idNum = static_cast*>(record.get())->get().mIdNum; Collection >::insertRecord(std::move(record), index, type); // add records only if (index < size-1) { for (std::map::iterator iter(mRefIndex.begin()); iter != mRefIndex.end(); ++iter) { if (iter->second >= index) ++(iter->second); } } mRefIndex.insert(std::make_pair(idNum, index)); } openmw-openmw-0.48.0/apps/opencs/model/world/refcollection.hpp000066400000000000000000000044311445372753700244600ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFCOLLECTION_H #define CSM_WOLRD_REFCOLLECTION_H #include #include #include "../doc/stage.hpp" #include "collection.hpp" #include "ref.hpp" #include "record.hpp" namespace CSMWorld { struct Cell; class UniversalId; template<> void Collection >::removeRows (int index, int count); template<> void Collection >::insertRecord (std::unique_ptr record, int index, UniversalId::Type type); /// \brief References in cells class RefCollection : public Collection { Collection& mCells; std::map mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum int mNextId; unsigned int extractIdNum(std::string_view id) const; int getIntIndex (unsigned int id) const; int searchId (unsigned int id) const; public: // MSVC needs the constructor for a class inheriting a template to be defined in header RefCollection (Collection& cells) : mCells (cells), mNextId (0) {} void load (ESM::ESMReader& reader, int cellIndex, bool base, std::map& cache, CSMDoc::Messages& messages); ///< Load a sequence of references. std::string getNewId(); virtual void removeRows (int index, int count); virtual void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); virtual void cloneRecord (const std::string& origin, const std::string& destination, const UniversalId::Type type); virtual int searchId(std::string_view id) const; virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); virtual void insertRecord (std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/refidadapter.cpp000066400000000000000000000003621445372753700242540ustar00rootroot00000000000000#include "refidadapter.hpp" CSMWorld::RefIdAdapter::RefIdAdapter() {} CSMWorld::RefIdAdapter::~RefIdAdapter() {} CSMWorld::NestedRefIdAdapterBase::NestedRefIdAdapterBase() {} CSMWorld::NestedRefIdAdapterBase::~NestedRefIdAdapterBase() {} openmw-openmw-0.48.0/apps/opencs/model/world/refidadapter.hpp000066400000000000000000000057241445372753700242700ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFIDADAPTER_H #define CSM_WOLRD_REFIDADAPTER_H #include #include /*! \brief * Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels of model. * Please notice that nested adaptor uses helper classes for actually performing any actions. Different record types require different helpers (needs to be created in the subclass and then fetched via member function). * * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having children! */ class QVariant; namespace CSMWorld { class RefIdColumn; class RefIdData; struct RecordBase; struct NestedTableWrapperBase; class HelperBase; class RefIdAdapter { // not implemented RefIdAdapter (const RefIdAdapter&); RefIdAdapter& operator= (const RefIdAdapter&); public: RefIdAdapter(); virtual ~RefIdAdapter(); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int idnex) const = 0; ///< If called on the nest column, should return QVariant(true). virtual void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const = 0; ///< If the data type does not match an exception is thrown. virtual std::string getId (const RecordBase& record) const = 0; virtual void setId(RecordBase& record, const std::string& id) = 0; // used by RefIdCollection::cloneRecord() }; class NestedRefIdAdapterBase { public: NestedRefIdAdapterBase(); virtual ~NestedRefIdAdapterBase(); virtual void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const = 0; virtual QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const = 0; virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const = 0; virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const = 0; virtual void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const = 0; virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const = 0; virtual void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const = 0; virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const = 0; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/refidadapterimp.cpp000066400000000000000000001556211445372753700247730ustar00rootroot00000000000000#include "refidadapterimp.hpp" #include #include #include #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns), mEffects(nullptr) {} CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc) : InventoryRefIdAdapter (UniversalId::Type_Potion, columns), mColumns(columns), mAutoCalc (autoCalc) {} QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion))); if (column==mAutoCalc) return record.get().mData.mAutoCalc!=0; // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) return QVariant::fromValue(ColumnBase::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion))); ESM::Potion potion = record.get(); if (column==mAutoCalc) potion.mData.mAutoCalc = value.toInt(); else { InventoryRefIdAdapter::setData (column, data, index, value); return; } record.setModified(potion); } CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) : InventoryColumns (columns) , mEffects(nullptr) {} CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), mColumns(columns) {} QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { if (column==mColumns.mEffects) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); return InventoryRefIdAdapter::getData (column, data, index); } void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { InventoryRefIdAdapter::setData (column, data, index, value); return; } CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() : mType(UniversalId::Type_Ingredient) {} CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() {} void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESM::Ingredient ingredient = record.get(); ingredient.mData = static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (ingredient); } CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); if (subRowIndex < 0 || subRowIndex >= 4) throw std::runtime_error ("index out of range"); switch (subColIndex) { case 0: return record.get().mData.mEffectID[subRowIndex]; case 1: { switch (record.get().mData.mEffectID[subRowIndex]) { case ESM::MagicEffect::DrainSkill: case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::RestoreSkill: case ESM::MagicEffect::FortifySkill: case ESM::MagicEffect::AbsorbSkill: return record.get().mData.mSkills[subRowIndex]; default: return QVariant(); } } case 2: { switch (record.get().mData.mEffectID[subRowIndex]) { case ESM::MagicEffect::DrainAttribute: case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::RestoreAttribute: case ESM::MagicEffect::FortifyAttribute: case ESM::MagicEffect::AbsorbAttribute: return record.get().mData.mAttributes[subRowIndex]; default: return QVariant(); } } default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESM::Ingredient ingredient = record.get(); if (subRowIndex < 0 || subRowIndex >= 4) throw std::runtime_error ("index out of range"); switch(subColIndex) { case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (ingredient); } int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 3; // effect, skill, attribute } int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { return 4; // up to 4 effects } CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, const RefIdColumn *quality) : InventoryRefIdAdapter (UniversalId::Type_Apparatus, columns), mType (type), mQuality (quality) {} QVariant CSMWorld::ApparatusRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus))); if (column==mType) return record.get().mData.mType; if (column==mQuality) return record.get().mData.mQuality; return InventoryRefIdAdapter::getData (column, data, index); } void CSMWorld::ApparatusRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus))); ESM::Apparatus apparatus = record.get(); if (column==mType) apparatus.mData.mType = value.toInt(); else if (column==mQuality) apparatus.mData.mQuality = value.toFloat(); else { InventoryRefIdAdapter::setData (column, data, index, value); return; } record.setModified(apparatus); } CSMWorld::ArmorRefIdAdapter::ArmorRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type, const RefIdColumn *health, const RefIdColumn *armor, const RefIdColumn *partRef) : EnchantableRefIdAdapter (UniversalId::Type_Armor, columns), mType (type), mHealth (health), mArmor (armor), mPartRef(partRef) {} QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor))); if (column==mType) return record.get().mData.mType; if (column==mHealth) return record.get().mData.mHealth; if (column==mArmor) return record.get().mData.mArmor; if (column==mPartRef) return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor))); ESM::Armor armor = record.get(); if (column==mType) armor.mData.mType = value.toInt(); else if (column==mHealth) armor.mData.mHealth = value.toInt(); else if (column==mArmor) armor.mData.mArmor = value.toInt(); else { EnchantableRefIdAdapter::setData (column, data, index, value); return; } record.setModified(armor); } CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *bookType, const RefIdColumn *skill, const RefIdColumn *text) : EnchantableRefIdAdapter (UniversalId::Type_Book, columns), mBookType (bookType), mSkill (skill), mText (text) {} QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); if (column==mBookType) return record.get().mData.mIsScroll; if (column==mSkill) return record.get().mData.mSkillId; if (column==mText) return QString::fromUtf8 (record.get().mText.c_str()); return EnchantableRefIdAdapter::getData (column, data, index); } void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); ESM::Book book = record.get(); if (column==mBookType) book.mData.mIsScroll = value.toInt(); else if (column==mSkill) book.mData.mSkillId = value.toInt(); else if (column==mText) book.mText = value.toString().toUtf8().data(); else { EnchantableRefIdAdapter::setData (column, data, index, value); return; } record.setModified(book); } CSMWorld::ClothingRefIdAdapter::ClothingRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type, const RefIdColumn *partRef) : EnchantableRefIdAdapter (UniversalId::Type_Clothing, columns), mType (type), mPartRef(partRef) {} QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing))); if (column==mType) return record.get().mData.mType; if (column==mPartRef) return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } void CSMWorld::ClothingRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing))); ESM::Clothing clothing = record.get(); if (column==mType) clothing.mData.mType = value.toInt(); else { EnchantableRefIdAdapter::setData (column, data, index, value); return; } record.setModified(clothing); } CSMWorld::ContainerRefIdAdapter::ContainerRefIdAdapter (const NameColumns& columns, const RefIdColumn *weight, const RefIdColumn *organic, const RefIdColumn *respawn, const RefIdColumn *content) : NameRefIdAdapter (UniversalId::Type_Container, columns), mWeight (weight), mOrganic (organic), mRespawn (respawn), mContent(content) {} QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container))); if (column==mWeight) return record.get().mWeight; if (column==mOrganic) return (record.get().mFlags & ESM::Container::Organic)!=0; if (column==mRespawn) return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) return QVariant::fromValue(ColumnBase::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container))); ESM::Container container = record.get(); if (column==mWeight) container.mWeight = value.toFloat(); else if (column==mOrganic) { if (value.toInt()) container.mFlags |= ESM::Container::Organic; else container.mFlags &= ~ESM::Container::Organic; } else if (column==mRespawn) { if (value.toInt()) container.mFlags |= ESM::Container::Respawn; else container.mFlags &= ~ESM::Container::Respawn; } else { NameRefIdAdapter::setData (column, data, index, value); return; } record.setModified(container); } CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mType(nullptr), mScale(nullptr), mOriginal(nullptr), mAttributes(nullptr), mAttacks(nullptr), mMisc(nullptr), mBloodType(nullptr) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) : ActorRefIdAdapter (UniversalId::Type_Creature, columns), mColumns (columns) {} QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); if (column==mColumns.mType) return record.get().mData.mType; if (column==mColumns.mScale) return record.get().mScale; if (column==mColumns.mOriginal) return QString::fromUtf8 (record.get().mOriginal.c_str()); if (column==mColumns.mAttributes) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mAttacks) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) return record.get().mBloodType; std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) return (record.get().mFlags & iter->second)!=0; return ActorRefIdAdapter::getData (column, data, index); } void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); if (column==mColumns.mType) creature.mData.mType = value.toInt(); else if (column==mColumns.mScale) creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); else if (column == mColumns.mBloodType) creature.mBloodType = value.toInt(); else { std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) creature.mFlags |= iter->second; else creature.mFlags &= ~iter->second; } else { ActorRefIdAdapter::setData (column, data, index, value); return; } } record.setModified(creature); } CSMWorld::DoorRefIdAdapter::DoorRefIdAdapter (const NameColumns& columns, const RefIdColumn *openSound, const RefIdColumn *closeSound) : NameRefIdAdapter (UniversalId::Type_Door, columns), mOpenSound (openSound), mCloseSound (closeSound) {} QVariant CSMWorld::DoorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door))); if (column==mOpenSound) return QString::fromUtf8 (record.get().mOpenSound.c_str()); if (column==mCloseSound) return QString::fromUtf8 (record.get().mCloseSound.c_str()); return NameRefIdAdapter::getData (column, data, index); } void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door))); ESM::Door door = record.get(); if (column==mOpenSound) door.mOpenSound = value.toString().toUtf8().constData(); else if (column==mCloseSound) door.mCloseSound = value.toString().toUtf8().constData(); else { NameRefIdAdapter::setData (column, data, index, value); return; } record.setModified(door); } CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) : InventoryColumns (columns) , mTime(nullptr) , mRadius(nullptr) , mColor(nullptr) , mSound(nullptr) , mEmitterType(nullptr) {} CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Light, columns), mColumns (columns) {} QVariant CSMWorld::LightRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light))); if (column==mColumns.mTime) return record.get().mData.mTime; if (column==mColumns.mRadius) return record.get().mData.mRadius; if (column==mColumns.mColor) return record.get().mData.mColor; if (column==mColumns.mSound) return QString::fromUtf8 (record.get().mSound.c_str()); if (column == mColumns.mEmitterType) { int mask = ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow; if ((record.get().mData.mFlags & mask) == ESM::Light::Flicker) return 1; if ((record.get().mData.mFlags & mask) == ESM::Light::FlickerSlow) return 2; if ((record.get().mData.mFlags & mask) == ESM::Light::Pulse) return 3; if ((record.get().mData.mFlags & mask) == ESM::Light::PulseSlow) return 4; return 0; } std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) return (record.get().mData.mFlags & iter->second)!=0; return InventoryRefIdAdapter::getData (column, data, index); } void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light))); ESM::Light light = record.get(); if (column==mColumns.mTime) light.mData.mTime = value.toInt(); else if (column==mColumns.mRadius) light.mData.mRadius = value.toInt(); else if (column==mColumns.mColor) light.mData.mColor = value.toInt(); else if (column==mColumns.mSound) light.mSound = value.toString().toUtf8().constData(); else if (column == mColumns.mEmitterType) { int mask = ~(ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow); if (value.toInt() == 0) light.mData.mFlags = light.mData.mFlags & mask; else if (value.toInt() == 1) light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::Flicker; else if (value.toInt() == 2) light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::FlickerSlow; else if (value.toInt() == 3) light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::Pulse; else light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::PulseSlow; } else { std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) light.mData.mFlags |= iter->second; else light.mData.mFlags &= ~iter->second; } else { InventoryRefIdAdapter::setData (column, data, index, value); return; } } record.setModified (light); } CSMWorld::MiscRefIdAdapter::MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key) : InventoryRefIdAdapter (UniversalId::Type_Miscellaneous, columns), mKey (key) {} QVariant CSMWorld::MiscRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous))); if (column==mKey) return record.get().mData.mIsKey!=0; return InventoryRefIdAdapter::getData (column, data, index); } void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous))); ESM::Miscellaneous misc = record.get(); if (column==mKey) misc.mData.mIsKey = value.toInt(); else { InventoryRefIdAdapter::setData (column, data, index, value); return; } record.setModified(misc); } CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mRace(nullptr), mClass(nullptr), mFaction(nullptr), mHair(nullptr), mHead(nullptr), mAttributes(nullptr), mSkills(nullptr), mMisc(nullptr), mBloodType(nullptr), mGender(nullptr) {} CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) : ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); if (column==mColumns.mRace) return QString::fromUtf8 (record.get().mRace.c_str()); if (column==mColumns.mClass) return QString::fromUtf8 (record.get().mClass.c_str()); if (column==mColumns.mFaction) return QString::fromUtf8 (record.get().mFaction.c_str()); if (column==mColumns.mHair) return QString::fromUtf8 (record.get().mHair.c_str()); if (column==mColumns.mHead) return QString::fromUtf8 (record.get().mHead.c_str()); if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) return QVariant::fromValue(ColumnBase::TableEdit_None); else return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } if (column==mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) return record.get().mBloodType; if (column == mColumns.mGender) { // Implemented this way to allow additional gender types in the future. if ((record.get().mFlags & ESM::NPC::Female) == ESM::NPC::Female) return 1; return 0; } std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) return (record.get().mFlags & iter->second)!=0; return ActorRefIdAdapter::getData (column, data, index); } void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); if (column==mColumns.mRace) npc.mRace = value.toString().toUtf8().constData(); else if (column==mColumns.mClass) npc.mClass = value.toString().toUtf8().constData(); else if (column==mColumns.mFaction) npc.mFaction = value.toString().toUtf8().constData(); else if (column==mColumns.mHair) npc.mHair = value.toString().toUtf8().constData(); else if (column==mColumns.mHead) npc.mHead = value.toString().toUtf8().constData(); else if (column == mColumns.mBloodType) npc.mBloodType = value.toInt(); else if (column == mColumns.mGender) { // Implemented this way to allow additional gender types in the future. if (value.toInt() == 1) npc.mFlags = (npc.mFlags & ~ESM::NPC::Female) | ESM::NPC::Female; else npc.mFlags = npc.mFlags & ~ESM::NPC::Female; } else { std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) npc.mFlags |= iter->second; else npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS : ESM::NPC::NPC_DEFAULT; } else { ActorRefIdAdapter::setData (column, data, index, value); return; } } record.setModified (npc); } CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::NpcAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); // store the whole struct npc.mNpdt = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); } CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; if (subColIndex == 0) return subRowIndex; else if (subColIndex == 1) switch (subRowIndex) { case 0: return static_cast(npcStruct.mStrength); case 1: return static_cast(npcStruct.mIntelligence); case 2: return static_cast(npcStruct.mWillpower); case 3: return static_cast(npcStruct.mAgility); case 4: return static_cast(npcStruct.mSpeed); case 5: return static_cast(npcStruct.mEndurance); case 6: return static_cast(npcStruct.mPersonality); case 7: return static_cast(npcStruct.mLuck); default: return QVariant(); // throw an exception here? } else return QVariant(); // throw an exception here? } void CSMWorld::NpcAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subColIndex == 1) switch(subRowIndex) { case 0: npcStruct.mStrength = static_cast(value.toInt()); break; case 1: npcStruct.mIntelligence = static_cast(value.toInt()); break; case 2: npcStruct.mWillpower = static_cast(value.toInt()); break; case 3: npcStruct.mAgility = static_cast(value.toInt()); break; case 4: npcStruct.mSpeed = static_cast(value.toInt()); break; case 5: npcStruct.mEndurance = static_cast(value.toInt()); break; case 6: npcStruct.mPersonality = static_cast(value.toInt()); break; case 7: npcStruct.mLuck = static_cast(value.toInt()); break; default: return; // throw an exception here? } else return; // throw an exception here? record.setModified (npc); } int CSMWorld::NpcAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 2; } int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { // There are 8 attributes return 8; } CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::NpcSkillsRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); // store the whole struct npc.mNpdt = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); } CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); if (subColIndex == 0) return subRowIndex; else if (subColIndex == 1) return static_cast(npcStruct.mSkills[subRowIndex]); else return QVariant(); // throw an exception here? } void CSMWorld::NpcSkillsRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); if (subColIndex == 1) npcStruct.mSkills[subRowIndex] = static_cast(value.toInt()); else return; // throw an exception here? record.setModified (npc); } int CSMWorld::NpcSkillsRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 2; } int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { // There are 27 skills return ESM::Skill::Length; } CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() {} void CSMWorld::NpcMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void CSMWorld::NpcMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { throw std::logic_error ("cannot remove a row to a fixed table"); } void CSMWorld::NpcMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } CSMWorld::NestedTableWrapperBase* CSMWorld::NpcMiscRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { throw std::logic_error ("table operation not supported"); } QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; if (autoCalc) switch (subColIndex) { case 0: return static_cast(record.get().mNpdt.mLevel); case 1: return QVariant(QVariant::UserType); case 2: return QVariant(QVariant::UserType); case 3: return QVariant(QVariant::UserType); case 4: return static_cast(record.get().mNpdt.mDisposition); case 5: return static_cast(record.get().mNpdt.mReputation); case 6: return static_cast(record.get().mNpdt.mRank); case 7: return record.get().mNpdt.mGold; default: return QVariant(); // throw an exception here? } else switch (subColIndex) { case 0: return static_cast(record.get().mNpdt.mLevel); case 1: return static_cast(record.get().mNpdt.mHealth); case 2: return static_cast(record.get().mNpdt.mMana); case 3: return static_cast(record.get().mNpdt.mFatigue); case 4: return static_cast(record.get().mNpdt.mDisposition); case 5: return static_cast(record.get().mNpdt.mReputation); case 6: return static_cast(record.get().mNpdt.mRank); case 7: return record.get().mNpdt.mGold; default: return QVariant(); // throw an exception here? } } void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; if (autoCalc) switch(subColIndex) { case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; case 1: return; case 2: return; case 3: return; case 4: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; case 5: npc.mNpdt.mReputation = static_cast(value.toInt()); break; case 6: npc.mNpdt.mRank = static_cast(value.toInt()); break; case 7: npc.mNpdt.mGold = value.toInt(); break; default: return; // throw an exception here? } else switch(subColIndex) { case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; case 1: npc.mNpdt.mHealth = static_cast(value.toInt()); break; case 2: npc.mNpdt.mMana = static_cast(value.toInt()); break; case 3: npc.mNpdt.mFatigue = static_cast(value.toInt()); break; case 4: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; case 5: npc.mNpdt.mReputation = static_cast(value.toInt()); break; case 6: npc.mNpdt.mRank = static_cast(value.toInt()); break; case 7: npc.mNpdt.mGold = value.toInt(); break; default: return; // throw an exception here? } record.setModified (npc); } int CSMWorld::NpcMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 8; // Level, Health, Mana, Fatigue, Disposition, Reputation, Rank, Gold } int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { return 1; // fixed at size 1 } CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() {} void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); // store the whole struct creature.mData = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); if (subColIndex == 0) return subRowIndex; else if (subColIndex == 1) switch (subRowIndex) { case 0: return creature.mData.mStrength; case 1: return creature.mData.mIntelligence; case 2: return creature.mData.mWillpower; case 3: return creature.mData.mAgility; case 4: return creature.mData.mSpeed; case 5: return creature.mData.mEndurance; case 6: return creature.mData.mPersonality; case 7: return creature.mData.mLuck; default: return QVariant(); // throw an exception here? } else return QVariant(); // throw an exception here? } void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); if (subColIndex == 1) switch(subRowIndex) { case 0: creature.mData.mStrength = value.toInt(); break; case 1: creature.mData.mIntelligence = value.toInt(); break; case 2: creature.mData.mWillpower = value.toInt(); break; case 3: creature.mData.mAgility = value.toInt(); break; case 4: creature.mData.mSpeed = value.toInt(); break; case 5: creature.mData.mEndurance = value.toInt(); break; case 6: creature.mData.mPersonality = value.toInt(); break; case 7: creature.mData.mLuck = value.toInt(); break; default: return; // throw an exception here? } else return; // throw an exception here? record.setModified (creature); } int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 2; } int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { // There are 8 attributes return 8; } CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() {} void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); // store the whole struct creature.mData = static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2) throw std::runtime_error ("index out of range"); if (subColIndex == 0) return subRowIndex + 1; else if (subColIndex == 1 || subColIndex == 2) return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; else throw std::runtime_error ("index out of range"); } void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2) throw std::runtime_error ("index out of range"); if (subColIndex == 1 || subColIndex == 2) creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); else return; // throw an exception here? record.setModified (creature); } int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 3; } int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { // There are 3 attacks return 3; } CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() {} CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() {} void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { throw std::logic_error ("cannot add a row to a fixed table"); } void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { throw std::logic_error ("cannot remove a row to a fixed table"); } void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { throw std::logic_error ("table operation not supported"); } CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const { throw std::logic_error ("table operation not supported"); } QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); switch (subColIndex) { case 0: return creature.mData.mLevel; case 1: return creature.mData.mHealth; case 2: return creature.mData.mMana; case 3: return creature.mData.mFatigue; case 4: return creature.mData.mSoul; case 5: return creature.mData.mCombat; case 6: return creature.mData.mMagic; case 7: return creature.mData.mStealth; case 8: return creature.mData.mGold; default: return QVariant(); // throw an exception here? } } void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); switch(subColIndex) { case 0: creature.mData.mLevel = value.toInt(); break; case 1: creature.mData.mHealth = value.toInt(); break; case 2: creature.mData.mMana = value.toInt(); break; case 3: creature.mData.mFatigue = value.toInt(); break; case 4: creature.mData.mSoul = value.toInt(); break; case 5: creature.mData.mCombat = value.toInt(); break; case 6: creature.mData.mMagic = value.toInt(); break; case 7: creature.mData.mStealth = value.toInt(); break; case 8: creature.mData.mGold = value.toInt(); break; default: return; // throw an exception here? } record.setModified (creature); } int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold } int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { return 1; // fixed at size 1 } CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) : EnchantableColumns (columns) , mType(nullptr) , mHealth(nullptr) , mSpeed(nullptr) , mReach(nullptr) , mChop{nullptr} , mSlash{nullptr} , mThrust{nullptr} {} CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) : EnchantableRefIdAdapter (UniversalId::Type_Weapon, columns), mColumns (columns) {} QVariant CSMWorld::WeaponRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); if (column==mColumns.mType) return record.get().mData.mType; if (column==mColumns.mHealth) return record.get().mData.mHealth; if (column==mColumns.mSpeed) return record.get().mData.mSpeed; if (column==mColumns.mReach) return record.get().mData.mReach; for (int i=0; i<2; ++i) { if (column==mColumns.mChop[i]) return record.get().mData.mChop[i]; if (column==mColumns.mSlash[i]) return record.get().mData.mSlash[i]; if (column==mColumns.mThrust[i]) return record.get().mData.mThrust[i]; } std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) return (record.get().mData.mFlags & iter->second)!=0; return EnchantableRefIdAdapter::getData (column, data, index); } void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); ESM::Weapon weapon = record.get(); if (column==mColumns.mType) weapon.mData.mType = value.toInt(); else if (column==mColumns.mHealth) weapon.mData.mHealth = value.toInt(); else if (column==mColumns.mSpeed) weapon.mData.mSpeed = value.toFloat(); else if (column==mColumns.mReach) weapon.mData.mReach = value.toFloat(); else if (column==mColumns.mChop[0]) weapon.mData.mChop[0] = value.toInt(); else if (column==mColumns.mChop[1]) weapon.mData.mChop[1] = value.toInt(); else if (column==mColumns.mSlash[0]) weapon.mData.mSlash[0] = value.toInt(); else if (column==mColumns.mSlash[1]) weapon.mData.mSlash[1] = value.toInt(); else if (column==mColumns.mThrust[0]) weapon.mData.mThrust[0] = value.toInt(); else if (column==mColumns.mThrust[1]) weapon.mData.mThrust[1] = value.toInt(); else { std::map::const_iterator iter = mColumns.mFlags.find (column); if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) weapon.mData.mFlags |= iter->second; else weapon.mData.mFlags &= ~iter->second; } else { EnchantableRefIdAdapter::setData (column, data, index, value); return; // Don't overwrite changes made by base class } } record.setModified(weapon); } openmw-openmw-0.48.0/apps/opencs/model/world/refidadapterimp.hpp000066400000000000000000002742771445372753700250110ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFIDADAPTERIMP_H #define CSM_WOLRD_REFIDADAPTERIMP_H #include #include #include #include #include #include #include #include "columnbase.hpp" #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" namespace CSMWorld { struct BaseColumns { const RefIdColumn *mId; const RefIdColumn *mModified; const RefIdColumn *mType; const RefIdColumn *mBlocked; BaseColumns () : mId(nullptr) , mModified(nullptr) , mType(nullptr) , mBlocked(nullptr) {} }; /// \brief Base adapter for all refereceable record types /// Adapters that can handle nested tables, needs to return valid qvariant for parent columns template class BaseRefIdAdapter : public RefIdAdapter { UniversalId::Type mType; BaseColumns mBase; public: BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base); std::string getId (const RecordBase& record) const override; void setId (RecordBase& record, const std::string& id) override; QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. UniversalId::Type getType() const; }; template BaseRefIdAdapter::BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base) : mType (type), mBase (base) {} template void BaseRefIdAdapter::setId (RecordBase& record, const std::string& id) { (dynamic_cast&> (record).get().mId) = id; } template std::string BaseRefIdAdapter::getId (const RecordBase& record) const { return dynamic_cast&> (record).get().mId; } template QVariant BaseRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, mType))); if (column==mBase.mId) return QString::fromUtf8 (record.get().mId.c_str()); if (column==mBase.mModified) { if (record.mState==Record::State_Erased) return static_cast (Record::State_Deleted); return static_cast (record.mState); } if (column==mBase.mType) return static_cast (mType); if (column==mBase.mBlocked) return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0; return QVariant(); } template void BaseRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, mType))); if (column==mBase.mModified) record.mState = static_cast (value.toInt()); else if (column==mBase.mBlocked) { RecordT record2 = record.get(); if (value.toInt() != 0) record2.mRecordFlags |= ESM::FLAG_Blocked; else record2.mRecordFlags &= ~ESM::FLAG_Blocked; record.setModified(record2); } } template UniversalId::Type BaseRefIdAdapter::getType() const { return mType; } // NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects // table at the moment). // // Spellmaking - not persistent - currently not part of objects table // Enchanting - not persistent - currently not part of objects table // // Leveled Creature - no model, so not persistent // Leveled Item - no model, so not persistent struct ModelColumns : public BaseColumns { const RefIdColumn *mModel; const RefIdColumn *mPersistence; ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr), mPersistence(nullptr) {} }; /// \brief Adapter for IDs with models (all but levelled lists) template class ModelRefIdAdapter : public BaseRefIdAdapter { ModelColumns mModel; public: ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template ModelRefIdAdapter::ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns) : BaseRefIdAdapter (type, columns), mModel (columns) {} template QVariant ModelRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mModel.mModel) return QString::fromUtf8 (record.get().mModel.c_str()); if (column==mModel.mPersistence) return (record.get().mRecordFlags & ESM::FLAG_Persistent) != 0; return BaseRefIdAdapter::getData (column, data, index); } template void ModelRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mModel.mModel) record2.mModel = value.toString().toUtf8().constData(); else if (column==mModel.mPersistence) { if (value.toInt() != 0) record2.mRecordFlags |= ESM::FLAG_Persistent; else record2.mRecordFlags &= ~ESM::FLAG_Persistent; } else { BaseRefIdAdapter::setData (column, data, index, value); return; } record.setModified(record2); } struct NameColumns : public ModelColumns { const RefIdColumn *mName; const RefIdColumn *mScript; NameColumns (const ModelColumns& base) : ModelColumns (base) , mName(nullptr) , mScript(nullptr) {} }; /// \brief Adapter for IDs with names (all but levelled lists and statics) template class NameRefIdAdapter : public ModelRefIdAdapter { NameColumns mName; public: NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template NameRefIdAdapter::NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns) : ModelRefIdAdapter (type, columns), mName (columns) {} template QVariant NameRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mName.mName) return QString::fromUtf8 (record.get().mName.c_str()); if (column==mName.mScript) return QString::fromUtf8 (record.get().mScript.c_str()); return ModelRefIdAdapter::getData (column, data, index); } template void NameRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mName.mName) record2.mName = value.toString().toUtf8().constData(); else if (column==mName.mScript) record2.mScript = value.toString().toUtf8().constData(); else { ModelRefIdAdapter::setData (column, data, index, value); return; } record.setModified(record2); } struct InventoryColumns : public NameColumns { const RefIdColumn *mIcon; const RefIdColumn *mWeight; const RefIdColumn *mValue; InventoryColumns (const NameColumns& base) : NameColumns (base) , mIcon(nullptr) , mWeight(nullptr) , mValue(nullptr) {} }; /// \brief Adapter for IDs that can go into an inventory template class InventoryRefIdAdapter : public NameRefIdAdapter { InventoryColumns mInventory; public: InventoryRefIdAdapter (UniversalId::Type type, const InventoryColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template InventoryRefIdAdapter::InventoryRefIdAdapter (UniversalId::Type type, const InventoryColumns& columns) : NameRefIdAdapter (type, columns), mInventory (columns) {} template QVariant InventoryRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mInventory.mIcon) return QString::fromUtf8 (record.get().mIcon.c_str()); if (column==mInventory.mWeight) return record.get().mData.mWeight; if (column==mInventory.mValue) return record.get().mData.mValue; return NameRefIdAdapter::getData (column, data, index); } template void InventoryRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mInventory.mIcon) record2.mIcon = value.toString().toUtf8().constData(); else if (column==mInventory.mWeight) record2.mData.mWeight = value.toFloat(); else if (column==mInventory.mValue) record2.mData.mValue = value.toInt(); else { NameRefIdAdapter::setData (column, data, index, value); return; } record.setModified(record2); } struct PotionColumns : public InventoryColumns { const RefIdColumn *mEffects; PotionColumns (const InventoryColumns& columns); }; class PotionRefIdAdapter : public InventoryRefIdAdapter { PotionColumns mColumns; const RefIdColumn *mAutoCalc; public: PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; struct IngredientColumns : public InventoryColumns { const RefIdColumn *mEffects; IngredientColumns (const InventoryColumns& columns); }; class IngredientRefIdAdapter : public InventoryRefIdAdapter { IngredientColumns mColumns; public: IngredientRefIdAdapter (const IngredientColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); public: IngredEffectRefIdAdapter(); ~IngredEffectRefIdAdapter() override; void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; struct EnchantableColumns : public InventoryColumns { const RefIdColumn *mEnchantment; const RefIdColumn *mEnchantmentPoints; EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) , mEnchantment(nullptr) , mEnchantmentPoints(nullptr) {} }; /// \brief Adapter for enchantable IDs template class EnchantableRefIdAdapter : public InventoryRefIdAdapter { EnchantableColumns mEnchantable; public: EnchantableRefIdAdapter (UniversalId::Type type, const EnchantableColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template EnchantableRefIdAdapter::EnchantableRefIdAdapter (UniversalId::Type type, const EnchantableColumns& columns) : InventoryRefIdAdapter (type, columns), mEnchantable (columns) {} template QVariant EnchantableRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mEnchantable.mEnchantment) return QString::fromUtf8 (record.get().mEnchant.c_str()); if (column==mEnchantable.mEnchantmentPoints) return static_cast (record.get().mData.mEnchant); return InventoryRefIdAdapter::getData (column, data, index); } template void EnchantableRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mEnchantable.mEnchantment) record2.mEnchant = value.toString().toUtf8().constData(); else if (column==mEnchantable.mEnchantmentPoints) record2.mData.mEnchant = value.toInt(); else { InventoryRefIdAdapter::setData (column, data, index, value); return; } record.setModified(record2); } struct ToolColumns : public InventoryColumns { const RefIdColumn *mQuality; const RefIdColumn *mUses; ToolColumns (const InventoryColumns& base) : InventoryColumns (base) , mQuality(nullptr) , mUses(nullptr) {} }; /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) template class ToolRefIdAdapter : public InventoryRefIdAdapter { ToolColumns mTools; public: ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template ToolRefIdAdapter::ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns) : InventoryRefIdAdapter (type, columns), mTools (columns) {} template QVariant ToolRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mTools.mQuality) return record.get().mData.mQuality; if (column==mTools.mUses) return record.get().mData.mUses; return InventoryRefIdAdapter::getData (column, data, index); } template void ToolRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mTools.mQuality) record2.mData.mQuality = value.toFloat(); else if (column==mTools.mUses) record2.mData.mUses = value.toInt(); else { InventoryRefIdAdapter::setData (column, data, index, value); return; } record.setModified(record2); } struct ActorColumns : public NameColumns { const RefIdColumn *mHello; const RefIdColumn *mFlee; const RefIdColumn *mFight; const RefIdColumn *mAlarm; const RefIdColumn *mInventory; const RefIdColumn *mSpells; const RefIdColumn *mDestinations; const RefIdColumn *mAiPackages; std::map mServices; ActorColumns (const NameColumns& base) : NameColumns (base) , mHello(nullptr) , mFlee(nullptr) , mFight(nullptr) , mAlarm(nullptr) , mInventory(nullptr) , mSpells(nullptr) , mDestinations(nullptr) , mAiPackages(nullptr) {} }; /// \brief Adapter for actor IDs (handles common AI functionality) template class ActorRefIdAdapter : public NameRefIdAdapter { ActorColumns mActors; public: ActorRefIdAdapter (UniversalId::Type type, const ActorColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template ActorRefIdAdapter::ActorRefIdAdapter (UniversalId::Type type, const ActorColumns& columns) : NameRefIdAdapter (type, columns), mActors (columns) {} template QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); if (column==mActors.mHello) return record.get().mAiData.mHello; if (column==mActors.mFlee) return record.get().mAiData.mFlee; if (column==mActors.mFight) return record.get().mAiData.mFight; if (column==mActors.mAlarm) return record.get().mAiData.mAlarm; if (column==mActors.mInventory) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mSpells) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mDestinations) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mAiPackages) return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); if (iter!=mActors.mServices.end()) return (record.get().mAiData.mServices & iter->second)!=0; return NameRefIdAdapter::getData (column, data, index); } template void ActorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); else if (column==mActors.mFlee) // Flee, Fight and Alarm ratings are probabilities. record2.mAiData.mFlee = std::min(100, value.toInt()); else if (column==mActors.mFight) record2.mAiData.mFight = std::min(100, value.toInt()); else if (column==mActors.mAlarm) record2.mAiData.mAlarm = std::min(100, value.toInt()); else { typename std::map::const_iterator iter = mActors.mServices.find (column); if (iter!=mActors.mServices.end()) { if (value.toInt()!=0) record2.mAiData.mServices |= iter->second; else record2.mAiData.mServices &= ~iter->second; } else { NameRefIdAdapter::setData (column, data, index, value); return; } } record.setModified(record2); } class ApparatusRefIdAdapter : public InventoryRefIdAdapter { const RefIdColumn *mType; const RefIdColumn *mQuality; public: ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, const RefIdColumn *quality); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class ArmorRefIdAdapter : public EnchantableRefIdAdapter { const RefIdColumn *mType; const RefIdColumn *mHealth; const RefIdColumn *mArmor; const RefIdColumn *mPartRef; public: ArmorRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type, const RefIdColumn *health, const RefIdColumn *armor, const RefIdColumn *partRef); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class BookRefIdAdapter : public EnchantableRefIdAdapter { const RefIdColumn *mBookType; const RefIdColumn *mSkill; const RefIdColumn *mText; public: BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *bookType, const RefIdColumn *skill, const RefIdColumn *text); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class ClothingRefIdAdapter : public EnchantableRefIdAdapter { const RefIdColumn *mType; const RefIdColumn *mPartRef; public: ClothingRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type, const RefIdColumn *partRef); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class ContainerRefIdAdapter : public NameRefIdAdapter { const RefIdColumn *mWeight; const RefIdColumn *mOrganic; const RefIdColumn *mRespawn; const RefIdColumn *mContent; public: ContainerRefIdAdapter (const NameColumns& columns, const RefIdColumn *weight, const RefIdColumn *organic, const RefIdColumn *respawn, const RefIdColumn *content); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; struct CreatureColumns : public ActorColumns { std::map mFlags; const RefIdColumn *mType; const RefIdColumn *mScale; const RefIdColumn *mOriginal; const RefIdColumn *mAttributes; const RefIdColumn *mAttacks; const RefIdColumn *mMisc; const RefIdColumn *mBloodType; CreatureColumns (const ActorColumns& actorColumns); }; class CreatureRefIdAdapter : public ActorRefIdAdapter { CreatureColumns mColumns; public: CreatureRefIdAdapter (const CreatureColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class DoorRefIdAdapter : public NameRefIdAdapter { const RefIdColumn *mOpenSound; const RefIdColumn *mCloseSound; public: DoorRefIdAdapter (const NameColumns& columns, const RefIdColumn *openSound, const RefIdColumn *closeSound); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; struct LightColumns : public InventoryColumns { const RefIdColumn *mTime; const RefIdColumn *mRadius; const RefIdColumn *mColor; const RefIdColumn *mSound; const RefIdColumn *mEmitterType; std::map mFlags; LightColumns (const InventoryColumns& columns); }; class LightRefIdAdapter : public InventoryRefIdAdapter { LightColumns mColumns; public: LightRefIdAdapter (const LightColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class MiscRefIdAdapter : public InventoryRefIdAdapter { const RefIdColumn *mKey; public: MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; struct NpcColumns : public ActorColumns { std::map mFlags; const RefIdColumn *mRace; const RefIdColumn *mClass; const RefIdColumn *mFaction; const RefIdColumn *mHair; const RefIdColumn *mHead; const RefIdColumn *mAttributes; // depends on npc type const RefIdColumn *mSkills; // depends on npc type const RefIdColumn *mMisc; // may depend on npc type, e.g. FactionID const RefIdColumn *mBloodType; const RefIdColumn *mGender; NpcColumns (const ActorColumns& actorColumns); }; class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; public: NpcRefIdAdapter (const NpcColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; struct WeaponColumns : public EnchantableColumns { const RefIdColumn *mType; const RefIdColumn *mHealth; const RefIdColumn *mSpeed; const RefIdColumn *mReach; const RefIdColumn *mChop[2]; const RefIdColumn *mSlash[2]; const RefIdColumn *mThrust[2]; std::map mFlags; WeaponColumns (const EnchantableColumns& columns); }; class WeaponRefIdAdapter : public EnchantableRefIdAdapter { WeaponColumns mColumns; public: WeaponRefIdAdapter (const WeaponColumns& columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; class NestedRefIdAdapterBase; class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { public: NpcAttributesRefIdAdapter (); void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { public: NpcSkillsRefIdAdapter (); void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: NpcMiscRefIdAdapter (); ~NpcMiscRefIdAdapter() override; void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase { public: CreatureAttributesRefIdAdapter (); void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase { public: CreatureAttackRefIdAdapter (); void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase { CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); public: CreatureMiscRefIdAdapter (); ~CreatureMiscRefIdAdapter() override; void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override; void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override; void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override; QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; }; template class EffectsListAdapter; template class EffectsRefIdAdapter : public EffectsListAdapter, public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented EffectsRefIdAdapter (const EffectsRefIdAdapter&); EffectsRefIdAdapter& operator= (const EffectsRefIdAdapter&); public: EffectsRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~EffectsRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); EffectsListAdapter::addRow(record, position); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); EffectsListAdapter::removeRow(record, rowToRemove); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); EffectsListAdapter::setTable(record, nestedTable); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return EffectsListAdapter::table(record); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return EffectsListAdapter::getData(record, subRowIndex, subColIndex); } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); EffectsListAdapter::setData(record, value, subRowIndex, subColIndex); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { const Record record; // not used, just a dummy return EffectsListAdapter::getColumnsCount(record); } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return EffectsListAdapter::getRowsCount(record); } }; template class NestedInventoryRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented NestedInventoryRefIdAdapter (const NestedInventoryRefIdAdapter&); NestedInventoryRefIdAdapter& operator= (const NestedInventoryRefIdAdapter&); public: NestedInventoryRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~NestedInventoryRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; ESM::ContItem newRow = ESM::ContItem(); if (position >= (int)list.size()) list.push_back(newRow); else list.insert(list.begin()+position, newRow); record.setModified (container); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (container); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT container = record.get(); container.mInventory.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (container); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mInventory.mList); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mInventory.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const ESM::ContItem& content = list.at(subRowIndex); switch (subColIndex) { case 0: return QString::fromUtf8(content.mItem.c_str()); case 1: return content.mCount; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); switch(subColIndex) { case 0: list.at(subRowIndex).mItem.assign(std::string(value.toString().toUtf8().constData())); break; case 1: list.at(subRowIndex).mCount = value.toInt(); break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (container); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 2; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mInventory.mList.size()); } }; template class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); NestedSpellRefIdAdapter& operator= (const NestedSpellRefIdAdapter&); public: NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~NestedSpellRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT caster = record.get(); std::vector& list = caster.mSpells.mList; std::string newString; if (position >= (int)list.size()) list.push_back(newString); else list.insert(list.begin()+position, newString); record.setModified (caster); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT caster = record.get(); std::vector& list = caster.mSpells.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (caster); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT caster = record.get(); caster.mSpells.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (caster); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mSpells.mList); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mSpells.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const std::string& content = list.at(subRowIndex); if (subColIndex == 0) return QString::fromUtf8(content.c_str()); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT caster = record.get(); std::vector& list = caster.mSpells.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); if (subColIndex == 0) list.at(subRowIndex) = std::string(value.toString().toUtf8()); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); record.setModified (caster); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 1; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mSpells.mList.size()); } }; template class NestedTravelRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented NestedTravelRefIdAdapter (const NestedTravelRefIdAdapter&); NestedTravelRefIdAdapter& operator= (const NestedTravelRefIdAdapter&); public: NestedTravelRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~NestedTravelRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; ESM::Position newPos; for (unsigned i = 0; i < 3; ++i) { newPos.pos[i] = 0; newPos.rot[i] = 0; } ESM::Transport::Dest newRow; newRow.mPos = newPos; newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); else list.insert(list.begin()+position, newRow); record.setModified (traveller); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (traveller); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT traveller = record.get(); traveller.mTransport.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (traveller); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mTransport.mList); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mTransport.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const ESM::Transport::Dest& content = list.at(subRowIndex); switch (subColIndex) { case 0: return QString::fromUtf8(content.mCellName.c_str()); case 1: return content.mPos.pos[0]; case 2: return content.mPos.pos[1]; case 3: return content.mPos.pos[2]; case 4: return content.mPos.rot[0]; case 5: return content.mPos.rot[1]; case 6: return content.mPos.rot[2]; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); switch(subColIndex) { case 0: list.at(subRowIndex).mCellName = std::string(value.toString().toUtf8().constData()); break; case 1: list.at(subRowIndex).mPos.pos[0] = value.toFloat(); break; case 2: list.at(subRowIndex).mPos.pos[1] = value.toFloat(); break; case 3: list.at(subRowIndex).mPos.pos[2] = value.toFloat(); break; case 4: list.at(subRowIndex).mPos.rot[0] = value.toFloat(); break; case 5: list.at(subRowIndex).mPos.rot[1] = value.toFloat(); break; case 6: list.at(subRowIndex).mPos.rot[2] = value.toFloat(); break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (traveller); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 7; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mTransport.mList.size()); } }; template class ActorAiRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented ActorAiRefIdAdapter (const ActorAiRefIdAdapter&); ActorAiRefIdAdapter& operator= (const ActorAiRefIdAdapter&); public: ActorAiRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~ActorAiRefIdAdapter() {} // FIXME: should check if the AI package type is already in the list and use a default // that wasn't used already (in extreme case do not add anything at all? void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; ESM::AIPackage newRow; newRow.mType = ESM::AI_Wander; newRow.mWander.mDistance = 0; newRow.mWander.mDuration = 0; newRow.mWander.mTimeOfDay = 0; for (int i = 0; i < 8; ++i) newRow.mWander.mIdle[i] = 0; newRow.mWander.mShouldRepeat = 1; newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); else list.insert(list.begin()+position, newRow); record.setModified (actor); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (actor); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT actor = record.get(); actor.mAiPackage.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (actor); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mAiPackage.mList); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mAiPackage.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const ESM::AIPackage& content = list.at(subRowIndex); switch (subColIndex) { case 0: // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { case ESM::AI_Wander: return 0; case ESM::AI_Travel: return 1; case ESM::AI_Follow: return 2; case ESM::AI_Escort: return 3; case ESM::AI_Activate: return 4; case ESM::AI_CNDT: default: return QVariant(); } case 1: // wander dist if (content.mType == ESM::AI_Wander) return content.mWander.mDistance; else return QVariant(); case 2: // wander dur if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mWander.mDuration; else return QVariant(); case 3: // wander ToD if (content.mType == ESM::AI_Wander) return content.mWander.mTimeOfDay; // FIXME: not sure of the format else return QVariant(); case 4: // wander idle case 5: case 6: case 7: case 8: case 9: case 10: case 11: if (content.mType == ESM::AI_Wander) return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); case 12: // repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; else if (content.mType == ESM::AI_Travel) return content.mTravel.mShouldRepeat != 0; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mShouldRepeat != 0; else if (content.mType == ESM::AI_Activate) return content.mActivate.mShouldRepeat != 0; else return QVariant(); case 13: // activate name if (content.mType == ESM::AI_Activate) return QString(content.mActivate.mName.toString().c_str()); else return QVariant(); case 14: // target id if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString(content.mTarget.mId.toString().c_str()); else return QVariant(); case 15: // target cell if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString::fromUtf8(content.mCellName.c_str()); else return QVariant(); case 16: if (content.mType == ESM::AI_Travel) return content.mTravel.mX; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mX; else return QVariant(); case 17: if (content.mType == ESM::AI_Travel) return content.mTravel.mY; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mY; else return QVariant(); case 18: if (content.mType == ESM::AI_Travel) return content.mTravel.mZ; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mZ; else return QVariant(); default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); ESM::AIPackage& content = list.at(subRowIndex); switch(subColIndex) { case 0: // ai package type switch (value.toInt()) { case 0: content.mType = ESM::AI_Wander; break; case 1: content.mType = ESM::AI_Travel; break; case 2: content.mType = ESM::AI_Follow; break; case 3: content.mType = ESM::AI_Escort; break; case 4: content.mType = ESM::AI_Activate; break; default: return; // return without saving } break; // always save case 1: if (content.mType == ESM::AI_Wander) content.mWander.mDistance = static_cast(value.toInt()); else return; // return without saving break; // always save case 2: if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mWander.mDuration = static_cast(value.toInt()); else return; // return without saving break; case 3: if (content.mType == ESM::AI_Wander) content.mWander.mTimeOfDay = static_cast(value.toInt()); else return; // return without saving break; // always save case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: if (content.mType == ESM::AI_Wander) content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); else return; // return without saving break; // always save case 12: if (content.mType == ESM::AI_Wander) content.mWander.mShouldRepeat = static_cast(value.toInt()); else if (content.mType == ESM::AI_Travel) content.mTravel.mShouldRepeat = static_cast(value.toInt()); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mShouldRepeat = static_cast(value.toInt()); else if (content.mType == ESM::AI_Activate) content.mActivate.mShouldRepeat = static_cast(value.toInt()); else return; // return without saving break; // always save case 13: // NAME32 if (content.mType == ESM::AI_Activate) { const QByteArray name = value.toString().toUtf8(); content.mActivate.mName.assign(std::string_view(name.constData(), name.size())); } else return; // return without saving break; // always save case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) { const QByteArray id = value.toString().toUtf8(); content.mTarget.mId.assign(std::string_view(id.constData(), id.size())); } else return; // return without saving break; // always save case 15: if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mCellName = std::string(value.toString().toUtf8().constData()); else return; // return without saving break; // always save case 16: if (content.mType == ESM::AI_Travel) content.mTravel.mX = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mX = value.toFloat(); else return; // return without saving break; // always save case 17: if (content.mType == ESM::AI_Travel) content.mTravel.mY = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mY = value.toFloat(); else return; // return without saving break; // always save case 18: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving break; // always save default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (actor); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 19; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mAiPackage.mList.size()); } }; template class BodyPartRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented BodyPartRefIdAdapter (const BodyPartRefIdAdapter&); BodyPartRefIdAdapter& operator= (const BodyPartRefIdAdapter&); public: BodyPartRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~BodyPartRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; ESM::PartReference newPart; newPart.mPart = 0; // 0 == head newPart.mMale.clear(); newPart.mFemale.clear(); if (position >= (int)list.size()) list.push_back(newPart); else list.insert(list.begin()+position, newPart); record.setModified (apparel); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (apparel); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT apparel = record.get(); apparel.mParts.mParts = static_cast >&>(nestedTable).mNestedTable; record.setModified (apparel); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mParts.mParts); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mParts.mParts; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const ESM::PartReference& content = list.at(subRowIndex); switch (subColIndex) { case 0: { if (content.mPart < ESM::PRT_Count) return content.mPart; else throw std::runtime_error("Part Reference Type unexpected value"); } case 1: return QString(content.mMale.c_str()); case 2: return QString(content.mFemale.c_str()); default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); switch(subColIndex) { case 0: list.at(subRowIndex).mPart = static_cast(value.toInt()); break; case 1: list.at(subRowIndex).mMale = value.toString().toStdString(); break; case 2: list.at(subRowIndex).mFemale = value.toString().toStdString(); break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (apparel); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 3; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mParts.mParts.size()); } }; struct LevListColumns : public BaseColumns { const RefIdColumn *mLevList; const RefIdColumn *mNestedListLevList; LevListColumns (const BaseColumns& base) : BaseColumns (base) , mLevList(nullptr) , mNestedListLevList(nullptr) {} }; template class LevelledListRefIdAdapter : public BaseRefIdAdapter { LevListColumns mLevList; public: LevelledListRefIdAdapter (UniversalId::Type type, const LevListColumns &columns); QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; void setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const override; ///< If the data type does not match an exception is thrown. }; template LevelledListRefIdAdapter::LevelledListRefIdAdapter (UniversalId::Type type, const LevListColumns &columns) : BaseRefIdAdapter (type, columns), mLevList (columns) {} template QVariant LevelledListRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) return QVariant::fromValue(ColumnBase::TableEdit_Full); return BaseRefIdAdapter::getData (column, data, index); } template void LevelledListRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const { BaseRefIdAdapter::setData (column, data, index, value); return; } // for non-tables template class NestedListLevListRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented NestedListLevListRefIdAdapter (const NestedListLevListRefIdAdapter&); NestedListLevListRefIdAdapter& operator= (const NestedListLevListRefIdAdapter&); public: NestedListLevListRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~NestedListLevListRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { throw std::logic_error ("cannot add a row to a fixed table"); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { throw std::logic_error ("cannot remove a row to a fixed table"); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { throw std::logic_error ("table operation not supported"); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { throw std::logic_error ("table operation not supported"); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); if (mType == UniversalId::Type_CreatureLevelledList) { switch (subColIndex) { case 0: return QVariant(); // disable the checkbox editor case 1: return record.get().mFlags & ESM::CreatureLevList::AllLevels; case 2: return static_cast (record.get().mChanceNone); default: throw std::runtime_error("Trying to access non-existing column in levelled creatues!"); } } else { switch (subColIndex) { case 0: return record.get().mFlags & ESM::ItemLevList::Each; case 1: return record.get().mFlags & ESM::ItemLevList::AllLevels; case 2: return static_cast (record.get().mChanceNone); default: throw std::runtime_error("Trying to access non-existing column in levelled items!"); } } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT leveled = record.get(); if (mType == UniversalId::Type_CreatureLevelledList) { switch(subColIndex) { case 0: return; // return without saving case 1: { if(value.toBool()) { leveled.mFlags |= ESM::CreatureLevList::AllLevels; break; } else { leveled.mFlags &= ~ESM::CreatureLevList::AllLevels; break; } } case 2: leveled.mChanceNone = static_cast(value.toInt()); break; default: throw std::runtime_error("Trying to set non-existing column in levelled creatures!"); } } else { switch(subColIndex) { case 0: { if(value.toBool()) { leveled.mFlags |= ESM::ItemLevList::Each; break; } else { leveled.mFlags &= ~ESM::ItemLevList::Each; break; } } case 1: { if(value.toBool()) { leveled.mFlags |= ESM::ItemLevList::AllLevels; break; } else { leveled.mFlags &= ~ESM::ItemLevList::AllLevels; break; } } case 2: leveled.mChanceNone = static_cast(value.toInt()); break; default: throw std::runtime_error("Trying to set non-existing column in levelled items!"); } } record.setModified (leveled); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 3; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { return 1; // fixed at size 1 } }; // for tables template class NestedLevListRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented NestedLevListRefIdAdapter (const NestedLevListRefIdAdapter&); NestedLevListRefIdAdapter& operator= (const NestedLevListRefIdAdapter&); public: NestedLevListRefIdAdapter(UniversalId::Type type) :mType(type) {} virtual ~NestedLevListRefIdAdapter() {} void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; ESM::LevelledListBase::LevelItem newItem; newItem.mId.clear(); newItem.mLevel = 0; if (position >= (int)list.size()) list.push_back(newItem); else list.insert(list.begin()+position, newItem); record.setModified (leveled); } void removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) throw std::runtime_error ("index out of range"); list.erase (list.begin () + rowToRemove); record.setModified (leveled); } void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); ESXRecordT leveled = record.get(); leveled.mList = static_cast >&>(nestedTable).mNestedTable; record.setModified (leveled); } NestedTableWrapperBase* nestedTable (const RefIdColumn* column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(record.get().mList); } QVariant getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); const std::vector& list = record.get().mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); const ESM::LevelledListBase::LevelItem& content = list.at(subRowIndex); switch (subColIndex) { case 0: return QString(content.mId.c_str()); case 1: return content.mLevel; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } void setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override { Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) throw std::runtime_error ("index out of range"); switch(subColIndex) { case 0: list.at(subRowIndex).mId = value.toString().toStdString(); break; case 1: list.at(subRowIndex).mLevel = static_cast(value.toInt()); break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } record.setModified (leveled); } int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override { return 2; } int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mList.size()); } }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/refidcollection.cpp000066400000000000000000001277071445372753700250040ustar00rootroot00000000000000#include "refidcollection.hpp" #include #include #include #include #include "refidadapter.hpp" #include "refidadapterimp.hpp" #include "columns.hpp" #include "nestedtablewrapper.hpp" #include "nestedcoladapterimp.hpp" CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, bool editable, bool userEditable) : NestableColumn (columnId, displayType, flag), mEditable (editable), mUserEditable (userEditable) {} bool CSMWorld::RefIdColumn::isEditable() const { return mEditable; } bool CSMWorld::RefIdColumn::isUserEditable() const { return mUserEditable; } const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalId::Type type) const { std::map::const_iterator iter = mAdapters.find (type); if (iter==mAdapters.end()) throw std::logic_error ("unsupported type in RefIdCollection"); return *iter->second; } CSMWorld::RefIdCollection::RefIdCollection() { BaseColumns baseColumns; mColumns.emplace_back(Columns::ColumnId_Id, ColumnBase::Display_Id, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mId = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Modification, ColumnBase::Display_RecordState, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true, false); baseColumns.mModified = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); baseColumns.mBlocked = &mColumns.back(); ModelColumns modelColumns (baseColumns); mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean); modelColumns.mPersistence = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); modelColumns.mModel = &mColumns.back(); NameColumns nameColumns (modelColumns); // Only items that can be placed in a container have the 32 character limit, but enforce // that for all referenceable types for now. mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String32); nameColumns.mName = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Script, ColumnBase::Display_Script); nameColumns.mScript = &mColumns.back(); InventoryColumns inventoryColumns (nameColumns); mColumns.emplace_back(Columns::ColumnId_Icon, ColumnBase::Display_Icon); inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns (inventoryColumns); mColumns.emplace_back(Columns::ColumnId_EffectList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); ingredientColumns.mEffects = &mColumns.back(); std::map ingredientEffectsMap; ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, new IngredEffectRefIdAdapter ())); mNestedAdapters.emplace_back(&mColumns.back(), ingredientEffectsMap); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); // nested table PotionColumns potionColumns (inventoryColumns); mColumns.emplace_back(Columns::ColumnId_EffectList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); potionColumns.mEffects = &mColumns.back(); // see refidadapterimp.hpp std::map effectsMap; effectsMap.insert(std::make_pair(UniversalId::Type_Potion, new EffectsRefIdAdapter (UniversalId::Type_Potion))); mNestedAdapters.emplace_back(&mColumns.back(), effectsMap); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); EnchantableColumns enchantableColumns (inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Enchantment, ColumnBase::Display_Enchantment); enchantableColumns.mEnchantment = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_EnchantmentPoints, ColumnBase::Display_Integer); enchantableColumns.mEnchantmentPoints = &mColumns.back(); ToolColumns toolsColumns (inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Quality, ColumnBase::Display_Float); toolsColumns.mQuality = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Charges, ColumnBase::Display_Integer); toolsColumns.mUses = &mColumns.back(); ActorColumns actorsColumns (nameColumns); mColumns.emplace_back(Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger16); actorsColumns.mHello = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_AiFlee, ColumnBase::Display_UnsignedInteger8); actorsColumns.mFlee = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_AiFight, ColumnBase::Display_UnsignedInteger8); actorsColumns.mFight = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_AiAlarm, ColumnBase::Display_UnsignedInteger8); actorsColumns.mAlarm = &mColumns.back(); // Nested table mColumns.emplace_back(Columns::ColumnId_ActorInventory, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mInventory = &mColumns.back(); std::map inventoryMap; inventoryMap.insert(std::make_pair(UniversalId::Type_Npc, new NestedInventoryRefIdAdapter (UniversalId::Type_Npc))); inventoryMap.insert(std::make_pair(UniversalId::Type_Creature, new NestedInventoryRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), inventoryMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); // Nested table mColumns.emplace_back(Columns::ColumnId_SpellList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mSpells = &mColumns.back(); std::map spellsMap; spellsMap.insert(std::make_pair(UniversalId::Type_Npc, new NestedSpellRefIdAdapter (UniversalId::Type_Npc))); spellsMap.insert(std::make_pair(UniversalId::Type_Creature, new NestedSpellRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), spellsMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); // Nested table mColumns.emplace_back(Columns::ColumnId_NpcDestinations, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mDestinations = &mColumns.back(); std::map destMap; destMap.insert(std::make_pair(UniversalId::Type_Npc, new NestedTravelRefIdAdapter (UniversalId::Type_Npc))); destMap.insert(std::make_pair(UniversalId::Type_Creature, new NestedTravelRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), destMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_DestinationCell, CSMWorld::ColumnBase::Display_Cell)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); // Nested table mColumns.emplace_back(Columns::ColumnId_AiPackageList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mAiPackages = &mColumns.back(); std::map aiMap; aiMap.insert(std::make_pair(UniversalId::Type_Npc, new ActorAiRefIdAdapter (UniversalId::Type_Npc))); aiMap.insert(std::make_pair(UniversalId::Type_Creature, new ActorAiRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), aiMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiPackageType, CSMWorld::ColumnBase::Display_AiPackageType)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderDist, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); static const struct { int mName; unsigned int mFlag; } sServiceTable[] = { { Columns::ColumnId_BuysWeapons, ESM::NPC::Weapon}, { Columns::ColumnId_BuysArmor, ESM::NPC::Armor}, { Columns::ColumnId_BuysClothing, ESM::NPC::Clothing}, { Columns::ColumnId_BuysBooks, ESM::NPC::Books}, { Columns::ColumnId_BuysIngredients, ESM::NPC::Ingredients}, { Columns::ColumnId_BuysLockpicks, ESM::NPC::Picks}, { Columns::ColumnId_BuysProbes, ESM::NPC::Probes}, { Columns::ColumnId_BuysLights, ESM::NPC::Lights}, { Columns::ColumnId_BuysApparati, ESM::NPC::Apparatus}, { Columns::ColumnId_BuysRepairItems, ESM::NPC::RepairItem}, { Columns::ColumnId_BuysMiscItems, ESM::NPC::Misc}, { Columns::ColumnId_BuysPotions, ESM::NPC::Potions}, { Columns::ColumnId_BuysMagicItems, ESM::NPC::MagicItems}, { Columns::ColumnId_SellsSpells, ESM::NPC::Spells}, { Columns::ColumnId_Trainer, ESM::NPC::Training}, { Columns::ColumnId_Spellmaking, ESM::NPC::Spellmaking}, { Columns::ColumnId_EnchantingService, ESM::NPC::Enchanting}, { Columns::ColumnId_RepairService, ESM::NPC::Repair}, { -1, 0 } }; for (int i=0; sServiceTable[i].mName!=-1; ++i) { mColumns.emplace_back(sServiceTable[i].mName, ColumnBase::Display_Boolean); actorsColumns.mServices.insert (std::make_pair (&mColumns.back(), sServiceTable[i].mFlag)); } mColumns.emplace_back(Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); const RefIdColumn *autoCalc = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ApparatusType, ColumnBase::Display_ApparatusType); const RefIdColumn *apparatusType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ArmorType, ColumnBase::Display_ArmorType); const RefIdColumn *armorType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Health, ColumnBase::Display_Integer); const RefIdColumn *health = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ArmorValue, ColumnBase::Display_Integer); const RefIdColumn *armor = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_BookType, ColumnBase::Display_BookType); const RefIdColumn *bookType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Skill, ColumnBase::Display_SkillId); const RefIdColumn *skill = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Text, ColumnBase::Display_LongString); const RefIdColumn *text = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType); const RefIdColumn *clothingType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_WeightCapacity, ColumnBase::Display_Float); const RefIdColumn *weightCapacity = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_OrganicContainer, ColumnBase::Display_Boolean); const RefIdColumn *organic = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Respawn, ColumnBase::Display_Boolean); const RefIdColumn *respawn = &mColumns.back(); // Nested table mColumns.emplace_back(Columns::ColumnId_ContainerContent, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); const RefIdColumn *content = &mColumns.back(); std::map contMap; contMap.insert(std::make_pair(UniversalId::Type_Container, new NestedInventoryRefIdAdapter (UniversalId::Type_Container))); mNestedAdapters.emplace_back(&mColumns.back(), contMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); CreatureColumns creatureColumns (actorsColumns); mColumns.emplace_back(Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType); creatureColumns.mType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Scale, ColumnBase::Display_Float); creatureColumns.mScale = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ParentCreature, ColumnBase::Display_Creature); creatureColumns.mOriginal = &mColumns.back(); static const struct { int mName; unsigned int mFlag; } sCreatureFlagTable[] = { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, { Columns::ColumnId_Essential, ESM::Creature::Essential }, { -1, 0 } }; // for re-use in NPC records const RefIdColumn *essential = nullptr; for (int i=0; sCreatureFlagTable[i].mName!=-1; ++i) { mColumns.emplace_back(sCreatureFlagTable[i].mName, ColumnBase::Display_Boolean); creatureColumns.mFlags.insert (std::make_pair (&mColumns.back(), sCreatureFlagTable[i].mFlag)); switch (sCreatureFlagTable[i].mFlag) { case ESM::Creature::Essential: essential = &mColumns.back(); break; } } mColumns.emplace_back(Columns::ColumnId_BloodType, ColumnBase::Display_BloodType); // For re-use in NPC records. const RefIdColumn *bloodType = &mColumns.back(); creatureColumns.mBloodType = bloodType; creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); // Nested table mColumns.emplace_back(Columns::ColumnId_CreatureAttributes, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); creatureColumns.mAttributes = &mColumns.back(); std::map creaAttrMap; creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), creaAttrMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); // Nested table mColumns.emplace_back(Columns::ColumnId_CreatureAttack, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); creatureColumns.mAttacks = &mColumns.back(); std::map attackMap; attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), attackMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); // Nested list mColumns.emplace_back(Columns::ColumnId_CreatureMisc, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); creatureColumns.mMisc = &mColumns.back(); std::map creaMiscMap; creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), creaMiscMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.emplace_back(Columns::ColumnId_OpenSound, ColumnBase::Display_Sound); const RefIdColumn *openSound = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_CloseSound, ColumnBase::Display_Sound); const RefIdColumn *closeSound = &mColumns.back(); LightColumns lightColumns (inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Duration, ColumnBase::Display_Integer); lightColumns.mTime = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Radius, ColumnBase::Display_Integer); lightColumns.mRadius = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Colour, ColumnBase::Display_Colour); lightColumns.mColor = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Sound, ColumnBase::Display_Sound); lightColumns.mSound = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_EmitterType, ColumnBase::Display_EmitterType); lightColumns.mEmitterType = &mColumns.back(); static const struct { int mName; unsigned int mFlag; } sLightFlagTable[] = { { Columns::ColumnId_Dynamic, ESM::Light::Dynamic }, { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Fire, ESM::Light::Fire }, { Columns::ColumnId_OffByDefault, ESM::Light::OffDefault }, { -1, 0 } }; for (int i=0; sLightFlagTable[i].mName!=-1; ++i) { mColumns.emplace_back(sLightFlagTable[i].mName, ColumnBase::Display_Boolean); lightColumns.mFlags.insert (std::make_pair (&mColumns.back(), sLightFlagTable[i].mFlag)); } mColumns.emplace_back(Columns::ColumnId_IsKey, ColumnBase::Display_Boolean); const RefIdColumn *key = &mColumns.back(); NpcColumns npcColumns (actorsColumns); mColumns.emplace_back(Columns::ColumnId_Race, ColumnBase::Display_Race); npcColumns.mRace = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Class, ColumnBase::Display_Class); npcColumns.mClass = &mColumns.back(); // NAME32 enforced in IdCompletionDelegate::createEditor() mColumns.emplace_back(Columns::ColumnId_Faction, ColumnBase::Display_Faction); npcColumns.mFaction = &mColumns.back(); mColumns.emplace_back(Columns::Columnid_Hair, ColumnBase::Display_BodyPart); npcColumns.mHair = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Head, ColumnBase::Display_BodyPart); npcColumns.mHead = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc); npcColumns.mGender = &mColumns.back(); npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential)); npcColumns.mFlags.insert (std::make_pair (respawn, ESM::NPC::Respawn)); npcColumns.mFlags.insert (std::make_pair (autoCalc, ESM::NPC::Autocalc)); // Re-used from Creature records. npcColumns.mBloodType = bloodType; // Need a way to add a table of stats and values (rather than adding a long list of // entries in the dialogue subview) E.g. attributes+stats(health, mana, fatigue), skills // These needs to be driven from the autocalculated setting. // Nested table mColumns.emplace_back(Columns::ColumnId_NpcAttributes, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), attrMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); // Nested table mColumns.emplace_back(Columns::ColumnId_NpcSkills, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), skillsMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); // Nested list mColumns.emplace_back(Columns::ColumnId_NpcMisc, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); npcColumns.mMisc = &mColumns.back(); std::map miscMap; miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), miscMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_SignedInteger16)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_UnsignedInteger16)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_UnsignedInteger16)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_UnsignedInteger16)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcReputation, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); WeaponColumns weaponColumns (enchantableColumns); mColumns.emplace_back(Columns::ColumnId_WeaponType, ColumnBase::Display_WeaponType); weaponColumns.mType = &mColumns.back(); weaponColumns.mHealth = health; mColumns.emplace_back(Columns::ColumnId_WeaponSpeed, ColumnBase::Display_Float); weaponColumns.mSpeed = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_WeaponReach, ColumnBase::Display_Float); weaponColumns.mReach = &mColumns.back(); for (int i=0; i<3; ++i) { const RefIdColumn **column = i==0 ? weaponColumns.mChop : (i==1 ? weaponColumns.mSlash : weaponColumns.mThrust); for (int j=0; j<2; ++j) { mColumns.emplace_back(Columns::ColumnId_MinChop+i*2+j, ColumnBase::Display_Integer); column[j] = &mColumns.back(); } } static const struct { int mName; unsigned int mFlag; } sWeaponFlagTable[] = { { Columns::ColumnId_Magical, ESM::Weapon::Magical }, { Columns::ColumnId_Silver, ESM::Weapon::Silver }, { -1, 0 } }; for (int i=0; sWeaponFlagTable[i].mName!=-1; ++i) { mColumns.emplace_back(sWeaponFlagTable[i].mName, ColumnBase::Display_Boolean); weaponColumns.mFlags.insert (std::make_pair (&mColumns.back(), sWeaponFlagTable[i].mFlag)); } // Nested table mColumns.emplace_back(Columns::ColumnId_PartRefList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); const RefIdColumn *partRef = &mColumns.back(); std::map partMap; partMap.insert(std::make_pair(UniversalId::Type_Armor, new BodyPartRefIdAdapter (UniversalId::Type_Armor))); partMap.insert(std::make_pair(UniversalId::Type_Clothing, new BodyPartRefIdAdapter (UniversalId::Type_Clothing))); mNestedAdapters.emplace_back(&mColumns.back(), partMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PartRefType, CSMWorld::ColumnBase::Display_PartRefType)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PartRefMale, CSMWorld::ColumnBase::Display_BodyPart)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PartRefFemale, CSMWorld::ColumnBase::Display_BodyPart)); LevListColumns creatureLevListColumns (baseColumns); LevListColumns itemLevListColumns (baseColumns); std::map creatureLevListMap, itemLevListMap; creatureLevListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, new NestedLevListRefIdAdapter (UniversalId::Type_CreatureLevelledList))); itemLevListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, new NestedLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); // Levelled creature nested table mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); creatureLevListColumns.mLevList = &mColumns.back(); mNestedAdapters.emplace_back(&mColumns.back(), creatureLevListMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledCreatureId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); // Levelled item nested table mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); itemLevListColumns.mLevList = &mColumns.back(); mNestedAdapters.emplace_back(&mColumns.back(), itemLevListMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); // Shared levelled list nested list mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); creatureLevListColumns.mNestedListLevList = &mColumns.back(); itemLevListColumns.mNestedListLevList = &mColumns.back(); std::map nestedListLevListMap; nestedListLevListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, new NestedListLevListRefIdAdapter (UniversalId::Type_CreatureLevelledList))); nestedListLevListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, new NestedListLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); mNestedAdapters.emplace_back(&mColumns.back(), nestedListLevListMap); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemTypeEach, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mAdapters.insert (std::make_pair (UniversalId::Type_Activator, new NameRefIdAdapter (UniversalId::Type_Activator, nameColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Potion, new PotionRefIdAdapter (potionColumns, autoCalc))); mAdapters.insert (std::make_pair (UniversalId::Type_Apparatus, new ApparatusRefIdAdapter (inventoryColumns, apparatusType, toolsColumns.mQuality))); mAdapters.insert (std::make_pair (UniversalId::Type_Armor, new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor, partRef))); mAdapters.insert (std::make_pair (UniversalId::Type_Book, new BookRefIdAdapter (enchantableColumns, bookType, skill, text))); mAdapters.insert (std::make_pair (UniversalId::Type_Clothing, new ClothingRefIdAdapter (enchantableColumns, clothingType, partRef))); mAdapters.insert (std::make_pair (UniversalId::Type_Container, new ContainerRefIdAdapter (nameColumns, weightCapacity, organic, respawn, content))); mAdapters.insert (std::make_pair (UniversalId::Type_Creature, new CreatureRefIdAdapter (creatureColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Door, new DoorRefIdAdapter (nameColumns, openSound, closeSound))); mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, new IngredientRefIdAdapter (ingredientColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, new LevelledListRefIdAdapter ( UniversalId::Type_CreatureLevelledList, creatureLevListColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_ItemLevelledList, new LevelledListRefIdAdapter (UniversalId::Type_ItemLevelledList, itemLevListColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Light, new LightRefIdAdapter (lightColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Lockpick, new ToolRefIdAdapter (UniversalId::Type_Lockpick, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, new NpcRefIdAdapter (npcColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, new ToolRefIdAdapter (UniversalId::Type_Repair, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Static, new ModelRefIdAdapter (UniversalId::Type_Static, modelColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Weapon, new WeaponRefIdAdapter (weaponColumns))); } CSMWorld::RefIdCollection::~RefIdCollection() { for (std::map::iterator iter (mAdapters.begin()); iter!=mAdapters.end(); ++iter) delete iter->second; for (std::vector > >::iterator iter (mNestedAdapters.begin()); iter!=mNestedAdapters.end(); ++iter) { for (std::map::iterator it ((iter->second).begin()); it!=(iter->second).end(); ++it) delete it->second; } } int CSMWorld::RefIdCollection::getSize() const { return mData.getSize(); } std::string CSMWorld::RefIdCollection::getId (int index) const { return getData (index, 0).toString().toUtf8().constData(); } int CSMWorld::RefIdCollection::getIndex (const std::string& id) const { int index = searchId (id); if (index==-1) throw std::runtime_error ("invalid ID: " + id); return index; } int CSMWorld::RefIdCollection::getColumns() const { return mColumns.size(); } const CSMWorld::ColumnBase& CSMWorld::RefIdCollection::getColumn (int column) const { return mColumns.at (column); } QVariant CSMWorld::RefIdCollection::getData (int index, int column) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); const RefIdAdapter& adaptor = findAdapter (localIndex.second); return adaptor.getData (&mColumns.at (column), mData, localIndex.first); } QVariant CSMWorld::RefIdCollection::getNestedData (int row, int column, int subRow, int subColumn) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.getNestedData(&mColumns.at (column), mData, localIndex.first, subRow, subColumn); } void CSMWorld::RefIdCollection::setData (int index, int column, const QVariant& data) { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); const RefIdAdapter& adaptor = findAdapter (localIndex.second); adaptor.setData (&mColumns.at (column), mData, localIndex.first, data); } void CSMWorld::RefIdCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.setNestedData(&mColumns.at (column), mData, localIndex.first, data, subRow, subColumn); } void CSMWorld::RefIdCollection::removeRows (int index, int count) { mData.erase (index, count); } void CSMWorld::RefIdCollection::removeNestedRows(int row, int column, int subRow) { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.removeNestedRow(&mColumns.at (column), mData, localIndex.first, subRow); } void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) { mData.appendRecord (type, id, false); } int CSMWorld::RefIdCollection::searchId(std::string_view id) const { RefIdData::LocalIndex localIndex = mData.searchId (id); if (localIndex.first==-1) return -1; return mData.localToGlobalIndex (localIndex); } void CSMWorld::RefIdCollection::replace (int index, std::unique_ptr record) { mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release()); } void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, const std::string& destination, const CSMWorld::UniversalId::Type type) { std::unique_ptr newRecord = mData.getRecord(mData.searchId(origin)).modifiedCopy(); mAdapters.find(type)->second->setId(*newRecord, destination); mData.insertRecord(std::move(newRecord), type, destination); } bool CSMWorld::RefIdCollection::touchRecord(const std::string& id) { throw std::runtime_error("RefIdCollection::touchRecord is unimplemented"); return false; } void CSMWorld::RefIdCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) { std::string id = findAdapter (type).getId (*record.get()); int index = mData.getAppendIndex (type); mData.appendRecord (type, id, false); mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release()); } const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const { return mData.getRecord (mData.searchId (id)); } const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) const { return mData.getRecord (mData.globalToLocalIndex (index)); } void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) { mData.load(reader, base, type); } int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const { return mData.getAppendIndex (type); } std::vector CSMWorld::RefIdCollection::getIds (bool listDeleted) const { return mData.getIds (listDeleted); } bool CSMWorld::RefIdCollection::reorderRows (int baseIndex, const std::vector& newOrder) { return false; } void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const { mData.save (index, writer); } const CSMWorld::RefIdData& CSMWorld::RefIdCollection::getDataSet() const { return mData; } int CSMWorld::RefIdCollection::getNestedRowsCount(int row, int column) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.getNestedRowsCount(&mColumns.at(column), mData, localIndex.first); } int CSMWorld::RefIdCollection::getNestedColumnsCount(int row, int column) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.getNestedColumnsCount(&mColumns.at(column), mData); } CSMWorld::NestableColumn *CSMWorld::RefIdCollection::getNestableColumn(int column) { return &mColumns.at(column); } void CSMWorld::RefIdCollection::addNestedRow(int row, int col, int position) { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(col), localIndex.second); nestedAdapter.addNestedRow(&mColumns.at(col), mData, localIndex.first, position); } void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.setNestedTable(&mColumns.at(column), mData, localIndex.first, nestedTable); } CSMWorld::NestedTableWrapperBase* CSMWorld::RefIdCollection::nestedTable(int row, int column) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.nestedTable(&mColumns.at(column), mData, localIndex.first); } const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdapter(const CSMWorld::ColumnBase &column, UniversalId::Type type) const { for (std::vector > >::const_iterator iter (mNestedAdapters.begin()); iter!=mNestedAdapters.end(); ++iter) { if ((iter->first) == &column) { std::map::const_iterator it = (iter->second).find(type); if (it == (iter->second).end()) throw std::runtime_error("No such type in the nestedadapters"); return *it->second; } } throw std::runtime_error("No such column in the nestedadapters"); } void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const { mData.copyTo (index, target.mData); } openmw-openmw-0.48.0/apps/opencs/model/world/refidcollection.hpp000066400000000000000000000122311445372753700247720ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFIDCOLLECTION_H #define CSM_WOLRD_REFIDCOLLECTION_H #include #include #include #include #include "columnbase.hpp" #include "collectionbase.hpp" #include "nestedcollection.hpp" #include "refiddata.hpp" namespace ESM { class ESMWriter; } namespace CSMWorld { class RefIdAdapter; struct NestedTableWrapperBase; class NestedRefIdAdapterBase; class RefIdColumn : public NestableColumn { bool mEditable; bool mUserEditable; public: RefIdColumn (int columnId, Display displayType, int flag = Flag_Table | Flag_Dialogue, bool editable = true, bool userEditable = true); bool isEditable() const override; bool isUserEditable() const override; }; class RefIdCollection : public CollectionBase, public NestedCollection { private: RefIdData mData; std::deque mColumns; std::map mAdapters; std::vector > > mNestedAdapters; private: const RefIdAdapter& findAdapter (UniversalId::Type) const; ///< Throws an exception if no adaptor for \a Type can be found. const NestedRefIdAdapterBase& getNestedAdapter(const ColumnBase &column, UniversalId::Type type) const; public: RefIdCollection(); ~RefIdCollection() override; int getSize() const override; std::string getId (int index) const override; int getIndex (const std::string& id) const override; int getColumns() const override; const ColumnBase& getColumn (int column) const override; QVariant getData (int index, int column) const override; void setData (int index, int column, const QVariant& data) override; void removeRows (int index, int count) override; void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) override; bool touchRecord(const std::string& id) override; void appendBlankRecord (const std::string& id, UniversalId::Type type) override; ///< \param type Will be ignored, unless the collection supports multiple record types int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) void replace (int index, std::unique_ptr record) override; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. void appendRecord (std::unique_ptr record, UniversalId::Type type) override; ///< If the record type does not match, an exception is thrown. /// ///< \param type Will be ignored, unless the collection supports multiple record types const RecordBase& getRecord (const std::string& id) const override; const RecordBase& getRecord (int index) const override; void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getAppendIndex (const std::string& id, UniversalId::Type type) const override; ///< \param type Will be ignored, unless the collection supports multiple record types std::vector getIds (bool listDeleted) const override; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list bool reorderRows (int baseIndex, const std::vector& newOrder) override; ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). /// /// \return Success? QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; NestedTableWrapperBase* nestedTable(int row, int column) const override; void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; int getNestedRowsCount(int row, int column) const override; int getNestedColumnsCount(int row, int column) const override; NestableColumn *getNestableColumn(int column) override; void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; void removeNestedRows(int row, int column, int subRow) override; void addNestedRow(int row, int col, int position) override; void save (int index, ESM::ESMWriter& writer) const; const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( void copyTo (int index, RefIdCollection& target) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/refiddata.cpp000066400000000000000000000347241445372753700235560ustar00rootroot00000000000000#include "refiddata.hpp" #include #include #include CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const { std::map::const_iterator found = mRecordContainers.find (index.second); if (found == mRecordContainers.end()) throw std::logic_error ("invalid local index type"); return found->second->getId(index.first); } CSMWorld::RefIdData::RefIdData() { mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Potion, &mPotions)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Apparatus, &mApparati)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Armor, &mArmors)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Book, &mBooks)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Clothing, &mClothing)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Container, &mContainers)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Creature, &mCreatures)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Door, &mDoors)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Ingredient, &mIngredients)); mRecordContainers.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, &mCreatureLevelledLists)); mRecordContainers.insert (std::make_pair (UniversalId::Type_ItemLevelledList, &mItemLevelledLists)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Light, &mLights)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Lockpick, &mLockpicks)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Miscellaneous, &mMiscellaneous)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Npc, &mNpcs)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Probe, &mProbes)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Repair, &mRepairs)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Static, &mStatics)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Weapon, &mWeapons)); } CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::globalToLocalIndex (int index) const { for (std::map::const_iterator iter ( mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter) { if (indexsecond->getSize()) return LocalIndex (index, iter->first); index -= iter->second->getSize(); } throw std::runtime_error ("RefIdData index out of range"); } int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index) const { std::map::const_iterator end = mRecordContainers.find (index.second); if (end==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); int globalIndex = index.first; for (std::map::const_iterator iter ( mRecordContainers.begin()); iter!=end; ++iter) globalIndex += iter->second->getSize(); return globalIndex; } CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase (id); std::map >::const_iterator iter = mIndex.find (id2); if (iter==mIndex.end()) return std::make_pair (-1, CSMWorld::UniversalId::Type_None); return iter->second; } unsigned int CSMWorld::RefIdData::getRecordFlags (const std::string& id) const { LocalIndex localIndex = searchId (id); switch (localIndex.second) { case UniversalId::Type_Activator: return mActivators.getRecordFlags(localIndex.first); case UniversalId::Type_Potion: return mPotions.getRecordFlags(localIndex.first); case UniversalId::Type_Apparatus: return mApparati.getRecordFlags(localIndex.first); case UniversalId::Type_Armor: return mArmors.getRecordFlags(localIndex.first); case UniversalId::Type_Book: return mBooks.getRecordFlags(localIndex.first); case UniversalId::Type_Clothing: return mClothing.getRecordFlags(localIndex.first); case UniversalId::Type_Container: return mContainers.getRecordFlags(localIndex.first); case UniversalId::Type_Creature: return mCreatures.getRecordFlags(localIndex.first); case UniversalId::Type_Door: return mDoors.getRecordFlags(localIndex.first); case UniversalId::Type_Ingredient: return mIngredients.getRecordFlags(localIndex.first); case UniversalId::Type_CreatureLevelledList: return mCreatureLevelledLists.getRecordFlags(localIndex.first); case UniversalId::Type_ItemLevelledList: return mItemLevelledLists.getRecordFlags(localIndex.first); case UniversalId::Type_Light: return mLights.getRecordFlags(localIndex.first); case UniversalId::Type_Lockpick: return mLockpicks.getRecordFlags(localIndex.first); case UniversalId::Type_Miscellaneous: return mMiscellaneous.getRecordFlags(localIndex.first); case UniversalId::Type_Npc: return mNpcs.getRecordFlags(localIndex.first); case UniversalId::Type_Probe: return mProbes.getRecordFlags(localIndex.first); case UniversalId::Type_Repair: return mRepairs.getRecordFlags(localIndex.first); case UniversalId::Type_Static: return mStatics.getRecordFlags(localIndex.first); case UniversalId::Type_Weapon: return mWeapons.getRecordFlags(localIndex.first); default: break; } return 0; } void CSMWorld::RefIdData::erase (int index, int count) { LocalIndex localIndex = globalToLocalIndex (index); std::map::const_iterator iter = mRecordContainers.find (localIndex.second); while (count>0 && iter!=mRecordContainers.end()) { int size = iter->second->getSize(); if (localIndex.first+count>size) { erase (localIndex, size-localIndex.first); count -= size-localIndex.first; ++iter; if (iter==mRecordContainers.end()) throw std::runtime_error ("invalid count value for erase operation"); localIndex.first = 0; localIndex.second = iter->first; } else { erase (localIndex, count); count = 0; } } } const CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index) const { std::map::const_iterator iter = mRecordContainers.find (index.second); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); return iter->second->getRecord (index.first); } CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index) { std::map::iterator iter = mRecordContainers.find (index.second); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); return iter->second->getRecord (index.first); } void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::string& id, bool base) { std::map::iterator iter = mRecordContainers.find (type); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); iter->second->appendRecord (id, base); mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const { int index = 0; for (std::map::const_iterator iter ( mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter) { index += iter->second->getSize(); if (type==iter->first) break; } return index; } void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { std::map::iterator found = mRecordContainers.find (type); if (found == mRecordContainers.end()) throw std::logic_error ("Invalid Referenceable ID type"); int index = found->second->load(reader, base); if (index != -1) { LocalIndex localIndex = LocalIndex(index, type); if (base && getRecord(localIndex).mState == RecordBase::State_Deleted) { erase(localIndex, 1); } else { mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; } } } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) { std::map::iterator iter = mRecordContainers.find (index.second); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); for (int i=index.first; i::iterator result = mIndex.find (Misc::StringUtils::lowerCase (iter->second->getId (i))); if (result!=mIndex.end()) mIndex.erase (result); } // Adjust the local indexes to avoid gaps between them after removal of records int recordIndex = index.first + count; int recordCount = iter->second->getSize(); while (recordIndex < recordCount) { std::map::iterator recordIndexFound = mIndex.find(Misc::StringUtils::lowerCase(iter->second->getId(recordIndex))); if (recordIndexFound != mIndex.end()) { recordIndexFound->second.first -= count; } ++recordIndex; } iter->second->erase (index.first, count); } int CSMWorld::RefIdData::getSize() const { return mIndex.size(); } std::vector CSMWorld::RefIdData::getIds (bool listDeleted) const { std::vector ids; for (std::map::const_iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter) { if (listDeleted || !getRecord (iter->second).isDeleted()) { std::map::const_iterator container = mRecordContainers.find (iter->second.second); if (container==mRecordContainers.end()) throw std::logic_error ("Invalid referenceable ID type"); ids.push_back (container->second->getId (iter->second.first)); } } return ids; } void CSMWorld::RefIdData::save (int index, ESM::ESMWriter& writer) const { LocalIndex localIndex = globalToLocalIndex (index); std::map::const_iterator iter = mRecordContainers.find (localIndex.second); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); iter->second->save (localIndex.first, writer); } const CSMWorld::RefIdDataContainer< ESM::Book >& CSMWorld::RefIdData::getBooks() const { return mBooks; } const CSMWorld::RefIdDataContainer< ESM::Activator >& CSMWorld::RefIdData::getActivators() const { return mActivators; } const CSMWorld::RefIdDataContainer< ESM::Potion >& CSMWorld::RefIdData::getPotions() const { return mPotions; } const CSMWorld::RefIdDataContainer< ESM::Apparatus >& CSMWorld::RefIdData::getApparati() const { return mApparati; } const CSMWorld::RefIdDataContainer< ESM::Armor >& CSMWorld::RefIdData::getArmors() const { return mArmors; } const CSMWorld::RefIdDataContainer< ESM::Clothing >& CSMWorld::RefIdData::getClothing() const { return mClothing; } const CSMWorld::RefIdDataContainer< ESM::Container >& CSMWorld::RefIdData::getContainers() const { return mContainers; } const CSMWorld::RefIdDataContainer< ESM::Creature >& CSMWorld::RefIdData::getCreatures() const { return mCreatures; } const CSMWorld::RefIdDataContainer< ESM::Door >& CSMWorld::RefIdData::getDoors() const { return mDoors; } const CSMWorld::RefIdDataContainer< ESM::Ingredient >& CSMWorld::RefIdData::getIngredients() const { return mIngredients; } const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& CSMWorld::RefIdData::getCreatureLevelledLists() const { return mCreatureLevelledLists; } const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& CSMWorld::RefIdData::getItemLevelledList() const { return mItemLevelledLists; } const CSMWorld::RefIdDataContainer< ESM::Light >& CSMWorld::RefIdData::getLights() const { return mLights; } const CSMWorld::RefIdDataContainer< ESM::Lockpick >& CSMWorld::RefIdData::getLocpicks() const { return mLockpicks; } const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& CSMWorld::RefIdData::getMiscellaneous() const { return mMiscellaneous; } const CSMWorld::RefIdDataContainer< ESM::NPC >& CSMWorld::RefIdData::getNPCs() const { return mNpcs; } const CSMWorld::RefIdDataContainer< ESM::Weapon >& CSMWorld::RefIdData::getWeapons() const { return mWeapons; } const CSMWorld::RefIdDataContainer< ESM::Probe >& CSMWorld::RefIdData::getProbes() const { return mProbes; } const CSMWorld::RefIdDataContainer< ESM::Repair >& CSMWorld::RefIdData::getRepairs() const { return mRepairs; } const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStatics() const { return mStatics; } void CSMWorld::RefIdData::insertRecord (std::unique_ptr record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); iter->second->insertRecord(std::move(record)); mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const { LocalIndex localIndex = globalToLocalIndex (index); RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; target.insertRecord(source->getRecord(localIndex.first).modifiedCopy(), localIndex.second, source->getId(localIndex.first)); } openmw-openmw-0.48.0/apps/opencs/model/world/refiddata.hpp000066400000000000000000000262031445372753700235540ustar00rootroot00000000000000#ifndef CSM_WOLRD_REFIDDATA_H #define CSM_WOLRD_REFIDDATA_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "record.hpp" #include "universalid.hpp" namespace ESM { class ESMReader; } namespace CSMWorld { struct RefIdDataContainerBase { virtual ~RefIdDataContainerBase(); virtual int getSize() const = 0; virtual const RecordBase& getRecord (int index) const = 0; virtual RecordBase& getRecord (int index)= 0; virtual unsigned int getRecordFlags (int index) const = 0; virtual void appendRecord (const std::string& id, bool base) = 0; virtual void insertRecord (std::unique_ptr record) = 0; virtual int load (ESM::ESMReader& reader, bool base) = 0; ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count) = 0; virtual std::string getId (int index) const = 0; virtual void save (int index, ESM::ESMWriter& writer) const = 0; }; template struct RefIdDataContainer : public RefIdDataContainerBase { std::vector > > mContainer; int getSize() const override; const RecordBase& getRecord (int index) const override; RecordBase& getRecord (int index) override; unsigned int getRecordFlags (int index) const override; void appendRecord (const std::string& id, bool base) override; void insertRecord (std::unique_ptr record) override; int load (ESM::ESMReader& reader, bool base) override; ///< \return index of a loaded record or -1 if no record was loaded void erase (int index, int count) override; std::string getId (int index) const override; void save (int index, ESM::ESMWriter& writer) const override; }; template void RefIdDataContainer::insertRecord(std::unique_ptr record) { assert(record != nullptr); // convert base pointer to record type pointer std::unique_ptr> typedRecord(&dynamic_cast&>(*record)); record.release(); mContainer.push_back(std::move(typedRecord)); } template int RefIdDataContainer::getSize() const { return static_cast (mContainer.size()); } template const RecordBase& RefIdDataContainer::getRecord (int index) const { return *mContainer.at (index); } template RecordBase& RefIdDataContainer::getRecord (int index) { return *mContainer.at (index); } template unsigned int RefIdDataContainer::getRecordFlags (int index) const { return mContainer.at (index)->get().mRecordFlags; } template void RefIdDataContainer::appendRecord (const std::string& id, bool base) { auto record = std::make_unique>(); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; record->mBase.mId = id; record->mModified.mId = id; (base ? record->mBase : record->mModified).blank(); mContainer.push_back (std::move(record)); } template int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { RecordT record; bool isDeleted = false; record.load(reader, isDeleted); int index = 0; int numRecords = static_cast(mContainer.size()); for (; index < numRecords; ++index) { if (Misc::StringUtils::ciEqual(mContainer[index]->get().mId, record.mId)) { break; } } if (isDeleted) { if (index == numRecords) { // deleting a record that does not exist // ignore it for now /// \todo report the problem to the user return -1; } // Flag the record as Deleted even for a base content file. // RefIdData is responsible for its erasure. mContainer[index]->mState = RecordBase::State_Deleted; } else { if (index == numRecords) { appendRecord(record.mId, base); if (base) { mContainer.back()->mBase = record; } else { mContainer.back()->mModified = record; } } else if (!base) { mContainer[index]->setModified(record); } else { // Overwrite mContainer[index]->setModified(record); mContainer[index]->merge(); } } return index; } template void RefIdDataContainer::erase (int index, int count) { if (index<0 || index+count>getSize()) throw std::runtime_error ("invalid RefIdDataContainer index"); mContainer.erase (mContainer.begin()+index, mContainer.begin()+index+count); } template std::string RefIdDataContainer::getId (int index) const { return mContainer.at (index)->get().mId; } template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { const Record& record = *mContainer.at(index); if (record.isModified() || record.mState == RecordBase::State_Deleted) { RecordT esmRecord = record.get(); writer.startRecord(esmRecord.sRecordId, esmRecord.mRecordFlags); esmRecord.save(writer, record.mState == RecordBase::State_Deleted); writer.endRecord(esmRecord.sRecordId); } } class RefIdData { public: typedef std::pair LocalIndex; private: RefIdDataContainer mActivators; RefIdDataContainer mPotions; RefIdDataContainer mApparati; RefIdDataContainer mArmors; RefIdDataContainer mBooks; RefIdDataContainer mClothing; RefIdDataContainer mContainers; RefIdDataContainer mCreatures; RefIdDataContainer mDoors; RefIdDataContainer mIngredients; RefIdDataContainer mCreatureLevelledLists; RefIdDataContainer mItemLevelledLists; RefIdDataContainer mLights; RefIdDataContainer mLockpicks; RefIdDataContainer mMiscellaneous; RefIdDataContainer mNpcs; RefIdDataContainer mProbes; RefIdDataContainer mRepairs; RefIdDataContainer mStatics; RefIdDataContainer mWeapons; std::map mIndex; std::map mRecordContainers; void erase (const LocalIndex& index, int count); ///< Must not spill over into another type. std::string getRecordId(const LocalIndex &index) const; public: RefIdData(); LocalIndex globalToLocalIndex (int index) const; int localToGlobalIndex (const LocalIndex& index) const; LocalIndex searchId(std::string_view id) const; void erase (int index, int count); void insertRecord (std::unique_ptr record, CSMWorld::UniversalId::Type type, const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; RecordBase& getRecord (const LocalIndex& index); unsigned int getRecordFlags(const std::string& id) const; void appendRecord (UniversalId::Type type, const std::string& id, bool base); int getAppendIndex (UniversalId::Type type) const; void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getSize() const; std::vector getIds (bool listDeleted = true) const; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list void save (int index, ESM::ESMWriter& writer) const; //RECORD CONTAINERS ACCESS METHODS const RefIdDataContainer& getBooks() const; const RefIdDataContainer& getActivators() const; const RefIdDataContainer& getPotions() const; const RefIdDataContainer& getApparati() const; const RefIdDataContainer& getArmors() const; const RefIdDataContainer& getClothing() const; const RefIdDataContainer& getContainers() const; const RefIdDataContainer& getCreatures() const; const RefIdDataContainer& getDoors() const; const RefIdDataContainer& getIngredients() const; const RefIdDataContainer& getCreatureLevelledLists() const; const RefIdDataContainer& getItemLevelledList() const; const RefIdDataContainer& getLights() const; const RefIdDataContainer& getLocpicks() const; const RefIdDataContainer& getMiscellaneous() const; const RefIdDataContainer& getNPCs() const; const RefIdDataContainer& getWeapons() const; const RefIdDataContainer& getProbes() const; const RefIdDataContainer& getRepairs() const; const RefIdDataContainer& getStatics() const; void copyTo (int index, RefIdData& target) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/regionmap.cpp000066400000000000000000000337331445372753700236130ustar00rootroot00000000000000#include "regionmap.hpp" #include #include #include #include #include #include "data.hpp" #include "universalid.hpp" CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted (false) {} CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) { const Cell& cell2 = cell.get(); if (!cell2.isExterior()) throw std::logic_error ("Interior cell in region map"); mDeleted = cell.isDeleted(); mRegion = cell2.mRegion; mName = cell2.mName; } CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const QModelIndex& index) const { return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1); } QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const { // I hate you, Qt API naming scheme! return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1, index.getX()-mMin.getX()); } CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const Cell& cell) const { std::istringstream stream (cell.mId); char ignore; int x = 0; int y = 0; stream >> ignore >> x >> y; return CellCoordinates (x, y); } void CSMWorld::RegionMap::buildRegions() { const IdCollection& regions = mData.getRegions(); int size = regions.getSize(); for (int i=0; i& region = regions.getRecord (i); if (!region.isDeleted()) mColours.insert (std::make_pair (Misc::StringUtils::lowerCase (region.get().mId), region.get().mMapColor)); } } void CSMWorld::RegionMap::buildMap() { const IdCollection& cells = mData.getCells(); int size = cells.getSize(); for (int i=0; i& cell = cells.getRecord (i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { CellDescription description (cell); CellCoordinates index = getIndex (cell2); mMap.insert (std::make_pair (index, description)); } } std::pair mapSize = getSize(); mMin = mapSize.first; mMax = mapSize.second; } void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescription& description) { std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { cell->second = description; } else { updateSize(); mMap.insert (std::make_pair (index, description)); } QModelIndex index2 = getIndex (index); dataChanged (index2, index2); } void CSMWorld::RegionMap::addCells (int start, int end) { const IdCollection& cells = mData.getCells(); for (int i=start; i<=end; ++i) { const Record& cell = cells.getRecord (i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { CellCoordinates index = getIndex (cell2); CellDescription description (cell); addCell (index, description); } } } void CSMWorld::RegionMap::removeCell (const CellCoordinates& index) { std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { mMap.erase (cell); QModelIndex index2 = getIndex (index); dataChanged (index2, index2); updateSize(); } } void CSMWorld::RegionMap::addRegion (const std::string& region, unsigned int colour) { mColours[Misc::StringUtils::lowerCase (region)] = colour; } void CSMWorld::RegionMap::removeRegion (const std::string& region) { std::map::iterator iter ( mColours.find (Misc::StringUtils::lowerCase (region))); if (iter!=mColours.end()) mColours.erase (iter); } void CSMWorld::RegionMap::updateRegions (const std::vector& regions) { std::vector regions2 (regions); std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (regions2.begin(), regions2.end()); for (std::map::const_iterator iter (mMap.begin()); iter!=mMap.end(); ++iter) { if (!iter->second.mRegion.empty() && std::find (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCase (iter->second.mRegion))!=regions2.end()) { QModelIndex index = getIndex (iter->first); dataChanged (index, index); } } } void CSMWorld::RegionMap::updateSize() { std::pair size = getSize(); if (int diff = size.first.getX() - mMin.getX()) { beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1); mMin = CellCoordinates (size.first.getX(), mMin.getY()); endInsertColumns(); } if (int diff = size.first.getY() - mMin.getY()) { beginInsertRows (QModelIndex(), 0, std::abs (diff)-1); mMin = CellCoordinates (mMin.getX(), size.first.getY()); endInsertRows(); } if (int diff = size.second.getX() - mMax.getX()) { int columns = columnCount(); if (diff>0) beginInsertColumns (QModelIndex(), columns, columns+diff-1); else beginRemoveColumns (QModelIndex(), columns+diff, columns-1); mMax = CellCoordinates (size.second.getX(), mMax.getY()); endInsertColumns(); } if (int diff = size.second.getY() - mMax.getY()) { int rows = rowCount(); if (diff>0) beginInsertRows (QModelIndex(), rows, rows+diff-1); else beginRemoveRows (QModelIndex(), rows+diff, rows-1); mMax = CellCoordinates (mMax.getX(), size.second.getY()); endInsertRows(); } } std::pair CSMWorld::RegionMap::getSize() const { const IdCollection& cells = mData.getCells(); int size = cells.getSize(); CellCoordinates min (0, 0); CellCoordinates max (0, 0); for (int i=0; i& cell = cells.getRecord (i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { CellCoordinates index = getIndex (cell2); if (min==max) { min = index; max = min.move (1, 1); } else { if (index.getX()=max.getX()) max = CellCoordinates (index.getX()+1, max.getY()); if (index.getY()=max.getY()) max = CellCoordinates (max.getX(), index.getY() + 1); } } } return std::make_pair (min, max); } CSMWorld::RegionMap::RegionMap (Data& data) : mData (data) { buildRegions(); buildMap(); QAbstractItemModel *regions = data.getTableModel (UniversalId (UniversalId::Type_Regions)); connect (regions, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (regionsAboutToBeRemoved (const QModelIndex&, int, int))); connect (regions, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (regionsInserted (const QModelIndex&, int, int))); connect (regions, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (regionsChanged (const QModelIndex&, const QModelIndex&))); QAbstractItemModel *cells = data.getTableModel (UniversalId (UniversalId::Type_Cells)); connect (cells, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (cellsAboutToBeRemoved (const QModelIndex&, int, int))); connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (cellsInserted (const QModelIndex&, int, int))); connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (cellsChanged (const QModelIndex&, const QModelIndex&))); } int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const { if (parent.isValid()) return 0; return mMax.getY()-mMin.getY(); } int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const { if (parent.isValid()) return 0; return mMax.getX()-mMin.getX(); } QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const { if (role==Qt::SizeHintRole) return QSize (16, 16); if (role==Qt::BackgroundRole) { /// \todo GUI class in non-GUI code. Needs to be addressed eventually. std::map::const_iterator cell = mMap.find (getIndex (index)); if (cell!=mMap.end()) { if (cell->second.mDeleted) return QBrush (Qt::red, Qt::DiagCrossPattern); std::map::const_iterator iter = mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); if (iter!=mColours.end()) return QBrush (QColor (iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff)); if (cell->second.mRegion.empty()) return QBrush (Qt::Dense6Pattern); // no region return QBrush (Qt::red, Qt::Dense6Pattern); // invalid region } return QBrush (Qt::DiagCrossPattern); } if (role==Qt::ToolTipRole) { CellCoordinates cellIndex = getIndex (index); std::ostringstream stream; stream << cellIndex; std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end()) { if (!cell->second.mName.empty()) stream << " " << cell->second.mName; if (cell->second.mDeleted) stream << " (deleted)"; if (!cell->second.mRegion.empty()) { stream << "
"; std::map::const_iterator iter = mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); if (iter!=mColours.end()) stream << cell->second.mRegion; else stream << "" << cell->second.mRegion << ""; } } else stream << " (no cell)"; return QString::fromUtf8 (stream.str().c_str()); } if (role==Role_Region) { CellCoordinates cellIndex = getIndex (index); std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end() && !cell->second.mRegion.empty()) return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); } if (role==Role_CellId) { CellCoordinates cellIndex = getIndex (index); std::ostringstream stream; stream << "#" << cellIndex.getX() << " " << cellIndex.getY(); return QString::fromUtf8 (stream.str().c_str()); } return QVariant(); } Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { std::vector update; const IdCollection& regions = mData.getRegions(); for (int i=start; i<=end; ++i) { const Record& region = regions.getRecord (i); update.push_back (region.get().mId); removeRegion (region.get().mId); } updateRegions (update); } void CSMWorld::RegionMap::regionsInserted (const QModelIndex& parent, int start, int end) { std::vector update; const IdCollection& regions = mData.getRegions(); for (int i=start; i<=end; ++i) { const Record& region = regions.getRecord (i); if (!region.isDeleted()) { update.push_back (region.get().mId); addRegion (region.get().mId, region.get().mMapColor); } } updateRegions (update); } void CSMWorld::RegionMap::regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating. std::vector update; const IdCollection& regions = mData.getRegions(); for (int i=topLeft.row(); i<=bottomRight.column(); ++i) { const Record& region = regions.getRecord (i); update.push_back (region.get().mId); if (!region.isDeleted()) addRegion (region.get().mId, region.get().mMapColor); else removeRegion (region.get().mId); } updateRegions (update); } void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { const IdCollection& cells = mData.getCells(); for (int i=start; i<=end; ++i) { const Record& cell = cells.getRecord (i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) removeCell (getIndex (cell2)); } } void CSMWorld::RegionMap::cellsInserted (const QModelIndex& parent, int start, int end) { addCells (start, end); } void CSMWorld::RegionMap::cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating. addCells (topLeft.row(), bottomRight.row()); } openmw-openmw-0.48.0/apps/opencs/model/world/regionmap.hpp000066400000000000000000000074311445372753700236140ustar00rootroot00000000000000#ifndef CSM_WOLRD_REGIONMAP_H #define CSM_WOLRD_REGIONMAP_H #include #include #include #include #include "record.hpp" #include "cell.hpp" #include "cellcoordinates.hpp" namespace CSMWorld { class Data; /// \brief Model for the region map /// /// This class does not holds any record data (other than for the purpose of buffering). class RegionMap : public QAbstractTableModel { Q_OBJECT public: enum Role { Role_Region = Qt::UserRole, Role_CellId = Qt::UserRole+1 }; private: struct CellDescription { bool mDeleted; std::string mRegion; std::string mName; CellDescription(); CellDescription (const Record& cell); }; Data& mData; std::map mMap; CellCoordinates mMin; ///< inclusive CellCoordinates mMax; ///< exclusive std::map mColours; ///< region ID, colour (RGBA) CellCoordinates getIndex (const QModelIndex& index) const; ///< Translates a Qt model index into a cell index (which can contain negative components) QModelIndex getIndex (const CellCoordinates& index) const; CellCoordinates getIndex (const Cell& cell) const; void buildRegions(); void buildMap(); void addCell (const CellCoordinates& index, const CellDescription& description); ///< May be called on a cell that is already in the map (in which case an update is // performed) void addCells (int start, int end); void removeCell (const CellCoordinates& index); ///< May be called on a cell that is not in the map (in which case the call is ignored) void addRegion (const std::string& region, unsigned int colour); ///< May be called on a region that is already listed (in which case an update is /// performed) /// /// \note This function does not update the region map. void removeRegion (const std::string& region); ///< May be called on a region that is not listed (in which case the call is ignored) /// /// \note This function does not update the region map. void updateRegions (const std::vector& regions); ///< Update cells affected by the listed regions void updateSize(); std::pair getSize() const; public: RegionMap (Data& data); int rowCount (const QModelIndex& parent = QModelIndex()) const override; int columnCount (const QModelIndex& parent = QModelIndex()) const override; QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const override; ///< \note Calling this function with role==Role_CellId may return the ID of a cell /// that does not exist. Qt::ItemFlags flags (const QModelIndex& index) const override; private slots: void regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end); void regionsInserted (const QModelIndex& parent, int start, int end); void regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end); void cellsInserted (const QModelIndex& parent, int start, int end); void cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/resources.cpp000066400000000000000000000047531445372753700236440ustar00rootroot00000000000000#include "resources.hpp" #include #include #include #include #include #include CSMWorld::Resources::Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char * const *extensions) : mBaseDirectory (baseDirectory), mType (type) { recreate(vfs, extensions); } void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *extensions) { mFiles.clear(); mIndex.clear(); size_t baseSize = mBaseDirectory.size(); for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { if (filepath.size() (mFiles.size())-1)); } } int CSMWorld::Resources::getSize() const { return static_cast(mFiles.size()); } std::string CSMWorld::Resources::getId (int index) const { return mFiles.at (index); } int CSMWorld::Resources::getIndex (const std::string& id) const { int index = searchId (id); if (index==-1) { std::ostringstream stream; stream << "Invalid resource: " << mBaseDirectory << '/' << id; throw std::runtime_error (stream.str()); } return index; } int CSMWorld::Resources::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase (id); std::replace (id2.begin(), id2.end(), '\\', '/'); std::map::const_iterator iter = mIndex.find (id2); if (iter==mIndex.end()) return -1; return iter->second; } CSMWorld::UniversalId::Type CSMWorld::Resources::getType() const { return mType; } openmw-openmw-0.48.0/apps/opencs/model/world/resources.hpp000066400000000000000000000017711445372753700236460ustar00rootroot00000000000000#ifndef CSM_WOLRD_RESOURCES_H #define CSM_WOLRD_RESOURCES_H #include #include #include #include #include "universalid.hpp" namespace VFS { class Manager; } namespace CSMWorld { class Resources { std::map mIndex; std::vector mFiles; std::string mBaseDirectory; UniversalId::Type mType; public: /// \param type Type of resources in this table. Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char * const *extensions = nullptr); void recreate(const VFS::Manager* vfs, const char * const *extensions = nullptr); int getSize() const; std::string getId (int index) const; int getIndex (const std::string& id) const; int searchId(std::string_view id) const; UniversalId::Type getType() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/resourcesmanager.cpp000066400000000000000000000036651445372753700252000ustar00rootroot00000000000000#include "resourcesmanager.hpp" #include CSMWorld::ResourcesManager::ResourcesManager() : mVFS(nullptr) { } void CSMWorld::ResourcesManager::addResources (const Resources& resources) { mResources.insert (std::make_pair (resources.getType(), resources)); mResources.insert (std::make_pair (UniversalId::getParentType (resources.getType()), resources)); } const char * const * CSMWorld::ResourcesManager::getMeshExtensions() { // maybe we could go over the osgDB::Registry to list all supported node formats static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; return sMeshTypes; } void CSMWorld::ResourcesManager::setVFS(const VFS::Manager *vfs) { mVFS = vfs; mResources.clear(); addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, getMeshExtensions())); addResources (Resources (vfs, "icons", UniversalId::Type_Icon)); addResources (Resources (vfs, "music", UniversalId::Type_Music)); addResources (Resources (vfs, "sound", UniversalId::Type_SoundRes)); addResources (Resources (vfs, "textures", UniversalId::Type_Texture)); addResources (Resources (vfs, "video", UniversalId::Type_Video)); } const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const { return mVFS; } void CSMWorld::ResourcesManager::recreateResources() { std::map::iterator it = mResources.begin(); for ( ; it != mResources.end(); ++it) { if (it->first == UniversalId::Type_Mesh) it->second.recreate(mVFS, getMeshExtensions()); else it->second.recreate(mVFS); } } const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const { std::map::const_iterator iter = mResources.find (type); if (iter==mResources.end()) throw std::logic_error ("Unknown resource type"); return iter->second; } openmw-openmw-0.48.0/apps/opencs/model/world/resourcesmanager.hpp000066400000000000000000000013471445372753700252000ustar00rootroot00000000000000#ifndef CSM_WOLRD_RESOURCESMANAGER_H #define CSM_WOLRD_RESOURCESMANAGER_H #include #include "universalid.hpp" #include "resources.hpp" namespace VFS { class Manager; } namespace CSMWorld { class ResourcesManager { std::map mResources; const VFS::Manager* mVFS; private: void addResources (const Resources& resources); const char * const * getMeshExtensions(); public: ResourcesManager(); const VFS::Manager* getVFS() const; void setVFS(const VFS::Manager* vfs); void recreateResources(); const Resources& get (UniversalId::Type type) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/resourcetable.cpp000066400000000000000000000073671445372753700244750ustar00rootroot00000000000000#include "resourcetable.hpp" #include #include "resources.hpp" #include "columnbase.hpp" #include "universalid.hpp" CSMWorld::ResourceTable::ResourceTable (const Resources *resources, unsigned int features) : IdTableBase (features | Feature_Constant), mResources (resources) {} CSMWorld::ResourceTable::~ResourceTable() {} int CSMWorld::ResourceTable::rowCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return mResources->getSize(); } int CSMWorld::ResourceTable::columnCount (const QModelIndex & parent) const { if (parent.isValid()) return 0; return 2; // ID, type } QVariant CSMWorld::ResourceTable::data (const QModelIndex & index, int role) const { if (role!=Qt::DisplayRole) return QVariant(); if (index.column()==0) return QString::fromUtf8 (mResources->getId (index.row()).c_str()); if (index.column()==1) return static_cast (mResources->getType()); throw std::logic_error ("Invalid column in resource table"); } QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orientation, int role ) const { if (orientation==Qt::Vertical) return QVariant(); if (role==ColumnBase::Role_Flags) return section==0 ? ColumnBase::Flag_Table : 0; switch (section) { case 0: if (role==Qt::DisplayRole) return Columns::getName (Columns::ColumnId_Id).c_str(); if (role==ColumnBase::Role_Display) return ColumnBase::Display_Id; break; case 1: if (role==Qt::DisplayRole) return Columns::getName (Columns::ColumnId_RecordType).c_str(); if (role==ColumnBase::Role_Display) return ColumnBase::Display_Integer; break; } return QVariant(); } bool CSMWorld::ResourceTable::setData ( const QModelIndex &index, const QVariant &value, int role) { return false; } Qt::ItemFlags CSMWorld::ResourceTable::flags (const QModelIndex & index) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QModelIndex CSMWorld::ResourceTable::index (int row, int column, const QModelIndex& parent) const { if (parent.isValid()) return QModelIndex(); if (row<0 || row>=mResources->getSize()) return QModelIndex(); if (column<0 || column>1) return QModelIndex(); return createIndex (row, column); } QModelIndex CSMWorld::ResourceTable::parent (const QModelIndex& index) const { return QModelIndex(); } QModelIndex CSMWorld::ResourceTable::getModelIndex (const std::string& id, int column) const { int row = mResources->searchId(id); if (row != -1) return index (row, column); return QModelIndex(); } int CSMWorld::ResourceTable::searchColumnIndex (Columns::ColumnId id) const { if (id==Columns::ColumnId_Id) return 0; if (id==Columns::ColumnId_RecordType) return 1; return -1; } int CSMWorld::ResourceTable::findColumnIndex (Columns::ColumnId id) const { int index = searchColumnIndex (id); if (index==-1) throw std::logic_error ("invalid column index"); return index; } std::pair CSMWorld::ResourceTable::view (int row) const { return std::make_pair (UniversalId::Type_None, ""); } bool CSMWorld::ResourceTable::isDeleted (const std::string& id) const { return false; } int CSMWorld::ResourceTable::getColumnId (int column) const { switch (column) { case 0: return Columns::ColumnId_Id; case 1: return Columns::ColumnId_RecordType; } return -1; } void CSMWorld::ResourceTable::beginReset() { beginResetModel(); } void CSMWorld::ResourceTable::endReset() { endResetModel(); } openmw-openmw-0.48.0/apps/opencs/model/world/resourcetable.hpp000066400000000000000000000044211445372753700244660ustar00rootroot00000000000000#ifndef CSM_WOLRD_RESOURCETABLE_H #define CSM_WOLRD_RESOURCETABLE_H #include "idtablebase.hpp" namespace CSMWorld { class Resources; class ResourceTable : public IdTableBase { const Resources *mResources; public: /// \note The feature Feature_Constant will be added implicitly. ResourceTable (const Resources *resources, unsigned int features = 0); ~ResourceTable() override; int rowCount (const QModelIndex & parent = QModelIndex()) const override; int columnCount (const QModelIndex & parent = QModelIndex()) const override; QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags (const QModelIndex & index) const override; QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent (const QModelIndex& index) const override; QModelIndex getModelIndex (const std::string& id, int column) const override; /// Return index of column with the given \a id. If no such column exists, -1 is /// returned. int searchColumnIndex (Columns::ColumnId id) const override; /// Return index of column with the given \a id. If no such column exists, an /// exception is thrown. int findColumnIndex (Columns::ColumnId id) const override; /// Return the UniversalId and the hint for viewing \a row. If viewing is not /// supported by this table, return (UniversalId::Type_None, ""). std::pair view (int row) const override; /// Is \a id flagged as deleted? bool isDeleted (const std::string& id) const override; int getColumnId (int column) const override; /// Signal Qt that the data is about to change. void beginReset(); /// Signal Qt that the data has been changed. void endReset(); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/scope.cpp000066400000000000000000000007751445372753700227430ustar00rootroot00000000000000#include "scope.hpp" #include #include CSMWorld::Scope CSMWorld::getScopeFromId (const std::string& id) { // get root namespace std::string namespace_; std::string::size_type i = id.find ("::"); if (i!=std::string::npos) namespace_ = Misc::StringUtils::lowerCase (id.substr (0, i)); if (namespace_=="project") return Scope_Project; if (namespace_=="session") return Scope_Session; return Scope_Content; } openmw-openmw-0.48.0/apps/opencs/model/world/scope.hpp000066400000000000000000000006451445372753700227440ustar00rootroot00000000000000#ifndef CSM_WOLRD_SCOPE_H #define CSM_WOLRD_SCOPE_H #include namespace CSMWorld { enum Scope { // record stored in content file Scope_Content = 1, // record stored in project file Scope_Project = 2, // record that exists only for the duration of one editing session Scope_Session = 4 }; Scope getScopeFromId (const std::string& id); } #endif openmw-openmw-0.48.0/apps/opencs/model/world/scriptcontext.cpp000066400000000000000000000066571445372753700245500ustar00rootroot00000000000000#include "scriptcontext.hpp" #include #include #include #include #include #include #include "data.hpp" CSMWorld::ScriptContext::ScriptContext (const Data& data) : mData (data), mIdsUpdated (false) {} bool CSMWorld::ScriptContext::canDeclareLocals() const { return true; } char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const { int index = mData.getGlobals().searchId (name); if (index!=-1) { switch (mData.getGlobals().getRecord (index).get().mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; case ESM::VT_Float: return 'f'; default: return ' '; } } return ' '; } std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); bool reference = false; if (index==-1) { // ID is not a script ID. Search for a matching referenceable instead. index = mData.getReferenceables().searchId (id2); if (index!=-1) { // Referenceable found. int columnIndex = mData.getReferenceables().findColumnIndex (Columns::ColumnId_Script); id2 = Misc::StringUtils::lowerCase (mData.getReferenceables(). getData (index, columnIndex).toString().toUtf8().constData()); if (!id2.empty()) { // Referenceable has a script -> use it. index = mData.getScripts().searchId (id2); reference = true; } } } if (index==-1) return std::make_pair (' ', false); std::map::iterator iter = mLocals.find (id2); if (iter==mLocals.end()) { Compiler::Locals locals; Compiler::NullErrorHandler errorHandler; std::istringstream stream (mData.getScripts().getRecord (index).get().mScriptText); Compiler::QuickFileParser parser (errorHandler, *this, locals); Compiler::Scanner scanner (errorHandler, stream, getExtensions()); scanner.scan (parser); iter = mLocals.insert (std::make_pair (id2, locals)).first; } return std::make_pair (iter->second.getType (Misc::StringUtils::lowerCase (name)), reference); } bool CSMWorld::ScriptContext::isId (const std::string& name) const { if (!mIdsUpdated) { mIds = mData.getIds(); std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); std::sort (mIds.begin(), mIds.end()); mIdsUpdated = true; } return std::binary_search (mIds.begin(), mIds.end(), Misc::StringUtils::lowerCase (name)); } void CSMWorld::ScriptContext::invalidateIds() { mIdsUpdated = false; } void CSMWorld::ScriptContext::clear() { mIds.clear(); mIdsUpdated = false; mLocals.clear(); } bool CSMWorld::ScriptContext::clearLocals (const std::string& script) { std::map::iterator iter = mLocals.find (Misc::StringUtils::lowerCase (script)); if (iter!=mLocals.end()) { mLocals.erase (iter); mIdsUpdated = false; return true; } return false; } openmw-openmw-0.48.0/apps/opencs/model/world/scriptcontext.hpp000066400000000000000000000030671445372753700245450ustar00rootroot00000000000000#ifndef CSM_WORLD_SCRIPTCONTEXT_H #define CSM_WORLD_SCRIPTCONTEXT_H #include #include #include #include #include namespace CSMWorld { class Data; class ScriptContext : public Compiler::Context { const Data& mData; mutable std::vector mIds; mutable bool mIdsUpdated; mutable std::map mLocals; public: ScriptContext (const Data& data); bool canDeclareLocals() const override; ///< Is the compiler allowed to declare local variables? char getGlobalType (const std::string& name) const override; ///< 'l: long, 's': short, 'f': float, ' ': does not exist. std::pair getMemberType (const std::string& name, const std::string& id) const override; ///< Return type of member variable \a name in script \a id or in script of reference of /// \a id /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. /// second: true: script of reference bool isId (const std::string& name) const override; ///< Does \a name match an ID, that can be referenced? void invalidateIds(); void clear(); ///< Remove all cached data. /// \return Were there any locals that needed clearing? bool clearLocals (const std::string& script); }; } #endif openmw-openmw-0.48.0/apps/opencs/model/world/subcellcollection.hpp000066400000000000000000000024471445372753700253420ustar00rootroot00000000000000#ifndef CSM_WOLRD_SUBCOLLECTION_H #define CSM_WOLRD_SUBCOLLECTION_H #include "nestedidcollection.hpp" namespace ESM { class ESMReader; } namespace CSMWorld { struct Cell; template class IdCollection; /// \brief Single type collection of top level records that are associated with cells template > class SubCellCollection : public NestedIdCollection { const IdCollection& mCells; void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) override; public: SubCellCollection (const IdCollection& cells); }; template void SubCellCollection::loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) { record.load (reader, isDeleted, mCells); } template SubCellCollection::SubCellCollection ( const IdCollection& cells) : mCells (cells) {} } #endif openmw-openmw-0.48.0/apps/opencs/model/world/tablemimedata.cpp000066400000000000000000000276421445372753700244250ustar00rootroot00000000000000#include "tablemimedata.hpp" #include #include #include "universalid.hpp" #include "columnbase.hpp" CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : mDocument(document) { mUniversalId.push_back (id); mObjectsFormats << QString::fromUtf8 (("tabledata/" + id.getTypeName()).c_str()); } CSMWorld::TableMimeData::TableMimeData (const std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : mUniversalId (id), mDocument(document) { for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) { mObjectsFormats << QString::fromUtf8 (("tabledata/" + it->getTypeName()).c_str()); } } QStringList CSMWorld::TableMimeData::formats() const { return mObjectsFormats; } CSMWorld::TableMimeData::~TableMimeData() { } std::string CSMWorld::TableMimeData::getIcon() const { if (mUniversalId.empty()) { qDebug()<<"TableMimeData object does not hold any records!"; //because throwing in the event loop tends to be problematic throw std::runtime_error ("TableMimeData object does not hold any records!"); } std::string tmpIcon; bool firstIteration = true; for (unsigned i = 0; i < mUniversalId.size(); ++i) { if (firstIteration) { firstIteration = false; tmpIcon = mUniversalId[i].getIcon(); continue; } if (tmpIcon != mUniversalId[i].getIcon()) { return ":/multitype.png"; //icon stolen from gnome TODO: get new icon } tmpIcon = mUniversalId[i].getIcon(); } return mUniversalId.begin()->getIcon(); //All objects are of the same type; } std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const { return mUniversalId; } bool CSMWorld::TableMimeData::isReferencable(CSMWorld::ColumnBase::Display type) const { return ( type == CSMWorld::ColumnBase::Display_Activator || type == CSMWorld::ColumnBase::Display_Potion || type == CSMWorld::ColumnBase::Display_Apparatus || type == CSMWorld::ColumnBase::Display_Armor || type == CSMWorld::ColumnBase::Display_Book || type == CSMWorld::ColumnBase::Display_Clothing || type == CSMWorld::ColumnBase::Display_Container || type == CSMWorld::ColumnBase::Display_Creature || type == CSMWorld::ColumnBase::Display_Door || type == CSMWorld::ColumnBase::Display_Ingredient || type == CSMWorld::ColumnBase::Display_CreatureLevelledList || type == CSMWorld::ColumnBase::Display_ItemLevelledList || type == CSMWorld::ColumnBase::Display_Light || type == CSMWorld::ColumnBase::Display_Lockpick || type == CSMWorld::ColumnBase::Display_Miscellaneous || type == CSMWorld::ColumnBase::Display_Npc || type == CSMWorld::ColumnBase::Display_Probe || type == CSMWorld::ColumnBase::Display_Repair || type == CSMWorld::ColumnBase::Display_Static || type == CSMWorld::ColumnBase::Display_Weapon); } bool CSMWorld::TableMimeData::isReferencable(CSMWorld::UniversalId::Type type) { return ( type == CSMWorld::UniversalId::Type_Activator || type == CSMWorld::UniversalId::Type_Potion || type == CSMWorld::UniversalId::Type_Apparatus || type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Book || type == CSMWorld::UniversalId::Type_Clothing || type == CSMWorld::UniversalId::Type_Container || type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Door || type == CSMWorld::UniversalId::Type_Ingredient || type == CSMWorld::UniversalId::Type_CreatureLevelledList || type == CSMWorld::UniversalId::Type_ItemLevelledList || type == CSMWorld::UniversalId::Type_Light || type == CSMWorld::UniversalId::Type_Lockpick || type == CSMWorld::UniversalId::Type_Miscellaneous || type == CSMWorld::UniversalId::Type_Npc || type == CSMWorld::UniversalId::Type_Probe || type == CSMWorld::UniversalId::Type_Repair || type == CSMWorld::UniversalId::Type_Static || type == CSMWorld::UniversalId::Type_Weapon); } bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const { bool referencable = (type == CSMWorld::UniversalId::Type_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) { if (referencable) { if (isReferencable(it->getType())) { return true; } } else { if (it->getType() == type) { return true; } } } return false; } bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const { bool referencable = (type == CSMWorld::ColumnBase::Display_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) { if (referencable) { if (isReferencable(it->getType())) { return true; } } else { if (it->getType() == convertEnums (type)) { return true; } } } return false; } CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const { bool referencable = (type == CSMWorld::UniversalId::Type_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) { if (referencable) { if (isReferencable(it->getType())) { return *it; } } else { if (it->getType() == type) { return *it; } } } throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const { bool referencable = (type == CSMWorld::ColumnBase::Display_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) { if (referencable) { if (isReferencable(it->getType())) { return *it; } } else { if (it->getType() == convertEnums (type)) { return *it; } } } throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const { return &document == &mDocument; } namespace { struct Mapping { CSMWorld::UniversalId::Type mUniversalIdType; CSMWorld::ColumnBase::Display mDisplayType; }; const Mapping mapping[] = { { CSMWorld::UniversalId::Type_Race, CSMWorld::ColumnBase::Display_Race }, { CSMWorld::UniversalId::Type_Skill, CSMWorld::ColumnBase::Display_Skill }, { CSMWorld::UniversalId::Type_Class, CSMWorld::ColumnBase::Display_Class }, { CSMWorld::UniversalId::Type_Faction, CSMWorld::ColumnBase::Display_Faction }, { CSMWorld::UniversalId::Type_Sound, CSMWorld::ColumnBase::Display_Sound }, { CSMWorld::UniversalId::Type_Region, CSMWorld::ColumnBase::Display_Region }, { CSMWorld::UniversalId::Type_Birthsign, CSMWorld::ColumnBase::Display_Birthsign }, { CSMWorld::UniversalId::Type_Spell, CSMWorld::ColumnBase::Display_Spell }, { CSMWorld::UniversalId::Type_Cell, CSMWorld::ColumnBase::Display_Cell }, { CSMWorld::UniversalId::Type_Referenceable, CSMWorld::ColumnBase::Display_Referenceable }, { CSMWorld::UniversalId::Type_Activator, CSMWorld::ColumnBase::Display_Activator }, { CSMWorld::UniversalId::Type_Potion, CSMWorld::ColumnBase::Display_Potion }, { CSMWorld::UniversalId::Type_Apparatus, CSMWorld::ColumnBase::Display_Apparatus }, { CSMWorld::UniversalId::Type_Armor, CSMWorld::ColumnBase::Display_Armor }, { CSMWorld::UniversalId::Type_Book, CSMWorld::ColumnBase::Display_Book }, { CSMWorld::UniversalId::Type_Clothing, CSMWorld::ColumnBase::Display_Clothing }, { CSMWorld::UniversalId::Type_Container, CSMWorld::ColumnBase::Display_Container }, { CSMWorld::UniversalId::Type_Creature, CSMWorld::ColumnBase::Display_Creature }, { CSMWorld::UniversalId::Type_Door, CSMWorld::ColumnBase::Display_Door }, { CSMWorld::UniversalId::Type_Ingredient, CSMWorld::ColumnBase::Display_Ingredient }, { CSMWorld::UniversalId::Type_CreatureLevelledList, CSMWorld::ColumnBase::Display_CreatureLevelledList }, { CSMWorld::UniversalId::Type_ItemLevelledList, CSMWorld::ColumnBase::Display_ItemLevelledList }, { CSMWorld::UniversalId::Type_Light, CSMWorld::ColumnBase::Display_Light }, { CSMWorld::UniversalId::Type_Lockpick, CSMWorld::ColumnBase::Display_Lockpick }, { CSMWorld::UniversalId::Type_Miscellaneous, CSMWorld::ColumnBase::Display_Miscellaneous }, { CSMWorld::UniversalId::Type_Npc, CSMWorld::ColumnBase::Display_Npc }, { CSMWorld::UniversalId::Type_Probe, CSMWorld::ColumnBase::Display_Probe }, { CSMWorld::UniversalId::Type_Repair, CSMWorld::ColumnBase::Display_Repair }, { CSMWorld::UniversalId::Type_Static, CSMWorld::ColumnBase::Display_Static }, { CSMWorld::UniversalId::Type_Weapon, CSMWorld::ColumnBase::Display_Weapon }, { CSMWorld::UniversalId::Type_Reference, CSMWorld::ColumnBase::Display_Reference }, { CSMWorld::UniversalId::Type_Filter, CSMWorld::ColumnBase::Display_Filter }, { CSMWorld::UniversalId::Type_Topic, CSMWorld::ColumnBase::Display_Topic }, { CSMWorld::UniversalId::Type_Journal, CSMWorld::ColumnBase::Display_Journal }, { CSMWorld::UniversalId::Type_TopicInfo, CSMWorld::ColumnBase::Display_TopicInfo }, { CSMWorld::UniversalId::Type_JournalInfo, CSMWorld::ColumnBase::Display_JournalInfo }, { CSMWorld::UniversalId::Type_Scene, CSMWorld::ColumnBase::Display_Scene }, { CSMWorld::UniversalId::Type_Script, CSMWorld::ColumnBase::Display_Script }, { CSMWorld::UniversalId::Type_Mesh, CSMWorld::ColumnBase::Display_Mesh }, { CSMWorld::UniversalId::Type_Icon, CSMWorld::ColumnBase::Display_Icon }, { CSMWorld::UniversalId::Type_Music, CSMWorld::ColumnBase::Display_Music }, { CSMWorld::UniversalId::Type_SoundRes, CSMWorld::ColumnBase::Display_SoundRes }, { CSMWorld::UniversalId::Type_Texture, CSMWorld::ColumnBase::Display_Texture }, { CSMWorld::UniversalId::Type_Video, CSMWorld::ColumnBase::Display_Video }, { CSMWorld::UniversalId::Type_Global, CSMWorld::ColumnBase::Display_GlobalVariable }, { CSMWorld::UniversalId::Type_BodyPart, CSMWorld::ColumnBase::Display_BodyPart }, { CSMWorld::UniversalId::Type_Enchantment, CSMWorld::ColumnBase::Display_Enchantment }, { CSMWorld::UniversalId::Type_None, CSMWorld::ColumnBase::Display_None } // end marker }; } CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (ColumnBase::Display type) { for (int i=0; mapping[i].mUniversalIdType!=CSMWorld::UniversalId::Type_None; ++i) if (mapping[i].mDisplayType==type) return mapping[i].mUniversalIdType; return CSMWorld::UniversalId::Type_None; } CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (UniversalId::Type type) { for (int i=0; mapping[i].mUniversalIdType!=CSMWorld::UniversalId::Type_None; ++i) if (mapping[i].mUniversalIdType==type) return mapping[i].mDisplayType; return CSMWorld::ColumnBase::Display_None; } const CSMDoc::Document* CSMWorld::TableMimeData::getDocumentPtr() const { return &mDocument; } openmw-openmw-0.48.0/apps/opencs/model/world/tablemimedata.hpp000066400000000000000000000037761445372753700244340ustar00rootroot00000000000000#ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H #include #include #include #include "universalid.hpp" #include "columnbase.hpp" namespace CSMDoc { class Document; } namespace CSMWorld { /// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. /// /// This class provides way to construct mimedata object holding the universalid copy /// Universalid is used in the majority of the tables to store type, id, argument types. /// This way universalid grants a way to retrieve record from the concrete table. /// Please note, that tablemimedata object can hold multiple universalIds in the vector. class TableMimeData : public QMimeData { std::vector mUniversalId; QStringList mObjectsFormats; const CSMDoc::Document& mDocument; public: TableMimeData(UniversalId id, const CSMDoc::Document& document); TableMimeData(const std::vector& id, const CSMDoc::Document& document); ~TableMimeData(); QStringList formats() const override; std::string getIcon() const; std::vector getData() const; bool holdsType(UniversalId::Type type) const; bool holdsType(CSMWorld::ColumnBase::Display type) const; bool fromDocument(const CSMDoc::Document& document) const; UniversalId returnMatching(UniversalId::Type type) const; const CSMDoc::Document* getDocumentPtr() const; UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); static bool isReferencable(CSMWorld::UniversalId::Type type); private: bool isReferencable(CSMWorld::ColumnBase::Display type) const; }; } #endif // TABLEMIMEDATA_H openmw-openmw-0.48.0/apps/opencs/model/world/universalid.cpp000066400000000000000000000473721445372753700241630ustar00rootroot00000000000000#include "universalid.hpp" #include #include #include namespace { struct TypeData { CSMWorld::UniversalId::Class mClass; CSMWorld::UniversalId::Type mType; const char *mName; const char *mIcon; }; static const TypeData sNoArg[] = { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", ":./global-variable.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":./dialogue-topics.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", ":./journal-topics.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", ":./dialogue-topic-infos.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", ":./journal-topic-infos.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", ":./enchantment.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":./body-part.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":./object.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":./instance.png" }, { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":./region-map.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":./filter.png" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":./resources-mesh" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":./resources-icon" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", ":./resources-music" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", ":resources-sound" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", ":./resources-texture" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":./resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", ":./debug-profile.png" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", ":./sound-generator.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", ":./magic-effect.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":./land-heightmap.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", ":./land-texture.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":./pathgrid.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", ":./start-script.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":./metadata.png" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIdArg[] = { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./global-variable.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":./journal-topics.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", ":./dialogue-topic-infos.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", ":./journal-topic-infos.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":./apparatus.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":./container.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, "Creature Levelled List", ":./levelled-creature.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", ":./levelled-item.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", ":./miscellaneous.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":./instance.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":./record-preview.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":./enchantment.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh"}, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon"}, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":./resources-music" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", ":./resources-sound" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":./resources-texture" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", ":./debug-profile.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", ":./sound-generator.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", ":./magic-effect.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", ":./land-texture.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", ":./start-script.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", ":./menu-verify.png" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", ":./error-log.png" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":./menu-search.png" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; } CSMWorld::UniversalId::UniversalId (const std::string& universalId) : mIndex(0) { std::string::size_type index = universalId.find (':'); if (index!=std::string::npos) { std::string type = universalId.substr (0, index); for (int i=0; sIdArg[i].mName; ++i) if (type==sIdArg[i].mName) { mArgumentType = ArgumentType_Id; mType = sIdArg[i].mType; mClass = sIdArg[i].mClass; mId = universalId.substr (index+2); return; } for (int i=0; sIndexArg[i].mName; ++i) if (type==sIndexArg[i].mName) { mArgumentType = ArgumentType_Index; mType = sIndexArg[i].mType; mClass = sIndexArg[i].mClass; std::istringstream stream (universalId.substr (index+2)); if (stream >> mIndex) return; break; } } else { for (int i=0; sNoArg[i].mName; ++i) if (universalId==sNoArg[i].mName) { mArgumentType = ArgumentType_None; mType = sNoArg[i].mType; mClass = sNoArg[i].mClass; return; } } throw std::runtime_error ("invalid UniversalId: " + universalId); } CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_None), mType (type), mIndex (0) { for (int i=0; sNoArg[i].mName; ++i) if (type==sNoArg[i].mType) { mClass = sNoArg[i].mClass; return; } for (int i=0; sIdArg[i].mName; ++i) if (type==sIdArg[i].mType) { mArgumentType = ArgumentType_Id; mClass = sIdArg[i].mClass; return; } for (int i=0; sIndexArg[i].mName; ++i) if (type==sIndexArg[i].mType) { mArgumentType = ArgumentType_Index; mClass = sIndexArg[i].mClass; return; } throw std::logic_error ("invalid argument-less UniversalId type"); } CSMWorld::UniversalId::UniversalId (Type type, const std::string& id) : mArgumentType (ArgumentType_Id), mType (type), mId (id), mIndex (0) { for (int i=0; sIdArg[i].mName; ++i) if (type==sIdArg[i].mType) { mClass = sIdArg[i].mClass; return; } throw std::logic_error ("invalid ID argument UniversalId type"); } CSMWorld::UniversalId::UniversalId (Type type, int index) : mArgumentType (ArgumentType_Index), mType (type), mIndex (index) { for (int i=0; sIndexArg[i].mName; ++i) if (type==sIndexArg[i].mType) { mClass = sIndexArg[i].mClass; return; } throw std::logic_error ("invalid index argument UniversalId type"); } CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const { return mClass; } CSMWorld::UniversalId::ArgumentType CSMWorld::UniversalId::getArgumentType() const { return mArgumentType; } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const { return mType; } const std::string& CSMWorld::UniversalId::getId() const { if (mArgumentType!=ArgumentType_Id) throw std::logic_error ("invalid access to ID of non-ID UniversalId"); return mId; } int CSMWorld::UniversalId::getIndex() const { if (mArgumentType!=ArgumentType_Index) throw std::logic_error ("invalid access to index of non-index UniversalId"); return mIndex; } bool CSMWorld::UniversalId::isEqual (const UniversalId& universalId) const { if (mClass!=universalId.mClass || mArgumentType!=universalId.mArgumentType || mType!=universalId.mType) return false; switch (mArgumentType) { case ArgumentType_Id: return mId==universalId.mId; case ArgumentType_Index: return mIndex==universalId.mIndex; default: return true; } } bool CSMWorld::UniversalId::isLess (const UniversalId& universalId) const { if (mTypeuniversalId.mType) return false; switch (mArgumentType) { case ArgumentType_Id: return mId CSMWorld::UniversalId::listReferenceableTypes() { std::vector list; for (int i=0; sIdArg[i].mName; ++i) if (sIdArg[i].mClass==Class_RefRecord) list.push_back (sIdArg[i].mType); return list; } std::vector CSMWorld::UniversalId::listTypes (int classes) { std::vector list; for (int i=0; sNoArg[i].mName; ++i) if (sNoArg[i].mClass & classes) list.push_back (sNoArg[i].mType); for (int i=0; sIdArg[i].mName; ++i) if (sIdArg[i].mClass & classes) list.push_back (sIdArg[i].mType); for (int i=0; sIndexArg[i].mName; ++i) if (sIndexArg[i].mClass & classes) list.push_back (sIndexArg[i].mType); return list; } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) { for (int i=0; sIdArg[i].mType; ++i) if (type==sIdArg[i].mType) { if (sIdArg[i].mClass==Class_RefRecord) return Type_Referenceables; if (sIdArg[i].mClass==Class_SubRecord || sIdArg[i].mClass==Class_Record || sIdArg[i].mClass==Class_Resource) { if (type==Type_Cell_Missing) return Type_Cells; return static_cast (type-1); } break; } return Type_None; } bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { return left.isEqual (right); } bool CSMWorld::operator!= (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { return !left.isEqual (right); } bool CSMWorld::operator< (const UniversalId& left, const UniversalId& right) { return left.isLess (right); } openmw-openmw-0.48.0/apps/opencs/model/world/universalid.hpp000066400000000000000000000143321445372753700241560ustar00rootroot00000000000000#ifndef CSM_WOLRD_UNIVERSALID_H #define CSM_WOLRD_UNIVERSALID_H #include #include #include namespace CSMWorld { class UniversalId { public: enum Class { Class_None = 0, Class_Record = 1, Class_RefRecord = 2, // referenceable record Class_SubRecord = 4, Class_RecordList = 8, Class_Collection = 16, // multiple types of records combined Class_Transient = 32, // not part of the world data or the project data Class_NonRecord = 64, // record like data that is not part of the world Class_Resource = 128, ///< \attention Resource IDs are unique only within the /// respective collection Class_ResourceList = 256 }; enum ArgumentType { ArgumentType_None, ArgumentType_Id, ArgumentType_Index }; /// \note A record list type must always be immediately followed by the matching /// record type, if this type is of class SubRecord or Record. enum Type { Type_None = 0, Type_Globals, Type_Global, Type_VerificationResults, Type_Gmsts, Type_Gmst, Type_Skills, Type_Skill, Type_Classes, Type_Class, Type_Factions, Type_Faction, Type_Races, Type_Race, Type_Sounds, Type_Sound, Type_Scripts, Type_Script, Type_Regions, Type_Region, Type_Birthsigns, Type_Birthsign, Type_Spells, Type_Spell, Type_Cells, Type_Cell, Type_Cell_Missing, //For cells that does not exist yet. Type_Referenceables, Type_Referenceable, Type_Activator, Type_Potion, Type_Apparatus, Type_Armor, Type_Book, Type_Clothing, Type_Container, Type_Creature, Type_Door, Type_Ingredient, Type_CreatureLevelledList, Type_ItemLevelledList, Type_Light, Type_Lockpick, Type_Miscellaneous, Type_Npc, Type_Probe, Type_Repair, Type_Static, Type_Weapon, Type_References, Type_Reference, Type_RegionMap, Type_Filters, Type_Filter, Type_Topics, Type_Topic, Type_Journals, Type_Journal, Type_TopicInfos, Type_TopicInfo, Type_JournalInfos, Type_JournalInfo, Type_Scene, Type_Preview, Type_LoadErrorLog, Type_Enchantments, Type_Enchantment, Type_BodyParts, Type_BodyPart, Type_Meshes, Type_Mesh, Type_Icons, Type_Icon, Type_Musics, Type_Music, Type_SoundsRes, Type_SoundRes, Type_Textures, Type_Texture, Type_Videos, Type_Video, Type_DebugProfiles, Type_DebugProfile, Type_SoundGens, Type_SoundGen, Type_MagicEffects, Type_MagicEffect, Type_Lands, Type_Land, Type_LandTextures, Type_LandTexture, Type_Pathgrids, Type_Pathgrid, Type_StartScripts, Type_StartScript, Type_Search, Type_MetaDatas, Type_MetaData, Type_RunLog }; enum { NumberOfTypes = Type_RunLog+1 }; private: Class mClass; ArgumentType mArgumentType; Type mType; std::string mId; int mIndex; public: UniversalId (const std::string& universalId); UniversalId (Type type = Type_None); UniversalId (Type type, const std::string& id); ///< Using a type for a non-ID-argument UniversalId will throw an exception. UniversalId (Type type, int index); ///< Using a type for a non-index-argument UniversalId will throw an exception. Class getClass() const; ArgumentType getArgumentType() const; Type getType() const; const std::string& getId() const; ///< Calling this function for a non-ID type will throw an exception. int getIndex() const; ///< Calling this function for a non-index type will throw an exception. bool isEqual (const UniversalId& universalId) const; bool isLess (const UniversalId& universalId) const; std::string getTypeName() const; std::string toString() const; std::string getIcon() const; ///< Will return an empty string, if no icon is available. static std::vector listReferenceableTypes(); static std::vector listTypes (int classes); /// If \a type is a SubRecord, RefRecord or Record type return the type of the table /// that contains records of type \a type. /// Otherwise return Type_None. static Type getParentType (Type type); }; bool operator== (const UniversalId& left, const UniversalId& right); bool operator!= (const UniversalId& left, const UniversalId& right); bool operator< (const UniversalId& left, const UniversalId& right); } Q_DECLARE_METATYPE (CSMWorld::UniversalId) #endif openmw-openmw-0.48.0/apps/opencs/view/000077500000000000000000000000001445372753700176405ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/doc/000077500000000000000000000000001445372753700204055ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/doc/adjusterwidget.cpp000066400000000000000000000062611445372753700241430ustar00rootroot00000000000000#include "adjusterwidget.hpp" #include #include #include #include #include CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) { QHBoxLayout *layout = new QHBoxLayout (this); mIcon = new QLabel (this); layout->addWidget (mIcon, 0); mMessage = new QLabel (this); mMessage->setWordWrap (true); mMessage->setSizePolicy (QSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum)); layout->addWidget (mMessage, 1); setName ("", false); setLayout (layout); } void CSVDoc::AdjusterWidget::setAction (ContentAction action) { mAction = action; } void CSVDoc::AdjusterWidget::setLocalData (const boost::filesystem::path& localData) { mLocalData = localData; } boost::filesystem::path CSVDoc::AdjusterWidget::getPath() const { if (!mValid) throw std::logic_error ("invalid content file path"); return mResultPath; } bool CSVDoc::AdjusterWidget::isValid() const { return mValid; } void CSVDoc::AdjusterWidget::setFilenameCheck (bool doCheck) { mDoFilenameCheck = doCheck; } void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { QString message; mValid = (!name.isEmpty()); bool warning = false; if (!mValid) { message = "No name."; } else { boost::filesystem::path path (name.toUtf8().data()); std::string extension = Misc::StringUtils::lowerCase(path.extension().string()); bool isLegacyPath = (extension == ".esm" || extension == ".esp"); bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); if (isLegacyPath) path.replace_extension (addon ? ".omwaddon" : ".omwgame"); //if the file came from data-local and is not a legacy file to be converted, //don't worry about doing a file check. if (!isFilePathChanged && !isLegacyPath) { // path already points to the local data directory message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; } //in all other cases, ensure the path points to data-local and do an existing file check else { // path points somewhere else or is a leaf name. if (isFilePathChanged) path = mLocalData / path.filename(); message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; if (boost::filesystem::exists (path)) { /// \todo add an user setting to make this an error. message += "

A file with the same name already exists. If you continue, it will be overwritten."; warning = true; } } } mMessage->setText (message); mIcon->setPixmap (style()->standardIcon ( mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). pixmap (QSize (16, 16))); emit stateChanged (mValid); } openmw-openmw-0.48.0/apps/opencs/view/doc/adjusterwidget.hpp000066400000000000000000000022571445372753700241510ustar00rootroot00000000000000#ifndef CSV_DOC_ADJUSTERWIDGET_H #define CSV_DOC_ADJUSTERWIDGET_H #include #include class QLabel; namespace CSVDoc { enum ContentAction { ContentAction_New, ContentAction_Edit, ContentAction_Undefined }; class AdjusterWidget : public QWidget { Q_OBJECT public: boost::filesystem::path mLocalData; QLabel *mMessage; QLabel *mIcon; bool mValid; boost::filesystem::path mResultPath; ContentAction mAction; bool mDoFilenameCheck; public: AdjusterWidget (QWidget *parent = nullptr); void setLocalData (const boost::filesystem::path& localData); void setAction (ContentAction action); void setFilenameCheck (bool doCheck); bool isValid() const; boost::filesystem::path getPath() const; ///< This function must not be called if there is no valid path. public slots: void setName (const QString& name, bool addon); signals: void stateChanged (bool valid); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/filedialog.cpp000066400000000000000000000135621445372753700232170ustar00rootroot00000000000000#include "filedialog.hpp" #include #include #include "components/contentselector/model/esmfile.hpp" #include "components/contentselector/view/contentselector.hpp" #include "filewidget.hpp" #include "adjusterwidget.hpp" CSVDoc::FileDialog::FileDialog(QWidget *parent) : QDialog(parent), mSelector (nullptr), mAction(ContentAction_Undefined), mFileWidget (nullptr), mAdjusterWidget (nullptr), mDialogBuilt(false) { ui.setupUi (this); resize(400, 400); setObjectName ("FileDialog"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/false); mAdjusterWidget = new AdjusterWidget (this); } void CSVDoc::FileDialog::addFiles(const std::vector& dataDirs) { for (auto iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) { QString path = QString::fromUtf8(iter->string().c_str()); mSelector->addFiles(path); } mSelector->sortFiles(); } void CSVDoc::FileDialog::setEncoding(const QString &encoding) { mSelector->setEncoding(encoding); } void CSVDoc::FileDialog::clearFiles() { mSelector->clearFiles(); } QStringList CSVDoc::FileDialog::selectedFilePaths() { QStringList filePaths; for (ContentSelectorModel::EsmFile *file : mSelector->selectedFiles() ) filePaths.append(file->filePath()); return filePaths; } void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) { mAdjusterWidget->setLocalData (localData); } void CSVDoc::FileDialog::showDialog (ContentAction action) { mAction = action; ui.projectGroupBoxLayout->insertWidget (0, mAdjusterWidget); switch (mAction) { case ContentAction_New: buildNewFileView(); break; case ContentAction_Edit: buildOpenFileView(); break; default: break; } mAdjusterWidget->setFilenameCheck (mAction == ContentAction_New); if(!mDialogBuilt) { //connections common to both dialog view flavors connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), this, SLOT (slotUpdateAcceptButton (int))); connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); mDialogBuilt = true; } show(); raise(); activateWindow(); } void CSVDoc::FileDialog::buildNewFileView() { setWindowTitle(tr("Create a new addon")); QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); createButton->setText ("Create"); createButton->setEnabled (false); if(!mFileWidget) { mFileWidget = new FileWidget (this); mFileWidget->setType (true); mFileWidget->extensionLabelIsVisible(true); connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), mAdjusterWidget, SLOT (setName (const QString&, bool))); connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), this, SLOT (slotUpdateAcceptButton(const QString &, bool))); } ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); } void CSVDoc::FileDialog::buildOpenFileView() { setWindowTitle(tr("Open")); ui.projectGroupBox->setTitle (QString("")); ui.projectButtonBox->button(QDialogButtonBox::Ok)->setText ("Open"); if(mSelector->isGamefileSelected()) ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (true); else ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); if(!mDialogBuilt) { connect (mSelector, SIGNAL (signalAddonDataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (slotAddonDataChanged(const QModelIndex&, const QModelIndex&))); } connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } void CSVDoc::FileDialog::slotAddonDataChanged(const QModelIndex &topleft, const QModelIndex &bottomright) { slotUpdateAcceptButton(0); } void CSVDoc::FileDialog::slotUpdateAcceptButton(int) { QString name = ""; if (mFileWidget && mAction == ContentAction_New) name = mFileWidget->getName(); slotUpdateAcceptButton (name, true); } void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) { bool success = !mSelector->selectedFiles().empty(); bool isNew = (mAction == ContentAction_New); if (isNew) success = !name.isEmpty(); else if (success) { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); } else mAdjusterWidget->setName ("", true); ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); } QString CSVDoc::FileDialog::filename() const { if (mAction == ContentAction_New) return ""; return mSelector->currentFile(); } void CSVDoc::FileDialog::slotRejected() { emit rejected(); disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); if(mFileWidget) { delete mFileWidget; mFileWidget = nullptr; } close(); } void CSVDoc::FileDialog::slotNewFile() { emit signalCreateNewFile (mAdjusterWidget->getPath()); if(mFileWidget) { delete mFileWidget; mFileWidget = nullptr; } disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); close(); } void CSVDoc::FileDialog::slotOpenFile() { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); emit signalOpenFiles (mAdjusterWidget->getPath()); disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); close(); } openmw-openmw-0.48.0/apps/opencs/view/doc/filedialog.hpp000066400000000000000000000034141445372753700232170ustar00rootroot00000000000000#ifndef FILEDIALOG_HPP #define FILEDIALOG_HPP #include #include #ifndef Q_MOC_RUN #include #include "adjusterwidget.hpp" #ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED #define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED Q_DECLARE_METATYPE (boost::filesystem::path) #endif #endif #include "ui_filedialog.h" namespace ContentSelectorView { class ContentSelector; } namespace CSVDoc { class FileWidget; class FileDialog : public QDialog { Q_OBJECT private: ContentSelectorView::ContentSelector *mSelector; Ui::FileDialog ui; ContentAction mAction; FileWidget *mFileWidget; AdjusterWidget *mAdjusterWidget; bool mDialogBuilt; public: explicit FileDialog(QWidget *parent = nullptr); void showDialog (ContentAction action); void addFiles(const std::vector& dataDirs); void setEncoding (const QString &encoding); void clearFiles (); QString filename() const; QStringList selectedFilePaths(); void setLocalData (const boost::filesystem::path& localData); private: void buildNewFileView(); void buildOpenFileView(); signals: void signalOpenFiles (const boost::filesystem::path &path); void signalCreateNewFile (const boost::filesystem::path &path); void signalUpdateAcceptButton (bool, int); private slots: void slotNewFile(); void slotOpenFile(); void slotUpdateAcceptButton (int); void slotUpdateAcceptButton (const QString &, bool); void slotRejected(); void slotAddonDataChanged(const QModelIndex& topleft, const QModelIndex& bottomright); }; } #endif // FILEDIALOG_HPP openmw-openmw-0.48.0/apps/opencs/view/doc/filewidget.cpp000066400000000000000000000024151445372753700232360ustar00rootroot00000000000000#include "filewidget.hpp" #include #include #include #include #include QString CSVDoc::FileWidget::getExtension() const { return mAddon ? ".omwaddon" : ".omwgame"; } CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (false) { QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); layout->addWidget (mInput, 1); mType = new QLabel (this); layout ->addWidget (mType); connect (mInput, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); setLayout (layout); } void CSVDoc::FileWidget::setType (bool addon) { mAddon = addon; mType->setText (getExtension()); } QString CSVDoc::FileWidget::getName() const { QString text = mInput->text(); if (text.isEmpty()) return ""; return text + getExtension(); } void CSVDoc::FileWidget::textChanged (const QString& text) { emit nameChanged (getName(), mAddon); } void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) { mType->setVisible(visible); } void CSVDoc::FileWidget::setName (const std::string& text) { QString text2 = QString::fromUtf8 (text.c_str()); mInput->setText (text2); textChanged (text2); } openmw-openmw-0.48.0/apps/opencs/view/doc/filewidget.hpp000066400000000000000000000014271445372753700232450ustar00rootroot00000000000000#ifndef CSV_DOC_FILEWIDGET_H #define CSV_DOC_FILEWIDGET_H #include #include class QLabel; class QString; class QLineEdit; namespace CSVDoc { class FileWidget : public QWidget { Q_OBJECT bool mAddon; QLineEdit *mInput; QLabel *mType; QString getExtension() const; public: FileWidget (QWidget *parent = nullptr); void setType (bool addon); QString getName() const; void extensionLabelIsVisible(bool visible); void setName (const std::string& text); private slots: void textChanged (const QString& text); signals: void nameChanged (const QString& file, bool addon); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/globaldebugprofilemenu.cpp000066400000000000000000000053331445372753700256320ustar00rootroot00000000000000#include "globaldebugprofilemenu.hpp" #include #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" void CSVDoc::GlobalDebugProfileMenu::rebuild() { clear(); delete mActions; mActions = nullptr; int idColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); int globalColumn = mDebugProfiles->findColumnIndex ( CSMWorld::Columns::ColumnId_GlobalProfile); int size = mDebugProfiles->rowCount(); std::vector ids; for (int i=0; idata (mDebugProfiles->index (i, stateColumn)).toInt(); bool global = mDebugProfiles->data (mDebugProfiles->index (i, globalColumn)).toInt(); if (state!=CSMWorld::RecordBase::State_Deleted && global) ids.push_back ( mDebugProfiles->data (mDebugProfiles->index (i, idColumn)).toString()); } mActions = new QActionGroup (this); connect (mActions, SIGNAL (triggered (QAction *)), this, SLOT (actionTriggered (QAction *))); std::sort (ids.begin(), ids.end()); for (std::vector::const_iterator iter (ids.begin()); iter!=ids.end(); ++iter) { mActions->addAction (addAction (*iter)); } } CSVDoc::GlobalDebugProfileMenu::GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent) : QMenu (parent), mDebugProfiles (debugProfiles), mActions (nullptr) { rebuild(); connect (mDebugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (profileAboutToBeRemoved (const QModelIndex&, int, int))); connect (mDebugProfiles, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (profileInserted (const QModelIndex&, int, int))); connect (mDebugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (profileChanged (const QModelIndex&, const QModelIndex&))); } void CSVDoc::GlobalDebugProfileMenu::updateActions (bool running) { if (mActions) mActions->setEnabled (!running); } void CSVDoc::GlobalDebugProfileMenu::profileAboutToBeRemoved (const QModelIndex& parent, int start, int end) { rebuild(); } void CSVDoc::GlobalDebugProfileMenu::profileInserted (const QModelIndex& parent, int start, int end) { rebuild(); } void CSVDoc::GlobalDebugProfileMenu::profileChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { rebuild(); } void CSVDoc::GlobalDebugProfileMenu::actionTriggered (QAction *action) { emit triggered (std::string (action->text().toUtf8().constData())); } openmw-openmw-0.48.0/apps/opencs/view/doc/globaldebugprofilemenu.hpp000066400000000000000000000017641445372753700256430ustar00rootroot00000000000000#ifndef CSV_DOC_GLOBALDEBUGPROFILEMENU_H #define CSV_DOC_GLOBALDEBUGPROFILEMENU_H #include class QModelIndex; class QActionGroup; namespace CSMWorld { class IdTable; } namespace CSVDoc { class GlobalDebugProfileMenu : public QMenu { Q_OBJECT CSMWorld::IdTable *mDebugProfiles; QActionGroup *mActions; private: void rebuild(); public: GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent = nullptr); void updateActions (bool running); private slots: void profileAboutToBeRemoved (const QModelIndex& parent, int start, int end); void profileInserted (const QModelIndex& parent, int start, int end); void profileChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void actionTriggered (QAction *action); signals: void triggered (const std::string& profile); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/loader.cpp000066400000000000000000000126061445372753700223640ustar00rootroot00000000000000#include "loader.hpp" #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) { event->ignore(); cancel(); } CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) : mDocument (document), mTotalRecordsLabel (0), mRecordsLabel (0), mAborted (false), mMessages (nullptr), mRecords(0) { setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); setMinimumWidth (400); mLayout = new QVBoxLayout (this); // total progress mTotalRecordsLabel = new QLabel (this); mLayout->addWidget (mTotalRecordsLabel); mTotalProgress = new QProgressBar (this); mLayout->addWidget (mTotalProgress); mTotalProgress->setMinimum (0); mTotalProgress->setMaximum (document->getData().getTotalRecords(document->getContentFiles())); mTotalProgress->setTextVisible (true); mTotalProgress->setValue (0); mTotalRecords = 0; mFilesLoaded = 0; // record progress mLayout->addWidget (mRecordsLabel = new QLabel ("Records", this)); mRecordProgress = new QProgressBar (this); mLayout->addWidget (mRecordProgress); mRecordProgress->setMinimum (0); mRecordProgress->setTextVisible (true); mRecordProgress->setValue (0); // error message mError = new QLabel (this); mError->setWordWrap (true); mLayout->addWidget (mError); // buttons mButtons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); mLayout->addWidget (mButtons); setLayout (mLayout); move (QCursor::pos()); show(); connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); } void CSVDoc::LoadingDocument::nextStage (const std::string& name, int fileRecords) { ++mFilesLoaded; size_t numFiles = mDocument->getContentFiles().size(); mTotalRecordsLabel->setText (QString::fromUtf8 (("Loading: "+name +" ("+std::to_string(mFilesLoaded)+" of "+std::to_string((numFiles))+")").c_str())); mTotalRecords = mTotalProgress->value(); mRecordProgress->setValue (0); mRecordProgress->setMaximum (fileRecords>0 ? fileRecords : 1); mRecords = fileRecords; } void CSVDoc::LoadingDocument::nextRecord (int records) { if (records <= mRecords) { mTotalProgress->setValue (mTotalRecords+records); mRecordProgress->setValue(records); mRecordsLabel->setText(QString::fromStdString( "Records: "+std::to_string(records)+" of "+std::to_string(mRecords))); } } void CSVDoc::LoadingDocument::abort (const std::string& error) { mAborted = true; mError->setText (QString::fromUtf8 (("Loading failed: " + error + "").c_str())); mButtons->setStandardButtons (QDialogButtonBox::Close); } void CSVDoc::LoadingDocument::addMessage (const std::string& message) { if (!mMessages) { mMessages = new QListWidget (this); mLayout->insertWidget (4, mMessages); } new QListWidgetItem (QString::fromUtf8 (message.c_str()), mMessages); } void CSVDoc::LoadingDocument::cancel() { if (!mAborted) emit cancel (mDocument); else { emit close (mDocument); deleteLater(); } } CSVDoc::Loader::Loader() {} CSVDoc::Loader::~Loader() { for (std::map::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) delete iter->second; } void CSVDoc::Loader::add (CSMDoc::Document *document) { LoadingDocument *loading = new LoadingDocument (document); mDocuments.insert (std::make_pair (document, loading)); connect (loading, SIGNAL (cancel (CSMDoc::Document *)), this, SIGNAL (cancel (CSMDoc::Document *))); connect (loading, SIGNAL (close (CSMDoc::Document *)), this, SIGNAL (close (CSMDoc::Document *))); } void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error) { std::map::iterator iter = mDocuments.begin(); for (; iter!=mDocuments.end(); ++iter) if (iter->first==document) break; if (iter==mDocuments.end()) return; if (completed || error.empty()) { delete iter->second; mDocuments.erase (iter); } else { iter->second->abort (error); // Leave the window open for now (wait for the user to close it) mDocuments.erase (iter); } } void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, int fileRecords) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) iter->second->nextStage (name, fileRecords); } void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) iter->second->nextRecord (records); } void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& message) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) iter->second->addMessage (message); } openmw-openmw-0.48.0/apps/opencs/view/doc/loader.hpp000066400000000000000000000043141445372753700223660ustar00rootroot00000000000000#ifndef CSV_DOC_LOADER_H #define CSV_DOC_LOADER_H #include #include #include class QLabel; class QProgressBar; class QDialogButtonBox; class QListWidget; class QVBoxLayout; namespace CSMDoc { class Document; } namespace CSVDoc { class LoadingDocument : public QWidget { Q_OBJECT CSMDoc::Document *mDocument; QLabel *mTotalRecordsLabel; QLabel *mRecordsLabel; QProgressBar *mTotalProgress; QProgressBar *mRecordProgress; bool mAborted; QDialogButtonBox *mButtons; QLabel *mError; QListWidget *mMessages; QVBoxLayout *mLayout; int mRecords; int mTotalRecords; int mFilesLoaded; private: void closeEvent (QCloseEvent *event) override; public: LoadingDocument (CSMDoc::Document *document); void nextStage (const std::string& name, int totalRecords); void nextRecord (int records); void abort (const std::string& error); void addMessage (const std::string& message); private slots: void cancel(); signals: void cancel (CSMDoc::Document *document); ///< Stop loading process. void close (CSMDoc::Document *document); ///< Close stopped loading process. }; class Loader : public QObject { Q_OBJECT std::map mDocuments; public: Loader(); ~Loader() override; signals: void cancel (CSMDoc::Document *document); void close (CSMDoc::Document *document); public slots: void add (CSMDoc::Document *document); void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); void nextRecord (CSMDoc::Document *document, int records); void loadMessage (CSMDoc::Document *document, const std::string& message); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/newgame.cpp000066400000000000000000000036311445372753700225370ustar00rootroot00000000000000#include "newgame.hpp" #include #include #include #include #include #include "filewidget.hpp" #include "adjusterwidget.hpp" CSVDoc::NewGameDialogue::NewGameDialogue() { setWindowTitle ("Create New Game"); QVBoxLayout *layout = new QVBoxLayout (this); mFileWidget = new FileWidget (this); mFileWidget->setType (false); layout->addWidget (mFileWidget, 1); mAdjusterWidget = new AdjusterWidget (this); layout->addWidget (mAdjusterWidget, 1); QDialogButtonBox *buttons = new QDialogButtonBox (this); mCreate = new QPushButton ("Create", this); mCreate->setDefault (true); mCreate->setEnabled (false); buttons->addButton (mCreate, QDialogButtonBox::AcceptRole); QPushButton *cancel = new QPushButton ("Cancel", this); buttons->addButton (cancel, QDialogButtonBox::RejectRole); layout->addWidget (buttons); setLayout (layout); connect (mAdjusterWidget, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); connect (mCreate, SIGNAL (clicked()), this, SLOT (create())); connect (cancel, SIGNAL (clicked()), this, SLOT (reject())); connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), mAdjusterWidget, SLOT (setName (const QString&, bool))); QRect scr = QGuiApplication::primaryScreen()->geometry(); QRect rect = geometry(); move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } void CSVDoc::NewGameDialogue::setLocalData (const boost::filesystem::path& localData) { mAdjusterWidget->setLocalData (localData); } void CSVDoc::NewGameDialogue::stateChanged (bool valid) { mCreate->setEnabled (valid); } void CSVDoc::NewGameDialogue::create() { emit createRequest (mAdjusterWidget->getPath()); } void CSVDoc::NewGameDialogue::reject() { emit cancelCreateGame (); QDialog::reject(); } openmw-openmw-0.48.0/apps/opencs/view/doc/newgame.hpp000066400000000000000000000016711445372753700225460ustar00rootroot00000000000000#ifndef CSV_DOC_NEWGAME_H #define CSV_DOC_NEWGAME_H #include #include #include #ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED #define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED Q_DECLARE_METATYPE (boost::filesystem::path) #endif class QPushButton; namespace CSVDoc { class FileWidget; class AdjusterWidget; class NewGameDialogue : public QDialog { Q_OBJECT QPushButton *mCreate; FileWidget *mFileWidget; AdjusterWidget *mAdjusterWidget; public: NewGameDialogue(); void setLocalData (const boost::filesystem::path& localData); signals: void createRequest (const boost::filesystem::path& file); void cancelCreateGame (); private slots: void stateChanged (bool valid); void create(); void reject() override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/operation.cpp000066400000000000000000000073771445372753700231270ustar00rootroot00000000000000#include "operation.hpp" #include #include #include #include #include "../../model/doc/document.hpp" void CSVDoc::Operation::updateLabel (int threads) { if (threads==-1 || ((threads==0)!=mStalling)) { std::string name ("unknown operation"); switch (mType) { case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; case CSMDoc::State_Searching: name = "searching"; break; case CSMDoc::State_Merging: name = "merging"; break; } std::ostringstream stream; if ((mStalling = (threads<=0))) { stream << name << " (waiting for a free worker thread)"; } else { stream << name << " (%p%)"; } mProgressBar->setFormat (stream.str().c_str()); } } CSVDoc::Operation::Operation (int type, QWidget* parent) : mType (type), mStalling (false) { /// \todo Add a cancel button or a pop up menu with a cancel item initWidgets(); setBarColor( type); updateLabel(); /// \todo assign different progress bar colours to allow the user to distinguish easily between operation types } CSVDoc::Operation::~Operation() { delete mLayout; delete mProgressBar; delete mAbortButton; } void CSVDoc::Operation::initWidgets() { mProgressBar = new QProgressBar (); mAbortButton = new QPushButton("Abort"); mLayout = new QHBoxLayout(); mLayout->addWidget (mProgressBar); mLayout->addWidget (mAbortButton); connect (mAbortButton, SIGNAL (clicked()), this, SLOT (abortOperation())); } void CSVDoc::Operation::setProgress (int current, int max, int threads) { updateLabel (threads); mProgressBar->setRange (0, max); mProgressBar->setValue (current); } int CSVDoc::Operation::getType() const { return mType; } void CSVDoc::Operation::setBarColor (int type) { QString style ="QProgressBar {" "text-align: center;" "}" "QProgressBar::chunk {" "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:.50 %2 stop: .51 %3 stop:1 %4);" "text-align: center;" "margin: 2px 1px 1p 2px;" "}"; QString topColor = "#F2F6F8"; QString bottomColor = "#E0EFF9"; QString midTopColor = "#D8E1E7"; QString midBottomColor = "#B5C6D0"; // default gray gloss // colors inspired by samples from: // http://www.colorzilla.com/gradient-editor/ switch (type) { case CSMDoc::State_Saving: topColor = "#FECCB1"; midTopColor = "#F17432"; midBottomColor = "#EA5507"; bottomColor = "#FB955E"; // red gloss #2 break; case CSMDoc::State_Searching: topColor = "#EBF1F6"; midTopColor = "#ABD3EE"; midBottomColor = "#89C3EB"; bottomColor = "#D5EBFB"; //blue gloss #4 break; case CSMDoc::State_Verifying: topColor = "#BFD255"; midTopColor = "#8EB92A"; midBottomColor = "#72AA00"; bottomColor = "#9ECB2D"; //green gloss break; case CSMDoc::State_Merging: topColor = "#F3E2C7"; midTopColor = "#C19E67"; midBottomColor = "#B68D4C"; bottomColor = "#E9D4B3"; //l Brown 3D break; default: topColor = "#F2F6F8"; bottomColor = "#E0EFF9"; midTopColor = "#D8E1E7"; midBottomColor = "#B5C6D0"; // gray gloss for undefined ops } mProgressBar->setStyleSheet(style.arg(topColor).arg(midTopColor).arg(midBottomColor).arg(bottomColor)); } QHBoxLayout *CSVDoc::Operation::getLayout() const { return mLayout; } void CSVDoc::Operation::abortOperation() { emit abortOperation (mType); } openmw-openmw-0.48.0/apps/opencs/view/doc/operation.hpp000066400000000000000000000020221445372753700231120ustar00rootroot00000000000000#ifndef CSV_DOC_OPERATION_H #define CSV_DOC_OPERATION_H #include class QHBoxLayout; class QPushButton; class QProgressBar; namespace CSVDoc { class Operation : public QObject { Q_OBJECT int mType; bool mStalling; QProgressBar *mProgressBar; QPushButton *mAbortButton; QHBoxLayout *mLayout; // not implemented Operation (const Operation&); Operation& operator= (const Operation&); void updateLabel (int threads = -1); public: Operation (int type, QWidget *parent); ~Operation() override; void setProgress (int current, int max, int threads); int getType() const; QHBoxLayout *getLayout() const; private: void setBarColor (int type); void initWidgets(); signals: void abortOperation (int type); private slots: void abortOperation(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/operations.cpp000066400000000000000000000036701445372753700233020ustar00rootroot00000000000000#include "operations.hpp" #include #include "operation.hpp" CSVDoc::Operations::Operations() { /// \todo make widget height fixed (exactly the height required to display all operations) setFeatures (QDockWidget::NoDockWidgetFeatures); QWidget *widgetContainer = new QWidget (this); mLayout = new QVBoxLayout; widgetContainer->setLayout (mLayout); setWidget (widgetContainer); setVisible (false); setFixedHeight (widgetContainer->height()); setTitleBarWidget (new QWidget (this)); } void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) { for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) if ((*iter)->getType()==type) { (*iter)->setProgress (current, max, threads); return; } int oldCount = static_cast(mOperations.size()); int newCount = oldCount + 1; Operation *operation = new Operation (type, this); connect (operation, SIGNAL (abortOperation (int)), this, SIGNAL (abortOperation (int))); mLayout->addLayout (operation->getLayout()); mOperations.push_back (operation); operation->setProgress (current, max, threads); if ( oldCount > 0) setFixedHeight (height()/oldCount * newCount); setVisible (true); } void CSVDoc::Operations::quitOperation (int type) { for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) if ((*iter)->getType()==type) { int oldCount = static_cast(mOperations.size()); int newCount = oldCount - 1; mLayout->removeItem ((*iter)->getLayout()); (*iter)->deleteLater(); mOperations.erase (iter); if (oldCount > 1) setFixedHeight (height() / oldCount * newCount); else setVisible (false); break; } } openmw-openmw-0.48.0/apps/opencs/view/doc/operations.hpp000066400000000000000000000015351445372753700233050ustar00rootroot00000000000000#ifndef CSV_DOC_OPERATIONS_H #define CSV_DOC_OPERATIONS_H #include #include class QVBoxLayout; namespace CSVDoc { class Operation; class Operations : public QDockWidget { Q_OBJECT QVBoxLayout *mLayout; std::vector mOperations; // not implemented Operations (const Operations&); Operations& operator= (const Operations&); public: Operations(); void setProgress (int current, int max, int type, int threads); ///< Implicitly starts the operation, if it is not running already. void quitOperation (int type); ///< Calling this function for an operation that is not running is a no-op. signals: void abortOperation (int type); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/runlogsubview.cpp000066400000000000000000000006601445372753700240260ustar00rootroot00000000000000#include "runlogsubview.hpp" #include CSVDoc::RunLogSubView::RunLogSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) { QTextEdit *edit = new QTextEdit (this); edit->setDocument (document.getRunLog()); edit->setReadOnly (true); setWidget (edit); } void CSVDoc::RunLogSubView::setEditLock (bool locked) { // ignored since this SubView does not have editing } openmw-openmw-0.48.0/apps/opencs/view/doc/runlogsubview.hpp000066400000000000000000000005421445372753700240320ustar00rootroot00000000000000#ifndef CSV_DOC_RUNLOGSUBVIEW_H #define CSV_DOC_RUNLOGSUBVIEW_H #include "subview.hpp" namespace CSVDoc { class RunLogSubView : public SubView { Q_OBJECT public: RunLogSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/sizehint.cpp000066400000000000000000000004551445372753700227520ustar00rootroot00000000000000#include "sizehint.hpp" CSVDoc::SizeHintWidget::SizeHintWidget(QWidget *parent) : QWidget(parent) {} CSVDoc::SizeHintWidget::~SizeHintWidget() {} QSize CSVDoc::SizeHintWidget::sizeHint() const { return mSize; } void CSVDoc::SizeHintWidget::setSizeHint(const QSize &size) { mSize = size; } openmw-openmw-0.48.0/apps/opencs/view/doc/sizehint.hpp000066400000000000000000000006561445372753700227620ustar00rootroot00000000000000#ifndef CSV_DOC_SIZEHINT_H #define CSV_DOC_SIZEHINT_H #include #include namespace CSVDoc { class SizeHintWidget : public QWidget { QSize mSize; public: SizeHintWidget(QWidget *parent = nullptr); ~SizeHintWidget() override; QSize sizeHint() const override; void setSizeHint(const QSize &size); }; } #endif // CSV_DOC_SIZEHINT_H openmw-openmw-0.48.0/apps/opencs/view/doc/startup.cpp000066400000000000000000000073161445372753700226220ustar00rootroot00000000000000#include "startup.hpp" #include #include #include #include #include #include #include #include #include QPushButton *CSVDoc::StartupDialogue::addButton (const QString& label, const QIcon& icon) { int column = mColumn--; QPushButton *button = new QPushButton (this); button->setIcon (QIcon (icon)); button->setSizePolicy (QSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred)); mLayout->addWidget (button, 0, column); mLayout->addWidget (new QLabel (label, this), 1, column, Qt::AlignCenter); int width = mLayout->itemAtPosition (1, column)->widget()->sizeHint().width(); if (width>mWidth) mWidth = width; return button; } QWidget *CSVDoc::StartupDialogue::createButtons() { QWidget *widget = new QWidget (this); mLayout = new QGridLayout (widget); /// \todo add icons QPushButton *loadDocument = addButton ("Edit A Content File", QIcon (":startup/edit-content")); connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument())); QPushButton *createAddon = addButton ("Create A New Addon", QIcon (":startup/create-addon")); connect (createAddon, SIGNAL (clicked()), this, SIGNAL (createAddon())); QPushButton *createGame = addButton ("Create A New Game", QIcon (":startup/create-game")); connect (createGame, SIGNAL (clicked()), this, SIGNAL (createGame())); for (int i=0; i<3; ++i) mLayout->setColumnMinimumWidth (i, mWidth); mLayout->setRowMinimumHeight (0, mWidth); mLayout->setSizeConstraint (QLayout::SetMinimumSize); mLayout->setHorizontalSpacing (32); mLayout->setContentsMargins (16, 16, 16, 8); loadDocument->setIconSize (QSize (mWidth, mWidth)); createGame->setIconSize (QSize (mWidth, mWidth)); createAddon->setIconSize (QSize (mWidth, mWidth)); widget->setLayout (mLayout); return widget; } QWidget *CSVDoc::StartupDialogue::createTools() { QWidget *widget = new QWidget (this); QHBoxLayout *layout = new QHBoxLayout (widget); layout->setDirection (QBoxLayout::RightToLeft); layout->setContentsMargins (4, 4, 4, 4); QPushButton *config = new QPushButton (widget); config->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); config->setIcon (QIcon (":startup/configure")); config->setToolTip ("Open user settings"); layout->addWidget (config); layout->addWidget (new QWidget, 1); // dummy widget; stops buttons from taking all the space widget->setLayout (layout); connect (config, SIGNAL (clicked()), this, SIGNAL (editConfig())); return widget; } CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) { setWindowTitle ("OpenMW-CS"); QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout->addWidget (createButtons()); layout->addWidget (createTools()); /// \todo remove this label once we are feature complete and convinced that this thing is /// working properly. QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advise to make backups regularly if you are working with live data.
"); QFont font; font.setPointSize (12); font.setBold (true); warning->setFont (font); warning->setWordWrap (true); layout->addWidget (warning, 1); setLayout (layout); QRect scr = QGuiApplication::primaryScreen()->geometry(); QRect rect = geometry(); move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } openmw-openmw-0.48.0/apps/opencs/view/doc/startup.hpp000066400000000000000000000013101445372753700226130ustar00rootroot00000000000000#ifndef CSV_DOC_STARTUP_H #define CSV_DOC_STARTUP_H #include class QGridLayout; class QString; class QPushButton; class QWidget; class QIcon; namespace CSVDoc { class StartupDialogue : public QWidget { Q_OBJECT private: int mWidth; int mColumn; QGridLayout *mLayout; QPushButton *addButton (const QString& label, const QIcon& icon); QWidget *createButtons(); QWidget *createTools(); public: StartupDialogue(); signals: void createGame(); void createAddon(); void loadDocument(); void editConfig(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/subview.cpp000066400000000000000000000026031445372753700225760ustar00rootroot00000000000000#include "subview.hpp" #include "view.hpp" #include #include bool CSVDoc::SubView::event (QEvent *event) { if (event->type()==QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast (event); if (keyEvent->key()==Qt::Key_W && keyEvent->modifiers()==(Qt::ShiftModifier | Qt::ControlModifier)) emit closeRequest(); return true; } return QDockWidget::event (event); } CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id) { /// \todo add a button to the title bar that clones this sub view setWindowTitle (QString::fromUtf8 (mUniversalId.toString().c_str())); setAttribute(Qt::WA_DeleteOnClose); } CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const { return mUniversalId; } void CSVDoc::SubView::setStatusBar (bool show) {} void CSVDoc::SubView::useHint (const std::string& hint) {} void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; setWindowTitle (QString::fromUtf8(mUniversalId.toString().c_str())); emit universalIdChanged (mUniversalId); } void CSVDoc::SubView::closeEvent (QCloseEvent *event) { emit updateSubViewIndices (this); } std::string CSVDoc::SubView::getTitle() const { return mUniversalId.toString(); } void CSVDoc::SubView::closeRequest() { emit closeRequest (this); } openmw-openmw-0.48.0/apps/opencs/view/doc/subview.hpp000066400000000000000000000031051445372753700226010ustar00rootroot00000000000000#ifndef CSV_DOC_SUBVIEW_H #define CSV_DOC_SUBVIEW_H #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "subviewfactory.hpp" #include class QUndoStack; namespace CSMWorld { class Data; } namespace CSVDoc { class View; class SubView : public QDockWidget { Q_OBJECT CSMWorld::UniversalId mUniversalId; // not implemented SubView (const SubView&); SubView& operator= (SubView&); protected: void setUniversalId(const CSMWorld::UniversalId& id); bool event (QEvent *event) override; public: SubView (const CSMWorld::UniversalId& id); CSMWorld::UniversalId getUniversalId() const; virtual void setEditLock (bool locked) = 0; virtual void setStatusBar (bool show); ///< Default implementation: ignored virtual void useHint (const std::string& hint); ///< Default implementation: ignored virtual std::string getTitle() const; private: void closeEvent (QCloseEvent *event) override; signals: void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); void closeRequest (SubView *subView); void updateTitle(); void updateSubViewIndices (SubView *view = nullptr); void universalIdChanged (const CSMWorld::UniversalId& universalId); protected slots: void closeRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/subviewfactory.cpp000066400000000000000000000022151445372753700241650ustar00rootroot00000000000000#include "subviewfactory.hpp" #include #include CSVDoc::SubViewFactoryBase::SubViewFactoryBase() {} CSVDoc::SubViewFactoryBase::~SubViewFactoryBase() {} CSVDoc::SubViewFactoryManager::SubViewFactoryManager() {} CSVDoc::SubViewFactoryManager::~SubViewFactoryManager() { for (std::map::iterator iter (mSubViewFactories.begin()); iter!=mSubViewFactories.end(); ++iter) delete iter->second; } void CSVDoc::SubViewFactoryManager::add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory) { assert (mSubViewFactories.find (id)==mSubViewFactories.end()); mSubViewFactories.insert (std::make_pair (id, factory)); } CSVDoc::SubView *CSVDoc::SubViewFactoryManager::makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) { std::map::iterator iter = mSubViewFactories.find (id.getType()); if (iter==mSubViewFactories.end()) throw std::runtime_error ("Failed to create a sub view for: " + id.toString()); return iter->second->makeSubView (id, document); } openmw-openmw-0.48.0/apps/opencs/view/doc/subviewfactory.hpp000066400000000000000000000026451445372753700242010ustar00rootroot00000000000000#ifndef CSV_DOC_SUBVIEWFACTORY_H #define CSV_DOC_SUBVIEWFACTORY_H #include #include "../../model/world/universalid.hpp" namespace CSMDoc { class Document; } namespace CSVDoc { class SubView; class SubViewFactoryBase { // not implemented SubViewFactoryBase (const SubViewFactoryBase&); SubViewFactoryBase& operator= (const SubViewFactoryBase&); public: SubViewFactoryBase(); virtual ~SubViewFactoryBase(); virtual SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0; ///< The ownership of the returned sub view is not transferred. }; class SubViewFactoryManager { std::map mSubViewFactories; // not implemented SubViewFactoryManager (const SubViewFactoryManager&); SubViewFactoryManager& operator= (const SubViewFactoryManager&); public: SubViewFactoryManager(); ~SubViewFactoryManager(); void add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory); ///< The ownership of \a factory is transferred to this. SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); ///< The ownership of the returned sub view is not transferred. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/subviewfactoryimp.hpp000066400000000000000000000026451445372753700247070ustar00rootroot00000000000000#ifndef CSV_DOC_SUBVIEWFACTORYIMP_H #define CSV_DOC_SUBVIEWFACTORYIMP_H #include "../../model/doc/document.hpp" #include "subviewfactory.hpp" namespace CSVDoc { template class SubViewFactory : public SubViewFactoryBase { public: CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; }; template CSVDoc::SubView *SubViewFactory::makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) { return new SubViewT (id, document); } template class SubViewFactoryWithCreator : public SubViewFactoryBase { bool mSorting; public: SubViewFactoryWithCreator (bool sorting = true); CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; }; template SubViewFactoryWithCreator::SubViewFactoryWithCreator (bool sorting) : mSorting (sorting) {} template CSVDoc::SubView *SubViewFactoryWithCreator::makeSubView ( const CSMWorld::UniversalId& id, CSMDoc::Document& document) { return new SubViewT (id, document, CreatorFactoryT(), mSorting); } } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/view.cpp000066400000000000000000001075201445372753700220700ustar00rootroot00000000000000#include "view.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../model/world/idtable.hpp" #include "../world/subviews.hpp" #include "../world/scenesubview.hpp" #include "../world/tablesubview.hpp" #include "../world/dialoguesubview.hpp" #include "../world/scriptsubview.hpp" #include "../tools/subviews.hpp" #include #include #include "viewmanager.hpp" #include "operations.hpp" #include "subview.hpp" #include "globaldebugprofilemenu.hpp" #include "runlogsubview.hpp" #include "subviewfactoryimp.hpp" void CSVDoc::View::closeEvent (QCloseEvent *event) { if (!mViewManager.closeRequest (this)) event->ignore(); else { // closeRequest() returns true if last document mViewManager.removeDocAndView(mDocument); } } void CSVDoc::View::setupFileMenu() { QMenu *file = menuBar()->addMenu (tr ("File")); QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); connect (newGame, SIGNAL (triggered()), this, SIGNAL (newGameRequest())); QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); connect (newAddon, SIGNAL (triggered()), this, SIGNAL (newAddonRequest())); QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); connect (save, SIGNAL (triggered()), this, SLOT (save())); mSave = save; file->addSeparator(); QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); connect (verify, SIGNAL (triggered()), this, SLOT (verify())); mVerify = verify; QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); connect (merge, SIGNAL (triggered()), this, SLOT (merge())); mMerge = merge; QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); file->addSeparator(); QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); connect (close, SIGNAL (triggered()), this, SLOT (close())); QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); connect (exit, SIGNAL (triggered()), this, SLOT (exit())); connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *))); } namespace { void updateUndoRedoAction(QAction *action, const std::string &settingsKey) { QKeySequence seq; CSMPrefs::State::get().getShortcutManager().getSequence(settingsKey, seq); action->setShortcut(seq); } } void CSVDoc::View::undoActionChanged() { updateUndoRedoAction(mUndo, "document-edit-undo"); } void CSVDoc::View::redoActionChanged() { updateUndoRedoAction(mRedo, "document-edit-redo"); } void CSVDoc::View::setupEditMenu() { QMenu *edit = menuBar()->addMenu (tr ("Edit")); mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, SIGNAL (changed ()), this, SLOT (undoActionChanged ())); mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); edit->addAction (mUndo); mRedo = mDocument->getUndoStack().createRedoAction (this, tr("Redo")); connect(mRedo, SIGNAL (changed ()), this, SLOT (redoActionChanged ())); setupShortcut("document-edit-redo", mRedo); mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); edit->addAction (mRedo); QAction* userSettings = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); connect (search, SIGNAL (triggered()), this, SLOT (addSearchSubView())); } void CSVDoc::View::setupViewMenu() { QMenu *view = menuBar()->addMenu (tr ("View")); QAction *newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); mShowStatusBar->setCheckable (true); mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->addAction (mShowStatusBar); QAction *filters = createMenuEntry(CSMWorld::UniversalId::Type_Filters, view, "document-mechanics-filters"); connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView())); } void CSVDoc::View::setupWorldMenu() { QMenu *world = menuBar()->addMenu (tr ("World")); QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); world->addSeparator(); QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); QAction *landTextures = createMenuEntry(CSMWorld::UniversalId::Type_LandTextures, world, "document-world-landtextures"); connect (landTextures, SIGNAL (triggered()), this, SLOT (addLandTexturesSubView())); QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); world->addSeparator(); QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); } void CSVDoc::View::setupMechanicsMenu() { QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); mechanics->addSeparator(); QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); QAction* enchantments = createMenuEntry(CSMWorld::UniversalId::Type_Enchantments, mechanics, "document-mechanics-enchantments"); connect (enchantments, SIGNAL (triggered()), this, SLOT (addEnchantmentsSubView())); QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); } void CSVDoc::View::setupCharacterMenu() { QMenu *characters = menuBar()->addMenu (tr ("Characters")); QAction* skills = createMenuEntry(CSMWorld::UniversalId::Type_Skills, characters, "document-character-skills"); connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); QAction* classes = createMenuEntry(CSMWorld::UniversalId::Type_Classes, characters, "document-character-classes"); connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); QAction* factions = createMenuEntry(CSMWorld::UniversalId::Type_Faction, characters, "document-character-factions"); connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); QAction* races = createMenuEntry(CSMWorld::UniversalId::Type_Races, characters, "document-character-races"); connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); characters->addSeparator(); QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); characters->addSeparator(); QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); } void CSVDoc::View::setupAssetsMenu() { QMenu *assets = menuBar()->addMenu (tr ("Assets")); QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); connect (reload, SIGNAL (triggered()), &mDocument->getData(), SLOT (assetsChanged())); assets->addSeparator(); QAction* sounds = createMenuEntry(CSMWorld::UniversalId::Type_Sounds, assets, "document-assets-sounds"); connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView())); QAction* soundGens = createMenuEntry(CSMWorld::UniversalId::Type_SoundGens, assets, "document-assets-soundgens"); connect (soundGens, SIGNAL (triggered()), this, SLOT (addSoundGensSubView())); assets->addSeparator(); // resources follow here QAction* meshes = createMenuEntry(CSMWorld::UniversalId::Type_Meshes, assets, "document-assets-meshes"); connect (meshes, SIGNAL (triggered()), this, SLOT (addMeshesSubView())); QAction* icons = createMenuEntry(CSMWorld::UniversalId::Type_Icons, assets, "document-assets-icons"); connect (icons, SIGNAL (triggered()), this, SLOT (addIconsSubView())); QAction* musics = createMenuEntry(CSMWorld::UniversalId::Type_Musics, assets, "document-assets-musics"); connect (musics, SIGNAL (triggered()), this, SLOT (addMusicsSubView())); QAction* soundFiles = createMenuEntry(CSMWorld::UniversalId::Type_SoundsRes, assets, "document-assets-soundres"); connect (soundFiles, SIGNAL (triggered()), this, SLOT (addSoundsResSubView())); QAction* textures = createMenuEntry(CSMWorld::UniversalId::Type_Textures, assets, "document-assets-textures"); connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); QAction* videos = createMenuEntry(CSMWorld::UniversalId::Type_Videos, assets, "document-assets-videos"); connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); } void CSVDoc::View::setupDebugMenu() { QMenu *debug = menuBar()->addMenu (tr ("Debug")); QAction* profiles = createMenuEntry(CSMWorld::UniversalId::Type_DebugProfiles, debug, "document-debug-profiles"); connect (profiles, SIGNAL (triggered()), this, SLOT (addDebugProfilesSubView())); debug->addSeparator(); mGlobalDebugProfileMenu = new GlobalDebugProfileMenu ( &dynamic_cast (*mDocument->getData().getTableModel ( CSMWorld::UniversalId::Type_DebugProfiles)), this); connect (mGlobalDebugProfileMenu, SIGNAL (triggered (const std::string&)), this, SLOT (run (const std::string&))); QAction *runDebug = debug->addMenu (mGlobalDebugProfileMenu); runDebug->setText (tr ("Run OpenMW")); setupShortcut("document-debug-run", runDebug); runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); connect (stopDebug, SIGNAL (triggered()), this, SLOT (stop())); mStopDebug = stopDebug; QAction* runLog = createMenuEntry(CSMWorld::UniversalId::Type_RunLog, debug, "document-debug-runlog"); connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); } void CSVDoc::View::setupHelpMenu() { QMenu *help = menuBar()->addMenu (tr ("Help")); QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help"); connect (helpInfo, SIGNAL (triggered()), this, SLOT (openHelp())); QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial"); connect (tutorial, SIGNAL (triggered()), this, SLOT (tutorial())); QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); connect (about, SIGNAL (triggered()), this, SLOT (infoAbout())); QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); connect (aboutQt, SIGNAL (triggered()), this, SLOT (infoAboutQt())); } QAction* CSVDoc::View::createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName) { const std::string title = CSMWorld::UniversalId (type).getTypeName(); QAction *entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); const std::string iconName = CSMWorld::UniversalId (type).getIcon(); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(QIcon(QString::fromStdString(iconName))); menu->addAction (entry); return entry; } QAction* CSVDoc::View::createMenuEntry(const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName) { QAction *entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(QIcon(QString::fromStdString(iconName))); menu->addAction (entry); return entry; } void CSVDoc::View::setupUi() { setupFileMenu(); setupEditMenu(); setupViewMenu(); setupWorldMenu(); setupMechanicsMenu(); setupCharacterMenu(); setupAssetsMenu(); setupDebugMenu(); setupHelpMenu(); } void CSVDoc::View::setupShortcut(const char* name, QAction* action) { CSMPrefs::Shortcut* shortcut = new CSMPrefs::Shortcut(name, this); shortcut->associateAction(action); } void CSVDoc::View::updateTitle() { std::ostringstream stream; stream << mDocument->getSavePath().filename().string(); if (mDocument->getState() & CSMDoc::State_Modified) stream << " *"; if (mViewTotal>1) stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); if (hideTitle) stream << " - " << mSubViews.at (0)->getTitle(); setWindowTitle (QString::fromUtf8(stream.str().c_str())); } void CSVDoc::View::updateSubViewIndices(SubView *view) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; if(view && mSubViews.contains(view)) { mSubViews.removeOne(view); // adjust (reduce) the scroll area (even floating), except when it is "Scrollbar Only" if (windows["mainwindow-scrollbar"].toString() == "Grow then Scroll") updateScrollbar(); } bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); updateTitle(); for (SubView *subView : mSubViews) { if (!subView->isFloating()) { if (hideTitle) { subView->setTitleBarWidget (new QWidget (this)); subView->setWindowTitle (QString::fromUtf8 (subView->getTitle().c_str())); } else { delete subView->titleBarWidget(); subView->setTitleBarWidget (nullptr); } } } } void CSVDoc::View::updateActions() { bool editing = !(mDocument->getState() & CSMDoc::State_Locked); bool running = mDocument->getState() & CSMDoc::State_Running; for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) (*iter)->setEnabled (editing); mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo()); mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo()); mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); mMerge->setEnabled (mDocument->getContentFiles().size()>1 && !(mDocument->getState() & CSMDoc::State_Merging)); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews), mScroll(nullptr), mScrollbarOnly(false) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; int width = std::max (windows["default-width"].toInt(), 300); int height = std::max (windows["default-height"].toInt(), 300); resize (width, height); mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); if (windows["mainwindow-scrollbar"].toString() == "Grow Only") { setCentralWidget (&mSubViewWindow); } else { createScrollArea(); } mOperations = new Operations; addDockWidget (Qt::BottomDockWidgetArea, mOperations); setContextMenuPolicy(Qt::NoContextMenu); updateTitle(); setupUi(); updateActions(); CSVWorld::addSubViewFactories (mSubViewFactory); CSVTools::addSubViewFactories (mSubViewFactory); mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); } CSVDoc::View::~View() { } const CSMDoc::Document *CSVDoc::View::getDocument() const { return mDocument; } CSMDoc::Document *CSVDoc::View::getDocument() { return mDocument; } void CSVDoc::View::setIndex (int viewIndex, int totalViews) { mViewIndex = viewIndex; mViewTotal = totalViews; updateTitle(); } void CSVDoc::View::updateDocumentState() { updateTitle(); updateActions(); static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, CSMDoc::State_Merging, -1 // end marker }; int state = mDocument->getState() ; for (int i=0; operations[i]!=-1; ++i) if (!(state & operations[i])) mOperations->quitOperation (operations[i]); QList subViews = findChildren(); for (QList::iterator iter (subViews.begin()); iter!=subViews.end(); ++iter) (*iter)->setEditLock (state & CSMDoc::State_Locked); } void CSVDoc::View::updateProgress (int current, int max, int type, int threads) { mOperations->setProgress (current, max, type, threads); } void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; bool isReferenceable = id.getClass() == CSMWorld::UniversalId::Class_RefRecord; // User setting to reuse sub views (on a per top level view basis) if (windows["reuse"].isTrue()) { for (SubView *sb : mSubViews) { bool isSubViewReferenceable = sb->getUniversalId().getType() == CSMWorld::UniversalId::Type_Referenceable; if((isReferenceable && isSubViewReferenceable && id.getId() == sb->getUniversalId().getId()) || (!isReferenceable && id == sb->getUniversalId())) { sb->setFocus(); if (!hint.empty()) sb->useHint (hint); return; } } } if (mScroll) QObject::connect(mScroll->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); // User setting for limiting the number of sub views per top level view. // Automatically open a new top level view if this number is exceeded // // If the sub view limit setting is one, the sub view title bar is hidden and the // text in the main title bar is adjusted accordingly if(mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view { mViewManager.addView(mDocument, id, hint); return; } SubView *view = nullptr; if(isReferenceable) { view = mSubViewFactory.makeSubView (CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); } else { view = mSubViewFactory.makeSubView (id, *mDocument); } assert(view); view->setParent(this); view->setEditLock (mDocument->getState() & CSMDoc::State_Locked); mSubViews.append(view); // only after assert int minWidth = windows["minimum-width"].toInt(); view->setMinimumWidth (minWidth); view->setStatusBar (mShowStatusBar->isChecked()); // Work out how to deal with additional subviews // // Policy for "Grow then Scroll": // // - Increase the horizontal width of the mainwindow until it becomes greater than or equal // to the screen (monitor) width. // - Move the mainwindow position sideways if necessary to fit within the screen. // - Any more additions increases the size of the mSubViewWindow (horizontal scrollbar // should become visible) // - Move the scroll bar to the newly added subview // mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; updateWidth(windows["grow-limit"].isTrue(), minWidth); mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); updateSubViewIndices(); connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); connect (view, SIGNAL (closeRequest (SubView *)), this, SLOT (closeRequest (SubView *))); connect (view, SIGNAL (updateTitle()), this, SLOT (updateTitle())); connect (view, SIGNAL (updateSubViewIndices (SubView *)), this, SLOT (updateSubViewIndices (SubView *))); CSVWorld::TableSubView* tableView = dynamic_cast(view); if (tableView) { connect (this, SIGNAL (requestFocus (const std::string&)), tableView, SLOT (requestFocus (const std::string&))); } CSVWorld::SceneSubView* sceneView = dynamic_cast(view); if (sceneView) { connect(sceneView, SIGNAL(requestFocus(const std::string&)), this, SLOT(onRequestFocus(const std::string&))); } if (CSMPrefs::State::get()["ID Tables"]["subview-new-window"].isTrue()) { CSVWorld::DialogueSubView* dialogueView = dynamic_cast(view); if (dialogueView) dialogueView->setFloating(true); CSVWorld::ScriptSubView* scriptView = dynamic_cast(view); if (scriptView) scriptView->setFloating(true); } view->show(); if (!hint.empty()) view->useHint (hint); } void CSVDoc::View::moveScrollBarToEnd(int min, int max) { if (mScroll) { mScroll->horizontalScrollBar()->setValue(max); QObject::disconnect(mScroll->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); } } void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Windows/hide-subview") updateSubViewIndices (nullptr); else if (*setting=="Windows/mainwindow-scrollbar") { if (setting->toString()!="Grow Only") { if (mScroll) { if (setting->toString()=="Scrollbar Only") { mScrollbarOnly = true; mSubViewWindow.setMinimumWidth(0); } else if (mScrollbarOnly) { mScrollbarOnly = false; updateScrollbar(); } } else { createScrollArea(); } } else if (mScroll) { mScroll->takeWidget(); setCentralWidget (&mSubViewWindow); mScroll->deleteLater(); mScroll = nullptr; } } } void CSVDoc::View::newView() { mViewManager.addView (mDocument); } void CSVDoc::View::save() { mDocument->save(); } void CSVDoc::View::openHelp() { Misc::HelpViewer::openHelp("manuals/openmw-cs/index.html"); } void CSVDoc::View::tutorial() { Misc::HelpViewer::openHelp("manuals/openmw-cs/tour.html"); } void CSVDoc::View::infoAbout() { // Get current OpenMW version QString versionInfo = (Version::getOpenmwVersionDescription(mDocument->getResourceDir().string())+ #if defined(__x86_64__) || defined(_M_X64) " (64-bit)").c_str(); #else " (32-bit)").c_str(); #endif // Get current year time_t now = time(nullptr); struct tm tstruct; char copyrightInfo[40]; tstruct = *localtime(&now); strftime(copyrightInfo, sizeof(copyrightInfo), "Copyright © 2008-%Y OpenMW Team", &tstruct); QString aboutText = QString( "

" "

OpenMW Construction Set

" "%1\n\n" "%2\n\n" "%3\n\n" "" "" "" "" "" "
%4https://openmw.org
%5https://forum.openmw.org
%6https://gitlab.com/OpenMW/openmw/issues
%7ircs://irc.libera.chat/#openmw
" "

") .arg(versionInfo , tr("OpenMW-CS is a content file editor for OpenMW, a modern, free and open source game engine.") , tr(copyrightInfo) , tr("Home Page:") , tr("Forum:") , tr("Bug Tracker:") , tr("IRC:")); QMessageBox::about(this, "About OpenMW-CS", aboutText); } void CSVDoc::View::infoAboutQt() { QMessageBox::aboutQt(this); } void CSVDoc::View::verify() { addSubView (mDocument->verify()); } void CSVDoc::View::addGlobalsSubView() { addSubView (CSMWorld::UniversalId::Type_Globals); } void CSVDoc::View::addGmstsSubView() { addSubView (CSMWorld::UniversalId::Type_Gmsts); } void CSVDoc::View::addSkillsSubView() { addSubView (CSMWorld::UniversalId::Type_Skills); } void CSVDoc::View::addClassesSubView() { addSubView (CSMWorld::UniversalId::Type_Classes); } void CSVDoc::View::addFactionsSubView() { addSubView (CSMWorld::UniversalId::Type_Factions); } void CSVDoc::View::addRacesSubView() { addSubView (CSMWorld::UniversalId::Type_Races); } void CSVDoc::View::addSoundsSubView() { addSubView (CSMWorld::UniversalId::Type_Sounds); } void CSVDoc::View::addScriptsSubView() { addSubView (CSMWorld::UniversalId::Type_Scripts); } void CSVDoc::View::addRegionsSubView() { addSubView (CSMWorld::UniversalId::Type_Regions); } void CSVDoc::View::addBirthsignsSubView() { addSubView (CSMWorld::UniversalId::Type_Birthsigns); } void CSVDoc::View::addSpellsSubView() { addSubView (CSMWorld::UniversalId::Type_Spells); } void CSVDoc::View::addCellsSubView() { addSubView (CSMWorld::UniversalId::Type_Cells); } void CSVDoc::View::addReferenceablesSubView() { addSubView (CSMWorld::UniversalId::Type_Referenceables); } void CSVDoc::View::addReferencesSubView() { addSubView (CSMWorld::UniversalId::Type_References); } void CSVDoc::View::addRegionMapSubView() { addSubView (CSMWorld::UniversalId::Type_RegionMap); } void CSVDoc::View::addFiltersSubView() { addSubView (CSMWorld::UniversalId::Type_Filters); } void CSVDoc::View::addTopicsSubView() { addSubView (CSMWorld::UniversalId::Type_Topics); } void CSVDoc::View::addJournalsSubView() { addSubView (CSMWorld::UniversalId::Type_Journals); } void CSVDoc::View::addTopicInfosSubView() { addSubView (CSMWorld::UniversalId::Type_TopicInfos); } void CSVDoc::View::addJournalInfosSubView() { addSubView (CSMWorld::UniversalId::Type_JournalInfos); } void CSVDoc::View::addEnchantmentsSubView() { addSubView (CSMWorld::UniversalId::Type_Enchantments); } void CSVDoc::View::addBodyPartsSubView() { addSubView (CSMWorld::UniversalId::Type_BodyParts); } void CSVDoc::View::addSoundGensSubView() { addSubView (CSMWorld::UniversalId::Type_SoundGens); } void CSVDoc::View::addMeshesSubView() { addSubView (CSMWorld::UniversalId::Type_Meshes); } void CSVDoc::View::addIconsSubView() { addSubView (CSMWorld::UniversalId::Type_Icons); } void CSVDoc::View::addMusicsSubView() { addSubView (CSMWorld::UniversalId::Type_Musics); } void CSVDoc::View::addSoundsResSubView() { addSubView (CSMWorld::UniversalId::Type_SoundsRes); } void CSVDoc::View::addMagicEffectsSubView() { addSubView (CSMWorld::UniversalId::Type_MagicEffects); } void CSVDoc::View::addTexturesSubView() { addSubView (CSMWorld::UniversalId::Type_Textures); } void CSVDoc::View::addVideosSubView() { addSubView (CSMWorld::UniversalId::Type_Videos); } void CSVDoc::View::addDebugProfilesSubView() { addSubView (CSMWorld::UniversalId::Type_DebugProfiles); } void CSVDoc::View::addRunLogSubView() { addSubView (CSMWorld::UniversalId::Type_RunLog); } void CSVDoc::View::addLandsSubView() { addSubView (CSMWorld::UniversalId::Type_Lands); } void CSVDoc::View::addLandTexturesSubView() { addSubView (CSMWorld::UniversalId::Type_LandTextures); } void CSVDoc::View::addPathgridSubView() { addSubView (CSMWorld::UniversalId::Type_Pathgrids); } void CSVDoc::View::addStartScriptsSubView() { addSubView (CSMWorld::UniversalId::Type_StartScripts); } void CSVDoc::View::addSearchSubView() { addSubView (mDocument->newSearch()); } void CSVDoc::View::addMetaDataSubView() { addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_MetaData, "sys::meta")); } void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); updateActions(); } CSVDoc::Operations *CSVDoc::View::getOperations() const { return mOperations; } void CSVDoc::View::exit() { emit exitApplicationRequest (this); } void CSVDoc::View::resizeViewWidth (int width) { if (width >= 0) resize (width, geometry().height()); } void CSVDoc::View::resizeViewHeight (int height) { if (height >= 0) resize (geometry().width(), height); } void CSVDoc::View::toggleShowStatusBar (bool show) { for (QObject *view : mSubViewWindow.children()) { if (CSVDoc::SubView *subView = dynamic_cast (view)) subView->setStatusBar (show); } } void CSVDoc::View::toggleStatusBar(bool checked) { mShowStatusBar->setChecked(checked); } void CSVDoc::View::loadErrorLog() { addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_LoadErrorLog, 0)); } void CSVDoc::View::run (const std::string& profile, const std::string& startupInstruction) { mDocument->startRunning (profile, startupInstruction); } void CSVDoc::View::stop() { mDocument->stopRunning(); } void CSVDoc::View::closeRequest (SubView *subView) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; if (mSubViews.size()>1 || mViewTotal<=1 || !windows["hide-subview"].isTrue()) { subView->deleteLater(); mSubViews.removeOne (subView); } else if (mViewManager.closeRequest (this)) mViewManager.removeDocAndView (mDocument); } void CSVDoc::View::updateScrollbar() { QRect rect; QWidget *topLevel = QApplication::topLevelAt(pos()); if (topLevel) rect = topLevel->rect(); else rect = this->rect(); int newWidth = 0; for (int i = 0; i < mSubViews.size(); ++i) { newWidth += mSubViews[i]->width(); } int frameWidth = frameGeometry().width() - width(); if ((newWidth+frameWidth) >= rect.width()) mSubViewWindow.setMinimumWidth(newWidth); else mSubViewWindow.setMinimumWidth(0); } void CSVDoc::View::merge() { emit mergeDocument (mDocument); } void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) { QDesktopWidget *dw = QApplication::desktop(); QRect rect; if (isGrowLimit) rect = dw->screenGeometry(this); else rect = QGuiApplication::screens().at(dw->screenNumber(this))->geometry(); if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) { int newWidth = width()+minSubViewWidth; int frameWidth = frameGeometry().width() - width(); if (newWidth+frameWidth <= rect.width()) { resize(newWidth, height()); // WARNING: below code assumes that new subviews are added to the right if (x() > rect.width()-(newWidth+frameWidth)) move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen } else { // full width resize(rect.width()-frameWidth, height()); mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minSubViewWidth); move(0, y()); } } } void CSVDoc::View::createScrollArea() { mScroll = new QScrollArea(this); mScroll->setWidgetResizable(true); mScroll->setWidget(&mSubViewWindow); setCentralWidget(mScroll); } void CSVDoc::View::onRequestFocus (const std::string& id) { if(CSMPrefs::get()["3D Scene Editing"]["open-list-view"].isTrue()) { addReferencesSubView(); emit requestFocus(id); } else { addSubView(CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Reference, id)); } } openmw-openmw-0.48.0/apps/opencs/view/doc/view.hpp000066400000000000000000000140501445372753700220700ustar00rootroot00000000000000#ifndef CSV_DOC_VIEW_H #define CSV_DOC_VIEW_H #include #include #include #include "subviewfactory.hpp" class QAction; class QDockWidget; class QScrollArea; namespace CSMDoc { class Document; } namespace CSMWorld { class UniversalId; } namespace CSMPrefs { class Setting; } namespace CSVDoc { class ViewManager; class Operations; class GlobalDebugProfileMenu; class View : public QMainWindow { Q_OBJECT ViewManager& mViewManager; CSMDoc::Document *mDocument; int mViewIndex; int mViewTotal; QList mSubViews; QAction *mUndo; QAction *mRedo; QAction *mSave; QAction *mVerify; QAction *mShowStatusBar; QAction *mStopDebug; QAction *mMerge; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; QMainWindow mSubViewWindow; GlobalDebugProfileMenu *mGlobalDebugProfileMenu; QScrollArea *mScroll; bool mScrollbarOnly; // not implemented View (const View&); View& operator= (const View&); private: void closeEvent (QCloseEvent *event) override; QAction* createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName); QAction* createMenuEntry(const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName); void setupFileMenu(); void setupEditMenu(); void setupViewMenu(); void setupWorldMenu(); void setupMechanicsMenu(); void setupCharacterMenu(); void setupAssetsMenu(); void setupDebugMenu(); void setupHelpMenu(); void setupUi(); void setupShortcut(const char* name, QAction* action); void updateActions(); void exitApplication(); /// User preference function void resizeViewWidth (int width); /// User preference function void resizeViewHeight (int height); void updateScrollbar(); void updateWidth(bool isGrowLimit, int minSubViewWidth); void createScrollArea(); public: View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews); ///< The ownership of \a document is not transferred to *this. ~View() override; const CSMDoc::Document *getDocument() const; CSMDoc::Document *getDocument(); void setIndex (int viewIndex, int totalViews); void updateDocumentState(); void updateProgress (int current, int max, int type, int threads); void toggleStatusBar(bool checked); Operations *getOperations() const; signals: void newGameRequest(); void newAddonRequest(); void loadDocumentRequest(); void exitApplicationRequest (CSVDoc::View *view); void editSettingsRequest(); void mergeDocument (CSMDoc::Document *document); void requestFocus (const std::string& id); public slots: void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); ///< \param hint Suggested view point (e.g. coordinates in a 3D scene or a line number /// in a script). void abortOperation (int type); void updateTitle(); // called when subviews are added or removed void updateSubViewIndices (SubView *view = nullptr); private slots: void settingChanged (const CSMPrefs::Setting *setting); void undoActionChanged(); void redoActionChanged(); void newView(); void save(); void exit(); static void openHelp(); static void tutorial(); void infoAbout(); void infoAboutQt(); void verify(); void addGlobalsSubView(); void addGmstsSubView(); void addSkillsSubView(); void addClassesSubView(); void addFactionsSubView(); void addRacesSubView(); void addSoundsSubView(); void addScriptsSubView(); void addRegionsSubView(); void addBirthsignsSubView(); void addSpellsSubView(); void addCellsSubView(); void addReferenceablesSubView(); void addReferencesSubView(); void addRegionMapSubView(); void addFiltersSubView(); void addTopicsSubView(); void addJournalsSubView(); void addTopicInfosSubView(); void addJournalInfosSubView(); void addEnchantmentsSubView(); void addBodyPartsSubView(); void addSoundGensSubView(); void addMagicEffectsSubView(); void addMeshesSubView(); void addIconsSubView(); void addMusicsSubView(); void addSoundsResSubView(); void addTexturesSubView(); void addVideosSubView(); void addDebugProfilesSubView(); void addRunLogSubView(); void addLandsSubView(); void addLandTexturesSubView(); void addPathgridSubView(); void addStartScriptsSubView(); void addSearchSubView(); void addMetaDataSubView(); void toggleShowStatusBar (bool show); void loadErrorLog(); void run (const std::string& profile, const std::string& startupInstruction = ""); void stop(); void closeRequest (SubView *subView); void moveScrollBarToEnd(int min, int max); void merge(); void onRequestFocus (const std::string& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/doc/viewmanager.cpp000066400000000000000000000412611445372753700234220ustar00rootroot00000000000000#include "viewmanager.hpp" #include #include #include #include #include #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/prefs/state.hpp" #include "../world/util.hpp" #include "../world/enumdelegate.hpp" #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" #include "../world/idcompletiondelegate.hpp" #include "../world/colordelegate.hpp" #include "view.hpp" void CSVDoc::ViewManager::updateIndices() { std::map > documents; for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) { std::map >::iterator document = documents.find ((*iter)->getDocument()); if (document==documents.end()) document = documents.insert ( std::make_pair ((*iter)->getDocument(), std::make_pair (0, countViews ((*iter)->getDocument())))). first; (*iter)->setIndex (document->second.first++, document->second.second); } } CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) : mDocumentManager (documentManager), mExitOnSaveStateChange(false), mUserWarned(false) { mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection; mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType, new CSVWorld::VarTypeDelegateFactory (ESM::VT_None, ESM::VT_String, ESM::VT_Int, ESM::VT_Float)); mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType, new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); mDelegateFactories->add (CSMWorld::ColumnBase::Display_RecordState, new CSVWorld::RecordStatusDelegateFactory()); mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType, new CSVWorld::IdTypeDelegateFactory()); mDelegateFactories->add (CSMWorld::ColumnBase::Display_Colour, new CSVWorld::ColorDelegateFactory()); std::vector idCompletionColumns = CSMWorld::IdCompletionManager::getDisplayTypes(); for (std::vector::const_iterator current = idCompletionColumns.begin(); current != idCompletionColumns.end(); ++current) { mDelegateFactories->add(*current, new CSVWorld::IdCompletionDelegateFactory()); } struct Mapping { CSMWorld::ColumnBase::Display mDisplay; CSMWorld::Columns::ColumnId mColumnId; bool mAllowNone; }; static const Mapping sMapping[] = { { CSMWorld::ColumnBase::Display_Specialisation, CSMWorld::Columns::ColumnId_Specialisation, false }, { CSMWorld::ColumnBase::Display_Attribute, CSMWorld::Columns::ColumnId_Attribute, true }, { CSMWorld::ColumnBase::Display_SpellType, CSMWorld::Columns::ColumnId_SpellType, false }, { CSMWorld::ColumnBase::Display_ApparatusType, CSMWorld::Columns::ColumnId_ApparatusType, false }, { CSMWorld::ColumnBase::Display_ArmorType, CSMWorld::Columns::ColumnId_ArmorType, false }, { CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false }, { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false }, { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false }, { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false }, { CSMWorld::ColumnBase::Display_EnchantmentType, CSMWorld::Columns::ColumnId_EnchantmentType, false }, { CSMWorld::ColumnBase::Display_BodyPartType, CSMWorld::Columns::ColumnId_BodyPartType, false }, { CSMWorld::ColumnBase::Display_MeshType, CSMWorld::Columns::ColumnId_MeshType, false }, { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, { CSMWorld::ColumnBase::Display_SkillId, CSMWorld::Columns::ColumnId_Skill, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, { CSMWorld::ColumnBase::Display_BookType, CSMWorld::Columns::ColumnId_BookType, false }, { CSMWorld::ColumnBase::Display_BloodType, CSMWorld::Columns::ColumnId_BloodType, false }, { CSMWorld::ColumnBase::Display_EmitterType, CSMWorld::Columns::ColumnId_EmitterType, false }, { CSMWorld::ColumnBase::Display_GenderNpc, CSMWorld::Columns::ColumnId_Gender, false } }; for (std::size_t i=0; iadd (sMapping[i].mDisplay, new CSVWorld::EnumDelegateFactory ( CSMWorld::Columns::getEnums (sMapping[i].mColumnId), sMapping[i].mAllowNone)); connect (&mDocumentManager, SIGNAL (loadRequest (CSMDoc::Document *)), &mLoader, SLOT (add (CSMDoc::Document *))); connect ( &mDocumentManager, SIGNAL (loadingStopped (CSMDoc::Document *, bool, const std::string&)), &mLoader, SLOT (loadingStopped (CSMDoc::Document *, bool, const std::string&))); connect ( &mDocumentManager, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), &mLoader, SLOT (nextStage (CSMDoc::Document *, const std::string&, int))); connect ( &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *, int)), &mLoader, SLOT (nextRecord (CSMDoc::Document *, int))); connect ( &mDocumentManager, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), &mLoader, SLOT (loadMessage (CSMDoc::Document *, const std::string&))); connect ( &mLoader, SIGNAL (cancel (CSMDoc::Document *)), &mDocumentManager, SIGNAL (cancelLoading (CSMDoc::Document *))); connect ( &mLoader, SIGNAL (close (CSMDoc::Document *)), &mDocumentManager, SLOT (removeDocument (CSMDoc::Document *))); } CSVDoc::ViewManager::~ViewManager() { delete mDelegateFactories; for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) delete *iter; } CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) { if (countViews (document)==0) { // new document connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (documentStateChanged (int, CSMDoc::Document *))); connect (document, SIGNAL (progress (int, int, int, int, CSMDoc::Document *)), this, SLOT (progress (int, int, int, int, CSMDoc::Document *))); } View *view = new View (*this, document, countViews (document)+1); mViews.push_back (view); view->toggleStatusBar (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->show(); connect (view, SIGNAL (newGameRequest ()), this, SIGNAL (newGameRequest())); connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest())); connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *))); updateIndices(); return view; } CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document, const CSMWorld::UniversalId& id, const std::string& hint) { View* view = addView(document); view->addSubView(id, hint); return view; } int CSVDoc::ViewManager::countViews (const CSMDoc::Document *document) const { int count = 0; for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) if ((*iter)->getDocument()==document) ++count; return count; } bool CSVDoc::ViewManager::closeRequest (View *view) { std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); bool continueWithClose = false; if (iter!=mViews.end()) { bool last = countViews (view->getDocument())<=1; if (last) continueWithClose = notifySaveOnClose (view); else { (*iter)->deleteLater(); mViews.erase (iter); updateIndices(); } } return continueWithClose; } // NOTE: This method assumes that it is called only if the last document void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) { for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) { // the first match should also be the only match if((*iter)->getDocument() == document) { mDocumentManager.removeDocument(document); (*iter)->deleteLater(); mViews.erase (iter); updateIndices(); return; } } } bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) { bool result = true; CSMDoc::Document *document = view->getDocument(); //notify user of saving in progress if ( (document->getState() & CSMDoc::State_Saving) ) result = showSaveInProgressMessageBox (view); //notify user of unsaved changes and process response else if ( document->getState() & CSMDoc::State_Modified) result = showModifiedDocumentMessageBox (view); return result; } bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view) { emit closeMessageBox(); QMessageBox messageBox(view); CSMDoc::Document *document = view->getDocument(); messageBox.setWindowTitle (QString::fromUtf8(document->getSavePath().filename().string().c_str())); messageBox.setText ("The document has been modified."); messageBox.setInformativeText ("Do you want to save your changes?"); messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); messageBox.setDefaultButton (QMessageBox::Save); messageBox.setWindowModality (Qt::NonModal); messageBox.hide(); messageBox.show(); bool retVal = true; connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close())); connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); mUserWarned = true; int response = messageBox.exec(); mUserWarned = false; switch (response) { case QMessageBox::Save: document->save(); mExitOnSaveStateChange = true; retVal = false; break; case QMessageBox::Discard: disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); break; case QMessageBox::Cancel: //disconnect to prevent unintended view closures disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); retVal = false; break; default: break; } return retVal; } bool CSVDoc::ViewManager::showSaveInProgressMessageBox (CSVDoc::View *view) { QMessageBox messageBox; CSMDoc::Document *document = view->getDocument(); messageBox.setText ("The document is currently being saved."); messageBox.setInformativeText("Do you want to close now and abort saving, or wait until saving has completed?"); QPushButton* waitButton = messageBox.addButton (tr("Wait"), QMessageBox::YesRole); QPushButton* closeButton = messageBox.addButton (tr("Close Now"), QMessageBox::RejectRole); QPushButton* cancelButton = messageBox.addButton (tr("Cancel"), QMessageBox::NoRole); messageBox.setDefaultButton (waitButton); bool retVal = true; //Connections shut down message box if operation ends before user makes a decision. connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close())); //set / clear the user warned flag to indicate whether or not the message box is currently active. mUserWarned = true; messageBox.exec(); mUserWarned = false; //if closed by the warning handler, defaults to the RejectRole button (closeButton) if (messageBox.clickedButton() == waitButton) { //save the View iterator for shutdown after the save operation ends mExitOnSaveStateChange = true; retVal = false; } else if (messageBox.clickedButton() == closeButton) { //disconnect to avoid segmentation fault disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); view->abortOperation(CSMDoc::State_Saving); mExitOnSaveStateChange = true; } else if (messageBox.clickedButton() == cancelButton) { //abort shutdown, allow save to complete //disconnection to prevent unintended view closures mExitOnSaveStateChange = false; disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); retVal = false; } return retVal; } void CSVDoc::ViewManager::documentStateChanged (int state, CSMDoc::Document *document) { for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) if ((*iter)->getDocument()==document) (*iter)->updateDocumentState(); } void CSVDoc::ViewManager::progress (int current, int max, int type, int threads, CSMDoc::Document *document) { for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) if ((*iter)->getDocument()==document) (*iter)->updateProgress (current, max, type, threads); } void CSVDoc::ViewManager::onExitWarningHandler (int state, CSMDoc::Document *document) { if ( !(state & CSMDoc::State_Saving) ) { //if the user is being warned (message box is active), shut down the message box, //as there is no save operation currently running if ( mUserWarned ) emit closeMessageBox(); //otherwise, the user has closed the message box before the save operation ended. //exit the application else if (mExitOnSaveStateChange) QApplication::instance()->exit(); } } bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) { if(!notifySaveOnClose(view)) return false; else { // don't bother closing views or updating indicies, but remove from mViews CSMDoc::Document * document = view->getDocument(); std::vector remainingViews; std::vector::const_iterator iter = mViews.begin(); for (; iter!=mViews.end(); ++iter) { if(document == (*iter)->getDocument()) (*iter)->setVisible(false); else remainingViews.push_back(*iter); } mDocumentManager.removeDocument(document); mViews = remainingViews; } return true; } void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) { if(!removeDocument(view)) // close the current document first return; while(!mViews.empty()) // attempt to close all other documents { mViews.back()->activateWindow(); mViews.back()->raise(); // raise the window to alert the user if(!removeDocument(mViews.back())) return; } // Editor exits (via a signal) when the last document is deleted } openmw-openmw-0.48.0/apps/opencs/view/doc/viewmanager.hpp000066400000000000000000000044361445372753700234320ustar00rootroot00000000000000#ifndef CSV_DOC_VIEWMANAGER_H #define CSV_DOC_VIEWMANAGER_H #include #include #include "loader.hpp" namespace CSMDoc { class Document; class DocumentManager; } namespace CSVWorld { class CommandDelegateFactoryCollection; } namespace CSMWorld { class UniversalId; } namespace CSVDoc { class View; class ViewManager : public QObject { Q_OBJECT CSMDoc::DocumentManager& mDocumentManager; std::vector mViews; CSVWorld::CommandDelegateFactoryCollection *mDelegateFactories; bool mExitOnSaveStateChange; bool mUserWarned; Loader mLoader; // not implemented ViewManager (const ViewManager&); ViewManager& operator= (const ViewManager&); void updateIndices(); bool notifySaveOnClose (View *view = nullptr); bool showModifiedDocumentMessageBox (View *view); bool showSaveInProgressMessageBox (View *view); bool removeDocument(View *view); public: ViewManager (CSMDoc::DocumentManager& documentManager); ~ViewManager() override; View *addView (CSMDoc::Document *document); ///< The ownership of the returned view is not transferred. View *addView (CSMDoc::Document *document, const CSMWorld::UniversalId& id, const std::string& hint); int countViews (const CSMDoc::Document *document) const; ///< Return number of views for \a document. bool closeRequest (View *view); void removeDocAndView (CSMDoc::Document *document); signals: void newGameRequest(); void newAddonRequest(); void loadDocumentRequest(); void closeMessageBox(); void editSettingsRequest(); void mergeDocument (CSMDoc::Document *document); public slots: void exitApplication (CSVDoc::View *view); private slots: void documentStateChanged (int state, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document); void onExitWarningHandler(int state, CSMDoc::Document* document); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/filter/000077500000000000000000000000001445372753700211255ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/filter/editwidget.cpp000066400000000000000000000146761445372753700240000ustar00rootroot00000000000000#include "editwidget.hpp" #include #include #include #include #include #include #include #include "../../model/world/data.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/columns.hpp" #include "../../model/prefs/shortcut.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) : QLineEdit (parent), mParser (data), mIsEmpty(true) { mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); const CSMWorld::IdTableBase *model = static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), Qt::QueuedConnection); connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), Qt::QueuedConnection); connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), Qt::QueuedConnection); mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); mHelpAction = new QAction (tr ("Help"), this); connect (mHelpAction, SIGNAL (triggered()), this, SLOT (openHelp())); mHelpAction->setIcon(QIcon(":/info.png")); addAction (mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); setText("!string(\"ID\", \".*\")"); } void CSVFilter::EditWidget::textChanged (const QString& text) { //no need to parse and apply filter if it was empty and now is empty too. //e.g. - we modifiing content of filter with already opened some other (big) tables. if (text.length() == 0){ if (mIsEmpty) return; else mIsEmpty = true; }else mIsEmpty = false; if (mParser.parse (text.toUtf8().constData())) { setPalette (mPalette); emit filterChanged (mParser.getFilter()); } else { QPalette palette (mPalette); palette.setColor (QPalette::Text, Qt::red); setPalette (palette); /// \todo improve error reporting; mark only the faulty part } } void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int i = topLeft.column(); i <= bottomRight.column(); ++i) if (i != mStateColumnIndex && i != mDescColumnIndex) textChanged (text()); } void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) { textChanged (text()); } void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) { textChanged (text()); } void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, Qt::DropAction action) { const unsigned count = filterSource.size(); bool multipleElements = false; switch (count) //setting multipleElements; { case 0: //empty return; //nothing to do here case 1: //only single multipleElements = false; break; default: multipleElements = true; break; } Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); QString oldContent (text()); bool replaceMode = false; std::string orAnd; switch (key) //setting replaceMode and string used to glue expressions { case Qt::ShiftModifier: orAnd = "!or("; replaceMode = false; break; case Qt::ControlModifier: orAnd = "!and("; replaceMode = false; break; default: replaceMode = true; break; } if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode { replaceMode = true; } if (!replaceMode) { oldContent.remove ('!'); } std::stringstream ss; if (multipleElements) { if (replaceMode) { ss<<"!or("; } else { ss << orAnd << oldContent.toUtf8().constData() << ','; } for (unsigned i = 0; i < count; ++i) { ss<4) { clear(); insert (QString::fromUtf8(ss.str().c_str())); } } std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const { const unsigned columns = seekedString.second.size(); bool multipleColumns = false; switch (columns) { case 0: //empty return ""; //no column to filter case 1: //one column to look for multipleColumns = false; break; default: multipleColumns = true; break; } std::stringstream ss; if (multipleColumns) { ss<<"or("; for (unsigned i = 0; i < columns; ++i) { ss<<"string("<<'"'<addAction(mHelpAction); menu->exec(event->globalPos()); delete menu; } void CSVFilter::EditWidget::openHelp() { Misc::HelpViewer::openHelp("manuals/openmw-cs/record-filters.html"); } openmw-openmw-0.48.0/apps/opencs/view/filter/editwidget.hpp000066400000000000000000000027331445372753700237740ustar00rootroot00000000000000#ifndef CSV_FILTER_EDITWIDGET_H #define CSV_FILTER_EDITWIDGET_H #include #include #include "../../model/filter/parser.hpp" #include "../../model/filter/node.hpp" class QModelIndex; namespace CSMWorld { class Data; } namespace CSVFilter { class EditWidget : public QLineEdit { Q_OBJECT CSMFilter::Parser mParser; QPalette mPalette; bool mIsEmpty; int mStateColumnIndex; int mDescColumnIndex; QAction *mHelpAction; public: EditWidget (CSMWorld::Data& data, QWidget *parent = nullptr); void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); signals: void filterChanged (std::shared_ptr filter); private: std::string generateFilter(std::pair >& seekedString) const; void contextMenuEvent (QContextMenuEvent *event) override; private slots: void textChanged (const QString& text); void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void filterRowsRemoved (const QModelIndex& parent, int start, int end); void filterRowsInserted (const QModelIndex& parent, int start, int end); static void openHelp(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/filter/filterbox.cpp000066400000000000000000000032321445372753700236270ustar00rootroot00000000000000#include "filterbox.hpp" #include #include #include "recordfilterbox.hpp" #include CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) : QWidget (parent) { QHBoxLayout *layout = new QHBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); mRecordFilterBox = new RecordFilterBox (data, this); layout->addWidget (mRecordFilterBox); setLayout (layout); connect (mRecordFilterBox, SIGNAL (filterChanged (std::shared_ptr)), this, SIGNAL (recordFilterChanged (std::shared_ptr))); setAcceptDrops(true); } void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) { mRecordFilterBox->setFilter (filter); } void CSVFilter::FilterBox::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; std::vector universalIdData = mime->getData(); emit recordDropped(universalIdData, event->proposedAction()); } void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) { event->acceptProposedAction(); } void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) { event->accept(); } void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, Qt::DropAction action) { mRecordFilterBox->createFilterRequest(filterSource, action); } openmw-openmw-0.48.0/apps/opencs/view/filter/filterbox.hpp000066400000000000000000000021711445372753700236350ustar00rootroot00000000000000#ifndef CSV_FILTER_FILTERBOX_H #define CSV_FILTER_FILTERBOX_H #include #include #include "../../model/filter/node.hpp" #include "../../model/world/universalid.hpp" namespace CSMWorld { class Data; } namespace CSVFilter { class RecordFilterBox; class FilterBox : public QWidget { Q_OBJECT RecordFilterBox *mRecordFilterBox; public: FilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); void setRecordFilter (const std::string& filter); void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); private: void dragEnterEvent (QDragEnterEvent* event) override; void dropEvent (QDropEvent* event) override; void dragMoveEvent(QDragMoveEvent *event) override; signals: void recordFilterChanged (std::shared_ptr filter); void recordDropped (std::vector& types, Qt::DropAction action); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/filter/recordfilterbox.cpp000066400000000000000000000021201445372753700250210ustar00rootroot00000000000000#include "recordfilterbox.hpp" #include #include #include "editwidget.hpp" CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) : QWidget (parent) { QHBoxLayout *layout = new QHBoxLayout (this); layout->setContentsMargins (0, 6, 5, 0); QLabel *label = new QLabel("Record Filter", this); label->setIndent(2); layout->addWidget (label); mEdit = new EditWidget (data, this); layout->addWidget (mEdit); setLayout (layout); connect ( mEdit, SIGNAL (filterChanged (std::shared_ptr)), this, SIGNAL (filterChanged (std::shared_ptr))); } void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) { mEdit->clear(); mEdit->setText (QString::fromUtf8 (filter.c_str())); } void CSVFilter::RecordFilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, Qt::DropAction action) { mEdit->createFilterRequest(filterSource, action); } openmw-openmw-0.48.0/apps/opencs/view/filter/recordfilterbox.hpp000066400000000000000000000015001445372753700250270ustar00rootroot00000000000000#ifndef CSV_FILTER_RECORDFILTERBOX_H #define CSV_FILTER_RECORDFILTERBOX_H #include #include "../../model/filter/node.hpp" namespace CSMWorld { class Data; } namespace CSVFilter { class EditWidget; class RecordFilterBox : public QWidget { Q_OBJECT EditWidget *mEdit; public: RecordFilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); void setFilter (const std::string& filter); void useFilterRequest(const std::string& idOfFilter); void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); signals: void filterChanged (std::shared_ptr filter); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/prefs/000077500000000000000000000000001445372753700207575ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/prefs/contextmenulist.cpp000066400000000000000000000020761445372753700247350ustar00rootroot00000000000000#include "contextmenulist.hpp" #include #include #include #include "../../model/prefs/state.hpp" CSVPrefs::ContextMenuList::ContextMenuList(QWidget* parent) :QListWidget(parent) { } void CSVPrefs::ContextMenuList::contextMenuEvent(QContextMenuEvent* e) { QMenu* menu = new QMenu(); menu->addAction("Reset category to default", this, SLOT(resetCategory())); menu->addAction("Reset all to default", this, SLOT(resetAll())); menu->exec(e->globalPos()); delete menu; } void CSVPrefs::ContextMenuList::mousePressEvent(QMouseEvent* e) { // enable all buttons except right click // This means that when right-clicking to enable the // context menu, the page doesn't switch at the same time. if (!(e->buttons() & Qt::RightButton)) { QListWidget::mousePressEvent(e); } } void CSVPrefs::ContextMenuList::resetCategory() { CSMPrefs::State::get().resetCategory(currentItem()->text().toStdString()); } void CSVPrefs::ContextMenuList::resetAll() { CSMPrefs::State::get().resetAll(); } openmw-openmw-0.48.0/apps/opencs/view/prefs/contextmenulist.hpp000066400000000000000000000011241445372753700247330ustar00rootroot00000000000000#ifndef CSV_PREFS_CONTEXTMENULIST_H #define CSV_PREFS_CONTEXTMENULIST_H #include class QContextMenuEvent; class QMouseEvent; namespace CSVPrefs { class ContextMenuList : public QListWidget { Q_OBJECT public: ContextMenuList(QWidget* parent = nullptr); protected: void contextMenuEvent(QContextMenuEvent* e) override; void mousePressEvent(QMouseEvent* e) override; private slots: void resetCategory(); void resetAll(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/prefs/dialogue.cpp000066400000000000000000000071761445372753700232670ustar00rootroot00000000000000#include "dialogue.hpp" #include #include #include #include #include #include #include "../../model/prefs/state.hpp" #include "page.hpp" #include "keybindingpage.hpp" #include "contextmenulist.hpp" void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) { CSVPrefs::ContextMenuList* list = new CSVPrefs::ContextMenuList (main); list->setMinimumWidth (50); list->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); list->setSelectionBehavior (QAbstractItemView::SelectItems); main->addWidget (list); QFontMetrics metrics (QApplication::font(list)); int maxWidth = 1; for (CSMPrefs::State::Iterator iter = CSMPrefs::get().begin(); iter!=CSMPrefs::get().end(); ++iter) { QString label = QString::fromUtf8 (iter->second.getKey().c_str()); maxWidth = std::max (maxWidth, metrics.horizontalAdvance (label)); list->addItem (label); } list->setMaximumWidth (maxWidth + 10); connect (list, SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)), this, SLOT (selectionChanged (QListWidgetItem *, QListWidgetItem *))); } void CSVPrefs::Dialogue::buildContentArea (QSplitter *main) { mContent = new QStackedWidget (main); mContent->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); main->addWidget (mContent); } CSVPrefs::PageBase *CSVPrefs::Dialogue::makePage (const std::string& key) { // special case page code goes here if (key == "Key Bindings") return new KeyBindingPage(CSMPrefs::get()[key], mContent); else return new Page (CSMPrefs::get()[key], mContent); } CSVPrefs::Dialogue::Dialogue() { setWindowTitle ("User Settings"); setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setMinimumSize (600, 400); QSplitter *main = new QSplitter (this); setCentralWidget (main); buildCategorySelector (main); buildContentArea (main); } CSVPrefs::Dialogue::~Dialogue() { try { if (isVisible()) CSMPrefs::State::get().save(); } catch(const std::exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) { QMainWindow::closeEvent (event); CSMPrefs::State::get().save(); } void CSVPrefs::Dialogue::show() { if (QWidget *active = QApplication::activeWindow()) { // place at the centre of the window with focus QSize size = active->size(); move (active->geometry().x()+(size.width() - frameGeometry().width())/2, active->geometry().y()+(size.height() - frameGeometry().height())/2); } else { QRect scr = QGuiApplication::primaryScreen()->geometry(); // otherwise place at the centre of the screen QPoint screenCenter = scr.center(); move (screenCenter - QPoint(frameGeometry().width()/2, frameGeometry().height()/2)); } QWidget::show(); } void CSVPrefs::Dialogue::selectionChanged (QListWidgetItem *current, QListWidgetItem *previous) { if (current) { std::string key = current->text().toUtf8().data(); for (int i=0; icount(); ++i) { PageBase& page = dynamic_cast (*mContent->widget (i)); if (page.getCategory().getKey()==key) { mContent->setCurrentIndex (i); return; } } PageBase *page = makePage (key); mContent->setCurrentIndex (mContent->addWidget (page)); } } openmw-openmw-0.48.0/apps/opencs/view/prefs/dialogue.hpp000066400000000000000000000015071445372753700232640ustar00rootroot00000000000000#ifndef CSV_PREFS_DIALOGUE_H #define CSV_PREFS_DIALOGUE_H #include class QSplitter; class QListWidget; class QStackedWidget; class QListWidgetItem; namespace CSVPrefs { class PageBase; class Dialogue : public QMainWindow { Q_OBJECT QStackedWidget *mContent; private: void buildCategorySelector (QSplitter *main); void buildContentArea (QSplitter *main); PageBase *makePage (const std::string& key); public: Dialogue(); ~Dialogue() override; protected: void closeEvent (QCloseEvent *event) override; public slots: void show(); private slots: void selectionChanged (QListWidgetItem *current, QListWidgetItem *previous); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/prefs/keybindingpage.cpp000066400000000000000000000064111445372753700244450ustar00rootroot00000000000000#include "keybindingpage.hpp" #include #include #include #include #include #include #include "../../model/prefs/setting.hpp" #include "../../model/prefs/category.hpp" #include "../../model/prefs/state.hpp" namespace CSVPrefs { KeyBindingPage::KeyBindingPage(CSMPrefs::Category& category, QWidget* parent) : PageBase(category, parent) , mStackedLayout(nullptr) , mPageLayout(nullptr) , mPageSelector(nullptr) { // Need one widget for scroll area QWidget* topWidget = new QWidget(); QVBoxLayout* topLayout = new QVBoxLayout(topWidget); // Allows switching between "pages" QWidget* stackedWidget = new QWidget(); mStackedLayout = new QStackedLayout(stackedWidget); mPageSelector = new QComboBox(); connect(mPageSelector, SIGNAL(currentIndexChanged(int)), mStackedLayout, SLOT(setCurrentIndex(int))); QFrame* lineSeparator = new QFrame(topWidget); lineSeparator->setFrameShape(QFrame::HLine); lineSeparator->setFrameShadow(QFrame::Sunken); // Reset key bindings button QPushButton* resetButton = new QPushButton ("Reset to Defaults", topWidget); connect(resetButton, SIGNAL(clicked()), this, SLOT(resetKeyBindings())); topLayout->addWidget(mPageSelector); topLayout->addWidget(stackedWidget); topLayout->addWidget(lineSeparator); topLayout->addWidget(resetButton); topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); // Add each option for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) addSetting (*iter); setWidgetResizable(true); setWidget(topWidget); } void KeyBindingPage::addSetting(CSMPrefs::Setting *setting) { std::pair widgets = setting->makeWidgets (this); if (widgets.first) { // Label, Option widgets assert(mPageLayout); int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.first, next, 0); mPageLayout->addWidget(widgets.second, next, 1); } else if (widgets.second) { // Wide single widget assert(mPageLayout); int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.second, next, 0, 1, 2); } else { if (setting->getLabel().empty()) { // Insert empty space assert(mPageLayout); int next = mPageLayout->rowCount(); mPageLayout->addWidget(new QWidget(), next, 0); } else { // Create new page QWidget* pageWidget = new QWidget(); mPageLayout = new QGridLayout(pageWidget); mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); mStackedLayout->addWidget(pageWidget); mPageSelector->addItem(QString::fromUtf8(setting->getLabel().c_str())); } } } void KeyBindingPage::resetKeyBindings() { CSMPrefs::State::get().resetCategory("Key Bindings"); } } openmw-openmw-0.48.0/apps/opencs/view/prefs/keybindingpage.hpp000066400000000000000000000012221445372753700244450ustar00rootroot00000000000000#ifndef CSV_PREFS_KEYBINDINGPAGE_H #define CSV_PREFS_KEYBINDINGPAGE_H #include "pagebase.hpp" class QComboBox; class QGridLayout; class QStackedLayout; namespace CSMPrefs { class Setting; } namespace CSVPrefs { class KeyBindingPage : public PageBase { Q_OBJECT public: KeyBindingPage(CSMPrefs::Category& category, QWidget* parent); void addSetting(CSMPrefs::Setting* setting); private: QStackedLayout* mStackedLayout; QGridLayout* mPageLayout; QComboBox* mPageSelector; private slots: void resetKeyBindings(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/prefs/page.cpp000066400000000000000000000016711445372753700224040ustar00rootroot00000000000000 #include "page.hpp" #include #include "../../model/prefs/setting.hpp" #include "../../model/prefs/category.hpp" CSVPrefs::Page::Page (CSMPrefs::Category& category, QWidget *parent) : PageBase (category, parent) { QWidget *widget = new QWidget (parent); mGrid = new QGridLayout (widget); for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) addSetting (*iter); setWidget (widget); } void CSVPrefs::Page::addSetting (CSMPrefs::Setting *setting) { std::pair widgets = setting->makeWidgets (this); int next = mGrid->rowCount(); if (widgets.first) { mGrid->addWidget (widgets.first, next, 0); mGrid->addWidget (widgets.second, next, 1); } else if (widgets.second) { mGrid->addWidget (widgets.second, next, 0, 1, 2); } else { mGrid->addWidget (new QWidget (this), next, 0); } } openmw-openmw-0.48.0/apps/opencs/view/prefs/page.hpp000066400000000000000000000006351445372753700224100ustar00rootroot00000000000000#ifndef CSV_PREFS_PAGE_H #define CSV_PREFS_PAGE_H #include "pagebase.hpp" class QGridLayout; namespace CSMPrefs { class Setting; } namespace CSVPrefs { class Page : public PageBase { Q_OBJECT QGridLayout *mGrid; public: Page (CSMPrefs::Category& category, QWidget *parent); void addSetting (CSMPrefs::Setting *setting); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/prefs/pagebase.cpp000066400000000000000000000015241445372753700232340ustar00rootroot00000000000000 #include "pagebase.hpp" #include #include #include "../../model/prefs/category.hpp" #include "../../model/prefs/state.hpp" CSVPrefs::PageBase::PageBase (CSMPrefs::Category& category, QWidget *parent) : QScrollArea (parent), mCategory (category) {} CSMPrefs::Category& CSVPrefs::PageBase::getCategory() { return mCategory; } void CSVPrefs::PageBase::contextMenuEvent(QContextMenuEvent* e) { QMenu* menu = new QMenu(); menu->addAction("Reset category to default", this, SLOT(resetCategory())); menu->addAction("Reset all to default", this, SLOT(resetAll())); menu->exec(e->globalPos()); delete menu; } void CSVPrefs::PageBase::resetCategory() { CSMPrefs::State::get().resetCategory(getCategory().getKey()); } void CSVPrefs::PageBase::resetAll() { CSMPrefs::State::get().resetAll(); } openmw-openmw-0.48.0/apps/opencs/view/prefs/pagebase.hpp000066400000000000000000000011441445372753700232370ustar00rootroot00000000000000#ifndef CSV_PREFS_PAGEBASE_H #define CSV_PREFS_PAGEBASE_H #include class QContextMenuEvent; namespace CSMPrefs { class Category; } namespace CSVPrefs { class PageBase : public QScrollArea { Q_OBJECT CSMPrefs::Category& mCategory; public: PageBase (CSMPrefs::Category& category, QWidget *parent); CSMPrefs::Category& getCategory(); protected: void contextMenuEvent(QContextMenuEvent*) override; private slots: void resetCategory(); void resetAll(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/000077500000000000000000000000001445372753700211175ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/render/actor.cpp000066400000000000000000000073671445372753700227500ustar00rootroot00000000000000#include "actor.hpp" #include #include #include #include #include #include #include #include #include "../../model/world/data.hpp" namespace CSVRender { const std::string Actor::MeshPrefix = "meshes\\"; Actor::Actor(const std::string& id, CSMWorld::Data& data) : mId(id) , mData(data) , mBaseNode(new osg::Group()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); connect(mData.getActorAdapter(), SIGNAL(actorChanged(const std::string&)), this, SLOT(handleActorChanged(const std::string&))); } osg::Group* Actor::getBaseNode() { return mBaseNode; } void Actor::update() { mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); // Load skeleton std::string skeletonModel = mActorData->getSkeleton(); skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); loadSkeleton(skeletonModel); if (!mActorData->isCreature()) { // Get rid of the extra attachments SceneUtil::CleanObjectRootVisitor cleanVisitor; mSkeleton->accept(cleanVisitor); cleanVisitor.remove(); // Attach parts to skeleton loadBodyParts(); } else { SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; mSkeleton->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); } // Post setup mSkeleton->markDirty(); mSkeleton->setActive(SceneUtil::Skeleton::Active); } void Actor::handleActorChanged(const std::string& refId) { if (mId == refId) { update(); } } void Actor::loadSkeleton(const std::string& model) { auto sceneMgr = mData.getResourceSystem()->getSceneManager(); osg::ref_ptr temp = sceneMgr->getInstance(model); mSkeleton = dynamic_cast(temp.get()); if (!mSkeleton) { mSkeleton = new SceneUtil::Skeleton(); mSkeleton->addChild(temp); } mBaseNode->addChild(mSkeleton); // Map bone names to bones mNodeMap.clear(); SceneUtil::NodeMapVisitor nmVisitor(mNodeMap); mSkeleton->accept(nmVisitor); } void Actor::loadBodyParts() { for (int i = 0; i < ESM::PRT_Count; ++i) { auto type = (ESM::PartReferenceType) i; const std::string_view partId = mActorData->getPart(type); attachBodyPart(type, getBodyPartMesh(partId)); } } void Actor::attachBodyPart(ESM::PartReferenceType type, const std::string& mesh) { auto sceneMgr = mData.getResourceSystem()->getSceneManager(); // Attach to skeleton std::string boneName = ESM::getBoneName(type); auto node = mNodeMap.find(boneName); if (!mesh.empty() && node != mNodeMap.end()) { auto instance = sceneMgr->getInstance(mesh); SceneUtil::attach(instance, mSkeleton, boneName, node->second, sceneMgr); } } std::string Actor::getBodyPartMesh(std::string_view bodyPartId) { const auto& bodyParts = mData.getBodyParts(); int index = bodyParts.searchId(bodyPartId); if (index != -1 && !bodyParts.getRecord(index).isDeleted()) return MeshPrefix + bodyParts.getRecord(index).get().mModel; else return ""; } } openmw-openmw-0.48.0/apps/opencs/view/render/actor.hpp000066400000000000000000000030601445372753700227370ustar00rootroot00000000000000#ifndef OPENCS_VIEW_RENDER_ACTOR_H #define OPENCS_VIEW_RENDER_ACTOR_H #include #include #include #include #include #include #include "../../model/world/actoradapter.hpp" namespace osg { class Group; } namespace CSMWorld { class Data; } namespace SceneUtil { class Skeleton; } namespace CSVRender { /// Handles loading an npc or creature class Actor : public QObject { Q_OBJECT public: /// Creates an actor. /// \param id The referenceable id /// \param type The record type /// \param data The data store Actor(const std::string& id, CSMWorld::Data& data); /// Retrieves the base node that meshes are attached to osg::Group* getBaseNode(); /// (Re)creates the npc or creature renderable void update(); private slots: void handleActorChanged(const std::string& refId); private: void loadSkeleton(const std::string& model); void loadBodyParts(); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); std::string getBodyPartMesh(std::string_view bodyPartId); static const std::string MeshPrefix; std::string mId; CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; SceneUtil::NodeMapVisitor::NodeMap mNodeMap; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/brushdraw.cpp000066400000000000000000000272431445372753700236340ustar00rootroot00000000000000#include "brushdraw.hpp" #include #include #include #include #include #include "../../model/world/cellcoordinates.hpp" #include "../widget/brushshapes.hpp" #include "mask.hpp" CSVRender::BrushDraw::BrushDraw(osg::ref_ptr parentNode, bool textureMode) : mParentNode(parentNode), mTextureMode(textureMode) { mBrushDrawNode = new osg::Group(); mGeometry = new osg::Geometry(); mBrushDrawNode->addChild(mGeometry); mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mParentNode->addChild(mBrushDrawNode); if (mTextureMode) mLandSizeFactor = static_cast(ESM::Land::REAL_SIZE) / static_cast(ESM::Land::LAND_TEXTURE_SIZE); else mLandSizeFactor = static_cast(ESM::Land::REAL_SIZE) / static_cast(ESM::Land::LAND_SIZE - 1); } CSVRender::BrushDraw::~BrushDraw() { mBrushDrawNode->removeChild(mGeometry); mParentNode->removeChild(mBrushDrawNode); } float CSVRender::BrushDraw::getIntersectionHeight (const osg::Vec3d& point) { osg::Vec3d start = point; osg::Vec3d end = point; start.z() = std::numeric_limits::max(); end.z() = std::numeric_limits::lowest(); osg::Vec3d direction = end - start; // Get intersection osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( osgUtil::Intersector::MODEL, start, end) ); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMask(Mask_Terrain); mParentNode->accept(visitor); for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); it != intersector->getIntersections().end(); ++it) { osgUtil::LineSegmentIntersector::Intersection intersection = *it; // reject back-facing polygons if (direction * intersection.getWorldIntersectNormal() > 0) { continue; } return intersection.getWorldIntersectPoint().z(); } return 0.0f; } void CSVRender::BrushDraw::buildPointGeometry(const osg::Vec3d& point) { osg::ref_ptr geom (new osg::Geometry()); osg::ref_ptr vertices (new osg::Vec3Array()); osg::ref_ptr colors (new osg::Vec4Array()); const float brushOutlineHeight (1.0f); const float crossHeadSize (8.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); vertices->push_back(osg::Vec3d( point.x() - crossHeadSize, point.y() - crossHeadSize, getIntersectionHeight(osg::Vec3d( point.x() - crossHeadSize, point.y() - crossHeadSize, point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); vertices->push_back(osg::Vec3d( point.x() + crossHeadSize, point.y() + crossHeadSize, getIntersectionHeight(osg::Vec3d( point.x() + crossHeadSize, point.y() + crossHeadSize, point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); vertices->push_back(osg::Vec3d( point.x() + crossHeadSize, point.y() - crossHeadSize, getIntersectionHeight(osg::Vec3d( point.x() + crossHeadSize, point.y() - crossHeadSize, point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); vertices->push_back(osg::Vec3d( point.x() - crossHeadSize, point.y() + crossHeadSize, getIntersectionHeight(osg::Vec3d( point.x() - crossHeadSize, point.y() + crossHeadSize, point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); geom->setVertexArray(vertices); geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 4)); mGeometry = geom; } void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::Vec3d& point) { osg::ref_ptr geom (new osg::Geometry()); osg::ref_ptr vertices (new osg::Vec3Array()); osg::ref_ptr colors (new osg::Vec4Array()); const float brushOutlineHeight (1.0f); float diameter = radius * 2; int resolution = static_cast(2.f * diameter / mLandSizeFactor); //half a vertex resolution float resAdjustedLandSizeFactor = mLandSizeFactor / 2; //128 if (resolution > 128) // limit accuracy for performance { resolution = 128; resAdjustedLandSizeFactor = diameter / resolution; } osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < resolution; i++) { int step = i * resAdjustedLandSizeFactor; int step2 = (i + 1) * resAdjustedLandSizeFactor; osg::Vec3d upHorizontalLinePoint1( point.x() - radius + step, point.y() - radius, getIntersectionHeight(osg::Vec3d( point.x() - radius + step, point.y() - radius, point.z())) + brushOutlineHeight); osg::Vec3d upHorizontalLinePoint2( point.x() - radius + step2, point.y() - radius, getIntersectionHeight(osg::Vec3d( point.x() - radius + step2, point.y() - radius, point.z())) + brushOutlineHeight); osg::Vec3d upVerticalLinePoint1( point.x() - radius, point.y() - radius + step, getIntersectionHeight(osg::Vec3d( point.x() - radius, point.y() - radius + step, point.z())) + brushOutlineHeight); osg::Vec3d upVerticalLinePoint2( point.x() - radius, point.y() - radius + step2, getIntersectionHeight(osg::Vec3d( point.x() - radius, point.y() - radius + step2, point.z())) + brushOutlineHeight); osg::Vec3d downHorizontalLinePoint1( point.x() + radius - step, point.y() + radius, getIntersectionHeight(osg::Vec3d( point.x() + radius - step, point.y() + radius, point.z())) + brushOutlineHeight); osg::Vec3d downHorizontalLinePoint2( point.x() + radius - step2, point.y() + radius, getIntersectionHeight(osg::Vec3d( point.x() + radius - step2, point.y() + radius, point.z())) + brushOutlineHeight); osg::Vec3d downVerticalLinePoint1( point.x() + radius, point.y() + radius - step, getIntersectionHeight(osg::Vec3d( point.x() + radius, point.y() + radius - step, point.z())) + brushOutlineHeight); osg::Vec3d downVerticalLinePoint2( point.x() + radius, point.y() + radius - step2, getIntersectionHeight(osg::Vec3d( point.x() + radius, point.y() + radius - step2, point.z())) + brushOutlineHeight); vertices->push_back(upHorizontalLinePoint1); colors->push_back(lineColor); vertices->push_back(upHorizontalLinePoint2); colors->push_back(lineColor); vertices->push_back(upVerticalLinePoint1); colors->push_back(lineColor); vertices->push_back(upVerticalLinePoint2); colors->push_back(lineColor); vertices->push_back(downHorizontalLinePoint1); colors->push_back(lineColor); vertices->push_back(downHorizontalLinePoint2); colors->push_back(lineColor); vertices->push_back(downVerticalLinePoint1); colors->push_back(lineColor); vertices->push_back(downVerticalLinePoint2); colors->push_back(lineColor); } geom->setVertexArray(vertices); geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, resolution * 8)); mGeometry = geom; } void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::Vec3d& point) { osg::ref_ptr geom (new osg::Geometry()); osg::ref_ptr vertices (new osg::Vec3Array()); osg::ref_ptr colors (new osg::Vec4Array()); const int amountOfPoints = 128; const float step ((osg::PI * 2.0f) / static_cast(amountOfPoints)); const float brushOutlineHeight (1.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < amountOfPoints + 2; i++) { float angle (i * step); vertices->push_back(osg::Vec3d( point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), getIntersectionHeight(osg::Vec3d( point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); angle = static_cast(i + 1) * step; vertices->push_back(osg::Vec3d( point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), getIntersectionHeight(osg::Vec3d( point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), point.z()) ) + brushOutlineHeight)); colors->push_back(lineColor); } geom->setVertexArray(vertices); geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, amountOfPoints * 2)); mGeometry = geom; } void CSVRender::BrushDraw::buildCustomGeometry(const float& radius, const osg::Vec3d& point) { // Not implemented } void CSVRender::BrushDraw::update(osg::Vec3d point, int brushSize, CSVWidget::BrushShape toolShape) { if (mBrushDrawNode->containsNode(mGeometry)) mBrushDrawNode->removeChild(mGeometry); float radius = (mLandSizeFactor * brushSize) / 2; osg::Vec3d snapToGridPoint = point; if (mTextureMode) { std::pair snapToGridXY = CSMWorld::CellCoordinates::toTextureCoords(point); float offsetToMiddle = mLandSizeFactor * 0.5f; snapToGridPoint = osg::Vec3d( CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(snapToGridXY.first) + offsetToMiddle, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(snapToGridXY.second) + offsetToMiddle, point.z()); } else { std::pair snapToGridXY = CSMWorld::CellCoordinates::toVertexCoords(point); snapToGridPoint = osg::Vec3d( CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.first), CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.second), point.z()); } switch (toolShape) { case (CSVWidget::BrushShape_Point) : buildPointGeometry(snapToGridPoint); break; case (CSVWidget::BrushShape_Square) : buildSquareGeometry(radius, snapToGridPoint); break; case (CSVWidget::BrushShape_Circle) : buildCircleGeometry(radius, snapToGridPoint); break; case (CSVWidget::BrushShape_Custom) : buildSquareGeometry(1, snapToGridPoint); //buildCustomGeometry break; } mGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); mBrushDrawNode->addChild(mGeometry); } void CSVRender::BrushDraw::hide() { if (mBrushDrawNode->containsNode(mGeometry)) mBrushDrawNode->removeChild(mGeometry); } openmw-openmw-0.48.0/apps/opencs/view/render/brushdraw.hpp000066400000000000000000000021351445372753700236320ustar00rootroot00000000000000#ifndef CSV_RENDER_BRUSHDRAW_H #define CSV_RENDER_BRUSHDRAW_H #include #include #include #include "../widget/brushshapes.hpp" namespace CSVRender { class BrushDraw { public: BrushDraw(osg::ref_ptr parentNode, bool textureMode = false); ~BrushDraw(); void update(osg::Vec3d point, int brushSize, CSVWidget::BrushShape toolShape); void hide(); private: void buildPointGeometry(const osg::Vec3d& point); void buildSquareGeometry(const float& radius, const osg::Vec3d& point); void buildCircleGeometry(const float& radius, const osg::Vec3d& point); void buildCustomGeometry(const float& radius, const osg::Vec3d& point); float getIntersectionHeight (const osg::Vec3d& point); osg::ref_ptr mParentNode; osg::ref_ptr mBrushDrawNode; osg::ref_ptr mGeometry; bool mTextureMode; float mLandSizeFactor; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cameracontroller.cpp000066400000000000000000000530011445372753700251560ustar00rootroot00000000000000#include "cameracontroller.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/prefs/shortcut.hpp" #include "scenewidget.hpp" namespace CSVRender { /* Camera Controller */ const osg::Vec3d CameraController::WorldUp = osg::Vec3d(0, 0, 1); const osg::Vec3d CameraController::LocalUp = osg::Vec3d(0, 1, 0); const osg::Vec3d CameraController::LocalLeft = osg::Vec3d(1, 0, 0); const osg::Vec3d CameraController::LocalForward = osg::Vec3d(0, 0, 1); CameraController::CameraController(QObject* parent) : QObject(parent) , mActive(false) , mInverted(false) , mCameraSensitivity(1/650.f) , mSecondaryMoveMult(50) , mWheelMoveMult(8) , mCamera(nullptr) { } CameraController::~CameraController() { } bool CameraController::isActive() const { return mActive; } osg::Camera* CameraController::getCamera() const { return mCamera; } double CameraController::getCameraSensitivity() const { return mCameraSensitivity; } bool CameraController::getInverted() const { return mInverted; } double CameraController::getSecondaryMovementMultiplier() const { return mSecondaryMoveMult; } double CameraController::getWheelMovementMultiplier() const { return mWheelMoveMult; } void CameraController::setCamera(osg::Camera* camera) { bool wasActive = mActive; mCamera = camera; mActive = (mCamera != nullptr); if (mActive != wasActive) { for (std::vector::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it) { CSMPrefs::Shortcut* shortcut = *it; shortcut->enable(mActive); } } } void CameraController::setCameraSensitivity(double value) { mCameraSensitivity = value; } void CameraController::setInverted(bool value) { mInverted = value; } void CameraController::setSecondaryMovementMultiplier(double value) { mSecondaryMoveMult = value; } void CameraController::setWheelMovementMultiplier(double value) { mWheelMoveMult = value; } void CameraController::setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up) { // Find World bounds osg::ComputeBoundsVisitor boundsVisitor; osg::BoundingBox& boundingBox = boundsVisitor.getBoundingBox(); boundsVisitor.setTraversalMask(mask); root->accept(boundsVisitor); if (!boundingBox.valid()) { // Try again without any mask boundsVisitor.reset(); boundsVisitor.setTraversalMask(~0u); root->accept(boundsVisitor); // Last resort, set a default if (!boundingBox.valid()) { boundingBox.set(-1, -1, -1, 1, 1, 1); } } // Calculate a good starting position osg::Vec3d minBounds = boundingBox.corner(0) - boundingBox.center(); osg::Vec3d maxBounds = boundingBox.corner(7) - boundingBox.center(); osg::Vec3d camOffset = up * maxBounds > 0 ? maxBounds : minBounds; camOffset *= 2; osg::Vec3d eye = camOffset + boundingBox.center(); osg::Vec3d center = boundingBox.center(); getCamera()->setViewMatrixAsLookAt(eye, center, up); } void CameraController::addShortcut(CSMPrefs::Shortcut* shortcut) { mShortcuts.push_back(shortcut); } /* Free Camera Controller */ FreeCameraController::FreeCameraController(QWidget* widget) : CameraController(widget) , mLockUpright(false) , mModified(false) , mNaviPrimary(false) , mNaviSecondary(false) , mFast(false) , mFastAlternate(false) , mLeft(false) , mRight(false) , mForward(false) , mBackward(false) , mRollLeft(false) , mRollRight(false) , mUp(LocalUp) , mLinSpeed(1000) , mRotSpeed(osg::PI / 2) , mSpeedMult(8) { CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); naviPrimaryShortcut->enable(false); connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); addShortcut(naviPrimaryShortcut); CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); naviSecondaryShortcut->enable(false); connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); addShortcut(naviSecondaryShortcut); CSMPrefs::Shortcut* forwardShortcut = new CSMPrefs::Shortcut("free-forward", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget); forwardShortcut->enable(false); connect(forwardShortcut, SIGNAL(activated(bool)), this, SLOT(forward(bool))); connect(forwardShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); addShortcut(forwardShortcut); CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", widget); leftShortcut->enable(false); connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); addShortcut(leftShortcut); CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", widget); backShortcut->enable(false); connect(backShortcut, SIGNAL(activated(bool)), this, SLOT(backward(bool))); addShortcut(backShortcut); CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", widget); rightShortcut->enable(false); connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); addShortcut(rightShortcut); CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", widget); rollLeftShortcut->enable(false); connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); addShortcut(rollLeftShortcut); CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", widget); rollRightShortcut->enable(false); connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); addShortcut(rollRightShortcut); CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", widget); speedModeShortcut->enable(false); connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); addShortcut(speedModeShortcut); } double FreeCameraController::getLinearSpeed() const { return mLinSpeed; } double FreeCameraController::getRotationalSpeed() const { return mRotSpeed; } double FreeCameraController::getSpeedMultiplier() const { return mSpeedMult; } void FreeCameraController::setLinearSpeed(double value) { mLinSpeed = value; } void FreeCameraController::setRotationalSpeed(double value) { mRotSpeed = value; } void FreeCameraController::setSpeedMultiplier(double value) { mSpeedMult = value; } void FreeCameraController::fixUpAxis(const osg::Vec3d& up) { mLockUpright = true; mUp = up; mModified = true; } void FreeCameraController::unfixUpAxis() { mLockUpright = false; } void FreeCameraController::handleMouseMoveEvent(int x, int y) { if (!isActive()) return; if (mNaviPrimary) { double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); yaw(x * scalar); pitch(y * scalar); } else if (mNaviSecondary) { osg::Vec3d movement; movement += LocalLeft * -x * getSecondaryMovementMultiplier(); movement += LocalUp * y * getSecondaryMovementMultiplier(); translate(movement); } } void FreeCameraController::handleMouseScrollEvent(int x) { if (!isActive()) return; translate(LocalForward * x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); } void FreeCameraController::update(double dt) { if (!isActive()) return; double linDist = mLinSpeed * dt; double rotDist = mRotSpeed * dt; if (mFast ^ mFastAlternate) linDist *= mSpeedMult; if (mLeft) translate(LocalLeft * linDist); if (mRight) translate(LocalLeft * -linDist); if (mForward) translate(LocalForward * linDist); if (mBackward) translate(LocalForward * -linDist); if (!mLockUpright) { if (mRollLeft) roll(-rotDist); if (mRollRight) roll(rotDist); } else if(mModified) { stabilize(); mModified = false; } // Normalize the matrix to counter drift getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); } void FreeCameraController::yaw(double value) { getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalUp); mModified = true; } void FreeCameraController::pitch(double value) { const double Constraint = osg::PI / 2 - 0.1; if (mLockUpright) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d forward = center - eye; osg::Vec3d left = up ^ forward; double pitchAngle = std::acos(up * mUp); if ((mUp ^ up) * left < 0) pitchAngle *= -1; if (std::abs(pitchAngle + value) > Constraint) value = (pitchAngle > 0 ? 1 : -1) * Constraint - pitchAngle; } getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalLeft); mModified = true; } void FreeCameraController::roll(double value) { getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); mModified = true; } void FreeCameraController::translate(const osg::Vec3d& offset) { getCamera()->getViewMatrix() *= osg::Matrixd::translate(offset); mModified = true; } void FreeCameraController::stabilize() { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); getCamera()->setViewMatrixAsLookAt(eye, center, mUp); } void FreeCameraController::naviPrimary(bool active) { mNaviPrimary = active; } void FreeCameraController::naviSecondary(bool active) { mNaviSecondary = active; } void FreeCameraController::forward(bool active) { mForward = active; } void FreeCameraController::left(bool active) { mLeft = active; } void FreeCameraController::backward(bool active) { mBackward = active; } void FreeCameraController::right(bool active) { mRight = active; } void FreeCameraController::rollLeft(bool active) { mRollLeft = active; } void FreeCameraController::rollRight(bool active) { mRollRight = active; } void FreeCameraController::alternateFast(bool active) { mFastAlternate = active; } void FreeCameraController::swapSpeedMode() { mFast = !mFast; } /* Orbit Camera Controller */ OrbitCameraController::OrbitCameraController(QWidget* widget) : CameraController(widget) , mInitialized(false) , mNaviPrimary(false) , mNaviSecondary(false) , mFast(false) , mFastAlternate(false) , mLeft(false) , mRight(false) , mUp(false) , mDown(false) , mRollLeft(false) , mRollRight(false) , mPickingMask(~0u) , mCenter(0,0,0) , mDistance(0) , mOrbitSpeed(osg::PI / 4) , mOrbitSpeedMult(4) , mConstRoll(false) { CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); naviPrimaryShortcut->enable(false); connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); addShortcut(naviPrimaryShortcut); CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); naviSecondaryShortcut->enable(false); connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); addShortcut(naviSecondaryShortcut); CSMPrefs::Shortcut* upShortcut = new CSMPrefs::Shortcut("orbit-up", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget); upShortcut->enable(false); connect(upShortcut, SIGNAL(activated(bool)), this, SLOT(up(bool))); connect(upShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); addShortcut(upShortcut); CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", widget); leftShortcut->enable(false); connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); addShortcut(leftShortcut); CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", widget); downShortcut->enable(false); connect(downShortcut, SIGNAL(activated(bool)), this, SLOT(down(bool))); addShortcut(downShortcut); CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", widget); rightShortcut->enable(false); connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); addShortcut(rightShortcut); CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", widget); rollLeftShortcut->enable(false); connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); addShortcut(rollLeftShortcut); CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", widget); rollRightShortcut->enable(false); connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); addShortcut(rollRightShortcut); CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", widget); speedModeShortcut->enable(false); connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); addShortcut(speedModeShortcut); } osg::Vec3d OrbitCameraController::getCenter() const { return mCenter; } double OrbitCameraController::getOrbitSpeed() const { return mOrbitSpeed; } double OrbitCameraController::getOrbitSpeedMultiplier() const { return mOrbitSpeedMult; } unsigned int OrbitCameraController::getPickingMask() const { return mPickingMask; } void OrbitCameraController::setCenter(const osg::Vec3d& value) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); mCenter = value; mDistance = (eye - mCenter).length(); getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); mInitialized = true; } void OrbitCameraController::setOrbitSpeed(double value) { mOrbitSpeed = value; } void OrbitCameraController::setOrbitSpeedMultiplier(double value) { mOrbitSpeedMult = value; } void OrbitCameraController::setPickingMask(unsigned int value) { mPickingMask = value; } void OrbitCameraController::handleMouseMoveEvent(int x, int y) { if (!isActive()) return; if (!mInitialized) initialize(); if (mNaviPrimary) { double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); rotateHorizontal(x * scalar); rotateVertical(-y * scalar); } else if (mNaviSecondary) { osg::Vec3d movement; movement += LocalLeft * x * getSecondaryMovementMultiplier(); movement += LocalUp * -y * getSecondaryMovementMultiplier(); translate(movement); } } void OrbitCameraController::handleMouseScrollEvent(int x) { if (!isActive()) return; zoom(-x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); } void OrbitCameraController::update(double dt) { if (!isActive()) return; if (!mInitialized) initialize(); double rotDist = mOrbitSpeed * dt; if (mFast ^ mFastAlternate) rotDist *= mOrbitSpeedMult; if (mLeft) rotateHorizontal(-rotDist); if (mRight) rotateHorizontal(rotDist); if (mUp) rotateVertical(rotDist); if (mDown) rotateVertical(-rotDist); if (mRollLeft) roll(-rotDist); if (mRollRight) roll(rotDist); // Normalize the matrix to counter drift getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); } void OrbitCameraController::reset() { mInitialized = false; } void OrbitCameraController::initialize() { static const int DefaultStartDistance = 10000.f; // Try to intelligently pick focus object osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMask(mPickingMask); getCamera()->accept(visitor); osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up, DefaultStartDistance); if (intersector->getIntersections().begin() != intersector->getIntersections().end()) { mCenter = intersector->getIntersections().begin()->getWorldIntersectPoint(); mDistance = (eye - mCenter).length(); } else { mCenter = center; mDistance = DefaultStartDistance; } mInitialized = true; } void OrbitCameraController::setConstRoll(bool enabled) { mConstRoll = enabled; } void OrbitCameraController::rotateHorizontal(double value) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d absoluteUp = osg::Vec3(0,0,1); osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; if (mConstRoll) up = rotation * up; getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } void OrbitCameraController::rotateVertical(double value) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d forward = center - eye; osg::Vec3d axis = up ^ forward; osg::Quat rotation = osg::Quat(value,axis); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; if (mConstRoll) up = rotation * up; getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); } void OrbitCameraController::roll(double value) { getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); } void OrbitCameraController::translate(const osg::Vec3d& offset) { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d newOffset = getCamera()->getViewMatrix().getRotate().inverse() * offset; mCenter += newOffset; eye += newOffset; getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); } void OrbitCameraController::zoom(double value) { mDistance = std::max(10., mDistance + value); osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up, 1.f); osg::Vec3d offset = (eye - center) * mDistance; getCamera()->setViewMatrixAsLookAt(mCenter + offset, mCenter, up); } void OrbitCameraController::naviPrimary(bool active) { mNaviPrimary = active; } void OrbitCameraController::naviSecondary(bool active) { mNaviSecondary = active; } void OrbitCameraController::up(bool active) { mUp = active; } void OrbitCameraController::left(bool active) { mLeft = active; } void OrbitCameraController::down(bool active) { mDown = active; } void OrbitCameraController::right(bool active) { mRight = active; } void OrbitCameraController::rollLeft(bool active) { if (isActive()) mRollLeft = active; } void OrbitCameraController::rollRight(bool active) { mRollRight = active; } void OrbitCameraController::alternateFast(bool active) { mFastAlternate = active; } void OrbitCameraController::swapSpeedMode() { mFast = !mFast; } } openmw-openmw-0.48.0/apps/opencs/view/render/cameracontroller.hpp000066400000000000000000000126061445372753700251710ustar00rootroot00000000000000#ifndef OPENCS_VIEW_CAMERACONTROLLER_H #define OPENCS_VIEW_CAMERACONTROLLER_H #include #include #include #include #include namespace osg { class Camera; class Group; } namespace CSMPrefs { class Shortcut; } namespace CSVRender { class SceneWidget; class CameraController : public QObject { Q_OBJECT public: static const osg::Vec3d WorldUp; static const osg::Vec3d LocalUp; static const osg::Vec3d LocalLeft; static const osg::Vec3d LocalForward; CameraController(QObject* parent); virtual ~CameraController(); bool isActive() const; osg::Camera* getCamera() const; double getCameraSensitivity() const; bool getInverted() const; double getSecondaryMovementMultiplier() const; double getWheelMovementMultiplier() const; void setCamera(osg::Camera*); void setCameraSensitivity(double value); void setInverted(bool value); void setSecondaryMovementMultiplier(double value); void setWheelMovementMultiplier(double value); // moves the camera to an intelligent position void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); virtual void handleMouseMoveEvent(int x, int y) = 0; virtual void handleMouseScrollEvent(int x) = 0; virtual void update(double dt) = 0; protected: void addShortcut(CSMPrefs::Shortcut* shortcut); private: bool mActive, mInverted; double mCameraSensitivity; double mSecondaryMoveMult; double mWheelMoveMult; osg::Camera* mCamera; std::vector mShortcuts; }; class FreeCameraController : public CameraController { Q_OBJECT public: FreeCameraController(QWidget* parent); double getLinearSpeed() const; double getRotationalSpeed() const; double getSpeedMultiplier() const; void setLinearSpeed(double value); void setRotationalSpeed(double value); void setSpeedMultiplier(double value); void fixUpAxis(const osg::Vec3d& up); void unfixUpAxis(); void handleMouseMoveEvent(int x, int y) override; void handleMouseScrollEvent(int x) override; void update(double dt) override; private: void yaw(double value); void pitch(double value); void roll(double value); void translate(const osg::Vec3d& offset); void stabilize(); bool mLockUpright, mModified; bool mNaviPrimary, mNaviSecondary; bool mFast, mFastAlternate; bool mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; osg::Vec3d mUp; double mLinSpeed; double mRotSpeed; double mSpeedMult; private slots: void naviPrimary(bool active); void naviSecondary(bool active); void forward(bool active); void left(bool active); void backward(bool active); void right(bool active); void rollLeft(bool active); void rollRight(bool active); void alternateFast(bool active); void swapSpeedMode(); }; class OrbitCameraController : public CameraController { Q_OBJECT public: OrbitCameraController(QWidget* parent); osg::Vec3d getCenter() const; double getOrbitSpeed() const; double getOrbitSpeedMultiplier() const; unsigned int getPickingMask() const; void setCenter(const osg::Vec3d& center); void setOrbitSpeed(double value); void setOrbitSpeedMultiplier(double value); void setPickingMask(unsigned int value); void handleMouseMoveEvent(int x, int y) override; void handleMouseScrollEvent(int x) override; void update(double dt) override; /// \brief Flag controller to be re-initialized. void reset(); void setConstRoll(bool enable); private: void initialize(); void rotateHorizontal(double value); void rotateVertical(double value); void roll(double value); void translate(const osg::Vec3d& offset); void zoom(double value); bool mInitialized; bool mNaviPrimary, mNaviSecondary; bool mFast, mFastAlternate; bool mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; unsigned int mPickingMask; osg::Vec3d mCenter; double mDistance; double mOrbitSpeed; double mOrbitSpeedMult; bool mConstRoll; private slots: void naviPrimary(bool active); void naviSecondary(bool active); void up(bool active); void left(bool active); void down(bool active); void right(bool active); void rollLeft(bool active); void rollRight(bool active); void alternateFast(bool active); void swapSpeedMode(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cell.cpp000066400000000000000000000433501445372753700225470ustar00rootroot00000000000000#include "cell.hpp" #include #include #include #include #include #include #include "../../model/world/idtable.hpp" #include "cellwater.hpp" #include "cellborder.hpp" #include "cellarrow.hpp" #include "cellmarker.hpp" #include "mask.hpp" #include "pathgrid.hpp" #include "terrainstorage.hpp" #include "object.hpp" #include "instancedragmodes.hpp" namespace CSVRender { class CellNodeContainer : public osg::Referenced { public: CellNodeContainer(Cell* cell) : mCell(cell) {} Cell* getCell(){ return mCell; } private: Cell* mCell; }; class CellNodeCallback : public osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { traverse(node, nv); CellNodeContainer* container = static_cast(node->getUserData()); container->getCell()->updateLand(); } }; } bool CSVRender::Cell::removeObject (const std::string& id) { std::map::iterator iter = mObjects.find (Misc::StringUtils::lowerCase (id)); if (iter==mObjects.end()) return false; removeObject (iter); return true; } std::map::iterator CSVRender::Cell::removeObject ( std::map::iterator iter) { delete iter->second; mObjects.erase (iter++); return iter; } bool CSVRender::Cell::addObjects (int start, int end) { bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); CSMWorld::RecordBase::State state = collection.getRecord (i).mState; if (cell==mId && state!=CSMWorld::RecordBase::State_Deleted) { std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); auto object = std::make_unique(mData, mCellNode, id, false); if (mSubModeElementMask & Mask_Reference) object->setSubMode (mSubMode); mObjects.insert (std::make_pair (id, object.release())); modified = true; } } return modified; } void CSVRender::Cell::updateLand() { if (!mUpdateLand || mLandDeleted) return; mUpdateLand = false; // Cell is deleted if (mDeleted) { unloadLand(); return; } // Setup land if available const CSMWorld::IdCollection& land = mData.getLand(); int landIndex = land.searchId(mId); if (landIndex != -1 && !land.getRecord(mId).isDeleted()) { const ESM::Land& esmLand = land.getRecord(mId).get(); if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { if (mTerrain) { mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); mTerrain->clearAssociatedCaches(); } else { mTerrain = std::make_unique(mCellNode, mCellNode, mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain); } mTerrain->loadCell(esmLand.mX, esmLand.mY); if (!mCellBorder) mCellBorder = std::make_unique(mCellNode, mCoordinates); mCellBorder->buildShape(esmLand); return; } } // No land data unloadLand(); } void CSVRender::Cell::unloadLand() { if (mTerrain) mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); if (mCellBorder) mCellBorder.reset(); } CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) : mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), mSubModeElementMask (0), mUpdateLand(true), mLandDeleted(false) { std::pair result = CSMWorld::CellCoordinates::fromId (id); mTerrainStorage = new TerrainStorage(mData); if (result.second) mCoordinates = result.first; mCellNode = new osg::Group; mCellNode->setUserData(new CellNodeContainer(this)); mCellNode->setUpdateCallback(new CellNodeCallback); rootNode->addChild(mCellNode); setCellMarker(); if (!mDeleted) { CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); int rows = references.rowCount(); addObjects (0, rows-1); updateLand(); mPathgrid = std::make_unique(mData, mCellNode, mId, mCoordinates); mCellWater = std::make_unique(mData, mCellNode, mId, mCoordinates); } } CSVRender::Cell::~Cell() { for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) delete iter->second; mCellNode->getParent(0)->removeChild(mCellNode); } CSVRender::Pathgrid* CSVRender::Cell::getPathgrid() const { return mPathgrid.get(); } bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { bool modified = false; for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->referenceableDataChanged (topLeft, bottomRight)) modified = true; return modified; } bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; bool modified = false; for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) modified = true; return modified; } bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mDeleted) return false; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); // list IDs in cell std::map ids; // id, deleted state for (int i=topLeft.row(); i<=bottomRight.row(); ++i) { std::string cell = Misc::StringUtils::lowerCase (references.data ( references.index (i, cellColumn)).toString().toUtf8().constData()); if (cell==mId) { std::string id = Misc::StringUtils::lowerCase (references.data ( references.index (i, idColumn)).toString().toUtf8().constData()); int state = references.data (references.index (i, stateColumn)).toInt(); ids.insert (std::make_pair (id, state==CSMWorld::RecordBase::State_Deleted)); } } // perform update and remove where needed bool modified = false; std::map::iterator iter = mObjects.begin(); while (iter!=mObjects.end()) { if (iter->second->referenceDataChanged (topLeft, bottomRight)) modified = true; std::map::iterator iter2 = ids.find (iter->first); if (iter2!=ids.end()) { bool deleted = iter2->second; ids.erase (iter2); if (deleted) { iter = removeObject (iter); modified = true; continue; } } ++iter; } // add new objects for (std::map::iterator mapIter (ids.begin()); mapIter!=ids.end(); ++mapIter) { if (!mapIter->second) { mObjects.insert (std::make_pair ( mapIter->first, new Object (mData, mCellNode, mapIter->first, false))); modified = true; } } return modified; } bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; if (mDeleted) return false; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); bool modified = false; for (int row = start; row<=end; ++row) if (removeObject (references.data ( references.index (row, idColumn)).toString().toUtf8().constData())) modified = true; return modified; } bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; if (mDeleted) return false; return addObjects (start, end); } void CSVRender::Cell::setAlteredHeight(int inCellX, int inCellY, float height) { mTerrainStorage->setAlteredHeight(inCellX, inCellY, height); mUpdateLand = true; } float CSVRender::Cell::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) { return mTerrainStorage->getSumOfAlteredAndTrueHeight(cellX, cellY, inCellX, inCellY); } float* CSVRender::Cell::getAlteredHeight(int inCellX, int inCellY) { return mTerrainStorage->getAlteredHeight(inCellX, inCellY); } void CSVRender::Cell::resetAlteredHeights() { mTerrainStorage->resetHeights(); mUpdateLand = true; } void CSVRender::Cell::pathgridModified() { if (mPathgrid) mPathgrid->recreateGeometry(); } void CSVRender::Cell::pathgridRemoved() { if (mPathgrid) mPathgrid->removeGeometry(); } void CSVRender::Cell::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { mUpdateLand = true; } void CSVRender::Cell::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) { mLandDeleted = true; unloadLand(); } void CSVRender::Cell::landAdded (const QModelIndex& parent, int start, int end) { mUpdateLand = true; mLandDeleted = false; } void CSVRender::Cell::landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { mUpdateLand = true; } void CSVRender::Cell::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) { mUpdateLand = true; } void CSVRender::Cell::landTextureAdded (const QModelIndex& parent, int start, int end) { mUpdateLand = true; } void CSVRender::Cell::reloadAssets() { for (std::map::const_iterator iter (mObjects.begin()); iter != mObjects.end(); ++iter) { iter->second->reloadAssets(); } if (mTerrain) { mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); mTerrain->loadCell(mCoordinates.getX(), mCoordinates.getY()); } if (mCellWater) mCellWater->reloadAssets(); } void CSVRender::Cell::setSelection (int elementMask, Selection mode) { if (elementMask & Mask_Reference) { for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) { bool selected = false; switch (mode) { case Selection_Clear: selected = false; break; case Selection_All: selected = true; break; case Selection_Invert: selected = !iter->second->getSelected(); break; } iter->second->setSelected (selected); } } if (mPathgrid && elementMask & Mask_Pathgrid) { // Only one pathgrid may be selected, so some operations will only have an effect // if the pathgrid is already focused switch (mode) { case Selection_Clear: mPathgrid->clearSelected(); break; case Selection_All: if (mPathgrid->isSelected()) mPathgrid->selectAll(); break; case Selection_Invert: if (mPathgrid->isSelected()) mPathgrid->invertSelected(); break; } } } void CSVRender::Cell::selectAllWithSameParentId (int elementMask) { std::set ids; for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) { if (iter->second->getSelected()) ids.insert (iter->second->getReferenceableId()); } for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) { if (!iter->second->getSelected() && ids.find (iter->second->getReferenceableId())!=ids.end()) { iter->second->setSelected (true); } } } void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) { if (dragMode == DragMode_Select_Only || dragMode == DragMode_Select_Add) object->setSelected(true); else if (dragMode == DragMode_Select_Remove) object->setSelected(false); else if (dragMode == DragMode_Select_Invert) object->setSelected (!object->getSelected()); } void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { for (auto& object : mObjects) { if (dragMode == DragMode_Select_Only) object.second->setSelected (false); if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) || ( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] )) { if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) || ( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] )) { if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) || ( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] )) handleSelectDrag(object.second, dragMode); } } } } void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) { for (auto& object : mObjects) { if (dragMode == DragMode_Select_Only) object.second->setSelected (false); float distanceFromObject = (point - object.second->getPosition().asVec3()).length(); if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode); } } void CSVRender::Cell::setCellArrows (int mask) { for (int i=0; i<4; ++i) { CellArrow::Direction direction = static_cast (1<(mCellNode, direction, mCoordinates); else mCellArrows[i].reset (nullptr); } } } void CSVRender::Cell::setCellMarker() { bool cellExists = false; bool isInteriorCell = false; int cellIndex = mData.getCells().searchId(mId); if (cellIndex > -1) { const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); cellExists = !cellRecord.isDeleted(); isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; } if (!isInteriorCell) { mCellMarker = std::make_unique(mCellNode, mCoordinates, cellExists); } } CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const { return mCoordinates; } bool CSVRender::Cell::isDeleted() const { return mDeleted; } std::vector > CSVRender::Cell::getSelection (unsigned int elementMask) const { std::vector > result; if (elementMask & Mask_Reference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid->isSelected()) result.emplace_back(mPathgrid->getTag()); return result; } std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const { std::vector > result; if (elementMask & Mask_Reference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) if (iter->second->isEdited()) result.push_back (iter->second->getTag()); return result; } void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) { mSubMode = subMode; mSubModeElementMask = elementMask; if (elementMask & Mask_Reference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->setSubMode (subMode); } void CSVRender::Cell::reset (unsigned int elementMask) { if (elementMask & Mask_Reference) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->reset(); if (mPathgrid && elementMask & Mask_Pathgrid) mPathgrid->resetIndicators(); } openmw-openmw-0.48.0/apps/opencs/view/render/cell.hpp000066400000000000000000000135141445372753700225530ustar00rootroot00000000000000#ifndef OPENCS_VIEW_CELL_H #define OPENCS_VIEW_CELL_H #include #include #include #include #include #include "../../model/world/cellcoordinates.hpp" #include "terrainstorage.hpp" #include "instancedragmodes.hpp" class QModelIndex; namespace osg { class Group; class Geometry; } namespace CSMWorld { class Data; } namespace Terrain { class TerrainGrid; } namespace CSVRender { class CellWater; class Pathgrid; class TagBase; class Object; class CellArrow; class CellBorder; class CellMarker; class CellWater; class Cell { CSMWorld::Data& mData; std::string mId; osg::ref_ptr mCellNode; std::map mObjects; std::unique_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::unique_ptr mCellArrows[4]; std::unique_ptr mCellMarker; std::unique_ptr mCellBorder; std::unique_ptr mCellWater; std::unique_ptr mPathgrid; bool mDeleted; int mSubMode; unsigned int mSubModeElementMask; bool mUpdateLand, mLandDeleted; TerrainStorage *mTerrainStorage; /// Ignored if cell does not have an object with the given ID. /// /// \return Was the object deleted? bool removeObject (const std::string& id); // Remove object and return iterator to next object. std::map::iterator removeObject ( std::map::iterator iter); /// Add objects from reference table that are within this cell. /// /// \return Have any objects been added? bool addObjects (int start, int end); void updateLand(); void unloadLand(); public: enum Selection { Selection_Clear, Selection_All, Selection_Invert }; public: /// \note Deleted covers both cells that are deleted and cells that don't exist in /// the first place. Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted = false); ~Cell(); /// \note Returns the pathgrid representation which will exist as long as the cell exists Pathgrid* getPathgrid() const; /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); void setAlteredHeight(int inCellX, int inCellY, float height); float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); float* getAlteredHeight(int inCellX, int inCellY); void resetAlteredHeights(); void pathgridModified(); void pathgridRemoved(); void landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); void landAdded (const QModelIndex& parent, int start, int end); void landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); void landTextureAdded (const QModelIndex& parent, int start, int end); void reloadAssets(); void setSelection (int elementMask, Selection mode); // Select everything that references the same ID as at least one of the elements // already selected void selectAllWithSameParentId (int elementMask); void handleSelectDrag(Object* object, DragMode dragMode); void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); void setCellArrows (int mask); /// \brief Set marker for this cell. void setCellMarker(); /// Returns 0, 0 in case of an unpaged cell. CSMWorld::CellCoordinates getCoordinates() const; bool isDeleted() const; std::vector > getSelection (unsigned int elementMask) const; std::vector > getEdited (unsigned int elementMask) const; void setSubMode (int subMode, unsigned int elementMask); /// Erase all overrides and restore the visual representation of the cell to its /// true state. void reset (unsigned int elementMask); friend class CellNodeCallback; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cellarrow.cpp000066400000000000000000000126131445372753700236200ustar00rootroot00000000000000#include "cellarrow.hpp" #include #include #include #include #include "../../model/prefs/state.hpp" #include #include "mask.hpp" CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) : TagBase (Mask_CellArrow), mArrow (arrow) {} CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const { return mArrow; } QString CSVRender::CellArrowTag::getToolTip(bool hideBasics, const WorldspaceHitResult& /*hit*/) const { QString text ("Direction: "); switch (mArrow->getDirection()) { case CellArrow::Direction_North: text += "North"; break; case CellArrow::Direction_West: text += "West"; break; case CellArrow::Direction_South: text += "South"; break; case CellArrow::Direction_East: text += "East"; break; } if (!hideBasics) { text += "

" "Modify which cells are shown" "

  • {scene-edit-primary}: Add cell in given direction
  • " "
  • {scene-edit-secondary}: Add cell and remove old cell
  • " "
  • {scene-select-primary}: Add cells in given direction
  • " "
  • {scene-select-secondary}: Add cells and remove old cells
  • " "
  • {scene-load-cam-cell}: Load cell where camera is located
  • " "
  • {scene-load-cam-eastcell}: Load cell to east
  • " "
  • {scene-load-cam-northcell}: Load cell to north
  • " "
  • {scene-load-cam-westcell}: Load cell to west
  • " "
  • {scene-load-cam-southcell}: Load cell to south
  • " "
"; } return CSMPrefs::State::get().getShortcutManager().processToolTip(text); } void CSVRender::CellArrow::adjustTransform() { // position const int cellSize = Constants::CellSizeInUnits; const int offset = cellSize / 2 + 600; int x = mCoordinates.getX()*cellSize + cellSize/2; int y = mCoordinates.getY()*cellSize + cellSize/2; float xr = 0; float yr = 0; float zr = 0; float angle = osg::DegreesToRadians (90.0f); switch (mDirection) { case Direction_North: y += offset; xr = -angle; zr = angle; break; case Direction_West: x -= offset; yr = -angle; break; case Direction_South: y -= offset; xr = angle; zr = angle; break; case Direction_East: x += offset; yr = angle; break; }; mBaseNode->setPosition (osg::Vec3f (x, y, 0)); // orientation osg::Quat xr2 (xr, osg::Vec3f (1,0,0)); osg::Quat yr2 (yr, osg::Vec3f (0,1,0)); osg::Quat zr2 (zr, osg::Vec3f (0,0,1)); mBaseNode->setAttitude (zr2*yr2*xr2); } void CSVRender::CellArrow::buildShape() { osg::ref_ptr geometry (new osg::Geometry); const int arrowWidth = 2700; const int arrowLength = 1350; const int arrowHeight = 300; osg::Vec3Array *vertices = new osg::Vec3Array; for (int i2=0; i2<2; ++i2) for (int i=0; i<2; ++i) { float height = i ? -arrowHeight/2 : arrowHeight/2; vertices->push_back (osg::Vec3f (height, -arrowWidth/2, 0)); vertices->push_back (osg::Vec3f (height, arrowWidth/2, 0)); vertices->push_back (osg::Vec3f (height, 0, arrowLength)); } geometry->setVertexArray (vertices); osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); // top primitives->push_back (0); primitives->push_back (1); primitives->push_back (2); // bottom primitives->push_back (5); primitives->push_back (4); primitives->push_back (3); // back primitives->push_back (3+6); primitives->push_back (4+6); primitives->push_back (1+6); primitives->push_back (3+6); primitives->push_back (1+6); primitives->push_back (0+6); // sides primitives->push_back (0+6); primitives->push_back (2+6); primitives->push_back (5+6); primitives->push_back (0+6); primitives->push_back (5+6); primitives->push_back (3+6); primitives->push_back (4+6); primitives->push_back (5+6); primitives->push_back (2+6); primitives->push_back (4+6); primitives->push_back (2+6); primitives->push_back (1+6); geometry->addPrimitiveSet (primitives); osg::Vec4Array *colours = new osg::Vec4Array; for (int i=0; i<6; ++i) colours->push_back (osg::Vec4f (0.11f, 0.6f, 0.95f, 1.0f)); for (int i=0; i<6; ++i) colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); mBaseNode->addChild (geometry); } CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, const CSMWorld::CellCoordinates& coordinates) : mDirection (direction), mParentNode (cellNode), mCoordinates (coordinates) { mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setUserData (new CellArrowTag (this)); mParentNode->addChild (mBaseNode); mBaseNode->setNodeMask (Mask_CellArrow); adjustTransform(); buildShape(); } CSVRender::CellArrow::~CellArrow() { mParentNode->removeChild (mBaseNode); } CSMWorld::CellCoordinates CSVRender::CellArrow::getCoordinates() const { return mCoordinates; } CSVRender::CellArrow::Direction CSVRender::CellArrow::getDirection() const { return mDirection; } openmw-openmw-0.48.0/apps/opencs/view/render/cellarrow.hpp000066400000000000000000000027611445372753700236300ustar00rootroot00000000000000#ifndef OPENCS_VIEW_CELLARROW_H #define OPENCS_VIEW_CELLARROW_H #include "tagbase.hpp" #include #include "../../model/world/cellcoordinates.hpp" namespace osg { class PositionAttitudeTransform; class Group; } namespace CSVRender { class CellArrow; class CellArrowTag : public TagBase { CellArrow *mArrow; public: CellArrowTag (CellArrow *arrow); CellArrow *getCellArrow() const; QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override; }; class CellArrow { public: enum Direction { Direction_North = 1, Direction_West = 2, Direction_South = 4, Direction_East = 8 }; private: // not implemented CellArrow (const CellArrow&); CellArrow& operator= (const CellArrow&); Direction mDirection; osg::Group* mParentNode; osg::ref_ptr mBaseNode; CSMWorld::CellCoordinates mCoordinates; void adjustTransform(); void buildShape(); public: CellArrow (osg::Group *cellNode, Direction direction, const CSMWorld::CellCoordinates& coordinates); ~CellArrow(); CSMWorld::CellCoordinates getCoordinates() const; Direction getDirection() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cellborder.cpp000066400000000000000000000065561445372753700237540ustar00rootroot00000000000000#include "cellborder.hpp" #include #include #include #include #include #include "mask.hpp" #include "../../model/world/cellcoordinates.hpp" const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE; /* The number of vertices per cell border is equal to the number of vertices per edge minus the duplicated corner vertices. An additional vertex to close the loop is NOT needed. */ const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 4; CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords) : mParentNode(cellNode) { mBorderGeometry = new osg::Geometry(); mBaseNode = new osg::PositionAttitudeTransform(); mBaseNode->setNodeMask(Mask_CellBorder); mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); mBaseNode->addChild(mBorderGeometry); mParentNode->addChild(mBaseNode); } CSVRender::CellBorder::~CellBorder() { mParentNode->removeChild(mBaseNode); } void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) { const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT); if (!landData) return; mBaseNode->removeChild(mBorderGeometry); mBorderGeometry = new osg::Geometry(); osg::ref_ptr vertices = new osg::Vec3Array(); int x = 0; int y = 0; /* Traverse the cell border counter-clockwise starting at the SW corner vertex (0, 0). Each loop starts at a corner vertex and ends right before the next corner vertex. */ for (; x < ESM::Land::LAND_SIZE - 1; ++x) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); x = ESM::Land::LAND_SIZE - 1; for (; y < ESM::Land::LAND_SIZE - 1; ++y) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); y = ESM::Land::LAND_SIZE - 1; for (; x > 0; --x) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); x = 0; for (; y > 0; --y) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); mBorderGeometry->setVertexArray(vertices); osg::ref_ptr colors = new osg::Vec4Array(); colors->push_back(osg::Vec4f(0.f, 0.5f, 0.f, 1.f)); mBorderGeometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount + 1); // Assign one primitive to each vertex. for (size_t i = 0; i < VertexCount; ++i) primitives->setElement(i, i); // Assign the last primitive to the first vertex to close the loop. primitives->setElement(VertexCount, 0); mBorderGeometry->addPrimitiveSet(primitives); mBorderGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); mBaseNode->addChild(mBorderGeometry); } size_t CSVRender::CellBorder::landIndex(int x, int y) { return static_cast(y) * ESM::Land::LAND_SIZE + x; } float CSVRender::CellBorder::scaleToWorld(int value) { return (CellSize + 128) * (float)value / ESM::Land::LAND_SIZE; } openmw-openmw-0.48.0/apps/opencs/view/render/cellborder.hpp000066400000000000000000000017011445372753700237440ustar00rootroot00000000000000#ifndef OPENCS_VIEW_CELLBORDER_H #define OPENCS_VIEW_CELLBORDER_H #include #include namespace osg { class Geometry; class Group; class PositionAttitudeTransform; } namespace ESM { struct Land; } namespace CSMWorld { class CellCoordinates; } namespace CSVRender { class CellBorder { public: CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords); ~CellBorder(); void buildShape(const ESM::Land& esmLand); private: static const int CellSize; static const int VertexCount; size_t landIndex(int x, int y); float scaleToWorld(int val); // unimplemented CellBorder(const CellBorder&); CellBorder& operator=(const CellBorder&); osg::Group* mParentNode; osg::ref_ptr mBaseNode; osg::ref_ptr mBorderGeometry; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cellmarker.cpp000066400000000000000000000052761445372753700237560ustar00rootroot00000000000000#include "cellmarker.hpp" #include #include #include #include CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) : TagBase(Mask_CellMarker), mMarker(marker) {} CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const { return mMarker; } void CSVRender::CellMarker::buildMarker() { const int characterSize = 20; // Set up attributes of marker text. osg::ref_ptr markerText (new osgText::Text); markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); markerText->setCharacterSize(characterSize); markerText->setAlignment(osgText::Text::CENTER_CENTER); markerText->setDrawMode(osgText::Text::TEXT | osgText::Text::FILLEDBOUNDINGBOX); // If cell exists then show black bounding box otherwise show red. if (mExists) { markerText->setBoundingBoxColor(osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)); } else { markerText->setBoundingBoxColor(osg::Vec4f(1.0f, 0.0f, 0.0f, 1.0f)); } // Add text containing cell's coordinates. std::string coordinatesText = std::to_string(mCoordinates.getX()) + "," + std::to_string(mCoordinates.getY()); markerText->setText(coordinatesText); // Add text to marker node. mMarkerNode->addChild(markerText); } void CSVRender::CellMarker::positionMarker() { const int cellSize = Constants::CellSizeInUnits; const int markerHeight = 0; // Move marker to center of cell. int x = (mCoordinates.getX() * cellSize) + (cellSize / 2); int y = (mCoordinates.getY() * cellSize) + (cellSize / 2); mMarkerNode->setPosition(osg::Vec3f(x, y, markerHeight)); } CSVRender::CellMarker::CellMarker( osg::Group *cellNode, const CSMWorld::CellCoordinates& coordinates, const bool cellExists ) : mCellNode(cellNode), mCoordinates(coordinates), mExists(cellExists) { // Set up node for cell marker. mMarkerNode = new osg::AutoTransform(); mMarkerNode->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN); mMarkerNode->setAutoScaleToScreen(true); mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mMarkerNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); osg::ref_ptr mat = new osg::Material; mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); mMarkerNode->getOrCreateStateSet()->setAttribute(mat); mMarkerNode->setUserData(new CellMarkerTag(this)); mMarkerNode->setNodeMask(Mask_CellMarker); mCellNode->addChild(mMarkerNode); buildMarker(); positionMarker(); } CSVRender::CellMarker::~CellMarker() { mCellNode->removeChild(mMarkerNode); } openmw-openmw-0.48.0/apps/opencs/view/render/cellmarker.hpp000066400000000000000000000027611445372753700237570ustar00rootroot00000000000000#ifndef OPENCS_VIEW_CELLMARKER_H #define OPENCS_VIEW_CELLMARKER_H #include "tagbase.hpp" #include #include "../../model/world/cellcoordinates.hpp" namespace osg { class AutoTransform; class Group; } namespace CSVRender { class CellMarker; class CellMarkerTag : public TagBase { private: CellMarker *mMarker; public: CellMarkerTag(CellMarker *marker); CellMarker *getCellMarker() const; }; /// \brief Marker to display cell coordinates. class CellMarker { private: osg::Group* mCellNode; osg::ref_ptr mMarkerNode; CSMWorld::CellCoordinates mCoordinates; bool mExists; // Not implemented. CellMarker(const CellMarker&); CellMarker& operator=(const CellMarker&); /// \brief Build marker containing cell's coordinates. void buildMarker(); /// \brief Position marker at center of cell. void positionMarker(); public: /// \brief Constructor. /// \param cellNode Cell to create marker for. /// \param coordinates Coordinates of cell. /// \param cellExists Whether or not cell exists. CellMarker( osg::Group *cellNode, const CSMWorld::CellCoordinates& coordinates, const bool cellExists); ~CellMarker(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/cellwater.cpp000066400000000000000000000126361445372753700236150ustar00rootroot00000000000000#include "cellwater.hpp" #include #include #include #include #include #include #include #include #include #include "../../model/world/cell.hpp" #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/data.hpp" #include "mask.hpp" namespace CSVRender { const int CellWater::CellSize = ESM::Land::REAL_SIZE; CellWater::CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, const CSMWorld::CellCoordinates& cellCoords) : mData(data) , mId(id) , mParentNode(cellNode) , mWaterTransform(nullptr) , mWaterGroup(nullptr) , mWaterGeometry(nullptr) , mDeleted(false) , mExterior(false) , mHasWater(false) { mWaterTransform = new osg::PositionAttitudeTransform(); mWaterTransform->setPosition(osg::Vec3f(cellCoords.getX() * CellSize + CellSize / 2.f, cellCoords.getY() * CellSize + CellSize / 2.f, 0)); mWaterTransform->setNodeMask(Mask_Water); mParentNode->addChild(mWaterTransform); mWaterGroup = new osg::Group(); mWaterTransform->addChild(mWaterGroup); int cellIndex = mData.getCells().searchId(mId); if (cellIndex > -1) { updateCellData(mData.getCells().getRecord(cellIndex)); } // Keep water existence/height up to date QAbstractItemModel* cells = mData.getTableModel(CSMWorld::UniversalId::Type_Cells); connect(cells, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(cellDataChanged(const QModelIndex&, const QModelIndex&))); } CellWater::~CellWater() { mParentNode->removeChild(mWaterTransform); } void CellWater::updateCellData(const CSMWorld::Record& cellRecord) { mDeleted = cellRecord.isDeleted(); if (!mDeleted) { const CSMWorld::Cell& cell = cellRecord.get(); if (mExterior != cell.isExterior() || mHasWater != cell.hasWater()) { mExterior = cellRecord.get().isExterior(); mHasWater = cellRecord.get().hasWater(); recreate(); } float waterHeight = -1; if (!mExterior) { waterHeight = cellRecord.get().mWater; } osg::Vec3d pos = mWaterTransform->getPosition(); pos.z() = waterHeight; mWaterTransform->setPosition(pos); } else { recreate(); } } void CellWater::reloadAssets() { recreate(); } void CellWater::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::Collection& cells = mData.getCells(); int rowStart = -1; int rowEnd = -1; if (topLeft.parent().isValid()) { rowStart = topLeft.parent().row(); rowEnd = bottomRight.parent().row(); } else { rowStart = topLeft.row(); rowEnd = bottomRight.row(); } for (int row = rowStart; row <= rowEnd; ++row) { const CSMWorld::Record& cellRecord = cells.getRecord(row); if (Misc::StringUtils::lowerCase(cellRecord.get().mId) == mId) updateCellData(cellRecord); } } void CellWater::recreate() { const int InteriorScalar = 20; const int SegmentsPerCell = 1; const int TextureRepeatsPerCell = 6; const float Alpha = 0.5f; const int RenderBin = osg::StateSet::TRANSPARENT_BIN - 1; if (mWaterGeometry) { mWaterGroup->removeChild(mWaterGeometry); mWaterGeometry = nullptr; } if (mDeleted || !mHasWater) return; float size; int segments; float textureRepeats; if (mExterior) { size = CellSize; segments = SegmentsPerCell; textureRepeats = TextureRepeatsPerCell; } else { size = CellSize * InteriorScalar; segments = SegmentsPerCell * InteriorScalar; textureRepeats = TextureRepeatsPerCell * InteriorScalar; } mWaterGeometry = SceneUtil::createWaterGeometry(size, segments, textureRepeats); mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin)); // Add water texture std::string textureName = Fallback::Map::getString("Water_SurfaceTexture"); textureName = "textures/water/" + textureName + "00.dds"; Resource::ImageManager* imageManager = mData.getResourceSystem()->getImageManager(); osg::ref_ptr waterTexture = new osg::Texture2D(); waterTexture->setImage(imageManager->getImage(textureName)); waterTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); waterTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mWaterGeometry->getStateSet()->setTextureAttributeAndModes(0, waterTexture, osg::StateAttribute::ON); mWaterGroup->addChild(mWaterGeometry); } } openmw-openmw-0.48.0/apps/opencs/view/render/cellwater.hpp000066400000000000000000000030321445372753700236100ustar00rootroot00000000000000#ifndef CSV_RENDER_CELLWATER_H #define CSV_RENDER_CELLWATER_H #include #include #include #include #include "../../model/world/record.hpp" namespace osg { class Geometry; class Group; class PositionAttitudeTransform; } namespace CSMWorld { struct Cell; class CellCoordinates; class Data; } namespace CSVRender { /// For exterior cells, this adds a patch of water to fit the size of the cell. For interior cells with water, this /// adds a large patch of water much larger than the typical size of a cell. class CellWater : public QObject { Q_OBJECT public: CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, const CSMWorld::CellCoordinates& cellCoords); ~CellWater(); void updateCellData(const CSMWorld::Record& cellRecord); void reloadAssets(); private slots: void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); private: void recreate(); static const int CellSize; CSMWorld::Data& mData; std::string mId; osg::Group* mParentNode; osg::ref_ptr mWaterTransform; osg::ref_ptr mWaterGroup; osg::ref_ptr mWaterGeometry; bool mDeleted; bool mExterior; bool mHasWater; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/commands.cpp000066400000000000000000000016671445372753700234360ustar00rootroot00000000000000#include "commands.hpp" #include #include "terrainshapemode.hpp" #include "worldspacewidget.hpp" CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) : mWorldspaceWidget(worldspaceWidget) { } void CSVRender::DrawTerrainSelectionCommand::redo() { tryUpdate(); } void CSVRender::DrawTerrainSelectionCommand::undo() { tryUpdate(); } void CSVRender::DrawTerrainSelectionCommand::tryUpdate() { if (!mWorldspaceWidget) { Log(Debug::Verbose) << "Can't update terrain selection, no WorldspaceWidget found!"; return; } auto terrainMode = dynamic_cast(mWorldspaceWidget->getEditMode()); if (!terrainMode) { Log(Debug::Verbose) << "Can't update terrain selection in current EditMode"; return; } terrainMode->getTerrainSelection()->update(); } openmw-openmw-0.48.0/apps/opencs/view/render/commands.hpp000066400000000000000000000023211445372753700234270ustar00rootroot00000000000000#ifndef CSV_RENDER_COMMANDS_HPP #define CSV_RENDER_COMMANDS_HPP #include #include #include "worldspacewidget.hpp" namespace CSVRender { class TerrainSelection; /* Current solution to force a redrawing of the terrain-selection grid when undoing/redoing changes in the editor. This only triggers a simple redraw of the grid, so only use it in conjunction with actual data changes which deform the grid. Please note that this command needs to be put onto the QUndoStack twice: at the start and at the end of the related terrain manipulation. This makes sure that the grid is always updated after all changes have been undone or redone -- but it also means that the selection is redrawn once at the beginning of either action. Future refinement may solve that. */ class DrawTerrainSelectionCommand : public QUndoCommand { private: QPointer mWorldspaceWidget; public: DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent = nullptr); void redo() override; void undo() override; void tryUpdate(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/editmode.cpp000066400000000000000000000041301445372753700234130ustar00rootroot00000000000000#include "editmode.hpp" #include "tagbase.hpp" #include "worldspacewidget.hpp" CSVRender::WorldspaceWidget& CSVRender::EditMode::getWorldspaceWidget() { return *mWorldspaceWidget; } CSVRender::EditMode::EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip, QWidget *parent) : ModeButton (icon, tooltip, parent), mWorldspaceWidget (worldspaceWidget), mMask (mask) {} unsigned int CSVRender::EditMode::getInteractionMask() const { return mMask; } void CSVRender::EditMode::activate (CSVWidget::SceneToolbar *toolbar) { mWorldspaceWidget->setInteractionMask (mMask); mWorldspaceWidget->clearSelection (~mMask); } void CSVRender::EditMode::setEditLock (bool locked) { } void CSVRender::EditMode::primaryOpenPressed (const WorldspaceHitResult& hit) {} void CSVRender::EditMode::primaryEditPressed (const WorldspaceHitResult& hit) {} void CSVRender::EditMode::secondaryEditPressed (const WorldspaceHitResult& hit) {} void CSVRender::EditMode::primarySelectPressed (const WorldspaceHitResult& hit) {} void CSVRender::EditMode::secondarySelectPressed (const WorldspaceHitResult& hit) {} bool CSVRender::EditMode::primaryEditStartDrag (const QPoint& pos) { return false; } bool CSVRender::EditMode::secondaryEditStartDrag (const QPoint& pos) { return false; } bool CSVRender::EditMode::primarySelectStartDrag (const QPoint& pos) { return false; } bool CSVRender::EditMode::secondarySelectStartDrag (const QPoint& pos) { return false; } void CSVRender::EditMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {} void CSVRender::EditMode::dragCompleted(const QPoint& pos) {} void CSVRender::EditMode::dragAborted() {} void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} void CSVRender::EditMode::dropEvent (QDropEvent *event) {} void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} void CSVRender::EditMode::mouseMoveEvent (QMouseEvent *event) {} int CSVRender::EditMode::getSubMode() const { return -1; } openmw-openmw-0.48.0/apps/opencs/view/render/editmode.hpp000066400000000000000000000065611445372753700234320ustar00rootroot00000000000000#ifndef CSV_RENDER_EDITMODE_H #define CSV_RENDER_EDITMODE_H #include #include "../widget/modebutton.hpp" class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; class QPoint; namespace CSVRender { class WorldspaceWidget; struct WorldspaceHitResult; class TagBase; class EditMode : public CSVWidget::ModeButton { Q_OBJECT WorldspaceWidget *mWorldspaceWidget; unsigned int mMask; protected: WorldspaceWidget& getWorldspaceWidget(); public: EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip = "", QWidget *parent = nullptr); unsigned int getInteractionMask() const; void activate (CSVWidget::SceneToolbar *toolbar) override; /// Default-implementation: Ignored. virtual void setEditLock (bool locked); /// Default-implementation: Ignored. virtual void primaryOpenPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. virtual void primaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. virtual void secondaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. virtual void primarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. virtual void secondarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: ignore and return false /// /// \return Drag accepted? virtual bool primaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? virtual bool secondaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? virtual bool primarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? virtual bool secondarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignored virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); /// Default-implementation: ignored virtual void dragCompleted(const QPoint& pos); /// Default-implementation: ignored /// /// \note dragAborted will not be called, if the drag is aborted via changing /// editing mode virtual void dragAborted(); /// Default-implementation: ignored virtual void dragWheel (int diff, double speedFactor); /// Default-implementation: ignored void dragEnterEvent (QDragEnterEvent *event) override; /// Default-implementation: ignored void dropEvent (QDropEvent *event) override; /// Default-implementation: ignored void dragMoveEvent (QDragMoveEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; /// Default: return -1 virtual int getSubMode() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/instancedragmodes.hpp000066400000000000000000000006751445372753700253320ustar00rootroot00000000000000#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H #define CSV_WIDGET_INSTANCEDRAGMODES_H namespace CSVRender { enum DragMode { DragMode_None, DragMode_Move, DragMode_Rotate, DragMode_Scale, DragMode_Select_Only, DragMode_Select_Add, DragMode_Select_Remove, DragMode_Select_Invert, DragMode_Move_Snap, DragMode_Rotate_Snap, DragMode_Scale_Snap }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/instancemode.cpp000066400000000000000000001205541445372753700243030ustar00rootroot00000000000000 #include "instancemode.hpp" #include #include #include #include "../../model/prefs/state.hpp" #include #include #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/prefs/shortcut.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" #include "mask.hpp" #include "object.hpp" #include "worldspacewidget.hpp" #include "pagedworldspacewidget.hpp" #include "instanceselectionmode.hpp" #include "instancemovemode.hpp" int CSVRender::InstanceMode::getSubModeFromId (const std::string& id) const { return id=="move" ? 0 : (id=="rotate" ? 1 : 2); } osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const { float x, y, z; float test = 2 * (rot.w() * rot.y() + rot.x() * rot.z()); if (std::abs(test) >= 1.f) { x = atan2(rot.x(), rot.w()); y = (test > 0) ? (osg::PI / 2) : (-osg::PI / 2); z = 0; } else { x = std::atan2(2 * (rot.w() * rot.x() - rot.y() * rot.z()), 1 - 2 * (rot.x() * rot.x() + rot.y() * rot.y())); y = std::asin(test); z = std::atan2(2 * (rot.w() * rot.z() - rot.x() * rot.y()), 1 - 2 * (rot.y() * rot.y() + rot.z() * rot.z())); } return osg::Vec3f(-x, -y, -z); } osg::Quat CSVRender::InstanceMode::eulerToQuat(const osg::Vec3f& euler) const { osg::Quat xr = osg::Quat(-euler[0], osg::Vec3f(1,0,0)); osg::Quat yr = osg::Quat(-euler[1], osg::Vec3f(0,1,0)); osg::Quat zr = osg::Quat(-euler[2], osg::Vec3f(0,0,1)); return zr * yr * xr; } float CSVRender::InstanceMode::roundFloatToMult(const float val, const double mult) const { return round(val / mult) * mult; } osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector >& selection) const { osg::Vec3f center = osg::Vec3f(0, 0, 0); int objectCount = 0; for (std::vector >::const_iterator iter (selection.begin()); iter!=selection.end(); ++iter) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { const ESM::Position& position = objectTag->mObject->getPosition(); center += osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]); ++objectCount; } } if (objectCount > 0) center /= objectCount; return center; } osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) { osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); osg::Matrix windowMatrix = getWorldspaceWidget().getCamera()->getViewport()->computeWindowMatrix(); osg::Matrix combined = viewMatrix * projMatrix * windowMatrix; return pos * combined; } osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos) { osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); osg::Matrix combined = viewMatrix * projMatrix; return pos * combined; } osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart) { osg::Matrix viewMatrix; viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix()); osg::Matrix projMatrix; projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix()); osg::Matrix combined = projMatrix * viewMatrix; /* calculate viewport normalized coordinates note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */ float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f; float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height(); osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined; return mousePlanePoint; } CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), mDragAxis (-1), mLocked (false), mUnitScaleDist(1), mParentNode (parentNode) { connect(this, SIGNAL(requestFocus(const std::string&)), worldspaceWidget, SIGNAL(requestFocus(const std::string&))); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); connect(deleteShortcut, SIGNAL(activated(bool)), this, SLOT(deleteSelectedInstances(bool))); // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and Qt5.14 CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); connect(dropToCollisionShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollision())); CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); connect(dropToTerrainLevelShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrain())); CSMPrefs::Shortcut* dropToCollisionShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget); connect(dropToCollisionShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollisionSeparately())); CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); connect(dropToTerrainLevelShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrainSeparately())); } void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) { if (!mSubMode) { mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); mSubMode->addButton (new InstanceMoveMode (this), "move"); mSubMode->addButton (":scenetoolbar/transform-rotate", "rotate", "Rotate selected instances" "
  • Use {scene-edit-primary} to rotate instances freely
  • " "
  • Use {scene-edit-secondary} to rotate instances within the grid
  • " "
  • The center of the view acts as the axis of rotation
  • " "
"); mSubMode->addButton (":scenetoolbar/transform-scale", "scale", "Scale selected instances" "
  • Use {scene-edit-primary} to scale instances freely
  • " "
  • Use {scene-edit-secondary} to scale instances along the grid
  • " "
  • The scaling rate is based on how close the start of a drag is to the center of the screen
  • " "
"); mSubMode->setButton (mSubModeId); connect (mSubMode, SIGNAL (modeChanged (const std::string&)), this, SLOT (subModeChanged (const std::string&))); } if (!mSelectionMode) mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; EditMode::activate (toolbar); toolbar->addTool (mSubMode); toolbar->addTool (mSelectionMode); std::string subMode = mSubMode->getCurrentId(); getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), Mask_Reference); } void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) { mDragMode = DragMode_None; getWorldspaceWidget().reset (Mask_Reference); if (mSelectionMode) { toolbar->removeTool (mSelectionMode); delete mSelectionMode; mSelectionMode = nullptr; } if (mSubMode) { toolbar->removeTool (mSubMode); delete mSubMode; mSubMode = nullptr; } EditMode::deactivate (toolbar); } void CSVRender::InstanceMode::setEditLock (bool locked) { mLocked = locked; if (mLocked) getWorldspaceWidget().abortDrag(); } void CSVRender::InstanceMode::primaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) primarySelectPressed (hit); } void CSVRender::InstanceMode::primaryOpenPressed (const WorldspaceHitResult& hit) { if(hit.tag) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { const std::string refId = objectTag->mObject->getReferenceId(); emit requestFocus(refId); } } } void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) secondarySelectPressed (hit); } void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) { getWorldspaceWidget().clearSelection (Mask_Reference); if (hit.tag) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; object->setSelected (true); return; } } } void CSVRender::InstanceMode::secondarySelectPressed (const WorldspaceHitResult& hit) { if (hit.tag) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; object->setSelected (!object->getSelected()); return; } } } bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) { if (mDragMode!=DragMode_None || mLocked) return false; WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); std::vector > selection = getWorldspaceWidget().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); if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; object->setSelected (true); } } selection = getWorldspaceWidget().getSelection (Mask_Reference); if (selection.empty()) return false; } mObjectsAtDragStart.clear(); for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { if (mSubModeId == "move") { objectTag->mObject->setEdited (Object::Override_Position); float x = objectTag->mObject->getPosition().pos[0]; float y = objectTag->mObject->getPosition().pos[1]; float z = objectTag->mObject->getPosition().pos[2]; osg::Vec3f thisPoint(x, y, z); mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") { objectTag->mObject->setEdited (Object::Override_Rotation); mDragMode = DragMode_Rotate; } else if (mSubModeId == "scale") { objectTag->mObject->setEdited (Object::Override_Scale); mDragMode = DragMode_Scale; // Calculate scale factor std::vector > editedSelection = getWorldspaceWidget().getEdited (Mask_Reference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); mUnitScaleDist = std::sqrt(dx * dx + dy * dy); } } } if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get())) { mDragAxis = objectTag->mAxis; } else mDragAxis = -1; return true; } bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) { if (mDragMode != DragMode_None || mLocked) return false; WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); std::vector > selection = getWorldspaceWidget().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); if (CSVRender::ObjectTag* objectTag = dynamic_cast (hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; object->setSelected(true); } } selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return false; } mObjectsAtDragStart.clear(); for (std::vector >::iterator iter(selection.begin()); iter != selection.end(); ++iter) { if (CSVRender::ObjectTag* objectTag = dynamic_cast (iter->get())) { if (mSubModeId == "move") { objectTag->mObject->setEdited(Object::Override_Position); float x = objectTag->mObject->getPosition().pos[0]; float y = objectTag->mObject->getPosition().pos[1]; float z = objectTag->mObject->getPosition().pos[2]; osg::Vec3f thisPoint(x, y, z); mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move_Snap; } else if (mSubModeId == "rotate") { objectTag->mObject->setEdited(Object::Override_Rotation); mDragMode = DragMode_Rotate_Snap; } else if (mSubModeId == "scale") { objectTag->mObject->setEdited(Object::Override_Scale); mDragMode = DragMode_Scale_Snap; // Calculate scale factor std::vector > editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); mUnitScaleDist = std::sqrt(dx * dx + dy * dy); } } } if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast (hit.tag.get())) { mDragAxis = objectTag->mAxis; } else mDragAxis = -1; return true; } bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) { if (mDragMode!=DragMode_None || mLocked) return false; std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos) { if (mDragMode!=DragMode_None || mLocked) return false; std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; osg::Quat rotation; std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) {} else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { osg::Vec3f eye, centre, up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); float angle; osg::Vec3f axis; if (mDragAxis == -1) { // Free rotate float rotationFactor = CSMPrefs::get()["3D Scene Input"]["rotate-factor"].toDouble() * speedFactor; osg::Quat cameraRotation = getWorldspaceWidget().getCamera()->getInverseViewMatrix().getRotate(); osg::Vec3f camForward = centre - eye; osg::Vec3f screenDir = cameraRotation * osg::Vec3f(diffX, diffY, 0); screenDir.normalize(); angle = std::sqrt(diffX*diffX + diffY*diffY) * rotationFactor; axis = screenDir ^ camForward; } else { // Global axis rotation osg::Vec3f camBack = eye - centre; for (int i = 0; i < 3; ++i) { if (i == mDragAxis) axis[i] = 1; else axis[i] = 0; } // Flip axis if facing opposite side if (camBack * axis < 0) axis *= -1; // Convert coordinate system osg::Vec3f screenCenter = getScreenCoords(getSelectionCenter(selection)); int widgetHeight = getWorldspaceWidget().height(); float newX = pos.x() - screenCenter.x(); float newY = (widgetHeight - pos.y()) - screenCenter.y(); float oldX = newX - diffX; float oldY = newY - diffY; // diffY appears to already be flipped osg::Vec3f oldVec = osg::Vec3f(oldX, oldY, 0); oldVec.normalize(); osg::Vec3f newVec = osg::Vec3f(newX, newY, 0); newVec.normalize(); // Find angle and axis of rotation angle = std::acos(oldVec * newVec) * speedFactor; if (((oldVec ^ newVec) * camBack < 0) ^ (camBack.z() < 0)) angle *= -1; } rotation = osg::Quat(angle, axis); } else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { osg::Vec3f center = getScreenCoords(getSelectionCenter(selection)); // Calculate scaling distance/rate int widgetHeight = getWorldspaceWidget().height(); float dx = pos.x() - center.x(); float dy = (widgetHeight - pos.y()) - center.y(); float dist = std::sqrt(dx * dx + dy * dy); float scale = dist / mUnitScaleDist; // Only uniform scaling is currently supported offset = osg::Vec3f(scale, scale, scale); } else if (mSelectionMode->getCurrentId() == "cube-centre") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionCubeCentre (mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "cube-corner") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionCubeCorner (mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "sphere") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->drawSelectionSphere (mousePlanePoint); return; } int i = 0; // Apply for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter, i++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { ESM::Position position = objectTag->mObject->getPosition(); osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); float addToX = mousePos.x() - mDragStart.x(); float addToY = mousePos.y() - mDragStart.y(); float addToZ = mousePos.z() - mDragStart.z(); position.pos[0] = mObjectsAtDragStart[i].x() + addToX; position.pos[1] = mObjectsAtDragStart[i].y() + addToY; position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; if (mDragMode == DragMode_Move_Snap) { double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); } // XYZ-locking if (mDragAxis != -1) { for (int j = 0; j < 3; ++j) { if (j != mDragAxis) position.pos[j] = mObjectsAtDragStart[i][j]; } } objectTag->mObject->setPosition(position.pos); } else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); osg::Quat currentRot = eulerToQuat(osg::Vec3f(position.rot[0], position.rot[1], position.rot[2])); osg::Quat combined = currentRot * rotation; osg::Vec3f euler = quatToEuler(combined); // There appears to be a very rare rounding error that can cause asin to return NaN if (!euler.isNaN()) { position.rot[0] = euler.x(); position.rot[1] = euler.y(); position.rot[2] = euler.z(); } objectTag->mObject->setRotation(position.rot); } else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { // Reset scale objectTag->mObject->setEdited(0); objectTag->mObject->setEdited(Object::Override_Scale); float scale = objectTag->mObject->getScale(); scale *= offset.x(); if (mDragMode == DragMode_Scale_Snap) { scale = CSVRender::InstanceMode::roundFloatToMult(scale, CSMPrefs::get()["3D Scene Editing"]["gridsnap-scale"].toDouble()); } objectTag->mObject->setScale (scale); } } } } void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description; switch (mDragMode) { case DragMode_Move: description = "Move Instances"; break; case DragMode_Rotate: description = "Rotate Instances"; break; case DragMode_Scale: description = "Scale Instances"; break; case DragMode_Select_Only : handleSelectDrag(pos); return; break; case DragMode_Select_Add : handleSelectDrag(pos); return; break; case DragMode_Select_Remove : handleSelectDrag(pos); return; break; case DragMode_Select_Invert : handleSelectDrag(pos); return; break; case DragMode_Move_Snap: description = "Move Instances"; break; case DragMode_Rotate_Snap: description = "Rotate Instances"; break; case DragMode_Scale_Snap: description = "Scale Instances"; break; case DragMode_None: break; } CSMWorld::CommandMacro macro (undoStack, description); for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { if (mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)); position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)); position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)); objectTag->mObject->setRotation(position.rot); } objectTag->mObject->apply (macro); } } mObjectsAtDragStart.clear(); mDragMode = DragMode_None; } void CSVRender::InstanceMode::dragAborted() { getWorldspaceWidget().reset (Mask_Reference); mDragMode = DragMode_None; } void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) { if (mDragMode==DragMode_Move || mDragMode==DragMode_Move_Snap) { osg::Vec3f eye; osg::Vec3f centre; osg::Vec3f up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); osg::Vec3f offset = centre - eye; offset.normalize(); offset *= diff * speedFactor; std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); int j = 0; for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter, j++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { ESM::Position position = objectTag->mObject->getPosition(); for (int i=0; i<3; ++i) position.pos[i] += offset[i]; if (mDragMode == DragMode_Move_Snap) { double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); } objectTag->mObject->setPosition (position.pos); osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); mDragStart = getMousePlaneCoords(getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart[j] = thisPoint; } } } } void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) { if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) { if (!mime->fromDocument (getWorldspaceWidget().getDocument())) return; if (mime->holdsType (CSMWorld::UniversalId::Type_Referenceable)) event->accept(); } } void CSVRender::InstanceMode::dropEvent (QDropEvent* event) { if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); if (!mime->fromDocument (document)) return; WorldspaceHitResult hit = getWorldspaceWidget().mousePick (event->pos(), getWorldspaceWidget().getInteractionMask()); std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); bool noCell = document.getData().getCells().searchId (cellId)==-1; if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString(); // target cell does not exist if (mode=="Discard") return; if (mode=="Create cell and insert") { std::unique_ptr createCommand ( new CSMWorld::CreateCommand (cellTable, cellId)); int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); createCommand->addNestedValue (parentIndex, index, false); document.getUndoStack().push (createCommand.release()); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } else if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) { // target cell exists, but is not shown std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString(); if (mode=="Discard") return; if (mode=="Show cell and insert") { selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } CSMWorld::IdTable& referencesTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); bool dropped = false; std::vector ids = mime->getData(); for (std::vector::const_iterator iter (ids.begin()); iter!=ids.end(); ++iter) if (mime->isReferencable (iter->getType())) { // create reference std::unique_ptr createCommand ( new CSMWorld::CreateCommand ( referencesTable, document.getData().getReferences().getNewId())); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_ReferenceableId), QString::fromUtf8 (iter->getId().c_str())); document.getUndoStack().push (createCommand.release()); dropped = true; } if (dropped) event->accept(); } } int CSVRender::InstanceMode::getSubMode() const { return mSubMode ? getSubModeFromId (mSubMode->getCurrentId()) : 0; } void CSVRender::InstanceMode::subModeChanged (const std::string& id) { mSubModeId = id; getWorldspaceWidget().abortDrag(); getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); } void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); mSelectionMode->dragEnded (mousePlanePoint, mDragMode); mDragMode = DragMode_None; } void CSVRender::InstanceMode::deleteSelectedInstances(bool active) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& referencesTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::CommandMacro macro (undoStack, "Delete Instances"); for(osg::ref_ptr tag: selection) if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) macro.push(new CSMWorld::DeleteCommand(referencesTable, objectTag->mObject->getReferenceId())); getWorldspaceWidget().clearSelection (Mask_Reference); } void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { object->setEdited(Object::Override_Position); ESM::Position position = object->getPosition(); position.pos[2] -= dropHeight; object->setPosition(position.pos); } float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); osg::Vec3d start = point; start.z() += objectHeight; osg::Vec3d end = point; end.z() = std::numeric_limits::lowest(); osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( osgUtil::Intersector::MODEL, start, end) ); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); if (it != intersector->getIntersections().end()) { osgUtil::LineSegmentIntersector::Intersection intersection = *it; float collisionLevel = intersection.getWorldIntersectPoint().z(); return point.z() - collisionLevel + objectHeight; } return 0.0f; } void CSVRender::InstanceMode::dropSelectedInstancesToCollision() { handleDropMethod(Collision, "Drop instances to next collision"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() { handleDropMethod(Terrain, "Drop instances to terrain level"); } void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::CommandMacro macro (undoStack, commandMsg); DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); if(dropMode & Separate) { int counter = 0; for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); dropInstance(objectTag->mObject, dropHeight); objectTag->mObject->apply(macro); counter++; } } else { float smallestDropHeight = std::numeric_limits::max(); int counter = 0; for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); if (thisDrop < smallestDropHeight) smallestDropHeight = thisDrop; counter++; } for (osg::ref_ptr tag : selection) if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { dropInstance(objectTag->mObject, smallestDropHeight); objectTag->mObject->apply(macro); } } } CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); for(osg::ref_ptr tag: selection) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); osg::ref_ptr objectNodeWithoutGUI = objectTag->mObject->getBaseNode(); osg::ComputeBoundsVisitor computeBounds; computeBounds.setTraversalMask(Mask_Reference); objectNodeWithoutGUI->accept(computeBounds); osg::BoundingBox bounds = computeBounds.getBoundingBox(); float boundingBoxOffset = 0.0f; if (bounds.valid()) boundingBoxOffset = bounds.zMin(); mObjectHeights.emplace_back(boundingBoxOffset); mOldMasks.emplace_back(objectNodeWithGUI->getNodeMask()); objectNodeWithGUI->setNodeMask(0); } } } CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); int counter = 0; for(osg::ref_ptr tag: selection) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); objectNodeWithGUI->setNodeMask(mOldMasks[counter]); counter++; } } } openmw-openmw-0.48.0/apps/opencs/view/render/instancemode.hpp000066400000000000000000000106011445372753700242770ustar00rootroot00000000000000#ifndef CSV_RENDER_INSTANCEMODE_H #define CSV_RENDER_INSTANCEMODE_H #include #include #include #include #include #include "editmode.hpp" #include "instancedragmodes.hpp" namespace CSVWidget { class SceneToolMode; } namespace CSVRender { class TagBase; class InstanceSelectionMode; class Object; class InstanceMode : public EditMode { Q_OBJECT enum DropMode { Separate = 0b1, Collision = 0b10, Terrain = 0b100, CollisionSep = Collision | Separate, TerrainSep = Terrain | Separate, }; CSVWidget::SceneToolMode *mSubMode; std::string mSubModeId; InstanceSelectionMode *mSelectionMode; DragMode mDragMode; int mDragAxis; bool mLocked; float mUnitScaleDist; osg::ref_ptr mParentNode; osg::Vec3f mDragStart; std::vector mObjectsAtDragStart; int getSubModeFromId (const std::string& id) const; osg::Vec3f quatToEuler(const osg::Quat& quat) const; osg::Quat eulerToQuat(const osg::Vec3f& euler) const; float roundFloatToMult(const float val, const double mult) const; osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); void dropInstance(CSVRender::Object* object, float dropHeight); float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent = nullptr); void activate (CSVWidget::SceneToolbar *toolbar) override; void deactivate (CSVWidget::SceneToolbar *toolbar) override; void setEditLock (bool locked) override; void primaryOpenPressed (const WorldspaceHitResult& hit) override; void primaryEditPressed (const WorldspaceHitResult& hit) override; void secondaryEditPressed (const WorldspaceHitResult& hit) override; void primarySelectPressed (const WorldspaceHitResult& hit) override; void secondarySelectPressed (const WorldspaceHitResult& hit) override; bool primaryEditStartDrag (const QPoint& pos) override; bool secondaryEditStartDrag (const QPoint& pos) override; bool primarySelectStartDrag(const QPoint& pos) override; bool secondarySelectStartDrag(const QPoint& pos) override; void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; void dragCompleted(const QPoint& pos) override; /// \note dragAborted will not be called, if the drag is aborted via changing /// editing mode void dragAborted() override; void dragWheel (int diff, double speedFactor) override; void dragEnterEvent (QDragEnterEvent *event) override; void dropEvent (QDropEvent *event) override; int getSubMode() const override; signals: void requestFocus (const std::string& id); private slots: void subModeChanged (const std::string& id); void deleteSelectedInstances(bool active); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToCollisionSeparately(); void dropSelectedInstancesToTerrainSeparately(); void handleDropMethod(DropMode dropMode, QString commandMsg); }; /// \brief Helper class to handle object mask data in safe way class DropObjectHeightHandler { public: DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); ~DropObjectHeightHandler(); std::vector mObjectHeights; private: WorldspaceWidget* mWorldspaceWidget; std::vector mOldMasks; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/instancemovemode.cpp000066400000000000000000000005611445372753700251650ustar00rootroot00000000000000 #include "instancemovemode.hpp" CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) : ModeButton (QIcon (QPixmap (":scenetoolbar/transform-move")), "Move selected instances" "
  • Use {scene-edit-primary} to move instances around freely
  • " "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " "
", parent) {} openmw-openmw-0.48.0/apps/opencs/view/render/instancemovemode.hpp000066400000000000000000000004721445372753700251730ustar00rootroot00000000000000#ifndef CSV_RENDER_INSTANCEMOVEMODE_H #define CSV_RENDER_INSTANCEMOVEMODE_H #include "../widget/modebutton.hpp" namespace CSVRender { class InstanceMoveMode : public CSVWidget::ModeButton { Q_OBJECT public: InstanceMoveMode (QWidget *parent = nullptr); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/instanceselectionmode.cpp000066400000000000000000000361661445372753700262160ustar00rootroot00000000000000#include "instanceselectionmode.hpp" #include #include #include #include #include #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" #include "instancedragmodes.hpp" #include "worldspacewidget.hpp" #include "object.hpp" namespace CSVRender { InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode) : SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); connect(mSelectSame, SIGNAL(triggered()), this, SLOT(selectSame())); connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); } InstanceSelectionMode::~InstanceSelectionMode() { mParentNode->removeChild(mBaseNode); } void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) { mDragStart = dragStart; } const osg::Vec3d& InstanceSelectionMode::getDragStart() { return mDragStart; } void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode) { float dragDistance = (mDragStart - dragEndPoint).length(); if (mBaseNode) mParentNode->removeChild (mBaseNode); if (getCurrentId() == "cube-centre") { osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance); osg::Vec3d pointB(mDragStart[0] + dragDistance, mDragStart[1] + dragDistance, mDragStart[2] + dragDistance); getWorldspaceWidget().selectInsideCube(pointA, pointB, dragMode); } else if (getCurrentId() == "cube-corner") { getWorldspaceWidget().selectInsideCube(mDragStart, dragEndPoint, dragMode); } else if (getCurrentId() == "sphere") { getWorldspaceWidget().selectWithinDistance(mDragStart, dragDistance, dragMode); } } void InstanceSelectionMode::drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint) { float dragDistance = (mDragStart - mousePlanePoint).length(); drawSelectionCube(mDragStart, dragDistance); } void InstanceSelectionMode::drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint) { drawSelectionBox(mDragStart, mousePlanePoint); } void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB) { if (mBaseNode) mParentNode->removeChild (mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(pointA); osg::ref_ptr geometry (new osg::Geometry); osg::Vec3Array *vertices = new osg::Vec3Array; vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f)); vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2])); vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f)); vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f)); vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); geometry->setVertexArray (vertices); osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); // top primitives->push_back (2); primitives->push_back (1); primitives->push_back (0); primitives->push_back (3); primitives->push_back (1); primitives->push_back (2); // bottom primitives->push_back (4); primitives->push_back (5); primitives->push_back (6); primitives->push_back (6); primitives->push_back (5); primitives->push_back (7); // sides primitives->push_back (1); primitives->push_back (4); primitives->push_back (0); primitives->push_back (4); primitives->push_back (1); primitives->push_back (5); primitives->push_back (4); primitives->push_back (2); primitives->push_back (0); primitives->push_back (6); primitives->push_back (2); primitives->push_back (4); primitives->push_back (6); primitives->push_back (3); primitives->push_back (2); primitives->push_back (7); primitives->push_back (3); primitives->push_back (6); primitives->push_back (1); primitives->push_back (3); primitives->push_back (5); primitives->push_back (5); primitives->push_back (3); primitives->push_back (7); geometry->addPrimitiveSet (primitives); osg::Vec4Array *colours = new osg::Vec4Array; colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); mBaseNode->addChild (geometry); mParentNode->addChild(mBaseNode); } void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius) { if (mBaseNode) mParentNode->removeChild (mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(point); osg::ref_ptr geometry (new osg::Geometry); osg::Vec3Array *vertices = new osg::Vec3Array; for (int i = 0; i < 2; ++i) { float height = i ? -radius : radius; vertices->push_back (osg::Vec3f (height, -radius, -radius)); vertices->push_back (osg::Vec3f (height, -radius, radius)); vertices->push_back (osg::Vec3f (height, radius, -radius)); vertices->push_back (osg::Vec3f (height, radius, radius)); } geometry->setVertexArray (vertices); osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); // top primitives->push_back (2); primitives->push_back (1); primitives->push_back (0); primitives->push_back (3); primitives->push_back (1); primitives->push_back (2); // bottom primitives->push_back (4); primitives->push_back (5); primitives->push_back (6); primitives->push_back (6); primitives->push_back (5); primitives->push_back (7); // sides primitives->push_back (1); primitives->push_back (4); primitives->push_back (0); primitives->push_back (4); primitives->push_back (1); primitives->push_back (5); primitives->push_back (4); primitives->push_back (2); primitives->push_back (0); primitives->push_back (6); primitives->push_back (2); primitives->push_back (4); primitives->push_back (6); primitives->push_back (3); primitives->push_back (2); primitives->push_back (7); primitives->push_back (3); primitives->push_back (6); primitives->push_back (1); primitives->push_back (3); primitives->push_back (5); primitives->push_back (5); primitives->push_back (3); primitives->push_back (7); geometry->addPrimitiveSet (primitives); osg::Vec4Array *colours = new osg::Vec4Array; colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); mBaseNode->addChild (geometry); mParentNode->addChild(mBaseNode); } void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3f& mousePlanePoint) { float dragDistance = (mDragStart - mousePlanePoint).length(); drawSelectionSphere(mDragStart, dragDistance); } void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius) { if (mBaseNode) mParentNode->removeChild (mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(point); osg::ref_ptr geometry (new osg::Geometry); osg::Vec3Array *vertices = new osg::Vec3Array; constexpr int resolution = 32; float radiusPerResolution = radius / resolution; float reciprocalResolution = 1.0f / resolution; float doubleReciprocalRes = reciprocalResolution * 2; osg::Vec4Array *colours = new osg::Vec4Array; for (int i = 0; i <= resolution; i += 2) { float iShifted = (static_cast(i) - resolution / 2.0f); // i - 16 = -16 ... 16 float xPercentile = iShifted * doubleReciprocalRes; float x = xPercentile * radius; float thisRadius = sqrt (radius * radius - x * x); //the next row float iShifted2 = (static_cast(i + 1) - resolution / 2.0f); float xPercentile2 = iShifted2 * doubleReciprocalRes; float x2 = xPercentile2 * radius; float thisRadius2 = sqrt (radius * radius - x2 * x2); for (int j = 0; j < resolution; ++j) { float vertexX = thisRadius * sin(j * reciprocalResolution * osg::PI * 2); float vertexY = i * radiusPerResolution * 2 - radius; float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2); float heightPercentage = (vertexZ + radius) / (radius * 2); vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ)); colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f)); float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2); float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius; float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2); float heightPercentageNextRow = (vertexZ + radius) / (radius * 2); vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ)); colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); } } geometry->setVertexArray (vertices); osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0); for (int i = 0; i < resolution; ++i) { //Even for (int j = 0; j < resolution * 2; ++j) { if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) continue; primitives->push_back (i * resolution * 2 + j); } if (i * resolution * 2 > static_cast(vertices->size()) - 1) continue; primitives->push_back (i * resolution * 2); primitives->push_back (i * resolution * 2 + 1); //Odd for (int j = 1; j < resolution * 2 - 2; j += 2) { if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) continue; primitives->push_back ((i + 1) * resolution * 2 + j - 1); primitives->push_back (i * resolution * 2 + j + 2); } if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) continue; primitives->push_back ((i + 2) * resolution * 2 - 2); primitives->push_back (i * resolution * 2 + 1); primitives->push_back ((i + 1) * resolution * 2); } geometry->addPrimitiveSet (primitives); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); mBaseNode->addChild (geometry); mParentNode->addChild(mBaseNode); } bool InstanceSelectionMode::createContextMenu(QMenu* menu) { if (menu) { SelectionMode::createContextMenu(menu); menu->addAction(mSelectSame); menu->addAction(mDeleteSelection); } return true; } void InstanceSelectionMode::selectSame() { getWorldspaceWidget().selectAllWithSameParentId(Mask_Reference); } void InstanceSelectionMode::deleteSelection() { std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); CSMWorld::IdTable& referencesTable = dynamic_cast( *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); for (std::vector >::iterator iter = selection.begin(); iter != selection.end(); ++iter) { CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable, static_cast(iter->get())->mObject->getReferenceId()); getWorldspaceWidget().getDocument().getUndoStack().push(command); } } } openmw-openmw-0.48.0/apps/opencs/view/render/instanceselectionmode.hpp000066400000000000000000000042371445372753700262150ustar00rootroot00000000000000#ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H #include #include #include #include "selectionmode.hpp" #include "instancedragmodes.hpp" namespace CSVRender { class InstanceSelectionMode : public SelectionMode { Q_OBJECT public: InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode); ~InstanceSelectionMode(); /// Store the worldspace-coordinate when drag begins void setDragStart(const osg::Vec3d& dragStart); /// Store the worldspace-coordinate when drag begins const osg::Vec3d& getDragStart(); /// Store the screen-coordinate when drag begins void setScreenDragStart(const QPoint& dragStartPoint); /// Apply instance selection changes void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint ); void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint ); void drawSelectionSphere(const osg::Vec3f& mousePlanePoint ); protected: /// Add context menu items to \a menu. /// /// \attention menu can be a 0-pointer /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. bool createContextMenu(QMenu* menu) override; private: void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); void drawSelectionCube(const osg::Vec3d& point, float radius); void drawSelectionSphere(const osg::Vec3d& point, float radius); QAction* mDeleteSelection; QAction* mSelectSame; osg::Vec3d mDragStart; osg::Group* mParentNode; osg::ref_ptr mBaseNode; private slots: void deleteSelection(); void selectSame(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/lighting.cpp000066400000000000000000000031021445372753700234240ustar00rootroot00000000000000#include "lighting.hpp" #include #include #include #include #include #include "../../model/prefs/state.hpp" class DayNightSwitchVisitor : public osg::NodeVisitor { public: DayNightSwitchVisitor(int index) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mIndex(index) { } void apply(osg::Switch &switchNode) override { constexpr int NoIndex = -1; int initialIndex = NoIndex; if (!switchNode.getUserValue("initialIndex", initialIndex)) { for (size_t i = 0; i < switchNode.getValueList().size(); ++i) { if (switchNode.getValueList()[i]) { initialIndex = i; break; } } if (initialIndex != NoIndex) switchNode.setUserValue("initialIndex", initialIndex); } if (CSMPrefs::get()["Rendering"]["scene-day-night-switch-nodes"].isTrue()) { if (switchNode.getName() == Constants::NightDayLabel) switchNode.setSingleChildOn(mIndex); } else if (initialIndex != NoIndex) { switchNode.setSingleChildOn(initialIndex); } traverse(switchNode); } private: int mIndex; }; CSVRender::Lighting::~Lighting() {} void CSVRender::Lighting::updateDayNightMode(int index) { if (mRootNode == nullptr) return; DayNightSwitchVisitor visitor(index); mRootNode->accept(visitor); } openmw-openmw-0.48.0/apps/opencs/view/render/lighting.hpp000066400000000000000000000012641445372753700234400ustar00rootroot00000000000000#ifndef OPENCS_VIEW_LIGHTING_H #define OPENCS_VIEW_LIGHTING_H #include namespace osg { class Vec4f; class LightSource; class Group; } namespace CSVRender { class Lighting { public: Lighting() : mRootNode(nullptr) {} virtual ~Lighting(); virtual void activate (osg::Group* rootNode, bool isExterior) = 0; virtual void deactivate() = 0; virtual osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) = 0; protected: void updateDayNightMode(int index); osg::ref_ptr mLightSource; osg::Group* mRootNode; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/lightingbright.cpp000066400000000000000000000016761445372753700246420ustar00rootroot00000000000000#include "lightingbright.hpp" #include CSVRender::LightingBright::LightingBright() {} void CSVRender::LightingBright::activate (osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; mLightSource = (new osg::LightSource); osg::ref_ptr light (new osg::Light); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); light->setConstantAttenuation(1.f); mLightSource->setLight(light); mRootNode->addChild(mLightSource); updateDayNightMode(0); } void CSVRender::LightingBright::deactivate() { if (mRootNode && mLightSource.get()) mRootNode->removeChild(mLightSource); } osg::Vec4f CSVRender::LightingBright::getAmbientColour(osg::Vec4f* /*defaultAmbient*/) { return osg::Vec4f(1.f, 1.f, 1.f, 1.f); } openmw-openmw-0.48.0/apps/opencs/view/render/lightingbright.hpp000066400000000000000000000007531445372753700246420ustar00rootroot00000000000000#ifndef OPENCS_VIEW_LIGHTING_BRIGHT_H #define OPENCS_VIEW_LIGHTING_BRIGHT_H #include "lighting.hpp" namespace osg { class Light; class Group; } namespace CSVRender { class LightingBright : public Lighting { public: LightingBright(); void activate (osg::Group* rootNode, bool /*isExterior*/) override; void deactivate() override; osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/lightingday.cpp000066400000000000000000000017541445372753700241350ustar00rootroot00000000000000#include "lightingday.hpp" #include CSVRender::LightingDay::LightingDay(){} void CSVRender::LightingDay::activate (osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; mLightSource = new osg::LightSource; osg::ref_ptr light (new osg::Light); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); light->setConstantAttenuation(1.f); mLightSource->setLight(light); mRootNode->addChild(mLightSource); updateDayNightMode(0); } void CSVRender::LightingDay::deactivate() { if (mRootNode && mLightSource.get()) mRootNode->removeChild(mLightSource); } osg::Vec4f CSVRender::LightingDay::getAmbientColour(osg::Vec4f *defaultAmbient) { if (defaultAmbient) return *defaultAmbient; else return osg::Vec4f(0.7f, 0.7f, 0.7f, 1.f); } openmw-openmw-0.48.0/apps/opencs/view/render/lightingday.hpp000066400000000000000000000006521445372753700241360ustar00rootroot00000000000000#ifndef OPENCS_VIEW_LIGHTING_DAY_H #define OPENCS_VIEW_LIGHTING_DAY_H #include "lighting.hpp" namespace CSVRender { class LightingDay : public Lighting { public: LightingDay(); void activate (osg::Group* rootNode, bool /*isExterior*/) override; void deactivate() override; osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/lightingnight.cpp000066400000000000000000000020121445372753700244550ustar00rootroot00000000000000#include "lightingnight.hpp" #include CSVRender::LightingNight::LightingNight() {} void CSVRender::LightingNight::activate (osg::Group* rootNode, bool isExterior) { mRootNode = rootNode; mLightSource = new osg::LightSource; osg::ref_ptr light (new osg::Light); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setDiffuse(osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f)); light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); light->setConstantAttenuation(1.f); mLightSource->setLight(light); mRootNode->addChild(mLightSource); updateDayNightMode(isExterior ? 1 : 0); } void CSVRender::LightingNight::deactivate() { if (mRootNode && mLightSource.get()) mRootNode->removeChild(mLightSource); } osg::Vec4f CSVRender::LightingNight::getAmbientColour(osg::Vec4f *defaultAmbient) { if (defaultAmbient) return *defaultAmbient; else return osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f); } openmw-openmw-0.48.0/apps/opencs/view/render/lightingnight.hpp000066400000000000000000000006551445372753700244750ustar00rootroot00000000000000#ifndef OPENCS_VIEW_LIGHTING_NIGHT_H #define OPENCS_VIEW_LIGHTING_NIGHT_H #include "lighting.hpp" namespace CSVRender { class LightingNight : public Lighting { public: LightingNight(); void activate (osg::Group* rootNode, bool isExterior) override; void deactivate() override; osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient) override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/mask.hpp000066400000000000000000000014261445372753700225660ustar00rootroot00000000000000#ifndef CSV_RENDER_ELEMENTS_H #define CSV_RENDER_ELEMENTS_H namespace CSVRender { /// Node masks used on the OSG scene graph in OpenMW-CS. /// @note See the respective file in OpenMW (apps/openmw/mwrender/vismask.hpp) /// for general usage hints about node masks. /// @copydoc MWRender::VisMask enum Mask : unsigned int { // elements that are part of the actual scene Mask_Reference = 0x2, Mask_Pathgrid = 0x4, Mask_Water = 0x8, Mask_Fog = 0x10, Mask_Terrain = 0x20, // used within models Mask_ParticleSystem = 0x100, Mask_Lighting = 0x200, // control elements Mask_CellMarker = 0x10000, Mask_CellArrow = 0x20000, Mask_CellBorder = 0x40000 }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/object.cpp000066400000000000000000000521101445372753700230700ustar00rootroot00000000000000#include "object.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/cellcoordinates.hpp" #include "../../model/prefs/state.hpp" #include #include #include #include #include "actor.hpp" #include "mask.hpp" 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 { osg::ref_ptr createErrorCube() { osg::ref_ptr shape(new osg::Box(osg::Vec3f(0,0,0), 50.f)); osg::ref_ptr shapedrawable(new osg::ShapeDrawable); shapedrawable->setShape(shape); osg::ref_ptr group (new osg::Group); group->addChild(shapedrawable); return group; } } CSVRender::ObjectTag::ObjectTag (Object* object) : TagBase (Mask_Reference), mObject (object) {} QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& /*hit*/) const { 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() { clear(); const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); const int TypeIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); const int ModelIndex = referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model); int index = referenceables.searchId (mReferenceableId); const ESM::Light* light = nullptr; mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); if (index == -1) { mBaseNode->addChild(createErrorCube()); return; } /// \todo check for Deleted state (error 1) int recordType = referenceables.getData(index, TypeIndex).toInt(); std::string model = referenceables.getData(index, ModelIndex).toString().toUtf8().constData(); if (recordType == CSMWorld::UniversalId::Type_Light) { light = &dynamic_cast& >(referenceables.getRecord(index)).get(); if (model.empty()) model = "marker_light.nif"; } if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) { if (model.empty()) model = "marker_creature.nif"; } try { if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { if (!mActor) mActor = std::make_unique(mReferenceableId, mData); mActor->update(); mBaseNode->addChild(mActor->getBaseNode()); } else if (!model.empty()) { std::string path = "meshes\\" + model; mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } else { throw std::runtime_error(mReferenceableId + " has no model"); } } catch (std::exception& e) { // TODO: use error marker mesh Log(Debug::Error) << e.what(); } if (light) { bool isExterior = false; // FIXME SceneUtil::addLight(mBaseNode, light, Mask_Lighting, isExterior); } } void CSVRender::Object::adjustTransform() { if (mReferenceId.empty()) return; ESM::Position position = getPosition(); // position mRootNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2])); // orientation osg::Quat xr (-position.rot[0], osg::Vec3f(1,0,0)); osg::Quat yr (-position.rot[1], osg::Vec3f(0,1,0)); osg::Quat zr (-position.rot[2], osg::Vec3f(0,0,1)); mBaseNode->setAttitude(zr*yr*xr); float scale = getScale(); mBaseNode->setScale(osg::Vec3(scale, scale, scale)); } const CSMWorld::CellRef& CSVRender::Object::getReference() const { if (mReferenceId.empty()) throw std::logic_error ("object does not represent a reference"); 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(); } 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 CSVRender::Object::makeMoveOrScaleMarker (int axis) { osg::ref_ptr 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 group (new osg::Group); group->addChild(geometry); return group; } osg::ref_ptr 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(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 geometry = new osg::Geometry(); osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); osg::ref_ptr colors = new osg::Vec4Array(1); osg::ref_ptr 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 group = new osg::Group(); group->addChild(geometry); return group; } void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geometry) { osg::ref_ptr 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), mBaseNode(nullptr), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->addCullCallback(new SceneUtil::LightListCallback); mOutline = new osgFX::Scribe; mBaseNode->setUserData(new ObjectTag(this)); mRootNode->addChild (mBaseNode); parentNode->addChild (mRootNode); mRootNode->setNodeMask(Mask_Reference); if (referenceable) { mReferenceableId = id; } else { mReferenceId = id; mReferenceableId = getReference().mRefID; } adjustTransform(); update(); updateMarker(); } CSVRender::Object::~Object() { clear(); mParentNode->removeChild (mRootNode); } void CSVRender::Object::setSelected(bool selected) { mSelected = selected; mOutline->removeChild(mBaseNode); mRootNode->removeChild(mOutline); mRootNode->removeChild(mBaseNode); if (selected) { mOutline->addChild(mBaseNode); mRootNode->addChild(mOutline); } else mRootNode->addChild(mBaseNode); mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble(); updateMarker(); } bool CSVRender::Object::getSelected() const { return mSelected; } osg::ref_ptr CSVRender::Object::getRootNode() { return mRootNode; } osg::ref_ptr CSVRender::Object::getBaseNode() { return mBaseNode; } bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); int index = referenceables.searchId (mReferenceableId); if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) { adjustTransform(); update(); updateMarker(); return true; } return false; } bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); int index = referenceables.searchId (mReferenceableId); if (index!=-1 && index>=start && index<=end) { // Deletion of referenceable-type objects is handled outside of Object. if (!mReferenceId.empty()) { adjustTransform(); update(); return true; } } return false; } bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mReferenceId.empty()) return false; const CSMWorld::RefCollection& references = mData.getReferences(); int index = references.searchId (mReferenceId); if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) { int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); adjustTransform(); if (columnIndex>=topLeft.column() && columnIndex<=bottomRight.row()) { mReferenceableId = references.getData (index, columnIndex).toString().toUtf8().constData(); update(); updateMarker(); } return true; } return false; } void CSVRender::Object::reloadAssets() { update(); updateMarker(); } std::string CSVRender::Object::getReferenceId() const { return mReferenceId; } std::string CSVRender::Object::getReferenceableId() const { return mReferenceableId; } osg::ref_ptr CSVRender::Object::getTag() const { return static_cast (mBaseNode->getUserData()); } bool CSVRender::Object::isEdited() const { return mOverrideFlags; } void CSVRender::Object::setEdited (int flags) { bool discard = mOverrideFlags & ~flags; int added = flags & ~mOverrideFlags; mOverrideFlags = flags; if (added & Override_Position) for (int i=0; i<3; ++i) mPositionOverride.pos[i] = getReference().mPos.pos[i]; if (added & Override_Rotation) for (int i=0; i<3; ++i) mPositionOverride.rot[i] = getReference().mPos.rot[i]; if (added & Override_Scale) mScaleOverride = getReference().mScale; if (discard) adjustTransform(); } ESM::Position CSVRender::Object::getPosition() const { ESM::Position position = getReference().mPos; if (mOverrideFlags & Override_Position) for (int i=0; i<3; ++i) position.pos[i] = mPositionOverride.pos[i]; if (mOverrideFlags & Override_Rotation) for (int i=0; i<3; ++i) position.rot[i] = mPositionOverride.rot[i]; return position; } float CSVRender::Object::getScale() const { return (mOverrideFlags & Override_Scale) ? mScaleOverride : getReference().mScale; } void CSVRender::Object::setPosition (const float position[3]) { mOverrideFlags |= Override_Position; for (int i=0; i<3; ++i) mPositionOverride.pos[i] = position[i]; adjustTransform(); } void CSVRender::Object::setRotation (const float rotation[3]) { mOverrideFlags |= Override_Rotation; for (int i=0; i<3; ++i) mPositionOverride.rot[i] = rotation[i]; adjustTransform(); } void CSVRender::Object::setScale (float scale) { mOverrideFlags |= Override_Scale; mScaleOverride = scale; adjustTransform(); } void CSVRender::Object::setMarkerTransparency(float value) { mMarkerTransparency = value; updateMarker(); } void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) { const CSMWorld::RefCollection& collection = mData.getReferences(); QAbstractItemModel *model = mData.getTableModel (CSMWorld::UniversalId::Type_References); int recordIndex = collection.getIndex (mReferenceId); if (mOverrideFlags & Override_Position) { //Do cell check first so positions can be compared const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get(); if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell)) { // Find cell index at new position std::pair cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex( mPositionOverride.pos[0], mPositionOverride.pos[1]); std::pair originalIndex = ref.getCellIndex(); int cellColumn = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_Cell)); int origCellColumn = collection.findColumnIndex(static_cast ( CSMWorld::Columns::ColumnId_OriginalCell)); if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId(""); std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); commands.push (new CSMWorld::ModifyCommand (*model, model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str()))); commands.push(new CSMWorld::ModifyCommand(*model, model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str()))); // NOTE: refnum is not modified for moving a reference to another cell } } for (int i=0; i<3; ++i) { int column = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_PositionXPos+i)); commands.push (new CSMWorld::ModifyCommand (*model, model->index (recordIndex, column), mPositionOverride.pos[i])); } } if (mOverrideFlags & Override_Rotation) { for (int i=0; i<3; ++i) { int column = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_PositionXRot+i)); commands.push (new CSMWorld::ModifyCommand (*model, model->index (recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i]))); } } if (mOverrideFlags & Override_Scale) { int column = collection.findColumnIndex (CSMWorld::Columns::ColumnId_Scale); commands.push (new CSMWorld::ModifyCommand (*model, model->index (recordIndex, column), mScaleOverride)); } mOverrideFlags = 0; } void CSVRender::Object::setSubMode (int subMode) { if (subMode!=mSubMode) { mSubMode = subMode; updateMarker(); } } void CSVRender::Object::reset() { mOverrideFlags = 0; adjustTransform(); updateMarker(); } openmw-openmw-0.48.0/apps/opencs/view/render/object.hpp000066400000000000000000000134771445372753700231120ustar00rootroot00000000000000#ifndef OPENCS_VIEW_OBJECT_H #define OPENCS_VIEW_OBJECT_H #include #include #include #include #include #include #include "tagbase.hpp" class QModelIndex; class QUndoStack; namespace osg { class PositionAttitudeTransform; class Group; class Node; } namespace osgFX { class Scribe; } namespace Resource { class ResourceSystem; } namespace CSMWorld { class Data; struct CellRef; class CommandMacro; } namespace CSVRender { class Actor; class Object; // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query class ObjectTag : public TagBase { public: ObjectTag (Object* object); Object* mObject; QString getToolTip (bool hideBasics, const WorldspaceHitResult& hit) const override; }; class ObjectMarkerTag : public ObjectTag { public: ObjectMarkerTag (Object* object, int axis); int mAxis; }; class Object { public: enum OverrideFlags { Override_Position = 1, Override_Rotation = 2, Override_Scale = 4 }; private: static const float MarkerShaftWidth; static const float MarkerShaftBaseLength; static const float MarkerHeadWidth; static const float MarkerHeadLength; CSMWorld::Data& mData; std::string mReferenceId; std::string mReferenceableId; osg::ref_ptr mRootNode; osg::ref_ptr mBaseNode; osg::ref_ptr mOutline; bool mSelected; osg::Group* mParentNode; Resource::ResourceSystem* mResourceSystem; bool mForceBaseToZero; ESM::Position mPositionOverride; float mScaleOverride; int mOverrideFlags; osg::ref_ptr mMarker[3]; int mSubMode; float mMarkerTransparency; std::unique_ptr mActor; /// Not implemented Object (const Object&); /// Not implemented Object& operator= (const Object&); /// Remove object from node (includes deleting) void clear(); /// Update model /// @note Make sure adjustTransform() was called first so world space particles get positioned correctly void update(); /// Adjust position, orientation and scale void adjustTransform(); /// Throws an exception if *this was constructed with referenceable const CSMWorld::CellRef& getReference() const; void updateMarker(); osg::ref_ptr makeMoveOrScaleMarker (int axis); osg::ref_ptr makeRotateMarker (int axis); /// Sets up a stateset with properties common to all marker types. void setupCommonMarkerState(osg::ref_ptr 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); /// \param forceBaseToZero If this is a reference ignore the coordinates and place /// it at 0, 0, 0 instead. ~Object(); /// Mark the object as selected, selected objects show an outline effect void setSelected(bool selected); bool getSelected() const; /// Get object node with GUI graphics osg::ref_ptr getRootNode(); /// Get object node without GUI graphics osg::ref_ptr getBaseNode(); /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// Reloads the underlying asset void reloadAssets(); /// Returns an empty string if this is a refereceable-type object. std::string getReferenceId() const; std::string getReferenceableId() const; osg::ref_ptr getTag() const; /// Is there currently an editing operation running on this object? bool isEdited() const; void setEdited (int flags); ESM::Position getPosition() const; float getScale() const; /// Set override position. void setPosition (const float position[3]); /// Set override rotation void setRotation (const float rotation[3]); /// Set override scale void setScale (float scale); void setMarkerTransparency(float value); /// 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(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/orbitcameramode.cpp000066400000000000000000000027641445372753700247710ustar00rootroot00000000000000#include "orbitcameramode.hpp" #include #include "../../model/prefs/shortcut.hpp" #include "worldspacewidget.hpp" namespace CSVRender { OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip, QWidget* parent) : ModeButton(icon, tooltip, parent) , mWorldspaceWidget(worldspaceWidget) , mCenterOnSelection(nullptr) { mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget); mCenterShortcut->enable(false); connect(mCenterShortcut, SIGNAL(activated()), this, SLOT(centerSelection())); } OrbitCameraMode::~OrbitCameraMode() { } void OrbitCameraMode::activate(CSVWidget::SceneToolbar* toolbar) { mCenterOnSelection = new QAction("Center on selected object", this); mCenterShortcut->associateAction(mCenterOnSelection); connect(mCenterOnSelection, SIGNAL(triggered()), this, SLOT(centerSelection())); mCenterShortcut->enable(true); } void OrbitCameraMode::deactivate(CSVWidget::SceneToolbar* toolbar) { mCenterShortcut->associateAction(nullptr); mCenterShortcut->enable(false); } bool OrbitCameraMode::createContextMenu(QMenu* menu) { if (menu) { menu->addAction(mCenterOnSelection); } return true; } void OrbitCameraMode::centerSelection() { mWorldspaceWidget->centerOrbitCameraOnSelection(); } } openmw-openmw-0.48.0/apps/opencs/view/render/orbitcameramode.hpp000066400000000000000000000017011445372753700247640ustar00rootroot00000000000000#ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H #define CSV_RENDER_ORBITCAMERAPICKMODE_H #include #include "../widget/modebutton.hpp" namespace CSMPrefs { class Shortcut; } namespace CSVRender { class WorldspaceWidget; class OrbitCameraMode : public CSVWidget::ModeButton { Q_OBJECT public: OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", QWidget* parent = nullptr); ~OrbitCameraMode(); void activate(CSVWidget::SceneToolbar* toolbar) override; void deactivate(CSVWidget::SceneToolbar* toolbar) override; bool createContextMenu(QMenu* menu) override; private: WorldspaceWidget* mWorldspaceWidget; QAction* mCenterOnSelection; CSMPrefs::Shortcut* mCenterShortcut; private slots: void centerSelection(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/pagedworldspacewidget.cpp000066400000000000000000000762661445372753700262140ustar00rootroot00000000000000#include "pagedworldspacewidget.hpp" #include #include #include #include #include "../../model/prefs/shortcut.hpp" #include "../../model/world/idtable.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolmode.hpp" #include "editmode.hpp" #include "mask.hpp" #include "cameracontroller.hpp" #include "cellarrow.hpp" #include "terraintexturemode.hpp" #include "terrainshapemode.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); { // remove/update std::map::iterator iter (mCells.begin()); while (iter!=mCells.end()) { if (!mSelection.has (iter->first)) { // remove delete iter->second; mCells.erase (iter++); modified = true; } else { // update int index = cells.searchId (iter->first.getId (mWorldspace)); bool deleted = index==-1 || cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; if (deleted!=iter->second->isDeleted()) { modified = true; auto cell = std::make_unique(mDocument.getData(), mRootNode, iter->first.getId (mWorldspace), deleted); delete iter->second; iter->second = cell.release(); } else if (!deleted) { // delete state has not changed -> just update // TODO check if name or region field has changed (cell marker) // FIXME: config setting //std::string name = cells.getRecord(index).get().mName; //std::string region = cells.getRecord(index).get().mRegion; modified = true; } ++iter; } } } // add for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { if (mCells.find (*iter)==mCells.end()) { addCellToScene (*iter); modified = true; } } if (modified) { for (std::map::const_iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) { int mask = 0; for (int i=CellArrow::Direction_North; i<=CellArrow::Direction_East; i *= 2) { CSMWorld::CellCoordinates coordinates (iter->second->getCoordinates()); switch (i) { case CellArrow::Direction_North: coordinates = coordinates.move (0, 1); break; case CellArrow::Direction_West: coordinates = coordinates.move (-1, 0); break; case CellArrow::Direction_South: coordinates = coordinates.move (0, -1); break; case CellArrow::Direction_East: coordinates = coordinates.move (1, 0); break; } if (!mSelection.has (coordinates)) mask |= i; } iter->second->setCellArrows (mask); } } return modified; } void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); tool->addButton (Button_Terrain, Mask_Terrain, "Terrain"); tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( CSVWidget::SceneToolMode *tool) { WorldspaceWidget::addEditModeSelectorButtons (tool); /// \todo replace EditMode with suitable subclasses tool->addButton ( new TerrainShapeMode (this, mRootNode, tool), "terrain-shape"); tool->addButton ( new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { if (hit.tag && hit.tag->getMask()==Mask_CellArrow) { if (CellArrowTag *cellArrowTag = dynamic_cast (hit.tag.get())) { CellArrow *arrow = cellArrowTag->getCellArrow(); CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); CellArrow::Direction direction = arrow->getDirection(); int x = 0; int y = 0; switch (direction) { case CellArrow::Direction_North: y = 1; break; case CellArrow::Direction_West: x = -1; break; case CellArrow::Direction_South: y = -1; break; case CellArrow::Direction_East: x = 1; break; } bool modified = false; if (type == InteractionType_PrimarySelect) { addCellSelection (x, y); modified = true; } else if (type == InteractionType_SecondarySelect) { moveCellSelection (x, y); modified = true; } else // Primary/SecondaryEdit { CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); if (mCells.find (newCoordinates)==mCells.end()) { addCellToScene (newCoordinates); mSelection.add (newCoordinates); modified = true; } if (type == InteractionType_SecondaryEdit) { if (mCells.find (coordinates)!=mCells.end()) { removeCellFromScene (coordinates); mSelection.remove (coordinates); modified = true; } } } if (modified) adjustCells(); return; } } WorldspaceWidget::handleInteractionPress (hit, type); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) if (iter->second->referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::referenceableAboutToBeRemoved ( const QModelIndex& parent, int start, int end) { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, int start, int end) { CSMWorld::IdTable& referenceables = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) { QModelIndex topLeft = referenceables.index (start, 0); QModelIndex bottomRight = referenceables.index (end, referenceables.columnCount()); if (iter->second->referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); } } void CSVRender::PagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) if (iter->second->referenceDataChanged (topLeft, bottomRight)) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) if (iter->second->referenceAboutToBeRemoved (parent, start, end)) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, int end) { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) if (iter->second->referenceAdded (parent, start, end)) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); int rowStart = -1; int rowEnd = -1; if (topLeft.parent().isValid()) { rowStart = topLeft.parent().row(); rowEnd = bottomRight.parent().row(); } else { rowStart = topLeft.row(); rowEnd = bottomRight.row(); } for (int row = rowStart; row <= rowEnd; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) { searchResult->second->pathgridModified(); flagAsModified(); } } } void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { // Pathgrid going to be deleted for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) { searchResult->second->pathgridRemoved(); flagAsModified(); } } } } void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) { searchResult->second->pathgridModified(); flagAsModified(); } } } } void CSVRender::PagedWorldspaceWidget::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { std::string id = mDocument.getData().getLand().getId(r); auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); if (cellIt != mCells.end()) { cellIt->second->landDataChanged(topLeft, bottomRight); flagAsModified(); } } } void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) { for (int r = start; r <= end; ++r) { std::string id = mDocument.getData().getLand().getId(r); auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); if (cellIt != mCells.end()) { cellIt->second->landAboutToBeRemoved(parent, start, end); flagAsModified(); } } } void CSVRender::PagedWorldspaceWidget::landAdded (const QModelIndex& parent, int start, int end) { for (int r = start; r <= end; ++r) { std::string id = mDocument.getData().getLand().getId(r); auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); if (cellIt != mCells.end()) { cellIt->second->landAdded(parent, start, end); flagAsModified(); } } } void CSVRender::PagedWorldspaceWidget::landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (auto cellIt : mCells) cellIt.second->landTextureChanged(topLeft, bottomRight); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) { for (auto cellIt : mCells) cellIt.second->landTextureAboutToBeRemoved(parent, start, end); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::landTextureAdded (const QModelIndex& parent, int start, int end) { for (auto cellIt : mCells) cellIt.second->landTextureAdded(parent, start, end); flagAsModified(); } std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { osg::Vec3d eye, center, up; mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d position = eye; std::ostringstream stream; stream << "player->position " << position.x() << ", " << position.y() << ", " << position.z() << ", 0"; return stream.str(); } void CSVRender::PagedWorldspaceWidget::addCellToScene ( const CSMWorld::CellCoordinates& coordinates) { const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); int index = cells.searchId (coordinates.getId (mWorldspace)); bool deleted = index==-1 || cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; auto cell = std::make_unique(mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), deleted); EditMode *editMode = getEditMode(); cell->setSubMode (editMode->getSubMode(), editMode->getInteractionMask()); mCells.insert (std::make_pair (coordinates, cell.release())); } void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( const CSMWorld::CellCoordinates& coordinates) { std::map::iterator iter = mCells.find (coordinates); if (iter!=mCells.end()) { delete iter->second; mCells.erase (iter); } } void CSVRender::PagedWorldspaceWidget::addCellSelection (int x, int y) { CSMWorld::CellSelection newSelection = mSelection; newSelection.move (x, y); for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); ++iter) { if (mCells.find (*iter)==mCells.end()) { addCellToScene (*iter); mSelection.add (*iter); } } } void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) { CSMWorld::CellSelection newSelection = mSelection; newSelection.move (x, y); for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { if (!newSelection.has (*iter)) removeCellFromScene (*iter); } for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); ++iter) { if (!mSelection.has (*iter)) addCellToScene (*iter); } mSelection = newSelection; } void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, int offsetY) { osg::Vec3f eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); int cellX = (int)std::floor(center.x() / Constants::CellSizeInUnits) + offsetX; int cellY = (int)std::floor(center.y() / Constants::CellSizeInUnits) + offsetY; CSMWorld::CellCoordinates cellCoordinates(cellX, cellY); if (!mSelection.has(cellCoordinates)) { addCellToScene(cellCoordinates); mSelection.add(cellCoordinates); adjustCells(); } } CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), mControlElements(nullptr), mDisplayCellCoord(true) { QAbstractItemModel *cells = document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells); connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); connect (cells, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (cellRemoved (const QModelIndex&, int, int))); connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (cellAdded (const QModelIndex&, int, int))); connect (&document.getData(), SIGNAL (assetTablesChanged ()), this, SLOT (assetTablesChanged ())); QAbstractItemModel *lands = document.getData().getTableModel (CSMWorld::UniversalId::Type_Lands); connect (lands, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (landDataChanged (const QModelIndex&, const QModelIndex&))); connect (lands, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (landAboutToBeRemoved (const QModelIndex&, int, int))); connect (lands, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (landAdded (const QModelIndex&, int, int))); QAbstractItemModel *ltexs = document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures); connect (ltexs, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (landTextureDataChanged (const QModelIndex&, const QModelIndex&))); connect (ltexs, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (landTextureAboutToBeRemoved (const QModelIndex&, int, int))); connect (ltexs, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (landTextureAdded (const QModelIndex&, int, int))); // Shortcuts CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this); connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell())); CSMPrefs::Shortcut* loadCameraEastCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-eastcell", this); connect(loadCameraEastCellShortcut, SIGNAL(activated()), this, SLOT(loadEastCell())); CSMPrefs::Shortcut* loadCameraNorthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-northcell", this); connect(loadCameraNorthCellShortcut, SIGNAL(activated()), this, SLOT(loadNorthCell())); CSMPrefs::Shortcut* loadCameraWestCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-westcell", this); connect(loadCameraWestCellShortcut, SIGNAL(activated()), this, SLOT(loadWestCell())); CSMPrefs::Shortcut* loadCameraSouthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-southcell", this); connect(loadCameraSouthCellShortcut, SIGNAL(activated()), this, SLOT(loadSouthCell())); } CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() { for (std::map::iterator iter (mCells.begin()); iter!=mCells.end(); ++iter) { delete iter->second; } } void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { if (!hint.empty()) { CSMWorld::CellSelection selection; if (hint[0]=='c') { // syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger) char ignore; std::istringstream stream (hint.c_str()); if (stream >> ignore) { char ignore1; // : or ; char ignore2; // # // Current coordinate int x, y; // Loop through all the coordinates to add them to selection while (stream >> ignore1 >> ignore2 >> x >> y) selection.add (CSMWorld::CellCoordinates (x, y)); // Mark that camera needs setup mCamPositionSet=false; } } else if (hint[0]=='r') { // syntax r:ref#number (e.g. r:ref#100) char ignore; std::istringstream stream (hint.c_str()); if (stream >> ignore) // ignore r { char ignore1; // : or ; std::string refCode; // ref#number (e.g. ref#100) while (stream >> ignore1 >> refCode) {} //Find out cell coordinate CSMWorld::IdTable& references = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); QVariant cell = references.data(references.getModelIndex(refCode, cellColumn)).value(); QString cellqs = cell.toString(); std::istringstream streamCellCoord (cellqs.toStdString().c_str()); if (streamCellCoord >> ignore) //ignore # { // Current coordinate int x, y; // Loop through all the coordinates to add them to selection while (streamCellCoord >> x >> y) selection.add (CSMWorld::CellCoordinates (x, y)); // Mark that camera needs setup mCamPositionSet=false; } } } setCellSelection (selection); } } void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) { mSelection = selection; if (adjustCells()) flagAsModified(); emit cellSelectionChanged (mSelection); } const CSMWorld::CellSelection& CSVRender::PagedWorldspaceWidget::getCellSelection() const { return mSelection; } std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (const std::string& record) const { std::istringstream stream (record.c_str()); char ignore; int x, y; stream >> ignore >> x >> y; return std::make_pair(x, y); } bool CSVRender::PagedWorldspaceWidget::handleDrop ( const std::vector< CSMWorld::UniversalId >& universalIdData, DropType type) { if (WorldspaceWidget::handleDrop (universalIdData, type)) return true; if (type!=Type_CellsExterior) return false; bool selectionChanged = false; for (unsigned i = 0; i < universalIdData.size(); ++i) { std::pair coordinates(getCoordinatesFromId(universalIdData[i].getId())); if (mSelection.add(CSMWorld::CellCoordinates(coordinates.first, coordinates.second))) { selectionChanged = true; } } if (selectionChanged) { if (adjustCells()) flagAsModified(); emit cellSelectionChanged(mSelection); } return true; } CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const { dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); if (requirements!=ignored) return requirements; switch (type) { case Type_CellsExterior: return canHandle; case Type_CellsInterior: return needUnpaged; default: return ignored; } } unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const { return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelectionMask(); } void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) { for (std::map::iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->setSelection (elementMask, Cell::Selection_Clear); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::invertSelection (int elementMask) { for (std::map::iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->setSelection (elementMask, Cell::Selection_Invert); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) { for (std::map::iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->setSelection (elementMask, Cell::Selection_All); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) { for (std::map::iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->selectAllWithSameParentId (elementMask); flagAsModified(); } void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { for (auto& cell : mCells) { cell.second->selectInsideCube (pointA, pointB, dragMode); } } void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) { for (auto& cell : mCells) { cell.second->selectWithinDistance (point, distance, dragMode); } } std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { CSMWorld::CellCoordinates cellCoordinates ( static_cast (std::floor (point.x() / Constants::CellSizeInUnits)), static_cast (std::floor (point.y() / Constants::CellSizeInUnits))); return cellCoordinates.getId (mWorldspace); } CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const { CSMWorld::CellCoordinates coords( static_cast (std::floor (point.x() / Constants::CellSizeInUnits)), static_cast (std::floor (point.y() / Constants::CellSizeInUnits))); std::map::const_iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) return searchResult->second; else return nullptr; } CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const { std::map::const_iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) return searchResult->second; else return nullptr; } void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) { std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) searchResult->second->setAlteredHeight(inCellX, inCellY, height); } float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) { std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) return searchResult->second->getAlteredHeight(inCellX, inCellY); return nullptr; } void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights() { for (const auto& cell : mCells) cell.second->resetAlteredHeights(); } std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { std::vector > result; for (std::map::const_iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) { std::vector > cellResult = iter->second->getSelection (elementMask); result.insert (result.end(), cellResult.begin(), cellResult.end()); } return result; } std::vector > CSVRender::PagedWorldspaceWidget::getEdited ( unsigned int elementMask) const { std::vector > result; for (std::map::const_iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) { std::vector > cellResult = iter->second->getEdited (elementMask); result.insert (result.end(), cellResult.begin(), cellResult.end()); } return result; } void CSVRender::PagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) { for (std::map::const_iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->setSubMode (subMode, elementMask); } void CSVRender::PagedWorldspaceWidget::reset (unsigned int elementMask) { for (std::map::const_iterator iter = mCells.begin(); iter!=mCells.end(); ++iter) iter->second->reset (elementMask); } CSVWidget::SceneToolToggle2 *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { mControlElements = new CSVWidget::SceneToolToggle2 (parent, "Controls & Guides Visibility", ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); mControlElements->addButton (1, Mask_CellMarker, "Cell Marker"); mControlElements->addButton (2, Mask_CellArrow, "Cell Arrows"); mControlElements->addButton (4, Mask_CellBorder, "Cell Border"); mControlElements->setSelectionMask (0xffffffff); connect (mControlElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); return mControlElements; } void CSVRender::PagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { /// \todo check if no selected cell is affected and do not update, if that is the case if (adjustCells()) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::cellRemoved (const QModelIndex& parent, int start, int end) { if (adjustCells()) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int start, int end) { /// \todo check if no selected cell is affected and do not update, if that is the case if (adjustCells()) flagAsModified(); } void CSVRender::PagedWorldspaceWidget::assetTablesChanged() { std::map::iterator iter = mCells.begin(); for ( ; iter != mCells.end(); ++iter) { iter->second->reloadAssets(); } } void CSVRender::PagedWorldspaceWidget::loadCameraCell() { addCellToSceneFromCamera(0, 0); } void CSVRender::PagedWorldspaceWidget::loadEastCell() { addCellToSceneFromCamera(1, 0); } void CSVRender::PagedWorldspaceWidget::loadNorthCell() { addCellToSceneFromCamera(0, 1); } void CSVRender::PagedWorldspaceWidget::loadWestCell() { addCellToSceneFromCamera(-1, 0); } void CSVRender::PagedWorldspaceWidget::loadSouthCell() { addCellToSceneFromCamera(0, -1); } openmw-openmw-0.48.0/apps/opencs/view/render/pagedworldspacewidget.hpp000066400000000000000000000164151445372753700262070ustar00rootroot00000000000000#ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #include #include "../../model/world/cellselection.hpp" #include "worldspacewidget.hpp" #include "cell.hpp" #include "instancedragmodes.hpp" namespace CSVWidget { class SceneToolToggle; class SceneToolToggle2; } namespace CSVRender { class TextOverlay; class OverlayMask; class PagedWorldspaceWidget : public WorldspaceWidget { Q_OBJECT CSMDoc::Document& mDocument; CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; CSVWidget::SceneToolToggle2 *mControlElements; bool mDisplayCellCoord; private: std::pair getCoordinatesFromId(const std::string& record) const; /// Bring mCells into sync with mSelection again. /// /// \return Any cells added or removed? bool adjustCells(); void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void referenceableAdded (const QModelIndex& index, int start, int end) override; void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void referenceAdded (const QModelIndex& index, int start, int end) override; void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void pathgridAdded (const QModelIndex& parent, int start, int end) override; std::string getStartupInstruction() override; /// \note Does not update the view or any cell marker void addCellToScene (const CSMWorld::CellCoordinates& coordinates); /// \note Does not update the view or any cell marker /// /// \note Calling this function for a cell that is not in the selection is a no-op. void removeCellFromScene (const CSMWorld::CellCoordinates& coordinates); /// \note Does not update the view or any cell marker void addCellSelection (int x, int y); /// \note Does not update the view or any cell marker void moveCellSelection (int x, int y); void addCellToSceneFromCamera (int offsetX, int offsetY); public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); ///< \note Sets the cell area selection to an invalid value to indicate that currently /// no cells are displayed. The cells to be displayed will be specified later through /// hint system. virtual ~PagedWorldspaceWidget(); /// Decodes the the hint string to set of cell that are rendered. void useViewHint (const std::string& hint) override; void setCellSelection(const CSMWorld::CellSelection& selection); const CSMWorld::CellSelection& getCellSelection() const; /// \return Drop handled? bool handleDrop (const std::vector& data, DropType type) override; dropRequirments getDropRequirements(DropType type) const override; /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. virtual CSVWidget::SceneToolToggle2 *makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent); unsigned int getVisibilityMask() const override; /// \param elementMask Elements to be affected by the clear operation void clearSelection (int elementMask) override; /// \param elementMask Elements to be affected by the select operation void invertSelection (int elementMask) override; /// \param elementMask Elements to be affected by the select operation void selectAll (int elementMask) override; // Select everything that references the same ID as at least one of the elements // already selected // /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); void resetAllAlteredHeights(); std::vector > getSelection (unsigned int elementMask) const override; std::vector > getEdited (unsigned int elementMask) const override; void setSubMode (int subMode, unsigned int elementMask) override; /// Erase all overrides and restore the visual representation to its true state. void reset (unsigned int elementMask) override; protected: void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool) override; void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) override; void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) override; signals: void cellSelectionChanged (const CSMWorld::CellSelection& selection); private slots: virtual void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); virtual void cellRemoved (const QModelIndex& parent, int start, int end); virtual void cellAdded (const QModelIndex& index, int start, int end); virtual void landDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); virtual void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); virtual void landAdded (const QModelIndex& parent, int start, int end); virtual void landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); virtual void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); virtual void landTextureAdded (const QModelIndex& parent, int start, int end); void assetTablesChanged (); void loadCameraCell(); void loadEastCell(); void loadNorthCell(); void loadWestCell(); void loadSouthCell(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/pathgrid.cpp000066400000000000000000000542751445372753700234420ustar00rootroot00000000000000#include "pathgrid.hpp" #include #include #include #include #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtree.hpp" #include "worldspacewidget.hpp" namespace CSVRender { class PathgridNodeCallback : public osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { PathgridTag* tag = static_cast(node->getUserData()); tag->getPathgrid()->update(); } }; PathgridTag::PathgridTag(Pathgrid* pathgrid) : TagBase(Mask_Pathgrid), mPathgrid(pathgrid) { } Pathgrid* PathgridTag::getPathgrid() const { return mPathgrid; } QString PathgridTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& hit) const { QString text("Pathgrid: "); text += mPathgrid->getId().c_str(); text += " ("; text += QString::number(SceneUtil::getPathgridNode(static_cast(hit.index0))); text += ")"; return text; } Pathgrid::Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, const CSMWorld::CellCoordinates& coordinates) : mData(data) , mPathgridCollection(mData.getPathgrids()) , mId(pathgridId) , mCoords(coordinates) , mInterior(false) , mDragOrigin(0) , mChangeGeometry(true) , mRemoveGeometry(false) , mUseOffset(true) , mParent(parent) , mPathgridGeometry(nullptr) , mDragGeometry(nullptr) , mTag(new PathgridTag(this)) { const float CoordScalar = ESM::Land::REAL_SIZE; mBaseNode = new osg::PositionAttitudeTransform (); mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * CoordScalar, mCoords.getY() * CoordScalar, 0.f)); mBaseNode->setUserData(mTag); mBaseNode->setUpdateCallback(new PathgridNodeCallback()); mBaseNode->setNodeMask(Mask_Pathgrid); mParent->addChild(mBaseNode); mPathgridGroup = new osg::Group(); mBaseNode->addChild(mPathgridGroup); recreateGeometry(); int index = mData.getCells().searchId(mId); if (index != -1) { const CSMWorld::Cell& cell = mData.getCells().getRecord(index).get(); mInterior = cell.mData.mFlags & ESM::Cell::Interior; } } Pathgrid::~Pathgrid() { mParent->removeChild(mBaseNode); } const CSMWorld::CellCoordinates& Pathgrid::getCoordinates() const { return mCoords; } const std::string& Pathgrid::getId() const { return mId; } bool Pathgrid::isSelected() const { return !mSelected.empty(); } const Pathgrid::NodeList& Pathgrid::getSelected() const { return mSelected; } void Pathgrid::selectAll() { mSelected.clear(); const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) mSelected.push_back(i); createSelectedGeometry(*source); } else { removeSelectedGeometry(); } } void Pathgrid::toggleSelected(unsigned short node) { NodeList::iterator searchResult = std::find(mSelected.begin(), mSelected.end(), node); if (searchResult != mSelected.end()) { mSelected.erase(searchResult); } else { mSelected.push_back(node); } createSelectedGeometry(); } void Pathgrid::invertSelected() { NodeList temp = NodeList(mSelected); mSelected.clear(); const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) { if (std::find(temp.begin(), temp.end(), i) == temp.end()) mSelected.push_back(i); } createSelectedGeometry(*source); } else { removeSelectedGeometry(); } } void Pathgrid::clearSelected() { mSelected.clear(); removeSelectedGeometry(); } void Pathgrid::moveSelected(const osg::Vec3d& offset) { mUseOffset = true; mMoveOffset += offset; recreateGeometry(); } void Pathgrid::setDragOrigin(unsigned short node) { mDragOrigin = node; } void Pathgrid::setDragEndpoint(unsigned short node) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { const CSMWorld::Pathgrid::Point& pointA = source->mPoints[mDragOrigin]; const CSMWorld::Pathgrid::Point& pointB = source->mPoints[node]; osg::Vec3f start = osg::Vec3f(pointA.mX, pointA.mY, pointA.mZ + SceneUtil::DiamondHalfHeight); osg::Vec3f end = osg::Vec3f(pointB.mX, pointB.mY, pointB.mZ + SceneUtil::DiamondHalfHeight); createDragGeometry(start, end, true); } } void Pathgrid::setDragEndpoint(const osg::Vec3d& pos) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { const CSMWorld::Pathgrid::Point& point = source->mPoints[mDragOrigin]; osg::Vec3f start = osg::Vec3f(point.mX, point.mY, point.mZ + SceneUtil::DiamondHalfHeight); osg::Vec3f end = pos - mBaseNode->getPosition(); createDragGeometry(start, end, false); } } void Pathgrid::resetIndicators() { mUseOffset = false; mMoveOffset.set(0, 0, 0); mPathgridGroup->removeChild(mDragGeometry); mDragGeometry = nullptr; } void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) { CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { osg::Vec3d localCoords = worldPos - mBaseNode->getPosition(); int posX = clampToCell(static_cast(localCoords.x())); int posY = clampToCell(static_cast(localCoords.y())); int posZ = clampToCell(static_cast(localCoords.z())); int recordIndex = mPathgridCollection.getIndex (mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX); int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY); int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ); QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast(source->mPoints.size()); // Add node to end of list commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), posX)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), posY)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), posZ)); } else { int index = mPathgridCollection.searchId(mId); if (index == -1) { // Does not exist commands.push(new CSMWorld::CreatePathgridCommand(*model, mId)); } else { source = &mPathgridCollection.getRecord(index).get(); // Deleted, so revert and remove all data commands.push(new CSMWorld::RevertCommand(*model, mId)); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); for (int row = source->mPoints.size() - 1; row >= 0; --row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); } parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); for (int row = source->mEdges.size() - 1; row >= 0; --row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); } } } } void Pathgrid::applyPosition(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { osg::Vec3d localCoords = mMoveOffset; int offsetX = static_cast(localCoords.x()); int offsetY = static_cast(localCoords.y()); int offsetZ = static_cast(localCoords.z()); QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids); int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX); int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY); int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ); QModelIndex parent = model->index(recordIndex, parentColumn); for (size_t i = 0; i < mSelected.size(); ++i) { const CSMWorld::Pathgrid::Point& point = source->mPoints[mSelected[i]]; int row = static_cast(mSelected[i]); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), clampToCell(point.mX + offsetX))); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), clampToCell(point.mY + offsetY))); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), clampToCell(point.mZ + offsetZ))); } } } void Pathgrid::applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { addEdge(commands, *source, node1, node2); } } void Pathgrid::applyEdges(CSMWorld::CommandMacro& commands, unsigned short node) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (size_t i = 0; i < mSelected.size(); ++i) { addEdge(commands, *source, node, mSelected[i]); } } } void Pathgrid::applyRemoveNodes(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); // Want to remove nodes from end of list first std::sort(mSelected.begin(), mSelected.end(), std::greater()); int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); for (std::vector::iterator row = mSelected.begin(); row != mSelected.end(); ++row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, static_cast(*row), parentColumn)); } // Fix/remove edges std::set > edgeRowsToRemove; parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0); int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1); QModelIndex parent = model->index(recordIndex, parentColumn); for (size_t edge = 0; edge < source->mEdges.size(); ++edge) { int adjustment0 = 0; int adjustment1 = 0; // Determine necessary adjustment for (std::vector::iterator point = mSelected.begin(); point != mSelected.end(); ++point) { if (source->mEdges[edge].mV0 == *point || source->mEdges[edge].mV1 == *point) { edgeRowsToRemove.insert(static_cast(edge)); adjustment0 = 0; // No need to adjust, its getting removed adjustment1 = 0; break; } if (source->mEdges[edge].mV0 > *point) --adjustment0; if (source->mEdges[edge].mV1 > *point) --adjustment1; } if (adjustment0 != 0) { int adjustedEdge = source->mEdges[edge].mV0 + adjustment0; commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), adjustedEdge)); } if (adjustment1 != 0) { int adjustedEdge = source->mEdges[edge].mV1 + adjustment1; commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge1Column, parent), adjustedEdge)); } } std::set >::iterator row; for (row = edgeRowsToRemove.begin(); row != edgeRowsToRemove.end(); ++row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); } } clearSelected(); } void Pathgrid::applyRemoveEdges(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { // Want to remove from end of row first std::set > rowsToRemove; for (size_t i = 0; i <= mSelected.size(); ++i) { for (size_t j = i + 1; j < mSelected.size(); ++j) { int row = edgeExists(*source, mSelected[i], mSelected[j]); if (row != -1) { rowsToRemove.insert(row); } row = edgeExists(*source, mSelected[j], mSelected[i]); if (row != -1) { rowsToRemove.insert(row); } } } CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); std::set >::iterator row; for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); } } } osg::ref_ptr Pathgrid::getTag() const { return mTag; } void Pathgrid::recreateGeometry() { mChangeGeometry = true; } void Pathgrid::removeGeometry() { mRemoveGeometry = true; } void Pathgrid::update() { if (mRemoveGeometry) { removePathgridGeometry(); removeSelectedGeometry(); } else if (mChangeGeometry) { createGeometry(); } mChangeGeometry = false; mRemoveGeometry = false; } void Pathgrid::createGeometry() { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { CSMWorld::Pathgrid temp; if (mUseOffset) { temp = *source; for (NodeList::iterator it = mSelected.begin(); it != mSelected.end(); ++it) { temp.mPoints[*it].mX += mMoveOffset.x(); temp.mPoints[*it].mY += mMoveOffset.y(); temp.mPoints[*it].mZ += mMoveOffset.z(); } source = &temp; } removePathgridGeometry(); mPathgridGeometry = SceneUtil::createPathgridGeometry(*source); mPathgridGroup->addChild(mPathgridGeometry); createSelectedGeometry(*source); } else { removePathgridGeometry(); removeSelectedGeometry(); } } void Pathgrid::createSelectedGeometry() { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { createSelectedGeometry(*source); } else { removeSelectedGeometry(); } } void Pathgrid::createSelectedGeometry(const CSMWorld::Pathgrid& source) { removeSelectedGeometry(); mSelectedGeometry = SceneUtil::createPathgridSelectedWireframe(source, mSelected); mPathgridGroup->addChild(mSelectedGeometry); } void Pathgrid::removePathgridGeometry() { if (mPathgridGeometry) { mPathgridGroup->removeChild(mPathgridGeometry); mPathgridGeometry = nullptr; } } void Pathgrid::removeSelectedGeometry() { if (mSelectedGeometry) { mPathgridGroup->removeChild(mSelectedGeometry); mSelectedGeometry = nullptr; } } void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) { if (mDragGeometry) mPathgridGroup->removeChild(mDragGeometry); mDragGeometry = new osg::Geometry(); osg::ref_ptr vertices = new osg::Vec3Array(2); osg::ref_ptr colors = new osg::Vec4Array(1); osg::ref_ptr indices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, 2); (*vertices)[0] = start; (*vertices)[1] = end; if (valid) { (*colors)[0] = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); } else { (*colors)[0] = osg::Vec4f(1.f, 0.f, 0.f, 1.f); } indices->setElement(0, 0); indices->setElement(1, 1); mDragGeometry->setVertexArray(vertices); mDragGeometry->setColorArray(colors, osg::Array::BIND_OVERALL); mDragGeometry->addPrimitiveSet(indices); mDragGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); mPathgridGroup->addChild(mDragGeometry); } const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() { int index = mPathgridCollection.searchId(mId); if (index != -1 && !mPathgridCollection.getRecord(index).isDeleted()) { return &mPathgridCollection.getRecord(index).get(); } return nullptr; } int Pathgrid::edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) { for (size_t i = 0; i < source.mEdges.size(); ++i) { if (source.mEdges[i].mV0 == node1 && source.mEdges[i].mV1 == node2) return static_cast(i); } return -1; } void Pathgrid::addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) { CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0); int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1); QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast(source.mEdges.size()); if (edgeExists(source, node1, node2) == -1) { commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node1)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node2)); ++row; } if (edgeExists(source, node2, node1) == -1) { commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node2)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node1)); } } int Pathgrid::clampToCell(int v) { const int CellExtent = ESM::Land::REAL_SIZE; if (mInterior) return v; else if (v > CellExtent) return CellExtent; else if (v < 0) return 0; else return v; } } openmw-openmw-0.48.0/apps/opencs/view/render/pathgrid.hpp000066400000000000000000000100101445372753700234220ustar00rootroot00000000000000#ifndef CSV_RENDER_PATHGRID_H #define CSV_RENDER_PATHGRID_H #include #include #include #include #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/idcollection.hpp" #include "../../model/world/subcellcollection.hpp" #include "tagbase.hpp" namespace osg { class Geometry; class Group; class PositionAttitudeTransform; } namespace CSMWorld { class CommandMacro; class Data; struct Pathgrid; } namespace CSVRender { class Pathgrid; class PathgridTag : public TagBase { public: PathgridTag (Pathgrid* pathgrid); Pathgrid* getPathgrid () const; QString getToolTip (bool hideBasics, const WorldspaceHitResult& hit) const override; private: Pathgrid* mPathgrid; }; class Pathgrid { public: typedef std::vector NodeList; Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, const CSMWorld::CellCoordinates& coordinates); ~Pathgrid(); const CSMWorld::CellCoordinates& getCoordinates() const; const std::string& getId() const; bool isSelected() const; const NodeList& getSelected() const; void selectAll(); void toggleSelected(unsigned short node); // Adds to end of vector void invertSelected(); void clearSelected(); void moveSelected(const osg::Vec3d& offset); void setDragOrigin(unsigned short node); void setDragEndpoint(unsigned short node); void setDragEndpoint(const osg::Vec3d& pos); void resetIndicators(); void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); void applyPosition(CSMWorld::CommandMacro& commands); void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); void applyRemoveNodes(CSMWorld::CommandMacro& commands); void applyRemoveEdges(CSMWorld::CommandMacro& commands); osg::ref_ptr getTag() const; void recreateGeometry(); void removeGeometry(); void update(); private: CSMWorld::Data& mData; CSMWorld::SubCellCollection& mPathgridCollection; std::string mId; CSMWorld::CellCoordinates mCoords; bool mInterior; NodeList mSelected; osg::Vec3d mMoveOffset; unsigned short mDragOrigin; bool mChangeGeometry; bool mRemoveGeometry; bool mUseOffset; osg::Group* mParent; osg::ref_ptr mBaseNode; osg::ref_ptr mPathgridGroup; osg::ref_ptr mPathgridGeometry; osg::ref_ptr mSelectedGeometry; osg::ref_ptr mDragGeometry; osg::ref_ptr mTag; void createGeometry(); void createSelectedGeometry(); void createSelectedGeometry(const CSMWorld::Pathgrid& source); void removePathgridGeometry(); void removeSelectedGeometry(); void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); const CSMWorld::Pathgrid* getPathgridSource(); int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); int clampToCell(int v); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/pathgridmode.cpp000066400000000000000000000230751445372753700243010ustar00rootroot00000000000000#include "pathgridmode.hpp" #include #include #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../widget/scenetoolbar.hpp" #include "cell.hpp" #include "mask.hpp" #include "pathgrid.hpp" #include "pathgridselectionmode.hpp" #include "worldspacewidget.hpp" namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) , mSelectionMode(nullptr) { } QString PathgridMode::getTooltip() { return QString( "Pathgrid editing" "
  • Press {scene-edit-primary} to add a node to the cursor location
  • " "
  • Press {scene-edit-secondary} to connect the selected nodes to the node beneath the cursor
  • " "
  • Press {scene-edit-primary} and drag to move selected nodes
  • " "
  • Press {scene-edit-secondary} and drag to connect one node to another
  • " "

Note: Only a single cell's pathgrid may be edited at a time"); } void PathgridMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mSelectionMode) { mSelectionMode = new PathgridSelectionMode(toolbar, getWorldspaceWidget()); } EditMode::activate(toolbar); toolbar->addTool(mSelectionMode); } void PathgridMode::deactivate(CSVWidget::SceneToolbar* toolbar) { if (mSelectionMode) { toolbar->removeTool (mSelectionMode); delete mSelectionMode; mSelectionMode = nullptr; } } void PathgridMode::primaryOpenPressed(const WorldspaceHitResult& hitResult) { } void PathgridMode::primaryEditPressed(const WorldspaceHitResult& hitResult) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() && dynamic_cast(hitResult.tag.get())) { primarySelectPressed(hitResult); } else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) { if (cell->getPathgrid()) { // Add node QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Add node"; CSMWorld::CommandMacro macro(undoStack, description); cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); } } } void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit) { if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) { if (tag->getPathgrid()->isSelected()) { unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Connect node to selected nodes"; CSMWorld::CommandMacro macro(undoStack, description); tag->getPathgrid()->applyEdges(macro, node); } } } } void PathgridMode::primarySelectPressed(const WorldspaceHitResult& hit) { getWorldspaceWidget().clearSelection(Mask_Pathgrid); if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) { mLastId = tag->getPathgrid()->getId(); unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); tag->getPathgrid()->toggleSelected(node); } } } void PathgridMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) { if (tag->getPathgrid()->getId() != mLastId) { getWorldspaceWidget().clearSelection(Mask_Pathgrid); mLastId = tag->getPathgrid()->getId(); } unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); tag->getPathgrid()->toggleSelected(node); return; } } getWorldspaceWidget().clearSelection(Mask_Pathgrid); } bool PathgridMode::primaryEditStartDrag(const QPoint& pos) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (dynamic_cast(hit.tag.get())) { primarySelectPressed(hit); selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); } } if (!selection.empty()) { mDragMode = DragMode_Move; return true; } return false; } bool PathgridMode::secondaryEditStartDrag(const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) { mDragMode = DragMode_Edge; mEdgeId = tag->getPathgrid()->getId(); mFromNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); tag->getPathgrid()->setDragOrigin(mFromNode); return true; } } return false; } void PathgridMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == DragMode_Move) { std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { osg::Vec3d eye, center, up, offset; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, center, up); offset = (up * diffY * speedFactor) + (((center - eye) ^ up) * diffX * speedFactor); tag->getPathgrid()->moveSelected(offset); } } } else if (mDragMode == DragMode_Edge) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); if (cell && cell->getPathgrid()) { PathgridTag* tag = nullptr; if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) { unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); cell->getPathgrid()->setDragEndpoint(node); } else { cell->getPathgrid()->setDragEndpoint(hit.worldPos); } } } } void PathgridMode::dragCompleted(const QPoint& pos) { if (mDragMode == DragMode_Move) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Move pathgrid node(s)"; CSMWorld::CommandMacro macro(undoStack, description); tag->getPathgrid()->applyPosition(macro); } } } else if (mDragMode == DragMode_Edge) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) { if (tag->getPathgrid()->getId() == mEdgeId) { unsigned short toNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Add edge between nodes"; CSMWorld::CommandMacro macro(undoStack, description); tag->getPathgrid()->applyEdge(macro, mFromNode, toNode); } } } mEdgeId.clear(); mFromNode = 0; } mDragMode = DragMode_None; getWorldspaceWidget().reset(Mask_Pathgrid); } void PathgridMode::dragAborted() { getWorldspaceWidget().reset(Mask_Pathgrid); } } openmw-openmw-0.48.0/apps/opencs/view/render/pathgridmode.hpp000066400000000000000000000032731445372753700243040ustar00rootroot00000000000000#ifndef CSV_RENDER_PATHGRIDMODE_H #define CSV_RENDER_PATHGRIDMODE_H #include #include "editmode.hpp" namespace CSVRender { class PathgridSelectionMode; class PathgridMode : public EditMode { Q_OBJECT public: PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=nullptr); void activate(CSVWidget::SceneToolbar* toolbar) override; void deactivate(CSVWidget::SceneToolbar* toolbar) override; void primaryOpenPressed(const WorldspaceHitResult& hit) override; void primaryEditPressed(const WorldspaceHitResult& hit) override; void secondaryEditPressed(const WorldspaceHitResult& hit) override; void primarySelectPressed(const WorldspaceHitResult& hit) override; void secondarySelectPressed(const WorldspaceHitResult& hit) override; bool primaryEditStartDrag (const QPoint& pos) override; bool secondaryEditStartDrag (const QPoint& pos) override; void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; void dragCompleted(const QPoint& pos) override; /// \note dragAborted will not be called, if the drag is aborted via changing /// editing mode void dragAborted() override; private: enum DragMode { DragMode_None, DragMode_Move, DragMode_Edge }; DragMode mDragMode; std::string mLastId, mEdgeId; unsigned short mFromNode; PathgridSelectionMode* mSelectionMode; QString getTooltip(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/pathgridselectionmode.cpp000066400000000000000000000047471445372753700262140ustar00rootroot00000000000000#include "pathgridselectionmode.hpp" #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "worldspacewidget.hpp" #include "pathgrid.hpp" namespace CSVRender { PathgridSelectionMode::PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) : SelectionMode(parent, worldspaceWidget, Mask_Pathgrid) { mRemoveSelectedNodes = new QAction("Remove selected nodes", this); mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); connect(mRemoveSelectedNodes, SIGNAL(triggered()), this, SLOT(removeSelectedNodes())); connect(mRemoveSelectedEdges, SIGNAL(triggered()), this, SLOT(removeSelectedEdges())); } bool PathgridSelectionMode::createContextMenu(QMenu* menu) { if (menu) { SelectionMode::createContextMenu(menu); menu->addAction(mRemoveSelectedNodes); menu->addAction(mRemoveSelectedEdges); } return true; } void PathgridSelectionMode::removeSelectedNodes() { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Remove selected nodes"; CSMWorld::CommandMacro macro(undoStack, description); tag->getPathgrid()->applyRemoveNodes(macro); } } } void PathgridSelectionMode::removeSelectedEdges() { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); QString description = "Remove edges between selected nodes"; CSMWorld::CommandMacro macro(undoStack, description); tag->getPathgrid()->applyRemoveEdges(macro); } } } } openmw-openmw-0.48.0/apps/opencs/view/render/pathgridselectionmode.hpp000066400000000000000000000016671445372753700262170ustar00rootroot00000000000000#ifndef CSV_RENDER_PATHGRID_SELECTION_MODE_H #define CSV_RENDER_PATHGRID_SELECTION_MODE_H #include "selectionmode.hpp" namespace CSVRender { class PathgridSelectionMode : public SelectionMode { Q_OBJECT public: PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); protected: /// Add context menu items to \a menu. /// /// \attention menu can be a 0-pointer /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. bool createContextMenu(QMenu* menu) override; private: QAction* mRemoveSelectedNodes; QAction* mRemoveSelectedEdges; private slots: void removeSelectedNodes(); void removeSelectedEdges(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/previewwidget.cpp000066400000000000000000000111311445372753700245050ustar00rootroot00000000000000#include "previewwidget.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) : SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) { selectNavigationMode("orbit"); QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); connect (&mData, SIGNAL (assetTablesChanged ()), this, SLOT (assetTablesChanged ())); setExterior(false); if (!referenceable) { QAbstractItemModel *references = mData.getTableModel (CSMWorld::UniversalId::Type_References); connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); } } void CSVRender::PreviewWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mObject.referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); if (mObject.getReferenceId().empty()) { CSMWorld::IdTable& referenceables = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); if (referenceables.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) emit closeRequest(); } } void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (mObject.referenceableAboutToBeRemoved (parent, start, end)) flagAsModified(); if (mObject.getReferenceableId().empty()) return; CSMWorld::IdTable& referenceables = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), 0); if (index.row()>=start && index.row()<=end) { if (mObject.getReferenceId().empty()) { // this is a preview for a referenceble emit closeRequest(); } } } void CSVRender::PreviewWidget::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mObject.referenceDataChanged (topLeft, bottomRight)) flagAsModified(); if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); // check for deleted state { QModelIndex index = references.getModelIndex (mObject.getReferenceId(), references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); if (references.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) { emit closeRequest(); return; } } int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); QModelIndex index = references.getModelIndex (mObject.getReferenceId(), columnIndex); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) if (index.column()>=topLeft.column() && index.column()<=bottomRight.row()) emit referenceableIdChanged (mObject.getReferenceableId()); } void CSVRender::PreviewWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); QModelIndex index = references.getModelIndex (mObject.getReferenceId(), 0); if (index.row()>=start && index.row()<=end) emit closeRequest(); } void CSVRender::PreviewWidget::assetTablesChanged () { mObject.reloadAssets(); } openmw-openmw-0.48.0/apps/opencs/view/render/previewwidget.hpp000066400000000000000000000022051445372753700245140ustar00rootroot00000000000000#ifndef OPENCS_VIEW_PREVIEWWIDGET_H #define OPENCS_VIEW_PREVIEWWIDGET_H #include "scenewidget.hpp" #include "object.hpp" class QModelIndex; namespace VFS { class Manager; } namespace CSMWorld { class Data; } namespace CSVRender { class PreviewWidget : public SceneWidget { Q_OBJECT CSMWorld::Data& mData; CSVRender::Object mObject; public: PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent = nullptr); signals: void closeRequest(); void referenceableIdChanged (const std::string& id); private slots: void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); void assetTablesChanged (); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/scenewidget.cpp000066400000000000000000000475741445372753700241450ustar00rootroot00000000000000#include "scenewidget.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../widget/scenetoolmode.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" #include "lighting.hpp" #include "mask.hpp" #include "cameracontroller.hpp" namespace CSVRender { RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , mRootNode(nullptr) { osgViewer::CompositeViewer& viewer = CompositeViewer::get(); osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); //ds->setNumMultiSamples(8); osg::ref_ptr traits = new osg::GraphicsContext::Traits; traits->windowName.clear(); traits->windowDecoration = true; traits->x = 0; traits->y = 0; traits->width = width(); traits->height = height(); traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); // Doesn't make much sense as we're running on demand updates, and there seems to be a bug with the refresh rate when running multiple QGLWidgets traits->vsync = false; mView = new osgViewer::View; updateCameraParameters( traits->width / static_cast(traits->height) ); osg::ref_ptr window = new osgQt::GraphicsWindowQt(traits.get()); QLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(window->getGLWidget()); setLayout(layout); mView->getCamera()->setGraphicsContext(window); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); osg::ref_ptr defaultMat (new osg::Material); defaultMat->setColorMode(osg::Material::OFF); defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); mView->getCamera()->getOrCreateStateSet()->setAttribute(defaultMat); mView->setSceneData(mRootNode); // Add ability to signal osg to show its statistics for debugging purposes mView->addEventHandler(new osgViewer::StatsHandler); viewer.addView(mView); viewer.setDone(false); viewer.realize(); } RenderWidget::~RenderWidget() { try { CompositeViewer::get().removeView(mView); } catch(const std::exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } void RenderWidget::flagAsModified() { mView->requestRedraw(); } void RenderWidget::setVisibilityMask(unsigned int mask) { mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); } osg::Camera *RenderWidget::getCamera() { return mView->getCamera(); } void RenderWidget::toggleRenderStats() { osgViewer::GraphicsWindow* window = static_cast(mView->getCamera()->getGraphicsContext()); window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); } // -------------------------------------------------- CompositeViewer::CompositeViewer() : mSimulationTime(0.0) { // TODO: Upgrade osgQt to support osgViewer::ViewerBase::DrawThreadPerContext // https://gitlab.com/OpenMW/openmw/-/issues/5481 setThreadingModel(osgViewer::ViewerBase::SingleThreaded); setUseConfigureAffinity(false); // disable the default setting of viewer.done() by pressing Escape. setKeyEventSetsDone(0); // Only render when the camera position changed, or content flagged dirty //setRunFrameScheme(osgViewer::ViewerBase::ON_DEMAND); setRunFrameScheme(osgViewer::ViewerBase::CONTINUOUS); connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); mTimer.start( 10 ); int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); setRunMaxFrameRate(frameRateLimit); } CompositeViewer &CompositeViewer::get() { static CompositeViewer sThis; return sThis; } void CompositeViewer::update() { double dt = mFrameTimer.time_s(); mFrameTimer.setStartTick(); emit simulationUpdated(dt); mSimulationTime += dt; frame(mSimulationTime); double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0; if (dt < minFrameTime) { std::this_thread::sleep_for(std::chrono::duration(minFrameTime - dt)); } } // --------------------------------------------------- SceneWidget::SceneWidget(std::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f, bool retrieveInput) : RenderWidget(parent, f) , mResourceSystem(resourceSystem) , mLighting(nullptr) , mHasDefaultAmbient(false) , mIsExterior(true) , mPrevMouseX(0) , mPrevMouseY(0) , mCamPositionSet(false) { mFreeCamControl = new FreeCameraController(this); mOrbitCamControl = new OrbitCameraController(this); mCurrentCamControl = mFreeCamControl; mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); // set up gradient view or configured clear color QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); mGradientCamera = createGradientCamera(bgColour, gradientColour); mView->getCamera()->setClearMask(0); mView->getCamera()->addChild(mGradientCamera.get()); } else { mView->getCamera()->setClearColor(osg::Vec4( bgColour.redF(), bgColour.greenF(), bgColour.blueF(), 1.0f )); } // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); setLighting(&mLightingDay); mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); // Recieve mouse move event even if mouse button is not pressed setMouseTracking(true); setFocusPolicy(Qt::ClickFocus); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); // TODO update this outside of the constructor where virtual methods can be used if (retrieveInput) { CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); } connect (&CompositeViewer::get(), SIGNAL (simulationUpdated(double)), this, SLOT (update(double))); // Shortcuts CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); connect(focusToolbarShortcut, SIGNAL(activated()), this, SIGNAL(focusToolbarRequest())); CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this); connect(renderStatsShortcut, SIGNAL(activated()), this, SLOT(toggleRenderStats())); } SceneWidget::~SceneWidget() { // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour) { osg::ref_ptr geometry = new osg::Geometry; osg::ref_ptr vertices = new osg::Vec3Array; vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f)); vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f)); vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f)); vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f)); geometry->setVertexArray(vertices); osg::ref_ptr primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); // triangle 1 primitives->push_back (0); primitives->push_back (1); primitives->push_back (2); // triangle 2 primitives->push_back (2); primitives->push_back (1); primitives->push_back (3); geometry->addPrimitiveSet(primitives); osg::ref_ptr colours = new osg::Vec4ubArray; colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); return geometry; } osg::ref_ptr SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour) { osg::ref_ptr camera = new osg::Camera(); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f)); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); camera->setViewMatrix(osg::Matrix::identity()); camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); camera->setAllowEventFocus(false); // draw subgraph before main camera view. camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); osg::ref_ptr gradientQuad = createGradientRectangle(bgColour, gradientColour); camera->addChild(gradientQuad); return camera; } void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour) { osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); // Replaces previous rectangle mGradientCamera->setChild(0, gradientRect.get()); } void SceneWidget::setLighting(Lighting *lighting) { if (mLighting) mLighting->deactivate(); mLighting = lighting; mLighting->activate (mRootNode, mIsExterior); osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : nullptr); setAmbient(ambient); flagAsModified(); } void SceneWidget::setAmbient(const osg::Vec4f& ambient) { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(ambient); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); mRootNode->setStateSet(stateset); } void SceneWidget::selectLightingMode (const std::string& mode) { QColor backgroundColour; QColor gradientColour; if (mode == "day") { backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); setLighting(&mLightingDay); } else if (mode == "night") { backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); setLighting(&mLightingNight); } else if (mode == "bright") { backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); setLighting(&mLightingBright); } if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { if (mGradientCamera.get() != nullptr) { // we can go ahead and update since this camera still exists updateGradientCamera(backgroundColour, gradientColour); if (!mView->getCamera()->containsNode(mGradientCamera.get())) { // need to re-attach the gradient camera mView->getCamera()->setClearMask(0); mView->getCamera()->addChild(mGradientCamera.get()); } } else { // need to create the gradient camera mGradientCamera = createGradientCamera(backgroundColour, gradientColour); mView->getCamera()->setClearMask(0); mView->getCamera()->addChild(mGradientCamera.get()); } } else { // Fall back to using the clear color for the camera mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mView->getCamera()->setClearColor(osg::Vec4( backgroundColour.redF(), backgroundColour.greenF(), backgroundColour.blueF(), 1.0f )); if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) { // Remove the child to prevent the gradient from rendering mView->getCamera()->removeChild(mGradientCamera.get()); } } } CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) { CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); /// \todo replace icons tool->addButton (":scenetoolbar/day", "day", "Day" "

  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Strong directional light source
  • " "
  • This mode closely resembles day time in-game
"); tool->addButton (":scenetoolbar/night", "night", "Night" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Weak directional light source
  • " "
  • This mode closely resembles night time in-game
"); tool->addButton (":scenetoolbar/bright", "bright", "Bright" "
  • Maximum ambient
  • " "
  • Strong directional light source
"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectLightingMode (const std::string&))); return tool; } void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) { mDefaultAmbient = colour; mHasDefaultAmbient = true; setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); } void SceneWidget::setExterior (bool isExterior) { mIsExterior = isExterior; } void SceneWidget::mouseMoveEvent (QMouseEvent *event) { mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); mPrevMouseX = event->x(); mPrevMouseY = event->y(); } void SceneWidget::wheelEvent(QWheelEvent *event) { mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y()); } void SceneWidget::update(double dt) { if (mCamPositionSet) { mCurrentCamControl->update(dt); } else { mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); mCamPositionSet = true; } } void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="3D Scene Input/p-navi-free-sensitivity") { mFreeCamControl->setCameraSensitivity(setting->toDouble()); } else if (*setting=="3D Scene Input/p-navi-orbit-sensitivity") { mOrbitCamControl->setCameraSensitivity(setting->toDouble()); } else if (*setting=="3D Scene Input/p-navi-free-invert") { mFreeCamControl->setInverted(setting->isTrue()); } else if (*setting=="3D Scene Input/p-navi-orbit-invert") { mOrbitCamControl->setInverted(setting->isTrue()); } else if (*setting=="3D Scene Input/s-navi-sensitivity") { mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble()); mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-wheel-factor") { mFreeCamControl->setWheelMovementMultiplier(setting->toDouble()); mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-free-lin-speed") { mFreeCamControl->setLinearSpeed(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-free-rot-speed") { mFreeCamControl->setRotationalSpeed(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-free-speed-mult") { mFreeCamControl->setSpeedMultiplier(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-orbit-rot-speed") { mOrbitCamControl->setOrbitSpeed(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-orbit-speed-mult") { mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); } else if (*setting=="3D Scene Input/navi-orbit-const-roll") { mOrbitCamControl->setConstRoll(setting->isTrue()); } else if (*setting=="Rendering/framerate-limit") { CompositeViewer::get().setRunMaxFrameRate(setting->toInt()); } else if (*setting=="Rendering/camera-fov" || *setting=="Rendering/camera-ortho" || *setting=="Rendering/camera-ortho-size") { updateCameraParameters(); } else if (*setting == "Rendering/scene-day-night-switch-nodes") { if (mLighting) setLighting(mLighting); } } void RenderWidget::updateCameraParameters(double overrideAspect) { const float nearDist = 1.0; const float farDist = 1000.0; if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue()) { const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt(); const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast(height())); const float halfH = size * 10.0; const float halfW = halfH * aspect; mView->getCamera()->setProjectionMatrixAsOrtho( -halfW, halfW, -halfH, halfH, nearDist, farDist); } else { mView->getCamera()->setProjectionMatrixAsPerspective( CSMPrefs::get()["Rendering"]["camera-fov"].toInt(), static_cast(width())/static_cast(height()), nearDist, farDist); } } void SceneWidget::selectNavigationMode (const std::string& mode) { if (mode=="1st") { mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mFreeCamControl; mFreeCamControl->setCamera(getCamera()); mFreeCamControl->fixUpAxis(CameraController::WorldUp); } else if (mode=="free") { mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mFreeCamControl; mFreeCamControl->setCamera(getCamera()); mFreeCamControl->unfixUpAxis(); } else if (mode=="orbit") { mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mOrbitCamControl; mOrbitCamControl->setCamera(getCamera()); mOrbitCamControl->reset(); } } } openmw-openmw-0.48.0/apps/opencs/view/render/scenewidget.hpp000066400000000000000000000106021445372753700241300ustar00rootroot00000000000000#ifndef OPENCS_VIEW_SCENEWIDGET_H #define OPENCS_VIEW_SCENEWIDGET_H #include #include #include #include #include #include #include "lightingday.hpp" #include "lightingnight.hpp" #include "lightingbright.hpp" namespace Resource { class ResourceSystem; } namespace osg { class Group; class Camera; } namespace CSVWidget { class SceneToolMode; class SceneToolbar; } namespace CSMPrefs { class Setting; } namespace CSVRender { class CameraController; class FreeCameraController; class OrbitCameraController; class Lighting; class RenderWidget : public QWidget { Q_OBJECT public: RenderWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~RenderWidget(); /// Initiates a request to redraw the view void flagAsModified(); void setVisibilityMask(unsigned int mask); osg::Camera *getCamera(); protected: osg::ref_ptr mView; osg::ref_ptr mRootNode; void updateCameraParameters(double overrideAspect = -1.0); QTimer mTimer; protected slots: void toggleRenderStats(); }; /// Extension of RenderWidget to support lighting mode selection & toolbar class SceneWidget : public RenderWidget { Q_OBJECT public: SceneWidget(std::shared_ptr resourceSystem, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags(), bool retrieveInput = true); virtual ~SceneWidget(); CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. void setDefaultAmbient (const osg::Vec4f& colour); ///< \note The actual ambient colour may differ based on lighting settings. void setExterior (bool isExterior); protected: void setLighting (Lighting *lighting); ///< \attention The ownership of \a lighting is not transferred to *this. void setAmbient(const osg::Vec4f& ambient); void mouseMoveEvent (QMouseEvent *event) override; void wheelEvent (QWheelEvent *event) override; osg::ref_ptr createGradientRectangle(QColor bgColour, QColor gradientColour); osg::ref_ptr createGradientCamera(QColor bgColour, QColor gradientColour); void updateGradientCamera(QColor bgColour, QColor gradientColour); std::shared_ptr mResourceSystem; Lighting* mLighting; osg::ref_ptr mGradientCamera; osg::Vec4f mDefaultAmbient; bool mHasDefaultAmbient; bool mIsExterior; LightingDay mLightingDay; LightingNight mLightingNight; LightingBright mLightingBright; int mPrevMouseX, mPrevMouseY; /// Tells update that camera isn't set bool mCamPositionSet; FreeCameraController* mFreeCamControl; OrbitCameraController* mOrbitCamControl; CameraController* mCurrentCamControl; public slots: void update(double dt); protected slots: virtual void settingChanged (const CSMPrefs::Setting *setting); void selectNavigationMode (const std::string& mode); private slots: void selectLightingMode (const std::string& mode); signals: void focusToolbarRequest(); }; // There are rendering glitches when using multiple Viewer instances, work around using CompositeViewer with multiple views class CompositeViewer : public QObject, public osgViewer::CompositeViewer { Q_OBJECT public: CompositeViewer(); static CompositeViewer& get(); QTimer mTimer; private: osg::Timer mFrameTimer; double mSimulationTime; public slots: void update(); signals: void simulationUpdated(double dt); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/selectionmode.cpp000066400000000000000000000062771445372753700244710ustar00rootroot00000000000000#include "selectionmode.hpp" #include #include #include "worldspacewidget.hpp" namespace CSVRender { SelectionMode::SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, unsigned int interactionMask) : SceneToolMode(parent, "Selection mode") , mWorldspaceWidget(worldspaceWidget) , mInteractionMask(interactionMask) { addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" "
  • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " "from the centre of the selection cube outwards.
  • " "
  • The selection cube is aligned to the word space axis
  • " "
  • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
  • " "
"); addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" "
  • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " "from one corner of the selection cube to the opposite corner
  • " "
  • The selection cube is aligned to the word space axis
  • " "
  • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
  • " "
"); addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" "
  • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " "from the centre of the selection sphere outwards
  • " "
  • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
  • " "
"); mSelectAll = new QAction("Select all", this); mDeselectAll = new QAction("Clear selection", this); mInvertSelection = new QAction("Invert selection", this); connect(mSelectAll, SIGNAL(triggered()), this, SLOT(selectAll())); connect(mDeselectAll, SIGNAL(triggered()), this, SLOT(clearSelection())); connect(mInvertSelection, SIGNAL(triggered()), this, SLOT(invertSelection())); } WorldspaceWidget& SelectionMode::getWorldspaceWidget() { return mWorldspaceWidget; } bool SelectionMode::createContextMenu (QMenu* menu) { if (menu) { menu->addAction(mSelectAll); menu->addAction(mDeselectAll); menu->addAction(mInvertSelection); } return true; } void SelectionMode::selectAll() { getWorldspaceWidget().selectAll(mInteractionMask); } void SelectionMode::clearSelection() { getWorldspaceWidget().clearSelection(mInteractionMask); } void SelectionMode::invertSelection() { getWorldspaceWidget().invertSelection(mInteractionMask); } } openmw-openmw-0.48.0/apps/opencs/view/render/selectionmode.hpp000066400000000000000000000023611445372753700244640ustar00rootroot00000000000000#ifndef CSV_RENDER_SELECTION_MODE_H #define CSV_RENDER_SELECTION_MODE_H #include "../widget/scenetoolmode.hpp" #include "mask.hpp" class QAction; namespace CSVRender { class WorldspaceWidget; class SelectionMode : public CSVWidget::SceneToolMode { Q_OBJECT public: SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, unsigned int interactionMask); protected: WorldspaceWidget& getWorldspaceWidget(); /// Add context menu items to \a menu. /// /// \attention menu can be a 0-pointer /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. bool createContextMenu (QMenu* menu) override; private: WorldspaceWidget& mWorldspaceWidget; unsigned int mInteractionMask; QAction* mSelectAll; QAction* mDeselectAll; QAction* mInvertSelection; protected slots: virtual void selectAll(); virtual void clearSelection(); virtual void invertSelection(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/tagbase.cpp000066400000000000000000000004251445372753700232320ustar00rootroot00000000000000 #include "tagbase.hpp" CSVRender::TagBase::TagBase (Mask mask) : mMask (mask) {} CSVRender::Mask CSVRender::TagBase::getMask() const { return mMask; } QString CSVRender::TagBase::getToolTip (bool hideBasics, const WorldspaceHitResult& /*hit*/) const { return ""; } openmw-openmw-0.48.0/apps/opencs/view/render/tagbase.hpp000066400000000000000000000007121445372753700232360ustar00rootroot00000000000000#ifndef OPENCS_VIEW_TAGBASE_H #define OPENCS_VIEW_TAGBASE_H #include #include #include "mask.hpp" namespace CSVRender { struct WorldspaceHitResult; class TagBase : public osg::Referenced { Mask mMask; public: TagBase (Mask mask); Mask getMask() const; virtual QString getToolTip (bool hideBasics, const WorldspaceHitResult& hit) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/terrainselection.cpp000066400000000000000000000307301445372753700252000ustar00rootroot00000000000000#include "terrainselection.hpp" #include #include #include #include #include "../../model/world/columnimp.hpp" #include "../../model/world/idtable.hpp" #include "cell.hpp" #include "worldspacewidget.hpp" CSVRender::TerrainSelection::TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type): mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mSelectionType(type) { mGeometry = new osg::Geometry(); mSelectionNode = new osg::Group(); mSelectionNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mSelectionNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mSelectionNode->addChild(mGeometry); activate(); } CSVRender::TerrainSelection::~TerrainSelection() { deactivate(); } std::vector> CSVRender::TerrainSelection::getTerrainSelection() const { return mSelection; } void CSVRender::TerrainSelection::onlySelect(const std::vector> &localPositions) { mSelection = localPositions; update(); } void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions) { handleSelection(localPositions, SelectionMethod::AddSelect); } void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions) { handleSelection(localPositions, SelectionMethod::RemoveSelect); } void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions) { handleSelection(localPositions, SelectionMethod::ToggleSelect); } void CSVRender::TerrainSelection::clearTemporarySelection() { mTemporarySelection.clear(); } void CSVRender::TerrainSelection::activate() { if (!mParentNode->containsNode(mSelectionNode)) mParentNode->addChild(mSelectionNode); } void CSVRender::TerrainSelection::deactivate() { mParentNode->removeChild(mSelectionNode); } void CSVRender::TerrainSelection::update() { mSelectionNode->removeChild(mGeometry); mGeometry = new osg::Geometry(); const osg::ref_ptr vertices (new osg::Vec3Array); switch (mSelectionType) { case TerrainSelectionType::Texture : drawTextureSelection(vertices); break; case TerrainSelectionType::Shape : drawShapeSelection(vertices); break; } mGeometry->setVertexArray(vertices); osg::ref_ptr drawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); drawArrays->setCount(vertices->size()); if (vertices->size() != 0) mGeometry->addPrimitiveSet(drawArrays); mSelectionNode->addChild(mGeometry); } void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr vertices) { if (!mSelection.empty()) { for (std::pair &localPos : mSelection) { int x (localPos.first); int y (localPos.second); float xWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x)); float yWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y)); osg::Vec3f pointXY(xWorldCoord, yWorldCoord, calculateLandHeight(x, y)); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1))); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y))); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1))); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y))); } } } void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr vertices) { if (!mSelection.empty()) { const int landHeightsNudge = (ESM::Land::REAL_SIZE / ESM::Land::LAND_SIZE) / (ESM::Land::LAND_SIZE - 1); // Does this work with all land size configurations? const int textureSizeToLandSizeModifier = (ESM::Land::LAND_SIZE - 1) / ESM::Land::LAND_TEXTURE_SIZE; for (std::pair &localPos : mSelection) { int x (localPos.first); int y (localPos.second); // convert texture selection to global vertex coordinates at selection box corners int x1 = x * textureSizeToLandSizeModifier + landHeightsNudge; int x2 = x * textureSizeToLandSizeModifier + textureSizeToLandSizeModifier + landHeightsNudge; int y1 = y * textureSizeToLandSizeModifier - landHeightsNudge; int y2 = y * textureSizeToLandSizeModifier + textureSizeToLandSizeModifier - landHeightsNudge; // Draw edges (check all sides, draw lines between vertices, +1 height to keep lines above ground) // Check adjancent selections, draw lines only to edges of the selection const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); if (north == mSelection.end()) { for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); vertices->push_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+(i-1), y2))); vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+i, y2))); } } const auto south = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y - 1)); if (south == mSelection.end()) { for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + (i - 1) *(ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); vertices->push_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+(i-1), y1))); vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+i, y1))); } } const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); if (east == mSelection.end()) { for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawPreviousY, calculateLandHeight(x2, y1+(i-1)))); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawCurrentY, calculateLandHeight(x2, y1+i))); } } const auto west = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x - 1, y)); if (west == mSelection.end()) { for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawPreviousY, calculateLandHeight(x1, y1+(i-1)))); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawCurrentY, calculateLandHeight(x1, y1+i))); } } } } } void CSVRender::TerrainSelection::handleSelection(const std::vector>& localPositions, SelectionMethod selectionMethod) { for (auto const& localPos : localPositions) { const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); switch (selectionMethod) { case SelectionMethod::OnlySelect: break; case SelectionMethod::AddSelect: if (iter == mSelection.end()) { mSelection.emplace_back(localPos); } break; case SelectionMethod::RemoveSelect: if (iter != mSelection.end()) { mSelection.erase(iter); } break; case SelectionMethod::ToggleSelect: { const auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); if (iterTemp == mTemporarySelection.end()) { if (iter == mSelection.end()) { mSelection.emplace_back(localPos); } else { mSelection.erase(iter); } } mTemporarySelection.emplace_back(localPos); break; } default: break; } } update(); } bool CSVRender::TerrainSelection::noCell(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); return cellCollection.searchId (cellId) == -1; } bool CSVRender::TerrainSelection::noLand(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); return landCollection.searchId (cellId) == -1; } bool CSVRender::TerrainSelection::noLandLoaded(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); } bool CSVRender::TerrainSelection::isLandLoaded(const std::string& cellId) { if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; return false; } int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global vertex coordinates { int cellX = std::floor(static_cast(x) / (ESM::Land::LAND_SIZE - 1)); int cellY = std::floor(static_cast(y) / (ESM::Land::LAND_SIZE - 1)); int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); CSMWorld::CellCoordinates coords (cellX, cellY); float landHeight = 0.f; if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) { landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY); } else if (isLandLoaded(CSMWorld::CellCoordinates::generateId(cellX, cellY))) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); const ESM::Land::LandData* landData = document.getData().getLand().getRecord(cellId).get().getLandData(ESM::Land::DATA_VHGT); return landData->mHeights[localY*ESM::Land::LAND_SIZE + localX]; } return landHeight; } openmw-openmw-0.48.0/apps/opencs/view/render/terrainselection.hpp000066400000000000000000000051071445372753700252050ustar00rootroot00000000000000#ifndef CSV_RENDER_TERRAINSELECTION_H #define CSV_RENDER_TERRAINSELECTION_H #include #include #include #include #include #include #include "../../model/world/cellcoordinates.hpp" namespace osg { class Group; } namespace CSVRender { struct WorldspaceHitResult; class WorldspaceWidget; enum class TerrainSelectionType { Texture, Shape }; enum class SelectionMethod { OnlySelect, AddSelect, RemoveSelect, ToggleSelect }; /// \brief Class handling the terrain selection data and rendering class TerrainSelection { public: TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type); ~TerrainSelection(); void onlySelect(const std::vector> &localPositions); void addSelect(const std::vector>& localPositions); void removeSelect(const std::vector>& localPositions); void toggleSelect(const std::vector> &localPositions); void clearTemporarySelection(); void activate(); void deactivate(); std::vector> getTerrainSelection() const; void update(); protected: void drawShapeSelection(const osg::ref_ptr vertices); void drawTextureSelection(const osg::ref_ptr vertices); int calculateLandHeight(int x, int y); private: void handleSelection(const std::vector>& localPositions, SelectionMethod selectionMethod); bool noCell(const std::string& cellId); bool noLand(const std::string& cellId); bool noLandLoaded(const std::string& cellId); bool isLandLoaded(const std::string& cellId); osg::Group* mParentNode; WorldspaceWidget *mWorldspaceWidget; osg::ref_ptr mBaseNode; osg::ref_ptr mGeometry; osg::ref_ptr mSelectionNode; std::vector> mSelection; // Global terrain selection coordinate in either vertex or texture units std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation TerrainSelectionType mSelectionType; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/terrainshapemode.cpp000066400000000000000000002236531445372753700251700ustar00rootroot00000000000000#include "terrainshapemode.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/tablemimedata.hpp" #include "brushdraw.hpp" #include "commands.hpp" #include "editmode.hpp" #include "pagedworldspacewidget.hpp" #include "mask.hpp" #include "tagbase.hpp" #include "terrainselection.hpp" #include "worldspacewidget.hpp" CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain, "Terrain land editing", parent), mParentNode(parentNode) { } void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mTerrainShapeSelection) { mTerrainShapeSelection = std::make_shared(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape); } if(!mShapeBrushScenetool) { mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate())); connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int))); connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int))); } if (!mBrushDraw) mBrushDraw = std::make_unique(mParentNode); EditMode::activate(toolbar); toolbar->addTool (mShapeBrushScenetool); } void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) { if(mShapeBrushScenetool) { toolbar->removeTool (mShapeBrushScenetool); } if (mTerrainShapeSelection) { mTerrainShapeSelection.reset(); } if (mBrushDraw) mBrushDraw.reset(); EditMode::deactivate(toolbar); } void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here { } void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) { if (hit.hit && hit.tag == nullptr) { if (mShapeEditTool == ShapeEditTool_Flatten) setFlattenToolTargetHeight(hit); if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag) { editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } } clearTransientEdits(); } void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); mTerrainShapeSelection->clearTemporarySelection(); } } void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); mTerrainShapeSelection->clearTemporarySelection(); } } bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimaryEdit; if (hit.hit && hit.tag == nullptr) { mEditingPos = hit.worldPos; mIsEditing = true; if (mShapeEditTool == ShapeEditTool_Flatten) setFlattenToolTargetHeight(hit); } return true; } bool CSVRender::TerrainShapeMode::secondaryEditStartDrag (const QPoint& pos) { return false; } bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); return true; } bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); return true; } void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == InteractionType_PrimaryEdit) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mTotalDiffY += diffY; if (mIsEditing) { if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); } } if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); } } void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) { if (mDragMode == InteractionType_PrimaryEdit) { applyTerrainEditChanges(); clearTransientEdits(); } if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect) { mTerrainShapeSelection->clearTemporarySelection(); } } void CSVRender::TerrainShapeMode::dragAborted() { clearTransientEdits(); mDragMode = InteractionType_None; } void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) { } void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() { bool passing = false; int passes = 0; std::sort(mAlteredCells.begin(), mAlteredCells.end()); mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { passing = true; for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { limitAlteredHeights(cellCoordinates); } std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { if (!limitAlteredHeights(cellCoordinates, true)) passing = false; } ++passes; if (passes > 2) { Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; clearTransientEdits(); return; } } } void CSVRender::TerrainShapeMode::clearTransientEdits() { mTotalDiffY = 0; mIsEditing = false; mAlteredCells.clear(); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) paged->resetAllAlteredHeights(); mTerrainShapeSelection->update(); } void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); QUndoStack& undoStack = document.getUndoStack(); sortAndLimitAlteredCells(); undoStack.beginMacro ("Edit shape and normal records"); // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes. undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); // Generate land height record for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j)) landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); else landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; } } pushEditToCommand(landShapeNew, document, landTable, cellId); } for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); // Generate land normals record for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { osg::Vec3f v1(128, 0, 0); osg::Vec3f v2(0, 128, 0); if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); if (isLandLoaded(shiftedCellId)) v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); if (isLandLoaded(shiftedCellId)) v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } osg::Vec3f normal = v1 ^ v2; const float hyp = normal.length() / 127.0f; normal /= hyp; landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal.x(); landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal.y(); landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal.z(); } } pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); } // One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes. undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); undoStack.endMacro(); clearTransientEdits(); } float CSVRender::TerrainShapeMode::calculateBumpShape(float distance, int radius, float height) { float distancePerRadius = distance / radius; return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); } void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); } if (mBrushShape == CSVWidget::BrushShape_Point) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } if (mBrushShape == CSVWidget::BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); float smoothedByDistance = 0.0f; if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); // Using floating-point radius here to prevent selecting too few vertices. if (distance <= mBrushSize / 2.0f) { if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, smoothedByDistance); float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { if(!mCustomBrushShape.empty()) { for(auto const& value: mCustomBrushShape) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } mTerrainShapeSelection->update(); } void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHitResult& hit) { std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId))))) return; CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); if (!paged) return; std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); std::string cellUpRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() - 1); std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); if (useTool) { mAlteredCells.emplace_back(cellCoords); if (mShapeEditTool == ShapeEditTool_Drag) { // Get distance from modified land, alter land change based on zoom osg::Vec3d eye, center, up; paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d distance = eye - mEditingPos; alteredHeight = alteredHeight * (distance.length() / 500); } if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; } if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); // Change values of cornering cells if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) { if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) { if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); } else return; } else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) { if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) { if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); } else return; } // Change values of edging cells if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) { if(allowLandShapeEditing(cellLeftId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) { if(allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); } } if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) { if(allowLandShapeEditing(cellRightId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) { if(allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); } } } void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); // ### Variable naming key ### // Variables here hold either the real value, or the altered value of current edit. // this = this Cell // left = x - 1, up = y - 1, right = x + 1, down = y + 1 // Altered = transient edit (in current edited) float thisAlteredHeight = 0.0f; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upAlteredHeight = 0.0f; float rightHeight = 0.0f; float rightAlteredHeight = 0.0f; float downHeight = 0.0f; float downAlteredHeight = 0.0f; float upHeight = 0.0f; if(allowLandShapeEditing(cellId)) { //Get key values for calculating average, handle cell edges, check for null pointers if (inCellX == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); } if (inCellY == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); } if (inCellX > 0) { leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); } if (inCellY > 0) { upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); } if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); } } if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); } } if (inCellX < ESM::Land::LAND_SIZE - 1) { rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); } if (inCellY < ESM::Land::LAND_SIZE - 1) { downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); } float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, - toolStrength); if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, + toolStrength); } } } void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } } if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = abs(thisHeight - targetHeight); //Cut down excessive changes if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); } void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); *thisHeight = 0.0f; // real + altered height *thisAlteredHeight = 0.0f; // only altered height *leftHeight = 0.0f; *leftAlteredHeight = 0.0f; *upHeight = 0.0f; *upAlteredHeight = 0.0f; *rightHeight = 0.0f; *rightAlteredHeight = 0.0f; *downHeight = 0.0f; *downAlteredHeight = 0.0f; if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); *thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight; // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found, // which is to prevent unnecessary action at limitHeightChange(). *leftHeight = *thisHeight; *upHeight = *thisHeight; *rightHeight = *thisHeight; *downHeight = *thisHeight; //If at edge, get values from neighboring cell if (inCellX == 0) { if(isLandLoaded(cellLeftId)) { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); *leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY)) { *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); *leftHeight += *leftAlteredHeight; } } } if (inCellY == 0) { if(isLandLoaded(cellUpId)) { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); *upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, ESM::Land::LAND_SIZE - 2)) { *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); *upHeight += *upAlteredHeight; } } } if (inCellX == ESM::Land::LAND_SIZE - 1) { if(isLandLoaded(cellRightId)) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); *rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); *rightHeight += *rightAlteredHeight; } } } if (inCellY == ESM::Land::LAND_SIZE - 1) { if(isLandLoaded(cellDownId)) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); *downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); *downHeight += *downAlteredHeight; } } } //If not at edge, get values from the same cell if (inCellX != 0) { *leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); *leftHeight += *leftAlteredHeight; } if (inCellY != 0) { *upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); *upHeight += *upAlteredHeight; } if (inCellX != ESM::Land::LAND_SIZE - 1) { *rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); *rightHeight += *rightAlteredHeight; } if (inCellY != ESM::Land::LAND_SIZE - 1) { *downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); *downHeight += *downAlteredHeight; } } } } void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) { if (limitedAlteredHeightXAxis) { if (limitedAlteredHeightYAxis) { if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); *steepnessIsWithinLimits = false; } else { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); *steepnessIsWithinLimits = false; } } else { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); *steepnessIsWithinLimits = false; } } else if (limitedAlteredHeightYAxis) { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); *steepnessIsWithinLimits = false; } } bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast (*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); int limitHeightChange = 1016.0f; // Limited by save format bool steepnessIsWithinLimits = true; if (isLandLoaded(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upHeight = 0.0f; float upAlteredHeight = 0.0f; float rightHeight = 0.0f; float rightAlteredHeight = 0.0f; float downHeight = 0.0f; float downAlteredHeight = 0.0f; if (!reverseMode) { for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) { for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (leftHeight - thisHeight > limitHeightChange) limitedAlteredHeightXAxis = std::make_unique(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (leftHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis = std::make_unique(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (upHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (upHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } if (reverseMode) { for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) { for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (rightHeight - thisHeight > limitHeightChange) limitedAlteredHeightXAxis = std::make_unique(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (rightHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis = std::make_unique(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (downHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (downHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } } return steepnessIsWithinLimits; } bool CSVRender::TerrainShapeMode::isInCellSelection(int globalSelectionX, int globalSelectionY) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { std::pair vertexCoords = std::make_pair(globalSelectionX, globalSelectionY); std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); return paged->getCellSelection().has(CSMWorld::CellCoordinates::fromId(cellId).first) && isLandLoaded(cellId); } return false; } void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections) { if (isInCellSelection(globalSelectionX, globalSelectionY)) selections->emplace_back(globalSelectionX, globalSelectionY); else { int moduloX = globalSelectionX % (ESM::Land::LAND_SIZE - 1); int moduloY = globalSelectionY % (ESM::Land::LAND_SIZE - 1); bool xIsAtCellBorder = moduloX == 0; bool yIsAtCellBorder = moduloY == 0; if (!xIsAtCellBorder && !yIsAtCellBorder) return; int selectionX = globalSelectionX; int selectionY = globalSelectionY; /* The northern and eastern edges don't belong to the current cell. If the corresponding adjacent cell is not loaded, some special handling is necessary to select border vertices. */ if (xIsAtCellBorder && yIsAtCellBorder) { /* Handle the NW, NE, and SE corner vertices. NW corner: (+1, -1) offset to reach current cell. NE corner: (-1, -1) offset to reach current cell. SE corner: (-1, +1) offset to reach current cell. */ if (isInCellSelection(globalSelectionX - 1, globalSelectionY - 1) || isInCellSelection(globalSelectionX + 1, globalSelectionY - 1) || isInCellSelection(globalSelectionX - 1, globalSelectionY + 1)) { selections->emplace_back(globalSelectionX, globalSelectionY); } } else if (xIsAtCellBorder) { selectionX--; } else if (yIsAtCellBorder) { selectionY--; } if (isInCellSelection(selectionX, selectionY)) selections->emplace_back(globalSelectionX, globalSelectionY); } } void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; if (mBrushShape == CSVWidget::BrushShape_Point) { handleSelection(vertexCoords.first, vertexCoords.second, &selections); } if (mBrushShape == CSVWidget::BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { handleSelection(i, j, &selections); } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); // Using floating-point radius here to prevent selecting too few vertices. if (distance <= mBrushSize / 2.0f) handleSelection(i, j, &selections); } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { if(!mCustomBrushShape.empty()) { for(auto const& value: mCustomBrushShape) { std::pair localVertexCoords (vertexCoords.first + value.first, vertexCoords.second + value.second); handleSelection(localVertexCoords.first, localVertexCoords.second, &selections); } } } std::string selectAction; if (selectMode == 0) selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); else selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); if (selectAction == "Select only") mTerrainShapeSelection->onlySelect(selections); else if (selectAction == "Add to selection") mTerrainShapeSelection->addSelect(selections); else if (selectAction == "Remove from selection") mTerrainShapeSelection->removeSelect(selections); else if (selectAction == "Invert selection") mTerrainShapeSelection->toggleSelect(selections); } void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); QUndoStack& undoStack = document.getUndoStack(); undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); } void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); QUndoStack& undoStack = document.getUndoStack(); undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); } bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); return cellCollection.searchId (cellId) == -1; } bool CSVRender::TerrainShapeMode::noLand(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); return landCollection.searchId (cellId) == -1; } bool CSVRender::TerrainShapeMode::noLandLoaded(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); } bool CSVRender::TerrainShapeMode::isLandLoaded(const std::string& cellId) { if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; return false; } void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordinates& cellCoords) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); float defaultHeight = 0.f; int averageDivider = 0; CSMWorld::CellCoordinates cellLeftCoords = cellCoords.move(-1, 0); CSMWorld::CellCoordinates cellRightCoords = cellCoords.move(1, 0); CSMWorld::CellCoordinates cellUpCoords = cellCoords.move(0, -1); CSMWorld::CellCoordinates cellDownCoords = cellCoords.move(0, 1); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellLeftCoords.getX(), cellLeftCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellRightCoords.getX(), cellRightCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellUpCoords.getX(), cellUpCoords.getY()); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellDownCoords.getX(), cellDownCoords.getY()); float leftCellSampleHeight = 0.0f; float rightCellSampleHeight = 0.0f; float upCellSampleHeight = 0.0f; float downCellSampleHeight = 0.0f; const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (isLandLoaded(cellLeftId)) { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); ++averageDivider; leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellRightId)) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); ++averageDivider; rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE]; if(paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellUpId)) { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); ++averageDivider; upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; if(paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); } if (isLandLoaded(cellDownId)) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); ++averageDivider; downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2]; if(paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0)) downCellSampleHeight += *paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0); } } if (averageDivider > 0) defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) / averageDivider; for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { landShapeNew[j * ESM::Land::LAND_SIZE + i] = defaultHeight; landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = 0; landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = 0; landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = 127; } } QVariant changedShape; changedShape.setValue(landShapeNew); QVariant changedNormals; changedNormals.setValue(landNormalsNew); QModelIndex indexShape(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); QModelIndex indexNormal(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); document.getUndoStack().push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexShape, changedShape)); document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals)); } bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId, bool useTool) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); if (noCell(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode=="Discard") return false; if (mode=="Create cell and land, then edit" && useTool) { auto createCommand = std::make_unique(cellTable, cellId); int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); createCommand->addNestedValue (parentIndex, index, false); document.getUndoStack().push (createCommand.release()); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } else if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) { // target cell exists, but is not shown std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); if (mode=="Discard") return false; if (mode=="Show cell and edit" && useTool) { selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } if (noLand(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode=="Discard") return false; if (mode=="Create cell and land, then edit" && useTool) { document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); sortAndLimitAlteredCells(); } } else if (noLandLoaded(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); if (mode=="Discard") return false; if (mode=="Create cell and land, then edit" && useTool) { createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); sortAndLimitAlteredCells(); } } if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId))) { Log(Debug::Warning) << "Land creation failed at cell id: " << cellId; return false; } return true; } void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { if (isLandLoaded(cellLeftId) && landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; if (isLandLoaded(cellRightId) && landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE]) landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE]; if (isLandLoaded(cellUpId) && landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]; if (isLandLoaded(cellDownId) && landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i]; } QVariant changedLand; changedLand.setValue(landShapeNew); QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); QUndoStack& undoStack = document.getUndoStack(); undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); } void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) { } void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask()); if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing)) mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape); if (!hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing)) mBrushDraw->hide(); } std::shared_ptr CSVRender::TerrainShapeMode::getTerrainSelection() { return mTerrainShapeSelection; } void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) { mBrushSize = brushSize; } void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape) { mBrushShape = brushShape; //Set custom brush shape if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); int selectionCenterX = 0; int selectionCenterY = 0; int selectionAmount = 0; for(auto const& value: terrainSelection) { selectionCenterX = selectionCenterX + value.first; selectionCenterY = selectionCenterY + value.second; ++selectionAmount; } if (selectionAmount != 0) { selectionCenterX /= selectionAmount; selectionCenterY /= selectionAmount; } mCustomBrushShape.clear(); std::pair differentialPos {}; for(auto const& value: terrainSelection) { differentialPos.first = value.first - selectionCenterX; differentialPos.second = value.second - selectionCenterY; mCustomBrushShape.push_back(differentialPos); } } } void CSVRender::TerrainShapeMode::setShapeEditTool(int shapeEditTool) { mShapeEditTool = shapeEditTool; } void CSVRender::TerrainShapeMode::setShapeEditToolStrength(int shapeEditToolStrength) { mShapeEditToolStrength = shapeEditToolStrength; } CSVRender::PagedWorldspaceWidget& CSVRender::TerrainShapeMode::getPagedWorldspaceWidget() { return dynamic_cast(getWorldspaceWidget()); } openmw-openmw-0.48.0/apps/opencs/view/render/terrainshapemode.hpp000066400000000000000000000205421445372753700251650ustar00rootroot00000000000000#ifndef CSV_RENDER_TERRAINSHAPEMODE_H #define CSV_RENDER_TERRAINSHAPEMODE_H #include "editmode.hpp" #include #include #include #include #ifndef Q_MOC_RUN #include "../../model/world/data.hpp" #include "../../model/world/land.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" #include "../widget/brushshapes.hpp" #endif #include "brushdraw.hpp" #include "terrainselection.hpp" namespace CSVWidget { class SceneToolShapeBrush; } namespace CSVRender { class PagedWorldspaceWidget; /// \brief EditMode for handling the terrain shape editing class TerrainShapeMode : public EditMode { Q_OBJECT public: enum InteractionType { InteractionType_PrimaryEdit, InteractionType_PrimarySelect, InteractionType_SecondaryEdit, InteractionType_SecondarySelect, InteractionType_None }; enum ShapeEditTool { ShapeEditTool_Drag = 0, ShapeEditTool_PaintToRaise = 1, ShapeEditTool_PaintToLower = 2, ShapeEditTool_Smooth = 3, ShapeEditTool_Flatten = 4 }; /// Editmode for terrain shape grid TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); void primaryOpenPressed (const WorldspaceHitResult& hit) override; /// Create single command for one-click shape editing void primaryEditPressed (const WorldspaceHitResult& hit) override; /// Open brush settings window void primarySelectPressed(const WorldspaceHitResult&) override; void secondarySelectPressed(const WorldspaceHitResult&) override; void activate(CSVWidget::SceneToolbar*) override; void deactivate(CSVWidget::SceneToolbar*) override; /// Start shape editing command macro bool primaryEditStartDrag (const QPoint& pos) override; bool secondaryEditStartDrag (const QPoint& pos) override; bool primarySelectStartDrag (const QPoint& pos) override; bool secondarySelectStartDrag (const QPoint& pos) override; /// Handle shape edit behavior during dragging void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; /// End shape editing command macro void dragCompleted(const QPoint& pos) override; /// Cancel shape editing, and reset all pending changes void dragAborted() override; void dragWheel (int diff, double speedFactor) override; void dragMoveEvent (QDragMoveEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; std::shared_ptr getTerrainSelection(); private: /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse void sortAndLimitAlteredCells(); /// Reset everything in the current edit void clearTransientEdits(); /// Move pending alteredHeights changes to omwgame/omwaddon -data void applyTerrainEditChanges(); /// Handle brush mechanics for shape editing void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); /// Calculate height, when aiming for bump-shaped terrain change float calculateBumpShape(float distance, int radius, float height); /// set the target height for flatten tool void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); /// Do a single height alteration for transient shape edit map void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true); /// Do a single smoothing height alteration for transient shape edit map void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength); /// Do a single flattening height alteration for transient shape edit map void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); /// Get altered height values around one vertex void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight); ///Limit steepness based on either X or Y and return false if steepness is limited void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); /// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is within limits bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); /// Check if global selection coordinate belongs to cell in view bool isInCellSelection(int globalSelectionX, int globalSelectionY); /// Select vertex at global selection coordinate void handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections); /// Handle brush mechanics for terrain shape selection void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode); /// Push terrain shape edits to command macro void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId); /// Push land normals edits to command macro void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId); bool noCell(const std::string& cellId); bool noLand(const std::string& cellId); bool noLandLoaded(const std::string& cellId); bool isLandLoaded(const std::string& cellId); /// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and set the average height based on that void createNewLandData(const CSMWorld::CellCoordinates& cellCoords); /// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for automated land changes) bool allowLandShapeEditing(const std::string& textureFileName, bool useTool = true); /// Bind the edging vertice to the values of the adjancent cells void fixEdges(CSMWorld::CellCoordinates cellCoords); std::string mBrushTexture; int mBrushSize = 1; CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; std::unique_ptr mBrushDraw; std::vector> mCustomBrushShape; CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr; int mDragMode = InteractionType_None; osg::Group* mParentNode; bool mIsEditing = false; std::shared_ptr mTerrainShapeSelection; int mTotalDiffY = 0; std::vector mAlteredCells; osg::Vec3d mEditingPos; int mShapeEditTool = ShapeEditTool_Drag; int mShapeEditToolStrength = 8; int mTargetHeight = 0; PagedWorldspaceWidget& getPagedWorldspaceWidget(); public slots: void setBrushSize(int brushSize); void setBrushShape(CSVWidget::BrushShape brushShape); void setShapeEditTool(int shapeEditTool); void setShapeEditToolStrength(int shapeEditToolStrength); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/terrainstorage.cpp000066400000000000000000000143121445372753700246550ustar00rootroot00000000000000#include "terrainstorage.hpp" #include namespace CSVRender { TerrainStorage::TerrainStorage(const CSMWorld::Data &data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) { resetHeights(); } osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) { // The cell isn't guaranteed to have Land. This is because the terrain implementation // has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY)); if (index == -1) return nullptr; const ESM::Land& land = mData.getLand().getRecord(index).get(); return new ESMTerrain::LandObject(&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { int row = mData.getLandTextures().searchId(CSMWorld::LandTexture::createUniqueRecordId(plugin, index)); if (row == -1) return nullptr; return &mData.getLandTextures().getRecord(row).get(); } void TerrainStorage::setAlteredHeight(int inCellX, int inCellY, float height) { mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] = height - fmod(height, 8); //Limit to divisible by 8 to avoid cell seam breakage } void TerrainStorage::resetHeights() { std::fill(std::begin(mAlteredHeight), std::end(mAlteredHeight), 0); } float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) { float height = 0.f; int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY)); if (index == -1) // no land! return height; const ESM::Land::LandData* landData = mData.getLand().getRecord(index).get().getLandData(ESM::Land::DATA_VHGT); height = landData->mHeights[inCellY*ESM::Land::LAND_SIZE + inCellX]; return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; } float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) { return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) { // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells throw std::runtime_error("getBounds not implemented"); } int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const { return heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; } int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const { return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)]; } int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const { return heightData->mHeights[col*ESM::Land::LAND_SIZE + row + 1] + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row + 1)]; } int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const { return heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)]; } int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const { return heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)]; } int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const { return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData)); } int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const { return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData)); } int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const { return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData)); } int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const { return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); } bool TerrainStorage::leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const { return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit || getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; } bool TerrainStorage::rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const { return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit || getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; } void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const { // Highlight broken height changes int heightWarningLimit = 1024; if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) { color.r() = 255; color.g() = 0; color.b() = 0; } } float TerrainStorage::getAlteredHeight(int col, int row) const { return mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; } } openmw-openmw-0.48.0/apps/opencs/view/render/terrainstorage.hpp000066400000000000000000000046321445372753700246660ustar00rootroot00000000000000#ifndef OPENCS_RENDER_TERRAINSTORAGE_H #define OPENCS_RENDER_TERRAINSTORAGE_H #include #include #include "../../model/world/data.hpp" namespace CSVRender { /** * @brief A bridge between the terrain component and OpenCS's terrain data storage. */ class TerrainStorage : public ESMTerrain::Storage { public: TerrainStorage(const CSMWorld::Data& data); void setAlteredHeight(int inCellX, int inCellY, float heightMap); void resetHeights(); bool useAlteration() const override { return true; } float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); float* getAlteredHeight(int inCellX, int inCellY); private: const CSMWorld::Data& mData; std::array mAlteredHeight; osg::ref_ptr getLand (int cellX, int cellY) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const; bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; float getAlteredHeight(int col, int row) const override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/terraintexturemode.cpp000066400000000000000000000757501445372753700255730ustar00rootroot00000000000000#include "terraintexturemode.hpp" #include #include #include #include #include #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetooltexturebrush.hpp" #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/landtexture.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" #include "../widget/brushshapes.hpp" #include "brushdraw.hpp" #include "editmode.hpp" #include "pagedworldspacewidget.hpp" #include "mask.hpp" #include "object.hpp" // Something small needed regarding pointers from here () #include "worldspacewidget.hpp" CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent), mBrushTexture("L0#0"), mBrushSize(1), mBrushShape(CSVWidget::BrushShape_Point), mTextureBrushScenetool(nullptr), mDragMode(InteractionType_None), mParentNode(parentNode), mIsEditing(false) { } void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar) { if(!mTextureBrushScenetool) { mTextureBrushScenetool = new CSVWidget::SceneToolTextureBrush (toolbar, "scenetooltexturebrush", getWorldspaceWidget().getDocument()); connect(mTextureBrushScenetool, SIGNAL (clicked()), mTextureBrushScenetool, SLOT (activate())); connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); connect(mTextureBrushScenetool->mTextureBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); connect(mTextureBrushScenetool, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string))); connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string))); connect(mTextureBrushScenetool, SIGNAL(passEvent(QDropEvent*)), this, SLOT(handleDropEvent(QDropEvent*))); connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool->mTextureBrushWindow, SLOT(setBrushTexture(std::string))); connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool, SLOT(updateBrushHistory(std::string))); } if (!mTerrainTextureSelection) { mTerrainTextureSelection = std::make_shared(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Texture); } if (!mBrushDraw) mBrushDraw = std::make_unique(mParentNode, true); EditMode::activate(toolbar); toolbar->addTool (mTextureBrushScenetool); } void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar) { if(mTextureBrushScenetool) { toolbar->removeTool (mTextureBrushScenetool); delete mTextureBrushScenetool; mTextureBrushScenetool = nullptr; } if (mTerrainTextureSelection) { mTerrainTextureSelection.reset(); } if (mBrushDraw) mBrushDraw.reset(); EditMode::deactivate(toolbar); } void CSVRender::TerrainTextureMode::primaryOpenPressed(const WorldspaceHitResult& hit) // Apply changes here { } void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); mCellId = getWorldspaceWidget().getCellId (hit.worldPos); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { undoStack.beginMacro ("Edit texture records"); if(allowLandTextureEditing(mCellId)) { undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); } undoStack.endMacro(); } } void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); mTerrainTextureSelection->clearTemporarySelection(); } } void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); mTerrainTextureSelection->clearTemporarySelection(); } } bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); mCellId = getWorldspaceWidget().getCellId (hit.worldPos); QUndoStack& undoStack = document.getUndoStack(); mDragMode = InteractionType_PrimaryEdit; CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { undoStack.beginMacro ("Edit texture records"); mIsEditing = true; if(allowLandTextureEditing(mCellId)) { undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); } } return true; } bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos) { return false; } bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); return true; } bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); return true; } void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == InteractionType_PrimaryEdit) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { editTerrainTextureGrid(hit); } } if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); } } void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) { if (mDragMode == InteractionType_PrimaryEdit && mIsEditing) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { undoStack.endMacro(); mIsEditing = false; } } mTerrainTextureSelection->clearTemporarySelection(); } void CSVRender::TerrainTextureMode::dragAborted() { } void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) { } void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->holdsType (CSMWorld::UniversalId::Type_LandTexture)) { const std::vector ids = mime->getData(); for (const CSMWorld::UniversalId& uid : ids) { mBrushTexture = uid.getId(); emit passBrushTexture(mBrushTexture); } } if (mime->holdsType (CSMWorld::UniversalId::Type_Texture)) { const std::vector ids = mime->getData(); for (const CSMWorld::UniversalId& uid : ids) { std::string textureFileName = uid.toString(); createTexture(textureFileName); emit passBrushTexture(mBrushTexture); } } } void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitResult& hit) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); mCellId = getWorldspaceWidget().getCellId (hit.worldPos); if(allowLandTextureEditing(mCellId)) {} std::pair cellCoordinates_pair = CSMWorld::CellCoordinates::fromId (mCellId); int cellX = cellCoordinates_pair.first.getX(); int cellY = cellCoordinates_pair.first.getY(); // The coordinates of hit in mCellId int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.25)); int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.25)); if (xHitInCell < 0) { xHitInCell = xHitInCell + landTextureSize; cellX = cellX - 1; } if (yHitInCell > 15) { yHitInCell = yHitInCell - landTextureSize; cellY = cellY + 1; } mCellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); if(allowLandTextureEditing(mCellId)) {} std::string iteratedCellId; int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex); std::size_t hashlocation = mBrushTexture.find('#'); std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1); int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1 int r = static_cast(mBrushSize) / 2; if (mBrushShape == CSVWidget::BrushShape_Point) { CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); if(allowLandTextureEditing(mCellId)) { newTerrain[yHitInCell*landTextureSize+xHitInCell] = brushInt; pushEditToCommand(newTerrain, document, landTable, mCellId); } } if (mBrushShape == CSVWidget::BrushShape_Square) { int upperLeftCellX = cellX - std::floor(r / landTextureSize); int upperLeftCellY = cellY - std::floor(r / landTextureSize); if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--; if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--; int lowerrightCellX = cellX + std::floor(r / landTextureSize); int lowerrightCellY = cellY + std::floor(r / landTextureSize); if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++; if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++; for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) { for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); if(allowLandTextureEditing(iteratedCellId)) { CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); for(int i = 0; i < landTextureSize; i++) { for(int j = 0; j < landTextureSize; j++) { if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { newTerrain[j*landTextureSize+i] = brushInt; } else { int distanceX(0); int distanceY(0); if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i; if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; if (i_cell == cellX) distanceX = abs(i-xHitInCell); if (j_cell == cellY) distanceY = abs(j-yHitInCell); if (distanceX < r && distanceY < r) newTerrain[j*landTextureSize+i] = brushInt; } } } pushEditToCommand(newTerrain, document, landTable, iteratedCellId); } } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { int upperLeftCellX = cellX - std::floor(r / landTextureSize); int upperLeftCellY = cellY - std::floor(r / landTextureSize); if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--; if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--; int lowerrightCellX = cellX + std::floor(r / landTextureSize); int lowerrightCellY = cellY + std::floor(r / landTextureSize); if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++; if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++; for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) { for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); if(allowLandTextureEditing(iteratedCellId)) { CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); for(int i = 0; i < landTextureSize; i++) { for(int j = 0; j < landTextureSize; j++) { if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { int distanceX = abs(i-xHitInCell); int distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; } else { int distanceX(0); int distanceY(0); if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i; if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; if (i_cell == cellX) distanceX = abs(i-xHitInCell); if (j_cell == cellY) distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; } } } pushEditToCommand(newTerrain, document, landTable, iteratedCellId); } } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); if(allowLandTextureEditing(mCellId) && !mCustomBrushShape.empty()) { for(auto const& value: mCustomBrushShape) { if(yHitInCell + value.second >= 0 && yHitInCell + value.second <= 15 && xHitInCell + value.first >= 0 && xHitInCell + value.first <= 15) { newTerrain[(yHitInCell+value.second)*landTextureSize+xHitInCell+value.first] = brushInt; } else { int cellXDifference = std::floor(1.0f*(xHitInCell + value.first)/landTextureSize); int cellYDifference = std::floor(1.0f*(yHitInCell + value.second)/landTextureSize); int xInOtherCell = xHitInCell + value.first - cellXDifference * landTextureSize; int yInOtherCell = yHitInCell + value.second - cellYDifference * landTextureSize; std::string cellId = CSMWorld::CellCoordinates::generateId(cellX+cellXDifference, cellY+cellYDifference); if (allowLandTextureEditing(cellId)) { CSMWorld::LandTexturesColumn::DataType newTerrainPointerOtherCell = landTable.data(landTable.getModelIndex(cellId, textureColumn)).value(); CSMWorld::LandTexturesColumn::DataType newTerrainOtherCell(newTerrainPointerOtherCell); newTerrainOtherCell[yInOtherCell*landTextureSize+xInOtherCell] = brushInt; pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); } } } pushEditToCommand(newTerrain, document, landTable, mCellId); } } } bool CSVRender::TerrainTextureMode::isInCellSelection(int globalSelectionX, int globalSelectionY) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { std::pair textureCoords = std::make_pair(globalSelectionX, globalSelectionY); std::string cellId = CSMWorld::CellCoordinates::textureGlobalToCellId(textureCoords); return paged->getCellSelection().has(CSMWorld::CellCoordinates::fromId(cellId).first); } return false; } void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; if (mBrushShape == CSVWidget::BrushShape_Point) { if (isInCellSelection(texCoords.first, texCoords.second)) selections.emplace_back(texCoords); } if (mBrushShape == CSVWidget::BrushShape_Square) { for (int i = -r; i <= r; i++) { for (int j = -r; j <= r; j++) { int x = i + texCoords.first; int y = j + texCoords.second; if (isInCellSelection(x, y)) { selections.emplace_back(x, y); } } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { for (int i = -r; i <= r; i++) { for (int j = -r; j <= r; j++) { osg::Vec2f coords(i,j); float rf = static_cast(mBrushSize) / 2; if (std::round(coords.length()) < rf) { int x = i + texCoords.first; int y = j + texCoords.second; if (isInCellSelection(x, y)) { selections.emplace_back(x, y); } } } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { if(!mCustomBrushShape.empty()) { for(auto const& value: mCustomBrushShape) { int x = texCoords.first + value.first; int y = texCoords.second + value.second; if (isInCellSelection(x, y)) { selections.emplace_back(x, y); } } } } std::string selectAction; if (selectMode == 0) selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); else selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); if (selectAction == "Select only") mTerrainTextureSelection->onlySelect(selections); else if (selectAction == "Add to selection") mTerrainTextureSelection->addSelect(selections); else if (selectAction == "Remove from selection") mTerrainTextureSelection->removeSelect(selections); else if (selectAction == "Invert selection") mTerrainTextureSelection->toggleSelect(selections); } void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, std::string cellId) { CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); QVariant changedLand; changedLand.setValue(newLandGrid); QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandTexturesIndex))); QUndoStack& undoStack = document.getUndoStack(); undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); } void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& ltexTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); QUndoStack& undoStack = document.getUndoStack(); std::string newId; int counter=0; bool freeIndexFound = false; do { const size_t maxCounter = std::numeric_limits::max() - 1; try { newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter; } catch (const std::exception&) { newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); freeIndexFound = true; } } while (freeIndexFound == false); std::size_t idlocation = textureFileName.find("Texture: "); textureFileName = textureFileName.substr (idlocation + 9); QVariant textureNameVariant; QVariant textureFileNameVariant; textureFileNameVariant.setValue(QString::fromStdString(textureFileName)); undoStack.beginMacro ("Add land texture record"); undoStack.push (new CSMWorld::CreateCommand (ltexTable, newId)); QModelIndex index(ltexTable.getModelIndex (newId, ltexTable.findColumnIndex (CSMWorld::Columns::ColumnId_Texture))); undoStack.push (new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); undoStack.endMacro(); mBrushTexture = newId; } bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); bool noCell = document.getData().getCells().searchId (cellId)==-1; bool noLand = document.getData().getLand().searchId (cellId)==-1; if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode=="Discard") return false; if (mode=="Create cell and land, then edit") { auto createCommand = std::make_unique(cellTable, cellId); int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); createCommand->addNestedValue (parentIndex, index, false); document.getUndoStack().push (createCommand.release()); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } else if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) { // target cell exists, but is not shown std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); if (mode=="Discard") return false; if (mode=="Show cell and edit") { selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } } } if (noLand) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode=="Discard") return false; if (mode=="Create cell and land, then edit") { document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); } } return true; } void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) { } void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask()); if (hit.hit && mBrushDraw) mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape); if (!hit.hit && mBrushDraw) mBrushDraw->hide(); } std::shared_ptr CSVRender::TerrainTextureMode::getTerrainSelection() { return mTerrainTextureSelection; } void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) { mBrushSize = brushSize; } void CSVRender::TerrainTextureMode::setBrushShape(CSVWidget::BrushShape brushShape) { mBrushShape = brushShape; //Set custom brush shape if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainTextureSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainTextureSelection->getTerrainSelection(); int selectionCenterX = 0; int selectionCenterY = 0; int selectionAmount = 0; for(auto const& value: terrainSelection) { selectionCenterX += value.first; selectionCenterY += value.second; ++selectionAmount; } if (selectionAmount != 0) { selectionCenterX /= selectionAmount; selectionCenterY /= selectionAmount; } mCustomBrushShape.clear(); for (auto const& value: terrainSelection) mCustomBrushShape.emplace_back(value.first - selectionCenterX, value.second - selectionCenterY); } } void CSVRender::TerrainTextureMode::setBrushTexture(std::string brushTexture) { mBrushTexture = brushTexture; } openmw-openmw-0.48.0/apps/opencs/view/render/terraintexturemode.hpp000066400000000000000000000111171445372753700255630ustar00rootroot00000000000000#ifndef CSV_RENDER_TERRAINTEXTUREMODE_H #define CSV_RENDER_TERRAINTEXTUREMODE_H #include "editmode.hpp" #include #include #include #include #ifndef Q_MOC_RUN #include "../../model/world/data.hpp" #include "../../model/world/land.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" #include "../widget/brushshapes.hpp" #include "brushdraw.hpp" #endif #include "terrainselection.hpp" namespace osg { class Group; } namespace CSVWidget { class SceneToolTextureBrush; } namespace CSVRender { class TerrainTextureMode : public EditMode { Q_OBJECT public: enum InteractionType { InteractionType_PrimaryEdit, InteractionType_PrimarySelect, InteractionType_SecondaryEdit, InteractionType_SecondarySelect, InteractionType_None }; /// \brief Editmode for terrain texture grid TerrainTextureMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); void primaryOpenPressed (const WorldspaceHitResult& hit) override; /// \brief Create single command for one-click texture editing void primaryEditPressed (const WorldspaceHitResult& hit) override; /// \brief Open brush settings window void primarySelectPressed(const WorldspaceHitResult&) override; void secondarySelectPressed(const WorldspaceHitResult&) override; void activate(CSVWidget::SceneToolbar*) override; void deactivate(CSVWidget::SceneToolbar*) override; /// \brief Start texture editing command macro bool primaryEditStartDrag (const QPoint& pos) override; bool secondaryEditStartDrag (const QPoint& pos) override; bool primarySelectStartDrag (const QPoint& pos) override; bool secondarySelectStartDrag (const QPoint& pos) override; /// \brief Handle texture edit behavior during dragging void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; /// \brief End texture editing command macro void dragCompleted(const QPoint& pos) override; void dragAborted() override; void dragWheel (int diff, double speedFactor) override; void dragMoveEvent (QDragMoveEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; std::shared_ptr getTerrainSelection(); private: /// \brief Handle brush mechanics, maths regarding worldspace hit etc. void editTerrainTextureGrid (const WorldspaceHitResult& hit); /// \brief Check if global selection coordinate belongs to cell in view bool isInCellSelection(int globalSelectionX, int globalSelectionY); /// \brief Handle brush mechanics for texture selection void selectTerrainTextures (const std::pair& texCoords, unsigned char selectMode); /// \brief Push texture edits to command macro void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, std::string cellId); /// \brief Create new land texture record from texture asset void createTexture(std::string textureFileName); /// \brief Create new cell and land if needed bool allowLandTextureEditing(std::string textureFileName); std::string mCellId; std::string mBrushTexture; int mBrushSize; CSVWidget::BrushShape mBrushShape; std::unique_ptr mBrushDraw; std::vector> mCustomBrushShape; CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool; int mDragMode; osg::Group* mParentNode; bool mIsEditing; std::shared_ptr mTerrainTextureSelection; const int cellSize {ESM::Land::REAL_SIZE}; const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; signals: void passBrushTexture(std::string brushTexture); public slots: void handleDropEvent(QDropEvent *event); void setBrushSize(int brushSize); void setBrushShape(CSVWidget::BrushShape brushShape); void setBrushTexture(std::string brushShape); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/unpagedworldspacewidget.cpp000066400000000000000000000245541445372753700265500ustar00rootroot00000000000000#include "unpagedworldspacewidget.hpp" #include #include #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/tablemimedata.hpp" #include "../widget/scenetooltoggle2.hpp" #include "cameracontroller.hpp" #include "mask.hpp" #include "tagbase.hpp" void CSVRender::UnpagedWorldspaceWidget::update() { const CSMWorld::Record& record = dynamic_cast&> (mCellsModel->getRecord (mCellId)); osg::Vec4f colour = SceneUtil::colourFromRGB(record.get().mAmbi.mAmbient); setDefaultAmbient (colour); bool isInterior = (record.get().mData.mFlags & ESM::Cell::Interior) != 0; bool behaveLikeExterior = (record.get().mData.mFlags & ESM::Cell::QuasiEx) != 0; setExterior(behaveLikeExterior || !isInterior); /// \todo deal with mSunlight and mFog/mForDensity flagAsModified(); } CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) : WorldspaceWidget (document, parent), mDocument(document), mCellId (cellId) { mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); mReferenceablesModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); connect (&document.getData(), SIGNAL (assetTablesChanged ()), this, SLOT (assetTablesChanged ())); update(); mCell = std::make_unique(document.getData(), mRootNode, mCellId); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { int index = mCellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, index); if (cellIndex.row()>=topLeft.row() && cellIndex.row()<=bottomRight.row()) { if (mCellsModel->data (cellIndex).toInt()==CSMWorld::RecordBase::State_Deleted) { emit closeRequest(); } else { /// \todo possible optimisation: check columns and update only if relevant columns have /// changed update(); } } } void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); if (cellIndex.row()>=start && cellIndex.row()<=end) emit closeRequest(); } void CSVRender::UnpagedWorldspaceWidget::assetTablesChanged() { if (mCell) mCell->reloadAssets(); } bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { if (WorldspaceWidget::handleDrop (universalIdData, type)) return true; if (type!=Type_CellsInterior) return false; mCellId = universalIdData.begin()->getId(); mCell = std::make_unique(getDocument().getData(), mRootNode, mCellId); mCamPositionSet = false; mOrbitCamControl->reset(); update(); emit cellChanged(*universalIdData.begin()); return true; } void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) { mCell->setSelection (elementMask, Cell::Selection_Clear); flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::invertSelection (int elementMask) { mCell->setSelection (elementMask, Cell::Selection_Invert); flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) { mCell->setSelection (elementMask, Cell::Selection_All); flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) { mCell->selectAllWithSameParentId (elementMask); flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { mCell->selectInsideCube (pointA, pointB, dragMode); } void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) { mCell->selectWithinDistance (point, distance, dragMode); } std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; } CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& point) const { return mCell.get(); } CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const { return mCell.get(); } std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { return mCell->getSelection (elementMask); } std::vector > CSVRender::UnpagedWorldspaceWidget::getEdited ( unsigned int elementMask) const { return mCell->getEdited (elementMask); } void CSVRender::UnpagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) { mCell->setSubMode (subMode, elementMask); } void CSVRender::UnpagedWorldspaceWidget::reset (unsigned int elementMask) { mCell->reset (elementMask); } void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mCell.get()) if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::referenceableAboutToBeRemoved ( const QModelIndex& parent, int start, int end) { if (mCell.get()) if (mCell.get()->referenceableAboutToBeRemoved (parent, start, end)) flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, int start, int end) { if (mCell.get()) { QModelIndex topLeft = mReferenceablesModel->index (start, 0); QModelIndex bottomRight = mReferenceablesModel->index (end, mReferenceablesModel->columnCount()); if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); } } void CSVRender::UnpagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mCell.get()) if (mCell.get()->referenceDataChanged (topLeft, bottomRight)) flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (mCell.get()) if (mCell.get()->referenceAboutToBeRemoved (parent, start, end)) flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, int end) { if (mCell.get()) if (mCell.get()->referenceAdded (parent, start, end)) flagAsModified(); } void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); int rowStart = -1; int rowEnd = -1; if (topLeft.parent().isValid()) { rowStart = topLeft.parent().row(); rowEnd = bottomRight.parent().row(); } else { rowStart = topLeft.row(); rowEnd = bottomRight.row(); } for (int row = rowStart; row <= rowEnd; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); if (mCellId == pathgrid.mId) { mCell->pathgridModified(); flagAsModified(); return; } } } void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { // Pathgrid going to be deleted for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); if (mCellId == pathgrid.mId) { mCell->pathgridRemoved(); flagAsModified(); return; } } } } void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); if (mCellId == pathgrid.mId) { mCell->pathgridModified(); flagAsModified(); return; } } } } void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { WorldspaceWidget::addVisibilitySelectorButtons (tool); tool->addButton (Button_Terrain, Mask_Terrain, "Terrain", "", true); tool->addButton (Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { osg::Vec3d eye, center, up; mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d position = eye; std::ostringstream stream; stream << "player->positionCell " << position.x() << ", " << position.y() << ", " << position.z() << ", 0, \"" << mCellId << "\""; return stream.str(); } CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const { dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); if (requirements!=ignored) return requirements; switch(type) { case Type_CellsInterior: return canHandle; case Type_CellsExterior: return needPaged; default: return ignored; } } openmw-openmw-0.48.0/apps/opencs/view/render/unpagedworldspacewidget.hpp000066400000000000000000000101361445372753700265440ustar00rootroot00000000000000#ifndef OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #include #include #include "worldspacewidget.hpp" #include "cell.hpp" class QModelIndex; namespace CSMDoc { class Document; } namespace CSMWorld { class IdTable; class CellCoordinates; } namespace CSVRender { class UnpagedWorldspaceWidget : public WorldspaceWidget { Q_OBJECT CSMDoc::Document& mDocument; std::string mCellId; CSMWorld::IdTable *mCellsModel; CSMWorld::IdTable *mReferenceablesModel; std::unique_ptr mCell; void update(); public: UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget *parent); dropRequirments getDropRequirements(DropType type) const override; /// \return Drop handled? bool handleDrop (const std::vector& data, DropType type) override; /// \param elementMask Elements to be affected by the clear operation void clearSelection (int elementMask) override; /// \param elementMask Elements to be affected by the select operation void invertSelection (int elementMask) override; /// \param elementMask Elements to be affected by the select operation void selectAll (int elementMask) override; // Select everything that references the same ID as at least one of the elements // already selected // /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; std::vector > getSelection (unsigned int elementMask) const override; std::vector > getEdited (unsigned int elementMask) const override; void setSubMode (int subMode, unsigned int elementMask) override; /// Erase all overrides and restore the visual representation to its true state. void reset (unsigned int elementMask) override; private: void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void referenceableAdded (const QModelIndex& index, int start, int end) override; void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void referenceAdded (const QModelIndex& index, int start, int end) override; void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; void pathgridAdded (const QModelIndex& parent, int start, int end) override; std::string getStartupInstruction() override; protected: void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool) override; private slots: void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); void assetTablesChanged (); signals: void cellChanged(const CSMWorld::UniversalId& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/render/worldspacewidget.cpp000066400000000000000000000647311445372753700252050ustar00rootroot00000000000000#include "worldspacewidget.hpp" #include #include #include #include #include #include #include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../model/prefs/state.hpp" #include "../render/orbitcameramode.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" #include "object.hpp" #include "mask.hpp" #include "instancemode.hpp" #include "pathgridmode.hpp" #include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false) , mSceneElements(nullptr) , mRun(nullptr) , mDocument(document) , mInteractionMask (0) , mEditMode (nullptr) , mLocked (false) , mDragMode(InteractionType_None) , mDragging (false) , mDragX(0) , mDragY(0) , mSpeedMode(false) , mDragFactor(0) , mDragWheelFactor(0) , mDragShiftFactor(0) , mToolTipPos (-1, -1) , mShowToolTips(false) , mToolTipDelay(0) , mInConstructor(true) { setAcceptDrops(true); QAbstractItemModel *referenceables = document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables); connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); connect (referenceables, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceableAdded (const QModelIndex&, int, int))); QAbstractItemModel *references = document.getData().getTableModel (CSMWorld::UniversalId::Type_References); connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceAdded (const QModelIndex&, int, int))); QAbstractItemModel *pathgrids = document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrids); connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (pathgridAdded (const QModelIndex&, int, int))); QAbstractItemModel *debugProfiles = document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); connect (debugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); mToolTipDelayTimer.setSingleShot (true); connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); // Shortcuts CSMPrefs::Shortcut* primaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-primary", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, this); CSMPrefs::Shortcut* primaryOpenShortcut = new CSMPrefs::Shortcut("scene-open-primary", this); connect(primaryOpenShortcut, SIGNAL(activated(bool)), this, SLOT(primaryOpen(bool))); connect(primaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(primaryEdit(bool))); connect(primaryEditShortcut, SIGNAL(secondary(bool)), this, SLOT(speedMode(bool))); CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this); connect(secondaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(secondaryEdit(bool))); CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this); connect(primarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(primarySelect(bool))); CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this); connect(secondarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(secondarySelect(bool))); CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); mInConstructor = false; } CSVRender::WorldspaceWidget::~WorldspaceWidget () { } void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="3D Scene Input/drag-factor") mDragFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-wheel-factor") 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 tag : selection) { if (auto objTag = dynamic_cast(tag.get())) objTag->mObject->setMarkerTransparency(alpha); } } else if (*setting=="Tooltips/scene-delay") mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") mShowToolTips = setting->isTrue(); else SceneWidget::settingChanged(setting); } void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { selectNavigationMode("1st"); } void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() { std::vector > selection = getSelection(~0u); for (std::vector >::iterator it = selection.begin(); it!=selection.end(); ++it) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (it->get())) { mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); } } } CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( CSVWidget::SceneToolbar *parent) { CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Camera Mode"); /// \todo replace icons /// \todo consider user-defined button-mapping tool->addButton (":scenetoolbar/1st-person", "1st", "First Person" "
  • Camera is held upright
  • " "
  • Mouse-Look while holding {scene-navi-primary}
  • " "
  • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary}
  • " "
  • Mouse wheel moves the camera forward/backward
  • " "
  • Hold {scene-speed-modifier} to speed up movement
  • " "
"); tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
  • Mouse-Look while holding {scene-navi-primary}
  • " "
  • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
  • " "
  • Roll camera with {free-roll-left} and {free-roll-right} keys
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary}
  • " "
  • Mouse wheel moves the camera forward/backward
  • " "
  • Hold {free-forward:mod} to speed up movement
  • " "
"); tool->addButton( new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"), "Orbiting Camera" "
  • Always facing the centre point
  • " "
  • Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving " "the mouse while holding {scene-navi-primary}
  • " "
  • Roll camera with {orbit-roll-left} and {orbit-roll-right} keys
  • " "
  • Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre point)
  • " "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " "
  • Hold {scene-speed-modifier} to speed up movement
  • " "
", tool), "orbit"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectNavigationMode (const std::string&))); return tool; } CSVWidget::SceneToolToggle2 *CSVRender::WorldspaceWidget::makeSceneVisibilitySelector (CSVWidget::SceneToolbar *parent) { mSceneElements = new CSVWidget::SceneToolToggle2 (parent, "Scene Element Visibility", ":scenetoolbar/scene-view-c", ":scenetoolbar/scene-view-"); addVisibilitySelectorButtons (mSceneElements); mSceneElements->setSelectionMask (0xffffffff); connect (mSceneElements, SIGNAL (selectionChanged()), this, SLOT (elementSelectionChanged())); return mSceneElements; } CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( CSVWidget::SceneToolbar *parent) { CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); std::vector profiles; int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); int defaultColumn = debugProfiles.findColumnIndex ( CSMWorld::Columns::ColumnId_DefaultProfile); int size = debugProfiles.rowCount(); for (int i=0; i& data) { DropType output = Type_Other; for (std::vector::const_iterator iter (data.begin()); iter!=data.end(); ++iter) { DropType type = Type_Other; if (iter->getType()==CSMWorld::UniversalId::Type_Cell || iter->getType()==CSMWorld::UniversalId::Type_Cell_Missing) { type = iter->getId().substr (0, 1)=="#" ? Type_CellsExterior : Type_CellsInterior; } else if (iter->getType()==CSMWorld::UniversalId::Type_DebugProfile) type = Type_DebugProfile; if (iter==data.begin()) output = type; else if (output!=type) // mixed types -> ignore return Type_Other; } return output; } CSVRender::WorldspaceWidget::dropRequirments CSVRender::WorldspaceWidget::getDropRequirements (DropType type) const { if (type==Type_DebugProfile) return canHandle; return ignored; } bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { if (type==Type_DebugProfile) { if (mRun) { for (std::vector::const_iterator iter (universalIdData.begin()); iter!=universalIdData.end(); ++iter) mRun->addProfile (iter->getId()); } return true; } return false; } unsigned int CSVRender::WorldspaceWidget::getVisibilityMask() const { return mSceneElements->getSelectionMask(); } void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) { mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; } unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const { return mInteractionMask & getVisibilityMask(); } void CSVRender::WorldspaceWidget::setEditLock (bool locked) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); } void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { tool->addButton (Button_Reference, Mask_Reference, "Instances"); tool->addButton (Button_Water, Mask_Water, "Water"); tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, mRootNode, tool), "object"); tool->addButton (new PathgridMode (this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() { return mDocument; } CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, unsigned int interactionMask) const { // (0,0) is considered the lower left corner of an OpenGL window int x = localPos.x(); int y = height() - localPos.y(); // Convert from screen space to world space osg::Matrixd wpvMat; wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); wpvMat.preMult (mView->getCamera()->getViewMatrix()); wpvMat = osg::Matrixd::inverse (wpvMat); 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; // Get intersection osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMask(interactionMask); 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; // reject back-facing polygons if (direction * intersection.getWorldIntersectNormal() > 0) { continue; } for (std::vector::iterator nodeIter = intersection.nodePath.begin(); nodeIter != intersection.nodePath.end(); ++nodeIter) { osg::Node* node = *nodeIter; if (osg::ref_ptr tag = dynamic_cast(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; } } // 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; } // Default placement direction.normalize(); direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt(); WorldspaceHitResult hit = { false, nullptr, 0, 0, 0, start + direction }; return hit; } CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() { return dynamic_cast (mEditMode->getCurrent()); } void CSVRender::WorldspaceWidget::abortDrag() { if (mDragging) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragAborted(); mDragMode = InteractionType_None; } } void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else dynamic_cast (*mEditMode->getCurrent()).dragEnterEvent (event); } } void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else dynamic_cast (*mEditMode->getCurrent()).dragMoveEvent (event); } } void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument (mDocument)) { if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) { emit dataDropped(mime->getData()); } else dynamic_cast (*mEditMode->getCurrent()).dropEvent (event); } } void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) { mDocument.startRunning (profile, getStartupInstruction()); } void CSVRender::WorldspaceWidget::debugProfileDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (!mRun) return; CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); for (int i=topLeft.row(); i<=bottomRight.row(); ++i) { int state = debugProfiles.data (debugProfiles.index (i, stateColumn)).toInt(); // As of version 0.33 this case can not happen because debug profiles exist only in // project or session scope, which means they will never be in deleted state. But we // are adding the code for the sake of completeness and to avoid surprises if debug // profile ever get extended to content scope. if (state==CSMWorld::RecordBase::State_Deleted) mRun->removeProfile (debugProfiles.data ( debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); } } void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (parent.isValid()) return; if (!mRun) return; CSMWorld::IdTable& debugProfiles = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); for (int i=start; i<=end; ++i) { mRun->removeProfile (debugProfiles.data ( debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); } } void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); mDragging = false; mDragMode = InteractionType_None; } void CSVRender::WorldspaceWidget::showToolTip() { if (mShowToolTips) { QPoint pos = QCursor::pos(); WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); QToolTip::showText(pos, hit.tag->getToolTip(hideBasics, hit), this); } } } void CSVRender::WorldspaceWidget::elementSelectionChanged() { setVisibilityMask (getVisibilityMask()); flagAsModified(); updateOverlay(); } void CSVRender::WorldspaceWidget::updateOverlay() { } void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { dynamic_cast (*mEditMode->getCurrent()).mouseMoveEvent (event); if (mDragging) { int diffX = event->x() - mDragX; int diffY = (height() - event->y()) - mDragY; mDragX = event->x(); mDragY = height() - event->y(); double factor = mDragFactor; if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.drag (event->pos(), diffX, diffY, factor); } else if (mDragMode != InteractionType_None) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (mDragMode == InteractionType_PrimaryEdit) mDragging = editMode.primaryEditStartDrag (event->pos()); else if (mDragMode == InteractionType_SecondaryEdit) mDragging = editMode.secondaryEditStartDrag (event->pos()); else if (mDragMode == InteractionType_PrimarySelect) mDragging = editMode.primarySelectStartDrag (event->pos()); else if (mDragMode == InteractionType_SecondarySelect) mDragging = editMode.secondarySelectStartDrag (event->pos()); if (mDragging) { mDragX = event->localPos().x(); mDragY = height() - event->localPos().y(); } } else { if (event->globalPos()!=mToolTipPos) { mToolTipPos = event->globalPos(); if (mShowToolTips) { QToolTip::hideText(); mToolTipDelayTimer.start (mToolTipDelay); } } SceneWidget::mouseMoveEvent(event); } } void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { if (mDragging) { double factor = mDragWheelFactor; if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragWheel (event->angleDelta().y(), factor); } else SceneWidget::wheelEvent(event); } void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (type == InteractionType_PrimaryEdit) editMode.primaryEditPressed (hit); else if (type == InteractionType_SecondaryEdit) editMode.secondaryEditPressed (hit); else if (type == InteractionType_PrimarySelect) editMode.primarySelectPressed (hit); else if (type == InteractionType_SecondarySelect) editMode.secondarySelectPressed (hit); else if (type == InteractionType_PrimaryOpen) editMode.primaryOpenPressed (hit); } void CSVRender::WorldspaceWidget::primaryOpen(bool activate) { handleInteraction(InteractionType_PrimaryOpen, activate); } void CSVRender::WorldspaceWidget::primaryEdit(bool activate) { handleInteraction(InteractionType_PrimaryEdit, activate); } void CSVRender::WorldspaceWidget::secondaryEdit(bool activate) { handleInteraction(InteractionType_SecondaryEdit, activate); } void CSVRender::WorldspaceWidget::primarySelect(bool activate) { handleInteraction(InteractionType_PrimarySelect, activate); } void CSVRender::WorldspaceWidget::secondarySelect(bool activate) { handleInteraction(InteractionType_SecondarySelect, activate); } void CSVRender::WorldspaceWidget::speedMode(bool activate) { mSpeedMode = activate; } void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) { if (activate) { if (!mDragging) mDragMode = type; } else { mDragMode = InteractionType_None; if (mDragging) { EditMode* editMode = getEditMode(); editMode->dragCompleted(mapFromGlobal(QCursor::pos())); mDragging = false; } else { WorldspaceHitResult hit = mousePick(mapFromGlobal(QCursor::pos()), getInteractionMask()); handleInteractionPress(hit, type); } } } openmw-openmw-0.48.0/apps/opencs/view/render/worldspacewidget.hpp000066400000000000000000000242121445372753700252000ustar00rootroot00000000000000#ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H #include #include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "instancedragmodes.hpp" #include "scenewidget.hpp" #include "mask.hpp" namespace CSMPrefs { class Setting; } namespace CSMWorld { class CellCoordinates; class UniversalId; } namespace CSVWidget { class SceneToolMode; class SceneToolToggle2; class SceneToolbar; class SceneToolRun; } namespace CSVRender { class TagBase; class Cell; class CellArrow; class EditMode; struct WorldspaceHitResult { bool hit; osg::ref_ptr tag; unsigned int index0, index1, index2; // indices of mesh vertices osg::Vec3d worldPos; }; class WorldspaceWidget : public SceneWidget { Q_OBJECT CSVWidget::SceneToolToggle2 *mSceneElements; CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; unsigned int mInteractionMask; CSVWidget::SceneToolMode *mEditMode; bool mLocked; int mDragMode; bool mDragging; int mDragX; int mDragY; bool mSpeedMode; double mDragFactor; double mDragWheelFactor; double mDragShiftFactor; QTimer mToolTipDelayTimer; QPoint mToolTipPos; bool mShowToolTips; int mToolTipDelay; bool mInConstructor; public: enum DropType { Type_CellsInterior, Type_CellsExterior, Type_Other, Type_DebugProfile }; enum dropRequirments { canHandle, needPaged, needUnpaged, ignored //either mixed cells, or not cells }; enum InteractionType { InteractionType_PrimaryEdit, InteractionType_PrimarySelect, InteractionType_SecondaryEdit, InteractionType_SecondarySelect, InteractionType_PrimaryOpen, InteractionType_None }; WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = nullptr); ~WorldspaceWidget (); CSVWidget::SceneToolMode *makeNavigationSelector (CSVWidget::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. CSVWidget::SceneToolToggle2 *makeSceneVisibilitySelector ( CSVWidget::SceneToolbar *parent); /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. CSVWidget::SceneToolRun *makeRunTool (CSVWidget::SceneToolbar *parent); /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. CSVWidget::SceneToolMode *makeEditModeSelector (CSVWidget::SceneToolbar *parent); void selectDefaultNavigationMode(); void centerOrbitCameraOnSelection(); static DropType getDropType(const std::vector& data); virtual dropRequirments getDropRequirements(DropType type) const; virtual void useViewHint (const std::string& hint); ///< Default-implementation: ignored. /// \return Drop handled? virtual bool handleDrop (const std::vector& data, DropType type); virtual unsigned int getVisibilityMask() const; /// \note This function will implicitly add elements that are independent of the /// selected edit mode. virtual void setInteractionMask (unsigned int mask); /// \note This function will only return those elements that are both visible and /// marked for interaction. unsigned int getInteractionMask() const; virtual void setEditLock (bool locked); CSMDoc::Document& getDocument(); /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; /// \param elementMask Elements to be affected by the select operation virtual void invertSelection (int elementMask) = 0; /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask) = 0; // Select everything that references the same ID as at least one of the elements // already selected // /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; /// 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 /// returned. WorldspaceHitResult mousePick (const QPoint& localPos, unsigned int interactionMask) const; virtual std::string getCellId (const osg::Vec3f& point) const = 0; /// \note Returns the cell if it exists, otherwise a null pointer virtual Cell* getCell(const osg::Vec3d& point) const = 0; virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0; virtual std::vector > getSelection (unsigned int elementMask) const = 0; virtual std::vector > getEdited (unsigned int elementMask) const = 0; virtual void setSubMode (int subMode, unsigned int elementMask) = 0; /// Erase all overrides and restore the visual representation to its true state. virtual void reset (unsigned int elementMask) = 0; EditMode *getEditMode(); protected: /// Visual elements in a scene /// @note do not change the enumeration values, they are used in pre-existing button file names! enum ButtonId { Button_Reference = 0x1, Button_Pathgrid = 0x2, Button_Water = 0x4, Button_Fog = 0x8, Button_Terrain = 0x10 }; virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); virtual void updateOverlay(); void mouseMoveEvent (QMouseEvent *event) override; void wheelEvent (QWheelEvent *event) override; virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); void settingChanged (const CSMPrefs::Setting *setting) override; bool getSpeedMode(); private: void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent* event) override; void dragMoveEvent(QDragMoveEvent *event) override; virtual std::string getStartupInstruction() = 0; void handleInteraction(InteractionType type, bool activate); public slots: /// \note Drags will be automatically aborted when the aborting is triggered /// (either explicitly or implicitly) from within this class. This function only /// needs to be called, when the drag abort is triggered externally (e.g. from /// an edit mode). void abortDrag(); private slots: virtual void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; virtual void referenceableAdded (const QModelIndex& index, int start, int end) = 0; virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; virtual void pathgridAdded (const QModelIndex& parent, int start, int end) = 0; virtual void runRequest (const std::string& profile); void debugProfileDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); void editModeChanged (const std::string& id); void showToolTip(); void primaryOpen(bool activate); void primaryEdit(bool activate); void secondaryEdit(bool activate); void primarySelect(bool activate); void secondarySelect(bool activate); void speedMode(bool activate); protected slots: void elementSelectionChanged(); signals: void closeRequest(); void dataDropped(const std::vector& data); void requestFocus (const std::string& id); friend class MouseState; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/000077500000000000000000000000001445372753700210005ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/tools/merge.cpp000066400000000000000000000074101445372753700226050ustar00rootroot00000000000000 #include "merge.hpp" #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/doc/documentmanager.hpp" #include "../doc/filewidget.hpp" #include "../doc/adjusterwidget.hpp" void CSVTools::Merge::keyPressEvent (QKeyEvent *event) { if (event->key()==Qt::Key_Escape) { event->accept(); cancel(); } else QWidget::keyPressEvent (event); } CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) : QWidget (parent), mDocument (nullptr), mDocumentManager (documentManager) { setWindowTitle ("Merge Content Files into a new Game File"); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout (mainLayout); QSplitter *splitter = new QSplitter (Qt::Horizontal, this); mainLayout->addWidget (splitter, 1); // left panel (files to be merged) QWidget *left = new QWidget (this); left->setContentsMargins (0, 0, 0, 0); splitter->addWidget (left); QVBoxLayout *leftLayout = new QVBoxLayout; left->setLayout (leftLayout); leftLayout->addWidget (new QLabel ("Files to be merged", this)); mFiles = new QListWidget (this); leftLayout->addWidget (mFiles, 1); // right panel (new game file) QWidget *right = new QWidget (this); right->setContentsMargins (0, 0, 0, 0); splitter->addWidget (right); QVBoxLayout *rightLayout = new QVBoxLayout; rightLayout->setAlignment (Qt::AlignTop); right->setLayout (rightLayout); rightLayout->addWidget (new QLabel ("New game file", this)); mNewFile = new CSVDoc::FileWidget (this); mNewFile->setType (false); mNewFile->extensionLabelIsVisible (true); rightLayout->addWidget (mNewFile); mAdjuster = new CSVDoc::AdjusterWidget (this); rightLayout->addWidget (mAdjuster); connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), mAdjuster, SLOT (setName (const QString&, bool))); connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); // buttons QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); mOkay = new QPushButton ("Merge", this); connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); mOkay->setDefault (true); buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); mainLayout->addWidget (buttons); } void CSVTools::Merge::configure (CSMDoc::Document *document) { mDocument = document; mNewFile->setName (""); // content files while (mFiles->count()) delete mFiles->takeItem (0); std::vector files = document->getContentFiles(); for (std::vector::const_iterator iter (files.begin()); iter!=files.end(); ++iter) mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); } void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) { mAdjuster->setLocalData (localData); } CSMDoc::Document *CSVTools::Merge::getDocument() const { return mDocument; } void CSVTools::Merge::cancel() { mDocument = nullptr; hide(); } void CSVTools::Merge::accept() { if ((mDocument->getState() & CSMDoc::State_Merging)==0) { std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); std::unique_ptr target ( mDocumentManager.makeDocument (files, files[0], true)); mDocument->runMerge (std::move(target)); hide(); } } void CSVTools::Merge::stateChanged (bool valid) { mOkay->setEnabled (valid); } openmw-openmw-0.48.0/apps/opencs/view/tools/merge.hpp000066400000000000000000000022771445372753700226200ustar00rootroot00000000000000#ifndef CSV_TOOLS_MERGE_H #define CSV_TOOLS_MERGE_H #include #ifndef Q_MOC_RUN #include #endif class QPushButton; class QListWidget; namespace CSMDoc { class Document; class DocumentManager; } namespace CSVDoc { class FileWidget; class AdjusterWidget; } namespace CSVTools { class Merge : public QWidget { Q_OBJECT CSMDoc::Document *mDocument; QPushButton *mOkay; QListWidget *mFiles; CSVDoc::FileWidget *mNewFile; CSVDoc::AdjusterWidget *mAdjuster; CSMDoc::DocumentManager& mDocumentManager; void keyPressEvent (QKeyEvent *event) override; public: Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = nullptr); /// Configure dialogue for a new merge void configure (CSMDoc::Document *document); void setLocalData (const boost::filesystem::path& localData); CSMDoc::Document *getDocument() const; public slots: void cancel(); private slots: void accept(); void stateChanged (bool valid); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/reportsubview.cpp000066400000000000000000000023741445372753700244320ustar00rootroot00000000000000#include "reportsubview.hpp" #include "reporttable.hpp" CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : CSVDoc::SubView (id), mDocument (document), mRefreshState (0) { if (id.getType()==CSMWorld::UniversalId::Type_VerificationResults) mRefreshState = CSMDoc::State_Verifying; setWidget (mTable = new ReportTable (document, id, false, mRefreshState, this)); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); if (mRefreshState==CSMDoc::State_Verifying) { connect (mTable, SIGNAL (refreshRequest()), this, SLOT (refreshRequest())); connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), mTable, SLOT (stateChanged (int, CSMDoc::Document *))); } } void CSVTools::ReportSubView::setEditLock (bool locked) { // ignored. We don't change document state anyway. } void CSVTools::ReportSubView::refreshRequest() { if (!(mDocument.getState() & mRefreshState)) { if (mRefreshState==CSMDoc::State_Verifying) { mTable->clear(); mDocument.verify (getUniversalId()); } } } openmw-openmw-0.48.0/apps/opencs/view/tools/reportsubview.hpp000066400000000000000000000012051445372753700244270ustar00rootroot00000000000000#ifndef CSV_TOOLS_REPORTSUBVIEW_H #define CSV_TOOLS_REPORTSUBVIEW_H #include "../doc/subview.hpp" class QTableView; class QModelIndex; namespace CSMDoc { class Document; } namespace CSVTools { class ReportTable; class ReportSubView : public CSVDoc::SubView { Q_OBJECT ReportTable *mTable; CSMDoc::Document& mDocument; int mRefreshState; public: ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; private slots: void refreshRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/reporttable.cpp000066400000000000000000000242011445372753700240260ustar00rootroot00000000000000#include "reporttable.hpp" #include #include #include #include #include #include #include #include #include #include #include "../../model/tools/reportmodel.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../view/world/idtypedelegate.hpp" namespace CSVTools { class RichTextDelegate : public QStyledItemDelegate { public: RichTextDelegate (QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } CSVTools::RichTextDelegate::RichTextDelegate (QObject *parent) : QStyledItemDelegate (parent) {} void CSVTools::RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QTextDocument document; QVariant value = index.data (Qt::DisplayRole); if (value.isValid() && !value.isNull()) { document.setHtml (value.toString()); painter->translate (option.rect.topLeft()); document.drawContents (painter); painter->translate (-option.rect.topLeft()); } } void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) { QModelIndexList selectedRows = selectionModel()->selectedRows(); // create context menu QMenu menu (this); if (!selectedRows.empty()) { menu.addAction (mShowAction); menu.addAction (mRemoveAction); bool found = false; for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') { found = true; break; } } if (found) menu.addAction (mReplaceAction); } if (mRefreshAction) menu.addAction (mRefreshAction); menu.exec (event->globalPos()); } void CSVTools::ReportTable::mouseMoveEvent (QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) startDragFromTable (*this); } void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) { Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); QModelIndex index = currentIndex(); selectionModel()->select (index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); std::map::iterator iter = mDoubleClickActions.find (modifiers); if (iter==mDoubleClickActions.end()) { event->accept(); return; } switch (iter->second) { case Action_None: event->accept(); break; case Action_Edit: event->accept(); showSelection(); break; case Action_Remove: event->accept(); removeSelection(); break; case Action_EditAndRemove: event->accept(); showSelection(); removeSelection(); break; } } CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState, QWidget *parent) : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), mRefreshAction (nullptr), mRefreshState (refreshState) { horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setSortingEnabled (true); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); mProxyModel = new QSortFilterProxyModel (this); mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); mProxyModel->setSourceModel (mModel); mProxyModel->setSortRole(Qt::UserRole); setModel (mProxyModel); setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (nullptr, mDocument, this); setItemDelegateForColumn (0, mIdTypeDelegate); if (richTextDescription) setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); CSMPrefs::Shortcut* showShortcut = new CSMPrefs::Shortcut("reporttable-show", this); showShortcut->associateAction(mShowAction); mRemoveAction = new QAction (tr ("Remove from list"), this); connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); addAction (mRemoveAction); CSMPrefs::Shortcut* removeShortcut = new CSMPrefs::Shortcut("reporttable-remove", this); removeShortcut->associateAction(mRemoveAction); mReplaceAction = new QAction (tr ("Replace"), this); connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); addAction (mReplaceAction); CSMPrefs::Shortcut* replaceShortcut = new CSMPrefs::Shortcut("reporttable-replace", this); replaceShortcut->associateAction(mReplaceAction); if (mRefreshState) { mRefreshAction = new QAction (tr ("Refresh"), this); mRefreshAction->setEnabled (!(mDocument.getState() & mRefreshState)); connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); addAction (mRefreshAction); CSMPrefs::Shortcut* refreshShortcut = new CSMPrefs::Shortcut("reporttable-refresh", this); refreshShortcut->associateAction(mRefreshAction); } mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["Reports"].update(); } std::vector CSVTools::ReportTable::getDraggedRecords() const { std::vector ids; QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); } return ids; } std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { std::vector indices; if (selection) { QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector rows; for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { rows.push_back (mProxyModel->mapToSource (*iter).row()); } std::sort (rows.begin(), rows.end()); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { QString hint = mModel->data (mModel->index (*iter, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') indices.push_back (*iter); } } else { for (int i=0; irowCount(); ++i) { QString hint = mModel->data (mModel->index (i, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') indices.push_back (i); } } return indices; } void CSVTools::ReportTable::flagAsReplaced (int index) { mModel->flagAsReplaced (index); } void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) { if (setting->getParent()->getKey()=="Reports") { QString base ("double"); QString key = setting->getKey().c_str(); if (key.startsWith (base)) { QString modifierString = key.mid (base.size()); Qt::KeyboardModifiers modifiers; if (modifierString=="-s") modifiers = Qt::ShiftModifier; else if (modifierString=="-c") modifiers = Qt::ControlModifier; else if (modifierString=="-sc") modifiers = Qt::ShiftModifier | Qt::ControlModifier; DoubleClickAction action = Action_None; std::string value = setting->toString(); if (value=="Edit") action = Action_Edit; else if (value=="Remove") action = Action_Remove; else if (value=="Edit And Remove") action = Action_EditAndRemove; mDoubleClickActions[modifiers] = action; return; } } else if (*setting=="Records/type-format") mIdTypeDelegate->settingChanged (setting); } void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { int row = mProxyModel->mapToSource (*iter).row(); emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); } } void CSVTools::ReportTable::removeSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector rows; for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { rows.push_back (mProxyModel->mapToSource (*iter).row()); } std::sort (rows.begin(), rows.end()); for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) mProxyModel->removeRows (*iter, 1); selectionModel()->clear(); } void CSVTools::ReportTable::clear() { mModel->clear(); } void CSVTools::ReportTable::stateChanged (int state, CSMDoc::Document *document) { if (mRefreshAction) mRefreshAction->setEnabled (!(state & mRefreshState)); } openmw-openmw-0.48.0/apps/opencs/view/tools/reporttable.hpp000066400000000000000000000052261445372753700240410ustar00rootroot00000000000000#ifndef CSV_TOOLS_REPORTTABLE_H #define CSV_TOOLS_REPORTTABLE_H #include #include "../world/dragrecordtable.hpp" class QAction; class QSortFilterProxyModel; namespace CSMTools { class ReportModel; } namespace CSMPrefs { class Setting; } namespace CSVWorld { class CommandDelegate; } namespace CSVTools { class ReportTable : public CSVWorld::DragRecordTable { Q_OBJECT enum DoubleClickAction { Action_None, Action_Edit, Action_Remove, Action_EditAndRemove }; QSortFilterProxyModel *mProxyModel; CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; QAction *mRemoveAction; QAction *mReplaceAction; QAction *mRefreshAction; std::map mDoubleClickActions; int mRefreshState; private: void contextMenuEvent (QContextMenuEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; void mouseDoubleClickEvent (QMouseEvent *event) override; public: /// \param richTextDescription Use rich text in the description column. /// \param refreshState Document state to check for refresh function. If value is /// 0 no refresh function exists. If the document current has the specified state /// the refresh function is disabled. ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState = 0, QWidget *parent = nullptr); std::vector getDraggedRecords() const override; void clear(); /// Return indices of rows that are suitable for replacement. /// /// \param selection Only list selected rows. /// /// \return rows in the original model std::vector getReplaceIndices (bool selection) const; /// \param index row in the original model void flagAsReplaced (int index); private slots: void settingChanged (const CSMPrefs::Setting *setting); void showSelection(); void removeSelection(); public slots: void stateChanged (int state, CSMDoc::Document *document); signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void replaceRequest(); void refreshRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/searchbox.cpp000066400000000000000000000120701445372753700234620ustar00rootroot00000000000000#include "searchbox.hpp" #include #include #include "../../model/world/columns.hpp" #include "../../model/tools/search.hpp" void CSVTools::SearchBox::updateSearchButton() { if (!mSearchEnabled) mSearch.setEnabled (false); else { switch (mMode.currentIndex()) { case 0: case 1: case 2: case 3: mSearch.setEnabled (!mText.text().isEmpty()); break; case 4: mSearch.setEnabled (true); break; } } } CSVTools::SearchBox::SearchBox (QWidget *parent) : QWidget (parent), mSearch (tr("Search")), mSearchEnabled (false), mReplace (tr("Replace All")) { mLayout = new QGridLayout (this); // search panel std::vector> states = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); states.resize (states.size()-1); // ignore erased state for (std::vector>::const_iterator iter (states.begin()); iter!=states.end(); ++iter) mRecordState.addItem (QString::fromUtf8 (iter->second.c_str())); mMode.addItem (tr("Text")); mMode.addItem (tr("Text (RegEx)")); mMode.addItem (tr("ID")); mMode.addItem (tr("ID (RegEx)")); mMode.addItem (tr("Record State")); connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int))); mLayout->addWidget (&mMode, 0, 0); connect (&mText, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); connect (&mText, SIGNAL (returnPressed()), this, SLOT (startSearch())); mInput.insertWidget (0, &mText); mInput.insertWidget (1, &mRecordState); mLayout->addWidget (&mInput, 0, 1); mCaseSensitive.setText (tr ("Case")); mLayout->addWidget (&mCaseSensitive, 0, 2); connect (&mSearch, SIGNAL (clicked (bool)), this, SLOT (startSearch (bool))); mLayout->addWidget (&mSearch, 0, 3); // replace panel mReplaceInput.insertWidget (0, &mReplaceText); mReplaceInput.insertWidget (1, &mReplacePlaceholder); mLayout->addWidget (&mReplaceInput, 1, 1); mLayout->addWidget (&mReplace, 1, 3); // layout adjustments mLayout->setColumnMinimumWidth (2, 50); mLayout->setColumnStretch (1, 1); mLayout->setContentsMargins (0, 0, 0, 0); connect (&mReplace, (SIGNAL (clicked (bool))), this, SLOT (replaceAll (bool))); // update modeSelected (0); updateSearchButton(); } void CSVTools::SearchBox::setSearchMode (bool enabled) { mSearchEnabled = enabled; updateSearchButton(); } CSMTools::Search CSVTools::SearchBox::getSearch() const { CSMTools::Search::Type type = static_cast (mMode.currentIndex()); bool caseSensitive = mCaseSensitive.isChecked(); switch (type) { case CSMTools::Search::Type_Text: case CSMTools::Search::Type_Id: return CSMTools::Search (type, caseSensitive, std::string (mText.text().toUtf8().data())); case CSMTools::Search::Type_TextRegEx: case CSMTools::Search::Type_IdRegEx: return CSMTools::Search (type, caseSensitive, QRegExp (mText.text().toUtf8().data(), Qt::CaseInsensitive)); case CSMTools::Search::Type_RecordState: return CSMTools::Search (type, caseSensitive, mRecordState.currentIndex()); case CSMTools::Search::Type_None: break; } throw std::logic_error ("invalid search mode index"); } std::string CSVTools::SearchBox::getReplaceText() const { CSMTools::Search::Type type = static_cast (mMode.currentIndex()); switch (type) { case CSMTools::Search::Type_Text: case CSMTools::Search::Type_TextRegEx: case CSMTools::Search::Type_Id: case CSMTools::Search::Type_IdRegEx: return mReplaceText.text().toUtf8().data(); default: throw std::logic_error ("Invalid search mode for replace"); } } void CSVTools::SearchBox::setEditLock (bool locked) { mReplace.setEnabled (!locked); } void CSVTools::SearchBox::focus() { mInput.currentWidget()->setFocus(); } void CSVTools::SearchBox::modeSelected (int index) { switch (index) { case CSMTools::Search::Type_Text: case CSMTools::Search::Type_TextRegEx: case CSMTools::Search::Type_Id: case CSMTools::Search::Type_IdRegEx: mInput.setCurrentIndex (0); mReplaceInput.setCurrentIndex (0); break; case CSMTools::Search::Type_RecordState: mInput.setCurrentIndex (1); mReplaceInput.setCurrentIndex (1); break; } mInput.currentWidget()->setFocus(); updateSearchButton(); } void CSVTools::SearchBox::textChanged (const QString& text) { updateSearchButton(); } void CSVTools::SearchBox::startSearch (bool checked) { if (mSearch.isEnabled()) emit startSearch (getSearch()); } void CSVTools::SearchBox::replaceAll (bool checked) { emit replaceAll(); } openmw-openmw-0.48.0/apps/opencs/view/tools/searchbox.hpp000066400000000000000000000027151445372753700234740ustar00rootroot00000000000000#ifndef CSV_TOOLS_SEARCHBOX_H #define CSV_TOOLS_SEARCHBOX_H #include #include #include #include #include #include #include class QGridLayout; namespace CSMTools { class Search; } namespace CSVTools { class SearchBox : public QWidget { Q_OBJECT QStackedWidget mInput; QLineEdit mText; QComboBox mRecordState; QCheckBox mCaseSensitive; QPushButton mSearch; QGridLayout *mLayout; QComboBox mMode; bool mSearchEnabled; QStackedWidget mReplaceInput; QLineEdit mReplaceText; QLabel mReplacePlaceholder; QPushButton mReplace; private: void updateSearchButton(); public: SearchBox (QWidget *parent = nullptr); void setSearchMode (bool enabled); CSMTools::Search getSearch() const; std::string getReplaceText() const; void setEditLock (bool locked); void focus(); private slots: void modeSelected (int index); void textChanged (const QString& text); void startSearch (bool checked = true); void replaceAll (bool checked); signals: void startSearch (const CSMTools::Search& search); void replaceAll(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/searchsubview.cpp000066400000000000000000000114331445372753700243600ustar00rootroot00000000000000#include "searchsubview.hpp" #include #include "../../model/doc/document.hpp" #include "../../model/doc/state.hpp" #include "../../model/tools/search.hpp" #include "../../model/tools/reportmodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/prefs/state.hpp" #include "../world/tablebottombox.hpp" #include "../world/creator.hpp" #include "reporttable.hpp" #include "searchbox.hpp" void CSVTools::SearchSubView::replace (bool selection) { if (mLocked) return; std::vector indices = mTable->getReplaceIndices (selection); std::string replace = mSearchBox.getReplaceText(); const CSMTools::ReportModel& model = dynamic_cast (*mTable->model()); bool autoDelete = CSMPrefs::get()["Search & Replace"]["auto-delete"].isTrue(); CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = nullptr; // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) { const CSMWorld::UniversalId& id = model.getUniversalId (*iter); CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); CSMWorld::IdTableBase *table = &dynamic_cast ( *mDocument.getData().getTableModel (type)); if (table!=currentTable) { search.configure (table); currentTable = table; } std::string hint = model.getHint (*iter); if (search.verify (mDocument, table, id, hint)) { search.replace (mDocument, table, id, hint, replace); mTable->flagAsReplaced (*iter); if (autoDelete) mTable->model()->removeRows (*iter, 1); } } } void CSVTools::SearchSubView::showEvent (QShowEvent *event) { CSVDoc::SubView::showEvent (event); mSearchBox.focus(); } CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : CSVDoc::SubView (id), mDocument (document), mLocked (false) { QVBoxLayout *layout = new QVBoxLayout; layout->addWidget (&mSearchBox); layout->addWidget (mTable = new ReportTable (document, id, true), 2); layout->addWidget (mBottom = new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0); QWidget *widget = new QWidget; widget->setLayout (layout); setWidget (widget); stateChanged (document.getState(), &document); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); connect (&mSearchBox, SIGNAL (startSearch (const CSMTools::Search&)), this, SLOT (startSearch (const CSMTools::Search&))); connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); connect (document.getReport (id), SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); connect (&document, SIGNAL (operationDone (int, bool)), this, SLOT (operationDone (int, bool))); } void CSVTools::SearchSubView::setEditLock (bool locked) { mLocked = locked; mSearchBox.setEditLock (locked); } void CSVTools::SearchSubView::setStatusBar (bool show) { mBottom->setStatusBar(show); } void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) { mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); } void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) { CSMPrefs::Category& settings = CSMPrefs::get()["Search & Replace"]; mSearch = search; mSearch.setPadding (settings["char-before"].toInt(), settings["char-after"].toInt()); mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); } void CSVTools::SearchSubView::replaceRequest() { replace (true); } void CSVTools::SearchSubView::replaceAllRequest() { replace (false); } void CSVTools::SearchSubView::tableSizeUpdate() { mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0); } void CSVTools::SearchSubView::operationDone (int type, bool failed) { if (type==CSMDoc::State_Searching && !failed && !mDocument.getReport (getUniversalId())->rowCount()) { mBottom->setStatusMessage ("No Results"); } } openmw-openmw-0.48.0/apps/opencs/view/tools/searchsubview.hpp000066400000000000000000000024721445372753700243700ustar00rootroot00000000000000#ifndef CSV_TOOLS_SEARCHSUBVIEW_H #define CSV_TOOLS_SEARCHSUBVIEW_H #include "../../model/tools/search.hpp" #include "../doc/subview.hpp" #include "searchbox.hpp" class QTableView; class QModelIndex; namespace CSMDoc { class Document; } namespace CSVWorld { class TableBottomBox; } namespace CSVTools { class ReportTable; class SearchSubView : public CSVDoc::SubView { Q_OBJECT ReportTable *mTable; SearchBox mSearchBox; CSMDoc::Document& mDocument; CSMTools::Search mSearch; bool mLocked; CSVWorld::TableBottomBox *mBottom; private: void replace (bool selection); protected: void showEvent (QShowEvent *event) override; public: SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; void setStatusBar (bool show) override; private slots: void stateChanged (int state, CSMDoc::Document *document); void startSearch (const CSMTools::Search& search); void replaceRequest(); void replaceAllRequest(); void tableSizeUpdate(); void operationDone (int type, bool failed); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/tools/subviews.cpp000066400000000000000000000010331445372753700233500ustar00rootroot00000000000000#include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" #include "reportsubview.hpp" #include "searchsubview.hpp" void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { manager.add (CSMWorld::UniversalId::Type_VerificationResults, new CSVDoc::SubViewFactory); manager.add (CSMWorld::UniversalId::Type_LoadErrorLog, new CSVDoc::SubViewFactory); manager.add (CSMWorld::UniversalId::Type_Search, new CSVDoc::SubViewFactory); } openmw-openmw-0.48.0/apps/opencs/view/tools/subviews.hpp000066400000000000000000000003301445372753700233540ustar00rootroot00000000000000#ifndef CSV_TOOLS_SUBVIEWS_H #define CSV_TOOLS_SUBVIEWS_H namespace CSVDoc { class SubViewFactoryManager; } namespace CSVTools { void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/000077500000000000000000000000001445372753700211235ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/widget/brushshapes.hpp000066400000000000000000000003551445372753700241660ustar00rootroot00000000000000#ifndef CSV_WIDGET_BRUSHSHAPES_H #define CSV_WIDGET_BRUSHSHAPES_H namespace CSVWidget { enum BrushShape { BrushShape_Point, BrushShape_Square, BrushShape_Circle, BrushShape_Custom }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/coloreditor.cpp000066400000000000000000000067301445372753700241620ustar00rootroot00000000000000#include "coloreditor.hpp" #include #include #include #include #include "colorpickerpopup.hpp" CSVWidget::ColorEditor::ColorEditor(const QColor &color, QWidget *parent, const bool popupOnStart) : ColorEditor(parent, popupOnStart) { setColor(color); } CSVWidget::ColorEditor::ColorEditor(const int colorInt, QWidget *parent, const bool popupOnStart) : ColorEditor(parent, popupOnStart) { setColor(colorInt); } CSVWidget::ColorEditor::ColorEditor(QWidget *parent, const bool popupOnStart) : QPushButton(parent), mColorPicker(new ColorPickerPopup(this)), mPopupOnStart(popupOnStart) { connect(this, SIGNAL(clicked()), this, SLOT(showPicker())); connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &))); } void CSVWidget::ColorEditor::paintEvent(QPaintEvent *event) { QPushButton::paintEvent(event); QRect buttonRect = rect(); QRect coloredRect(buttonRect.x() + qRound(buttonRect.width() / 4.0), buttonRect.y() + qRound(buttonRect.height() / 4.0), buttonRect.width() / 2, buttonRect.height() / 2); QPainter painter(this); painter.fillRect(coloredRect, mColor); painter.setPen(Qt::black); painter.drawRect(coloredRect); } void CSVWidget::ColorEditor::showEvent(QShowEvent *event) { QPushButton::showEvent(event); if (isVisible() && mPopupOnStart) { setChecked(true); showPicker(); mPopupOnStart = false; } } QColor CSVWidget::ColorEditor::color() const { return mColor; } int CSVWidget::ColorEditor::colorInt() const { return (mColor.blue() << 16) | (mColor.green() << 8) | (mColor.red()); } void CSVWidget::ColorEditor::setColor(const QColor &color) { mColor = color; update(); } void CSVWidget::ColorEditor::setColor(const int colorInt) { // Color RGB values are stored in given integer. // First byte is red, second byte is green, third byte is blue. QColor color = QColor(colorInt & 0xff, (colorInt >> 8) & 0xff, (colorInt >> 16) & 0xff); setColor(color); } void CSVWidget::ColorEditor::showPicker() { mColorPicker->showPicker(calculatePopupPosition(), mColor); emit pickingFinished(); } void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) { mColor = color; update(); } QPoint CSVWidget::ColorEditor::calculatePopupPosition() { QRect editorGeometry = geometry(); QRect popupGeometry = mColorPicker->geometry(); QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); // Center the popup horizontally relative to the editor int localPopupX = (editorGeometry.width() - popupGeometry.width()) / 2; // Popup position need to be specified in global coords QPoint popupPosition = mapToGlobal(QPoint(localPopupX, editorGeometry.height())); // Make sure that the popup isn't out of the screen if (popupPosition.x() < screenGeometry.left()) { popupPosition.setX(screenGeometry.left() + 1); } else if (popupPosition.x() + popupGeometry.width() > screenGeometry.right()) { popupPosition.setX(screenGeometry.right() - popupGeometry.width() - 1); } if (popupPosition.y() + popupGeometry.height() > screenGeometry.bottom()) { // Place the popup above the editor popupPosition.setY(popupPosition.y() - popupGeometry.height() - editorGeometry.height() - 1); } return popupPosition; } openmw-openmw-0.48.0/apps/opencs/view/widget/coloreditor.hpp000066400000000000000000000026021445372753700241610ustar00rootroot00000000000000#ifndef CSV_WIDGET_COLOREDITOR_HPP #define CSV_WIDGET_COLOREDITOR_HPP #include class QColor; class QPoint; class QSize; namespace CSVWidget { class ColorPickerPopup; class ColorEditor : public QPushButton { Q_OBJECT QColor mColor; ColorPickerPopup *mColorPicker; bool mPopupOnStart; QPoint calculatePopupPosition(); public: ColorEditor(const QColor &color, QWidget *parent = nullptr, const bool popupOnStart = false); ColorEditor(const int colorInt, QWidget *parent = nullptr, const bool popupOnStart = false); QColor color() const; /// \return Color RGB value encoded in an int. int colorInt() const; void setColor(const QColor &color); /// \brief Set color using given int value. /// \param colorInt RGB color value encoded as an integer. void setColor(const int colorInt); protected: void paintEvent(QPaintEvent *event) override; void showEvent(QShowEvent *event) override; private: ColorEditor(QWidget *parent = nullptr, const bool popupOnStart = false); private slots: void showPicker(); void pickerColorChanged(const QColor &color); signals: void pickingFinished(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/colorpickerpopup.cpp000066400000000000000000000051201445372753700252250ustar00rootroot00000000000000#include "colorpickerpopup.hpp" #include #include #include #include #include #include CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) : QFrame(parent) { setWindowFlags(Qt::Popup); setFrameStyle(QFrame::Box | static_cast(QFrame::Plain)); hide(); mColorPicker = new QColorDialog(this); mColorPicker->setWindowFlags(Qt::Widget); mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog); mColorPicker->installEventFilter(this); connect(mColorPicker, SIGNAL(currentColorChanged(const QColor &)), this, SIGNAL(colorChanged(const QColor &))); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mColorPicker); layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); setFixedSize(mColorPicker->size()); } void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColor &initialColor) { QRect geometry = this->geometry(); geometry.moveTo(position); setGeometry(geometry); // Calling getColor() creates a blocking dialog that will continue execution once the user chooses OK or Cancel QColor color = mColorPicker->getColor(initialColor); if (color.isValid()) mColorPicker->setCurrentColor(color); } void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) { QPushButton *button = qobject_cast(parentWidget()); if (button != nullptr) { QStyleOptionButton option; option.init(button); QRect buttonRect = option.rect; buttonRect.moveTo(button->mapToGlobal(buttonRect.topLeft())); // 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())) { setAttribute(Qt::WA_NoMouseReplay); } } QFrame::mousePressEvent(event); } bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event) { if (object == mColorPicker && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); // Prevent QColorDialog from closing when Escape is pressed. // Instead, hide the popup. if (keyEvent->key() == Qt::Key_Escape) { hide(); return true; } } return QFrame::eventFilter(object, event); } openmw-openmw-0.48.0/apps/opencs/view/widget/colorpickerpopup.hpp000066400000000000000000000011441445372753700252340ustar00rootroot00000000000000#ifndef CSVWIDGET_COLORPICKERPOPUP_HPP #define CSVWIDGET_COLORPICKERPOPUP_HPP #include class QColorDialog; namespace CSVWidget { class ColorPickerPopup : public QFrame { Q_OBJECT QColorDialog *mColorPicker; public: explicit ColorPickerPopup(QWidget *parent); void showPicker(const QPoint &position, const QColor &initialColor); protected: void mousePressEvent(QMouseEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; signals: void colorChanged(const QColor &color); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/completerpopup.cpp000066400000000000000000000014341445372753700247070ustar00rootroot00000000000000#include "completerpopup.hpp" CSVWidget::CompleterPopup::CompleterPopup(QWidget *parent) : QListView(parent) { setEditTriggers(QAbstractItemView::NoEditTriggers); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::SingleSelection); } int CSVWidget::CompleterPopup::sizeHintForRow(int row) const { if (model() == nullptr) { return -1; } if (row < 0 || row >= model()->rowCount()) { return -1; } ensurePolished(); QModelIndex index = model()->index(row, modelColumn()); QStyleOptionViewItem option = viewOptions(); QAbstractItemDelegate *delegate = itemDelegate(index); return delegate->sizeHint(option, index).height(); } openmw-openmw-0.48.0/apps/opencs/view/widget/completerpopup.hpp000066400000000000000000000004741445372753700247170ustar00rootroot00000000000000#ifndef CSV_WIDGET_COMPLETERPOPUP_HPP #define CSV_WIDGET_COMPLETERPOPUP_HPP #include namespace CSVWidget { class CompleterPopup : public QListView { public: CompleterPopup(QWidget *parent = nullptr); int sizeHintForRow(int row) const override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/droplineedit.cpp000066400000000000000000000021431445372753700243110ustar00rootroot00000000000000#include "droplineedit.hpp" #include #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" #include "../world/dragdroputils.hpp" CSVWidget::DropLineEdit::DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent) : QLineEdit(parent), mDropType(type) { setAcceptDrops(true); } void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { event->acceptProposedAction(); } } void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { event->accept(); } } void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, mDropType); setText(QString::fromUtf8(id.getId().c_str())); emit tableMimeDataDropped(id, CSVWorld::DragDropUtils::getTableMimeData(*event)->getDocumentPtr()); } } openmw-openmw-0.48.0/apps/opencs/view/widget/droplineedit.hpp000066400000000000000000000016221445372753700243170ustar00rootroot00000000000000#ifndef CSV_WIDGET_DROPLINEEDIT_HPP #define CSV_WIDGET_DROPLINEEDIT_HPP #include #include "../../model/world/columnbase.hpp" namespace CSMDoc { class Document; } namespace CSMWorld { class TableMimeData; class UniversalId; } namespace CSVWidget { class DropLineEdit : public QLineEdit { Q_OBJECT CSMWorld::ColumnBase::Display mDropType; ///< The accepted Display type for this LineEdit. public: DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = nullptr); protected: void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; signals: void tableMimeDataDropped(const CSMWorld::UniversalId &id, const CSMDoc::Document *document); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/modebutton.cpp000066400000000000000000000006041445372753700240070ustar00rootroot00000000000000#include "modebutton.hpp" CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) : PushButton (icon, Type_Mode, tooltip, parent) {} void CSVWidget::ModeButton::activate (SceneToolbar *toolbar) {} void CSVWidget::ModeButton::deactivate (SceneToolbar *toolbar) {} bool CSVWidget::ModeButton::createContextMenu (QMenu *menu) { return false; } openmw-openmw-0.48.0/apps/opencs/view/widget/modebutton.hpp000066400000000000000000000020511445372753700240120ustar00rootroot00000000000000#ifndef CSV_WIDGET_MODEBUTTON_H #define CSV_WIDGET_MODEBUTTON_H #include "pushbutton.hpp" class QMenu; namespace CSVWidget { class SceneToolbar; /// \brief Specialist PushButton of Type_Mode for use in SceneToolMode class ModeButton : public PushButton { Q_OBJECT public: ModeButton (const QIcon& icon, const QString& tooltip = "", QWidget *parent = nullptr); /// Default-Implementation: do nothing virtual void activate (SceneToolbar *toolbar); /// Default-Implementation: do nothing virtual void deactivate (SceneToolbar *toolbar); /// Add context menu items to \a menu. Default-implementation: return false /// /// \attention menu can be a 0-pointer /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. virtual bool createContextMenu (QMenu *menu); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/pushbutton.cpp000066400000000000000000000063111445372753700240430ustar00rootroot00000000000000#include "pushbutton.hpp" #include #include #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcutmanager.hpp" void CSVWidget::PushButton::processShortcuts() { mProcessedToolTip = CSMPrefs::State::get().getShortcutManager().processToolTip(mToolTip); } void CSVWidget::PushButton::setExtendedToolTip() { QString tooltip = mProcessedToolTip; if (tooltip.isEmpty()) tooltip = "(Tool tip not implemented yet)"; switch (mType) { case Type_TopMode: tooltip += "

(left click to change mode)"; break; case Type_TopAction: break; case Type_Mode: tooltip += "

(left click to activate," "
shift-left click to activate and keep panel open)"; break; case Type_Toggle: tooltip += "

(left click to "; tooltip += isChecked() ? "disable" : "enable"; tooltip += "

shift-left click to "; tooltip += isChecked() ? "disable" : "enable"; tooltip += " and keep panel open)"; break; } setToolTip (tooltip); } void CSVWidget::PushButton::keyPressEvent (QKeyEvent *event) { if (event->key()!=Qt::Key_Shift) mKeepOpen = false; QPushButton::keyPressEvent (event); } void CSVWidget::PushButton::keyReleaseEvent (QKeyEvent *event) { if (event->key()==Qt::Key_Space) mKeepOpen = event->modifiers() & Qt::ShiftModifier; QPushButton::keyReleaseEvent (event); } void CSVWidget::PushButton::mouseReleaseEvent (QMouseEvent *event) { mKeepOpen = event->button()==Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier); QPushButton::mouseReleaseEvent (event); } CSVWidget::PushButton::PushButton (const QIcon& icon, Type type, const QString& tooltip, QWidget *parent) : QPushButton (icon, "", parent), mKeepOpen (false), mType (type), mToolTip (tooltip) { if (type==Type_Mode || type==Type_Toggle) { setCheckable (true); connect (this, SIGNAL (toggled (bool)), this, SLOT (checkedStateChanged (bool))); } setCheckable (type==Type_Mode || type==Type_Toggle); processShortcuts(); setExtendedToolTip(); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); } CSVWidget::PushButton::PushButton (Type type, const QString& tooltip, QWidget *parent) : QPushButton (parent), mKeepOpen (false), mType (type), mToolTip (tooltip) { setCheckable (type==Type_Mode || type==Type_Toggle); processShortcuts(); setExtendedToolTip(); } bool CSVWidget::PushButton::hasKeepOpen() const { return mKeepOpen; } QString CSVWidget::PushButton::getBaseToolTip() const { return mProcessedToolTip; } CSVWidget::PushButton::Type CSVWidget::PushButton::getType() const { return mType; } void CSVWidget::PushButton::checkedStateChanged (bool checked) { setExtendedToolTip(); } void CSVWidget::PushButton::settingChanged (const CSMPrefs::Setting *setting) { if (setting->getParent()->getKey() == "Key Bindings") { processShortcuts(); setExtendedToolTip(); } } openmw-openmw-0.48.0/apps/opencs/view/widget/pushbutton.hpp000066400000000000000000000032541445372753700240530ustar00rootroot00000000000000#ifndef CSV_WIDGET_PUSHBUTTON_H #define CSV_WIDGET_PUSHBUTTON_H #include namespace CSMPrefs { class Setting; } namespace CSVWidget { class PushButton : public QPushButton { Q_OBJECT public: enum Type { Type_TopMode, // top level button for mode selector panel Type_TopAction, // top level button that triggers an action Type_Mode, // mode button Type_Toggle }; private: bool mKeepOpen; Type mType; QString mToolTip; QString mProcessedToolTip; private: void processShortcuts(); void setExtendedToolTip(); protected: void keyPressEvent (QKeyEvent *event) override; void keyReleaseEvent (QKeyEvent *event) override; void mouseReleaseEvent (QMouseEvent *event) override; public: /// \param push Do not maintain a toggle state PushButton (const QIcon& icon, Type type, const QString& tooltip = "", QWidget *parent = nullptr); /// \param push Do not maintain a toggle state PushButton (Type type, const QString& tooltip = "", QWidget *parent = nullptr); bool hasKeepOpen() const; /// Return tooltip used at construction (without any button-specific modifications) QString getBaseToolTip() const; Type getType() const; private slots: void checkedStateChanged (bool checked); void settingChanged (const CSMPrefs::Setting *setting); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetool.cpp000066400000000000000000000016411445372753700236240ustar00rootroot00000000000000#include "scenetool.hpp" #include #include "scenetoolbar.hpp" CSVWidget::SceneTool::SceneTool (SceneToolbar *parent, Type type) : PushButton (type, "", parent) { setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); setFixedSize (parent->getButtonSize(), parent->getButtonSize()); connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); } void CSVWidget::SceneTool::activate() {} void CSVWidget::SceneTool::mouseReleaseEvent (QMouseEvent *event) { if (getType()==Type_TopAction && event->button()==Qt::RightButton) showPanel (parentWidget()->mapToGlobal (pos())); else PushButton::mouseReleaseEvent (event); } void CSVWidget::SceneTool::openRequest() { if (getType()==Type_TopAction) activate(); else showPanel (parentWidget()->mapToGlobal (pos())); } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetool.hpp000066400000000000000000000013271445372753700236320ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOL_H #define CSV_WIDGET_SCENETOOL_H #include "pushbutton.hpp" namespace CSVWidget { class SceneToolbar; ///< \brief Tool base class class SceneTool : public PushButton { Q_OBJECT public: SceneTool (SceneToolbar *parent, Type type = Type_TopMode); virtual void showPanel (const QPoint& position) = 0; /// This function will only called for buttons of type Type_TopAction. The default /// implementation is empty. virtual void activate(); protected: void mouseReleaseEvent (QMouseEvent *event) override; private slots: void openRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolbar.cpp000066400000000000000000000026101445372753700243060ustar00rootroot00000000000000#include "scenetoolbar.hpp" #include #include "../../model/prefs/shortcut.hpp" #include "scenetool.hpp" void CSVWidget::SceneToolbar::focusInEvent (QFocusEvent *event) { QWidget::focusInEvent (event); if (mLayout->count()) dynamic_cast (*mLayout->itemAt (0)).widget()->setFocus(); } CSVWidget::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) : QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) { setFixedWidth (mButtonSize); mLayout = new QVBoxLayout (this); mLayout->setAlignment (Qt::AlignTop); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); setLayout (mLayout); CSMPrefs::Shortcut* focusSceneShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); connect(focusSceneShortcut, SIGNAL(activated()), this, SIGNAL(focusSceneRequest())); } void CSVWidget::SceneToolbar::addTool (SceneTool *tool, SceneTool *insertPoint) { if (!insertPoint) mLayout->addWidget (tool, 0, Qt::AlignTop); else { int index = mLayout->indexOf (insertPoint); mLayout->insertWidget (index+1, tool, 0, Qt::AlignTop); } } void CSVWidget::SceneToolbar::removeTool (SceneTool *tool) { mLayout->removeWidget (tool); } int CSVWidget::SceneToolbar::getButtonSize() const { return mButtonSize; } int CSVWidget::SceneToolbar::getIconSize() const { return mIconSize; } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolbar.hpp000066400000000000000000000016031445372753700243140ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLBAR_H #define CSV_WIDGET_SCENETOOLBAR_H #include class QVBoxLayout; namespace CSVWidget { class SceneTool; class SceneToolbar : public QWidget { Q_OBJECT QVBoxLayout *mLayout; int mButtonSize; int mIconSize; protected: void focusInEvent (QFocusEvent *event) override; public: SceneToolbar (int buttonSize, QWidget *parent = nullptr); /// If insertPoint==0, insert \a tool at the end of the scrollbar. Otherwise /// insert tool after \a insertPoint. void addTool (SceneTool *tool, SceneTool *insertPoint = nullptr); void removeTool (SceneTool *tool); int getButtonSize() const; int getIconSize() const; signals: void focusSceneRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolmode.cpp000066400000000000000000000076131445372753700244760ustar00rootroot00000000000000#include "scenetoolmode.hpp" #include #include #include #include #include #include "scenetoolbar.hpp" #include "modebutton.hpp" void CSVWidget::SceneToolMode::contextMenuEvent (QContextMenuEvent *event) { QMenu menu (this); if (createContextMenu (&menu)) menu.exec (event->globalPos()); } bool CSVWidget::SceneToolMode::createContextMenu (QMenu *menu) { if (mCurrent) return mCurrent->createContextMenu (menu); return false; } void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) { QString toolTip = mToolTip; toolTip += "

Currently selected: " + activeMode->getBaseToolTip(); toolTip += "

(left click to change mode)"; if (createContextMenu (nullptr)) toolTip += "
(right click to access context menu)"; setToolTip (toolTip); } void CSVWidget::SceneToolMode::setButton (std::map::iterator iter) { for (std::map::const_iterator iter2 = mButtons.begin(); iter2!=mButtons.end(); ++iter2) iter2->first->setChecked (iter2==iter); setIcon (iter->first->icon()); adjustToolTip (iter->first); if (mCurrent!=iter->first) { if (mCurrent) mCurrent->deactivate (mToolbar); mCurrent = iter->first; mCurrent->activate (mToolbar); } emit modeChanged (iter->second); } CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) : SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (nullptr), mCurrent (nullptr), mToolbar (parent) { mPanel = new QFrame (this, Qt::Popup); mLayout = new QHBoxLayout (mPanel); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); mPanel->setLayout (mLayout); } void CSVWidget::SceneToolMode::showPanel (const QPoint& position) { mPanel->move (position); mPanel->show(); if (mFirst) mFirst->setFocus (Qt::OtherFocusReason); } void CSVWidget::SceneToolMode::addButton (const std::string& icon, const std::string& id, const QString& tooltip) { ModeButton *button = new ModeButton (QIcon (QPixmap (icon.c_str())), tooltip, mPanel); addButton (button, id); } void CSVWidget::SceneToolMode::addButton (ModeButton *button, const std::string& id) { button->setParent (mPanel); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); mLayout->addWidget (button); mButtons.insert (std::make_pair (button, id)); connect (button, SIGNAL (clicked()), this, SLOT (selected())); if (mButtons.size()==1) { mFirst = mCurrent = button; setIcon (button->icon()); button->setChecked (true); adjustToolTip (button); mCurrent->activate (mToolbar); } } CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() { return mCurrent; } std::string CSVWidget::SceneToolMode::getCurrentId() const { return mButtons.find (mCurrent)->second; } void CSVWidget::SceneToolMode::setButton (const std::string& id) { for (std::map::iterator iter = mButtons.begin(); iter!=mButtons.end(); ++iter) if (iter->second==id) { setButton (iter); break; } } bool CSVWidget::SceneToolMode::event(QEvent* event) { if (event->type() == QEvent::ToolTip) { adjustToolTip(mCurrent); } return SceneTool::event(event); } void CSVWidget::SceneToolMode::selected() { std::map::iterator iter = mButtons.find (dynamic_cast (sender())); if (iter!=mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); setButton (iter); } } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolmode.hpp000066400000000000000000000044471445372753700245050ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOL_MODE_H #define CSV_WIDGET_SCENETOOL_MODE_H #include "scenetool.hpp" #include class QHBoxLayout; class QMenu; class QEvent; namespace CSVWidget { class SceneToolbar; class ModeButton; ///< \brief Mode selector tool class SceneToolMode : public SceneTool { Q_OBJECT QWidget *mPanel; QHBoxLayout *mLayout; std::map mButtons; // widget, id int mButtonSize; int mIconSize; QString mToolTip; PushButton *mFirst; ModeButton *mCurrent; SceneToolbar *mToolbar; void adjustToolTip (const ModeButton *activeMode); void contextMenuEvent (QContextMenuEvent *event) override; /// Add context menu items to \a menu. Default-implementation: Pass on request to /// current mode button or return false, if there is no current mode button. /// /// \attention menu can be a 0-pointer /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. virtual bool createContextMenu (QMenu *menu); void setButton (std::map::iterator iter); protected: bool event(QEvent* event) override; public: SceneToolMode (SceneToolbar *parent, const QString& toolTip); void showPanel (const QPoint& position) override; void addButton (const std::string& icon, const std::string& id, const QString& tooltip = ""); /// The ownership of \a button is transferred to *this. void addButton (ModeButton *button, const std::string& id); /// Will return a 0-pointer only if the mode does not have any buttons yet. ModeButton *getCurrent(); /// Must not be called if there aren't any buttons yet. std::string getCurrentId() const; /// Manually change the current mode void setButton (const std::string& id); signals: void modeChanged (const std::string& id); private slots: void selected(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolrun.cpp000066400000000000000000000073621445372753700243570ustar00rootroot00000000000000#include "scenetoolrun.hpp" #include #include #include #include #include #include void CSVWidget::SceneToolRun::adjustToolTips() { QString toolTip = mToolTip; if (mSelected==mProfiles.end()) toolTip += "

No debug profile selected (function disabled)"; else { toolTip += "

Debug profile: " + QString::fromUtf8 (mSelected->c_str()); toolTip += "

(right click to switch to a different profile)"; } setToolTip (toolTip); } void CSVWidget::SceneToolRun::updateIcon() { setDisabled (mSelected==mProfiles.end()); } void CSVWidget::SceneToolRun::updatePanel() { mTable->setRowCount (static_cast(mProfiles.size())); int i = 0; for (std::set::const_iterator iter (mProfiles.begin()); iter!=mProfiles.end(); ++iter, ++i) { mTable->setItem (i, 0, new QTableWidgetItem (QString::fromUtf8 (iter->c_str()))); mTable->setItem (i, 1, new QTableWidgetItem ( QApplication::style()->standardIcon (QStyle::SP_TitleBarCloseButton), "")); } } CSVWidget::SceneToolRun::SceneToolRun (SceneToolbar *parent, const QString& toolTip, const QString& icon, const std::vector& profiles) : SceneTool (parent, Type_TopAction), mProfiles (profiles.begin(), profiles.end()), mSelected (mProfiles.begin()), mToolTip (toolTip) { setIcon (QIcon (icon)); updateIcon(); adjustToolTips(); mPanel = new QFrame (this, Qt::Popup); QHBoxLayout *layout = new QHBoxLayout (mPanel); layout->setContentsMargins (QMargins (0, 0, 0, 0)); mTable = new QTableWidget (0, 2, this); mTable->setShowGrid (false); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); connect (mTable, SIGNAL (clicked (const QModelIndex&)), this, SLOT (clicked (const QModelIndex&))); } void CSVWidget::SceneToolRun::showPanel (const QPoint& position) { updatePanel(); mPanel->move (position); mPanel->show(); } void CSVWidget::SceneToolRun::activate() { if (mSelected!=mProfiles.end()) emit runRequest (*mSelected); } void CSVWidget::SceneToolRun::removeProfile (const std::string& profile) { std::set::iterator iter = mProfiles.find (profile); if (iter!=mProfiles.end()) { if (iter==mSelected) { if (iter!=mProfiles.begin()) --mSelected; else ++mSelected; } mProfiles.erase (iter); if (mSelected==mProfiles.end()) updateIcon(); adjustToolTips(); } } void CSVWidget::SceneToolRun::addProfile (const std::string& profile) { std::set::iterator iter = mProfiles.find (profile); if (iter==mProfiles.end()) { mProfiles.insert (profile); if (mSelected==mProfiles.end()) { mSelected = mProfiles.begin(); updateIcon(); } adjustToolTips(); } } void CSVWidget::SceneToolRun::clicked (const QModelIndex& index) { if (index.column()==0) { // select profile mSelected = mProfiles.begin(); std::advance (mSelected, index.row()); mPanel->hide(); adjustToolTips(); } else if (index.column()==1) { // remove profile from list std::set::iterator iter = mProfiles.begin(); std::advance (iter, index.row()); removeProfile (*iter); updatePanel(); } } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolrun.hpp000066400000000000000000000027601445372753700243610ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLRUN_H #define CSV_WIDGET_SCENETOOLRUN_H #include #include #include "scenetool.hpp" class QFrame; class QTableWidget; class QModelIndex; namespace CSVWidget { class SceneToolRun : public SceneTool { Q_OBJECT std::set mProfiles; std::set::iterator mSelected; QString mToolTip; QFrame *mPanel; QTableWidget *mTable; private: void adjustToolTips(); void updateIcon(); void updatePanel(); public: SceneToolRun (SceneToolbar *parent, const QString& toolTip, const QString& icon, const std::vector& profiles); void showPanel (const QPoint& position) override; void activate() override; /// \attention This function does not remove the profile from the profile selection /// panel. void removeProfile (const std::string& profile); /// \attention This function doe not add the profile to the profile selection /// panel. This only happens when the panel is re-opened. /// /// \note Adding profiles that are already listed is a no-op. void addProfile (const std::string& profile); private slots: void clicked (const QModelIndex& index); signals: void runRequest (const std::string& profile); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolshapebrush.cpp000066400000000000000000000205401445372753700257100ustar00rootroot00000000000000#include "scenetoolshapebrush.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "brushshapes.hpp" #include "scenetool.hpp" #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) : QGroupBox(title, parent) { mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); mBrushSizeSlider->setTickInterval(10); mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSlider->setSingleStep(1); mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSpinBox->setSingleStep(1); QHBoxLayout *layoutSliderSize = new QHBoxLayout; layoutSliderSize->addWidget(mBrushSizeSlider); layoutSliderSize->addWidget(mBrushSizeSpinBox); connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); setLayout(layoutSliderSize); } CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), mDocument(document) { mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this); QVBoxLayout *layoutMain = new QVBoxLayout; layoutMain->setSpacing(0); layoutMain->setContentsMargins(4,0,4,4); QHBoxLayout *layoutHorizontal = new QHBoxLayout; layoutHorizontal->setSpacing(0); layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); configureButtonInitialSettings(mButtonPoint); configureButtonInitialSettings(mButtonSquare); configureButtonInitialSettings(mButtonCircle); configureButtonInitialSettings(mButtonCustom); mButtonPoint->setToolTip (toolTipPoint); mButtonSquare->setToolTip (toolTipSquare); mButtonCircle->setToolTip (toolTipCircle); mButtonCustom->setToolTip (toolTipCustom); QButtonGroup* brushButtonGroup = new QButtonGroup(this); brushButtonGroup->addButton(mButtonPoint); brushButtonGroup->addButton(mButtonSquare); brushButtonGroup->addButton(mButtonCircle); brushButtonGroup->addButton(mButtonCustom); brushButtonGroup->setExclusive(true); layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop); mHorizontalGroupBox = new QGroupBox(tr("")); mHorizontalGroupBox->setLayout(layoutHorizontal); mToolSelector = new QComboBox(this); mToolSelector->addItem(tr("Height (drag)")); mToolSelector->addItem(tr("Height, raise (paint)")); mToolSelector->addItem(tr("Height, lower (paint)")); mToolSelector->addItem(tr("Smooth (paint)")); mToolSelector->addItem(tr("Flatten (paint)")); QLabel *brushStrengthLabel = new QLabel(this); brushStrengthLabel->setText("Brush strength:"); mToolStrengthSlider = new QSlider(Qt::Horizontal); mToolStrengthSlider->setTickPosition(QSlider::TicksBothSides); mToolStrengthSlider->setTickInterval(8); mToolStrengthSlider->setRange(8, 128); mToolStrengthSlider->setSingleStep(8); mToolStrengthSlider->setValue(8); layoutMain->addWidget(mHorizontalGroupBox); layoutMain->addWidget(mSizeSliders); layoutMain->addWidget(mToolSelector); layoutMain->addWidget(brushStrengthLabel); layoutMain->addWidget(mToolStrengthSlider); setLayout(layoutMain); connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); } void CSVWidget::ShapeBrushWindow::configureButtonInitialSettings(QPushButton *button) { button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setContentsMargins (QMargins (0, 0, 0, 0)); button->setIconSize (QSize (48-6, 48-6)); button->setFixedSize (48, 48); button->setCheckable(true); } void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) { mBrushSize = brushSize; emit passBrushSize(mBrushSize); } void CSVWidget::ShapeBrushWindow::setBrushShape() { if(mButtonPoint->isChecked()) mBrushShape = BrushShape_Point; if(mButtonSquare->isChecked()) mBrushShape = BrushShape_Square; if(mButtonCircle->isChecked()) mBrushShape = BrushShape_Circle; if(mButtonCustom->isChecked()) mBrushShape = BrushShape_Custom; emit passBrushShape(mBrushShape); } void CSVWidget::SceneToolShapeBrush::adjustToolTips() { } CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) : SceneTool (parent, Type_TopAction), mToolTip (toolTip), mDocument (document), mShapeBrushWindow(new ShapeBrushWindow(document, this)) { setAcceptDrops(true); connect(mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); setButtonIcon(mShapeBrushWindow->mBrushShape); mPanel = new QFrame (this, Qt::Popup); QHBoxLayout *layout = new QHBoxLayout (mPanel); layout->setContentsMargins (QMargins (0, 0, 0, 0)); mTable = new QTableWidget (0, 2, this); mTable->setShowGrid (true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); connect (mTable, SIGNAL (clicked (const QModelIndex&)), this, SLOT (clicked (const QModelIndex&))); } void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushShape) { QString tooltip = "Change brush settings

Currently selected: "; switch (brushShape) { case BrushShape_Point: setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); tooltip += mShapeBrushWindow->toolTipPoint; break; case BrushShape_Square: setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); tooltip += mShapeBrushWindow->toolTipSquare; break; case BrushShape_Circle: setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); tooltip += mShapeBrushWindow->toolTipCircle; break; case BrushShape_Custom: setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); tooltip += mShapeBrushWindow->toolTipCustom; break; } setToolTip (tooltip); } void CSVWidget::SceneToolShapeBrush::showPanel (const QPoint& position) { } void CSVWidget::SceneToolShapeBrush::updatePanel () { } void CSVWidget::SceneToolShapeBrush::clicked (const QModelIndex& index) { } void CSVWidget::SceneToolShapeBrush::activate () { QPoint position = QCursor::pos(); mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mShapeBrushWindow->move (position); mShapeBrushWindow->show(); } void CSVWidget::SceneToolShapeBrush::dragEnterEvent (QDragEnterEvent *event) { emit passEvent(event); event->accept(); } void CSVWidget::SceneToolShapeBrush::dropEvent (QDropEvent *event) { emit passEvent(event); event->accept(); } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetoolshapebrush.hpp000066400000000000000000000064541445372753700257250ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H #define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include "brushshapes.hpp" #include "scenetool.hpp" #include "../../model/doc/document.hpp" #endif class QTableWidget; namespace CSVRender { class TerrainShapeMode; } namespace CSVWidget { /// \brief Layout-box for some brush button settings class ShapeBrushSizeControls : public QGroupBox { Q_OBJECT public: ShapeBrushSizeControls(const QString &title, QWidget *parent); private: QSlider *mBrushSizeSlider = new QSlider(Qt::Horizontal); QSpinBox *mBrushSizeSpinBox = new QSpinBox; friend class SceneToolShapeBrush; friend class CSVRender::TerrainShapeMode; }; /// \brief Brush settings window class ShapeBrushWindow : public QFrame { Q_OBJECT public: ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); void configureButtonInitialSettings(QPushButton *button); const QString toolTipPoint = "Paint single point"; const QString toolTipSquare = "Paint with square brush"; const QString toolTipCircle = "Paint with circle brush"; const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; private: CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; int mBrushSize = 1; CSMDoc::Document& mDocument; QGroupBox *mHorizontalGroupBox; QComboBox *mToolSelector; QSlider *mToolStrengthSlider; QPushButton *mButtonPoint; QPushButton *mButtonSquare; QPushButton *mButtonCircle; QPushButton *mButtonCustom; ShapeBrushSizeControls* mSizeSliders; friend class SceneToolShapeBrush; friend class CSVRender::TerrainShapeMode; public slots: void setBrushShape(); void setBrushSize(int brushSize); signals: void passBrushSize (int brushSize); void passBrushShape(CSVWidget::BrushShape brushShape); }; class SceneToolShapeBrush : public SceneTool { Q_OBJECT QString mToolTip; CSMDoc::Document& mDocument; QFrame *mPanel; QTableWidget *mTable; ShapeBrushWindow *mShapeBrushWindow; private: void adjustToolTips(); public: SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); void showPanel (const QPoint& position) override; void updatePanel (); void dropEvent (QDropEvent *event) override; void dragEnterEvent (QDragEnterEvent *event) override; friend class CSVRender::TerrainShapeMode; public slots: void setButtonIcon(CSVWidget::BrushShape brushShape); void clicked (const QModelIndex& index); void activate() override; signals: void passEvent(QDropEvent *event); void passEvent(QDragEnterEvent *event); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltexturebrush.cpp000066400000000000000000000337201445372753700263140ustar00rootroot00000000000000#include "scenetooltexturebrush.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scenetool.hpp" #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idcollection.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" #include "../../model/world/universalid.hpp" CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent) : QGroupBox(title, parent), mLayoutSliderSize(new QHBoxLayout), mBrushSizeSlider(new QSlider(Qt::Horizontal)), mBrushSizeSpinBox(new QSpinBox) { mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); mBrushSizeSlider->setTickInterval(10); mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); mBrushSizeSlider->setSingleStep(1); mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); mBrushSizeSpinBox->setSingleStep(1); mLayoutSliderSize->addWidget(mBrushSizeSlider); mLayoutSliderSize->addWidget(mBrushSizeSpinBox); connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); setLayout(mLayoutSliderSize); } CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), mDocument(document) { mBrushTextureLabel = "Selected texture: " + mBrushTexture + " "; CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); int index = landtexturesCollection.searchId(mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); } else { mBrushTextureLabel = "No selected texture or invalid texture"; mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); } mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); mSizeSliders = new BrushSizeControls("Brush size", this); QVBoxLayout *layoutMain = new QVBoxLayout; layoutMain->setSpacing(0); layoutMain->setContentsMargins(4,0,4,4); QHBoxLayout *layoutHorizontal = new QHBoxLayout; layoutHorizontal->setSpacing(0); layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); configureButtonInitialSettings(mButtonPoint); configureButtonInitialSettings(mButtonSquare); configureButtonInitialSettings(mButtonCircle); configureButtonInitialSettings(mButtonCustom); mButtonPoint->setToolTip (toolTipPoint); mButtonSquare->setToolTip (toolTipSquare); mButtonCircle->setToolTip (toolTipCircle); mButtonCustom->setToolTip (toolTipCustom); QButtonGroup* brushButtonGroup = new QButtonGroup(this); brushButtonGroup->addButton(mButtonPoint); brushButtonGroup->addButton(mButtonSquare); brushButtonGroup->addButton(mButtonCircle); brushButtonGroup->addButton(mButtonCustom); brushButtonGroup->setExclusive(true); layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop); layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop); mHorizontalGroupBox = new QGroupBox(tr("")); mHorizontalGroupBox->setLayout(layoutHorizontal); layoutMain->addWidget(mHorizontalGroupBox); layoutMain->addWidget(mSizeSliders); layoutMain->addWidget(mSelectedBrush); setLayout(layoutMain); connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); } void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton *button) { button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setContentsMargins (QMargins (0, 0, 0, 0)); button->setIconSize (QSize (48-6, 48-6)); button->setFixedSize (48, 48); button->setCheckable(true); } void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) { CSMWorld::IdTable& ltexTable = dynamic_cast ( *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); QUndoStack& undoStack = mDocument.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); int index = 0; int pluginInDragged = 0; CSMWorld::LandTexture::parseUniqueRecordId(brushTexture, pluginInDragged, index); std::string newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, index); int rowInBase = landtexturesCollection.searchId(brushTexture); int rowInNew = landtexturesCollection.searchId(newBrushTextureId); // Check if texture exists in current plugin, and clone if id found in base, otherwise reindex the texture // TO-DO: Handle case when texture is not found in neither base or plugin properly (finding new index is not enough) // TO-DO: Handle conflicting plugins properly if (rowInNew == -1) { if (rowInBase == -1) { int counter=0; bool freeIndexFound = false; const int maxCounter = std::numeric_limits::max() - 1; do { newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); if (landtexturesCollection.searchId(brushTexture) != -1 && landtexturesCollection.getRecord(brushTexture).isDeleted() == 0 && landtexturesCollection.searchId(newBrushTextureId) != -1 && landtexturesCollection.getRecord(newBrushTextureId).isDeleted() == 0) counter = (counter + 1) % maxCounter; else freeIndexFound = true; } while (freeIndexFound == false || counter < maxCounter); } undoStack.beginMacro ("Add land texture record"); undoStack.push (new CSMWorld::CloneCommand (ltexTable, brushTexture, newBrushTextureId, CSMWorld::UniversalId::Type_LandTexture)); undoStack.endMacro(); } if (index != -1 && !landtexturesCollection.getRecord(rowInNew).isDeleted()) { mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " "; mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(rowInNew, landTextureFilename).value()); } else { newBrushTextureId.clear(); mBrushTextureLabel = "No selected texture or invalid texture"; mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); } mBrushTexture = newBrushTextureId; emit passTextureId(mBrushTexture); emit passBrushShape(mBrushShape); // updates the icon tooltip } void CSVWidget::TextureBrushWindow::setBrushSize(int brushSize) { mBrushSize = brushSize; emit passBrushSize(mBrushSize); } void CSVWidget::TextureBrushWindow::setBrushShape() { if (mButtonPoint->isChecked()) mBrushShape = CSVWidget::BrushShape_Point; if (mButtonSquare->isChecked()) mBrushShape = CSVWidget::BrushShape_Square; if (mButtonCircle->isChecked()) mBrushShape = CSVWidget::BrushShape_Circle; if (mButtonCustom->isChecked()) mBrushShape = CSVWidget::BrushShape_Custom; emit passBrushShape(mBrushShape); } void CSVWidget::SceneToolTextureBrush::adjustToolTips() { } CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) : SceneTool (parent, Type_TopAction), mToolTip (toolTip), mDocument (document), mTextureBrushWindow(new TextureBrushWindow(document, this)) { mBrushHistory.resize(1); mBrushHistory[0] = "L0#0"; setAcceptDrops(true); connect(mTextureBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); setButtonIcon(mTextureBrushWindow->mBrushShape); mPanel = new QFrame (this, Qt::Popup); QHBoxLayout *layout = new QHBoxLayout (mPanel); layout->setContentsMargins (QMargins (0, 0, 0, 0)); mTable = new QTableWidget (0, 2, this); mTable->setShowGrid (true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); connect (mTable, SIGNAL (clicked (const QModelIndex&)), this, SLOT (clicked (const QModelIndex&))); } void CSVWidget::SceneToolTextureBrush::setButtonIcon (CSVWidget::BrushShape brushShape) { QString tooltip = "Change brush settings

Currently selected: "; switch (brushShape) { case BrushShape_Point: setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); tooltip += mTextureBrushWindow->toolTipPoint; break; case BrushShape_Square: setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); tooltip += mTextureBrushWindow->toolTipSquare; break; case BrushShape_Circle: setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); tooltip += mTextureBrushWindow->toolTipCircle; break; case BrushShape_Custom: setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); tooltip += mTextureBrushWindow->toolTipCustom; break; } tooltip += "

(right click to access of previously used brush settings)"; CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); int index = landtexturesCollection.searchId(mTextureBrushWindow->mBrushTexture); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { tooltip += "

Selected texture: " + QString::fromStdString(mTextureBrushWindow->mBrushTexture) + " "; tooltip += landtexturesCollection.getData(index, landTextureFilename).value(); } else { tooltip += "

No selected texture or invalid texture"; } tooltip += "
(drop texture here to change)"; setToolTip (tooltip); } void CSVWidget::SceneToolTextureBrush::showPanel (const QPoint& position) { updatePanel(); mPanel->move (position); mPanel->show(); } void CSVWidget::SceneToolTextureBrush::updatePanel() { mTable->setRowCount (mBrushHistory.size()); for (int i = mBrushHistory.size()-1; i >= 0; --i) { CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); int index = landtexturesCollection.searchId(mBrushHistory[i]); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { mTable->setItem (i, 1, new QTableWidgetItem (landtexturesCollection.getData(index, landTextureFilename).value())); mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i]))); } else { mTable->setItem (i, 1, new QTableWidgetItem ("Invalid/deleted texture")); mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i]))); } } } void CSVWidget::SceneToolTextureBrush::updateBrushHistory (const std::string& brushTexture) { mBrushHistory.insert(mBrushHistory.begin(), brushTexture); if(mBrushHistory.size() > 5) mBrushHistory.pop_back(); } void CSVWidget::SceneToolTextureBrush::clicked (const QModelIndex& index) { if (index.column()==0 || index.column()==1) { std::string brushTexture = mBrushHistory[index.row()]; std::swap(mBrushHistory[index.row()], mBrushHistory[0]); mTextureBrushWindow->setBrushTexture(brushTexture); emit passTextureId(brushTexture); updatePanel(); mPanel->hide(); } } void CSVWidget::SceneToolTextureBrush::activate () { QPoint position = QCursor::pos(); mTextureBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); mTextureBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); mTextureBrushWindow->move (position); mTextureBrushWindow->show(); } void CSVWidget::SceneToolTextureBrush::dragEnterEvent (QDragEnterEvent *event) { emit passEvent(event); event->accept(); } void CSVWidget::SceneToolTextureBrush::dropEvent (QDropEvent *event) { emit passEvent(event); event->accept(); } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltexturebrush.hpp000066400000000000000000000073411445372753700263210ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H #define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include "brushshapes.hpp" #include "scenetool.hpp" #include "../../model/doc/document.hpp" #endif class QTableWidget; namespace CSVRender { class TerrainTextureMode; } namespace CSVWidget { class SceneToolTextureBrush; /// \brief Layout-box for some brush button settings class BrushSizeControls : public QGroupBox { Q_OBJECT public: BrushSizeControls(const QString &title, QWidget *parent); private: QHBoxLayout *mLayoutSliderSize; QSlider *mBrushSizeSlider; QSpinBox *mBrushSizeSpinBox; friend class SceneToolTextureBrush; friend class CSVRender::TerrainTextureMode; }; class SceneToolTextureBrush; /// \brief Brush settings window class TextureBrushWindow : public QFrame { Q_OBJECT public: TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); void configureButtonInitialSettings(QPushButton *button); const QString toolTipPoint = "Paint single point"; const QString toolTipSquare = "Paint with square brush"; const QString toolTipCircle = "Paint with circle brush"; const QString toolTipCustom = "Paint custom selection (not implemented yet)"; private: CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; int mBrushSize = 1; std::string mBrushTexture = "L0#0"; CSMDoc::Document& mDocument; QLabel *mSelectedBrush; QGroupBox *mHorizontalGroupBox; std::string mBrushTextureLabel; QPushButton *mButtonPoint; QPushButton *mButtonSquare; QPushButton *mButtonCircle; QPushButton *mButtonCustom; BrushSizeControls* mSizeSliders; friend class SceneToolTextureBrush; friend class CSVRender::TerrainTextureMode; public slots: void setBrushTexture(std::string brushTexture); void setBrushShape(); void setBrushSize(int brushSize); signals: void passBrushSize (int brushSize); void passBrushShape(CSVWidget::BrushShape brushShape); void passTextureId(std::string brushTexture); }; class SceneToolTextureBrush : public SceneTool { Q_OBJECT QString mToolTip; CSMDoc::Document& mDocument; QFrame *mPanel; QTableWidget *mTable; std::vector mBrushHistory; TextureBrushWindow *mTextureBrushWindow; private: void adjustToolTips(); public: SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); void showPanel (const QPoint& position) override; void updatePanel (); void dropEvent (QDropEvent *event) override; void dragEnterEvent (QDragEnterEvent *event) override; friend class CSVRender::TerrainTextureMode; public slots: void setButtonIcon(CSVWidget::BrushShape brushShape); void updateBrushHistory (const std::string& mBrushTexture); void clicked (const QModelIndex& index); void activate() override; signals: void passEvent(QDropEvent *event); void passEvent(QDragEnterEvent *event); void passTextureId(std::string brushTexture); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltoggle.cpp000066400000000000000000000124361445372753700250320ustar00rootroot00000000000000#include "scenetooltoggle.hpp" #include #include #include #include #include #include "scenetoolbar.hpp" #include "pushbutton.hpp" void CSVWidget::SceneToolToggle::adjustToolTip() { QString toolTip = mToolTip; toolTip += "

Currently enabled: "; bool first = true; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) { if (!first) toolTip += ", "; else first = false; toolTip += iter->second.mName; } if (first) toolTip += "none"; toolTip += "

(left click to alter selection)"; setToolTip (toolTip); } void CSVWidget::SceneToolToggle::adjustIcon() { unsigned int selection = getSelectionMask(); if (!selection) setIcon (QIcon (QString::fromUtf8 (mEmptyIcon.c_str()))); else { QPixmap pixmap (48, 48); pixmap.fill (QColor (0, 0, 0, 0)); { QPainter painter (&pixmap); for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) { painter.drawImage (getIconBox (iter->second.mIndex), QImage (QString::fromUtf8 (iter->second.mSmallIcon.c_str()))); } } setIcon (pixmap); } } QRect CSVWidget::SceneToolToggle::getIconBox (int index) const { // layout for a 3x3 grid int xMax = 3; int yMax = 3; // icon size int xBorder = 1; int yBorder = 1; int iconXSize = (mIconSize-xBorder*(xMax+1))/xMax; int iconYSize = (mIconSize-yBorder*(yMax+1))/yMax; int y = index / xMax; int x = index % xMax; int total = static_cast(mButtons.size()); int actualYIcons = total/xMax; if (total % xMax) ++actualYIcons; if (actualYIcons!=yMax) { // space out icons vertically, if there aren't enough to populate all rows int diff = yMax - actualYIcons; yBorder += (diff*(yBorder+iconXSize)) / (actualYIcons+1); } if (y==actualYIcons-1) { // generating the last row of icons int actualXIcons = total % xMax; if (actualXIcons) { // space out icons horizontally, if there aren't enough to fill the last row int diff = xMax - actualXIcons; xBorder += (diff*(xBorder+iconXSize)) / (actualXIcons+1); } } return QRect ((iconXSize+xBorder)*x+xBorder, (iconYSize+yBorder)*y+yBorder, iconXSize, iconYSize); } CSVWidget::SceneToolToggle::SceneToolToggle (SceneToolbar *parent, const QString& toolTip, const std::string& emptyIcon) : SceneTool (parent), mEmptyIcon (emptyIcon), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (nullptr) { mPanel = new QFrame (this, Qt::Popup); mLayout = new QHBoxLayout (mPanel); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); mPanel->setLayout (mLayout); } void CSVWidget::SceneToolToggle::showPanel (const QPoint& position) { mPanel->move (position); mPanel->show(); if (mFirst) mFirst->setFocus (Qt::OtherFocusReason); } void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip) { if (mButtons.size()>=9) throw std::runtime_error ("Exceeded number of buttons in toggle type tool"); PushButton *button = new PushButton (QIcon (QPixmap (icon.c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); mLayout->addWidget (button); ButtonDesc desc; desc.mMask = mask; desc.mSmallIcon = smallIcon; desc.mName = name; desc.mIndex = static_cast(mButtons.size()); mButtons.insert (std::make_pair (button, desc)); connect (button, SIGNAL (clicked()), this, SLOT (selected())); if (mButtons.size()==1) mFirst = button; } unsigned int CSVWidget::SceneToolToggle::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) selection |= iter->second.mMask; return selection; } void CSVWidget::SceneToolToggle::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); } void CSVWidget::SceneToolToggle::selected() { std::map::const_iterator iter = mButtons.find (dynamic_cast (sender())); if (iter!=mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); adjustToolTip(); adjustIcon(); emit selectionChanged(); } } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltoggle.hpp000066400000000000000000000040071445372753700250320ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOL_TOGGLE_H #define CSV_WIDGET_SCENETOOL_TOGGLE_H #include "scenetool.hpp" #include class QHBoxLayout; class QRect; namespace CSVWidget { class SceneToolbar; class PushButton; ///< \brief Multi-Toggle tool class SceneToolToggle : public SceneTool { Q_OBJECT struct ButtonDesc { unsigned int mMask; std::string mSmallIcon; QString mName; int mIndex; }; std::string mEmptyIcon; QWidget *mPanel; QHBoxLayout *mLayout; std::map mButtons; // widget, id int mButtonSize; int mIconSize; QString mToolTip; PushButton *mFirst; void adjustToolTip(); void adjustIcon(); QRect getIconBox (int index) const; public: SceneToolToggle (SceneToolbar *parent, const QString& toolTip, const std::string& emptyIcon); void showPanel (const QPoint& position) override; /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. /// /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An /// attempt to add more will result in an exception being thrown. /// The small icons will be sized at (x-4)/3 (where x is the main icon size). void addButton (const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, const QString& tooltip = ""); unsigned int getSelectionMask() const; /// \param or'ed button masks. buttons that do not exist will be ignored. void setSelectionMask (unsigned int selection); signals: void selectionChanged(); private slots: void selected(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltoggle2.cpp000066400000000000000000000075601445372753700251160ustar00rootroot00000000000000#include "scenetooltoggle2.hpp" #include #include #include #include #include #include "scenetoolbar.hpp" #include "pushbutton.hpp" void CSVWidget::SceneToolToggle2::adjustToolTip() { QString toolTip = mToolTip; toolTip += "

Currently enabled: "; bool first = true; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) { if (!first) toolTip += ", "; else first = false; toolTip += iter->second.mName; } if (first) toolTip += "none"; toolTip += "

(left click to alter selection)"; setToolTip (toolTip); } void CSVWidget::SceneToolToggle2::adjustIcon() { unsigned int buttonIds = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) buttonIds |= iter->second.mButtonId; std::ostringstream stream; stream << mCompositeIcon << buttonIds; setIcon (QIcon (QString::fromUtf8 (stream.str().c_str()))); } CSVWidget::SceneToolToggle2::SceneToolToggle2 (SceneToolbar *parent, const QString& toolTip, const std::string& compositeIcon, const std::string& singleIcon) : SceneTool (parent), mCompositeIcon (compositeIcon), mSingleIcon (singleIcon), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (nullptr) { mPanel = new QFrame (this, Qt::Popup); mLayout = new QHBoxLayout (mPanel); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); mPanel->setLayout (mLayout); } void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) { mPanel->move (position); mPanel->show(); if (mFirst) mFirst->setFocus (Qt::OtherFocusReason); } void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; stream << mSingleIcon << id; PushButton *button = new PushButton (QIcon (QPixmap (stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); if (disabled) button->setDisabled (true); mLayout->addWidget (button); ButtonDesc desc; desc.mButtonId = id; desc.mMask = mask; desc.mName = name; desc.mIndex = static_cast(mButtons.size()); mButtons.insert (std::make_pair (button, desc)); connect (button, SIGNAL (clicked()), this, SLOT (selected())); if (mButtons.size()==1 && !disabled) mFirst = button; } unsigned int CSVWidget::SceneToolToggle2::getSelectionMask() const { unsigned int selection = 0; for (std::map::const_iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) if (iter->first->isChecked()) selection |= iter->second.mMask; return selection; } void CSVWidget::SceneToolToggle2::setSelectionMask (unsigned int selection) { for (std::map::iterator iter (mButtons.begin()); iter!=mButtons.end(); ++iter) iter->first->setChecked (selection & iter->second.mMask); adjustToolTip(); adjustIcon(); } void CSVWidget::SceneToolToggle2::selected() { std::map::const_iterator iter = mButtons.find (dynamic_cast (sender())); if (iter!=mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); adjustToolTip(); adjustIcon(); emit selectionChanged(); } } openmw-openmw-0.48.0/apps/opencs/view/widget/scenetooltoggle2.hpp000066400000000000000000000043751445372753700251240ustar00rootroot00000000000000#ifndef CSV_WIDGET_SCENETOOL_TOGGLE2_H #define CSV_WIDGET_SCENETOOL_TOGGLE2_H #include "scenetool.hpp" #include class QHBoxLayout; class QRect; namespace CSVWidget { class SceneToolbar; class PushButton; ///< \brief Multi-Toggle tool /// /// Top level button is using predefined icons instead building a composite icon. class SceneToolToggle2 : public SceneTool { Q_OBJECT struct ButtonDesc { unsigned int mButtonId; unsigned int mMask; QString mName; int mIndex; }; std::string mCompositeIcon; std::string mSingleIcon; QWidget *mPanel; QHBoxLayout *mLayout; std::map mButtons; // widget, id int mButtonSize; int mIconSize; QString mToolTip; PushButton *mFirst; void adjustToolTip(); void adjustIcon(); public: /// The top level icon is compositeIcon + sum of bitpatterns for active buttons (in /// decimal) /// /// The icon for individual toggle buttons is signleIcon + bitmask for button (in /// decimal) SceneToolToggle2 (SceneToolbar *parent, const QString& toolTip, const std::string& compositeIcon, const std::string& singleIcon); void showPanel (const QPoint& position) override; /// \param buttonId used to compose the icon filename /// \param mask used for the reported getSelectionMask() / setSelectionMask() /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. void addButton (unsigned int buttonId, unsigned int mask, const QString& name, const QString& tooltip = "", bool disabled = false); unsigned int getSelectionMask() const; /// \param or'ed button masks. buttons that do not exist will be ignored. void setSelectionMask (unsigned int selection); signals: void selectionChanged(); private slots: void selected(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/000077500000000000000000000000001445372753700207675ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/opencs/view/world/bodypartcreator.cpp000066400000000000000000000021241445372753700246760ustar00rootroot00000000000000#include "bodypartcreator.hpp" #include #include "../../model/world/data.hpp" #include "../../model/world/universalid.hpp" std::string CSVWorld::BodyPartCreator::getId() const { std::string id = CSVWorld::GenericCreator::getId(); if (mFirstPerson->isChecked()) { id += ".1st"; } return id; } CSVWorld::BodyPartCreator::BodyPartCreator( CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id ) : GenericCreator(data, undoStack, id) { mFirstPerson = new QCheckBox("First Person", this); insertBeforeButtons(mFirstPerson, false); connect(mFirstPerson, SIGNAL(clicked(bool)), this, SLOT(checkboxClicked())); } std::string CSVWorld::BodyPartCreator::getErrors() const { std::string errors; std::string id = getId(); if (getData().hasId(id)) { errors = "ID is already in use"; } return errors; } void CSVWorld::BodyPartCreator::reset() { CSVWorld::GenericCreator::reset(); mFirstPerson->setChecked(false); } void CSVWorld::BodyPartCreator::checkboxClicked() { update(); } openmw-openmw-0.48.0/apps/opencs/view/world/bodypartcreator.hpp000066400000000000000000000016601445372753700247070ustar00rootroot00000000000000#ifndef BODYPARTCREATOR_HPP #define BODYPARTCREATOR_HPP class QCheckBox; #include "genericcreator.hpp" namespace CSMWorld { class Data; class UniversalId; } namespace CSVWorld { /// \brief Record creator for body parts. class BodyPartCreator : public GenericCreator { Q_OBJECT QCheckBox *mFirstPerson; private: /// \return ID entered by user. std::string getId() const override; public: BodyPartCreator( CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); /// \return Error description for current user input. std::string getErrors() const override; /// \brief Clear ID and checkbox input widgets. void reset() override; private slots: void checkboxClicked(); }; } #endif // BODYPARTCREATOR_HPP openmw-openmw-0.48.0/apps/opencs/view/world/cellcreator.cpp000066400000000000000000000070161445372753700237760ustar00rootroot00000000000000#include "cellcreator.hpp" #include #include #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/idtree.hpp" std::string CSVWorld::CellCreator::getId() const { if (mType->currentIndex()==0) return GenericCreator::getId(); std::ostringstream stream; stream << "#" << mX->value() << " " << mY->value(); return stream.str(); } void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { CSMWorld::IdTree* model = &dynamic_cast(*getData().getTableModel(getCollectionId())); int parentIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Cell); int index = model->findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); } CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) { mY = new QSpinBox (this); mY->setVisible (false); mY->setMinimum (std::numeric_limits::min()); mY->setMaximum (std::numeric_limits::max()); connect (mY, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); insertAtBeginning (mY, true); mYLabel = new QLabel ("Y", this); mYLabel->setVisible (false); insertAtBeginning (mYLabel, false); mX = new QSpinBox (this); mX->setVisible (false); mX->setMinimum (std::numeric_limits::min()); mX->setMaximum (std::numeric_limits::max()); connect (mX, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); insertAtBeginning (mX, true); mXLabel = new QLabel ("X", this); mXLabel->setVisible (false); insertAtBeginning (mXLabel, false); mType = new QComboBox (this); mType->addItem ("Interior Cell"); mType->addItem ("Exterior Cell"); connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); insertAtBeginning (mType, false); } void CSVWorld::CellCreator::reset() { mX->setValue (0); mY->setValue (0); mType->setCurrentIndex (0); setType(0); GenericCreator::reset(); } void CSVWorld::CellCreator::setType (int index) { setManualEditing (index==0); mXLabel->setVisible (index==1); mX->setVisible (index==1); mYLabel->setVisible (index==1); mY->setVisible (index==1); // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) std::string text = mType->currentText().toStdString(); if (text == "Interior Cell") GenericCreator::setEditorMaxLength (64); else GenericCreator::setEditorMaxLength (32767); update(); } void CSVWorld::CellCreator::valueChanged (int index) { update(); } void CSVWorld::CellCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); if (*(originId.begin()) == '#') //if originid points to the exterior cell { setType(1); //enable x and y controls mType->setCurrentIndex(1); } else { setType(0); mType->setCurrentIndex(0); } } std::string CSVWorld::CellCreator::getErrors() const { std::string errors; if (mType->currentIndex() == 0) { errors = GenericCreator::getErrors(); } else if (getData().hasId(getId())) { errors = "The Exterior Cell is already exist"; } return errors; } openmw-openmw-0.48.0/apps/opencs/view/world/cellcreator.hpp000066400000000000000000000023361445372753700240030ustar00rootroot00000000000000#ifndef CSV_WORLD_CELLCREATOR_H #define CSV_WORLD_CELLCREATOR_H class QLabel; class QSpinBox; class QComboBox; #include "genericcreator.hpp" namespace CSVWorld { class CellCreator : public GenericCreator { Q_OBJECT QComboBox *mType; QLabel *mXLabel; QSpinBox *mX; QLabel *mYLabel; QSpinBox *mY; protected: std::string getId() const override; /// Allow subclasses to add additional data to \a command. void configureCreateCommand(CSMWorld::CreateCommand& command) const override; public: CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); void reset() override; void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; std::string getErrors() const override; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. private slots: void setType (int index); void valueChanged (int index); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/colordelegate.cpp000066400000000000000000000026051445372753700243070ustar00rootroot00000000000000#include "colordelegate.hpp" #include CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : CommandDelegate(dispatcher, document, parent) {} void CSVWorld::ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int colorInt = index.data().toInt(); QColor color(colorInt & 0xff, (colorInt >> 8) & 0xff, (colorInt >> 16) & 0xff); QRect coloredRect(option.rect.x() + qRound(option.rect.width() / 4.0), option.rect.y() + qRound(option.rect.height() / 4.0), option.rect.width() / 2, option.rect.height() / 2); painter->save(); painter->fillRect(coloredRect, color); painter->setPen(Qt::black); painter->drawRect(coloredRect); painter->restore(); } CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document &document, QObject *parent) const { return new ColorDelegate(dispatcher, document, parent); } openmw-openmw-0.48.0/apps/opencs/view/world/colordelegate.hpp000066400000000000000000000020341445372753700243100ustar00rootroot00000000000000#ifndef CSV_WORLD_COLORDELEGATE_HPP #define CSV_WORLD_COLORDELEGATE_HPP #include "util.hpp" class QRect; namespace CSVWidget { class ColorEditButton; } namespace CSVWorld { class ColorDelegate : public CommandDelegate { public: ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; class ColorDelegateFactory : public CommandDelegateFactory { public: CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document &document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/creator.cpp000066400000000000000000000010021445372753700231230ustar00rootroot00000000000000#include "creator.hpp" #include CSVWorld::Creator::~Creator() {} void CSVWorld::Creator::setScope (unsigned int scope) { if (scope!=CSMWorld::Scope_Content) throw std::logic_error ("Invalid scope in creator"); } CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return nullptr; } openmw-openmw-0.48.0/apps/opencs/view/world/creator.hpp000066400000000000000000000062551445372753700231470ustar00rootroot00000000000000#ifndef CSV_WORLD_CREATOR_H #define CSV_WORLD_CREATOR_H #include #include #ifndef Q_MOC_RUN #include "../../model/doc/document.hpp" #include "../../model/world/scope.hpp" #include "../../model/world/universalid.hpp" #endif namespace CSMDoc { class Document; } namespace CSVWorld { /// \brief Record creator UI base class class Creator : public QWidget { Q_OBJECT public: virtual ~Creator(); virtual void reset() = 0; virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) = 0; /// Touches a record, if the creator supports it. virtual void touch(const std::vector& ids) = 0; virtual void setEditLock (bool locked) = 0; virtual void toggleWidgets(bool active = true) = 0; /// Default implementation: Throw an exception if scope!=Scope_Content. virtual void setScope (unsigned int scope); /// Focus main input widget virtual void focus() = 0; signals: void done(); void requestFocus (const std::string& id); ///< Request owner of this creator to focus the just created \a id. The owner may /// ignore this request. }; /// \brief Base class for Creator factory class CreatorFactoryBase { public: virtual ~CreatorFactoryBase(); virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const = 0; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function can return a 0-pointer, which means no UI for creating/deleting /// records should be provided. }; /// \brief Creator factory that does not produces any creator class NullCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function always returns 0. }; template class CreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function can return a 0-pointer, which means no UI for creating/deleting /// records should be provided. }; template Creator *CreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { auto creator = std::make_unique(document.getData(), document.getUndoStack(), id); creator->setScope (scope); return creator.release(); } } #endif openmw-openmw-0.48.0/apps/opencs/view/world/datadisplaydelegate.cpp000066400000000000000000000123321445372753700254660ustar00rootroot00000000000000#include "datadisplaydelegate.hpp" #include "../../model/prefs/state.hpp" #include #include CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, const std::string &pageName, const std::string &settingName, QObject *parent) : EnumDelegate (values, dispatcher, document, parent), mDisplayMode (Mode_TextOnly), mIcons (icons), mIconSize (QSize(16, 16)), mHorizontalMargin(QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1), mTextLeftOffset(8), mSettingKey (pageName + '/' + settingName) { buildPixmaps(); if (!pageName.empty()) updateDisplayMode (CSMPrefs::get()[pageName][settingName].toString()); } void CSVWorld::DataDisplayDelegate::buildPixmaps () { if (!mPixmaps.empty()) mPixmaps.clear(); IconList::iterator it = mIcons.begin(); while (it != mIcons.end()) { mPixmaps.emplace_back (it->mValue, it->mIcon.pixmap (mIconSize) ); ++it; } } void CSVWorld::DataDisplayDelegate::setIconSize(const QSize& size) { mIconSize = size; buildPixmaps(); } void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset) { mTextLeftOffset = offset; } QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = EnumDelegate::sizeHint(option, index); int valueIndex = getValueIndex(index); if (valueIndex != -1) { if (mDisplayMode == Mode_IconOnly) { size.setWidth(mIconSize.width() + 2 * mHorizontalMargin); } else if (mDisplayMode == Mode_IconAndText) { size.setWidth(size.width() + mIconSize.width() + mTextLeftOffset); } if (mDisplayMode != Mode_TextOnly) { size.setHeight(qMax(size.height(), mIconSize.height())); } } return size; } void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); //default to enum delegate's paint method for text-only conditions if (mDisplayMode == Mode_TextOnly) EnumDelegate::paint(painter, option, index); else { int valueIndex = getValueIndex(index); if (valueIndex != -1) { paintIcon(painter, option, valueIndex); } } painter->restore(); } void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int index) const { QRect iconRect = option.rect; QRect textRect = iconRect; iconRect.setLeft(iconRect.left() + mHorizontalMargin); iconRect.setRight(option.rect.right() - mHorizontalMargin); if (mDisplayMode == Mode_IconAndText) { iconRect.setWidth(mIconSize.width()); textRect.setLeft(iconRect.right() + mTextLeftOffset); textRect.setRight(option.rect.right() - mHorizontalMargin); QString text = option.fontMetrics.elidedText(mValues.at(index).second, option.textElideMode, textRect.width()); QApplication::style()->drawItemText(painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, option.palette, true, text); } QApplication::style()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, mPixmaps.at(index).second); } void CSVWorld::DataDisplayDelegate::updateDisplayMode (const std::string &mode) { if (mode == "Icon and Text") mDisplayMode = Mode_IconAndText; else if (mode == "Icon Only") mDisplayMode = Mode_IconOnly; else if (mode == "Text Only") mDisplayMode = Mode_TextOnly; } CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { } void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *setting) { if (*setting==mSettingKey) updateDisplayMode (setting->toString()); } void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& enumName, const QString& iconFilename) { EnumDelegateFactory::add(enumValue, enumName); Icon icon; icon.mValue = enumValue; icon.mName = enumName; icon.mIcon = QIcon(iconFilename); for (auto it=mIcons.begin(); it!=mIcons.end(); ++it) { if (it->mName > enumName) { mIcons.insert(it, icon); return; } } mIcons.push_back(icon); } CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate ( CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { return new DataDisplayDelegate (mValues, mIcons, dispatcher, document, "", "", parent); } openmw-openmw-0.48.0/apps/opencs/view/world/datadisplaydelegate.hpp000077500000000000000000000052041445372753700254760ustar00rootroot00000000000000#ifndef DATADISPLAYDELEGATE_HPP #define DATADISPLAYDELEGATE_HPP #include "enumdelegate.hpp" namespace CSMPrefs { class Setting; } namespace CSVWorld { struct Icon { int mValue; QIcon mIcon; QString mName; }; class DataDisplayDelegate : public EnumDelegate { public: typedef std::vector IconList; typedef std::vector > ValueList; protected: enum DisplayMode { Mode_TextOnly, Mode_IconOnly, Mode_IconAndText }; DisplayMode mDisplayMode; IconList mIcons; private: std::vector > mPixmaps; QSize mIconSize; int mHorizontalMargin; int mTextLeftOffset; std::string mSettingKey; public: DataDisplayDelegate (const ValueList & values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, const std::string& pageName, const std::string& settingName, QObject *parent); ~DataDisplayDelegate(); void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; /// pass a QSize defining height / width of icon. Default is QSize (16,16). void setIconSize (const QSize& icon); /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); private: /// update the display mode based on a passed string void updateDisplayMode (const std::string &); /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; /// rebuild the list of pixmaps from the provided icons (called when icon size is changed) void buildPixmaps(); void settingChanged (const CSMPrefs::Setting *setting) override; }; class DataDisplayDelegateFactory : public EnumDelegateFactory { protected: DataDisplayDelegate::IconList mIcons; public: CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. protected: void add (int enumValue, const QString& enumName, const QString& iconFilename); }; } #endif // DATADISPLAYDELEGATE_HPP openmw-openmw-0.48.0/apps/opencs/view/world/dialoguecreator.cpp000066400000000000000000000023771445372753700246550ustar00rootroot00000000000000#include "dialoguecreator.hpp" #include #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { int index = dynamic_cast (*getData().getTableModel (getCollectionId())). findColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); command.addValue (index, mType); } CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, int type) : GenericCreator (data, undoStack, id, true), mType (type) {} CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Topic); } CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Journal); } openmw-openmw-0.48.0/apps/opencs/view/world/dialoguecreator.hpp000066400000000000000000000020411445372753700246460ustar00rootroot00000000000000#ifndef CSV_WORLD_DIALOGUECREATOR_H #define CSV_WORLD_DIALOGUECREATOR_H #include "genericcreator.hpp" namespace CSVWorld { class DialogueCreator : public GenericCreator { int mType; protected: void configureCreateCommand (CSMWorld::CreateCommand& command) const override; public: DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, int type); }; class TopicCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. }; class JournalCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/dialoguespinbox.cpp000066400000000000000000000023311445372753700246660ustar00rootroot00000000000000#include "dialoguespinbox.hpp" #include CSVWorld::DialogueSpinBox::DialogueSpinBox(QWidget *parent) : QSpinBox(parent) { setFocusPolicy(Qt::StrongFocus); } void CSVWorld::DialogueSpinBox::focusInEvent(QFocusEvent *event) { setFocusPolicy(Qt::WheelFocus); QSpinBox::focusInEvent(event); } void CSVWorld::DialogueSpinBox::focusOutEvent(QFocusEvent *event) { setFocusPolicy(Qt::StrongFocus); QSpinBox::focusOutEvent(event); } void CSVWorld::DialogueSpinBox::wheelEvent(QWheelEvent *event) { if (!hasFocus()) event->ignore(); else QSpinBox::wheelEvent(event); } CSVWorld::DialogueDoubleSpinBox::DialogueDoubleSpinBox(QWidget *parent) : QDoubleSpinBox(parent) { setFocusPolicy(Qt::StrongFocus); } void CSVWorld::DialogueDoubleSpinBox::focusInEvent(QFocusEvent *event) { setFocusPolicy(Qt::WheelFocus); QDoubleSpinBox::focusInEvent(event); } void CSVWorld::DialogueDoubleSpinBox::focusOutEvent(QFocusEvent *event) { setFocusPolicy(Qt::StrongFocus); QDoubleSpinBox::focusOutEvent(event); } void CSVWorld::DialogueDoubleSpinBox::wheelEvent(QWheelEvent *event) { if (!hasFocus()) event->ignore(); else QDoubleSpinBox::wheelEvent(event); } openmw-openmw-0.48.0/apps/opencs/view/world/dialoguespinbox.hpp000066400000000000000000000016031445372753700246740ustar00rootroot00000000000000#ifndef CSV_WORLD_DIALOGUESPINBOX_H #define CSV_WORLD_DIALOGUESPINBOX_H #include #include namespace CSVWorld { class DialogueSpinBox : public QSpinBox { Q_OBJECT public: DialogueSpinBox (QWidget *parent = nullptr); protected: void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; void wheelEvent(QWheelEvent *event) override; }; class DialogueDoubleSpinBox : public QDoubleSpinBox { Q_OBJECT public: DialogueDoubleSpinBox (QWidget *parent = nullptr); protected: void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; void wheelEvent(QWheelEvent *event) override; }; } #endif // CSV_WORLD_DIALOGUESPINBOX_H openmw-openmw-0.48.0/apps/opencs/view/world/dialoguesubview.cpp000066400000000000000000001032531445372753700246750ustar00rootroot00000000000000#include "dialoguesubview.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/columnbase.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/record.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" #include "tablebottombox.hpp" #include "nestedtable.hpp" #include "recordbuttonbar.hpp" /* ==============================NotEditableSubDelegate========================================== */ CSVWorld::NotEditableSubDelegate::NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject * parent) : QAbstractItemDelegate(parent), mTable(table) {} void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QModelIndex& index) const { QLabel* label = qobject_cast(editor); if(!label) return; QVariant v = index.data(Qt::EditRole); if (!v.isValid()) { v = index.data(Qt::DisplayRole); if (!v.isValid()) { return; } } CSMWorld::Columns::ColumnId columnId = static_cast ( mTable->getColumnId (index.column())); if (QVariant::String == v.type()) { label->setText(v.toString()); } else if (CSMWorld::Columns::hasEnums (columnId)) { int data = v.toInt(); std::vector> enumNames (CSMWorld::Columns::getEnums (columnId)); label->setText(QString::fromUtf8(enumNames.at(data).second.c_str())); } else { label->setText (v.toString()); } } void CSVWorld::NotEditableSubDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { //not editable widgets will not save model data } void CSVWorld::NotEditableSubDelegate::paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { //does nothing } QSize CSVWorld::NotEditableSubDelegate::sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const { return QSize(); } QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { QLabel *label = new QLabel(parent); label->setTextInteractionFlags (Qt::TextSelectableByMouse); return label; } /* ==============================DialogueDelegateDispatcherProxy========================================== */ CSVWorld::DialogueDelegateDispatcherProxy::refWrapper::refWrapper(const QModelIndex& index) : mIndex(index) {} CSVWorld::DialogueDelegateDispatcherProxy::DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display) : mEditor(editor), mDisplay(display), mIndexWrapper(nullptr) { } void CSVWorld::DialogueDelegateDispatcherProxy::editorDataCommited() { if (mIndexWrapper.get()) { emit editorDataCommited(mEditor, mIndexWrapper->mIndex, mDisplay); } } void CSVWorld::DialogueDelegateDispatcherProxy::setIndex(const QModelIndex& index) { mIndexWrapper = std::make_unique(index); } QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const { return mEditor; } /* ==============================DialogueDelegateDispatcher========================================== */ CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, QAbstractItemModel *model) : mParent(parent), mTable(model ? model : table), mCommandDispatcher (commandDispatcher), mDocument (document), mNotEditableDelegate(table, parent) { } CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CSMWorld::ColumnBase::Display display) { CommandDelegate *delegate = nullptr; std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt == mDelegates.end()) { delegate = CommandDelegateFactoryCollection::get().makeDelegate ( display, &mCommandDispatcher, mDocument, mParent); mDelegates.insert(std::make_pair(display, delegate)); } else { delegate = delegateIt->second; } return delegate; } void CSVWorld::DialogueDelegateDispatcher::editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display) { setModelData(editor, mTable, index, display); } void CSVWorld::DialogueDelegateDispatcher::setEditorData (QWidget* editor, const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None; if (index.parent().isValid()) { display = static_cast (static_cast(mTable)->nestedHeaderData (index.parent().column(), index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); } else { display = static_cast (mTable->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); } QLabel* label = qobject_cast(editor); if(label) { mNotEditableDelegate.setEditorData(label, index); return; } std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt != mDelegates.end()) { delegateIt->second->setEditorData(editor, index, true); } for (unsigned i = 0; i < mProxys.size(); ++i) { if (mProxys[i]->getEditor() == editor) { mProxys[i]->setIndex(index); } } } void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { setModelData(editor, model, index, CSMWorld::ColumnBase::Display_None); } void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt != mDelegates.end()) { delegateIt->second->setModelData(editor, model, index); } } void CSVWorld::DialogueDelegateDispatcher::paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { //Does nothing } QSize CSVWorld::DialogueDelegateDispatcher::sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const { return QSize(); //silencing warning, otherwise does nothing } QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase::Display display, const QModelIndex& index) { QVariant variant = index.data(); if (!variant.isValid()) { variant = index.data(Qt::DisplayRole); if (!variant.isValid()) { return nullptr; } } QWidget* editor = nullptr; if (! (mTable->flags (index) & Qt::ItemIsEditable)) { return mNotEditableDelegate.createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index); } std::map::iterator delegateIt(mDelegates.find(display)); if (delegateIt != mDelegates.end()) { editor = delegateIt->second->createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index, display); DialogueDelegateDispatcherProxy* proxy = new DialogueDelegateDispatcherProxy(editor, display); // NOTE: For each entry in CSVWorld::CommandDelegate::createEditor() a corresponding entry // is required here if (qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); connect(editor, SIGNAL(tableMimeDataDropped(const CSMWorld::UniversalId&, const CSMDoc::Document*)), proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { connect(editor, SIGNAL(stateChanged(int)), proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { connect(editor, SIGNAL(textChanged()), proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { connect(editor, SIGNAL(currentIndexChanged (int)), proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor) || qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { connect(editor, SIGNAL(pickingFinished()), proxy, SLOT(editorDataCommited())); } else // throw an exception because this is a coding error throw std::logic_error ("Dialogue editor type missing"); connect(proxy, SIGNAL(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display)), this, SLOT(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display))); mProxys.push_back(proxy); //deleted in the destructor } return editor; } CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() { for (unsigned i = 0; i < mProxys.size(); ++i) { delete mProxys[i]; //unique_ptr could be handy } } CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) : QObject(widget), mWidget(widget), mIdType(CSMWorld::TableMimeData::convertEnums(display)) { Q_ASSERT(mWidget != nullptr); Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); mWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(mWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showContextMenu(const QPoint &))); mEditIdAction = new QAction(this); connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); QLineEdit *lineEdit = qobject_cast(mWidget); if (lineEdit != nullptr) { mContextMenu = lineEdit->createStandardContextMenu(); } else { mContextMenu = new QMenu(mWidget); } } void CSVWorld::IdContextMenu::excludeId(const std::string &id) { mExcludedIds.insert(id); } QString CSVWorld::IdContextMenu::getWidgetValue() const { QLineEdit *lineEdit = qobject_cast(mWidget); QLabel *label = qobject_cast(mWidget); QString value = ""; if (lineEdit != nullptr) { value = lineEdit->text(); } else if (label != nullptr) { value = label->text(); } return value; } void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) { mEditIdAction->setText(text); if (mContextMenu->actions().isEmpty()) { mContextMenu->addAction(mEditIdAction); } else if (mContextMenu->actions().first() != mEditIdAction) { QAction *action = mContextMenu->actions().first(); mContextMenu->insertAction(action, mEditIdAction); mContextMenu->insertSeparator(action); } } void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() { if (mContextMenu->actions().isEmpty()) { return; } if (mContextMenu->actions().first() == mEditIdAction) { mContextMenu->removeAction(mEditIdAction); if (!mContextMenu->actions().isEmpty() && mContextMenu->actions().first()->isSeparator()) { mContextMenu->removeAction(mContextMenu->actions().first()); } } } void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { QString value = getWidgetValue(); bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); if (!value.isEmpty() && !isExcludedId) { addEditIdActionToMenu("Edit '" + value + "'"); } else { removeEditIdActionFromMenu(); } if (!mContextMenu->actions().isEmpty()) { mContextMenu->exec(mWidget->mapToGlobal(pos)); } } void CSVWorld::IdContextMenu::editIdRequest() { CSMWorld::UniversalId editId(mIdType, getWidgetValue().toUtf8().constData()); emit editIdRequest(editId, ""); } /* =============================================================EditWidget===================================================== */ void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, CSMWorld::ColumnBase::Display display, int currentRow) const { Q_ASSERT(editor != nullptr); if (CSMWorld::ColumnBase::isId(display) && CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) { int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); IdContextMenu *menu = new IdContextMenu(editor, display); // Current ID is already opened, so no need to create Edit 'ID' action for it menu->excludeId(id.toUtf8().constData()); connect(menu, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), this, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } } CSVWorld::EditWidget::~EditWidget() { for (unsigned i = 0; i < mNestedModels.size(); ++i) delete mNestedModels[i]; if (mDispatcher) delete mDispatcher; if (mNestedTableDispatcher) delete mNestedTableDispatcher; } CSVWorld::EditWidget::EditWidget(QWidget *parent, int row, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, bool createAndDelete) : QScrollArea(parent), mWidgetMapper(nullptr), mNestedTableMapper(nullptr), mDispatcher(nullptr), mNestedTableDispatcher(nullptr), mMainWidget(nullptr), mTable(table), mCommandDispatcher (commandDispatcher), mDocument (document) { remake (row); } void CSVWorld::EditWidget::remake(int row) { if (mMainWidget) { QWidget *del = this->takeWidget(); del->deleteLater(); } mMainWidget = new QWidget (this); for (unsigned i = 0; i < mNestedModels.size(); ++i) delete mNestedModels[i]; mNestedModels.clear(); if (mDispatcher) delete mDispatcher; mDispatcher = new DialogueDelegateDispatcher(nullptr/*this*/, mTable, mCommandDispatcher, mDocument); if (mNestedTableDispatcher) delete mNestedTableDispatcher; //not sure if widget mapper can handle deleting the widgets that were mapped if (mWidgetMapper) delete mWidgetMapper; mWidgetMapper = new QDataWidgetMapper (this); mWidgetMapper->setModel(mTable); mWidgetMapper->setItemDelegate(mDispatcher); if (mNestedTableMapper) delete mNestedTableMapper; QFrame* line = new QFrame(mMainWidget); line->setObjectName(QString::fromUtf8("line")); line->setGeometry(QRect(320, 150, 118, 3)); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); QFrame* line2 = new QFrame(mMainWidget); line2->setObjectName(QString::fromUtf8("line")); line2->setGeometry(QRect(320, 150, 118, 3)); line2->setFrameShape(QFrame::HLine); line2->setFrameShadow(QFrame::Sunken); QVBoxLayout *mainLayout = new QVBoxLayout(mMainWidget); QGridLayout *lockedLayout = new QGridLayout(); QGridLayout *unlockedLayout = new QGridLayout(); QVBoxLayout *tablesLayout = new QVBoxLayout(); mainLayout->addLayout(lockedLayout, QSizePolicy::Fixed); mainLayout->addWidget(line, 1); mainLayout->addLayout(unlockedLayout, QSizePolicy::Preferred); mainLayout->addWidget(line2, 1); mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred); mainLayout->addStretch(1); int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked); bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt(); int unlocked = 0; int locked = 0; const int columns = mTable->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); if (flags & CSMWorld::ColumnBase::Flag_Dialogue) { CSMWorld::ColumnBase::Display display = static_cast (mTable->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); if (mTable->hasChildren(mTable->index(row, i)) && !(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { CSMWorld::IdTree* innerTable = &dynamic_cast(*mTable); mNestedModels.push_back(new CSMWorld::NestedTableProxyModel (mTable->index(row, i), display, innerTable)); int idColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Id); int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); CSMWorld::UniversalId id = CSMWorld::UniversalId( static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); bool editable = true; bool fixedRows = false; QVariant v = mTable->index(row, i).data(); if (v.canConvert()) { assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); if (v.value() == CSMWorld::ColumnBase::TableEdit_None) editable = false; else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) fixedRows = true; } // Create and display nested table only if it's editable. if (editable) { NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); table->resizeColumnsToContents(); if (isBlocked) table->setEditTriggers(QAbstractItemView::NoEditTriggers); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); int headerHeight = table->horizontalHeader()->height(); int tableMaxHeight = (5 * rowHeight) + headerHeight + (2 * table->frameWidth()); table->setMinimumHeight(tableMaxHeight); QString headerText = mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(); QLabel* label = new QLabel (headerText, mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); tablesLayout->addWidget(label); tablesLayout->addWidget(table); connect(table, SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), this, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { mDispatcher->makeDelegate (display); QWidget* editor = mDispatcher->makeEditor (display, (mTable->index (row, i))); if (editor) { mWidgetMapper->addMapping (editor, i); QLabel* label = new QLabel (mTable->headerData (i, Qt::Horizontal).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); // HACK: the blocked checkbox needs to keep the same position // FIXME: unfortunately blocked record displays a little differently to unblocked one if (!(mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable) || i == blockedColumn) { lockedLayout->addWidget (label, locked, 0); lockedLayout->addWidget (editor, locked, 1); ++locked; } else { unlockedLayout->addWidget (label, unlocked, 0); unlockedLayout->addWidget (editor, unlocked, 1); ++unlocked; } if(mTable->index(row, i).data().type() == QVariant::UserType) { editor->setEnabled(false); label->setEnabled(false); } createEditorContextMenu(editor, display, row); } } else // Flag_Dialogue_List { CSMWorld::IdTree *tree = static_cast(mTable); mNestedTableMapper = new QDataWidgetMapper (this); mNestedTableMapper->setModel(tree); // FIXME: lack MIME support? mNestedTableDispatcher = new DialogueDelegateDispatcher (nullptr/*this*/, mTable, mCommandDispatcher, mDocument, tree); mNestedTableMapper->setRootIndex (tree->index(row, i)); mNestedTableMapper->setItemDelegate(mNestedTableDispatcher); int columnCount = tree->columnCount(tree->index(row, i)); for (int col = 0; col < columnCount; ++col) { int displayRole = tree->nestedHeaderData (i, col, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt(); display = static_cast (displayRole); mNestedTableDispatcher->makeDelegate (display); // FIXME: assumed all columns are editable QWidget* editor = mNestedTableDispatcher->makeEditor (display, tree->index (0, col, tree->index(row, i))); if (editor) { mNestedTableMapper->addMapping (editor, col); // Need to use Qt::DisplayRole in order to get the correct string // from CSMWorld::Columns QLabel* label = new QLabel (tree->nestedHeaderData (i, col, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); unlockedLayout->addWidget (label, unlocked, 0); unlockedLayout->addWidget (editor, unlocked, 1); ++unlocked; if(tree->index(0, col, tree->index(row, i)).data().type() == QVariant::UserType) { editor->setEnabled(false); label->setEnabled(false); } if (!isBlocked) createEditorContextMenu(editor, display, row); else editor->setEnabled(false); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); } } } mWidgetMapper->setCurrentModelIndex(mTable->index(row, 0)); if (unlocked == 0) mainLayout->removeWidget(line); this->setWidget(mMainWidget); this->setWidgetResizable(true); } QVBoxLayout& CSVWorld::SimpleDialogueSubView::getMainLayout() { return *mMainLayout; } CSMWorld::IdTable& CSVWorld::SimpleDialogueSubView::getTable() { return *mTable; } CSMWorld::CommandDispatcher& CSVWorld::SimpleDialogueSubView::getCommandDispatcher() { return mCommandDispatcher; } CSVWorld::EditWidget& CSVWorld::SimpleDialogueSubView::getEditWidget() { return *mEditWidget; } bool CSVWorld::SimpleDialogueSubView::isLocked() const { return mLocked; } CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mEditWidget(nullptr), mMainLayout(nullptr), mTable(dynamic_cast(document.getData().getTableModel(id))), mLocked(false), mDocument(document), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); connect(mTable, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); updateCurrentId(); QWidget *mainWidget = new QWidget(this); mMainLayout = new QVBoxLayout(mainWidget); setWidget (mainWidget); int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mEditWidget = new EditWidget(mainWidget, mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); connect(mEditWidget, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), this, SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); } void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) { if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mLocked = locked; QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (currentIndex.row(), 1)).toInt()); mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || locked); mCommandDispatcher.setEditLock (locked); } } void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) { CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (currentIndex.row(), 1)).toInt()); mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || mLocked); // Check if the changed data should force refresh (rebuild) the dialogue subview int flags = 0; if (index.parent().isValid()) // TODO: check that index is topLeft { flags = static_cast(mTable)->nestedHeaderData (index.parent().column(), index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); } else { flags = mTable->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); } if (flags & CSMWorld::ColumnBase::Flag_Dialogue_Refresh) { int y = mEditWidget->verticalScrollBar()->value(); mEditWidget->remake (index.parent().isValid() ? index.parent().row() : index.row()); mEditWidget->verticalScrollBar()->setValue(y); } } } void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (!currentIndex.isValid()) { return; } if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { if(mEditWidget) { delete mEditWidget; mEditWidget = nullptr; } emit closeRequest(this); } } void CSVWorld::SimpleDialogueSubView::updateCurrentId() { std::vector selection; selection.push_back (getUniversalId().getId()); mCommandDispatcher.setSelection(selection); } void CSVWorld::DialogueSubView::addButtonBar() { if (mButtons) return; mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, &getCommandDispatcher(), this); getMainLayout().insertWidget (1, mButtons); // connections connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : SimpleDialogueSubView (id, document), mButtons (nullptr) { // bottom box mBottom = new TableBottomBox (creatorFactory, document, id, this); connect (mBottom, SIGNAL (requestFocus (const std::string&)), this, SLOT (requestFocus (const std::string&))); // layout getMainLayout().addWidget (mBottom); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["ID Dialogues"].update(); } void CSVWorld::DialogueSubView::setEditLock (bool locked) { SimpleDialogueSubView::setEditLock (locked); if (mButtons) mButtons->setEditLock (locked); } void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="ID Dialogues/toolbar") { if (setting->isTrue()) { addButtonBar(); } else if (mButtons) { getMainLayout().removeWidget (mButtons); delete mButtons; mButtons = nullptr; } } } void CSVWorld::DialogueSubView::showPreview () { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && currentIndex.row() < getTable().rowCount()) { emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, getUniversalId().getId()), ""); } } void CSVWorld::DialogueSubView::viewRecord () { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && currentIndex.row() < getTable().rowCount()) { std::pair params = getTable().view (currentIndex.row()); if (params.first.getType()!=CSMWorld::UniversalId::Type_None) emit focusId (params.first, params.second); } } void CSVWorld::DialogueSubView::switchToRow (int row) { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); std::string id = getTable().data (getTable().index (row, idColumn)).toString().toUtf8().constData(); int typeColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); CSMWorld::UniversalId::Type type = static_cast ( getTable().data (getTable().index (row, typeColumn)).toInt()); setUniversalId (CSMWorld::UniversalId (type, id)); updateCurrentId(); getEditWidget().remake (row); int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); CSMWorld::RecordBase::State state = static_cast ( getTable().data (getTable().index (row, stateColumn)).toInt()); getEditWidget().setDisabled (isLocked() || state==CSMWorld::RecordBase::State_Deleted); } void CSVWorld::DialogueSubView::requestFocus (const std::string& id) { int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); QModelIndex index = getTable().getModelIndex (id, idColumn); if (index.isValid()) switchToRow (index.row()); } openmw-openmw-0.48.0/apps/opencs/view/world/dialoguesubview.hpp000066400000000000000000000211741445372753700247030ustar00rootroot00000000000000#ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H #include #include #include #include #include #ifndef Q_MOC_RUN #include "../doc/subview.hpp" #include "../../model/world/columnbase.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/universalid.hpp" #endif class QDataWidgetMapper; class QSize; class QEvent; class QLabel; class QVBoxLayout; class QMenu; namespace CSMWorld { class IdTable; class NestedTableProxyModel; } namespace CSMPrefs { class Setting; } namespace CSMDoc { class Document; } namespace CSVWorld { class CommandDelegate; class CreatorFactoryBase; class TableBottomBox; class NotEditableSubDelegate : public QAbstractItemDelegate { const CSMWorld::IdTable* mTable; public: NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject * parent = nullptr); void setEditorData (QWidget* editor, const QModelIndex& index) const override; void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing QSize sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; //this can't be nested into the DialogueDelegateDispatcher, because it needs to emit signals class DialogueDelegateDispatcherProxy : public QObject { Q_OBJECT class refWrapper { public: refWrapper(const QModelIndex& index); const QModelIndex& mIndex; }; QWidget* mEditor; CSMWorld::ColumnBase::Display mDisplay; std::unique_ptr mIndexWrapper; public: DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display); QWidget* getEditor() const; public slots: void editorDataCommited(); void setIndex(const QModelIndex& index); signals: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); }; class DialogueDelegateDispatcher : public QAbstractItemDelegate { Q_OBJECT std::map mDelegates; QObject* mParent; QAbstractItemModel* mTable; CSMWorld::CommandDispatcher& mCommandDispatcher; CSMDoc::Document& mDocument; NotEditableSubDelegate mNotEditableDelegate; std::vector mProxys; //once we move to the C++11 we should use unique_ptr public: DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, QAbstractItemModel* model = nullptr); ~DialogueDelegateDispatcher(); CSVWorld::CommandDelegate* makeDelegate(CSMWorld::ColumnBase::Display display); QWidget* makeEditor(CSMWorld::ColumnBase::Display display, const QModelIndex& index); ///< will return null if delegate is not present, parent of the widget is //same as for dispatcher itself void setEditorData (QWidget* editor, const QModelIndex& index) const override; void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; virtual void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const; void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing QSize sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing private slots: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); }; /// A context menu with "Edit 'ID'" action for editors in the dialogue subview class IdContextMenu : public QObject { Q_OBJECT QWidget *mWidget; CSMWorld::UniversalId::Type mIdType; std::set mExcludedIds; ///< A list of IDs that should not have the Edit 'ID' action. QMenu *mContextMenu; QAction *mEditIdAction; QString getWidgetValue() const; void addEditIdActionToMenu(const QString &text); void removeEditIdActionFromMenu(); public: IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); void excludeId(const std::string &id); private slots: void showContextMenu(const QPoint &pos); void editIdRequest(); signals: void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class EditWidget : public QScrollArea { Q_OBJECT QDataWidgetMapper *mWidgetMapper; QDataWidgetMapper *mNestedTableMapper; DialogueDelegateDispatcher *mDispatcher; DialogueDelegateDispatcher *mNestedTableDispatcher; QWidget* mMainWidget; CSMWorld::IdTable* mTable; CSMWorld::CommandDispatcher& mCommandDispatcher; CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor void createEditorContextMenu(QWidget *editor, CSMWorld::ColumnBase::Display display, int currentRow) const; public: EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, bool createAndDelete = false); virtual ~EditWidget(); void remake(int row); signals: void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class SimpleDialogueSubView : public CSVDoc::SubView { Q_OBJECT EditWidget* mEditWidget; QVBoxLayout* mMainLayout; CSMWorld::IdTable* mTable; bool mLocked; const CSMDoc::Document& mDocument; CSMWorld::CommandDispatcher mCommandDispatcher; protected: QVBoxLayout& getMainLayout(); CSMWorld::IdTable& getTable(); CSMWorld::CommandDispatcher& getCommandDispatcher(); EditWidget& getEditWidget(); void updateCurrentId(); bool isLocked() const; public: SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; private slots: void dataChanged(const QModelIndex & index); ///\brief we need to care for deleting currently edited record void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); }; class RecordButtonBar; class DialogueSubView : public SimpleDialogueSubView { Q_OBJECT TableBottomBox* mBottom; RecordButtonBar *mButtons; private: void addButtonBar(); public: DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting = false); void setEditLock (bool locked) override; private slots: void settingChanged (const CSMPrefs::Setting *setting); void showPreview(); void viewRecord(); void switchToRow (int row); void requestFocus (const std::string& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/dragdroputils.cpp000066400000000000000000000023441445372753700243610ustar00rootroot00000000000000#include "dragdroputils.hpp" #include #include "../../model/world/tablemimedata.hpp" const CSMWorld::TableMimeData *CSVWorld::DragDropUtils::getTableMimeData(const QDropEvent &event) { return dynamic_cast(event.mimeData()); } bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { const CSMWorld::TableMimeData *data = getTableMimeData(event); return data != nullptr && data->holdsType(type); } bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { const CSMWorld::TableMimeData *data = getTableMimeData(event); return data != nullptr && ( data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) || data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) ); } CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { if (canAcceptData(event, type)) { if (const CSMWorld::TableMimeData *data = getTableMimeData(event)) return data->returnMatching(type); } return CSMWorld::UniversalId::Type_None; } openmw-openmw-0.48.0/apps/opencs/view/world/dragdroputils.hpp000066400000000000000000000017011445372753700243620ustar00rootroot00000000000000#ifndef CSV_WORLD_DRAGDROPUTILS_HPP #define CSV_WORLD_DRAGDROPUTILS_HPP #include "../../model/world/columnbase.hpp" class QDropEvent; namespace CSMWorld { class TableMimeData; class UniversalId; } namespace CSVWorld { namespace DragDropUtils { const CSMWorld::TableMimeData *getTableMimeData(const QDropEvent &event); bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Info types can be dragged to sort the info table CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Gets the accepted data from the \a event using the \a type ///< \return Type_None if the \a event data doesn't holds the \a type } } #endif openmw-openmw-0.48.0/apps/opencs/view/world/dragrecordtable.cpp000066400000000000000000000057051445372753700246260ustar00rootroot00000000000000#include "dragrecordtable.hpp" #include #include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commands.hpp" #include "dragdroputils.hpp" void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTable& table) { std::vector records = table.getDraggedRecords(); if (records.empty()) { return; } CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); QDrag* drag = new QDrag (this); drag->setMimeData (mime); drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); drag->exec (Qt::CopyAction); } CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : QTableView(parent), mDocument(document), mEditLock(false) { setAcceptDrops(true); } void CSVWorld::DragRecordTable::setEditLock (bool locked) { mEditLock = locked; } void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) { event->acceptProposedAction(); } void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { QModelIndex index = indexAt(event->pos()); if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) || CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) ) { if (index.flags() & Qt::ItemIsEditable) { event->accept(); return; } } event->ignore(); } void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) { QModelIndex index = indexAt(event->pos()); CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) { const CSMWorld::TableMimeData *tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); if (tableMimeData->fromDocument(mDocument)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); QVariant newIndexData = QString::fromUtf8(id.getId().c_str()); QVariant oldIndexData = index.data(Qt::EditRole); if (newIndexData != oldIndexData) { mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*model(), index, newIndexData)); } } } else if (CSVWorld::DragDropUtils::isInfo(*event, display) && event->source() == this) { emit moveRecordsFromSameTable(event); } } CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const { Q_ASSERT(model() != nullptr); if (index.isValid()) { QVariant display = model()->headerData(index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display); if (display.isValid()) { return static_cast(display.toInt()); } } return CSMWorld::ColumnBase::Display_None; } openmw-openmw-0.48.0/apps/opencs/view/world/dragrecordtable.hpp000066400000000000000000000022071445372753700246250ustar00rootroot00000000000000#ifndef CSV_WORLD_DRAGRECORDTABLE_H #define CSV_WORLD_DRAGRECORDTABLE_H #include #include "../../model/world/columnbase.hpp" class QWidget; class QAction; namespace CSMDoc { class Document; } namespace CSMWorld { class UniversalId; } namespace CSVWorld { class DragRecordTable : public QTableView { Q_OBJECT protected: CSMDoc::Document& mDocument; bool mEditLock; public: DragRecordTable(CSMDoc::Document& document, QWidget* parent = nullptr); virtual std::vector getDraggedRecords() const = 0; void setEditLock(bool locked); protected: void startDragFromTable(const DragRecordTable& table); void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; private: CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; signals: void moveRecordsFromSameTable(QDropEvent *event); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/enumdelegate.cpp000066400000000000000000000130441445372753700241340ustar00rootroot00000000000000#include "enumdelegate.hpp" #include #include #include #include #include "../../model/world/commands.hpp" int CSVWorld::EnumDelegate::getValueIndex(const QModelIndex &index, int role) const { if (index.isValid() && index.data(role).isValid()) { int value = index.data(role).toInt(); int size = static_cast(mValues.size()); for (int i = 0; i < size; ++i) { if (value == mValues.at(i).first) { return i; } } } return -1; } void CSVWorld::EnumDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { if (QComboBox *comboBox = dynamic_cast (editor)) { QString value = comboBox->currentText(); for (std::vector >::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) if (iter->second==value) { // do nothing if the value has not changed if (model->data(index).toInt() != iter->first) addCommands (model, index, iter->first); break; } } } void CSVWorld::EnumDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const { getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); } CSVWorld::EnumDelegate::EnumDelegate (const std::vector >& values, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : CommandDelegate (dispatcher, document, parent), mValues (values) { } QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_None); } QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) return nullptr; QComboBox *comboBox = new QComboBox (parent); for (std::vector >::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) comboBox->addItem (iter->second); comboBox->setMaxVisibleItems(20); return comboBox; } void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { if (QComboBox *comboBox = dynamic_cast(editor)) { int role = Qt::EditRole; if (tryDisplay && !index.data(role).isValid()) { role = Qt::DisplayRole; if (!index.data(role).isValid()) { return; } } int valueIndex = getValueIndex(index, role); if (valueIndex != -1) { comboBox->setCurrentIndex(valueIndex); } } } void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { int valueIndex = getValueIndex(index); if (valueIndex != -1) { QStyleOptionViewItem itemOption(option); itemOption.text = mValues.at(valueIndex).second; QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter); } } QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { int valueIndex = getValueIndex(index); if (valueIndex != -1) { // Calculate the size hint as for a combobox. // So, the whole text is visible (isn't elided) when the editor is created QStyleOptionComboBox itemOption; itemOption.fontMetrics = option.fontMetrics; itemOption.palette = option.palette; itemOption.rect = option.rect; itemOption.state = option.state; const QString &valueText = mValues.at(valueIndex).second; QSize valueSize = QSize(itemOption.fontMetrics.horizontalAdvance(valueText), itemOption.fontMetrics.height()); itemOption.currentText = valueText; return QApplication::style()->sizeFromContents(QStyle::CT_ComboBox, &itemOption, valueSize); } return option.rect.size(); } CSVWorld::EnumDelegateFactory::EnumDelegateFactory() {} CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const char **names, bool allowNone) { assert (names); if (allowNone) add (-1, ""); for (int i=0; names[i]; ++i) add (i, names[i]); } CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const std::vector>& names, bool allowNone) { if (allowNone) add (-1, ""); int size = static_cast (names.size()); for (int i=0; isecond > name) { mValues.insert(it, pair); return; } } mValues.emplace_back (value, name); } openmw-openmw-0.48.0/apps/opencs/view/world/enumdelegate.hpp000066400000000000000000000054421445372753700241440ustar00rootroot00000000000000#ifndef CSV_WORLD_ENUMDELEGATE_H #define CSV_WORLD_ENUMDELEGATE_H #include #include #include #include "util.hpp" namespace CSVWorld { /// \brief Integer value that represents an enum and is interacted with via a combobox class EnumDelegate : public CommandDelegate { protected: std::vector > mValues; int getValueIndex(const QModelIndex &index, int role = Qt::DisplayRole) const; private: void setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const override; virtual void addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const; public: EnumDelegate (const std::vector >& values, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const override; void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const override; void paint (QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; class EnumDelegateFactory : public CommandDelegateFactory { protected: std::vector > mValues; public: EnumDelegateFactory(); EnumDelegateFactory (const char **names, bool allowNone = false); ///< \param names Array of char pointer with a 0-pointer as end mark /// \param allowNone Use value of -1 for "none selected" (empty string) EnumDelegateFactory (const std::vector>& names, bool allowNone = false); /// \param allowNone Use value of -1 for "none selected" (empty string) CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. void add (int value, const QString& name); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/extendedcommandconfigurator.cpp000066400000000000000000000161001445372753700272530ustar00rootroot00000000000000#include "extendedcommandconfigurator.hpp" #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/data.hpp" CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, QWidget *parent) : QWidget(parent), mNumUsedCheckBoxes(0), mNumChecked(0), mMode(Mode_None), mData(document.getData()), mEditLock(false) { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); mPerformButton = new QPushButton(this); mPerformButton->setDefault(true); mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); mCancelButton = new QPushButton("Cancel", this); mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); mTypeGroup = new QGroupBox(this); QGridLayout *groupLayout = new QGridLayout(mTypeGroup); groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); mTypeGroup->setLayout(groupLayout); QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(mTypeGroup); mainLayout->addWidget(mPerformButton); mainLayout->addWidget(mCancelButton); } void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, const std::vector &selectedIds) { mMode = mode; if (mMode != Mode_None) { mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); mSelectedIds = selectedIds; mCommandDispatcher->setSelection(mSelectedIds); setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); lockWidgets(mEditLock); } } void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) { if (mEditLock != locked) { mEditLock = locked; lockWidgets(mEditLock); } } void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); setupGroupLayout(); } void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() { if (mMode == Mode_None) { return; } int groupWidth = mTypeGroup->geometry().width(); QGridLayout *layout = qobject_cast(mTypeGroup->layout()); // Find the optimal number of rows to place the checkboxes within the available space int divider = 1; do { while (layout->itemAt(0) != nullptr) { layout->removeItem(layout->itemAt(0)); } int counter = 0; int itemsPerRow = mNumUsedCheckBoxes / divider; CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); for (; current != end; ++current) { if (counter < mNumUsedCheckBoxes) { int row = counter / itemsPerRow; int column = counter - (counter / itemsPerRow) * itemsPerRow; layout->addWidget(current->first, row, column); } ++counter; } divider *= 2; } while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); } void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) { // Make sure that we have enough checkboxes int numTypes = static_cast(types.size()); int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); if (numTypes > numCheckBoxes) { for (int i = numTypes - numCheckBoxes; i > 0; --i) { QCheckBox *checkBox = new QCheckBox(mTypeGroup); connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); } } // Set up the checkboxes int counter = 0; CheckBoxMap::iterator current = mTypeCheckBoxes.begin(); CheckBoxMap::iterator end = mTypeCheckBoxes.end(); for (; current != end; ++current) { if (counter < numTypes) { CSMWorld::UniversalId type = types[counter]; current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); current->first->setChecked(true); current->second = type; ++counter; } else { current->first->hide(); } } mNumChecked = mNumUsedCheckBoxes = numTypes; } void CSVWorld::ExtendedCommandConfigurator::lockWidgets(bool locked) { mPerformButton->setEnabled(!mEditLock && mNumChecked > 0); CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); for (int i = 0; current != end && i < mNumUsedCheckBoxes; ++current, ++i) { current->first->setEnabled(!mEditLock); } } void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() { std::vector types; CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); for (; current != end; ++current) { if (current->first->isChecked()) { types.push_back(current->second); } } mCommandDispatcher->setExtendedTypes(types); if (mMode == Mode_Delete) { mCommandDispatcher->executeExtendedDelete(); } else { mCommandDispatcher->executeExtendedRevert(); } emit done(); } void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) { switch (state) { case Qt::Unchecked: --mNumChecked; break; case Qt::Checked: ++mNumChecked; break; case Qt::PartiallyChecked: // Not used break; } mPerformButton->setEnabled(mNumChecked > 0); } void CSVWorld::ExtendedCommandConfigurator::dataIdListChanged() { bool idsRemoved = false; for (int i = 0; i < static_cast(mSelectedIds.size()); ++i) { if (!mData.hasId(mSelectedIds[i])) { std::swap(mSelectedIds[i], mSelectedIds.back()); mSelectedIds.pop_back(); idsRemoved = true; --i; } } // If all selected IDs were removed, cancel the configurator if (mSelectedIds.empty()) { emit done(); return; } if (idsRemoved) { mCommandDispatcher->setSelection(mSelectedIds); } } openmw-openmw-0.48.0/apps/opencs/view/world/extendedcommandconfigurator.hpp000066400000000000000000000035731445372753700272720ustar00rootroot00000000000000#ifndef CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP #define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP #include #include #include "../../model/world/universalid.hpp" class QPushButton; class QGroupBox; class QCheckBox; class QLabel; class QHBoxLayout; namespace CSMDoc { class Document; } namespace CSMWorld { class CommandDispatcher; class Data; } namespace CSVWorld { class ExtendedCommandConfigurator : public QWidget { Q_OBJECT public: enum Mode { Mode_None, Mode_Delete, Mode_Revert }; private: typedef std::map CheckBoxMap; QPushButton *mPerformButton; QPushButton *mCancelButton; QGroupBox *mTypeGroup; CheckBoxMap mTypeCheckBoxes; int mNumUsedCheckBoxes; int mNumChecked; Mode mMode; CSMWorld::CommandDispatcher *mCommandDispatcher; CSMWorld::Data &mData; std::vector mSelectedIds; bool mEditLock; void setupGroupLayout(); void setupCheckBoxes(const std::vector &types); void lockWidgets(bool locked); public: ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, QWidget *parent = nullptr); void configure(Mode mode, const std::vector &selectedIds); void setEditLock(bool locked); protected: void resizeEvent(QResizeEvent *event) override; private slots: void performExtendedCommand(); void checkBoxStateChanged(int state); void dataIdListChanged(); signals: void done(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/genericcreator.cpp000066400000000000000000000225101445372753700244670ustar00rootroot00000000000000#include "genericcreator.hpp" #include #include #include #include #include #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "idvalidator.hpp" void CSVWorld::GenericCreator::update() { mErrors = getErrors(); mCreate->setToolTip (QString::fromUtf8 (mErrors.c_str())); mId->setToolTip (QString::fromUtf8 (mErrors.c_str())); mCreate->setEnabled (mErrors.empty() && !mLocked); } void CSVWorld::GenericCreator::setManualEditing (bool enabled) { mId->setVisible (enabled); } void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretched) { mLayout->insertWidget (0, widget, stretched ? 1 : 0); } void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched) { mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0); // Reset tab order relative to buttons. setTabOrder(widget, mCreate); setTabOrder(mCreate, mCancel); } std::string CSVWorld::GenericCreator::getId() const { return mId->text().toUtf8().constData(); } std::string CSVWorld::GenericCreator::getClonedId() const { return mClonedId; } std::string CSVWorld::GenericCreator::getIdValidatorResult() const { std::string errors; if (!mId->hasAcceptableInput()) errors = mValidator->getError(); return errors; } void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} void CSVWorld::GenericCreator::pushCommand (std::unique_ptr command, const std::string& id) { mUndoStack.push (command.release()); } CSMWorld::Data& CSVWorld::GenericCreator::getData() const { return mData; } QUndoStack& CSVWorld::GenericCreator::getUndoStack() { return mUndoStack; } const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const { return mListId; } std::string CSVWorld::GenericCreator::getNamespace() const { CSMWorld::Scope scope = CSMWorld::Scope_Content; if (mScope) { scope = static_cast (mScope->itemData (mScope->currentIndex()).toInt()); } else { if (mScopes & CSMWorld::Scope_Project) scope = CSMWorld::Scope_Project; else if (mScopes & CSMWorld::Scope_Session) scope = CSMWorld::Scope_Session; } switch (scope) { case CSMWorld::Scope_Content: return ""; case CSMWorld::Scope_Project: return "project::"; case CSMWorld::Scope_Session: return "session::"; } return ""; } void CSVWorld::GenericCreator::updateNamespace() { std::string namespace_ = getNamespace(); mValidator->setNamespace (namespace_); int index = mId->text().indexOf ("::"); if (index==-1) { // no namespace in old text mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text()); } else { std::string oldNamespace = Misc::StringUtils::lowerCase (mId->text().left (index).toUtf8().constData()); if (oldNamespace=="project" || oldNamespace=="session") mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text().mid (index+2)); } } void CSVWorld::GenericCreator::addScope (const QString& name, CSMWorld::Scope scope, const QString& tooltip) { mScope->addItem (name, static_cast (scope)); mScope->setItemData (mScope->count()-1, tooltip, Qt::ToolTipRole); } CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, bool relaxedIdRules) : mData (data), mUndoStack (undoStack), mListId (id), mLocked (false), mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (nullptr), mScopeLabel (nullptr), mCloneMode (false) { // If the collection ID has a parent type, use it instead. // It will change IDs with Record/SubRecord class (used for creators in Dialogue subviews) // to IDs with general RecordList class (used for creators in Table subviews). CSMWorld::UniversalId::Type listParentType = CSMWorld::UniversalId::getParentType(mListId.getType()); if (listParentType != CSMWorld::UniversalId::Type_None) { mListId = listParentType; } mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); mId = new QLineEdit; mId->setValidator (mValidator = new IdValidator (relaxedIdRules, this)); mLayout->addWidget (mId, 1); mCreate = new QPushButton ("Create"); mLayout->addWidget (mCreate); mCancel = new QPushButton("Cancel"); mLayout->addWidget(mCancel); setLayout (mLayout); connect (mCancel, SIGNAL (clicked (bool)), this, SIGNAL (done())); connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); connect (mId, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } void CSVWorld::GenericCreator::setEditorMaxLength (int length) { mId->setMaxLength (length); } void CSVWorld::GenericCreator::setEditLock (bool locked) { mLocked = locked; update(); } void CSVWorld::GenericCreator::reset() { mCloneMode = false; mId->setText (""); update(); updateNamespace(); } std::string CSVWorld::GenericCreator::getErrors() const { std::string errors; if (!mId->hasAcceptableInput()) errors = mValidator->getError(); else if (mData.hasId (getId())) errors = "ID is already in use"; return errors; } void CSVWorld::GenericCreator::textChanged (const QString& text) { update(); } void CSVWorld::GenericCreator::inputReturnPressed() { if (mCreate->isEnabled()) { create(); } } void CSVWorld::GenericCreator::create() { if (!mLocked) { std::string id = getId(); std::unique_ptr command; if (mCloneMode) { command = std::make_unique( dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType); } else { command = std::make_unique( dynamic_cast (*mData.getTableModel (mListId)), id); } configureCreateCommand (*command); pushCommand (std::move(command), id); emit done(); emit requestFocus(id); } } void CSVWorld::GenericCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { mCloneMode = true; mClonedId = originId; mClonedType = type; } void CSVWorld::GenericCreator::touch(const std::vector& ids) { // Combine multiple touch commands into one "macro" command mUndoStack.beginMacro("Touch Records"); CSMWorld::IdTable& table = dynamic_cast(*mData.getTableModel(mListId)); for (const CSMWorld::UniversalId& uid : ids) { CSMWorld::TouchCommand* touchCmd = new CSMWorld::TouchCommand(table, uid.getId()); mUndoStack.push(touchCmd); } // Execute mUndoStack.endMacro(); } void CSVWorld::GenericCreator::toggleWidgets(bool active) { } void CSVWorld::GenericCreator::focus() { mId->setFocus(); } void CSVWorld::GenericCreator::setScope (unsigned int scope) { mScopes = scope; int count = (mScopes & CSMWorld::Scope_Content) + (mScopes & CSMWorld::Scope_Project) + (mScopes & CSMWorld::Scope_Session); // scope selector widget if (count>1) { mScope = new QComboBox (this); insertAtBeginning (mScope, false); if (mScopes & CSMWorld::Scope_Content) addScope ("Content", CSMWorld::Scope_Content, "Record will be stored in the currently edited content file."); if (mScopes & CSMWorld::Scope_Project) addScope ("Project", CSMWorld::Scope_Project, "Record will be stored in a local project file.

" "Record will be created in the reserved namespace \"project\".

" "Record is available when running OpenMW via OpenCS."); if (mScopes & CSMWorld::Scope_Session) addScope ("Session", CSMWorld::Scope_Session, "Record exists only for the duration of the current editing session.

" "Record will be created in the reserved namespace \"session\".

" "Record is not available when running OpenMW via OpenCS."); connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (scopeChanged (int))); mScopeLabel = new QLabel ("Scope", this); insertAtBeginning (mScopeLabel, false); mScope->setCurrentIndex (0); } else { delete mScope; mScope = nullptr; delete mScopeLabel; mScopeLabel = nullptr; } updateNamespace(); } void CSVWorld::GenericCreator::scopeChanged (int index) { update(); updateNamespace(); } void CSVWorld::GenericCreator::dataIdListChanged() { // If the original ID of cloned record was removed, cancel the creator if (mCloneMode && !mData.hasId(mClonedId)) { emit done(); } } openmw-openmw-0.48.0/apps/opencs/view/world/genericcreator.hpp000066400000000000000000000073011445372753700244750ustar00rootroot00000000000000#ifndef CSV_WORLD_GENERICCREATOR_H #define CSV_WORLD_GENERICCREATOR_H #include #include "../../model/world/universalid.hpp" #include "creator.hpp" class QString; class QPushButton; class QLineEdit; class QHBoxLayout; class QComboBox; class QLabel; class QUndoStack; namespace CSMWorld { class CreateCommand; class Data; } namespace CSVWorld { class IdValidator; class GenericCreator : public Creator { Q_OBJECT CSMWorld::Data& mData; QUndoStack& mUndoStack; CSMWorld::UniversalId mListId; QPushButton *mCreate; QPushButton *mCancel; QLineEdit *mId; std::string mErrors; QHBoxLayout *mLayout; bool mLocked; std::string mClonedId; CSMWorld::UniversalId::Type mClonedType; unsigned int mScopes; QComboBox *mScope; QLabel *mScopeLabel; IdValidator *mValidator; protected: bool mCloneMode; protected: void update(); virtual void setManualEditing (bool enabled); ///< Enable/disable manual ID editing (enabled by default). void insertAtBeginning (QWidget *widget, bool stretched); /// \brief Insert given widget before Create and Cancel buttons. /// \param widget Widget to add to layout. /// \param stretched Whether widget should be streched or not. void insertBeforeButtons (QWidget *widget, bool stretched); virtual std::string getId() const; std::string getClonedId() const; virtual std::string getIdValidatorResult() const; /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; /// Allow subclasses to wrap the create command together with additional commands /// into a macro. virtual void pushCommand (std::unique_ptr command, const std::string& id); CSMWorld::Data& getData() const; QUndoStack& getUndoStack(); const CSMWorld::UniversalId& getCollectionId() const; std::string getNamespace() const; void setEditorMaxLength(int length); private: void updateNamespace(); void addScope (const QString& name, CSMWorld::Scope scope, const QString& tooltip); public: GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, bool relaxedIdRules = false); void setEditLock (bool locked) override; void reset() override; void toggleWidgets (bool active = true) override; void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; void touch(const std::vector& ids) override; virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. void setScope (unsigned int scope) override; /// Focus main input widget void focus() override; private slots: void textChanged (const QString& text); /// \brief Create record if able to after Return key is pressed on input. void inputReturnPressed(); void create(); void scopeChanged (int index); void dataIdListChanged(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/globalcreator.cpp000066400000000000000000000013311445372753700243110ustar00rootroot00000000000000#include "globalcreator.hpp" #include #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" namespace CSVWorld { void GlobalCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { CSMWorld::IdTable* table = static_cast(getData().getTableModel(getCollectionId())); int index = table->findColumnIndex(CSMWorld::Columns::ColumnId_ValueType); int type = (int)ESM::VT_Float; command.addValue(index, type); } GlobalCreator::GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id, true) { } } openmw-openmw-0.48.0/apps/opencs/view/world/globalcreator.hpp000066400000000000000000000006771445372753700243320ustar00rootroot00000000000000#ifndef CSV_WORLD_GLOBALCREATOR_H #define CSV_WORLD_GLOBALCREATOR_H #include "genericcreator.hpp" namespace CSVWorld { class GlobalCreator : public GenericCreator { Q_OBJECT public: GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); protected: void configureCreateCommand(CSMWorld::CreateCommand& command) const override; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/idcompletiondelegate.cpp000066400000000000000000000116311445372753700256560ustar00rootroot00000000000000#include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" CSVWorld::IdCompletionDelegate::IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : CommandDelegate(dispatcher, document, parent) {} QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { return createEditor(parent, option, index, getDisplayTypeFromIndex(index)); } QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index, CSMWorld::ColumnBase::Display display) const { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) { return nullptr; } // The completer for InfoCondVar needs to return a completer based on the first column if (display == CSMWorld::ColumnBase::Display_InfoCondVar) { QModelIndex sibling = index.sibling(index.row(), 0); int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); switch (conditionFunction) { case CSMWorld::ConstInfoSelectWrapper::Function_Global: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); } case CSMWorld::ConstInfoSelectWrapper::Function_Journal: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); } case CSMWorld::ConstInfoSelectWrapper::Function_Item: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } case CSMWorld::ConstInfoSelectWrapper::Function_Dead: case CSMWorld::ConstInfoSelectWrapper::Function_NotId: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); } case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); } case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); } case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: { return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); } case CSMWorld::ConstInfoSelectWrapper::Function_Local: case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: { return new CSVWidget::DropLineEdit(display, parent); } default: return nullptr; // The rest of them can't be edited anyway } } CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); // The savegame format limits the player faction string to 32 characters. // The region sound name is limited to 32 characters. (ESM::Region::SoundRef::mSound) // The script name is limited to 32 characters. (ESM::Script::SCHD::mName) // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) if (display == CSMWorld::ColumnBase::Display_Faction || display == CSMWorld::ColumnBase::Display_Sound || display == CSMWorld::ColumnBase::Display_Script || display == CSMWorld::ColumnBase::Display_Referenceable) { editor->setMaxLength (32); } else if (display == CSMWorld::ColumnBase::Display_Cell) { editor->setMaxLength (64); } return editor; } CSVWorld::CommandDelegate *CSVWorld::IdCompletionDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { return new IdCompletionDelegate(dispatcher, document, parent); } openmw-openmw-0.48.0/apps/opencs/view/world/idcompletiondelegate.hpp000066400000000000000000000026151445372753700256650ustar00rootroot00000000000000#ifndef CSV_WORLD_IDCOMPLETIONDELEGATE_HPP #define CSV_WORLD_IDCOMPLETIONDELEGATE_HPP #include "util.hpp" namespace CSVWorld { /// \brief Enables the Id completion for a column class IdCompletionDelegate : public CommandDelegate { public: IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index, CSMWorld::ColumnBase::Display display) const override; }; class IdCompletionDelegateFactory : public CommandDelegateFactory { public: CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/idtypedelegate.cpp000077500000000000000000000020221445372753700244630ustar00rootroot00000000000000#include "idtypedelegate.hpp" #include "../../model/world/universalid.hpp" CSVWorld::IdTypeDelegate::IdTypeDelegate (const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, "Records", "type-format", parent) {} CSVWorld::IdTypeDelegateFactory::IdTypeDelegateFactory() { for (int i=0; i (i)); DataDisplayDelegateFactory::add (id.getType(), QString::fromUtf8 (id.getTypeName().c_str()), QString::fromUtf8 (id.getIcon().c_str())); } } CSVWorld::CommandDelegate *CSVWorld::IdTypeDelegateFactory::makeDelegate ( CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { return new IdTypeDelegate (mValues, mIcons, dispatcher, document, parent); } openmw-openmw-0.48.0/apps/opencs/view/world/idtypedelegate.hpp000077500000000000000000000015361445372753700245010ustar00rootroot00000000000000#ifndef IDTYPEDELEGATE_HPP #define IDTYPEDELEGATE_HPP #include "enumdelegate.hpp" #include "util.hpp" #include "../../model/world/universalid.hpp" #include "datadisplaydelegate.hpp" namespace CSVWorld { class IdTypeDelegate : public DataDisplayDelegate { public: IdTypeDelegate (const ValueList &mValues, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); }; class IdTypeDelegateFactory : public DataDisplayDelegateFactory { public: IdTypeDelegateFactory(); CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } #endif // REFIDTYPEDELEGATE_HPP openmw-openmw-0.48.0/apps/opencs/view/world/idvalidator.cpp000066400000000000000000000060421445372753700237770ustar00rootroot00000000000000#include "idvalidator.hpp" #include bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const { if (c.isLetter() || c=='_') return true; if (!first && (c.isDigit() || c.isSpace())) return true; return false; } CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent) : QValidator (parent), mRelaxed (relaxed) {} QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const { mError.clear(); if (mRelaxed) { if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1) return QValidator::Invalid; } else { if (input.isEmpty()) { mError = "Missing ID"; return QValidator::Intermediate; } bool first = true; bool scope = false; bool prevScope = false; QString::const_iterator iter = input.begin(); if (!mNamespace.empty()) { std::string namespace_ = input.left (static_cast(mNamespace.size())).toUtf8().constData(); if (Misc::StringUtils::lowerCase (namespace_)!=mNamespace) return QValidator::Invalid; // incorrect namespace iter += namespace_.size(); first = false; prevScope = true; } else { int index = input.indexOf (":"); if (index!=-1) { QString namespace_ = input.left (index); if (namespace_=="project" || namespace_=="session") return QValidator::Invalid; // reserved namespace } } for (; iter!=input.end(); ++iter, first = false) { if (*iter==':') { if (first) return QValidator::Invalid; // scope operator at the beginning if (scope) { scope = false; prevScope = true; } else { if (prevScope) return QValidator::Invalid; // sequence of two scope operators scope = true; } } else if (scope) return QValidator::Invalid; // incomplete scope operator else { prevScope = false; if (!isValid (*iter, first)) return QValidator::Invalid; } } if (scope) { mError = "ID ending with incomplete scope operator"; return QValidator::Intermediate; } if (prevScope) { mError = "ID ending with scope operator"; return QValidator::Intermediate; } } return QValidator::Acceptable; } void CSVWorld::IdValidator::setNamespace (const std::string& namespace_) { mNamespace = Misc::StringUtils::lowerCase (namespace_); } std::string CSVWorld::IdValidator::getError() const { return mError; } openmw-openmw-0.48.0/apps/opencs/view/world/idvalidator.hpp000066400000000000000000000020201445372753700237740ustar00rootroot00000000000000#ifndef CSV_WORLD_IDVALIDATOR_H #define CSV_WORLD_IDVALIDATOR_H #include #include namespace CSVWorld { class IdValidator : public QValidator { bool mRelaxed; std::string mNamespace; mutable std::string mError; private: bool isValid (const QChar& c, bool first) const; public: IdValidator (bool relaxed = false, QObject *parent = nullptr); ///< \param relaxed Relaxed rules for IDs that also functino as user visible text State validate (QString& input, int& pos) const override; void setNamespace (const std::string& namespace_); /// Return a description of the error that resulted in the last call of validate /// returning QValidator::Intermediate. If the last call to validate returned /// a different value (or if there was no such call yet), an empty string is /// returned. std::string getError() const; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/infocreator.cpp000066400000000000000000000115341445372753700240120ustar00rootroot00000000000000#include "infocreator.hpp" #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../widget/droplineedit.hpp" std::string CSVWorld::InfoCreator::getId() const { std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData()); std::string unique = QUuid::createUuid().toByteArray().data(); unique.erase (std::remove (unique.begin(), unique.end(), '-'), unique.end()); unique = unique.substr (1, unique.size()-2); return id + '#' + unique; } void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) { if (!cloneCommand) { command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); } else { cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); } } else { if (!cloneCommand) { command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } else cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager) : GenericCreator (data, undoStack, id) { // Determine if we're dealing with topics or journals. CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Topic; QString labelText = "Topic"; if (getCollectionId().getType() == CSMWorld::UniversalId::Type_JournalInfos) { displayType = CSMWorld::ColumnBase::Display_Journal; labelText = "Journal"; } QLabel *label = new QLabel (labelText, this); insertBeforeButtons (label, false); // Add topic/journal ID input with auto-completion. // Only existing topic/journal IDs are accepted so no ID validation is performed. mTopic = new CSVWidget::DropLineEdit(displayType, this); mTopic->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons (mTopic, true); setManualEditing (false); connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); connect (mTopic, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::InfoCreator::cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type) { CSMWorld::IdTable& infoTable = dynamic_cast (*getData().getTableModel (getCollectionId())); int topicColumn = infoTable.findColumnIndex ( getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); mTopic->setText ( infoTable.data (infoTable.getModelIndex (originId, topicColumn)).toString()); GenericCreator::cloneMode (originId, type); } void CSVWorld::InfoCreator::reset() { mTopic->setText (""); GenericCreator::reset(); } std::string CSVWorld::InfoCreator::getErrors() const { // We ignore errors from GenericCreator here, because they can never happen in an InfoCreator. std::string errors; std::string topic = mTopic->text().toUtf8().constData(); if ((getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? getData().getTopics() : getData().getJournals()).searchId (topic)==-1) { errors += "Invalid Topic ID"; } return errors; } void CSVWorld::InfoCreator::focus() { mTopic->setFocus(); } void CSVWorld::InfoCreator::topicChanged() { update(); } CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new InfoCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } openmw-openmw-0.48.0/apps/opencs/view/world/infocreator.hpp000066400000000000000000000027651445372753700240250ustar00rootroot00000000000000#ifndef CSV_WORLD_INFOCREATOR_H #define CSV_WORLD_INFOCREATOR_H #include "genericcreator.hpp" namespace CSMWorld { class InfoCollection; class IdCompletionManager; } namespace CSVWidget { class DropLineEdit; } namespace CSVWorld { class InfoCreator : public GenericCreator { Q_OBJECT CSVWidget::DropLineEdit *mTopic; std::string getId() const override; void configureCreateCommand (CSMWorld::CreateCommand& command) const override; public: InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager); void cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type) override; void reset() override; std::string getErrors() const override; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. /// Focus main input widget void focus() override; private slots: void topicChanged(); }; class InfoCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/landcreator.cpp000066400000000000000000000072361445372753700240010ustar00rootroot00000000000000#include "landcreator.hpp" #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/land.hpp" namespace CSVWorld { LandCreator::LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator(data, undoStack, id) , mXLabel(nullptr) , mYLabel(nullptr) , mX(nullptr) , mY(nullptr) { const int MaxInt = std::numeric_limits::max(); const int MinInt = std::numeric_limits::min(); setManualEditing(false); mXLabel = new QLabel("X: "); mX = new QSpinBox(); mX->setMinimum(MinInt); mX->setMaximum(MaxInt); insertBeforeButtons(mXLabel, false); insertBeforeButtons(mX, true); mYLabel = new QLabel("Y: "); mY = new QSpinBox(); mY->setMinimum(MinInt); mY->setMaximum(MaxInt); insertBeforeButtons(mYLabel, false); insertBeforeButtons(mY, true); connect (mX, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); connect (mY, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); } void LandCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { GenericCreator::cloneMode(originId, type); int x = 0, y = 0; CSMWorld::Land::parseUniqueRecordId(originId, x, y); mX->setValue(x); mY->setValue(y); } void LandCreator::touch(const std::vector& ids) { // Combine multiple touch commands into one "macro" command getUndoStack().beginMacro("Touch records"); CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); for (const CSMWorld::UniversalId& uid : ids) { CSMWorld::TouchLandCommand* touchCmd = new CSMWorld::TouchLandCommand(lands, ltexs, uid.getId()); getUndoStack().push(touchCmd); } // Execute getUndoStack().endMacro(); } void LandCreator::focus() { mX->setFocus(); } void LandCreator::reset() { GenericCreator::reset(); mX->setValue(0); mY->setValue(0); } std::string LandCreator::getErrors() const { if (getData().getLand().searchId(getId()) >= 0) return "A land with that name already exists."; return ""; } std::string LandCreator::getId() const { return CSMWorld::Land::createUniqueRecordId(mX->value(), mY->value()); } void LandCreator::pushCommand(std::unique_ptr command, const std::string& id) { if (mCloneMode) { CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); getUndoStack().beginMacro(("Clone " + id).c_str()); getUndoStack().push(command.release()); CSMWorld::CopyLandTexturesCommand* ltexCopy = new CSMWorld::CopyLandTexturesCommand(lands, ltexs, getClonedId(), getId()); getUndoStack().push(ltexCopy); getUndoStack().endMacro(); } else getUndoStack().push (command.release()); } void LandCreator::coordChanged(int value) { update(); } } openmw-openmw-0.48.0/apps/opencs/view/world/landcreator.hpp000066400000000000000000000020301445372753700237710ustar00rootroot00000000000000#ifndef CSV_WORLD_LANDCREATOR_H #define CSV_WORLD_LANDCREATOR_H #include "genericcreator.hpp" class QLabel; class QSpinBox; namespace CSVWorld { class LandCreator : public GenericCreator { Q_OBJECT QLabel* mXLabel; QLabel* mYLabel; QSpinBox* mX; QSpinBox* mY; public: LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; void touch(const std::vector& ids) override; void focus() override; void reset() override; std::string getErrors() const override; protected: std::string getId() const override; void pushCommand(std::unique_ptr command, const std::string& id) override; private slots: void coordChanged(int value); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/landtexturecreator.cpp000066400000000000000000000060771445372753700254240ustar00rootroot00000000000000#include "landtexturecreator.hpp" #include #include #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" namespace CSVWorld { LandTextureCreator::LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator(data, undoStack, id) { // One index is reserved for a default texture const size_t MaxIndex = std::numeric_limits::max() - 1; setManualEditing(false); QLabel* nameLabel = new QLabel("Name"); insertBeforeButtons(nameLabel, false); mNameEdit = new QLineEdit(this); insertBeforeButtons(mNameEdit, true); QLabel* indexLabel = new QLabel("Index"); insertBeforeButtons(indexLabel, false); mIndexBox = new QSpinBox(this); mIndexBox->setMinimum(0); mIndexBox->setMaximum(MaxIndex); insertBeforeButtons(mIndexBox, true); connect(mNameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(nameChanged(const QString&))); connect(mIndexBox, SIGNAL(valueChanged(int)), this, SLOT(indexChanged(int))); } void LandTextureCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { GenericCreator::cloneMode(originId, type); CSMWorld::IdTable& table = dynamic_cast(*getData().getTableModel(getCollectionId())); int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname); mNameEdit->setText((table.data(table.getModelIndex(originId, column)).toString())); column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureIndex); mIndexBox->setValue((table.data(table.getModelIndex(originId, column)).toInt())); } void LandTextureCreator::focus() { mIndexBox->setFocus(); } void LandTextureCreator::reset() { GenericCreator::reset(); mNameEdit->setText(""); mIndexBox->setValue(0); } std::string LandTextureCreator::getErrors() const { if (getData().getLandTextures().searchId(getId()) >= 0) { return "Index is already in use"; } return ""; } void LandTextureCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { GenericCreator::configureCreateCommand(command); CSMWorld::IdTable& table = dynamic_cast(*getData().getTableModel(getCollectionId())); int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname); command.addValue(column, mName.c_str()); } std::string LandTextureCreator::getId() const { return CSMWorld::LandTexture::createUniqueRecordId(0, mIndexBox->value()); } void LandTextureCreator::nameChanged(const QString& value) { mName = value.toUtf8().constData(); update(); } void LandTextureCreator::indexChanged(int value) { update(); } } openmw-openmw-0.48.0/apps/opencs/view/world/landtexturecreator.hpp000066400000000000000000000020121445372753700254120ustar00rootroot00000000000000#ifndef CSV_WORLD_LANDTEXTURECREATOR_H #define CSV_WORLD_LANDTEXTURECREATOR_H #include #include "genericcreator.hpp" class QLineEdit; class QSpinBox; namespace CSVWorld { class LandTextureCreator : public GenericCreator { Q_OBJECT public: LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; void focus() override; void reset() override; std::string getErrors() const override; protected: void configureCreateCommand(CSMWorld::CreateCommand& command) const override; std::string getId() const override; private slots: void nameChanged(const QString& val); void indexChanged(int val); private: QLineEdit* mNameEdit; QSpinBox* mIndexBox; std::string mName; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/nestedtable.cpp000066400000000000000000000114621445372753700237710ustar00rootroot00000000000000#include "nestedtable.hpp" #include #include #include #include "../../model/prefs/shortcut.hpp" #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/commandmacro.hpp" #include "tableeditidaction.hpp" #include "util.hpp" CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, QWidget* parent, bool editable, bool fixedRows) : DragRecordTable(document, parent), mAddNewRowAction(nullptr), mRemoveRowAction(nullptr), mEditIdAction(nullptr), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); int columns = model->columnCount(QModelIndex()); for(int i = 0 ; i < columns; ++i) { CSMWorld::ColumnBase::Display display = static_cast ( model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, mDispatcher, document, this); setItemDelegateForColumn(i, delegate); } setModel(model); if (editable) { if (!fixedRows) { mAddNewRowAction = new QAction (tr ("Add new row"), this); connect(mAddNewRowAction, SIGNAL(triggered()), this, SLOT(addNewRowActionTriggered())); CSMPrefs::Shortcut* addRowShortcut = new CSMPrefs::Shortcut("table-add", this); addRowShortcut->associateAction(mAddNewRowAction); mRemoveRowAction = new QAction (tr ("Remove rows"), this); connect(mRemoveRowAction, SIGNAL(triggered()), this, SLOT(removeRowActionTriggered())); CSMPrefs::Shortcut* removeRowShortcut = new CSMPrefs::Shortcut("table-remove", this); removeRowShortcut->associateAction(mRemoveRowAction); } mEditIdAction = new TableEditIdAction(*this, this); connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); } } std::vector CSVWorld::NestedTable::getDraggedRecords() const { // No drag support for nested tables return std::vector(); } void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { if (!mEditIdAction) return; QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); int currentRow = rowAt(event->y()); int currentColumn = columnAt(event->x()); if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { mEditIdAction->setCell(currentRow, currentColumn); menu.addAction(mEditIdAction); menu.addSeparator(); } if (mAddNewRowAction && mRemoveRowAction) { menu.addAction(mAddNewRowAction); menu.addAction(mRemoveRowAction); } menu.exec (event->globalPos()); } void CSVWorld::NestedTable::removeRowActionTriggered() { CSMWorld::CommandMacro macro(mDocument.getUndoStack(), selectionModel()->selectedRows().size() > 1 ? tr("Remove rows") : ""); // Remove rows in reverse order for (int i = selectionModel()->selectedRows().size() - 1; i >= 0; --i) { macro.push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), mModel->getParentId(), selectionModel()->selectedRows()[i].row(), mModel->getParentColumn())); } } void CSVWorld::NestedTable::addNewRowActionTriggered() { int row = 0; if (!selectionModel()->selectedRows().empty()) row = selectionModel()->selectedRows().back().row() + 1; mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), mModel->getParentId(), row, mModel->getParentColumn())); } void CSVWorld::NestedTable::editCell() { emit editRequest(mEditIdAction->getCurrentId(), ""); } openmw-openmw-0.48.0/apps/opencs/view/world/nestedtable.hpp000066400000000000000000000024601445372753700237740ustar00rootroot00000000000000#ifndef CSV_WORLD_NESTEDTABLE_H #define CSV_WORLD_NESTEDTABLE_H #include "dragrecordtable.hpp" class QAction; class QContextMenuEvent; namespace CSMWorld { class NestedTableProxyModel; class UniversalId; class CommandDispatcher; } namespace CSMDoc { class Document; } namespace CSVWorld { class TableEditIdAction; class NestedTable : public DragRecordTable { Q_OBJECT QAction *mAddNewRowAction; QAction *mRemoveRowAction; TableEditIdAction *mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; CSMWorld::CommandDispatcher *mDispatcher; public: NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, QWidget* parent = nullptr, bool editable = true, bool fixedRows = false); std::vector getDraggedRecords() const override; private: void contextMenuEvent (QContextMenuEvent *event) override; private slots: void removeRowActionTriggered(); void addNewRowActionTriggered(); void editCell(); signals: void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/pathgridcreator.cpp000066400000000000000000000057101445372753700246600ustar00rootroot00000000000000#include "pathgridcreator.hpp" #include #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/idtable.hpp" #include "../widget/droplineedit.hpp" std::string CSVWorld::PathgridCreator::getId() const { return mCell->text().toUtf8().constData(); } CSMWorld::IdTable& CSVWorld::PathgridCreator::getPathgridsTable() const { return dynamic_cast ( *getData().getTableModel(getCollectionId()) ); } CSVWorld::PathgridCreator::PathgridCreator( CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager ) : GenericCreator(data, undoStack, id) { setManualEditing(false); QLabel *label = new QLabel("Cell", this); insertBeforeButtons(label, false); // Add cell ID input with auto-completion. // Only existing cell IDs are accepted so no ID validation is performed. CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Cell; mCell = new CSVWidget::DropLineEdit(displayType, this); mCell->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons(mCell, true); connect(mCell, SIGNAL (textChanged(const QString&)), this, SLOT (cellChanged())); connect(mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::PathgridCreator::cloneMode( const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); // Look up cloned record in pathgrids table and set cell ID text. CSMWorld::IdTable& table = getPathgridsTable(); int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); mCell->setText(table.data(table.getModelIndex(originId, column)).toString()); } std::string CSVWorld::PathgridCreator::getErrors() const { std::string cellId = getId(); // Check user input for any errors. std::string errors; if (cellId.empty()) { errors = "No cell ID selected"; } else if (getData().getPathgrids().searchId(cellId) > -1) { errors = "Pathgrid for selected cell ID already exists"; } else if (getData().getCells().searchId(cellId) == -1) { errors = "Cell with selected cell ID does not exist"; } return errors; } void CSVWorld::PathgridCreator::focus() { mCell->setFocus(); } void CSVWorld::PathgridCreator::reset() { CSVWorld::GenericCreator::reset(); mCell->setText(""); } void CSVWorld::PathgridCreator::cellChanged() { update(); } CSVWorld::Creator *CSVWorld::PathgridCreatorFactory::makeCreator( CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new PathgridCreator( document.getData(), document.getUndoStack(), id, document.getIdCompletionManager() ); } openmw-openmw-0.48.0/apps/opencs/view/world/pathgridcreator.hpp000066400000000000000000000037511445372753700246700ustar00rootroot00000000000000#ifndef PATHGRIDCREATOR_HPP #define PATHGRIDCREATOR_HPP #include "genericcreator.hpp" namespace CSMDoc { class Document; } namespace CSMWorld { class Data; class IdCompletionManager; class IdTable; class UniversalId; } namespace CSVWidget { class DropLineEdit; } namespace CSVWorld { /// \brief Record creator for pathgrids. class PathgridCreator : public GenericCreator { Q_OBJECT CSVWidget::DropLineEdit *mCell; private: /// \return Cell ID entered by user. std::string getId() const override; /// \return reference to table containing pathgrids. CSMWorld::IdTable& getPathgridsTable() const; public: PathgridCreator( CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager); /// \brief Set cell ID input widget to ID of record to be cloned. /// \param originId Cell ID to be cloned. /// \param type Type of record to be cloned. void cloneMode( const std::string& originId, const CSMWorld::UniversalId::Type type) override; /// \return Error description for current user input. std::string getErrors() const override; /// \brief Set focus to cell ID input widget. void focus() override; /// \brief Clear cell ID input widget. void reset() override; private slots: /// \brief Check user input for errors. void cellChanged(); }; /// \brief Creator factory for pathgrid record creator. class PathgridCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator( CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; }; } #endif // PATHGRIDCREATOR_HPP openmw-openmw-0.48.0/apps/opencs/view/world/previewsubview.cpp000066400000000000000000000040111445372753700245550ustar00rootroot00000000000000#include "previewsubview.hpp" #include #include "../render/previewwidget.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mTitle (id.toString().c_str()) { QHBoxLayout *layout = new QHBoxLayout; if (document.getData().getReferenceables().searchId (id.getId())==-1) { std::string referenceableId = document.getData().getReferences().getRecord (id.getId()).get().mRefID; referenceableIdChanged (referenceableId); mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), false, this); } else mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); mScene->setExterior(true); CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this); CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); layout->addWidget (toolbar, 0); layout->addWidget (mScene, 1); QWidget *widget = new QWidget; widget->setLayout (layout); setWidget (widget); connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); connect (mScene, SIGNAL (referenceableIdChanged (const std::string&)), this, SLOT (referenceableIdChanged (const std::string&))); connect (mScene, SIGNAL (focusToolbarRequest()), toolbar, SLOT (setFocus())); connect (toolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); } void CSVWorld::PreviewSubView::setEditLock (bool locked) {} std::string CSVWorld::PreviewSubView::getTitle() const { return mTitle; } void CSVWorld::PreviewSubView::referenceableIdChanged (const std::string& id) { if (id.empty()) mTitle = "Preview: Reference to "; else mTitle = "Preview: Reference to " + id; setWindowTitle (QString::fromUtf8 (mTitle.c_str())); emit updateTitle(); } openmw-openmw-0.48.0/apps/opencs/view/world/previewsubview.hpp000066400000000000000000000012641445372753700245710ustar00rootroot00000000000000#ifndef CSV_WORLD_PREVIEWSUBVIEW_H #define CSV_WORLD_PREVIEWSUBVIEW_H #include "../doc/subview.hpp" namespace CSMDoc { class Document; } namespace CSVRender { class PreviewWidget; } namespace CSVWorld { class PreviewSubView : public CSVDoc::SubView { Q_OBJECT CSVRender::PreviewWidget *mScene; std::string mTitle; public: PreviewSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; std::string getTitle() const override; private slots: void referenceableIdChanged (const std::string& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/recordbuttonbar.cpp000066400000000000000000000145661445372753700247060ustar00rootroot00000000000000#include "recordbuttonbar.hpp" #include #include #include "../../model/world/idtable.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" #include "../world/tablebottombox.hpp" void CSVWorld::RecordButtonBar::updateModificationButtons() { bool createAndDeleteDisabled = !mBottom || !mBottom->canCreateAndDelete() || mLocked; mCloneButton->setDisabled (createAndDeleteDisabled); mAddButton->setDisabled (createAndDeleteDisabled); bool commandDisabled = !mCommandDispatcher || mLocked; mRevertButton->setDisabled (commandDisabled); mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); } void CSVWorld::RecordButtonBar::updatePrevNextButtons() { int rows = mTable.rowCount(); if (rows<=1) { mPrevButton->setDisabled (true); mNextButton->setDisabled (true); } else if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) { mPrevButton->setDisabled (false); mNextButton->setDisabled (false); } else { int row = mTable.getModelIndex (mId.getId(), 0).row(); mPrevButton->setDisabled (row<=0); mNextButton->setDisabled (row>=rows-1); } } CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, CSMWorld::IdTable& table, TableBottomBox *bottomBox, CSMWorld::CommandDispatcher *commandDispatcher, QWidget *parent) : QWidget (parent), mId (id), mTable (table), mBottom (bottomBox), mCommandDispatcher (commandDispatcher), mLocked (false) { QHBoxLayout *buttonsLayout = new QHBoxLayout; buttonsLayout->setContentsMargins (0, 0, 0, 0); // left section mPrevButton = new QToolButton (this); mPrevButton->setIcon(QIcon(":record-previous")); mPrevButton->setToolTip ("Switch to previous record"); buttonsLayout->addWidget (mPrevButton, 0); mNextButton = new QToolButton (this); mNextButton->setIcon(QIcon(":/record-next")); mNextButton->setToolTip ("Switch to next record"); buttonsLayout->addWidget (mNextButton, 1); buttonsLayout->addStretch(2); // optional buttons of the right section if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton (this); previewButton->setIcon(QIcon(":edit-preview")); previewButton->setToolTip ("Open a preview of this record"); buttonsLayout->addWidget(previewButton); connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); } if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton (this); viewButton->setIcon(QIcon(":/cell.png")); viewButton->setToolTip ("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect (viewButton, SIGNAL(clicked()), this, SIGNAL (viewRecord())); } // right section mCloneButton = new QToolButton (this); mCloneButton->setIcon(QIcon(":edit-clone")); mCloneButton->setToolTip ("Clone record"); buttonsLayout->addWidget(mCloneButton); mAddButton = new QToolButton (this); mAddButton->setIcon(QIcon(":edit-add")); mAddButton->setToolTip ("Add new record"); buttonsLayout->addWidget(mAddButton); mDeleteButton = new QToolButton (this); mDeleteButton->setIcon(QIcon(":edit-delete")); mDeleteButton->setToolTip ("Delete record"); buttonsLayout->addWidget(mDeleteButton); mRevertButton = new QToolButton (this); mRevertButton->setIcon(QIcon(":edit-undo")); mRevertButton->setToolTip ("Revert record"); buttonsLayout->addWidget(mRevertButton); setLayout (buttonsLayout); // connections if(mBottom && mBottom->canCreateAndDelete()) { connect (mAddButton, SIGNAL (clicked()), mBottom, SLOT (createRequest())); connect (mCloneButton, SIGNAL (clicked()), this, SLOT (cloneRequest())); } connect (mNextButton, SIGNAL (clicked()), this, SLOT (nextId())); connect (mPrevButton, SIGNAL (clicked()), this, SLOT (prevId())); if (mCommandDispatcher) { connect (mRevertButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeRevert())); connect (mDeleteButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeDelete())); } connect (&mTable, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); updateModificationButtons(); updatePrevNextButtons(); } void CSVWorld::RecordButtonBar::setEditLock (bool locked) { mLocked = locked; updateModificationButtons(); } void CSVWorld::RecordButtonBar::universalIdChanged (const CSMWorld::UniversalId& id) { mId = id; updatePrevNextButtons(); } void CSVWorld::RecordButtonBar::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="General Input/cycle") updatePrevNextButtons(); } void CSVWorld::RecordButtonBar::cloneRequest() { if (mBottom) { int typeColumn = mTable.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); QModelIndex typeIndex = mTable.getModelIndex (mId.getId(), typeColumn); CSMWorld::UniversalId::Type type = static_cast ( mTable.data (typeIndex).toInt()); mBottom->cloneRequest (mId.getId(), type); } } void CSVWorld::RecordButtonBar::nextId() { int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; if (newRow >= mTable.rowCount()) { if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) newRow = 0; else return; } emit switchToRow (newRow); } void CSVWorld::RecordButtonBar::prevId() { int newRow = mTable.getModelIndex (mId.getId(), 0).row() - 1; if (newRow < 0) { if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) newRow = mTable.rowCount()-1; else return; } emit switchToRow (newRow); } void CSVWorld::RecordButtonBar::rowNumberChanged (const QModelIndex& parent, int start, int end) { updatePrevNextButtons(); } openmw-openmw-0.48.0/apps/opencs/view/world/recordbuttonbar.hpp000066400000000000000000000037321445372753700247040ustar00rootroot00000000000000#ifndef CSV_WORLD_RECORDBUTTONBAR_H #define CSV_WORLD_RECORDBUTTONBAR_H #include #include "../../model/world/universalid.hpp" class QToolButton; class QModelIndex; namespace CSMWorld { class IdTable; class CommandDispatcher; } namespace CSMPrefs { class Setting; } namespace CSVWorld { class TableBottomBox; /// \brief Button bar for use in dialogue-type subviews /// /// Contains the following buttons: /// - next/prev /// - clone /// - add /// - delete /// - revert /// - preview (optional) /// - view (optional) class RecordButtonBar : public QWidget { Q_OBJECT CSMWorld::UniversalId mId; CSMWorld::IdTable& mTable; TableBottomBox *mBottom; CSMWorld::CommandDispatcher *mCommandDispatcher; QToolButton *mPrevButton; QToolButton *mNextButton; QToolButton *mCloneButton; QToolButton *mAddButton; QToolButton *mDeleteButton; QToolButton *mRevertButton; bool mLocked; private: void updateModificationButtons(); void updatePrevNextButtons(); public: RecordButtonBar (const CSMWorld::UniversalId& id, CSMWorld::IdTable& table, TableBottomBox *bottomBox = nullptr, CSMWorld::CommandDispatcher *commandDispatcher = nullptr, QWidget *parent = nullptr); void setEditLock (bool locked); public slots: void universalIdChanged (const CSMWorld::UniversalId& id); private slots: void settingChanged (const CSMPrefs::Setting *setting); void cloneRequest(); void nextId(); void prevId(); void rowNumberChanged (const QModelIndex& parent, int start, int end); signals: void showPreview(); void viewRecord(); void switchToRow (int row); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/recordstatusdelegate.cpp000066400000000000000000000024151445372753700257120ustar00rootroot00000000000000#include "recordstatusdelegate.hpp" #include "../../model/world/columns.hpp" CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, "Records", "status-format", parent) {} CSVWorld::CommandDelegate *CSVWorld::RecordStatusDelegateFactory::makeDelegate ( CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { return new RecordStatusDelegate (mValues, mIcons, dispatcher, document, parent); } CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory() { std::vector> enums = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); static const char *sIcons[] = { ":list-base", ":list-modified", ":list-added", ":list-removed", ":list-removed", 0 }; for (int i=0; sIcons[i]; ++i) { auto& enumPair = enums.at(i); add (enumPair.first, enumPair.second.c_str(), sIcons[i]); } } openmw-openmw-0.48.0/apps/opencs/view/world/recordstatusdelegate.hpp000066400000000000000000000016271445372753700257230ustar00rootroot00000000000000#ifndef RECORDSTATUSDELEGATE_H #define RECORDSTATUSDELEGATE_H #include "util.hpp" #include "datadisplaydelegate.hpp" #include "../../model/world/record.hpp" class QIcon; class QFont; namespace CSVWorld { class RecordStatusDelegate : public DataDisplayDelegate { public: RecordStatusDelegate (const ValueList& values, const IconList& icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent = nullptr); }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory { public: RecordStatusDelegateFactory(); CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } #endif // RECORDSTATUSDELEGATE_HPP openmw-openmw-0.48.0/apps/opencs/view/world/referenceablecreator.cpp000066400000000000000000000047161445372753700256450ustar00rootroot00000000000000#include "referenceablecreator.hpp" #include #include #include "../../model/world/universalid.hpp" #include "../../model/world/commands.hpp" void CSVWorld::ReferenceableCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { command.setType ( static_cast (mType->itemData (mType->currentIndex()).toInt())); } CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) { QLabel *label = new QLabel ("Type", this); insertBeforeButtons (label, false); std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); mType = new QComboBox (this); mType->setMaxVisibleItems(20); for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { CSMWorld::UniversalId id2 (*iter, ""); mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast (id2.getType())); } mType->model()->sort(0); insertBeforeButtons (mType, false); connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); } void CSVWorld::ReferenceableCreator::reset() { mType->setCurrentIndex (0); GenericCreator::reset(); } void CSVWorld::ReferenceableCreator::setType (int index) { // container items have name limit of 32 characters std::string text = mType->currentText().toStdString(); if (text == "Potion" || text == "Apparatus" || text == "Armor" || text == "Book" || text == "Clothing" || text == "Ingredient" || text == "ItemLevelledList" || text == "Light" || text == "Lockpick" || text == "Miscellaneous" || text == "Probe" || text == "Repair" || text == "Weapon") { GenericCreator::setEditorMaxLength (32); } else GenericCreator::setEditorMaxLength (32767); } void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type) { GenericCreator::cloneMode (originId, type); mType->setCurrentIndex (mType->findData (static_cast (type))); } void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) { CSVWorld::GenericCreator::toggleWidgets(active); mType->setEnabled(active); } openmw-openmw-0.48.0/apps/opencs/view/world/referenceablecreator.hpp000066400000000000000000000014711445372753700256450ustar00rootroot00000000000000#ifndef CSV_WORLD_REFERENCEABLECREATOR_H #define CSV_WORLD_REFERENCEABLECREATOR_H class QComboBox; #include "genericcreator.hpp" namespace CSVWorld { class ReferenceableCreator : public GenericCreator { Q_OBJECT QComboBox *mType; private: void configureCreateCommand (CSMWorld::CreateCommand& command) const override; public: ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); void reset() override; void cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type) override; void toggleWidgets(bool active = true) override; private slots: void setType (int index); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/referencecreator.cpp000066400000000000000000000066511445372753700250210ustar00rootroot00000000000000#include "referencecreator.hpp" #include #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/commandmacro.hpp" #include "../widget/droplineedit.hpp" std::string CSVWorld::ReferenceCreator::getId() const { return mId; } void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { // Set cellID int cellIdColumn = dynamic_cast (*getData().getTableModel (getCollectionId())). findColumnIndex (CSMWorld::Columns::ColumnId_Cell); command.addValue (cellIdColumn, mCell->text()); } CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager) : GenericCreator (data, undoStack, id) { QLabel *label = new QLabel ("Cell", this); insertBeforeButtons (label, false); // Add cell ID input with auto-completion. // Only existing cell IDs are accepted so no ID validation is performed. mCell = new CSVWidget::DropLineEdit(CSMWorld::ColumnBase::Display_Cell, this); mCell->setCompleter(completionManager.getCompleter(CSMWorld::ColumnBase::Display_Cell).get()); insertBeforeButtons (mCell, true); setManualEditing (false); connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged())); connect (mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::ReferenceCreator::reset() { GenericCreator::reset(); mCell->setText (""); mId = getData().getReferences().getNewId(); } std::string CSVWorld::ReferenceCreator::getErrors() const { // We are ignoring errors coming from GenericCreator here, because the ID of the new // record is internal and requires neither user input nor verification. std::string errors; std::string cell = mCell->text().toUtf8().constData(); if (cell.empty()) errors += "Missing Cell ID"; else if (getData().getCells().searchId (cell)==-1) errors += "Invalid Cell ID"; return errors; } void CSVWorld::ReferenceCreator::focus() { mCell->setFocus(); } void CSVWorld::ReferenceCreator::cellChanged() { update(); } void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSMWorld::IdTable& referenceTable = dynamic_cast ( *getData().getTableModel (CSMWorld::UniversalId::Type_References)); int cellIdColumn = referenceTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); mCell->setText ( referenceTable.data (referenceTable.getModelIndex (originId, cellIdColumn)).toString()); CSVWorld::GenericCreator::cloneMode(originId, type); cellChanged(); //otherwise ok button will remain disabled } CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new ReferenceCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } openmw-openmw-0.48.0/apps/opencs/view/world/referencecreator.hpp000066400000000000000000000030341445372753700250160ustar00rootroot00000000000000#ifndef CSV_WORLD_REFERENCECREATOR_H #define CSV_WORLD_REFERENCECREATOR_H #include "genericcreator.hpp" namespace CSMWorld { class IdCompletionManager; } namespace CSVWidget { class DropLineEdit; } namespace CSVWorld { class ReferenceCreator : public GenericCreator { Q_OBJECT CSVWidget::DropLineEdit *mCell; std::string mId; private: std::string getId() const override; void configureCreateCommand (CSMWorld::CreateCommand& command) const override; public: ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager); void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; void reset() override; std::string getErrors() const override; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. /// Focus main input widget void focus() override; private slots: void cellChanged(); }; class ReferenceCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; ///< The ownership of the returned Creator is transferred to the caller. }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/regionmap.cpp000066400000000000000000000277301445372753700234650ustar00rootroot00000000000000#include "regionmap.hpp" #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/regionmap.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commandmacro.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { QMenu menu (this); if (getUnselectedCells().size()>0) menu.addAction (mSelectAllAction); if (selectionModel()->selectedIndexes().size()>0) menu.addAction (mClearSelectionAction); if (getMissingRegionCells().size()>0) menu.addAction (mSelectRegionsAction); int selectedNonExistentCells = getSelectedCells (false, true).size(); if (selectedNonExistentCells>0) { if (selectedNonExistentCells==1) mCreateCellsAction->setText ("Create one Cell"); else { std::ostringstream stream; stream << "Create " << selectedNonExistentCells << " cells"; mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str())); } menu.addAction (mCreateCellsAction); } if (getSelectedCells().size()>0) { if (!mRegionId.empty()) { mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str())); menu.addAction (mSetRegionAction); } menu.addAction (mUnsetRegionAction); menu.addAction (mViewInTableAction); } if (selectionModel()->selectedIndexes().size()>0) menu.addAction (mViewAction); menu.exec (event->globalPos()); } QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const { const QAbstractItemModel *model = QTableView::model(); int rows = model->rowCount(); int columns = model->columnCount(); QModelIndexList selected = selectionModel()->selectedIndexes(); std::sort (selected.begin(), selected.end()); QModelIndexList all; for (int y=0; yindex (y, x); if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern)) all.push_back (index); } std::sort (all.begin(), all.end()); QModelIndexList list; std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(), std::back_inserter (list)); return list; } QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const { const QAbstractItemModel *model = QTableView::model(); QModelIndexList selected = selectionModel()->selectedIndexes(); QModelIndexList list; for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); if ((exists && existent) || (!exists && nonExistent)) list.push_back (*iter); } return list; } QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const { const QAbstractItemModel *model = QTableView::model(); QModelIndexList selected = selectionModel()->selectedIndexes(); std::set regions; for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string region = model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); if (!region.empty()) regions.insert (region); } QModelIndexList list; QModelIndexList unselected = getUnselectedCells(); for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) { std::string region = model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); if (!region.empty() && regions.find (region)!=regions.end()) list.push_back (*iter); } return list; } void CSVWorld::RegionMap::setRegion (const std::string& regionId) { QModelIndexList selected = getSelectedCells(); QAbstractItemModel *regionModel = model(); CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); QString regionId2 = QString::fromUtf8 (regionId.c_str()); CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Set Region") : ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); QModelIndex index = cellsModel->getModelIndex (cellId, cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); macro.push (new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); } } CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent) : DragRecordTable(document, parent) { verticalHeader()->hide(); horizontalHeader()->hide(); setSelectionMode (QAbstractItemView::ExtendedSelection); setModel (document.getData().getTableModel (universalId)); resizeColumnsToContents(); resizeRowsToContents(); mSelectAllAction = new QAction (tr ("Select All"), this); connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll())); addAction (mSelectAllAction); mClearSelectionAction = new QAction (tr ("Clear Selection"), this); connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection())); addAction (mClearSelectionAction); mSelectRegionsAction = new QAction (tr ("Select Regions"), this); connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); addAction (mSelectRegionsAction); mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); addAction (mCreateCellsAction); mSetRegionAction = new QAction (tr ("Set Region"), this); connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion())); addAction (mSetRegionAction); mUnsetRegionAction = new QAction (tr ("Unset Region"), this); connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); addAction (mUnsetRegionAction); mViewAction = new QAction (tr ("View Cells"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); addAction (mViewAction); mViewInTableAction = new QAction (tr ("View Cells in Table"), this); connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable())); addAction (mViewInTableAction); setAcceptDrops(true); } void CSVWorld::RegionMap::selectAll() { QModelIndexList unselected = getUnselectedCells(); for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) selectionModel()->select (*iter, QItemSelectionModel::Select); } void CSVWorld::RegionMap::clearSelection() { selectionModel()->clearSelection(); } void CSVWorld::RegionMap::selectRegions() { QModelIndexList unselected = getMissingRegionCells(); for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) selectionModel()->select (*iter, QItemSelectionModel::Select); } void CSVWorld::RegionMap::createCells() { if (mEditLock) return; QModelIndexList selected = getSelectedCells (false, true); CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Create cells"): ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); macro.push (new CSMWorld::CreateCommand (*cellsModel, cellId)); } } void CSVWorld::RegionMap::setRegion() { if (mEditLock) return; setRegion (mRegionId); } void CSVWorld::RegionMap::unsetRegion() { if (mEditLock) return; setRegion (""); } void CSVWorld::RegionMap::view() { std::ostringstream hint; hint << "c:"; QModelIndexList selected = selectionModel()->selectedIndexes(); bool first = true; for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); if (first) first = false; else hint << "; "; hint << cellId; } emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace), hint.str()); } void CSVWorld::RegionMap::viewInTable() { std::ostringstream hint; hint << "f:!or("; QModelIndexList selected = getSelectedCells(); bool first = true; for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); if (first) first = false; else hint << ","; hint << "string(ID,\"" << cellId << "\")"; } hint << ")"; emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str()); } void CSVWorld::RegionMap::mouseMoveEvent (QMouseEvent* event) { startDragFromTable(*this); } std::vector< CSMWorld::UniversalId > CSVWorld::RegionMap::getDraggedRecords() const { QModelIndexList selected(getSelectedCells(true, false)); std::vector ids; for (const QModelIndex& it : selected) { ids.emplace_back( CSMWorld::UniversalId::Type_Cell, model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); } selected = getSelectedCells(false, true); for (const QModelIndex& it : selected) { ids.emplace_back( CSMWorld::UniversalId::Type_Cell_Missing, model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); } return ids; } void CSVWorld::RegionMap::dropEvent (QDropEvent* event) { QModelIndex index = indexAt (event->pos()); bool exists = QTableView::model()->data(index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); if (!index.isValid() || !exists) { return; } const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument(mDocument) && mime->holdsType(CSMWorld::UniversalId::Type_Region)) { CSMWorld::UniversalId record (mime->returnMatching (CSMWorld::UniversalId::Type_Region)); QAbstractItemModel *regionModel = model(); CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); std::string cellId(regionModel->data (index, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData()); QModelIndex index2(cellsModel->getModelIndex (cellId, cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region))); mDocument.getUndoStack().push(new CSMWorld::ModifyCommand (*cellsModel, index2, QString::fromUtf8(record.getId().c_str()))); mRegionId = record.getId(); } } openmw-openmw-0.48.0/apps/opencs/view/world/regionmap.hpp000066400000000000000000000041471445372753700234670ustar00rootroot00000000000000#ifndef CSV_WORLD_REGIONMAP_H #define CSV_WORLD_REGIONMAP_H #include #include #include "./dragrecordtable.hpp" class QAction; namespace CSMDoc { class Document; } namespace CSMWorld { class UniversalId; } namespace CSVWorld { class RegionMap : public DragRecordTable { Q_OBJECT QAction *mSelectAllAction; QAction *mClearSelectionAction; QAction *mSelectRegionsAction; QAction *mCreateCellsAction; QAction *mSetRegionAction; QAction *mUnsetRegionAction; QAction *mViewAction; QAction *mViewInTableAction; std::string mRegionId; private: void contextMenuEvent (QContextMenuEvent *event) override; QModelIndexList getUnselectedCells() const; ///< \note Non-existent cells are not listed. QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; ///< \param existent Include existent cells. /// \param nonExistent Include non-existent cells. QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. void setRegion (const std::string& regionId); ///< Set region Id of selected cells. void mouseMoveEvent(QMouseEvent *event) override; void dropEvent(QDropEvent* event) override; public: RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent = nullptr); std::vector getDraggedRecords() const override; signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); private slots: void selectAll() override; void clearSelection(); void selectRegions(); void createCells(); void setRegion(); void unsetRegion(); void view(); void viewInTable(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/regionmapsubview.cpp000066400000000000000000000013231445372753700250600ustar00rootroot00000000000000#include "regionmapsubview.hpp" #include "regionmap.hpp" CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document) : CSVDoc::SubView (universalId) { mRegionMap = new RegionMap (universalId, document, this); setWidget (mRegionMap); connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); } void CSVWorld::RegionMapSubView::setEditLock (bool locked) { mRegionMap->setEditLock (locked); } void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id, const std::string& hint) { focusId (id, hint); } openmw-openmw-0.48.0/apps/opencs/view/world/regionmapsubview.hpp000066400000000000000000000011531445372753700250660ustar00rootroot00000000000000#ifndef CSV_WORLD_REGIONMAPSUBVIEW_H #define CSV_WORLD_REGIONMAPSUBVIEW_H #include "../doc/subview.hpp" class QAction; namespace CSMDoc { class Document; } namespace CSVWorld { class RegionMap; class RegionMapSubView : public CSVDoc::SubView { Q_OBJECT RegionMap *mRegionMap; public: RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); void setEditLock (bool locked) override; private slots: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/scenesubview.cpp000066400000000000000000000174121445372753700242020ustar00rootroot00000000000000#include "scenesubview.hpp" #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/cellselection.hpp" #include "../filter/filterbox.hpp" #include "../render/pagedworldspacewidget.hpp" #include "../render/unpagedworldspacewidget.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" #include "tablebottombox.hpp" #include "creator.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mScene(nullptr), mLayout(new QHBoxLayout), mDocument(document), mToolbar(nullptr) { QVBoxLayout *layout = new QVBoxLayout; layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); CSVRender::WorldspaceWidget* worldspaceWidget = nullptr; widgetType whatWidget; if (id.getId()==ESM::CellId::sDefaultWorldspace) { whatWidget = widget_Paged; CSVRender::PagedWorldspaceWidget *newWidget = new CSVRender::PagedWorldspaceWidget (this, document); worldspaceWidget = newWidget; makeConnections(newWidget); } else { whatWidget = widget_Unpaged; CSVRender::UnpagedWorldspaceWidget *newWidget = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); worldspaceWidget = newWidget; makeConnections(newWidget); } replaceToolbarAndWorldspace(worldspaceWidget, makeToolbar(worldspaceWidget, whatWidget)); layout->insertLayout (0, mLayout, 1); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); layout->insertWidget (0, filterBox); QWidget *widget = new QWidget; widget->setLayout (layout); setWidget (widget); } void CSVWorld::SceneSubView::makeConnections (CSVRender::UnpagedWorldspaceWidget* widget) { connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); connect(widget, SIGNAL(dataDropped(const std::vector&)), this, SLOT(handleDrop(const std::vector&))); connect(widget, SIGNAL(cellChanged(const CSMWorld::UniversalId&)), this, SLOT(cellSelectionChanged(const CSMWorld::UniversalId&))); connect(widget, SIGNAL(requestFocus (const std::string&)), this, SIGNAL(requestFocus (const std::string&))); } void CSVWorld::SceneSubView::makeConnections (CSVRender::PagedWorldspaceWidget* widget) { connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); connect(widget, SIGNAL(dataDropped(const std::vector&)), this, SLOT(handleDrop(const std::vector&))); connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); connect(widget, SIGNAL(requestFocus (const std::string&)), this, SIGNAL(requestFocus (const std::string&))); } CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::WorldspaceWidget* widget, widgetType type) { CSVWidget::SceneToolbar* toolbar = new CSVWidget::SceneToolbar (48+6, this); CSVWidget::SceneToolMode *navigationTool = widget->makeNavigationSelector (toolbar); toolbar->addTool (navigationTool); CSVWidget::SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); CSVWidget::SceneToolToggle2 *sceneVisibilityTool = widget->makeSceneVisibilitySelector (toolbar); toolbar->addTool (sceneVisibilityTool); if (type==widget_Paged) { CSVWidget::SceneToolToggle2 *controlVisibilityTool = dynamic_cast (*widget). makeControlVisibilitySelector (toolbar); toolbar->addTool (controlVisibilityTool); } CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); toolbar->addTool (runTool); toolbar->addTool (widget->makeEditModeSelector (toolbar), runTool); return toolbar; } void CSVWorld::SceneSubView::setEditLock (bool locked) { mScene->setEditLock (locked); } void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } void CSVWorld::SceneSubView::useHint (const std::string& hint) { mScene->useViewHint (hint); } std::string CSVWorld::SceneSubView::getTitle() const { return mTitle; } void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) { setUniversalId(id); mTitle = "Scene: " + getUniversalId().getId(); setWindowTitle (QString::fromUtf8 (mTitle.c_str())); emit updateTitle(); } void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace)); int size = selection.getSize(); std::ostringstream stream; stream << "Scene: " << getUniversalId().getId(); if (size==0) stream << " (empty)"; else if (size==1) { stream << " (" << *selection.begin() << ")"; } else { stream << " (" << selection.getCentre() << " and " << size-1 << " more "; if (size>1) stream << "cells around it)"; else stream << "cell around it)"; } mTitle = stream.str(); setWindowTitle (QString::fromUtf8 (mTitle.c_str())); emit updateTitle(); } void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& universalIdData) { CSVRender::PagedWorldspaceWidget* pagedNewWidget = nullptr; CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = nullptr; CSVWidget::SceneToolbar* toolbar = nullptr; CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (universalIdData); switch (mScene->getDropRequirements (type)) { case CSVRender::WorldspaceWidget::canHandle: mScene->handleDrop (universalIdData, type); break; case CSVRender::WorldspaceWidget::needPaged: pagedNewWidget = new CSVRender::PagedWorldspaceWidget(this, mDocument); toolbar = makeToolbar(pagedNewWidget, widget_Paged); makeConnections(pagedNewWidget); replaceToolbarAndWorldspace(pagedNewWidget, toolbar); mScene->handleDrop (universalIdData, type); break; case CSVRender::WorldspaceWidget::needUnpaged: unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(universalIdData.begin()->getId(), mDocument, this); toolbar = makeToolbar(unPagedNewWidget, widget_Unpaged); makeConnections(unPagedNewWidget); replaceToolbarAndWorldspace(unPagedNewWidget, toolbar); cellSelectionChanged(*(universalIdData.begin())); break; case CSVRender::WorldspaceWidget::ignored: return; } } void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar) { assert(mLayout); if (mScene) { mLayout->removeWidget(mScene); mScene->deleteLater(); } if (mToolbar) { mLayout->removeWidget(mToolbar); mToolbar->deleteLater(); } mScene = widget; mToolbar = toolbar; connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); mLayout->addWidget (mToolbar, 0); mLayout->addWidget (mScene, 1); mScene->selectDefaultNavigationMode(); setFocusProxy (mScene); } openmw-openmw-0.48.0/apps/opencs/view/world/scenesubview.hpp000066400000000000000000000037371445372753700242140ustar00rootroot00000000000000#ifndef CSV_WORLD_SCENESUBVIEW_H #define CSV_WORLD_SCENESUBVIEW_H #include #include "../doc/subview.hpp" class QModelIndex; namespace CSMWorld { class CellSelection; } namespace CSMDoc { class Document; } namespace CSVRender { class WorldspaceWidget; class PagedWorldspaceWidget; class UnpagedWorldspaceWidget; } namespace CSVWidget { class SceneToolbar; class SceneToolMode; } namespace CSVWorld { class TableBottomBox; class CreatorFactoryBase; class SceneSubView : public CSVDoc::SubView { Q_OBJECT TableBottomBox *mBottom; CSVRender::WorldspaceWidget *mScene; QHBoxLayout* mLayout; CSMDoc::Document& mDocument; CSVWidget::SceneToolbar* mToolbar; std::string mTitle; public: SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; void setStatusBar (bool show) override; void useHint (const std::string& hint) override; std::string getTitle() const override; private: void makeConnections(CSVRender::PagedWorldspaceWidget* widget); void makeConnections(CSVRender::UnpagedWorldspaceWidget* widget); void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar); enum widgetType { widget_Paged, widget_Unpaged }; CSVWidget::SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); private slots: void cellSelectionChanged (const CSMWorld::CellSelection& selection); void cellSelectionChanged (const CSMWorld::UniversalId& id); void handleDrop(const std::vector& data); signals: void requestFocus (const std::string& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/scriptedit.cpp000066400000000000000000000346451445372753700236610ustar00rootroot00000000000000#include "scriptedit.hpp" #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) { ++mEdit.mChangeLocked; } CSVWorld::ScriptEdit::ChangeLock::~ChangeLock() { --mEdit.mChangeLocked; } bool CSVWorld::ScriptEdit::event (QEvent *event) { // ignore undo and redo shortcuts if (event->type()==QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast (event); if (keyEvent->matches (QKeySequence::Undo) || keyEvent->matches (QKeySequence::Redo)) return true; } return QPlainTextEdit::event (event); } CSVWorld::ScriptEdit::ScriptEdit( const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent ) : QPlainTextEdit(parent), mChangeLocked(0), mShowLineNum(false), mLineNumberArea(nullptr), mDefaultFont(font()), mMonoFont(QFont("Monospace")), mTabCharCount(4), mMarkOccurrences(true), mDocument(document), mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) { wrapLines(false); setTabWidth(); setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead mAllowedTypes <associateAction(mCommentAction); mUncommentAction = new QAction (tr ("Uncomment Selection"), this); connect(mUncommentAction, SIGNAL (triggered()), this, SLOT (uncommentSelection())); CSMPrefs::Shortcut *uncommentShortcut = new CSMPrefs::Shortcut("script-editor-uncomment", this); uncommentShortcut->associateAction(mUncommentAction); mHighlighter = new ScriptHighlighter (document.getData(), mode, ScriptEdit::document()); connect (&document.getData(), SIGNAL (idListChanged()), this, SLOT (idListChanged())); connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); { ChangeLock lock (*this); CSMPrefs::get()["Scripts"].update(); } mUpdateTimer.setSingleShot (true); // TODO: provide a font selector dialogue mMonoFont.setStyleHint(QFont::TypeWriter); mLineNumberArea = new LineNumberArea(this); updateLineNumberAreaWidth(0); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); updateHighlighting(); } void CSVWorld::ScriptEdit::showLineNum(bool show) { if(show!=mShowLineNum) { mShowLineNum = show; updateLineNumberAreaWidth(0); } } bool CSVWorld::ScriptEdit::isChangeLocked() const { return mChangeLocked!=0; } void CSVWorld::ScriptEdit::dragEnterEvent (QDragEnterEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) QPlainTextEdit::dragEnterEvent(event); else { setTextCursor (cursorForPosition (event->pos())); event->acceptProposedAction(); } } void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) QPlainTextEdit::dragMoveEvent(event); else { setTextCursor (cursorForPosition (event->pos())); event->accept(); } } void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped { QPlainTextEdit::dropEvent(event); return; } setTextCursor (cursorForPosition (event->pos())); if (mime->fromDocument (mDocument)) { std::vector records (mime->getData()); for (std::vector::iterator it = records.begin(); it != records.end(); ++it) { if (mAllowedTypes.contains (it->getType())) { if (stringNeedsQuote(it->getId())) { insertPlainText(QString::fromUtf8 (('"' + it->getId() + '"').c_str())); } else { insertPlainText(QString::fromUtf8 (it->getId().c_str())); } } } } } bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const { const QString string(QString::fromUtf8(id.c_str())); // is only for c++11, so let's use qregexp for now. //I'm not quite sure when do we need to put quotes. To be safe we will use quotes for anything other than… return !(string.contains(mWhiteListQoutes)); } void CSVWorld::ScriptEdit::setTabWidth() { // Set tab width to specified number of characters using current font. setTabStopDistance(mTabCharCount * fontMetrics().horizontalAdvance(' ')); } void CSVWorld::ScriptEdit::wrapLines(bool wrap) { if (wrap) { setLineWrapMode(QPlainTextEdit::WidgetWidth); } else { setLineWrapMode(QPlainTextEdit::NoWrap); } } void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting *setting) { // Determine which setting was changed. if (mHighlighter->settingChanged(setting)) { updateHighlighting(); } else if (*setting == "Scripts/mono-font") { setFont(setting->isTrue() ? mMonoFont : mDefaultFont); setTabWidth(); } else if (*setting == "Scripts/show-linenum") { showLineNum(setting->isTrue()); } else if (*setting == "Scripts/wrap-lines") { wrapLines(setting->isTrue()); } else if (*setting == "Scripts/tab-width") { mTabCharCount = setting->toInt(); setTabWidth(); } else if (*setting == "Scripts/highlight-occurrences") { mMarkOccurrences = setting->isTrue(); mHighlighter->setMarkedWord(""); updateHighlighting(); mHighlighter->setMarkOccurrences(mMarkOccurrences); } } void CSVWorld::ScriptEdit::idListChanged() { mHighlighter->invalidateIds(); if (!mUpdateTimer.isActive()) mUpdateTimer.start (0); } void CSVWorld::ScriptEdit::updateHighlighting() { if (isChangeLocked()) return; ChangeLock lock (*this); mHighlighter->rehighlight(); } int CSVWorld::ScriptEdit::lineNumberAreaWidth() { if(!mShowLineNum) return 0; int digits = 1; int max = qMax(1, blockCount()); while (max >= 10) { max /= 10; ++digits; } int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; return space; } void CSVWorld::ScriptEdit::updateLineNumberAreaWidth(int /* newBlockCount */) { setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } void CSVWorld::ScriptEdit::updateLineNumberArea(const QRect &rect, int dy) { if (dy) mLineNumberArea->scroll(0, dy); else mLineNumberArea->update(0, rect.y(), mLineNumberArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0); } void CSVWorld::ScriptEdit::markOccurrences() { if (mMarkOccurrences) { QTextCursor cursor = textCursor(); // prevent infinite recursion with cursor.select(), // which ends up calling this function again // could be fixed with blockSignals, but mDocument is const disconnect(this, SIGNAL(cursorPositionChanged()), this, nullptr); cursor.select(QTextCursor::WordUnderCursor); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(markOccurrences())); QString word = cursor.selectedText(); mHighlighter->setMarkedWord(word.toStdString()); mHighlighter->rehighlight(); } } 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(); } void CSVWorld::ScriptEdit::uncommentSelection() { 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.select(QTextCursor::LineUnderCursor); QString line = begin.selectedText(); if (line.size() == 0) continue; // get first nonspace character in line int index; for (index = 0; index != line.size(); ++index) { if (!line[index].isSpace()) break; } if (index != line.size() && line[index] == ';') { // remove the semicolon line.remove(index, 1); // put the line back begin.insertText(line); } } begin.endEditBlock(); } void CSVWorld::ScriptEdit::resizeEvent(QResizeEvent *e) { QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); mLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } void CSVWorld::ScriptEdit::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = createStandardContextMenu(); // remove redo/undo since they are disabled QList menuActions = menu->actions(); for (QList::iterator i = menuActions.begin(); i < menuActions.end(); ++i) { if ((*i)->text().contains("Undo") || (*i)->text().contains("Redo")) { (*i)->setVisible(false); } } menu->addAction(mCommentAction); menu->addAction(mUncommentAction); menu->exec(event->globalPos()); delete menu; } void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(mLineNumberArea); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + (int) blockBoundingRect(block).height(); int startBlock = textCursor().blockNumber(); int endBlock = textCursor().blockNumber(); if(textCursor().hasSelection()) { QString str = textCursor().selection().toPlainText(); int offset = str.count("\n"); if(textCursor().position() < textCursor().anchor()) endBlock += offset; else startBlock -= offset; } painter.setBackgroundMode(Qt::OpaqueMode); QFont font = painter.font(); QBrush background = painter.background(); while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { QFont newFont = painter.font(); QString number = QString::number(blockNumber + 1); if(blockNumber >= startBlock && blockNumber <= endBlock) { painter.setBackground(Qt::cyan); painter.setPen(Qt::darkMagenta); newFont.setBold(true); } else { painter.setBackground(background); painter.setPen(Qt::black); } painter.setFont(newFont); painter.drawText(0, top, mLineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number); painter.setFont(font); } block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); ++blockNumber; } } CSVWorld::LineNumberArea::LineNumberArea(ScriptEdit *editor) : QWidget(editor), mScriptEdit(editor) {} QSize CSVWorld::LineNumberArea::sizeHint() const { return QSize(mScriptEdit->lineNumberAreaWidth(), 0); } void CSVWorld::LineNumberArea::paintEvent(QPaintEvent *event) { mScriptEdit->lineNumberAreaPaintEvent(event); } openmw-openmw-0.48.0/apps/opencs/view/world/scriptedit.hpp000066400000000000000000000067051445372753700236620ustar00rootroot00000000000000#ifndef SCRIPTEDIT_H #define SCRIPTEDIT_H #include #include #include #include #include #include #include "../../model/world/universalid.hpp" #include "scripthighlighter.hpp" class QRegExp; namespace CSMDoc { class Document; } namespace CSVWorld { class LineNumberArea; /// \brief Editor for scripts. class ScriptEdit : public QPlainTextEdit { Q_OBJECT public: class ChangeLock { ScriptEdit& mEdit; ChangeLock (const ChangeLock&); ChangeLock& operator= (const ChangeLock&); public: ChangeLock (ScriptEdit& edit); ~ChangeLock(); }; friend class ChangeLock; private: int mChangeLocked; ScriptHighlighter *mHighlighter; QTimer mUpdateTimer; bool mShowLineNum; LineNumberArea *mLineNumberArea; QFont mDefaultFont; QFont mMonoFont; int mTabCharCount; bool mMarkOccurrences; QAction *mCommentAction; QAction *mUncommentAction; protected: bool event (QEvent *event) override; public: ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent); /// Should changes to the data be ignored (i.e. not cause updated)? /// /// \note This mechanism is used to avoid infinite update recursions bool isChangeLocked() const; void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); void showLineNum(bool show); protected: void resizeEvent(QResizeEvent *e) override; void contextMenuEvent(QContextMenuEvent *event) override; private: QVector mAllowedTypes; const CSMDoc::Document& mDocument; const QRegExp mWhiteListQoutes; void dragEnterEvent (QDragEnterEvent* event) override; void dropEvent (QDropEvent* event) override; void dragMoveEvent (QDragMoveEvent* event) override; bool stringNeedsQuote(const std::string& id) const; /// \brief Set tab width for script editor. void setTabWidth(); /// \brief Turn line wrapping in script editor on or off. /// \param wrap Whether or not to wrap lines. void wrapLines(bool wrap); private slots: /// \brief Update editor when related setting is changed. /// \param setting Setting that was changed. void settingChanged(const CSMPrefs::Setting *setting); void idListChanged(); void updateHighlighting(); void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberArea(const QRect &, int); void markOccurrences(); void commentSelection(); void uncommentSelection(); }; class LineNumberArea : public QWidget { ScriptEdit *mScriptEdit; public: LineNumberArea(ScriptEdit *editor); QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *event) override; }; } #endif // SCRIPTEDIT_H openmw-openmw-0.48.0/apps/opencs/view/world/scripterrortable.cpp000066400000000000000000000115311445372753700250620ustar00rootroot00000000000000#include "scripterrortable.hpp" #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream stream; stream << message << " (" << loc.mLiteral << ")"; addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, loc.mLine, loc.mColumn-loc.mLiteral.length()); } void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) { addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); } void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, CSMDoc::Message::Severity severity, int line, int column) { int row = rowCount(); setRowCount (row+1); QTableWidgetItem *severityItem = new QTableWidgetItem ( QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); setItem (row, 0, severityItem); if (line!=-1) { QTableWidgetItem *lineItem = new QTableWidgetItem; lineItem->setData (Qt::DisplayRole, line+1); lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); setItem (row, 1, lineItem); QTableWidgetItem *columnItem = new QTableWidgetItem; columnItem->setData (Qt::DisplayRole, column); columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); setItem (row, 3, columnItem); } else { QTableWidgetItem *lineItem = new QTableWidgetItem; lineItem->setData (Qt::DisplayRole, "-"); lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); setItem (row, 1, lineItem); } QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); setItem (row, 2, messageItem); } void CSVWorld::ScriptErrorTable::setWarningsMode (const std::string& value) { if (value=="Ignore") Compiler::ErrorHandler::setWarningsMode (0); else if (value=="Normal") Compiler::ErrorHandler::setWarningsMode (1); else if (value=="Strict") Compiler::ErrorHandler::setWarningsMode (2); } CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) : QTableWidget (parent), mContext (document.getData()) { setColumnCount (4); QStringList headers; headers << "Severity" << "Line" << "Description"; setHorizontalHeaderLabels (headers); horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setColumnHidden (3, true); setSelectionMode (QAbstractItemView::NoSelection); Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["Scripts"].update(); connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); } void CSVWorld::ScriptErrorTable::update (const std::string& source) { clear(); try { std::istringstream input (source); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); Compiler::FileParser parser (*this, mContext); scanner.scan (parser); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); } } void CSVWorld::ScriptErrorTable::clear() { setRowCount (0); } bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) { return mContext.clearLocals (script); } void CSVWorld::ScriptErrorTable::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Scripts/warnings") setWarningsMode (setting->toString()); } void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { if (item (row, 3)) { int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); emit highlightError (scriptLine-1, scriptColumn); } } openmw-openmw-0.48.0/apps/opencs/view/world/scripterrortable.hpp000066400000000000000000000032361445372753700250720ustar00rootroot00000000000000#ifndef CSV_WORLD_SCRIPTERRORTABLE_H #define CSV_WORLD_SCRIPTERRORTABLE_H #include #include #include #include "../../model/world/scriptcontext.hpp" #include "../../model/doc/messages.hpp" namespace CSMDoc { class Document; } namespace CSMPrefs { class Setting; } namespace CSVWorld { class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler { Q_OBJECT Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; ///< Report error to the user. void report (const std::string& message, Type type) override; ///< Report a file related error void addMessage (const std::string& message, CSMDoc::Message::Severity severity, int line = -1, int column = -1); void setWarningsMode (const std::string& value); public: ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = nullptr); void update (const std::string& source); void clear(); /// Clear local variable cache for \a script. /// /// \return Were there any locals that needed clearing? bool clearLocals (const std::string& script); private slots: void settingChanged (const CSMPrefs::Setting *setting); void cellClicked (int row, int column); signals: void highlightError (int line, int column); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/scripthighlighter.cpp000066400000000000000000000111511445372753700252150ustar00rootroot00000000000000#include "scripthighlighter.hpp" #include #include #include #include "../../model/prefs/setting.hpp" #include "../../model/prefs/category.hpp" bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { highlight (loc, Type_Int); return true; } bool CSVWorld::ScriptHighlighter::parseFloat (float value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { highlight (loc, Type_Float); return true; } bool CSVWorld::ScriptHighlighter::parseName (const std::string& name, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { highlight (loc, mContext.isId (name) ? Type_Id : Type_Name); return true; } bool CSVWorld::ScriptHighlighter::parseKeyword (int keyword, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { if (((mMode==Mode_Console || mMode==Mode_Dialogue) && (keyword==Compiler::Scanner::K_begin || keyword==Compiler::Scanner::K_end || keyword==Compiler::Scanner::K_short || keyword==Compiler::Scanner::K_long || keyword==Compiler::Scanner::K_float)) || (mMode==Mode_Console && (keyword==Compiler::Scanner::K_if || keyword==Compiler::Scanner::K_endif || keyword==Compiler::Scanner::K_else || keyword==Compiler::Scanner::K_elseif || keyword==Compiler::Scanner::K_while || keyword==Compiler::Scanner::K_endwhile))) return parseName (loc.mLiteral, loc, scanner); highlight (loc, Type_Keyword); return true; } bool CSVWorld::ScriptHighlighter::parseSpecial (int code, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { highlight (loc, Type_Special); return true; } bool CSVWorld::ScriptHighlighter::parseComment (const std::string& comment, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { highlight (loc, Type_Comment); return true; } void CSVWorld::ScriptHighlighter::parseEOF (Compiler::Scanner& scanner) {} void CSVWorld::ScriptHighlighter::highlight (const Compiler::TokenLoc& loc, Type type) { // We should take in account multibyte characters int length = 0; const char* token = loc.mLiteral.c_str(); while (*token) length += (*token++ & 0xc0) != 0x80; int index = loc.mColumn; // compensate for bug in Compiler::Scanner (position of token is the character after the token) index -= length; QTextCharFormat scheme = mScheme[type]; if (mMarkOccurrences && type == Type_Name && loc.mLiteral == mMarkedWord) scheme.merge(mScheme[Type_Highlight]); setFormat (index, length, scheme); } CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode mode, QTextDocument *parent) : QSyntaxHighlighter (parent) , Compiler::Parser (mErrorHandler, mContext) , mContext (data) , mMode (mode) , mMarkOccurrences (false) { QColor color ("black"); QTextCharFormat format; format.setForeground (color); for (int i=0; i<=Type_Id; ++i) mScheme.insert (std::make_pair (static_cast (i), format)); // configure compiler Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); } void CSVWorld::ScriptHighlighter::highlightBlock (const QString& text) { std::istringstream stream (text.toUtf8().constData()); Compiler::Scanner scanner (mErrorHandler, stream, mContext.getExtensions()); try { scanner.scan (*this); } catch (...) {} // ignore syntax errors } void CSVWorld::ScriptHighlighter::setMarkOccurrences(bool flag) { mMarkOccurrences = flag; } void CSVWorld::ScriptHighlighter::setMarkedWord(const std::string& name) { mMarkedWord = name; } void CSVWorld::ScriptHighlighter::invalidateIds() { mContext.invalidateIds(); } bool CSVWorld::ScriptHighlighter::settingChanged (const CSMPrefs::Setting *setting) { if (setting->getParent()->getKey()=="Scripts") { static const char *const colours[Type_Id+2] = { "colour-int", "colour-float", "colour-name", "colour-keyword", "colour-special", "colour-comment", "colour-highlight", "colour-id", 0 }; for (int i=0; colours[i]; ++i) if (setting->getKey()==colours[i]) { QTextCharFormat format; if (i == Type_Highlight) format.setBackground (setting->toColor()); else format.setForeground (setting->toColor()); mScheme[static_cast (i)] = format; return true; } } return false; } openmw-openmw-0.48.0/apps/opencs/view/world/scripthighlighter.hpp000066400000000000000000000061361445372753700252310ustar00rootroot00000000000000#ifndef CSV_WORLD_SCRIPTHIGHLIGHTER_H #define CSV_WORLD_SCRIPTHIGHLIGHTER_H #include #include #include #include #include #include #include "../../model/world/scriptcontext.hpp" namespace CSMPrefs { class Setting; } namespace CSVWorld { class ScriptHighlighter : public QSyntaxHighlighter, private Compiler::Parser { public: enum Type { Type_Int = 0, Type_Float = 1, Type_Name = 2, Type_Keyword = 3, Type_Special = 4, Type_Comment = 5, Type_Highlight = 6, Type_Id = 7 }; enum Mode { Mode_General, Mode_Console, Mode_Dialogue }; private: Compiler::NullErrorHandler mErrorHandler; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::map mScheme; Mode mMode; bool mMarkOccurrences; std::string mMarkedWord; private: bool parseInt (int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? bool parseComment (const std::string& comment, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; ///< Handle comment token. /// \return fetch another token? void parseEOF (Compiler::Scanner& scanner) override; ///< Handle EOF token. void highlight (const Compiler::TokenLoc& loc, Type type); public: ScriptHighlighter (const CSMWorld::Data& data, Mode mode, QTextDocument *parent); void highlightBlock (const QString& text) override; void setMarkOccurrences(bool); void setMarkedWord(const std::string& name); void invalidateIds(); bool settingChanged (const CSMPrefs::Setting *setting); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/scriptsubview.cpp000066400000000000000000000253121445372753700244070ustar00rootroot00000000000000#include "scriptsubview.hpp" #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/prefs/state.hpp" #include "scriptedit.hpp" #include "recordbuttonbar.hpp" #include "tablebottombox.hpp" #include "genericcreator.hpp" #include "scripterrortable.hpp" void CSVWorld::ScriptSubView::addButtonBar() { if (mButtons) return; mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); mLayout.insertWidget (1, mButtons); connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } void CSVWorld::ScriptSubView::recompile() { if (!mCompileDelay->isActive() && !isDeleted()) mCompileDelay->start (CSMPrefs::get()["Scripts"]["compile-delay"].toInt()); } bool CSVWorld::ScriptSubView::isDeleted() const { return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() ==CSMWorld::RecordBase::State_Deleted; } void CSVWorld::ScriptSubView::updateDeletedState() { if (isDeleted()) { mErrors->clear(); adjustSplitter(); mEditor->setEnabled (false); } else { mEditor->setEnabled (true); recompile(); } } void CSVWorld::ScriptSubView::adjustSplitter() { QList sizes; if (mErrors->rowCount()) { if (mErrors->height()) return; // keep old height if the error panel was already open sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; } else { if (mErrors->height()) mErrorHeight = mErrors->height(); sizes << 1 << 0; } mMain->setSizes (sizes); } CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(nullptr), mButtons (nullptr), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt()) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); mMain = new QSplitter (this); mMain->setOrientation (Qt::Vertical); mLayout.addWidget (mMain, 2); mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); mMain->addWidget (mEditor); mMain->setCollapsible (0, false); mErrors = new ScriptErrorTable (document, this); mMain->addWidget (mErrors); QList sizes; sizes << 1 << 0; mMain->setSizes (sizes); QWidget *widget = new QWidget (this); widget->setLayout (&mLayout); setWidget (widget); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); mEditor->setPlainText (source); // bottom box and buttons mBottom = new TableBottomBox (CreatorFactory(), document, id, this); connect (mBottom, SIGNAL (requestFocus (const std::string&)), this, SLOT (switchToId (const std::string&))); mLayout.addWidget (mBottom); // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&))); connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); mErrors->update (source.toUtf8().constData()); connect (mErrors, SIGNAL (highlightError (int, int)), this, SLOT (highlightError (int, int))); mCompileDelay = new QTimer (this); mCompileDelay->setSingleShot (true); connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); updateDeletedState(); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["Scripts"].update(); } void CSVWorld::ScriptSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Scripts/toolbar") { if (setting->isTrue()) { addButtonBar(); } else if (mButtons) { mLayout.removeWidget (mButtons); delete mButtons; mButtons = nullptr; } } else if (*setting=="Scripts/compile-delay") { mCompileDelay->setInterval (setting->toInt()); } else if (*setting=="Scripts/warnings") recompile(); } void CSVWorld::ScriptSubView::updateStatusBar () { mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, mEditor->textCursor().columnNumber() + 1); } void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); if (mButtons) mButtons->setEditLock (locked); mCommandDispatcher.setEditLock (locked); } void CSVWorld::ScriptSubView::useHint (const std::string& hint) { if (hint.empty()) return; unsigned line = 0, column = 0; char c; std::istringstream stream (hint.c_str()+1); switch(hint[0]) { case 'R': case 'r': { QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QString source = mModel->data (index).toString(); unsigned stringSize = source.length(); unsigned pos, dummy; if (!(stream >> c >> dummy >> pos) ) return; if (pos > stringSize) { Log(Debug::Warning) << "CSVWorld::ScriptSubView: requested position is higher than actual string length"; pos = stringSize; } for (unsigned i = 0; i <= pos; ++i) { if (source[i] == '\n') { ++line; column = i+1; } } column = pos - column; break; } case 'l': if (!(stream >> c >> line >> column)) return; } QTextCursor cursor = mEditor->textCursor(); cursor.movePosition (QTextCursor::Start); if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); mEditor->setFocus(); mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::textChanged() { if (mEditor->isChangeLocked()) return; ScriptEdit::ChangeLock lock (*mEditor); QString source = mEditor->toPlainText(); mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); recompile(); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mEditor->isChangeLocked()) return; ScriptEdit::ChangeLock lock (*mEditor); bool updateRequired = false; for (int i=topLeft.row(); i<=bottomRight.row(); ++i) { std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); if (mErrors->clearLocals (id)) updateRequired = true; } QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) { if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) updateDeletedState(); if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) { QString source = mModel->data (index).toString(); QTextCursor cursor = mEditor->textCursor(); mEditor->setPlainText (source); mEditor->setTextCursor (cursor); updateRequired = true; } } if (updateRequired) recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { bool updateRequired = false; for (int i=start; i<=end; ++i) { std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); if (mErrors->clearLocals (id)) updateRequired = true; } if (updateRequired) recompile(); QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) emit closeRequest(); } void CSVWorld::ScriptSubView::switchToRow (int row) { int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); bool oldSignalsState = mEditor->blockSignals( true ); mEditor->setPlainText( mModel->data(mModel->index(row, mColumn)).toString() ); mEditor->blockSignals( oldSignalsState ); std::vector selection (1, id); mCommandDispatcher.setSelection (selection); updateDeletedState(); } void CSVWorld::ScriptSubView::switchToId (const std::string& id) { switchToRow (mModel->getModelIndex (id, 0).row()); } void CSVWorld::ScriptSubView::highlightError (int line, int column) { QTextCursor cursor = mEditor->textCursor(); cursor.movePosition (QTextCursor::Start); if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); mEditor->setFocus(); mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::updateRequest() { QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QString source = mModel->data (index).toString(); mErrors->update (source.toUtf8().constData()); adjustSplitter(); } openmw-openmw-0.48.0/apps/opencs/view/world/scriptsubview.hpp000066400000000000000000000040761445372753700244200ustar00rootroot00000000000000#ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H #include #include "../../model/world/commanddispatcher.hpp" #include "../doc/subview.hpp" class QModelIndex; class QLabel; class QSplitter; class QTime; namespace CSMDoc { class Document; } namespace CSMWorld { class IdTable; } namespace CSMPrefs { class Setting; } namespace CSVWorld { class ScriptEdit; class RecordButtonBar; class TableBottomBox; class ScriptErrorTable; class ScriptSubView : public CSVDoc::SubView { Q_OBJECT ScriptEdit *mEditor; CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; int mIdColumn; int mStateColumn; TableBottomBox *mBottom; RecordButtonBar *mButtons; CSMWorld::CommandDispatcher mCommandDispatcher; QVBoxLayout mLayout; QSplitter *mMain; ScriptErrorTable *mErrors; QTimer *mCompileDelay; int mErrorHeight; private: void addButtonBar(); void recompile(); bool isDeleted() const; void updateDeletedState(); void adjustSplitter(); public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); void setEditLock (bool locked) override; void useHint (const std::string& hint) override; void setStatusBar (bool show) override; public slots: void textChanged(); void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); private slots: void settingChanged (const CSMPrefs::Setting *setting); void updateStatusBar(); void switchToRow (int row); void switchToId (const std::string& id); void highlightError (int line, int column); void updateRequest(); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/startscriptcreator.cpp000066400000000000000000000061441445372753700254420ustar00rootroot00000000000000#include "startscriptcreator.hpp" #include #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/idtable.hpp" #include "../widget/droplineedit.hpp" std::string CSVWorld::StartScriptCreator::getId() const { return mScript->text().toUtf8().constData(); } CSMWorld::IdTable& CSVWorld::StartScriptCreator::getStartScriptsTable() const { return dynamic_cast ( *getData().getTableModel(getCollectionId()) ); } CSVWorld::StartScriptCreator::StartScriptCreator( CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, CSMWorld::IdCompletionManager& completionManager ) : GenericCreator(data, undoStack, id) { setManualEditing(false); // Add script ID input label. QLabel *label = new QLabel("Script", this); insertBeforeButtons(label, false); // Add script ID input with auto-completion. // Only existing script IDs are accepted so no ID validation is performed. CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Script; mScript = new CSVWidget::DropLineEdit(displayType, this); mScript->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons(mScript, true); connect(mScript, SIGNAL (textChanged(const QString&)), this, SLOT (scriptChanged())); connect(mScript, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::StartScriptCreator::cloneMode( const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); // Look up cloned record in start scripts table and set script ID text. CSMWorld::IdTable& table = getStartScriptsTable(); int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); mScript->setText(table.data(table.getModelIndex(originId, column)).toString()); } std::string CSVWorld::StartScriptCreator::getErrors() const { std::string scriptId = getId(); // Check user input for any errors. std::string errors; if (scriptId.empty()) { errors = "No Script ID entered"; } else if (getData().getScripts().searchId(scriptId) == -1) { errors = "Script ID not found"; } else if (getData().getStartScripts().searchId(scriptId) > -1) { errors = "Script with this ID already registered as Start Script"; } return errors; } void CSVWorld::StartScriptCreator::focus() { mScript->setFocus(); } void CSVWorld::StartScriptCreator::reset() { CSVWorld::GenericCreator::reset(); mScript->setText(""); } void CSVWorld::StartScriptCreator::scriptChanged() { update(); } CSVWorld::Creator *CSVWorld::StartScriptCreatorFactory::makeCreator( CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new StartScriptCreator( document.getData(), document.getUndoStack(), id, document.getIdCompletionManager() ); } openmw-openmw-0.48.0/apps/opencs/view/world/startscriptcreator.hpp000066400000000000000000000037201445372753700254440ustar00rootroot00000000000000#ifndef STARTSCRIPTCREATOR_HPP #define STARTSCRIPTCREATOR_HPP #include "genericcreator.hpp" namespace CSMWorld { class IdCompletionManager; class IdTable; } namespace CSVWidget { class DropLineEdit; } namespace CSVWorld { /// \brief Record creator for start scripts. class StartScriptCreator : public GenericCreator { Q_OBJECT CSVWidget::DropLineEdit *mScript; private: /// \return script ID entered by user. std::string getId() const override; /// \return reference to table containing start scripts. CSMWorld::IdTable& getStartScriptsTable() const; public: StartScriptCreator( CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager); /// \brief Set script ID input widget to ID of record to be cloned. /// \param originId Script ID to be cloned. /// \param type Type of record to be cloned. void cloneMode( const std::string& originId, const CSMWorld::UniversalId::Type type) override; /// \return Error description for current user input. std::string getErrors() const override; /// \brief Set focus to script ID input widget. void focus() override; /// \brief Clear script ID input widget. void reset() override; private slots: /// \brief Check user input for any errors. void scriptChanged(); }; /// \brief Creator factory for start script record creator. class StartScriptCreatorFactory : public CreatorFactoryBase { public: Creator *makeCreator( CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; }; } #endif // STARTSCRIPTCREATOR_HPP openmw-openmw-0.48.0/apps/opencs/view/world/subviews.cpp000066400000000000000000000226771445372753700233600ustar00rootroot00000000000000#include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" #include "tablesubview.hpp" #include "dialoguesubview.hpp" #include "scriptsubview.hpp" #include "regionmapsubview.hpp" #include "genericcreator.hpp" #include "globalcreator.hpp" #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" #include "startscriptcreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" #include "pathgridcreator.hpp" #include "previewsubview.hpp" #include "bodypartcreator.hpp" #include "landcreator.hpp" #include "landtexturecreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { // Regular record tables (including references which are actually sub-records, but are promoted // to top-level records within the editor) manager.add (CSMWorld::UniversalId::Type_Gmsts, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Skills, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_MagicEffects, new CSVDoc::SubViewFactoryWithCreator); static const CSMWorld::UniversalId::Type sTableTypes[] = { CSMWorld::UniversalId::Type_Classes, CSMWorld::UniversalId::Type_Factions, CSMWorld::UniversalId::Type_Races, CSMWorld::UniversalId::Type_Sounds, CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, CSMWorld::UniversalId::Type_Enchantments, CSMWorld::UniversalId::Type_SoundGens, CSMWorld::UniversalId::Type_None // end marker }; for (int i=0; sTableTypes[i]!=CSMWorld::UniversalId::Type_None; ++i) manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_BodyParts, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_StartScripts, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_Referenceables, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_References, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Topics, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Journals, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_JournalInfos, new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Lands, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_LandTextures, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_Globals, new CSVDoc::SubViewFactoryWithCreator >); // Subviews for resources tables manager.add (CSMWorld::UniversalId::Type_Meshes, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Icons, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Musics, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_SoundsRes, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Textures, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Videos, new CSVDoc::SubViewFactoryWithCreator); // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); // Other stuff (combined record tables) manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); // More other stuff manager.add (CSMWorld::UniversalId::Type_Filters, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_DebugProfiles, new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_Scripts, new CSVDoc::SubViewFactoryWithCreator >); // Dialogue subviews static const CSMWorld::UniversalId::Type sTableTypes2[] = { CSMWorld::UniversalId::Type_Region, CSMWorld::UniversalId::Type_Spell, CSMWorld::UniversalId::Type_Birthsign, CSMWorld::UniversalId::Type_Global, CSMWorld::UniversalId::Type_Race, CSMWorld::UniversalId::Type_Class, CSMWorld::UniversalId::Type_Sound, CSMWorld::UniversalId::Type_Faction, CSMWorld::UniversalId::Type_Enchantment, CSMWorld::UniversalId::Type_SoundGen, CSMWorld::UniversalId::Type_None // end marker }; for (int i=0; sTableTypes2[i]!=CSMWorld::UniversalId::Type_None; ++i) manager.add (sTableTypes2[i], new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_BodyPart, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_StartScript, new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_MagicEffect, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Gmst, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Referenceable, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_Reference, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Cell, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_JournalInfo, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_TopicInfo, new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Topic, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Journal, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Pathgrid, new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Land, new CSVDoc::SubViewFactoryWithCreator >(false)); manager.add (CSMWorld::UniversalId::Type_LandTexture, new CSVDoc::SubViewFactoryWithCreator >(false)); manager.add (CSMWorld::UniversalId::Type_DebugProfile, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_Filter, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_MetaData, new CSVDoc::SubViewFactory); //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } openmw-openmw-0.48.0/apps/opencs/view/world/subviews.hpp000066400000000000000000000003301445372753700233430ustar00rootroot00000000000000#ifndef CSV_WORLD_SUBVIEWS_H #define CSV_WORLD_SUBVIEWS_H namespace CSVDoc { class SubViewFactoryManager; } namespace CSVWorld { void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); } #endif openmw-openmw-0.48.0/apps/opencs/view/world/table.cpp000066400000000000000000000756401445372753700225760ustar00rootroot00000000000000#include "table.hpp" #include #include #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/infotableproxymodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexturetableproxymodel.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" #include "tableeditidaction.hpp" #include "tableheadermouseeventhandler.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { // configure dispatcher mDispatcher->setSelection (getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); mDispatcher->setExtendedTypes (extendedTypes); // create context menu QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu (this); /// \todo add menu items for select all and clear selection int currentRow = rowAt(event->y()); int currentColumn = columnAt(event->x()); if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { mEditIdAction->setCell(currentRow, currentColumn); menu.addAction(mEditIdAction); menu.addSeparator(); } if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { if (selectedRows.size()==1) { menu.addAction (mEditAction); if (mCreateAction) menu.addAction(mCloneAction); } if (mTouchAction) menu.addAction (mTouchAction); if (mCreateAction) menu.addAction (mCreateAction); if (mDispatcher->canRevert()) { menu.addAction (mRevertAction); if (!extendedTypes.empty()) menu.addAction (mExtendedRevertAction); } if (mDispatcher->canDelete()) { menu.addAction (mDeleteAction); if (!extendedTypes.empty()) menu.addAction (mExtendedDeleteAction); } if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows if (selectedRows.size()==1) { int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); if (column==-1) column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); if (column!=-1) { int row = mProxyModel->mapToSource ( mProxyModel->index (selectedRows.begin()->row(), 0)).row(); QString curData = mModel->data(mModel->index(row, column)).toString(); if (row > 0) { QString prevData = mModel->data(mModel->index(row - 1, column)).toString(); if (Misc::StringUtils::ciEqual(curData.toStdString(), prevData.toStdString())) { menu.addAction(mMoveUpAction); } } if (row < mModel->rowCount() - 1) { QString nextData = mModel->data(mModel->index(row + 1, column)).toString(); if (Misc::StringUtils::ciEqual(curData.toStdString(), nextData.toStdString())) { menu.addAction(mMoveDownAction); } } } } } } if (selectedRows.size()==1) { int row = selectedRows.begin()->row(); row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View) { CSMWorld::UniversalId id = mModel->view (row).first; int index = mDocument.getData().getCells().searchId (id.getId()); // index==-1: the ID references a worldspace instead of a cell (ignore for now and go // ahead) if (index==-1 || !mDocument.getData().getCells().getRecord (index).isDeleted()) menu.addAction (mViewAction); } if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Preview) { const CSMWorld::UniversalId id = getUniversalId(currentRow); const CSMWorld::UniversalId::Type type = id.getType(); QModelIndex index = mModel->index (row, mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); CSMWorld::RecordBase::State state = static_cast ( mModel->data (index).toInt()); if (state!=CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList) menu.addAction (mPreviewAction); } } if (mHelpAction) menu.addAction (mHelpAction); menu.exec (event->globalPos()); } void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) { Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); QModelIndex index = currentIndex(); selectionModel()->select (index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); std::map::iterator iter = mDoubleClickActions.find (modifiers); if (iter==mDoubleClickActions.end()) { event->accept(); return; } switch (iter->second) { case Action_None: event->accept(); break; case Action_InPlaceEdit: DragRecordTable::mouseDoubleClickEvent (event); break; case Action_EditRecord: event->accept(); editRecord(); break; case Action_View: event->accept(); viewRecord(); break; case Action_Revert: event->accept(); if (mDispatcher->canRevert()) mDispatcher->executeRevert(); break; case Action_Delete: event->accept(); if (mDispatcher->canDelete()) mDispatcher->executeDelete(); break; case Action_EditRecordAndClose: event->accept(); editRecord(); emit closeRequest(); break; case Action_ViewAndClose: event->accept(); viewRecord(); emit closeRequest(); break; } } CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : DragRecordTable(document), mCreateAction (nullptr), mCloneAction(nullptr), mTouchAction(nullptr), mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false), mAutoJump (false) { mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || id.getType() == CSMWorld::UniversalId::Type_JournalInfos; bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures); if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); } else if (isLtexTable) { mProxyModel = new CSMWorld::LandTextureTableProxyModel (this); } else { mProxyModel = new CSMWorld::IdTableProxyModel (this); } mProxyModel->setSourceModel (mModel); mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); setModel (mProxyModel); horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); int columns = mModel->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); if (flags & CSMWorld::ColumnBase::Flag_Table) { CSMWorld::ColumnBase::Display display = static_cast ( mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, mDispatcher, document, this); mDelegates.push_back (delegate); setItemDelegateForColumn (i, delegate); } else hideColumn (i); } if (sorting) { // FIXME: some tables (e.g. CellRef) have this column hidden, which makes it confusing sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); } setSortingEnabled (sorting); mEditAction = new QAction (tr ("Edit Record"), this); connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); mEditAction->setIcon(QIcon(":edit-edit")); addAction (mEditAction); CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); editShortcut->associateAction(mEditAction); if (createAndDelete) { mCreateAction = new QAction (tr ("Add Record"), this); connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); mCreateAction->setIcon(QIcon(":edit-add")); addAction (mCreateAction); CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); createShortcut->associateAction(mCreateAction); mCloneAction = new QAction (tr ("Clone Record"), this); connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); mCloneAction->setIcon(QIcon(":edit-clone")); addAction(mCloneAction); CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); cloneShortcut->associateAction(mCloneAction); } if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) { mTouchAction = new QAction(tr("Touch Record"), this); connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord())); mTouchAction->setIcon(QIcon(":edit-touch")); addAction(mTouchAction); CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); touchShortcut->associateAction(mTouchAction); } mRevertAction = new QAction (tr ("Revert Record"), this); connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); mRevertAction->setIcon(QIcon(":edit-undo")); addAction (mRevertAction); CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); revertShortcut->associateAction(mRevertAction); mDeleteAction = new QAction (tr ("Delete Record"), this); connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); mDeleteAction->setIcon(QIcon(":edit-delete")); addAction (mDeleteAction); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); deleteShortcut->associateAction(mDeleteAction); mMoveUpAction = new QAction (tr ("Move Up"), this); connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); mMoveUpAction->setIcon(QIcon(":record-up")); addAction (mMoveUpAction); CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); moveUpShortcut->associateAction(mMoveUpAction); mMoveDownAction = new QAction (tr ("Move Down"), this); connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); mMoveDownAction->setIcon(QIcon(":record-down")); addAction (mMoveDownAction); CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); moveDownShortcut->associateAction(mMoveDownAction); mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); mViewAction->setIcon(QIcon(":/cell.png")); addAction (mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); mPreviewAction = new QAction (tr ("Preview"), this); connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); mPreviewAction->setIcon(QIcon(":edit-preview")); addAction (mPreviewAction); CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); previewShortcut->associateAction(mPreviewAction); mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); mExtendedDeleteAction->setIcon(QIcon(":edit-delete")); addAction (mExtendedDeleteAction); CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); extendedDeleteShortcut->associateAction(mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); mExtendedRevertAction->setIcon(QIcon(":edit-undo")); addAction (mExtendedRevertAction); CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); extendedRevertShortcut->associateAction(mExtendedRevertAction); mEditIdAction = new TableEditIdAction (*this, this); connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); addAction (mEditIdAction); mHelpAction = new QAction (tr ("Help"), this); connect (mHelpAction, SIGNAL (triggered()), this, SLOT (openHelp())); mHelpAction->setIcon(QIcon(":/info.png")); addAction (mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (dataChangedEvent(const QModelIndex&, const QModelIndex&))); connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT (selectionSizeUpdate ())); setAcceptDrops(true); mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_InPlaceEdit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["ID Tables"].update(); new TableHeaderMouseEventHandler(this); } void CSVWorld::Table::setEditLock (bool locked) { for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) (*iter)->setEditLock (locked); DragRecordTable::setEditLock(locked); mDispatcher->setEditLock (locked); } CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const { row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); int typeColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); return CSMWorld::UniversalId ( static_cast (mModel->data (mModel->index (row, typeColumn)).toInt()), mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } std::vector CSVWorld::Table::getSelectedIds() const { std::vector ids; QModelIndexList selectedRows = selectionModel()->selectedRows(); int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter != selectedRows.end(); ++iter) { int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); ids.emplace_back(mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); } return ids; } void CSVWorld::Table::editRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) emit editRequest (getUniversalId (selectedRows.begin()->row()), ""); } } void CSVWorld::Table::cloneRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { QModelIndexList selectedRows = selectionModel()->selectedRows(); const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); if (selectedRows.size() == 1) { emit cloneRequest (toClone); } } } void CSVWorld::Table::touchRecord() { if (!mEditLock && mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) { std::vector touchIds; QModelIndexList selectedRows = selectionModel()->selectedRows(); for (auto it = selectedRows.begin(); it != selectedRows.end(); ++it) { touchIds.push_back(getUniversalId(it->row())); } emit touchRequest(touchIds); } } void CSVWorld::Table::moveUpRecord() { if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return; QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) { int row2 =selectedRows.begin()->row(); if (row2>0) { int row = row2-1; row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); if (row2<=row) throw std::runtime_error ("Inconsistent row order"); std::vector newOrder (row2-row+1); newOrder[0] = row2-row; newOrder[row2-row] = 0; for (int i=1; i (*mModel), row, newOrder)); } } } void CSVWorld::Table::moveDownRecord() { if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return; QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) { int row =selectedRows.begin()->row(); if (rowrowCount()-1) { int row2 = row+1; row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); if (row2<=row) throw std::runtime_error ("Inconsistent row order"); std::vector newOrder (row2-row+1); newOrder[0] = row2-row; newOrder[row2-row] = 0; for (int i=1; i (*mModel), row, newOrder)); } } } void CSVWorld::Table::moveRecords(QDropEvent *event) { if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return; QModelIndex targedIndex = indexAt(event->pos()); QModelIndexList selectedRows = selectionModel()->selectedRows(); int targetRowRaw = targedIndex.row(); int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row(); int baseRowRaw = targedIndex.row() - 1; int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row(); int highestDifference = 0; for (const auto& thisRowData : selectedRows) { int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row(); if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); if (thisRow - 1 < baseRow) baseRow = thisRow - 1; } std::vector newOrder (highestDifference + 1); for (long unsigned int i = 0; i < newOrder.size(); ++i) { newOrder[i] = i; } if (selectedRows.size() > 1) { Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented."; return; } for (const auto& thisRowData : selectedRows) { /* Moving algorithm description a) Remove the (ORIGIN + 1)th list member. b) Add (ORIGIN+1)th list member with value TARGET c) If ORIGIN > TARGET,d_INC; ELSE d_DEC d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET */ int originRowRaw = thisRowData.row(); int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row(); newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); if (originRow > targetRow) { for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i) { ++newOrder[i]; } } else { for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i) { --newOrder[i]; } } } mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( dynamic_cast (*mModel), baseRow + 1, newOrder)); } void CSVWorld::Table::editCell() { emit editRequest(mEditIdAction->getCurrentId(), ""); } void CSVWorld::Table::openHelp() { Misc::HelpViewer::openHelp("manuals/openmw-cs/tables.html"); } void CSVWorld::Table::viewRecord() { if (!(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View)) return; QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) { int row = selectedRows.begin()->row(); row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); std::pair params = mModel->view (row); if (params.first.getType()!=CSMWorld::UniversalId::Type_None) emit editRequest (params.first, params.second); } } void CSVWorld::Table::previewRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) { std::string id = getUniversalId (selectedRows.begin()->row()).getId(); QModelIndex index = mModel->getModelIndex (id, mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); if (mModel->data (index)!=CSMWorld::RecordBase::State_Deleted) emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id), ""); } } void CSVWorld::Table::executeExtendedDelete() { if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedDeleteConfigRequest(getSelectedIds()); } else { QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); } } void CSVWorld::Table::executeExtendedRevert() { if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { emit extendedRevertConfigRequest(getSelectedIds()); } else { QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); } } void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="ID Tables/jump-to-added") { if (setting->toString()=="Jump and Select") { mJumpToAddedRecord = true; mUnselectAfterJump = false; } else if (setting->toString()=="Jump Only") { mJumpToAddedRecord = true; mUnselectAfterJump = true; } else // No Jump { mJumpToAddedRecord = false; mUnselectAfterJump = false; } } else if (*setting=="Records/type-format" || *setting=="Records/status-format") { int columns = mModel->columnCount(); for (int i=0; i (*delegate).settingChanged (setting); emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); } } else if (setting->getParent()->getKey()=="ID Tables" && setting->getKey().substr (0, 6)=="double") { std::string modifierString = setting->getKey().substr (6); Qt::KeyboardModifiers modifiers; if (modifierString=="-s") modifiers = Qt::ShiftModifier; else if (modifierString=="-c") modifiers = Qt::ControlModifier; else if (modifierString=="-sc") modifiers = Qt::ShiftModifier | Qt::ControlModifier; DoubleClickAction action = Action_None; std::string value = setting->toString(); if (value=="Edit in Place") action = Action_InPlaceEdit; else if (value=="Edit Record") action = Action_EditRecord; else if (value=="View") action = Action_View; else if (value=="Revert") action = Action_Revert; else if (value=="Delete") action = Action_Delete; else if (value=="Edit Record and Close") action = Action_EditRecordAndClose; else if (value=="View and Close") action = Action_ViewAndClose; mDoubleClickActions[modifiers] = action; } } void CSVWorld::Table::tableSizeUpdate() { int size = 0; int deleted = 0; int modified = 0; if (mProxyModel->columnCount()>0) { int rows = mProxyModel->rowCount(); int columnIndex = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Modification); if (columnIndex!=-1) { for (int i=0; imapToSource (mProxyModel->index (i, 0)); int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); switch (state) { case CSMWorld::RecordBase::State_BaseOnly: ++size; break; case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; case CSMWorld::RecordBase::State_Deleted: ++deleted; ++modified; break; } } } else size = rows; } emit tableSizeChanged (size, deleted, modified); } void CSVWorld::Table::selectionSizeUpdate() { emit selectionSizeChanged (selectionModel()->selectedRows().size()); } void CSVWorld::Table::requestFocus (const std::string& id) { QModelIndex index = mProxyModel->getModelIndex (id, 0); if (index.isValid()) { // This will scroll to the row. selectRow (index.row()); // This will actually select it. selectionModel()->select (index, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } void CSVWorld::Table::recordFilterChanged (std::shared_ptr filter) { mProxyModel->setFilter (filter); tableSizeUpdate(); selectionSizeUpdate(); } void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { startDragFromTable(*this); } } std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const { const int count = mModel->columnCount(); std::vector titles; for (int i = 0; i < count; ++i) { CSMWorld::ColumnBase::Display columndisplay = static_cast (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); if (display == columndisplay) { titles.emplace_back(mModel->headerData (i, Qt::Horizontal).toString().toUtf8().constData()); } } return titles; } std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const { QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector idToDrag; for (QModelIndex& it : selectedRows) idToDrag.push_back (getUniversalId (it.row())); return idToDrag; } // parent, start and end depend on the model sending the signal, in this case mProxyModel // // If, for example, mModel was used instead, then scrolTo() should use the index // mProxyModel->mapFromSource(mModel->index(end, 0)) void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); int end = mProxyModel->getModelIndex(id, idColumn).row(); selectRow(end); // without this delay the scroll works but goes to top for add/clone QMetaObject::invokeMethod(this, "queuedScrollTo", Qt::QueuedConnection, Q_ARG(int, end)); if(mUnselectAfterJump) clearSelection(); } } void CSVWorld::Table::queuedScrollTo(int row) { scrollTo(mProxyModel->index(row, 0), QAbstractItemView::PositionAtCenter); } void CSVWorld::Table::dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight) { tableSizeUpdate(); if (mAutoJump) { selectRow(bottomRight.row()); scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); } } void CSVWorld::Table::jumpAfterModChanged(int state) { if(state == Qt::Checked) mAutoJump = true; else mAutoJump = false; } openmw-openmw-0.48.0/apps/opencs/view/world/table.hpp000066400000000000000000000111261445372753700225700ustar00rootroot00000000000000#ifndef CSV_WORLD_TABLE_H #define CSV_WORLD_TABLE_H #include #include #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" #include "../../model/world/universalid.hpp" #include "dragrecordtable.hpp" class QAction; namespace CSMDoc { class Document; } namespace CSMWorld { class IdTableProxyModel; class IdTableBase; class CommandDispatcher; } namespace CSMPrefs { class Setting; } namespace CSVWorld { class CommandDelegate; class TableEditIdAction; ///< Table widget class Table : public DragRecordTable { Q_OBJECT enum DoubleClickAction { Action_None, Action_InPlaceEdit, Action_EditRecord, Action_View, Action_Revert, Action_Delete, Action_EditRecordAndClose, Action_ViewAndClose }; std::vector mDelegates; QAction *mEditAction; QAction *mCreateAction; QAction *mCloneAction; QAction *mTouchAction; QAction *mRevertAction; QAction *mDeleteAction; QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; QAction *mHelpAction; TableEditIdAction *mEditIdAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTableBase *mModel; int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; bool mAutoJump; private: void contextMenuEvent (QContextMenuEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; protected: void mouseDoubleClickEvent (QMouseEvent *event) override; public: Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document); ///< \param createAndDelete Allow creation and deletion of records. /// \param sorting Allow changing order of rows in the view via column headers. virtual void setEditLock (bool locked); CSMWorld::UniversalId getUniversalId (int row) const; std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; std::vector getSelectedIds() const; std::vector getDraggedRecords() const override; signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void selectionSizeChanged (int size); void tableSizeChanged (int size, int deleted, int modified); ///< \param size Number of not deleted records /// \param deleted Number of deleted records /// \param modified Number of added and modified records void createRequest(); void cloneRequest(const CSMWorld::UniversalId&); void touchRequest(const std::vector& ids); void closeRequest(); void extendedDeleteConfigRequest(const std::vector &selectedIds); void extendedRevertConfigRequest(const std::vector &selectedIds); private slots: void editCell(); static void openHelp(); void editRecord(); void cloneRecord(); void touchRecord(); void moveUpRecord(); void moveDownRecord(); void moveRecords(QDropEvent *event); void viewRecord(); void previewRecord(); void executeExtendedDelete(); void executeExtendedRevert(); public slots: void settingChanged (const CSMPrefs::Setting *setting); void tableSizeUpdate(); void selectionSizeUpdate(); void requestFocus (const std::string& id); void recordFilterChanged (std::shared_ptr filter); void rowAdded(const std::string &id); void dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight); void jumpAfterModChanged(int state); void queuedScrollTo(int state); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/tablebottombox.cpp000066400000000000000000000156551445372753700245340ustar00rootroot00000000000000#include "tablebottombox.hpp" #include #include #include #include #include #include #include "creator.hpp" void CSVWorld::TableBottomBox::updateSize() { // Make sure that the size of the bottom box is determined by the currently visible widget for (int i = 0; i < mLayout->count(); ++i) { QSizePolicy::Policy verPolicy = QSizePolicy::Ignored; if (mLayout->widget(i) == mLayout->currentWidget()) { verPolicy = QSizePolicy::Expanding; } mLayout->widget(i)->setSizePolicy(QSizePolicy::Expanding, verPolicy); } } void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) { if (!mStatusMessage.isEmpty()) { mStatus->setText (mStatusMessage); return; } static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; std::ostringstream stream; bool first = true; for (int i=0; i<4; ++i) { if (mStatusCount[i]>0) { if (first) first = false; else stream << ", "; stream << mStatusCount[i] << ' ' << (mStatusCount[i]==1 ? sLabels[i] : sLabelsPlural[i]); } } if (mHasPosition) { if (!first) stream << " -- "; stream << "(" << mRow << ", " << mColumn << ")"; } mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } } void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, const std::vector &selectedIds) { mExtendedConfigurator->configure (mode, selectedIds); mLayout->setCurrentWidget (mExtendedConfigurator); mEditMode = EditMode_ExtendedConfig; setVisible (true); mExtendedConfigurator->setFocus(); } CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) : QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; setVisible (false); mLayout = new QStackedLayout; mLayout->setContentsMargins (0, 0, 0, 0); connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); mStatus = new QLabel; mStatusBar = new QStatusBar(this); mStatusBar->addWidget (mStatus); mLayout->addWidget (mStatusBar); setLayout (mLayout); mCreator = creatorFactory.makeCreator (document, id); if (mCreator) { mCreator->installEventFilter(this); mLayout->addWidget (mCreator); connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); mExtendedConfigurator->installEventFilter(this); mLayout->addWidget (mExtendedConfigurator); connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); updateSize(); } void CSVWorld::TableBottomBox::setEditLock (bool locked) { if (mCreator) mCreator->setEditLock (locked); mExtendedConfigurator->setEditLock (locked); } CSVWorld::TableBottomBox::~TableBottomBox() { delete mCreator; } bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { requestDone(); return true; } } return QWidget::eventFilter(object, event); } void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) { setVisible (show || (mEditMode != EditMode_None)); mShowStatusBar = show; if (show) updateStatus(); } } bool CSVWorld::TableBottomBox::canCreateAndDelete() const { return mCreator; } void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) setVisible (false); else updateStatus(); mLayout->setCurrentWidget (mStatusBar); mEditMode = EditMode_None; } void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) { updateSize(); } void CSVWorld::TableBottomBox::setStatusMessage (const QString& message) { mStatusMessage = message; updateStatus(); } void CSVWorld::TableBottomBox::selectionSizeChanged (int size) { if (mStatusCount[3]!=size) { mStatusMessage = ""; mStatusCount[3] = size; updateStatus(); } } void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modified) { bool changed = false; if (mStatusCount[0]!=size) { mStatusCount[0] = size; changed = true; } if (mStatusCount[1]!=deleted) { mStatusCount[1] = deleted; changed = true; } if (mStatusCount[2]!=modified) { mStatusCount[2] = modified; changed = true; } if (changed) { mStatusMessage = ""; updateStatus(); } } void CSVWorld::TableBottomBox::positionChanged (int row, int column) { mRow = row; mColumn = column; mHasPosition = true; updateStatus(); } void CSVWorld::TableBottomBox::noMorePosition() { mHasPosition = false; updateStatus(); } void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); mEditMode = EditMode_Creation; mCreator->focus(); } void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type) { mCreator->reset(); mCreator->cloneMode(id, type); mLayout->setCurrentWidget(mCreator); mCreator->toggleWidgets(false); setVisible (true); mEditMode = EditMode_Creation; mCreator->focus(); } void CSVWorld::TableBottomBox::touchRequest(const std::vector& ids) { mCreator->touch(ids); } void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) { extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); } void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) { extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); } openmw-openmw-0.48.0/apps/opencs/view/world/tablebottombox.hpp000066400000000000000000000063131445372753700245300ustar00rootroot00000000000000#ifndef CSV_WORLD_BOTTOMBOX_H #define CSV_WORLD_BOTTOMBOX_H #include #include #include "extendedcommandconfigurator.hpp" class QLabel; class QStackedLayout; class QStatusBar; namespace CSMDoc { class Document; } namespace CSVWorld { class CreatorFactoryBase; class Creator; class TableBottomBox : public QWidget { Q_OBJECT enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; bool mShowStatusBar; QLabel *mStatus; QStatusBar *mStatusBar; int mStatusCount[4]; EditMode mEditMode; Creator *mCreator; ExtendedCommandConfigurator *mExtendedConfigurator; QStackedLayout *mLayout; bool mHasPosition; int mRow; int mColumn; QString mStatusMessage; private: // not implemented TableBottomBox (const TableBottomBox&); TableBottomBox& operator= (const TableBottomBox&); void updateSize(); void updateStatus(); void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, const std::vector &selectedIds); public: TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent = nullptr); ~TableBottomBox() override; bool eventFilter(QObject *object, QEvent *event) override; void setEditLock (bool locked); void setStatusBar (bool show); bool canCreateAndDelete() const; ///< Is record creation and deletion supported? /// /// \note The BotomBox does not partake in the deletion of records. void setStatusMessage (const QString& message); signals: void requestFocus (const std::string& id); ///< Request owner of this box to focus the just created \a id. The owner may /// ignore this request. private slots: void requestDone(); ///< \note This slot being called does not imply success. void currentWidgetChanged(int index); public slots: void selectionSizeChanged (int size); void tableSizeChanged (int size, int deleted, int modified); ///< \param size Number of not deleted records /// \param deleted Number of deleted records /// \param modified Number of added and modified records void positionChanged (int row, int column); void noMorePosition(); void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); void touchRequest(const std::vector&); void extendedDeleteConfigRequest(const std::vector &selectedIds); void extendedRevertConfigRequest(const std::vector &selectedIds); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/tableeditidaction.cpp000066400000000000000000000032321445372753700251430ustar00rootroot00000000000000#include "tableeditidaction.hpp" #include #include "../../model/world/tablemimedata.hpp" CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const { QModelIndex index = mTable.model()->index(row, column); if (index.isValid()) { QVariant display = mTable.model()->data(index, CSMWorld::ColumnBase::Role_Display); QString value = mTable.model()->data(index).toString(); return std::make_pair(static_cast(display.toInt()), value); } return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); } CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) : QAction(parent), mTable(table), mCurrentId(CSMWorld::UniversalId::Type_None) {} void CSVWorld::TableEditIdAction::setCell(int row, int column) { CellData data = getCellData(row, column); CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); if (idType != CSMWorld::UniversalId::Type_None) { mCurrentId = CSMWorld::UniversalId(idType, data.second.toUtf8().constData()); setText("Edit '" + data.second + "'"); } } CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const { return mCurrentId; } bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const { CellData data = getCellData(row, column); CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); return CSMWorld::ColumnBase::isId(data.first) && idType != CSMWorld::UniversalId::Type_None && !data.second.isEmpty(); } openmw-openmw-0.48.0/apps/opencs/view/world/tableeditidaction.hpp000066400000000000000000000014231445372753700251500ustar00rootroot00000000000000#ifndef CSVWORLD_TABLEEDITIDACTION_HPP #define CSVWORLD_TABLEEDITIDACTION_HPP #include #include "../../model/world/columnbase.hpp" #include "../../model/world/universalid.hpp" class QTableView; namespace CSVWorld { class TableEditIdAction : public QAction { const QTableView &mTable; CSMWorld::UniversalId mCurrentId; typedef std::pair CellData; CellData getCellData(int row, int column) const; public: TableEditIdAction(const QTableView &table, QWidget *parent = nullptr); void setCell(int row, int column); CSMWorld::UniversalId getCurrentId() const; bool isValidIdCell(int row, int column) const; }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/tableheadermouseeventhandler.cpp000066400000000000000000000036731445372753700274150ustar00rootroot00000000000000#include "tableheadermouseeventhandler.hpp" #include "dragrecordtable.hpp" #include #include #include namespace CSVWorld { TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent) : QWidget(parent) , table(*parent) , header(*table.horizontalHeader()) { header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); connect( &header, &QHeaderView::customContextMenuRequested, [this](const QPoint & position) { showContextMenu(position); }); header.viewport()->installEventFilter(this); } bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event) { if (event->type() == QEvent::Type::MouseButtonPress) { auto & clickEvent = static_cast(*event); if ((clickEvent.button() == Qt::MiddleButton)) { const auto & index = table.indexAt(clickEvent.pos()); table.setColumnHidden(index.column(), true); clickEvent.accept(); return true; } } return false; } void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position) { auto & menu{createContextMenu()}; menu.popup(header.viewport()->mapToGlobal(position)); } QMenu & TableHeaderMouseEventHandler::createContextMenu() { auto * menu = new QMenu(this); for (int i = 0; i < table.model()->columnCount(); ++i) { const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole); QAction * action{new QAction(name.toString(), this)}; action->setCheckable(true); action->setChecked(!table.isColumnHidden(i)); menu->addAction(action); connect(action, &QAction::triggered, [this, action, i]() { table.setColumnHidden(i, !action->isChecked()); action->setChecked(!action->isChecked()); action->toggle(); }); } return *menu; } } // namespace CSVWorld openmw-openmw-0.48.0/apps/opencs/view/world/tableheadermouseeventhandler.hpp000066400000000000000000000007541445372753700274170ustar00rootroot00000000000000#pragma once #include #include namespace CSVWorld { class DragRecordTable; class TableHeaderMouseEventHandler : public QWidget { public: explicit TableHeaderMouseEventHandler(DragRecordTable * parent); void showContextMenu(const QPoint &); private: DragRecordTable & table; QHeaderView & header; QMenu & createContextMenu(); bool eventFilter(QObject *, QEvent *) override; }; // class TableHeaderMouseEventHandler } // namespace CSVWorld openmw-openmw-0.48.0/apps/opencs/view/world/tablesubview.cpp000066400000000000000000000176121445372753700241760ustar00rootroot00000000000000#include "tablesubview.hpp" #include #include #include #include #include #include #include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "../doc/sizehint.hpp" #include "../filter/filterbox.hpp" #include "table.hpp" #include "tablebottombox.hpp" #include "creator.hpp" CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : SubView (id), mShowOptions(false), mOptions(0) { QVBoxLayout *layout = new QVBoxLayout; layout->addWidget (mBottom = new TableBottomBox (creatorFactory, document, id, this), 0); layout->insertWidget (0, mTable = new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); mFilterBox = new CSVFilter::FilterBox (document.getData(), this); QHBoxLayout *hLayout = new QHBoxLayout; hLayout->insertWidget(0,mFilterBox); mOptions = new QWidget; QHBoxLayout *optHLayout = new QHBoxLayout; QCheckBox *autoJump = new QCheckBox("Auto Jump"); autoJump->setToolTip ("Whether to jump to the modified record." "\nCan be useful in finding the moved or modified" "\nobject instance while 3D editing."); autoJump->setCheckState(Qt::Unchecked); connect(autoJump, SIGNAL (stateChanged(int)), mTable, SLOT (jumpAfterModChanged(int))); optHLayout->insertWidget(0, autoJump); optHLayout->setContentsMargins (QMargins (0, 3, 0, 0)); mOptions->setLayout(optHLayout); mOptions->resize(mOptions->width(), mFilterBox->height()); mOptions->hide(); QPushButton *opt = new QPushButton (); opt->setIcon (QIcon (":startup/configure")); opt->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); opt->setToolTip ("Open additional options for this subview."); connect (opt, SIGNAL (clicked()), this, SLOT (toggleOptions())); QVBoxLayout *buttonLayout = new QVBoxLayout; // work around margin issues buttonLayout->setContentsMargins (QMargins (0/*left*/, 3/*top*/, 3/*right*/, 0/*bottom*/)); buttonLayout->insertWidget(0, opt, 0, Qt::AlignVCenter|Qt::AlignRight); hLayout->insertWidget(1, mOptions); hLayout->insertLayout(2, buttonLayout); layout->insertLayout (0, hLayout); CSVDoc::SizeHintWidget *widget = new CSVDoc::SizeHintWidget; widget->setLayout (layout); setWidget (widget); // prefer height of the screen and full width of the table const QRect rect = QApplication::desktop()->screenGeometry(this); int frameHeight = 40; // set a reasonable default QWidget *topLevel = QApplication::topLevelAt(pos()); if (topLevel) frameHeight = topLevel->frameGeometry().height() - topLevel->height(); widget->setSizeHint(QSize(mTable->horizontalHeader()->length(), rect.height()-frameHeight)); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (selectionSizeChanged (int)), mBottom, SLOT (selectionSizeChanged (int))); connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), mBottom, SLOT (tableSizeChanged (int, int, int))); mTable->tableSizeUpdate(); mTable->selectionSizeUpdate(); mTable->viewport()->installEventFilter(this); mBottom->installEventFilter(this); mFilterBox->installEventFilter(this); if (mBottom->canCreateAndDelete()) { connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, SLOT(cloneRequest(const CSMWorld::UniversalId&))); connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); connect (mTable, SIGNAL(touchRequest(const std::vector&)), mBottom, SLOT(touchRequest(const std::vector&))); connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); connect (mFilterBox, SIGNAL (recordFilterChanged (std::shared_ptr)), mTable, SLOT (recordFilterChanged (std::shared_ptr))); connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); connect (mTable, SIGNAL (closeRequest()), this, SLOT (closeRequest())); } void CSVWorld::TableSubView::setEditLock (bool locked) { mTable->setEditLock (locked); mBottom->setEditLock (locked); } void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const std::string& hint) { focusId (id, hint); } void CSVWorld::TableSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } void CSVWorld::TableSubView::useHint (const std::string& hint) { if (hint.empty()) return; if (hint[0]=='f' && hint.size()>=2) mFilterBox->setRecordFilter (hint.substr (2)); } void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) { emit cloneRequest(toClone.getId(), toClone.getType()); } void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) { std::vector > > filterSource; std::vector refIdColumns = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(CSMWorld::UniversalId::Type_Referenceable)); bool hasRefIdDisplay = !refIdColumns.empty(); for (std::vector::iterator it(types.begin()); it != types.end(); ++it) { CSMWorld::UniversalId::Type type = it->getType(); std::vector col = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(type)); if(!col.empty()) { filterSource.emplace_back(it->getId(), col); } if(hasRefIdDisplay && CSMWorld::TableMimeData::isReferencable(type)) { filterSource.emplace_back(it->getId(), refIdColumns); } } mFilterBox->createFilterRequest(filterSource, action); } bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { if (event->type() == QEvent::Drop) { if (QDropEvent* drop = dynamic_cast(event)) { const CSMWorld::TableMimeData* tableMimeData = dynamic_cast(drop->mimeData()); if (!tableMimeData) // May happen when non-records (e.g. plain text) are dragged and dropped return false; bool handled = tableMimeData->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { mFilterBox->setRecordFilter(tableMimeData->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); } return handled; } } return false; } void CSVWorld::TableSubView::toggleOptions() { if (mShowOptions) { mShowOptions = false; mOptions->hide(); } else { mShowOptions = true; mOptions->show(); } } void CSVWorld::TableSubView::requestFocus (const std::string& id) { mTable->requestFocus(id); } openmw-openmw-0.48.0/apps/opencs/view/world/tablesubview.hpp000066400000000000000000000031451445372753700241770ustar00rootroot00000000000000#ifndef CSV_WORLD_TABLESUBVIEW_H #define CSV_WORLD_TABLESUBVIEW_H #include "../doc/subview.hpp" class QModelIndex; class QWidget; namespace CSMWorld { class IdTable; } namespace CSMDoc { class Document; } namespace CSVFilter { class FilterBox; } namespace CSVWorld { class Table; class TableBottomBox; class CreatorFactoryBase; class TableSubView : public CSVDoc::SubView { Q_OBJECT Table *mTable; TableBottomBox *mBottom; CSVFilter::FilterBox *mFilterBox; bool mShowOptions; QWidget *mOptions; public: TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting); void setEditLock (bool locked) override; void setStatusBar (bool show) override; void useHint (const std::string& hint) override; protected: bool eventFilter(QObject* object, QEvent *event) override; signals: void cloneRequest(const std::string&, const CSMWorld::UniversalId::Type); private slots: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void cloneRequest (const CSMWorld::UniversalId& toClone); void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, Qt::DropAction action); void toggleOptions (); public slots: void requestFocus (const std::string& id); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/util.cpp000066400000000000000000000270441445372753700224570ustar00rootroot00000000000000#include "util.hpp" #include #include #include #include #include #include #include #include #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" #include "dialoguespinbox.hpp" #include "scriptedit.hpp" CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) : mModel (model) {} int CSVWorld::NastyTableModelHack::rowCount (const QModelIndex & parent) const { return mModel.rowCount (parent); } int CSVWorld::NastyTableModelHack::columnCount (const QModelIndex & parent) const { return mModel.columnCount (parent); } QVariant CSVWorld::NastyTableModelHack::data (const QModelIndex & index, int role) const { return mModel.data (index, role); } bool CSVWorld::NastyTableModelHack::setData ( const QModelIndex &index, const QVariant &value, int role) { mData = value; return true; } QVariant CSVWorld::NastyTableModelHack::getData() const { return mData; } CSVWorld::CommandDelegateFactory::~CommandDelegateFactory() {} CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = nullptr; CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection() { if (sThis) throw std::logic_error ("multiple instances of CSVWorld::CommandDelegateFactoryCollection"); sThis = this; } CSVWorld::CommandDelegateFactoryCollection::~CommandDelegateFactoryCollection() { sThis = nullptr; for (std::map::iterator iter ( mFactories.begin()); iter!=mFactories.end(); ++iter) delete iter->second; } void CSVWorld::CommandDelegateFactoryCollection::add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory) { mFactories.insert (std::make_pair (display, factory)); } CSVWorld::CommandDelegate *CSVWorld::CommandDelegateFactoryCollection::makeDelegate ( CSMWorld::ColumnBase::Display display, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { std::map::const_iterator iter = mFactories.find (display); if (iter!=mFactories.end()) return iter->second->makeDelegate (dispatcher, document, parent); return new CommandDelegate (dispatcher, document, parent); } const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFactoryCollection::get() { if (!sThis) throw std::logic_error ("no instance of CSVWorld::CommandDelegateFactoryCollection"); return *sThis; } QUndoStack& CSVWorld::CommandDelegate::getUndoStack() const { return mDocument.getUndoStack(); } CSMDoc::Document& CSVWorld::CommandDelegate::getDocument() const { return mDocument; } CSMWorld::ColumnBase::Display CSVWorld::CommandDelegate::getDisplayTypeFromIndex(const QModelIndex &index) const { int rawDisplay = index.data(CSMWorld::ColumnBase::Role_Display).toInt(); return static_cast(rawDisplay); } void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { if (!mCommandDispatcher) return; QVariant variant; // Color columns use a custom editor, so we need to fetch selected color from it. CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); if (colorEditor != nullptr) { variant = colorEditor->colorInt(); } else { NastyTableModelHack hack (*model); QStyledItemDelegate::setModelData (editor, &hack, index); variant = hack.getData(); } if ((model->data (index)!=variant) && (model->flags(index) & Qt::ItemIsEditable)) mCommandDispatcher->executeModify (model, index, variant); } CSVWorld::CommandDelegate::CommandDelegate (CSMWorld::CommandDispatcher *commandDispatcher, CSMDoc::Document& document, QObject *parent) : QStyledItemDelegate (parent), mEditLock (false), mCommandDispatcher (commandDispatcher), mDocument (document) {} void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { if (!mEditLock) { setModelDataImp (editor, model, index); } ///< \todo provide some kind of feedback to the user, indicating that editing is currently not possible. } QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); // This createEditor() method is called implicitly from tables. // For boolean values in tables use the default editor (combobox). // Checkboxes is looking ugly in the table view. // TODO: Find a better solution? if (display == CSMWorld::ColumnBase::Display_Boolean) { return QItemEditorFactory::defaultFactory()->createEditor(QVariant::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) else if (display == CSMWorld::ColumnBase::Display_Colour) { return new CSVWidget::ColorEditor(index.data().toInt(), parent, true); } return createEditor (parent, option, index, display); } QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { QVariant variant = index.data(); if (!variant.isValid()) { variant = index.data(Qt::DisplayRole); if (!variant.isValid()) { return nullptr; } } // NOTE: for each editor type (e.g. QLineEdit) there needs to be a corresponding // entry in CSVWorld::DialogueDelegateDispatcher::makeEditor() switch (display) { case CSMWorld::ColumnBase::Display_Colour: { return new CSVWidget::ColorEditor(variant.toInt(), parent); } case CSMWorld::ColumnBase::Display_Integer: { DialogueSpinBox *sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_SignedInteger8: { DialogueSpinBox *sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_SignedInteger16: { DialogueSpinBox *sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_UnsignedInteger8: { DialogueSpinBox *sb = new DialogueSpinBox(parent); sb->setRange(0, std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_UnsignedInteger16: { DialogueSpinBox *sb = new DialogueSpinBox(parent); sb->setRange(0, std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_Var: return new QLineEdit(parent); case CSMWorld::ColumnBase::Display_Float: { DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(3); return dsb; } case CSMWorld::ColumnBase::Display_Double: { DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(6); return dsb; } /// \todo implement size limit. QPlainTextEdit does not support a size limit. case CSMWorld::ColumnBase::Display_LongString: case CSMWorld::ColumnBase::Display_LongString256: { QPlainTextEdit *edit = new QPlainTextEdit(parent); edit->setUndoRedoEnabled (false); return edit; } case CSMWorld::ColumnBase::Display_Boolean: return new QCheckBox(parent); case CSMWorld::ColumnBase::Display_ScriptLines: return new ScriptEdit (mDocument, ScriptHighlighter::Mode_Console, parent); case CSMWorld::ColumnBase::Display_String: // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used return new CSVWidget::DropLineEdit(display, parent); case CSMWorld::ColumnBase::Display_String32: { // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); widget->setMaxLength (32); return widget; } case CSMWorld::ColumnBase::Display_String64: { // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); widget->setMaxLength (64); return widget; } default: return QStyledItemDelegate::createEditor (parent, option, index); } } void CSVWorld::CommandDelegate::setEditLock (bool locked) { mEditLock = locked; } bool CSVWorld::CommandDelegate::isEditLocked() const { return mEditLock; } void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index) const { setEditorData (editor, index, false); } void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { QVariant variant = index.data(Qt::EditRole); if (tryDisplay) { if (!variant.isValid()) { variant = index.data(Qt::DisplayRole); if (!variant.isValid()) { return; } } QPlainTextEdit* plainTextEdit = qobject_cast(editor); if(plainTextEdit) //for some reason it is easier to brake the loop here { if (plainTextEdit->toPlainText() == variant.toString()) { return; } } } // Color columns use a custom editor, so we need explicitly set a data for it CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); if (colorEditor != nullptr) { colorEditor->setColor(variant.toInt()); return; } QByteArray n = editor->metaObject()->userProperty().name(); if (n == "dateTime") { if (editor->inherits("QTimeEdit")) n = "time"; else if (editor->inherits("QDateEdit")) n = "date"; } if (!n.isEmpty()) { if (!variant.isValid()) variant = QVariant(editor->property(n).userType(), (const void *)nullptr); editor->setProperty(n, variant); } } void CSVWorld::CommandDelegate::settingChanged (const CSMPrefs::Setting *setting) {} openmw-openmw-0.48.0/apps/opencs/view/world/util.hpp000066400000000000000000000117261445372753700224640ustar00rootroot00000000000000#ifndef CSV_WORLD_UTIL_H #define CSV_WORLD_UTIL_H #include #include #include #ifndef Q_MOC_RUN #include "../../model/world/columnbase.hpp" #include "../../model/doc/document.hpp" #endif class QUndoStack; namespace CSMWorld { class TableMimeData; class UniversalId; class CommandDispatcher; } namespace CSMPrefs { class Setting; } namespace CSVWorld { ///< \brief Getting the data out of an editor widget /// /// Really, Qt? Really? class NastyTableModelHack : public QAbstractTableModel { QAbstractItemModel& mModel; QVariant mData; public: NastyTableModelHack (QAbstractItemModel& model); int rowCount (const QModelIndex & parent = QModelIndex()) const override; int columnCount (const QModelIndex & parent = QModelIndex()) const override; QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant getData() const; }; class CommandDelegate; class CommandDelegateFactory { public: virtual ~CommandDelegateFactory(); virtual CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const = 0; ///< The ownership of the returned CommandDelegate is transferred to the caller. }; class CommandDelegateFactoryCollection { static CommandDelegateFactoryCollection *sThis; std::map mFactories; private: // not implemented CommandDelegateFactoryCollection (const CommandDelegateFactoryCollection&); CommandDelegateFactoryCollection& operator= (const CommandDelegateFactoryCollection&); public: CommandDelegateFactoryCollection(); ~CommandDelegateFactoryCollection(); void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory); ///< The ownership of \a factory is transferred to *this. /// /// This function must not be called more than once per value of \a display. CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. /// /// If no factory is registered for \a display, a CommandDelegate will be returned. static const CommandDelegateFactoryCollection& get(); }; ///< \brief Use commands instead of manipulating the model directly class CommandDelegate : public QStyledItemDelegate { Q_OBJECT bool mEditLock; CSMWorld::CommandDispatcher *mCommandDispatcher; CSMDoc::Document& mDocument; protected: QUndoStack& getUndoStack() const; CSMDoc::Document& getDocument() const; CSMWorld::ColumnBase::Display getDisplayTypeFromIndex(const QModelIndex &index) const; virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; public: /// \param commandDispatcher If CommandDelegate will be only be used on read-only /// cells, a 0-pointer can be passed here. CommandDelegate (CSMWorld::CommandDispatcher *commandDispatcher, CSMDoc::Document& document, QObject *parent); void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const override; QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; virtual QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const; void setEditLock (bool locked); bool isEditLocked() const; ///< \return Does column require update? void setEditorData (QWidget *editor, const QModelIndex& index) const override; virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const; /// \attention This is not a slot. For ordering reasons this function needs to be /// called manually from the parent object's settingChanged function. virtual void settingChanged (const CSMPrefs::Setting *setting); }; } #endif openmw-openmw-0.48.0/apps/opencs/view/world/vartypedelegate.cpp000066400000000000000000000044231445372753700246630ustar00rootroot00000000000000#include "vartypedelegate.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/commandmacro.hpp" void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const { QModelIndex next = model->index (index.row(), index.column()+1); QVariant old = model->data (next); QVariant value; switch (type) { case ESM::VT_Short: case ESM::VT_Int: case ESM::VT_Long: value = old.toInt(); break; case ESM::VT_Float: value = old.toFloat(); break; case ESM::VT_String: value = old.toString(); break; default: break; // ignore the rest } CSMWorld::CommandMacro macro (getUndoStack(), "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); macro.push (new CSMWorld::ModifyCommand (*model, index, type)); macro.push (new CSMWorld::ModifyCommand (*model, next, value)); } CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : EnumDelegate (values, dispatcher, document, parent) {} CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory (ESM::VarType type0, ESM::VarType type1, ESM::VarType type2, ESM::VarType type3) { if (type0!=ESM::VT_Unknown) add (type0); if (type1!=ESM::VT_Unknown) add (type1); if (type2!=ESM::VT_Unknown) add (type2); if (type3!=ESM::VT_Unknown) add (type3); } CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate ( CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const { return new VarTypeDelegate (mValues, dispatcher, document, parent); } void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) { std::vector> enums = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_ValueType); if (static_cast(type) >= enums.size()) throw std::logic_error ("Unsupported variable type"); mValues.emplace_back(type, QString::fromUtf8 (enums[type].second.c_str())); } openmw-openmw-0.48.0/apps/opencs/view/world/vartypedelegate.hpp000066400000000000000000000023471445372753700246730ustar00rootroot00000000000000#ifndef CSV_WORLD_VARTYPEDELEGATE_H #define CSV_WORLD_VARTYPEDELEGATE_H #include #include "enumdelegate.hpp" namespace CSVWorld { class VarTypeDelegate : public EnumDelegate { private: void addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const override; public: VarTypeDelegate (const std::vector >& values, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); }; class VarTypeDelegateFactory : public CommandDelegateFactory { std::vector > mValues; public: VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown, ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown, ESM::VarType type3 = ESM::VT_Unknown); CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. void add (ESM::VarType type); }; } #endif openmw-openmw-0.48.0/apps/openmw/000077500000000000000000000000001445372753700167045ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/CMakeLists.txt000066400000000000000000000214341445372753700214500ustar00rootroot00000000000000# local files set(GAME main.cpp engine.cpp options.cpp ${CMAKE_SOURCE_DIR}/files/windows/openmw.rc ${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest ) if (ANDROID) set(GAME ${GAME} android_main.cpp) endif() set(GAME_HEADER engine.hpp ) source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode ) add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch inputmanagerimp mousemanager keyboardmanager sensormanager gyromanager ) add_openmw_dir (mwgui layout textinput widgets race class birth review windowmanagerimp console dialogue windowbase statswindow messagebox journalwindow charactercreation mapwindow windowpinnablebase tooltips scrollwindow bookwindow resourceskin formatting inventorywindow container hud countdialog tradewindow settingswindow confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog enchantingdialog trainingwindow travelwindow exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher postprocessorhud ) add_openmw_dir (mwdialogue dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch scripttest ) add_openmw_dir (mwscript locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions guiextensions soundextensions skyextensions statsextensions containerextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions animationextensions transformationextensions consoleextensions userextensions ) add_openmw_dir (mwlua luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair ) add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings ) add_openmw_dir (mwworld refdata worldimp scene globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellvisitors failedaction cells localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager cellpreloader datetimemanager groundcoverstore magiceffects ) add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart ) add_openmw_dir (mwmechanics mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction summoning character actors objects aistate trading weaponpriority spellpriority weapontype spellutil spelleffects ) add_openmw_dir (mwstate statemanagerimp charactermanager character quicksavemanager ) add_openmw_dir (mwbase environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager inputmanager windowmanager statemanager ) # Main executable if (NOT ANDROID) openmw_add_executable(openmw ${OPENMW_FILES} ${GAME} ${GAME_HEADER} ${APPLE_BUNDLE_RESOURCES} ) else () add_library(openmw SHARED ${OPENMW_FILES} ${GAME} ${GAME_HEADER} ) endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. include_directories( ${FFmpeg_INCLUDE_DIRS} ) target_link_libraries(openmw # CMake's built-in OSG finder does not use pkgconfig, so we have to # manually ensure the order is correct for inter-library dependencies. # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGPARTICLE_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGSHADOW_LIBRARIES} ${OSGDB_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSG_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} ${FFmpeg_LIBRARIES} ${MyGUI_LIBRARIES} ${SDL2_LIBRARY} ${RecastNavigation_LIBRARIES} "osg-ffmpeg-videoplayer" "oics" components ) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw PRIVATE ) endif() if (ANDROID) target_link_libraries(openmw EGL android log z) endif (ANDROID) if (USE_SYSTEM_TINYXML) target_link_libraries(openmw ${TinyXML_LIBRARIES}) endif() if (NOT UNIX) target_link_libraries(openmw ${SDL2MAIN_LIBRARY}) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT}) endif() if(APPLE) set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) configure_file("${OpenMW_BINARY_DIR}/defaults.bin" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) add_custom_command(TARGET openmw POST_BUILD COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources") find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) if (FFmpeg_FOUND) target_link_libraries(openmw z) target_link_options(openmw PRIVATE "LINKER:SHELL:-framework CoreVideo" "LINKER:SHELL:-framework CoreMedia" "LINKER:SHELL:-framework VideoToolbox" "LINKER:SHELL:-framework AudioToolbox" "LINKER:SHELL:-framework VideoDecodeAcceleration") endif() endif(APPLE) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) endif() if (WIN32) INSTALL(TARGETS openmw RUNTIME DESTINATION ".") endif (WIN32) openmw-openmw-0.48.0/apps/openmw/android_main.cpp000066400000000000000000000041441445372753700220370ustar00rootroot00000000000000#ifndef stderr int stderr = 0; // Hack: fix linker error #endif #include "SDL_main.h" #include #include #include /******************************************************************************* Functions called by JNI *******************************************************************************/ #include /* Called before to initialize JNI bindings */ extern void SDL_Android_Init(JNIEnv* env, jclass cls); extern int argcData; extern const char **argvData; void releaseArgv(); extern "C" int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; SDL_GetMouseState(&ret, nullptr); return ret; } extern "C" int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; SDL_GetMouseState(nullptr, &ret); return ret; } extern "C" int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobject obj) { return SDL_ShowCursor(SDL_QUERY); } extern SDL_Window *Android_Window; extern "C" int SDL_SendMouseMotion(SDL_Window * window, int mouseID, int relative, int x, int y); extern "C" void Java_org_libsdl_app_SDLActivity_sendRelativeMouseMotion(JNIEnv *env, jclass cls, int x, int y) { SDL_SendMouseMotion(Android_Window, 0, 1, x, y); } extern "C" int SDL_SendMouseButton(SDL_Window * window, int mouseID, Uint8 state, Uint8 button); extern "C" void Java_org_libsdl_app_SDLActivity_sendMouseButton(JNIEnv *env, jclass cls, int state, int button) { SDL_SendMouseButton(Android_Window, 0, state, button); } extern "C" int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); // On Android, we use a virtual controller with guid="Virtual" SDL_GameControllerAddMapping("5669727475616c000000000000000000,Virtual,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4"); return 0; } openmw-openmw-0.48.0/apps/openmw/doc.hpp000066400000000000000000000016441445372753700201670ustar00rootroot00000000000000// Note: This is not a regular source file. /// \ingroup apps /// \defgroup openmw OpenMW /// \namespace OMW /// \ingroup openmw /// \brief Integration of OpenMW-subsystems /// \namespace MWDialogue /// \ingroup openmw /// \brief NPC dialogues /// \namespace MWMechanics /// \ingroup openmw /// \brief Game mechanics and NPC-AI /// \namespace MWSound /// \ingroup openmw /// \brief Sound & music /// \namespace MWGUI /// \ingroup openmw /// \brief HUD and windows /// \namespace MWRender /// \ingroup openmw /// \brief Rendering /// \namespace MWWorld /// \ingroup openmw /// \brief World data /// \namespace MWClass /// \ingroup openmw /// \brief Workaround for non-OOP design of the record system /// \namespace MWInput /// \ingroup openmw /// \brief User input and character controls /// \namespace MWScript /// \ingroup openmw /// \brief MW-specific script extensions and integration of the script system into OpenMW openmw-openmw-0.48.0/apps/openmw/engine.cpp000066400000000000000000001200031445372753700206510ustar00rootroot00000000000000#include "engine.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" #include "mwlua/luamanagerimp.hpp" #include "mwscript/scriptmanagerimp.hpp" #include "mwscript/interpretercontext.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" #include "mwworld/player.hpp" #include "mwworld/worldimp.hpp" #include "mwrender/vismask.hpp" #include "mwclass/classes.hpp" #include "mwdialogue/dialoguemanagerimp.hpp" #include "mwdialogue/journalimp.hpp" #include "mwdialogue/scripttest.hpp" #include "mwmechanics/mechanicsmanagerimp.hpp" #include "mwstate/statemanagerimp.hpp" namespace { void checkSDLError(int ret) { if (ret != 0) Log(Debug::Error) << "SDL error: " << SDL_GetError(); } struct UserStats { const std::string mLabel; const std::string mBegin; const std::string mEnd; const std::string mTaken; UserStats(const std::string& label, const std::string& prefix) : mLabel(label), mBegin(prefix + "_time_begin"), mEnd(prefix + "_time_end"), mTaken(prefix + "_time_taken") {} }; enum class UserStatsType : std::size_t { Input, Sound, State, Script, Mechanics, Physics, PhysicsWorker, World, Gui, Lua, LuaSyncUpdate, Number, }; template struct UserStatsValue { static const UserStats sValue; }; template <> const UserStats UserStatsValue::sValue {"Input", "input"}; template <> const UserStats UserStatsValue::sValue {"Sound", "sound"}; template <> const UserStats UserStatsValue::sValue {"State", "state"}; template <> const UserStats UserStatsValue::sValue {"Script", "script"}; template <> const UserStats UserStatsValue::sValue {"Mech", "mechanics"}; template <> const UserStats UserStatsValue::sValue {"Phys", "physics"}; template <> const UserStats UserStatsValue::sValue {" -Async", "physicsworker"}; template <> const UserStats UserStatsValue::sValue {"World", "world"}; template <> const UserStats UserStatsValue::sValue {"Gui", "gui"}; template <> const UserStats UserStatsValue::sValue {"Lua", "lua"}; template <> const UserStats UserStatsValue::sValue{ " -Sync", "luasyncupdate" }; template struct ForEachUserStatsValue { template static void apply(F&& f) { f(UserStatsValue::sValue); using Next = ForEachUserStatsValue(static_cast(type) + 1)>; Next::apply(std::forward(f)); } }; template <> struct ForEachUserStatsValue { template static void apply(F&&) {} }; template void forEachUserStatsValue(F&& f) { ForEachUserStatsValue(0)>::apply(std::forward(f)); } template class ScopedProfile { public: ScopedProfile(osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats) : mScopeStart(timer.tick()), mFrameStart(frameStart), mFrameNumber(frameNumber), mTimer(timer), mStats(stats) { } ScopedProfile(const ScopedProfile&) = delete; ScopedProfile& operator=(const ScopedProfile&) = delete; ~ScopedProfile() { if (!mStats.collectStats("engine")) return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart)); mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end)); mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end)); } private: const osg::Timer_t mScopeStart; const osg::Timer_t mFrameStart; const unsigned int mFrameNumber; const osg::Timer& mTimer; osg::Stats& mStats; }; void initStatsHandler(Resource::Profiler& profiler) { const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f); const osg::Vec4f barColor(1.f, 1.f, 1.f, 1.f); const float multiplier = 1000; const bool average = true; const bool averageInInverseSpace = false; const float maxValue = 10000; forEachUserStatsValue([&] (const UserStats& v) { profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. // Unconditionnally add the async physics stats, and then remove it at runtime if necessary if (Settings::Manager::getInt("async num threads", "Physics") == 0) profiler.removeUserStatsLine(" -Async"); } struct ScheduleNonDialogMessageBox { void operator()(std::string message) const { MWBase::Environment::get().getWindowManager()->scheduleMessageBox(std::move(message), MWGui::ShowInDialogueMode_Never); } }; struct IgnoreString { void operator()(std::string) const {} }; class IdentifyOpenGLOperation : public osg::GraphicsOperation { public: IdentifyOpenGLOperation() : GraphicsOperation("IdentifyOpenGLOperation", false) {} void operator()(osg::GraphicsContext* graphicsContext) override { Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR); Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER); Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxTextureImageUnits); } int getMaxTextureImageUnits() const { if (mMaxTextureImageUnits == 0) throw std::logic_error("mMaxTextureImageUnits is not initialized"); return mMaxTextureImageUnits; } private: int mMaxTextureImageUnits = 0; }; class InitializeStereoOperation final : public osg::GraphicsOperation { public: InitializeStereoOperation() : GraphicsOperation("InitializeStereoOperation", false) {} void operator()(osg::GraphicsContext* graphicsContext) override { Stereo::Manager::instance().initializeStereo(graphicsContext); } }; } void OMW::Engine::executeLocalScripts() { MWWorld::LocalScripts& localScripts = mWorld->getLocalScripts(); localScripts.startIteration(); std::pair script; while (localScripts.getNext(script)) { MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); mScriptManager->run (script.first, interpreterContext); } } bool OMW::Engine::frame(float frametime) { try { const osg::Timer_t frameStart = mViewer->getStartTick(); const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); const osg::Timer* const timer = osg::Timer::instance(); osg::Stats* const stats = mViewer->getViewerStats(); mEnvironment.setFrameDuration(frametime); // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mInputManager->update(frametime, false); } // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2), // and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (!mWindowManager->isWindowVisible()) { mSoundManager->pausePlayback(); return false; } else mSoundManager->resumePlayback(); // sound if (mUseSound) mSoundManager->update(frametime); } // Main menu opened? Then scripts are also paused. bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); // Should be called after input manager update and before any change to the game world. // It applies to the game world queued changes from the previous frame. mLuaManager->synchronizedUpdate(); } // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mStateManager->update (frametime); } bool guiActive = mWindowManager->isGuiMode(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { if (mWorld->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts mScriptManager->getGlobalScripts().run(); } mWorld->markCellAsUnchanged(); } if (!guiActive) { double hours = (frametime * mWorld->getTimeScaleFactor()) / 3600.0; mWorld->advanceTime(hours, true); mWorld->rechargeItems(frametime, true); } } } // update mechanics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mMechanicsManager->update(frametime, guiActive); } if (mStateManager->getState() == MWBase::StateManager::State_Running) { MWWorld::Ptr player = mWorld->getPlayerPtr(); if(!guiActive && player.getClass().getCreatureStats(player).isDead()) mStateManager->endGame(); } } // update physics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } // update world { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { mWorld->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mWindowManager->update(frametime); } const bool reportResource = stats->collectStats("resource"); if (reportResource) stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize()); mUnrefQueue->flush(*mWorkQueue); if (reportResource) { stats->setAttribute(frameNumber, "FrameNumber", frameNumber); mResourceSystem->reportStats(frameNumber, stats); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); mMechanicsManager->reportStats(frameNumber, *stats); mWorld->reportStats(frameNumber, *stats); mLuaManager->reportStats(frameNumber, *stats); } } catch (const std::exception& e) { Log(Debug::Error) << "Error in frame: " << e.what(); } return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mWindow(nullptr) , mEncoding(ToUTF8::WINDOWS_1252) , mScreenCaptureOperation(nullptr) , mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation()) , mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation()) , mStereoManager(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) , mCompileAllDialogue (false) , mWarningsMode (1) , mScriptConsoleMode (false) , mActivationDistanceOverride(-1) , mGrab(true) , mRandomSeed(0) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) , mCfgMgr(configurationManager) , mGlMaxTextureImageUnits(0) { SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; if(SDL_WasInit(flags) == 0) { SDL_SetMainReady(); if(SDL_Init(flags) != 0) { throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); } } } OMW::Engine::~Engine() { if (mScreenCaptureOperation != nullptr) mScreenCaptureOperation->stop(); mMechanicsManager = nullptr; mDialogueManager = nullptr; mJournal = nullptr; mScriptManager = nullptr; mWindowManager = nullptr; mWorld = nullptr; mStereoManager = nullptr; mSoundManager = nullptr; mInputManager = nullptr; mStateManager = nullptr; mLuaManager = nullptr; mScriptContext = nullptr; mUnrefQueue = nullptr; mWorkQueue = nullptr; mViewer = nullptr; mResourceSystem.reset(); mEncoder = nullptr; if (mWindow) { SDL_DestroyWindow(mWindow); mWindow = nullptr; } SDL_Quit(); } void OMW::Engine::enableFSStrict(bool fsStrict) { mFSStrict = fsStrict; } // Set data dir void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs) { mDataDirs = dataDirs; mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs")); mFileCollections = Files::Collections (mDataDirs, !mFSStrict); } // Add BSA archive void OMW::Engine::addArchive (const std::string& archive) { mArchives.push_back(archive); } // Set resource dir void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) { mResDir = parResDir; } // Set start cell name void OMW::Engine::setCell (const std::string& cellName) { mCellName = cellName; } void OMW::Engine::addContentFile(const std::string& file) { mContentFiles.push_back(file); } void OMW::Engine::addGroundcoverFile(const std::string& file) { mGroundcoverFiles.emplace_back(file); } void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; mNewGame = newGame; } void OMW::Engine::createWindow() { int screen = Settings::Manager::getInt("screen", "Video"); int width = Settings::Manager::getInt("resolution x", "Video"); int height = Settings::Manager::getInt("resolution y", "Video"); Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); bool windowBorder = Settings::Manager::getBool("window border", "Video"); bool vsync = Settings::Manager::getBool("vsync", "Video"); unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); if(windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); } Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; if(windowMode == Settings::WindowMode::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN; else if (windowMode == Settings::WindowMode::WindowedFullscreen) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; // Allows for Windows snapping features to properly work in borderless window SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24)); if (Debug::shouldDebugOpenGL()) checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG)); if (antialiasing > 0) { checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); } osg::ref_ptr graphicsWindow; while (!graphicsWindow || !graphicsWindow->valid()) { while (!mWindow) { mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags); if (!mWindow) { // Try with a lower AA if (antialiasing > 0) { Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } else { std::stringstream error; error << "Failed to create SDL window: " << SDL_GetError(); throw std::runtime_error(error.str()); } } } setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); SDL_GetWindowSize(mWindow, &traits->width, &traits->height); traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); traits->vsync = vsync; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext"); if (traits->samples < antialiasing) { Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead."; graphicsWindow->closeImplementation(); SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } if (traits->red < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->red << " bit red channel."; if (traits->green < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; if (traits->depth < 24) Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } osg::ref_ptr camera = mViewer->getCamera(); camera->setGraphicsContext(graphicsWindow); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); osg::ref_ptr realizeOperations = new SceneUtil::OperationSequence(false); mViewer->setRealizeOperation(realizeOperations); osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); realizeOperations->add(identifyOp); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); realizeOperations->add(mSelectDepthFormatOperation); realizeOperations->add(mSelectColorFormatOperation); if (Stereo::getStereo()) { realizeOperations->add(new InitializeStereoOperation()); Stereo::setVertexBufferHint(); } mViewer->realize(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } void OMW::Engine::setWindowIcon() { boost::filesystem::ifstream windowIconStream; std::string windowIcon = (mResDir / "openmw.png").string(); windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) Log(Debug::Error) << "Error: Failed to open " << windowIcon; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status(); else { osg::ref_ptr image = result.getImage(); auto surface = SDLUtil::imageToSurface(image, true); SDL_SetWindowIcon(mWindow, surface.get()); } } void OMW::Engine::prepareEngine() { mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mEnvironment.setStateManager(*mStateManager); mStereoManager = std::make_unique(mViewer); osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); createWindow(); mVFS = std::make_unique(mFSStrict); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem = std::make_unique(mVFS.get()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General") ); mEnvironment.setResourceSystem(*mResourceSystem); int numThreads = Settings::Manager::getInt("preload num threads", "Cells"); if (numThreads <= 0) throw std::runtime_error("Invalid setting: 'preload num threads' must be >0"); mWorkQueue = new SceneUtil::WorkQueue(numThreads); mUnrefQueue = std::make_unique(); mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation( mWorkQueue, new SceneUtil::WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General"), Settings::Manager::getBool("notify on saved screenshot", "General") ? std::function(ScheduleNonDialogMessageBox {}) : std::function(IgnoreString {}) ) ); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); mViewer->addEventHandler(mScreenCaptureHandler); mLuaManager = std::make_unique(mVFS.get(), (mResDir / "lua_libs").string()); mEnvironment.setLuaManager(*mLuaManager); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); if(!keybinderUserExists) { std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string(); if(boost::filesystem::exists(input2)) { boost::filesystem::copy_file(input2, keybinderUser); keybinderUserExists = boost::filesystem::exists(keybinderUser); Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; } } else Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/gamecontrollerdb.txt"; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; std::string userGameControllerdb; if (boost::filesystem::exists(userdefault)) userGameControllerdb = userdefault; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; else if (boost::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; //else if it doesn't exist, pass in an empty string // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath((mResDir / "shaders").string()); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 if (exts) exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), mScriptConsoleMode, mTranslationDataStorage, mEncoding, Version::getOpenmwVersionDescription(mResDir.string()), shadersSupported); mEnvironment.setWindowManager(*mWindowManager); mInputManager = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); mEnvironment.setInputManager(*mInputManager); // Create sound system mSoundManager = std::make_unique(mVFS.get(), mUseSound); mEnvironment.setSoundManager(*mSoundManager); if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) mWindowManager->playVideo(logo, true); } // Create the world mWorld = std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), *mUnrefQueue, mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder.get(), mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); mEnvironment.setWorld(*mWorld); mWindowManager->setStore(mWorld->getStore()); mLuaManager->initL10n(); mWindowManager->initUI(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder.get()); for (size_t i = 0; i < mContentFiles.size(); i++) mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); Compiler::registerExtensions (mExtensions); // Create script system mScriptContext = std::make_unique(MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); mScriptManager = std::make_unique(mWorld->getStore(), *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector()); mEnvironment.setScriptManager(*mScriptManager); // Create game mechanics system mMechanicsManager = std::make_unique(); mEnvironment.setMechanicsManager(*mMechanicsManager); // Create dialog system mJournal = std::make_unique(); mEnvironment.setJournal(*mJournal); mDialogueManager = std::make_unique(mExtensions, mTranslationDataStorage); mEnvironment.setDialogueManager(*mDialogueManager); // scripts if (mCompileAll) { std::pair result = mScriptManager->compileAll(); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" << 100*static_cast (result.second)/result.first << "%)"; } if (mCompileAllDialogue) { std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" << 100*static_cast (result.second)/result.first << "%)"; } mLuaManager->init(); mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath().string()); } class OMW::Engine::LuaWorker { public: explicit LuaWorker(Engine* engine) : mEngine(engine) { if (Settings::Manager::getInt("lua num threads", "Lua") > 0) mThread = std::thread([this]{ threadBody(); }); }; ~LuaWorker() { if (mThread && mThread->joinable()) { Log(Debug::Error) << "Unexpected destruction of LuaWorker; likely there is an unhandled exception in the main thread."; join(); } } void allowUpdate() { if (!mThread) return; { std::lock_guard lk(mMutex); mUpdateRequest = true; } mCV.notify_one(); } void finishUpdate() { if (mThread) { std::unique_lock lk(mMutex); mCV.wait(lk, [&]{ return !mUpdateRequest; }); } else update(); }; void join() { if (mThread) { { std::lock_guard lk(mMutex); mJoinRequest = true; } mCV.notify_one(); mThread->join(); } } private: void update() { const auto& viewer = mEngine->mViewer; const osg::Timer_t frameStart = viewer->getStartTick(); const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber(); ScopedProfile profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats()); mEngine->mLuaManager->update(); } void threadBody() { while (true) { std::unique_lock lk(mMutex); mCV.wait(lk, [&]{ return mUpdateRequest || mJoinRequest; }); if (mJoinRequest) break; update(); mUpdateRequest = false; lk.unlock(); mCV.notify_one(); } } Engine* mEngine; std::mutex mMutex; std::condition_variable mCV; bool mUpdateRequest = false; bool mJoinRequest = false; std::optional mThread; }; // Initialise and enter main loop. void OMW::Engine::go() { assert (!mContentFiles.empty()); Log(Debug::Info) << "OSG version: " << osgGetVersion(); SDL_version sdlVersion; SDL_GetVersion(&sdlVersion); Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch; Misc::Rng::init(mRandomSeed); Settings::ShaderManager::get().load((mCfgMgr.getUserConfigPath() / "shaders.yaml").string()); MWClass::registerClasses(); // Create encoder mEncoder = std::make_unique(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; mViewer->setReleaseContextAtEndOfFrameHint(false); // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); prepareEngine(); boost::filesystem::ofstream stats; if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) { stats.open(path, std::ios_base::out); if (stats.is_open()) Log(Debug::Info) << "Stats will be written to: " << path; else Log(Debug::Warning) << "Failed to open file for stats: " << path; } // Setup profiler osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); mViewer->addEventHandler(resourceshandler); if (stats.is_open()) Resource::CollectStatistics(mViewer); // Start the game if (!mSaveGameFile.empty()) { mStateManager->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu mWindowManager->pushGuiMode (MWGui::GM_MainMenu); mSoundManager->playTitleMusic(); const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) mWindowManager->playVideo(logo, /*allowSkipping*/true, /*overrideSounds*/false); } else { mStateManager->newGame (!mNewGame); } if (!mStartupScript.empty() && mStateManager->getState() == MWState::StateManager::State_Running) { mWindowManager->executeInConsole(mStartupScript); } LuaWorker luaWorker(this); // starts a separate lua thread if "lua num threads" > 0 // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mStateManager->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(std::min( frameRateLimiter.getLastFrameDuration(), maxSimulationInterval )).count() * mEnvironment.getWorld()->getSimulationTimeScale(); mViewer->advance(simulationTime); if (!frame(dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } else { mViewer->eventTraversal(); mViewer->updateTraversal(); mWorld->updateWindowManager(); luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now mViewer->renderingTraversals(); luaWorker.finishUpdate(); bool guiActive = mWindowManager->isGuiMode(); if (!guiActive) simulationTime += dt; } if (stats) { constexpr unsigned statsReportDelay = 3; const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (frameNumber >= statsReportDelay) { const unsigned reportFrameNumber = frameNumber - statsReportDelay; mViewer->getViewerStats()->report(stats, reportFrameNumber); osgViewer::Viewer::Cameras cameras; mViewer->getCameras(cameras); for (auto camera : cameras) camera->getStats()->report(stats, reportFrameNumber); } } frameRateLimiter.limit(); } luaWorker.join(); // Save user settings Settings::Manager::saveUser((mCfgMgr.getUserConfigPath() / "settings.cfg").string()); Settings::ShaderManager::get().save(); mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string()); Log(Debug::Info) << "Quitting peacefully."; } void OMW::Engine::setCompileAll (bool all) { mCompileAll = all; } void OMW::Engine::setCompileAllDialogue (bool all) { mCompileAllDialogue = all; } void OMW::Engine::setSoundUsage(bool soundUsage) { mUseSound = soundUsage; } void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; } void OMW::Engine::setScriptConsoleMode (bool enabled) { mScriptConsoleMode = enabled; } void OMW::Engine::setStartupScript (const std::string& path) { mStartupScript = path; } void OMW::Engine::setActivationDistanceOverride (int distance) { mActivationDistanceOverride = distance; } void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; } void OMW::Engine::setScriptBlacklist (const std::vector& list) { mScriptBlacklist = list; } void OMW::Engine::setScriptBlacklistUse (bool use) { mScriptBlacklistUse = use; } void OMW::Engine::setSaveGameFile(const std::string &savegame) { mSaveGameFile = savegame; } void OMW::Engine::setRandomSeed(unsigned int seed) { mRandomSeed = seed; } openmw-openmw-0.48.0/apps/openmw/engine.hpp000066400000000000000000000162601445372753700206670ustar00rootroot00000000000000#ifndef ENGINE_H #define ENGINE_H #include #include #include #include #include #include #include "mwbase/environment.hpp" #include "mwworld/ptr.hpp" namespace Resource { class ResourceSystem; } namespace SceneUtil { class WorkQueue; class AsyncScreenCaptureOperation; class UnrefQueue; } namespace VFS { class Manager; } namespace Compiler { class Context; } namespace MWLua { class LuaManager; } namespace Stereo { class Manager; } namespace Files { struct ConfigurationManager; } namespace osgViewer { class ScreenCaptureHandler; } namespace SceneUtil { class SelectDepthFormatOperation; namespace Color { class SelectColorFormatOperation; } } namespace MWState { class StateManager; } namespace MWGui { class WindowManager; } namespace MWInput { class InputManager; } namespace MWSound { class SoundManager; } namespace MWWorld { class World; } namespace MWScript { class ScriptManager; } namespace MWMechanics { class MechanicsManager; } namespace MWDialogue { class DialogueManager; } namespace MWDialogue { class Journal; } struct SDL_Window; namespace OMW { /// \brief Main engine class, that brings together all the components of OpenMW class Engine { SDL_Window* mWindow; std::unique_ptr mVFS; std::unique_ptr mResourceSystem; osg::ref_ptr mWorkQueue; std::unique_ptr mUnrefQueue; std::unique_ptr mWorld; std::unique_ptr mSoundManager; std::unique_ptr mScriptManager; std::unique_ptr mWindowManager; std::unique_ptr mMechanicsManager; std::unique_ptr mDialogueManager; std::unique_ptr mJournal; std::unique_ptr mInputManager; std::unique_ptr mStateManager; std::unique_ptr mLuaManager; MWBase::Environment mEnvironment; ToUTF8::FromType mEncoding; std::unique_ptr mEncoder; Files::PathContainer mDataDirs; std::vector mArchives; boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osg::ref_ptr mScreenCaptureOperation; osg::ref_ptr mSelectDepthFormatOperation; osg::ref_ptr mSelectColorFormatOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; std::unique_ptr mStereoManager; bool mSkipMenu; bool mUseSound; bool mCompileAll; bool mCompileAllDialogue; int mWarningsMode; std::string mFocusName; bool mScriptConsoleMode; std::string mStartupScript; int mActivationDistanceOverride; std::string mSaveGameFile; // Grab mouse? bool mGrab; unsigned int mRandomSeed; Compiler::Extensions mExtensions; std::unique_ptr mScriptContext; Files::Collections mFileCollections; bool mFSStrict; Translation::Storage mTranslationDataStorage; std::vector mScriptBlacklist; bool mScriptBlacklistUse; bool mNewGame; // not implemented Engine (const Engine&); Engine& operator= (const Engine&); void executeLocalScripts(); bool frame (float dt); /// Prepare engine for game play void prepareEngine(); void createWindow(); void setWindowIcon(); public: Engine(Files::ConfigurationManager& configurationManager); virtual ~Engine(); /// Enable strict filesystem mode (do not fold case) /// /// \attention The strict mode must be specified before any path-related settings /// are given to the engine. void enableFSStrict(bool fsStrict); /// Set data dirs void setDataDirs(const Files::PathContainer& dataDirs); /// Add BSA archive void addArchive(const std::string& archive); /// Set resource dir void setResourceDir(const boost::filesystem::path& parResDir); /// Set start cell name void setCell(const std::string& cellName); /** * @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container. * @param file - filename (extension is required) */ void addContentFile(const std::string& file); void addGroundcoverFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); /// Skip main menu and go directly into the game /// /// \param newGame Start a new game instead off dumping the player into the game /// (ignored if !skipMenu). void setSkipMenu (bool skipMenu, bool newGame); void setGrabMouse(bool grab) { mGrab = grab; } /// Initialise and enter main loop. void go(); /// Compile all scripts (excludign dialogue scripts) at startup? void setCompileAll (bool all); /// Compile all dialogue scripts at startup? void setCompileAllDialogue (bool all); /// Font encoding void setEncoding(const ToUTF8::FromType& encoding); /// Enable console-only script functionality void setScriptConsoleMode (bool enabled); /// Set path for a script that is run on startup in the console. void setStartupScript (const std::string& path); /// Override the game setting specified activation distance. void setActivationDistanceOverride (int distance); void setWarningsMode (int mode); void setScriptBlacklist (const std::vector& list); void setScriptBlacklistUse (bool use); /// Set the save game file to load after initialising the engine. void setSaveGameFile(const std::string& savegame); void setRandomSeed(unsigned int seed); private: Files::ConfigurationManager& mCfgMgr; class LuaWorker; int mGlMaxTextureImageUnits; }; } #endif /* ENGINE_H */ openmw-openmw-0.48.0/apps/openmw/main.cpp000066400000000000000000000202651445372753700203410ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "mwgui/debugwindow.hpp" #include "engine.hpp" #include "options.hpp" #include #if defined(_WIN32) #include // makes __argc and __argv available on windows #include extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #endif #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) #include #endif using namespace Fallback; namespace { constexpr std::string_view applicationName = "OpenMW"; } /** * \brief Parses application command line and calls \ref Cfg::ConfigurationManager * to parse configuration files. * * Results are directly written to \ref Engine class. * * \retval true - Everything goes OK * \retval false - Error */ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr) { // Create a local alias for brevity namespace bpo = boost::program_options; typedef std::vector StringsVector; bpo::options_description desc = OpenMW::makeOptionsDescription(); bpo::variables_map variables; Files::parseArgs(argc, argv, variables, desc); bpo::notify(variables); if (variables.count ("help")) { getRawStdout() << desc << std::endl; return false; } if (variables.count ("version")) { cfgMgr.readConfiguration(variables, desc, true); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); getRawStdout() << v.describe() << std::endl; return false; } cfgMgr.readConfiguration(variables, desc); Settings::Manager::load(cfgMgr); setupLogging(cfgMgr.getLogPath().string(), applicationName); MWGui::DebugWindow::startLogRecording(); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings std::string encoding(variables["encoding"].as()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings engine.enableFSStrict(variables["fs-strict"].as()); Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataDirs.push_back(local); cfgMgr.filterOutNonExistingPaths(dataDirs); engine.setResourceDir(variables["resources"].as()); engine.setDataDirs(dataDirs); // fallback archives StringsVector archives = variables["fallback-archive"].as(); for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it) { engine.addArchive(*it); } StringsVector content = variables["content"].as(); if (content.empty()) { Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } std::set contentDedupe; for (const auto& contentFile : content) { if (!contentDedupe.insert(contentFile).second) { Log(Debug::Error) << "Content file specified more than once: " << contentFile << ". Aborting..."; return false; } } for (auto& file : content) { engine.addContentFile(file); } StringsVector groundcover = variables["groundcover"].as(); for (auto& file : groundcover) { engine.addGroundcoverFile(file); } if (variables.count("lua-scripts")) { Log(Debug::Warning) << "Lua scripts have been specified via the old lua-scripts option and will not be loaded. " "Please update them to a version which uses the new omwscripts format."; } // startup-settings engine.setCell(variables["start"].as()); engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); if (!variables["skip-menu"].as() && variables["new-game"].as()) Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it"; // scripts engine.setCompileAll(variables["script-all"].as()); engine.setCompileAllDialogue(variables["script-all-dialogue"].as()); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); engine.setSaveGameFile (variables["load-savegame"].as().string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); engine.setSoundUsage(!variables["no-sound"].as()); engine.setActivationDistanceOverride (variables["activate-dist"].as()); engine.setRandomSeed(variables["random-seed"].as()); return true; } namespace { class OSGLogHandler : public osg::NotifyHandler { void notify(osg::NotifySeverity severity, const char* msg) override { // Copy, because osg logging is not thread safe. std::string msgCopy(msg); if (msgCopy.empty()) return; Debug::Level level; switch (severity) { case osg::ALWAYS: case osg::FATAL: level = Debug::Error; break; case osg::WARN: case osg::NOTICE: level = Debug::Warning; break; case osg::INFO: level = Debug::Info; break; case osg::DEBUG_INFO: case osg::DEBUG_FP: default: level = Debug::Debug; } std::string_view s(msgCopy); if (s.size() < 1024) Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); else { while (!s.empty()) { size_t lineSize = 1; while (lineSize < s.size() && s[lineSize - 1] != '\n') lineSize++; Log(level) << s.substr(0, s[lineSize - 1] == '\n' ? lineSize - 1 : lineSize); s = s.substr(lineSize); } } } }; } int runApplication(int argc, char *argv[]) { Platform::init(); #ifdef __APPLE__ boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0])); boost::filesystem::current_path(binary_path.parent_path()); setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif osg::setNotifyHandler(new OSGLogHandler()); Files::ConfigurationManager cfgMgr; std::unique_ptr engine = std::make_unique(cfgMgr); if (parseOptions(argc, argv, *engine, cfgMgr)) { engine->go(); } return 0; } #ifdef ANDROID extern "C" int SDL_main(int argc, char**argv) #else int main(int argc, char**argv) #endif { return wrapApplication(&runApplication, argc, argv, applicationName); } // Platform specific for Windows when there is no console built into the executable. // Windows will call the WinMain function instead of main in this case, the normal // main function is then called with the __argc and __argv parameters. #if defined(_WIN32) && !defined(_CONSOLE) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { return main(__argc, __argv); } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/000077500000000000000000000000001445372753700201625ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwbase/dialoguemanager.hpp000066400000000000000000000071661445372753700240310ustar00rootroot00000000000000#ifndef GAME_MWBASE_DIALOGUEMANAGER_H #define GAME_MWBASE_DIALOGUEMANAGER_H #include #include #include #include #include namespace Loading { class Listener; } namespace ESM { class ESMReader; class ESMWriter; } namespace MWWorld { class Ptr; } namespace MWBase { /// \brief Interface for dialogue manager (implemented in MWDialogue) class DialogueManager { DialogueManager (const DialogueManager&); ///< not implemented DialogueManager& operator= (const DialogueManager&); ///< not implemented public: class ResponseCallback { public: virtual ~ResponseCallback() = default; virtual void addResponse(const std::string& title, const std::string& text) = 0; }; DialogueManager() {} virtual void clear() = 0; virtual ~DialogueManager() {} virtual bool isInChoice() const = 0; virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; virtual bool inJournal (const std::string& topicId, const std::string& infoId) const = 0; virtual void addTopic(std::string_view topic) = 0; virtual void addChoice(std::string_view text,int choice) = 0; virtual const std::vector >& getChoices() const = 0; virtual bool isGoodbye() const = 0; virtual void goodbye() = 0; virtual void say(const MWWorld::Ptr &actor, const std::string &topic) = 0; virtual void keywordSelected (const std::string& keyword, ResponseCallback* callback) = 0; virtual void goodbyeSelected() = 0; virtual void questionAnswered (int answer, ResponseCallback* callback) = 0; enum TopicType { Specific = 1, Exhausted = 2 }; enum ServiceType { Any = -1, Barter = 1, Repair = 2, Spells = 3, Training = 4, Travel = 5, Spellmaking = 6, Enchanting = 7 }; virtual std::list getAvailableTopics() = 0; virtual int getTopicFlag(const std::string&) const = 0; virtual bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) = 0; virtual void persuade (int type, ResponseCallback* callback) = 0; /// @note Controlled by an option, gets discarded when dialogue ends by default virtual void applyBarterDispositionChange (int delta) = 0; virtual int countSavedGameRecords() const = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction (std::string_view faction1, std::string_view faction2, int diff) = 0; virtual void setFactionReaction (std::string_view faction1, std::string_view faction2, int absolute) = 0; /// @return faction1's opinion of faction2 virtual int getFactionReaction (std::string_view faction1, std::string_view faction2) const = 0; /// Removes the last added topic response for the given actor from the journal virtual void clearInfoActor (const MWWorld::Ptr& actor) const = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/environment.cpp000066400000000000000000000004641445372753700232360ustar00rootroot00000000000000#include "environment.hpp" #include #include MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() { assert(sThis == nullptr); sThis = this; } MWBase::Environment::~Environment() { sThis = nullptr; } openmw-openmw-0.48.0/apps/openmw/mwbase/environment.hpp000066400000000000000000000075651445372753700232540ustar00rootroot00000000000000#ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H #include #include namespace Resource { class ResourceSystem; } namespace MWBase { class World; class ScriptManager; class DialogueManager; class Journal; class SoundManager; class MechanicsManager; class InputManager; class WindowManager; class StateManager; class LuaManager; /// \brief Central hub for mw-subsystems /// /// This class allows each mw-subsystem to access any others subsystem's top-level manager class. /// class Environment { static Environment *sThis; World* mWorld = nullptr; SoundManager* mSoundManager = nullptr; ScriptManager* mScriptManager = nullptr; WindowManager* mWindowManager = nullptr; MechanicsManager* mMechanicsManager = nullptr; DialogueManager* mDialogueManager = nullptr; Journal* mJournal = nullptr; InputManager* mInputManager = nullptr; StateManager* mStateManager = nullptr; LuaManager* mLuaManager = nullptr; Resource::ResourceSystem* mResourceSystem = nullptr; float mFrameRateLimit = 0; float mFrameDuration = 0; public: Environment(); ~Environment(); Environment(const Environment&) = delete; Environment& operator=(const Environment&) = delete; void setWorld(World& value) { mWorld = &value; } void setSoundManager(SoundManager& value) { mSoundManager = &value; } void setScriptManager(ScriptManager& value) { mScriptManager = &value; } void setWindowManager(WindowManager& value) { mWindowManager = &value; } void setMechanicsManager(MechanicsManager& value) { mMechanicsManager = &value; } void setDialogueManager(DialogueManager& value) { mDialogueManager = &value; } void setJournal(Journal& value) { mJournal = &value; } void setInputManager(InputManager& value) { mInputManager = &value; } void setStateManager(StateManager& value) { mStateManager = &value; } void setLuaManager(LuaManager& value) { mLuaManager = &value; } void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; } Misc::NotNullPtr getWorld() const { return mWorld; } Misc::NotNullPtr getSoundManager() const { return mSoundManager; } Misc::NotNullPtr getScriptManager() const { return mScriptManager; } Misc::NotNullPtr getWindowManager() const { return mWindowManager; } Misc::NotNullPtr getMechanicsManager() const { return mMechanicsManager; } Misc::NotNullPtr getDialogueManager() const { return mDialogueManager; } Misc::NotNullPtr getJournal() const { return mJournal; } Misc::NotNullPtr getInputManager() const { return mInputManager; } Misc::NotNullPtr getStateManager() const { return mStateManager; } Misc::NotNullPtr getLuaManager() const { return mLuaManager; } Misc::NotNullPtr getResourceSystem() const { return mResourceSystem; } float getFrameRateLimit() const { return mFrameRateLimit; } void setFrameRateLimit(float value) { mFrameRateLimit = value; } float getFrameDuration() const { return mFrameDuration; } void setFrameDuration(float value) { mFrameDuration = value; } /// Return instance of this class. static const Environment& get() { assert(sThis != nullptr); return *sThis; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/inputmanager.hpp000066400000000000000000000065721445372753700233770ustar00rootroot00000000000000#ifndef GAME_MWBASE_INPUTMANAGER_H #define GAME_MWBASE_INPUTMANAGER_H #include #include #include #include #include namespace Loading { class Listener; } namespace ESM { class ESMReader; class ESMWriter; } namespace MWBase { /// \brief Interface for input manager (implemented in MWInput) class InputManager { InputManager (const InputManager&); ///< not implemented InputManager& operator= (const InputManager&); ///< not implemented public: InputManager() {} /// Clear all savegame-specific data virtual void clear() = 0; virtual ~InputManager() {} virtual void update(float dt, bool disableControls, bool disableEvents=false) = 0; virtual void changeInputMode(bool guiMode) = 0; virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void setDragDrop(bool dragDrop) = 0; virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; virtual void setAttemptJump(bool jumping) = 0; virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; virtual bool getControlSwitch(std::string_view sw) = 0; virtual std::string getActionDescription (int action) const = 0; virtual std::string getActionKeyBindingName (int action) const = 0; virtual std::string getActionControllerBindingName (int action) const = 0; virtual bool actionIsActive(int action) const = 0; virtual float getActionValue(int action) const = 0; // returns value in range [0, 1] virtual bool isControllerButtonPressed(SDL_GameControllerButton button) const = 0; virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1] virtual int getMouseMoveX() const = 0; virtual int getMouseMoveY() const = 0; ///Actions available for binding to keyboard buttons virtual std::vector getActionKeySorting() = 0; ///Actions available for binding to controller buttons virtual std::vector getActionControllerSorting() = 0; virtual int getNumActions() = 0; ///If keyboard is true, only pay attention to keyboard events. If false, only pay attention to controller events (excluding esc) virtual void enableDetectingBindingMode (int action, bool keyboard) = 0; virtual void resetToDefaultKeyBindings() = 0; virtual void resetToDefaultControllerBindings() = 0; /// Returns if the last used input device was a joystick or a keyboard /// @return true if joystick, false otherwise virtual bool joystickLastUsed() = 0; virtual void setJoystickLastUsed(bool enabled) = 0; virtual int countSavedGameRecords() const = 0; virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; virtual void resetIdleTime() = 0; virtual bool isIdle() const = 0; virtual void executeAction(int action) = 0; virtual bool controlsDisabled() = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/journal.hpp000066400000000000000000000064041445372753700223510ustar00rootroot00000000000000#ifndef GAME_MWBASE_JOURNAL_H #define GAME_MWBASE_JOURNAL_H #include #include #include #include #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" #include "../mwdialogue/quest.hpp" namespace Loading { class Listener; } namespace ESM { class ESMReader; class ESMWriter; } namespace MWBase { /// \brief Interface for the player's journal (implemented in MWDialogue) class Journal { Journal (const Journal&); ///< not implemented Journal& operator= (const Journal&); ///< not implemented public: typedef std::deque TEntryContainer; typedef TEntryContainer::const_iterator TEntryIter; typedef std::map TQuestContainer; // topic, quest typedef TQuestContainer::const_iterator TQuestIter; typedef std::map TTopicContainer; // topic-id, topic-content typedef TTopicContainer::const_iterator TTopicIter; public: Journal() {} virtual void clear() = 0; virtual ~Journal() {} virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) = 0; ///< Add a journal entry. /// @param actor Used as context for replacing of escape sequences (%name, etc). virtual void setJournalIndex (const std::string& id, int index) = 0; ///< Set the journal index without adding an entry. virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; /// \note topicId must be lowercase virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) = 0; ///< Removes the last topic response added for the given topicId and actor name. /// \note topicId must be lowercase virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. /// /// \note Iterators to main journal entries will never become invalid. virtual TEntryIter end() const = 0; ///< Iterator pointing past the end of the main journal. virtual TQuestIter questBegin() const = 0; ///< Iterator pointing to the first quest (sorted by topic ID) virtual TQuestIter questEnd() const = 0; ///< Iterator pointing past the last quest. virtual TTopicIter topicBegin() const = 0; ///< Iterator pointing to the first topic (sorted by topic ID) /// /// \note The topic ID is identical with the user-visible topic string. virtual TTopicIter topicEnd() const = 0; ///< Iterator pointing past the last topic. virtual int countSavedGameRecords() const = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/luamanager.hpp000066400000000000000000000064141445372753700230140ustar00rootroot00000000000000#ifndef GAME_MWBASE_LUAMANAGER_H #define GAME_MWBASE_LUAMANAGER_H #include #include #include namespace MWWorld { class Ptr; } namespace Loading { class Listener; } namespace ESM { class ESMReader; class ESMWriter; struct LuaScripts; } namespace MWBase { class LuaManager { public: virtual ~LuaManager() = default; virtual std::string translate(const std::string& contextName, const std::string& key) = 0; virtual void newGameStarted() = 0; virtual void gameLoaded() = 0; virtual void registerObject(const MWWorld::Ptr& ptr) = 0; virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; // TODO: notify LuaManager about other events // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; struct InputEvent { enum { KeyPressed, KeyReleased, ControllerPressed, ControllerReleased, Action, TouchPressed, TouchReleased, TouchMoved, } mType; std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; struct ActorControls { bool mDisableAI = false; bool mChanged = false; bool mJump = false; bool mRun = false; float mMovement = 0; float mSideMovement = 0; float mPitchChange = 0; float mYawChange = 0; int mUse = 0; }; virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; virtual void clear() = 0; virtual void setupPlayer(const MWWorld::Ptr&) = 0; // Saving int countSavedGameRecords() const { return 1; }; virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; virtual void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) = 0; // Loading from a save virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; virtual void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) = 0; // Should be called before loading. The map is used to fix refnums if the order of content files was changed. virtual void setContentFileMapping(const std::map&) = 0; // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. virtual void reloadAllScripts() = 0; virtual void handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) = 0; }; } #endif // GAME_MWBASE_LUAMANAGER_H openmw-openmw-0.48.0/apps/openmw/mwbase/mechanicsmanager.hpp000066400000000000000000000320721445372753700241640ustar00rootroot00000000000000#ifndef GAME_MWBASE_MECHANICSMANAGER_H #define GAME_MWBASE_MECHANICSMANAGER_H #include #include #include #include #include #include "../mwmechanics/greetingstate.hpp" #include "../mwworld/ptr.hpp" namespace osg { class Stats; class Vec3f; } namespace ESM { struct Class; class ESMReader; class ESMWriter; } namespace MWWorld { class Ptr; class CellStore; class CellRef; } namespace Loading { class Listener; } namespace MWBase { /// \brief Interface for game mechanics manager (implemented in MWMechanics) class MechanicsManager { MechanicsManager (const MechanicsManager&); ///< not implemented MechanicsManager& operator= (const MechanicsManager&); ///< not implemented public: MechanicsManager() {} virtual ~MechanicsManager() {} virtual void add (const MWWorld::Ptr& ptr) = 0; ///< Register an object for management virtual void remove (const MWWorld::Ptr& ptr, bool keepActive) = 0; ///< Deregister an object for management virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0; ///< Moves an object to a new cell virtual void drop (const MWWorld::CellStore *cellStore) = 0; ///< Deregister all objects in the given cell. virtual void setPlayerName (const std::string& name) = 0; ///< Set player name. virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) = 0; ///< Set player race. virtual void setPlayerBirthsign (const std::string& id) = 0; ///< Set player birthsign. virtual void setPlayerClass (const std::string& id) = 0; ///< Set player class to stock class. virtual void setPlayerClass (const ESM::Class& class_) = 0; ///< Set player class to custom class. virtual void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) = 0; virtual void rest(double hours, bool sleep) = 0; ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? virtual int getHoursToRest() const = 0; ///< Calculate how many hours the player needs to rest in order to be fully healed virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) = 0; ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp = true) = 0; ///< Calculate the diposition of an NPC toward the player. virtual int countDeaths (const std::string& id) const = 0; ///< Return the number of deaths for actors with the given ID. /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; /// Makes \a ptr fight \a target. Also shouts a combat taunt. virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; /// Removes an actor and its allies from combat with the actor's targets. virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; enum OffenseType { OT_Theft, // Taking items owned by an NPC or a faction you are not a member of OT_Assault, // Attacking a peaceful NPC OT_Murder, // Murdering a peaceful NPC OT_Trespassing, // Picking the lock of an owned door/chest OT_SleepingInOwnedBed, // Sleeping in a bed owned by an NPC or a faction you are not a member of OT_Pickpocket // Entering pickpocket mode, leaving it, and being detected. Any items stolen are a separate crime (Theft) }; /** * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @param victimAware Is the victim already aware of the crime? * If this parameter is false, it will be determined by a line-of-sight and awareness check. * @return was the crime seen? */ virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, const std::string& factionId="", int arg=0, bool victimAware=false) = 0; /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; /// Notify that actor was killed, add a murder bounty if applicable /// @note No-op for non-player attackers virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, int count, bool alarm = true) = 0; /// Utility to check if unlocking this object is illegal and calling commitCrime if so virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; /// Attempt sleeping in a bed. If this is illegal, call commitCrime. /// @return was it illegal, and someone saw you doing it? virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0; enum PersuasionType { PT_Admire, PT_Intimidate, PT_Taunt, PT_Bribe10, PT_Bribe100, PT_Bribe1000 }; virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) = 0; ///< Perform a persuasion action on NPC virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; ///< Forces an object to refresh its animation state. virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1, bool persist=false) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. /// /// \param mode 0 normal, 1 immediate start, 2 immediate loop /// \param count How many times the animation should be run /// \param persist Whether the animation state should be stored in saved games /// and persist after cell unload. /// \return Success or error virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; ///< Skip the animation for the given MW-reference for one frame. Calls to this function for /// references that are currently not in the scene should be ignored. virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; /// Save the current animation state of managed references to their RefData. virtual void persistAnimationStates() = 0; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; virtual bool toggleAI() = 0; virtual bool isAIActive() = 0; virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) = 0; virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) = 0; /// Check if there are actors in selected range virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0; ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ virtual std::vector getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::vector getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ virtual std::vector getActorsFighting(const MWWorld::Ptr& actor) = 0; virtual std::vector getEnemiesNearby(const MWWorld::Ptr& actor) = 0; /// Recursive versions of above methods virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) = 0; virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) = 0; virtual void playerLoaded() = 0; virtual int countSavedGameRecords() const = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; virtual void clear() = 0; virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; /// Resurrects the player if necessary virtual void resurrect(const MWWorld::Ptr& ptr) = 0; virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const = 0; virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; virtual void castSpell(const MWWorld::Ptr& ptr, const std::string& spellId, bool manualSpell) = 0; virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; virtual float getActorsProcessingRange() const = 0; virtual void notifyDied(const MWWorld::Ptr& actor) = 0; virtual bool onOpen(const MWWorld::Ptr& ptr) = 0; virtual void onClose(const MWWorld::Ptr& ptr) = 0; /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). /// virtual std::vector > getStolenItemOwners(const std::string& itemid) = 0; /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) = 0; virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; /// Turn actor into werewolf or normal form. virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. /// It only applies to the current form the NPC is in. virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; virtual int getGreetingTimer(const MWWorld::Ptr& ptr) const = 0; virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/rotationflags.hpp000066400000000000000000000004601445372753700235470ustar00rootroot00000000000000#ifndef GAME_MWBASE_ROTATIONFLAGS_H #define GAME_MWBASE_ROTATIONFLAGS_H namespace MWBase { using RotationFlags = unsigned short; enum RotationFlag : RotationFlags { RotationFlag_none = 0, RotationFlag_adjust = 1, RotationFlag_inverseOrder = 1 << 1, }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/scriptmanager.hpp000066400000000000000000000026561445372753700235430ustar00rootroot00000000000000#ifndef GAME_MWBASE_SCRIPTMANAGER_H #define GAME_MWBASE_SCRIPTMANAGER_H #include namespace Interpreter { class Context; } namespace Compiler { class Extensions; class Locals; } namespace MWScript { class GlobalScripts; } namespace MWBase { /// \brief Interface for script manager (implemented in MWScript) class ScriptManager { ScriptManager (const ScriptManager&); ///< not implemented ScriptManager& operator= (const ScriptManager&); ///< not implemented public: ScriptManager() {} virtual ~ScriptManager() {} virtual void clear() = 0; virtual bool run (const std::string& name, Interpreter::Context& interpreterContext) = 0; ///< Run the script with the given name (compile first, if not compiled yet) virtual bool compile (const std::string& name) = 0; ///< Compile script with the given namen /// \return Success? virtual std::pair compileAll() = 0; ///< Compile all scripts /// \return count, success virtual const Compiler::Locals& getLocals (const std::string& name) = 0; ///< Return locals for script \a name. virtual MWScript::GlobalScripts& getGlobalScripts() = 0; virtual const Compiler::Extensions& getExtensions() const = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/soundmanager.hpp000066400000000000000000000212711445372753700233610ustar00rootroot00000000000000#ifndef GAME_MWBASE_SOUNDMANAGER_H #define GAME_MWBASE_SOUNDMANAGER_H #include #include #include #include #include "../mwworld/ptr.hpp" #include "../mwsound/type.hpp" namespace MWWorld { class CellStore; } namespace MWSound { // Each entry excepts of MaxCount should be used only in one place enum BlockerType { VideoPlayback, MaxCount }; class Sound; class Stream; struct Sound_Decoder; typedef std::shared_ptr DecoderPtr; /* These must all fit together */ enum class PlayMode { Normal = 0, /* non-looping, affected by environment */ Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away * from the sound source, the sound is removed. * This is weird stuff but apparently how vanilla works for sounds * played by the PlayLoopSound family of script functions. Perhaps * we can make this cut off a more subtle fade later, but have to * be careful to not change the overall volume of areas by too * much. */ NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the * player is making it. */ NoScaling = 1<<4, /* Don't scale audio with simulation time */ NoEnvNoScaling = NoEnv | NoScaling, LoopNoEnv = Loop | NoEnv, LoopRemoveAtDistance = Loop | RemoveAtDistance }; // Used for creating a type mask for SoundManager::pauseSounds and resumeSounds inline int operator~(Type a) { return ~static_cast(a); } inline int operator&(Type a, Type b) { return static_cast(a) & static_cast(b); } inline int operator&(int a, Type b) { return a & static_cast(b); } inline int operator|(Type a, Type b) { return static_cast(a) | static_cast(b); } } namespace MWBase { using Sound = MWSound::Sound; using SoundStream = MWSound::Stream; /// \brief Interface for sound manager (implemented in MWSound) class SoundManager { SoundManager (const SoundManager&); ///< not implemented SoundManager& operator= (const SoundManager&); ///< not implemented protected: using PlayMode = MWSound::PlayMode; using Type = MWSound::Type; float mSimulationTimeScale = 1.0; public: SoundManager() {} virtual ~SoundManager() {} virtual void processChangedSettings(const std::set< std::pair >& settings) = 0; virtual void stopMusic() = 0; ///< Stops music if it's playing virtual void streamMusic(const std::string& filename) = 0; ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing virtual void playPlaylist(const std::string &playlist) = 0; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist virtual void playTitleMusic() = 0; ///< Start playing title music virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. virtual void say(const std::string& filename) = 0; ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. virtual bool sayActive(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; ///< Is actor not speaking? virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; ///< For scripting backward compatibility virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) = 0; ///< Stop an actor speaking virtual float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const = 0; ///< Check the currently playing say sound for this actor /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. virtual SoundStream *playTrack(const MWSound::DecoderPtr& decoder, Type type) = 0; ///< Play a 2D audio track, using a custom decoder. The caller is expected to call /// stopTrack with the returned handle when done. virtual void stopTrack(SoundStream *stream) = 0; ///< Stop the given audio track from playing virtual double getTrackTimeDelay(SoundStream *stream) = 0; ///< Retives the time delay, in seconds, of the audio track (must be a sound /// returned by \ref playTrack). Only intended to be called by the track /// decoder's read method. virtual Sound *playSound(std::string_view soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) = 0; ///< Play a sound, independently of 3D-position ///< @param offset Number of seconds into the sound to start playback. virtual Sound *playSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) = 0; ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. ///< @param offset Number of seconds into the sound to start playback. virtual Sound *playSound3D(const osg::Vec3f& initialPos, std::string_view soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) = 0; ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. virtual void stopSound(Sound *sound) = 0; ///< Stop the given sound from playing virtual void stopSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId) = 0; ///< Stop the given object from playing the given sound, virtual void stopSound3D(const MWWorld::ConstPtr &reference) = 0; ///< Stop the given object from playing all sounds. virtual void stopSound(const MWWorld::CellStore *cell) = 0; ///< Stop all sounds for the given cell. virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId, float duration) = 0; ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, std::string_view soundId) const = 0; ///< Is the given sound currently playing on the given object? /// If you want to check if sound played with playSound is playing, use empty Ptr virtual void pauseSounds(MWSound::BlockerType blocker, int types=int(Type::Mask)) = 0; ///< Pauses all currently playing sounds, including music. virtual void resumeSounds(MWSound::BlockerType blocker) = 0; ///< Resumes all previously paused sounds. virtual void pausePlayback() = 0; virtual void resumePlayback() = 0; virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) = 0; virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; void setSimulationTimeScale(float scale) { mSimulationTimeScale = scale; } float getSimulationTimeScale() const { return mSimulationTimeScale; } virtual void clear() = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/statemanager.hpp000066400000000000000000000054021445372753700233470ustar00rootroot00000000000000#ifndef GAME_MWSTATE_STATEMANAGER_H #define GAME_MWSTATE_STATEMANAGER_H #include #include namespace MWState { struct Slot; class Character; } namespace MWBase { /// \brief Interface for game state manager (implemented in MWState) class StateManager { public: enum State { State_NoGame, State_Ended, State_Running }; typedef std::list::const_iterator CharacterIterator; private: StateManager (const StateManager&); ///< not implemented StateManager& operator= (const StateManager&); ///< not implemented public: StateManager() {} virtual ~StateManager() {} virtual void requestQuit() = 0; virtual bool hasQuitRequest() const = 0; virtual void askLoadRecent() = 0; virtual State getState() const = 0; virtual void newGame (bool bypass = false) = 0; ///< Start a new game. /// /// \param bypass Skip new game mechanics. virtual void resumeGame() = 0; virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; virtual void saveGame (const std::string& description, const MWState::Slot *slot = nullptr) = 0; ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// /// \note Slot must belong to the current character. virtual void loadGame (const std::string& filepath) = 0; ///< Load a saved game directly from the given file path. This will search the CharacterManager /// for a Character containing this save file, and set this Character current if one was found. /// Otherwise, a new Character will be created. virtual void loadGame (const MWState::Character *character, const std::string& filepath) = 0; ///< Load a saved game file belonging to the given character. ///Simple saver, writes over the file if already existing /** Used for quick save and autosave **/ virtual void quickSave(std::string = "Quicksave")=0; ///Simple loader, loads the last saved file /** Used for quickload **/ virtual void quickLoad()=0; virtual MWState::Character *getCurrentCharacter () = 0; ///< @note May return null. virtual CharacterIterator characterBegin() = 0; ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned /// iterator. virtual CharacterIterator characterEnd() = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/windowmanager.hpp000066400000000000000000000343321445372753700235420ustar00rootroot00000000000000#ifndef GAME_MWBASE_WINDOWMANAGER_H #define GAME_MWBASE_WINDOWMANAGER_H #include #include #include #include #include #include #include "../mwgui/mode.hpp" #include namespace Loading { class Listener; } namespace Translation { class Storage; } namespace MyGUI { class Gui; class Widget; class UString; } namespace ESM { class ESMReader; class ESMWriter; struct CellId; } namespace MWMechanics { class AttributeValue; template class DynamicStat; class SkillValue; } namespace MWWorld { class CellStore; class Ptr; } namespace MWGui { class Layout; class Console; class SpellWindow; class TradeWindow; class TravelWindow; class SpellBuyingWindow; class ConfirmationDialog; class CountDialog; class ScrollWindow; class BookWindow; class InventoryWindow; class ContainerWindow; class DialogueWindow; class WindowModal; class JailScreen; class MessageBox; class PostProcessorHud; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, ShowInDialogueMode_Only, ShowInDialogueMode_Never }; struct TextColours; } namespace SFO { class CursorManager; } namespace MWBase { /// \brief Interface for widnow manager (implemented in MWGui) class WindowManager : public SDLUtil::WindowListener { WindowManager (const WindowManager&); ///< not implemented WindowManager& operator= (const WindowManager&); ///< not implemented public: typedef std::vector SkillList; WindowManager() {} virtual ~WindowManager() {} /// @note This method will block until the video finishes playing /// (and will continually update the window while doing so) virtual void playVideo(const std::string& name, bool allowSkipping, bool overrideSounds = true) = 0; virtual void setNewGame(bool newgame) = 0; virtual void pushGuiMode (MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0; virtual void pushGuiMode (MWGui::GuiMode mode) = 0; virtual void popGuiMode(bool noSound=false) = 0; virtual void removeGuiMode (MWGui::GuiMode mode, bool noSound=false) = 0; ///< can be anywhere in the stack virtual void goToJail(int days) = 0; virtual void updatePlayer() = 0; virtual MWGui::GuiMode getMode() const = 0; virtual bool containsMode(MWGui::GuiMode) const = 0; virtual bool isGuiMode() const = 0; virtual bool isConsoleMode() const = 0; virtual bool isPostProcessorHudVisible() const = 0; virtual void toggleVisible (MWGui::GuiWindow wnd) = 0; virtual void forceHide(MWGui::GuiWindow wnd) = 0; virtual void unsetForceHide(MWGui::GuiWindow wnd) = 0; /// Disallow all inventory mode windows virtual void disallowAll() = 0; /// Allow one or more windows virtual void allow (MWGui::GuiWindow wnd) = 0; virtual bool isAllowed (MWGui::GuiWindow wnd) const = 0; /// \todo investigate, if we really need to expose every single lousy UI element to the outside world virtual MWGui::InventoryWindow* getInventoryWindow() = 0; virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual const std::vector getActiveMessageBoxes() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; virtual void updateSpellWindow() = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; virtual void setConsoleMode(const std::string& mode) = 0; static constexpr std::string_view sConsoleColor_Default = "#FFFFFF"; static constexpr std::string_view sConsoleColor_Error = "#FF2222"; static constexpr std::string_view sConsoleColor_Success = "#FF00FF"; static constexpr std::string_view sConsoleColor_Info = "#AAAAAA"; virtual void printToConsole(const std::string& msg, std::string_view color) = 0; /// Set time left for the player to start drowning (update the drowning bar) /// @param time time left to start drowning /// @param maxTime how long we can be underwater (in total) until drowning starts virtual void setDrowningTimeLeft (float time, float maxTime) = 0; virtual void changeCell(const MWWorld::CellStore* cell) = 0; ///< change the active cell virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0; virtual void setCursorVisible(bool visible) = 0; virtual void setCursorActive(bool active) = 0; virtual void getMousePosition(int &x, int &y) = 0; virtual void getMousePosition(float &x, float &y) = 0; virtual void setDragDrop(bool dragDrop) = 0; virtual bool getWorldMouseOver() = 0; virtual float getScalingFactor() const = 0; virtual bool toggleFogOfWar() = 0; virtual bool toggleFullHelp() = 0; ///< show extra info in item tooltips (owner, script) virtual bool getFullHelp() const = 0; virtual void setActiveMap(int x, int y, bool interior) = 0; ///< set the indices of the map texture that should be used /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible) = 0; /// sets the visibility of the hud health/magicka/stamina bars virtual void setHMSVisibility(bool visible) = 0; /// sets the visibility of the hud minimap virtual void setMinimapVisibility(bool visible) = 0; virtual void setWeaponVisibility(bool visible) = 0; virtual void setSpellVisibility(bool visible) = 0; virtual void setSneakVisibility(bool visible) = 0; /// activate selected quick key virtual void activateQuickKey (int index) = 0; /// update activated quick key state (if action executing was delayed for some reason) virtual void updateActivatedQuickKey () = 0; virtual std::string getSelectedSpell() = 0; virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0; virtual const MWWorld::Ptr& getSelectedEnchantItem() const = 0; virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0; virtual const MWWorld::Ptr& getSelectedWeapon() const = 0; virtual int getFontHeight() const = 0; virtual void unsetSelectedSpell() = 0; virtual void unsetSelectedWeapon() = 0; virtual void showCrosshair(bool show) = 0; virtual bool getSubtitlesEnabled() = 0; virtual bool toggleHud() = 0; virtual void disallowMouse() = 0; virtual void allowMouse() = 0; virtual void notifyInputActionBound() = 0; virtual void addVisitedLocation(const std::string& name, int x, int y) = 0; /// Hides dialog and schedules dialog to be deleted. virtual void removeDialog(MWGui::Layout* dialog) = 0; ///Gracefully attempts to exit the topmost GUI mode /** No guarantee of actually closing the window **/ virtual void exitCurrentGuiMode() = 0; virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; /// Puts message into a queue to show on the next update. Thread safe alternative for messageBox. virtual void scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void staticMessageBox(const std::string& message) = 0; virtual void removeStaticMessageBox() = 0; virtual void interactiveMessageBox (const std::string& message, const std::vector& buttons = std::vector(), bool block=false) = 0; /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton() = 0; virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) = 0; /** * Fetches a GMST string from the store, if there is no setting with the given * ID or it is not a string the default string is returned. * * @param id Identifier for the GMST setting, e.g. "aName" * @param default Default value if the GMST setting cannot be used. */ virtual std::string getGameSettingString(const std::string &id, const std::string &default_) = 0; virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void executeInConsole (const std::string& path) = 0; virtual void enableRest() = 0; virtual bool getRestEnabled() = 0; virtual bool getJournalAllowed() = 0; virtual bool getPlayerSleeping() = 0; virtual void wakeUpPlayer() = 0; virtual void showSoulgemDialog (MWWorld::Ptr item) = 0; virtual void changePointer (const std::string& name) = 0; virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; virtual int getMessagesCount() const = 0; virtual const Translation::Storage& getTranslationDataStorage() const = 0; /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; virtual Loading::Listener* getLoadingScreen() = 0; /// Should the cursor be visible? virtual bool getCursorVisible() = 0; /// Clear all savegame-specific data virtual void clear() = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) = 0; virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; virtual int countSavedGameRecords() const = 0; /// Does the current stack of GUI-windows permit saving? virtual bool isSavingAllowed() const = 0; /// Send exit command to active Modal window virtual void exitCurrentModal() = 0; /// Sets the current Modal /** Used to send exit command to active Modal when Esc is pressed **/ virtual void addCurrentModal(MWGui::WindowModal* input) = 0; /// Removes the top Modal /** Used when one Modal adds another Modal \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; virtual void pinWindow (MWGui::GuiWindow window) = 0; virtual void toggleMaximized(MWGui::Layout *layout) = 0; /// Fade the screen in, over \a time seconds virtual void fadeScreenIn(const float time, bool clearQueue=true, float delay=0.f) = 0; /// Fade the screen out to black, over \a time seconds virtual void fadeScreenOut(const float time, bool clearQueue=true, float delay=0.f) = 0; /// Fade the screen to a specified percentage of black, over \a time seconds virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true, float delay=0.f) = 0; /// Darken the screen to a specified percentage virtual void setBlindness(const int percent) = 0; virtual void activateHitOverlay(bool interrupt=true) = 0; virtual void setWerewolfOverlay(bool set) = 0; virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; virtual void togglePostProcessorHud() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; /// Cycle to next or previous weapon virtual void cycleWeapon(bool next) = 0; virtual void playSound(const std::string& soundId, float volume = 1.f, float pitch = 1.f) = 0; virtual void addCell(MWWorld::CellStore* cell) = 0; virtual void removeCell(MWWorld::CellStore* cell) = 0; virtual void writeFog(MWWorld::CellStore* cell) = 0; virtual const MWGui::TextColours& getTextColours() = 0; virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) = 0; virtual bool injectKeyRelease(MyGUI::KeyCode key) = 0; void windowVisibilityChange(bool visible) override = 0; void windowResized(int x, int y) override = 0; void windowClosed() override = 0; virtual bool isWindowVisible() = 0; virtual void watchActor(const MWWorld::Ptr& ptr) = 0; virtual MWWorld::Ptr getWatchedActor() const = 0; virtual const std::string& getVersionDescription() const = 0; virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; virtual void asyncPrepareSaveMap() = 0; /// Sets the cull masks for all applicable views virtual void setCullMask(uint32_t mask) = 0; /// Same as viewer->getCamera()->getCullMask(), provided for consistency. virtual uint32_t getCullMask() = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwbase/world.hpp000066400000000000000000000756741445372753700220450ustar00rootroot00000000000000#ifndef GAME_MWBASE_WORLD_H #define GAME_MWBASE_WORLD_H #include "rotationflags.hpp" #include #include #include #include #include #include #include #include #include #include "../mwworld/ptr.hpp" #include "../mwworld/doorstate.hpp" #include "../mwworld/spellcaststate.hpp" #include "../mwrender/rendermode.hpp" namespace osg { class Vec3f; class Matrixf; class Quat; class Image; class Stats; } namespace Loading { class Listener; } namespace ESM { class ESMReader; class ESMWriter; struct Position; struct Cell; struct Class; struct Container; struct Creature; struct Potion; struct Spell; struct NPC; struct Armor; struct Weapon; struct Clothing; struct Enchantment; struct Book; struct EffectList; struct CreatureLevList; struct ItemLevList; struct TimeStamp; } namespace MWPhysics { class RayCastingResult; class RayCastingInterface; } namespace MWRender { class Animation; class Camera; class RenderingManager; class PostProcessor; } namespace MWMechanics { struct Movement; } namespace DetourNavigator { struct Navigator; struct AgentBounds; } namespace MWWorld { class CellStore; class Player; class LocalScripts; class TimeStamp; class ESMStore; class RefData; typedef std::vector > PtrMovementList; } namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) class World { World (const World&); ///< not implemented World& operator= (const World&); ///< not implemented public: struct DoorMarker { std::string name; float x, y; // world position ESM::CellId dest; }; World() {} virtual ~World() {} virtual void setRandomSeed(uint32_t seed) = 0; ///< \param seed The seed used when starting a new game. virtual void startNewGame (bool bypass) = 0; ///< \param bypass Bypass regular game start. virtual void clear() = 0; virtual int countSavedGameRecords() const = 0; virtual int countSavedGameCells() const = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; virtual void readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) = 0; virtual MWWorld::CellStore *getExterior (int x, int y) = 0; virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; virtual MWWorld::CellStore *getCell (const ESM::CellId& id) = 0; virtual bool isCellActive(MWWorld::CellStore* cell) const = 0; virtual void testExteriorCells() = 0; virtual void testInteriorCells() = 0; virtual void useDeathCamera() = 0; virtual void setWaterHeight(const float height) = 0; virtual bool toggleWater() = 0; virtual bool toggleWorld() = 0; virtual bool toggleBorders() = 0; virtual MWWorld::Player& getPlayer() = 0; virtual MWWorld::Ptr getPlayerPtr() = 0; virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0; virtual MWWorld::LocalScripts& getLocalScripts() = 0; virtual bool hasCellChanged() const = 0; ///< Has the set of active cells changed, since the last frame? virtual bool isCellExterior() const = 0; virtual bool isCellQuasiExterior() const = 0; virtual osg::Vec2f getNorthVector (const MWWorld::CellStore* cell) = 0; ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; ///< get a list of teleport door markers for a given cell, to be displayed on the local map virtual void setGlobalInt(std::string_view name, int value) = 0; ///< Set value independently from real type. virtual void setGlobalFloat(std::string_view name, float value) = 0; ///< Set value independently from real type. virtual int getGlobalInt(std::string_view name) const = 0; ///< Get value independently from real type. virtual float getGlobalFloat(std::string_view name) const = 0; ///< Get value independently from real type. virtual char getGlobalVariableType(std::string_view name) const = 0; ///< Return ' ', if there is no global variable with this name. virtual std::string getCellName (const MWWorld::CellStore *cell = nullptr) const = 0; ///< Return name of the cell. /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. virtual std::string getCellName(const ESM::Cell* cell) const = 0; virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts virtual MWWorld::Ptr getPtr (std::string_view name, bool activeOnly) = 0; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. virtual MWWorld::Ptr searchPtr (std::string_view name, bool activeOnly, bool searchInContainers = true) = 0; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; ///< Search is limited to the active cells. virtual MWWorld::Ptr searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum) = 0; virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) = 0; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. virtual void enable (const MWWorld::Ptr& ptr) = 0; virtual void disable (const MWWorld::Ptr& ptr) = 0; virtual void advanceTime (double hours, bool incremental = false) = 0; ///< Advance in-game time. virtual std::string getMonthName (int month = -1) const = 0; ///< Return name of month (-1: current month) virtual MWWorld::TimeStamp getTimeStamp() const = 0; ///< Return current in-game time and number of day since new game start. virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0; ///< Return current in-game date and time. virtual bool toggleSky() = 0; ///< \return Resulting mode virtual void changeWeather(const std::string& region, const unsigned int id) = 0; virtual int getCurrentWeather() const = 0; virtual int getNextWeather() const = 0; virtual float getWeatherTransition() const = 0; virtual unsigned int getNightDayMode() const = 0; virtual int getMasserPhase() const = 0; virtual int getSecundaPhase() const = 0; virtual void setMoonColour (bool red) = 0; virtual void modRegion(const std::string ®ionid, const std::vector &chances) = 0; virtual float getTimeScaleFactor() const = 0; virtual float getSimulationTimeScale() const = 0; virtual void setSimulationTimeScale(float scale) = 0; virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; ///< Move to interior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; ///< Move to exterior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. virtual void markCellAsUnchanged() = 0; virtual MWWorld::Ptr getFacedObject() = 0; ///< Return pointer to the object the player is looking at, if it is within activation range virtual float getDistanceToFacedObject() = 0; virtual float getMaxActivationDistance() const = 0; /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) = 0; virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying virtual void fixPosition () = 0; ///< Attempt to fix position so that the player is not stuck inside the geometry. /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0; ///< @return an updated Ptr virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr& ptr, const osg::Vec3f& vec, bool moveToActive) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale, bool force = false) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0; virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) = 0; ///< Place an object. Makes a copy of the Ptr. virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const = 0; ///< Convert cell numbers to position. virtual void queueMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. virtual void updateAnimatedCollisionShape(const MWWorld::Ptr &ptr) = 0; virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) = 0; ///< cast a Ray and return true if there is an object in the ray path. virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, bool ignorePlayer, bool ignoreActors) = 0; virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; virtual bool toggleCollisionMode() = 0; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. /// \return Resulting mode virtual bool toggleRenderMode (MWRender::RenderMode mode) = 0; ///< Toggle a render mode. ///< \return Resulting mode virtual const ESM::Potion *createRecord (const ESM::Potion& record) = 0; ///< Create a new record (of type potion) in the ESM store. /// \return pointer to created record virtual const ESM::Spell *createRecord (const ESM::Spell& record) = 0; ///< Create a new record (of type spell) in the ESM store. /// \return pointer to created record virtual const ESM::Class *createRecord (const ESM::Class& record) = 0; ///< Create a new record (of type class) in the ESM store. /// \return pointer to created record virtual const ESM::Cell *createRecord (const ESM::Cell& record) = 0; ///< Create a new record (of type cell) in the ESM store. /// \return pointer to created record virtual const ESM::NPC *createRecord(const ESM::NPC &record) = 0; ///< Create a new record (of type npc) in the ESM store. /// \return pointer to created record virtual const ESM::Armor *createRecord (const ESM::Armor& record) = 0; ///< Create a new record (of type armor) in the ESM store. /// \return pointer to created record virtual const ESM::Weapon *createRecord (const ESM::Weapon& record) = 0; ///< Create a new record (of type weapon) in the ESM store. /// \return pointer to created record virtual const ESM::Clothing *createRecord (const ESM::Clothing& record) = 0; ///< Create a new record (of type clothing) in the ESM store. /// \return pointer to created record virtual const ESM::Enchantment *createRecord (const ESM::Enchantment& record) = 0; ///< Create a new record (of type enchantment) in the ESM store. /// \return pointer to created record virtual const ESM::Book *createRecord (const ESM::Book& record) = 0; ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) = 0; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) = 0; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record virtual const ESM::Creature *createOverrideRecord (const ESM::Creature& record) = 0; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record virtual const ESM::NPC *createOverrideRecord (const ESM::NPC& record) = 0; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record virtual const ESM::Container *createOverrideRecord (const ESM::Container& record) = 0; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) = 0; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object /// @param number of objects to place virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; virtual bool isSwimming(const MWWorld::ConstPtr &object) const = 0; virtual bool isWading(const MWWorld::ConstPtr &object) const = 0; ///Is the head of the creature underwater? virtual bool isSubmerged(const MWWorld::ConstPtr &object) const = 0; virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const = 0; virtual bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const = 0; virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; virtual MWRender::Camera* getCamera() = 0; virtual void togglePOV(bool force = false) = 0; virtual bool isFirstPerson() const = 0; virtual bool isPreviewModeEnabled() const = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual bool vanityRotateCamera(float * rot) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void disableDeferredPreviewRotation() = 0; virtual void saveLoaded() = 0; virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door) = 0; /// update movement state of a non-teleport door as specified /// @param state see MWClass::setDoorState /// @note throws an exception when invoked on a teleport door virtual void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0; virtual void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) = 0; ///< get a list of actors standing on \a object virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is colliding with \a object virtual void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; ///< Apply a health difference to any actors standing on \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. virtual void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; ///< Apply a health difference to any actors colliding with \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. virtual float getWindSpeed() = 0; virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all containers in active cells owned by this Npc virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) = 0; virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; enum RestPermitted { Rest_Allowed = 0, Rest_OnlyWaiting = 1, Rest_PlayerIsInAir = 2, Rest_PlayerIsUnderwater = 3, Rest_EnemiesAreNearby = 4 }; /// check if the player is allowed to rest virtual RestPermitted canRest() const = 0; /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const = 0; virtual void reattachPlayerCamera() = 0; /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; virtual bool screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise virtual bool findExteriorPosition(const std::string &name, ESM::Position &pos) = 0; /// Find default position inside interior cell specified by name /// \return false if interior with given name not exists, true otherwise virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos) = 0; /// Enables or disables use of teleport spell effects (recall, intervention, etc). virtual void enableTeleporting(bool enable) = 0; /// Returns true if teleport spell effects are allowed. virtual bool isTeleportingEnabled() const = 0; /// Enables or disables use of levitation spell effect. virtual void enableLevitation(bool enable) = 0; /// Returns true if levitation spell effect is allowed. virtual bool isLevitationEnabled() const = 0; virtual bool getGodModeState() const = 0; virtual bool toggleGodMode() = 0; virtual bool toggleScripts() = 0; virtual bool getScriptsEnabled() const = 0; /** * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. * @param actor * @return Success or the failure condition. */ virtual MWWorld::SpellCastState startSpellCast (const MWWorld::Ptr& actor) = 0; virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) = 0; virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void updateProjectilesCasters() = 0; virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) const = 0; virtual const std::vector& getContentFiles() const = 0; virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; // Allow NPCs to use torches? virtual bool useTorches() const = 0; virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) = 0; enum DetectionType { Detect_Enchantment, Detect_Key, Detect_Creature }; /// List all references (filtered by \a type) detected by \a ptr. The range /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. /// @note This also works for references in containers. virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, DetectionType type) = 0; /// Update the value of some globals according to the world state, which may be used by dialogue entries. /// This should be called when initiating a dialogue. virtual void updateDialogueGlobals() = 0; /// Moves all stolen items from \a ptr to the closest evidence chest. virtual void confiscateStolenItems(const MWWorld::Ptr& ptr) = 0; virtual void goToJail () = 0; /// Spawn a random creature from a levelled list next to the player virtual void spawnRandomCreature(const std::string& 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 (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; /// @see MWWorld::WeatherManager::isInStorm virtual bool isInStorm() const = 0; /// @see MWWorld::WeatherManager::getStormDirection virtual osg::Vec3f getStormDirection() const = 0; /// Resets all actors in the current active cells to their original location within that cell. virtual void resetActors() = 0; virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor) const = 0; /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0; /// Return the distance between actor's weapon and target's collision box. virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0; virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; virtual bool isPlayerInJail() const = 0; virtual void rest(double hours) = 0; virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; virtual void rotateWorldObject (const MWWorld::Ptr& ptr, const osg::Quat& rotate) = 0; /// Return terrain height at \a worldPos position. virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; /// Return physical or rendering half extents of the given actor. virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const = 0; /// Export scene graph to a file and return the filename. /// \param ptr object to export scene graph for (if empty, export entire scene graph) virtual std::string exportSceneGraph(const MWWorld::Ptr& ptr) = 0; /// Preload VFX associated with this effect list virtual void preloadEffects(const ESM::EffectList* effectList) = 0; virtual DetourNavigator::Navigator* getNavigator() const = 0; virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; virtual void setNavMeshNumberToRender(const std::size_t value) = 0; virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; 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, const Misc::Span& ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; virtual std::vector getAll(const std::string& id) = 0; virtual Misc::Rng::Generator& getPrng() = 0; virtual MWRender::RenderingManager* getRenderingManager() = 0; virtual MWRender::PostProcessor* getPostProcessor() = 0; virtual void setActorActive(const MWWorld::Ptr& ptr, bool value) = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/000077500000000000000000000000001445372753700203555ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwclass/activator.cpp000066400000000000000000000164601445372753700230640ustar00rootroot00000000000000#include "activator.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/action.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/nullaction.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/vismask.hpp" #include "../mwgui/tooltips.hpp" #include "../mwmechanics/npcstats.hpp" #include "classmodel.hpp" namespace MWClass { Activator::Activator() : MWWorld::RegisteredClass(ESM::Activator::sRecordId) { } void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, true); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { insertObjectPhysics(ptr, model, rotation, physics); } void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } bool Activator::isActivator() const { return true; } bool Activator::useAnim() const { return true; } std::string Activator::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } std::string Activator::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const { return !getName(ptr).empty(); } MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::unique_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfActivator", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } return std::make_unique(); } MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const { const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); std::string creatureId; const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature &iter : store.get()) { if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel, vfs))) { creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId; break; } } int type = getSndGenTypeFromName(name); std::vector fallbacksounds; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!creatureId.empty()) { std::vector sounds; for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) { if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) sounds.push_back(&*sound); if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); } if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } else { // The activator doesn't have a corresponding creature ID, but we can try to use the defaults for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } return std::string(); } int Activator::getSndGenTypeFromName(const std::string &name) { if (name == "left") return ESM::SoundGenerator::LeftFoot; if (name == "right") return ESM::SoundGenerator::RightFoot; if (name == "swimleft") return ESM::SoundGenerator::SwimLeft; if (name == "swimright") return ESM::SoundGenerator::SwimRight; if (name == "moan") return ESM::SoundGenerator::Moan; if (name == "roar") return ESM::SoundGenerator::Roar; if (name == "scream") return ESM::SoundGenerator::Scream; if (name == "land") return ESM::SoundGenerator::Land; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } } openmw-openmw-0.48.0/apps/openmw/mwclass/activator.hpp000066400000000000000000000043411445372753700230640ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ACTIVATOR_H #define GAME_MWCLASS_ACTIVATOR_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Activator final : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Activator(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; static int getSndGenTypeFromName(const std::string &name); public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool useAnim() const override; ///< Whether or not to use animated variant of model (default false) bool isActivator() const override; std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/actor.cpp000066400000000000000000000065741445372753700222050ustar00rootroot00000000000000#include "actor.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" namespace MWClass { void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { physics.addActor(ptr, model); if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } bool Actor::useAnim() const { return true; } void Actor::block(const MWWorld::Ptr &ptr) const { const MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end()) return; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); switch (shield->getClass().getEquipmentSkill(*shield)) { case ESM::Skill::LightArmor: sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); break; case ESM::Skill::MediumArmor: sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); break; case ESM::Skill::HeavyArmor: sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); break; default: return; } } osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const { MWMechanics::Movement &movement = getMovementSettings(ptr); osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); movement.mRotation[0] = 0.0f; movement.mRotation[1] = 0.0f; movement.mRotation[2] = 0.0f; return vec; } float Actor::getEncumbrance(const MWWorld::Ptr& ptr) const { float weight = getContainerStore(ptr).getWeight(); const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); if (ptr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->getGodModeState()) weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); return (weight < 0) ? 0.0f : weight; } bool Actor::allowTelekinesis(const MWWorld::ConstPtr &ptr) const { return false; } bool Actor::isActor() const { return true; } float Actor::getCurrentSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); float moveSpeed = this->getMaxSpeed(ptr) * movementSettings.mSpeedFactor; if (movementSettings.mIsStrafing) moveSpeed *= 0.75f; return moveSpeed; } } openmw-openmw-0.48.0/apps/openmw/mwclass/actor.hpp000066400000000000000000000045141445372753700222020ustar00rootroot00000000000000#ifndef GAME_MWCLASS_MOBILE_H #define GAME_MWCLASS_MOBILE_H #include "../mwworld/class.hpp" #include "../mwmechanics/magiceffects.hpp" #include #include namespace ESM { struct GameSetting; } namespace MWClass { /// \brief Class holding functionality common to Creature and NPC class Actor : public MWWorld::Class { protected: explicit Actor(unsigned type) : Class(type) {} template float getSwimSpeedImpl(const MWWorld::Ptr& ptr, const GMST& gmst, const MWMechanics::MagicEffects& mageffects, float baseSpeed) const { return baseSpeed * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) * (gmst.fSwimRunBase->mValue.getFloat() + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); } public: ~Actor() override = default; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; void block(const MWWorld::Ptr &ptr) const override; osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override; ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. float getEncumbrance(const MWWorld::Ptr& ptr) const override; ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const override; ///< Return whether this class of object can be activated with telekinesis bool isActor() const override; /// Return current movement speed. float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented Actor(const Actor&) = delete; Actor& operator= (const Actor&) = delete; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/apparatus.cpp000066400000000000000000000101261445372753700230610ustar00rootroot00000000000000#include "apparatus.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionalchemy.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" #include "classmodel.hpp" namespace MWClass { Apparatus::Apparatus() : MWWorld::RegisteredClass(ESM::Apparatus::sRecordId) { } void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Apparatus::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Apparatus::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Apparatus::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Up"); } std::string Apparatus::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Down"); } std::string Apparatus::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::unique_ptr Apparatus::use (const MWWorld::Ptr& ptr, bool force) const { return std::make_unique(force); } MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } bool Apparatus::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Apparatus) != 0; } float Apparatus::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/apparatus.hpp000066400000000000000000000044401445372753700230700ustar00rootroot00000000000000#ifndef GAME_MWCLASS_APPARATUS_H #define GAME_MWCLASS_APPARATUS_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Apparatus : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Apparatus(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: float getWeight (const MWWorld::ConstPtr& ptr) const override; void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. 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. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/armor.cpp000066400000000000000000000323431445372753700222060ustar00rootroot00000000000000#include "armor.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" #include "classmodel.hpp" namespace MWClass { Armor::Armor() : MWWorld::RegisteredClass(ESM::Armor::sRecordId) { } void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Armor::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } bool Armor::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } int Armor::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } std::string Armor::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Armor::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; const int size = 11; static const int sMapping[size][2] = { { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet }, { ESM::Armor::Cuirass, MWWorld::InventoryStore::Slot_Cuirass }, { ESM::Armor::LPauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, { ESM::Armor::RPauldron, MWWorld::InventoryStore::Slot_RightPauldron }, { ESM::Armor::Greaves, MWWorld::InventoryStore::Slot_Greaves }, { ESM::Armor::Boots, MWWorld::InventoryStore::Slot_Boots }, { ESM::Armor::LGauntlet, MWWorld::InventoryStore::Slot_LeftGauntlet }, { ESM::Armor::RGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }, { ESM::Armor::Shield, MWWorld::InventoryStore::Slot_CarriedLeft }, { ESM::Armor::LBracer, MWWorld::InventoryStore::Slot_LeftGauntlet }, { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet } }; for (int i=0; imBase->mData.mType) { slots_.push_back (int (sMapping[i][1])); break; } return std::make_pair (slots_, false); } int Armor::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); std::string typeGmst; switch (ref->mBase->mData.mType) { case ESM::Armor::Helmet: typeGmst = "iHelmWeight"; break; case ESM::Armor::Cuirass: typeGmst = "iCuirassWeight"; break; case ESM::Armor::LPauldron: case ESM::Armor::RPauldron: typeGmst = "iPauldronWeight"; break; case ESM::Armor::Greaves: typeGmst = "iGreavesWeight"; break; case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break; case ESM::Armor::LGauntlet: case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break; case ESM::Armor::Shield: typeGmst = "iShieldWeight"; break; case ESM::Armor::LBracer: case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break; } if (typeGmst.empty()) return -1; const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); float iWeight = floor(gmst.find(typeGmst)->mValue.getFloat()); float epsilon = 0.0005f; if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::LightArmor; if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::MediumArmor; else return ESM::Skill::HeavyArmor; } int Armor::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) return std::string("Item Armor Light Up"); else if (es == ESM::Skill::MediumArmor) return std::string("Item Armor Medium Up"); else return std::string("Item Armor Heavy Up"); } std::string Armor::getDownSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) return std::string("Item Armor Light Down"); else if (es == ESM::Skill::MediumArmor) return std::string("Item Armor Medium Down"); else return std::string("Item Armor Heavy Down"); } std::string Armor::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; // get armor type string (light/medium/heavy) std::string typeText; if (ref->mBase->mData.mWeight == 0) { // no type } else { int armorType = getEquipmentSkill(ptr); if (armorType == ESM::Skill::LightArmor) typeText = "#{sLight}"; else if (armorType == ESM::Skill::MediumArmor) typeText = "#{sMedium}"; else typeText = "#{sHeavy}"; } text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(static_cast(getEffectiveArmorRating(ptr, MWMechanics::getPlayer()))); int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); if (typeText != "") text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); info.text = text; return info; } std::string Armor::getEnchantment (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } std::string Armor::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Armor newItem = *ref->mBase; newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); return record->mId; } float Armor::getEffectiveArmorRating(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &actor) const { const MWWorld::LiveCellRef *ref = ptr.get(); int armorSkillType = getEquipmentSkill(ptr); float armorSkill = actor.getClass().getSkill(actor, armorSkillType); const MWBase::World *world = MWBase::Environment::get().getWorld(); int iBaseArmorSkill = world->getStore().get().find("iBaseArmorSkill")->mValue.getInteger(); if(ref->mBase->mData.mWeight == 0) return ref->mBase->mData.mArmor; else return ref->mBase->mData.mArmor * armorSkill / static_cast(iBaseArmorSkill); } std::pair Armor::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); if (getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // slots that this item can be equipped in std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); if (npc.getClass().isNpc()) { std::string npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); if(race->mData.mFlags & ESM::Race::Beast) { std::vector parts = ptr.get()->mBase->mParts.mParts; for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { if((*itr).mPart == ESM::PRT_Head) return std::make_pair(0, "#{sNotifyMessage13}"); if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) return std::make_pair(0, "#{sNotifyMessage14}"); } } } for (std::vector::const_iterator slot=slots_.first.begin(); slot!=slots_.first.end(); ++slot) { // If equipping a shield, check if there's a twohanded weapon conflicting with it if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != invStore.end() && weapon->getType() == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded) return std::make_pair(3,""); } return std::make_pair(1,""); } } return std::make_pair(1,""); } std::unique_ptr Armor::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Armor::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } int Armor::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } bool Armor::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Armor) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Armor::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/armor.hpp000066400000000000000000000101701445372753700222050ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ARMOR_H #define GAME_MWCLASS_ARMOR_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Armor : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Armor(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: float getWeight (const MWWorld::ConstPtr& ptr) const override; void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; ///< \return Item health data available? int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; ///< Return item max health or throw an exception, if class does not have item health std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. 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. int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n /// Second item in the pair specifies the error message std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/bodypart.cpp000066400000000000000000000022271445372753700227100ustar00rootroot00000000000000#include "bodypart.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" #include "../mwworld/cellstore.hpp" #include "classmodel.hpp" namespace MWClass { BodyPart::BodyPart() : MWWorld::RegisteredClass(ESM::BodyPart::sRecordId) { } MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } void BodyPart::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string &model, MWRender::RenderingInterface &renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); } bool BodyPart::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } } openmw-openmw-0.48.0/apps/openmw/mwclass/bodypart.hpp000066400000000000000000000017511445372753700227160ustar00rootroot00000000000000#ifndef GAME_MWCLASS_BODYPART_H #define GAME_MWCLASS_BODYPART_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class BodyPart : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; BodyPart(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/book.cpp000066400000000000000000000131561445372753700220210ustar00rootroot00000000000000#include "book.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionread.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" #include "../mwmechanics/npcstats.hpp" #include "classmodel.hpp" namespace MWClass { Book::Book() : MWWorld::RegisteredClass(ESM::Book::sRecordId) { } void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Book::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Book::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } return std::make_unique(ptr); } std::string Book::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Book::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Up"); } std::string Book::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Down"); } std::string Book::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.enchant = ref->mBase->mEnchant; info.text = text; return info; } std::string Book::getEnchantment (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } std::string Book::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Book newItem = *ref->mBase; newItem.mId.clear(); newItem.mName=newName; newItem.mData.mIsScroll = 1; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); return record->mId; } std::unique_ptr Book::use (const MWWorld::Ptr& ptr, bool force) const { return std::make_unique(ptr); } MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } int Book::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } bool Book::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Books) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Book::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/book.hpp000066400000000000000000000054541445372753700220300ustar00rootroot00000000000000#ifndef GAME_MWCLASS_BOOK_H #define GAME_MWCLASS_BOOK_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Book : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Book(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr 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. int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/classes.cpp000066400000000000000000000023731445372753700225230ustar00rootroot00000000000000#include "classes.hpp" #include "activator.hpp" #include "creature.hpp" #include "npc.hpp" #include "weapon.hpp" #include "armor.hpp" #include "potion.hpp" #include "apparatus.hpp" #include "book.hpp" #include "clothing.hpp" #include "container.hpp" #include "door.hpp" #include "ingredient.hpp" #include "creaturelevlist.hpp" #include "itemlevlist.hpp" #include "light.hpp" #include "lockpick.hpp" #include "misc.hpp" #include "probe.hpp" #include "repair.hpp" #include "static.hpp" #include "bodypart.hpp" namespace MWClass { void registerClasses() { Activator::registerSelf(); Creature::registerSelf(); Npc::registerSelf(); Weapon::registerSelf(); Armor::registerSelf(); Potion::registerSelf(); Apparatus::registerSelf(); Book::registerSelf(); Clothing::registerSelf(); Container::registerSelf(); Door::registerSelf(); Ingredient::registerSelf(); CreatureLevList::registerSelf(); ItemLevList::registerSelf(); Light::registerSelf(); Lockpick::registerSelf(); Miscellaneous::registerSelf(); Probe::registerSelf(); Repair::registerSelf(); Static::registerSelf(); BodyPart::registerSelf(); } } openmw-openmw-0.48.0/apps/openmw/mwclass/classes.hpp000066400000000000000000000002351445372753700225230ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CLASSES_H #define GAME_MWCLASS_CLASSES_H namespace MWClass { void registerClasses(); ///< register all known classes } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/classmodel.hpp000066400000000000000000000013041445372753700232120ustar00rootroot00000000000000#ifndef OPENMW_MWCLASS_CLASSMODEL_H #define OPENMW_MWCLASS_CLASSMODEL_H #include "../mwbase/environment.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/livecellref.hpp" #include #include #include namespace MWClass { template std::string getClassModel(const MWWorld::ConstPtr& ptr) { const MWWorld::LiveCellRef *ref = ptr.get(); if (!ref->mBase->mModel.empty()) return Misc::ResourceHelpers::correctMeshPath(ref->mBase->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); return {}; } } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/clothing.cpp000066400000000000000000000216741445372753700227020ustar00rootroot00000000000000#include "clothing.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Clothing::Clothing() : MWWorld::RegisteredClass(ESM::Clothing::sRecordId) { } void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Clothing::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Clothing::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; if (ref->mBase->mData.mType==ESM::Clothing::Ring) { slots_.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); slots_.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); } else { const int size = 9; static const int sMapping[size][2] = { { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Shirt }, { ESM::Clothing::Belt, MWWorld::InventoryStore::Slot_Belt }, { ESM::Clothing::Robe, MWWorld::InventoryStore::Slot_Robe }, { ESM::Clothing::Pants, MWWorld::InventoryStore::Slot_Pants }, { ESM::Clothing::Shoes, MWWorld::InventoryStore::Slot_Boots }, { ESM::Clothing::LGlove, MWWorld::InventoryStore::Slot_LeftGauntlet }, { ESM::Clothing::RGlove, MWWorld::InventoryStore::Slot_RightGauntlet }, { ESM::Clothing::Skirt, MWWorld::InventoryStore::Slot_Skirt }, { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet } }; for (int i=0; imBase->mData.mType) { slots_.push_back (int (sMapping[i][1])); break; } } return std::make_pair (slots_, false); } int Clothing::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType==ESM::Clothing::Shoes) return ESM::Skill::Unarmored; return -1; } int Clothing::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { return std::string("Item Ring Up"); } return std::string("Item Clothes Up"); } std::string Clothing::getDownSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { return std::string("Item Ring Down"); } return std::string("Item Clothes Down"); } std::string Clothing::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); info.text = text; return info; } std::string Clothing::getEnchantment (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } std::string Clothing::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Clothing newItem = *ref->mBase; newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); return record->mId; } std::pair Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); if (npc.getClass().isNpc()) { std::string npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); if(race->mData.mFlags & ESM::Race::Beast) { std::vector parts = ptr.get()->mBase->mParts.mParts; for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { if((*itr).mPart == ESM::PRT_Head) return std::make_pair(0, "#{sNotifyMessage13}"); if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) return std::make_pair(0, "#{sNotifyMessage15}"); } } } return std::make_pair (1, ""); } std::unique_ptr Clothing::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Clothing::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } int Clothing::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } bool Clothing::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Clothing) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Clothing::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/clothing.hpp000066400000000000000000000071641445372753700227050ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CLOTHING_H #define GAME_MWCLASS_CLOTHING_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Clothing : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Clothing(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. 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. int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/container.cpp000066400000000000000000000263171445372753700230540ustar00rootroot00000000000000#include "container.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/actionharvest.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontrap.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/inventory.hpp" #include "classmodel.hpp" namespace MWClass { ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max(), prng); // setting ownership not needed, since taking items from a container inherits the // container's owner automatically mStore.fillNonRandom(container.mInventory, "", seed); } ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory) { mStore.readState(inventory); } ContainerCustomData& ContainerCustomData::asContainerCustomData() { return *this; } const ContainerCustomData& ContainerCustomData::asContainerCustomData() const { return *this; } Container::Container() : MWWorld::RegisteredClass(ESM::Container::sRecordId) , mHarvestEnabled(Settings::Manager::getBool("graphic herbalism", "Game")) { } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { MWWorld::LiveCellRef *ref = ptr.get(); // store ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell())); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } } bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const { if (!mHarvestEnabled) return false; const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation == nullptr) return false; return animation->canBeHarvested(); } void Container::respawn(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mFlags & ESM::Container::Respawn) { // Container was not touched, there is no need to modify its content. if (ptr.getRefData().getCustomData() == nullptr) return; MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(nullptr); } } void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, true); } } void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { insertObjectPhysics(ptr, model, rotation, physics); } void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } bool Container::useAnim() const { return true; } std::unique_ptr Container::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::make_unique(); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfContainer", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } const std::string lockedSound = "LockedChest"; const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; std::string keyName; const std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { MWWorld::Ptr keyPtr = invStore.search(keyId); if (!keyPtr.isEmpty()) { hasKey = true; keyName = keyPtr.getClass().getName(keyPtr); } } if (isLocked && hasKey) { MWBase::Environment::get().getWindowManager ()->messageBox (keyName + " #{sKeyUsed}"); ptr.getCellRef().unlock(); // using a key disarms the trap if(isTrapped) { ptr.getCellRef().setTrap(""); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); isTrapped = false; } } if (!isLocked || hasKey) { if(!isTrapped) { if (canBeHarvested(ptr)) { return std::make_unique(ptr); } return std::make_unique(ptr); } else { // Activate trap std::unique_ptr action = std::make_unique(ptr.getCellRef().getTrap(), ptr); action->setSound(trapActivationSound); return action; } } else { std::unique_ptr action = std::make_unique(std::string(), ptr); action->setSound(lockedSound); return action; } } std::string Container::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); auto& data = ptr.getRefData().getCustomData()->asContainerCustomData(); data.mStore.mPtr = ptr; return data.mStore; } std::string Container::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems(); return true; } MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); std::string text; int lockLevel = ptr.getCellRef().getLockLevel(); if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(lockLevel); else if (lockLevel < 0) text += "\n#{sUnlocked}"; if (ptr.getCellRef().getTrap() != "") text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "stolen_goods")) text += "\nYou can not use evidence chests"; } info.text = text; return info; } float Container::getCapacity (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mWeight; } float Container::getEncumbrance (const MWWorld::Ptr& ptr) const { return getContainerStore (ptr).getWeight(); } bool Container::canLock(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return !(ref->mBase->mFlags & ESM::Container::Organic); } void Container::modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const { MWMechanics::modifyBaseInventory(containerId, itemId, amount); } MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; const ESM::ContainerState& containerState = state.asContainerState(); ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory)); } void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); if (!customData.mStore.isResolved()) { state.mHasCustomState = false; return; } ESM::ContainerState& containerState = state.asContainerState(); customData.mStore.writeState (containerState.mInventory); } } openmw-openmw-0.48.0/apps/openmw/mwclass/container.hpp000066400000000000000000000076731445372753700230650ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H #include "../mwworld/registeredclass.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" namespace ESM { struct Container; struct InventoryState; } namespace MWClass { class ContainerCustomData : public MWWorld::TypedCustomData { MWWorld::ContainerStore mStore; public: ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::InventoryState& inventory); ContainerCustomData& asContainerCustomData() override; const ContainerCustomData& asContainerCustomData() const override; friend class Container; }; class Container : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; const bool mHarvestEnabled; Container(); void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) 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. MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const override; ///< Return container store std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr float getCapacity (const MWWorld::Ptr& ptr) const override; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. float getEncumbrance (const MWWorld::Ptr& ptr) const override; ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. bool canLock(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. void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. void respawn (const MWWorld::Ptr& ptr) const override; std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool useAnim() const override; void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/creature.cpp000066400000000000000000001075101445372753700226770ustar00rootroot00000000000000#include "creature.hpp" #include #include #include #include #include #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturecustomdataresetter.hpp" #include "../mwmechanics/aisetting.hpp" #include "../mwmechanics/inventory.hpp" #include "../mwmechanics/setbaseaisetting.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/localscripts.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" #include "../mwgui/tooltips.hpp" #include "classmodel.hpp" namespace { bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask) { return (ptr.get()->mBase->mFlags & bitMask) != 0; } } namespace MWClass { class CreatureCustomData : public MWWorld::TypedCustomData { public: MWMechanics::CreatureStats mCreatureStats; std::unique_ptr mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; CreatureCustomData() = default; CreatureCustomData(const CreatureCustomData& other); CreatureCustomData(CreatureCustomData&& other) = default; CreatureCustomData& asCreatureCustomData() override { return *this; } const CreatureCustomData& asCreatureCustomData() const override { return *this; } }; CreatureCustomData::CreatureCustomData(const CreatureCustomData& other) : mCreatureStats(other.mCreatureStats), mContainerStore(other.mContainerStore->clone()), mMovement(other.mMovement) { } Creature::Creature() : MWWorld::RegisteredClass(ESM::Creature::sRecordId) { } const Creature::GMST& Creature::getGmst() { static const GMST staticGmst = [] { GMST gmst; const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); gmst.fMinFlySpeed = store.find("fMinFlySpeed"); gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); gmst.fSwimRunBase = store.find("fSwimRunBase"); gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); gmst.fKnockDownMult = store.find("fKnockDownMult"); gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); return gmst; } (); return staticGmst; } void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { auto tempData = std::make_unique(); CreatureCustomData* data = tempData.get(); MWMechanics::CreatureCustomDataResetter resetter {ptr}; ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); // creature stats data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence); data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower); data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility); data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed); data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance); data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality); data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck); data->mCreatureStats.setHealth(static_cast(ref->mBase->mData.mHealth)); data->mCreatureStats.setMagicka(static_cast(ref->mBase->mData.mMana)); data->mCreatureStats.setFatigue(static_cast(ref->mBase->mData.mFatigue)); data->mCreatureStats.setLevel(ref->mBase->mData.mLevel); data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Hello, ref->mBase->mAiData.mHello); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Fight, ref->mBase->mAiData.mFight); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Alarm, ref->mBase->mAiData.mAlarm); // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); // spells bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId); if (!spellsInitialised) data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory bool hasInventory = hasInventoryStore(ptr); if (hasInventory) data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); resetter.mPtr = {}; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); if (hasInventory) getInventoryStore(ptr).autoEquip(ptr); } } void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { std::string model = getModel(ptr); if (!model.empty()) models.push_back(model); // FIXME: use const version of InventoryStore functions once they are available if (hasInventoryStore(ptr)) { const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); if (equipped != invStore.end()) { model = equipped->getClass().getModel(*equipped); if (!model.empty()) models.push_back(model); } } } } std::string Creature::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; } void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const { MWWorld::LiveCellRef *ref = ptr.get(); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::CreatureStats &stats = getCreatureStats(ptr); if (stats.getDrawState() != MWMechanics::DrawState::Weapon) return; // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; if (hasInventoryStore(ptr)) { MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) weapon = *weaponslot; } MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); float dist = gmst.find("fCombatDistance")->mValue.getFloat(); if (!weapon.isEmpty()) dist *= weapon.get()->mBase->mData.mReach; // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; stats.getAiSequence().getCombatTargets(targetActors); std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); if (result.first.isEmpty()) return; // Didn't hit anything MWWorld::Ptr victim = result.first; if (!victim.getClass().isActor()) return; // Can't hit non-actors osg::Vec3f hitPosition (result.second); float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if(Misc::Rng::roll0to99(prng) >= hitchance) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } int min,max; switch (type) { case 0: min = ref->mBase->mData.mAttack[0]; max = ref->mBase->mData.mAttack[1]; break; case 1: min = ref->mBase->mData.mAttack[2]; max = ref->mBase->mData.mAttack[3]; break; case 2: default: min = ref->mBase->mData.mAttack[4]; max = ref->mBase->mData.mAttack[5]; break; } float damage = min + (max - min) * attackStrength; bool healthdmg = true; if (!weapon.isEmpty()) { const unsigned char *attack = nullptr; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; else if(type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash; else if(type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust; if(attack) { damage = attack[0] + ((attack[1]-attack[0])*attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); } // Apply "On hit" enchanted weapons MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); } else if (isBipedal(ptr)) { MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; MWMechanics::diseaseContact(victim, ptr); victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } 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) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) stats.setAttacked(true); // Self defense bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. // No retaliation for totally static creatures (they have no movement or attacks anyway) if (isMobile(ptr) && !attacker.isEmpty()) setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor if ((stats.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) stats.setHitAttemptActorId(statsAttacker.getActorId()); // Next handle the attacking actor if ((statsAttacker.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) statsAttacker.setHitAttemptActorId(stats.getActorId()); } if (!object.isEmpty()) stats.setLastHitAttemptObject(object.getCellRef().getRefId()); if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ if(!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } if (!successful) { // Missed if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } if (!object.isEmpty()) stats.setLastHitObject(object.getCellRef().getRefId()); if (damage < 0.001f) damage = 0; if (damage > 0.f) { if (!attacker.isEmpty()) { // Check for knockdown float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * 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)) 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, "Health Damage", 1.0f, 1.0f); MWMechanics::DynamicStat health(stats.getHealth()); health.setCurrent(health.getCurrent() - damage); stats.setHealth(health); } else { MWMechanics::DynamicStat fatigue(stats.getFatigue()); fatigue.setCurrent(fatigue.getCurrent() - damage, true); stats.setFatigue(fatigue); } } } std::unique_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfCreature", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if(stats.isDead()) { bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) return std::make_unique(ptr); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::make_unique(ptr); } else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique(ptr); return std::make_unique(); } MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const { if (hasInventoryStore(ptr)) return dynamic_cast(getContainerStore(ptr)); else throw std::runtime_error("this creature has no inventory store"); } bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const { return isFlagBitSet(ptr, ESM::Creature::Weapon); } std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Essential); } float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) return 0.f; const GMST& gmst = getGmst(); const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); float moveSpeed; if(getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(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; } else if(world->isSwimming(ptr)) moveSpeed = getSwimSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); return moveSpeed; } MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; } bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) return true; const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) return true; return !customData.mCreatureStats.getAiSequence().isInCombat(); } MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); info.text = text; return info; } float Creature::getArmorRating (const MWWorld::Ptr& ptr) const { // Equipment armor rating is deliberately ignored. return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); } float Creature::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5; } int Creature::getServices(const MWWorld::ConstPtr &actor) const { return actor.get()->mBase->mAiData.mServices; } bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const { const MWWorld::LiveCellRef* ref = actor.get(); return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const { int type = getSndGenTypeFromName(ptr, name); if (type < 0) return std::string(); std::vector sounds; std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto sound = store.get().begin(); while (sound != store.get().end()) { if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ourId, sound->mCreature)) sounds.push_back(&*sound); if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); ++sound; } if (sounds.empty()) { const std::string model = getModel(ptr); if (!model.empty()) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature &creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(creature.mModel, vfs))) { const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); while (sound != store.get().end()) { if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(fallbackId, sound->mCreature)) sounds.push_back(&*sound); ++sound; } break; } } } } auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; return std::string(); } MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, ESM::Creature::Bipedal); } bool Creature::canFly(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, ESM::Creature::Flies); } bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") { MWBase::World *world = MWBase::Environment::get().getWorld(); if(world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimLeft; if(world->isOnGround(ptr)) return ESM::SoundGenerator::LeftFoot; return -1; } if(name == "right") { MWBase::World *world = MWBase::Environment::get().getWorld(); if(world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimRight; if(world->isOnGround(ptr)) return ESM::SoundGenerator::RightFoot; return -1; } if(name == "swimleft") return ESM::SoundGenerator::SwimLeft; if(name == "swimright") return ESM::SoundGenerator::SwimRight; if(name == "moan") return ESM::SoundGenerator::Moan; if(name == "roar") return ESM::SoundGenerator::Roar; if(name == "scream") return ESM::SoundGenerator::Scream; if(name == "land") return ESM::SoundGenerator::Land; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const { MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get().find(skill); switch (skillRecord->mData.mSpecialization) { case ESM::Class::Combat: return ref->mBase->mData.mCombat; case ESM::Class::Magic: return ref->mBase->mData.mMagic; case ESM::Class::Stealth: return ref->mBase->mData.mStealth; default: throw std::runtime_error("invalid specialisation"); } } int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const { return ptr.get()->mBase->mBloodType; } void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; const ESM::CreatureState& creatureState = state.asCreatureState(); if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { if (creatureState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else { // Create a CustomData, but don't fill it from ESM records (not needed) auto data = std::make_unique(); if (hasInventoryStore(ptr)) data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); ptr.getRefData().setCustomData (std::move(data)); } } } else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->readState (creatureState.mInventory); bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); if(spellsInitialised) customData.mCreatureStats.getSpells().clear(); customData.mCreatureStats.readState (creatureState.mCreatureStats); } void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); if (ptr.getRefData().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; return; } ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->writeState (creatureState.mInventory); customData.mCreatureStats.writeState (creatureState.mCreatureStats); } int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mData.mGold; } void Creature::respawn(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) return; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (isFlagBitSet(ptr, ESM::Creature::Respawn) && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) { ptr.getRefData().setCount(1); const std::string& script = getScript(ptr); if(!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const { const MWWorld::LiveCellRef *ref = ptr.get(); scale *= ref->mBase->mScale; } void Creature::setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const { MWMechanics::setBaseAISetting(id, setting, value); } void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const { MWMechanics::modifyBaseInventory(actorId, itemId, amount); } float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const GMST& gmst = getGmst(); return gmst.fMinWalkSpeedCreature->mValue.getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); } float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const { return getWalkSpeed(ptr); } float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); return getSwimSpeedImpl(ptr, getGmst(), mageffects, getWalkSpeed(ptr)); } } openmw-openmw-0.48.0/apps/openmw/mwclass/creature.hpp000066400000000000000000000147551445372753700227140ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H #include #include "../mwworld/registeredclass.hpp" #include "actor.hpp" namespace ESM { struct GameSetting; } namespace MWClass { class Creature : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Creature(); void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); // cached GMSTs struct GMST { const ESM::GameSetting *fMinWalkSpeedCreature; const ESM::GameSetting *fMaxWalkSpeedCreature; const ESM::GameSetting *fEncumberedMoveEffect; const ESM::GameSetting *fSneakSpeedMultiplier; const ESM::GameSetting *fAthleticsRunBonus; const ESM::GameSetting *fBaseRunMultiplier; const ESM::GameSetting *fMinFlySpeed; const ESM::GameSetting *fMaxFlySpeed; const ESM::GameSetting *fSwimRunBase; const ESM::GameSetting *fSwimRunAthleticsMult; const ESM::GameSetting *fKnockDownMult; const ESM::GameSetting *iKnockDownOddsMult; const ESM::GameSetting *iKnockDownOddsBase; }; static const GMST& getGmst(); public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) 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. MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const override; ///< Return creature stats void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) 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) const override; std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation MWWorld::ContainerStore& getContainerStore ( const MWWorld::Ptr& ptr) const override; ///< Return container store MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const override; ///< Return inventory store bool hasInventoryStore (const MWWorld::Ptr &ptr) const override; std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr float getCapacity (const MWWorld::Ptr& ptr) const override; ///< 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; ///< @return combined armor rating of this actor bool isEssential (const MWWorld::ConstPtr& ptr) const override; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) int getServices (const MWWorld::ConstPtr& actor) const override; bool isPersistent (const MWWorld::ConstPtr& ptr) const override; std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const override; ///< Return desired movement. float getMaxSpeed (const MWWorld::Ptr& ptr) const override; std::string getModel(const MWWorld::ConstPtr &ptr) const override; void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). bool isBipedal (const MWWorld::ConstPtr &ptr) const override; bool canFly (const MWWorld::ConstPtr &ptr) const override; bool canSwim (const MWWorld::ConstPtr &ptr) const override; bool canWalk (const MWWorld::ConstPtr &ptr) const override; float getSkill(const MWWorld::Ptr &ptr, int skill) 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. void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. int getBaseGold(const MWWorld::ConstPtr& ptr) const override; void respawn (const MWWorld::Ptr& ptr) const override; int getBaseFightRating(const MWWorld::ConstPtr &ptr) const override; void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override; /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh void setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const override; void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const override; float getWalkSpeed(const MWWorld::Ptr& ptr) const override; float getRunSpeed(const MWWorld::Ptr& ptr) const override; float getSwimSpeed(const MWWorld::Ptr& ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/creaturelevlist.cpp000066400000000000000000000173031445372753700243020ustar00rootroot00000000000000#include "creaturelevlist.hpp" #include #include #include "../mwmechanics/levelledlist.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" namespace MWClass { class CreatureLevListCustomData : public MWWorld::TypedCustomData { public: // actorId of the creature we spawned int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? CreatureLevListCustomData& asCreatureLevListCustomData() override { return *this; } const CreatureLevListCustomData& asCreatureLevListCustomData() const override { return *this; } }; CreatureLevList::CreatureLevList() : MWWorld::RegisteredClass(ESM::CreatureLevList::sRecordId) { } MWWorld::Ptr CreatureLevList::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } void CreatureLevList::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { if (ptr.getRefData().getCustomData() == nullptr) return; CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); if (!creature.isEmpty()) MWBase::Environment::get().getWorld()->adjustPosition(creature, force); } std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } bool CreatureLevList::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } void CreatureLevList::respawn(const MWWorld::Ptr &ptr) const { ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); if (customData.mSpawn) return; MWWorld::Ptr creature; if(customData.mSpawnActorId != -1) { creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); if(creature.isEmpty()) creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId); } if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); if (creature.getRefData().getCount() == 0) customData.mSpawn = true; else if (creatureStats.isDead()) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) customData.mSpawn = true; } } else customData.mSpawn = true; } void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { // disable for now, too many false positives /* const MWWorld::LiveCellRef *ref = ptr.get(); for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) continue; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef ref(store, it->mId); ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); } */ } void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); if (!customData.mSpawn) return; MWWorld::LiveCellRef *ref = ptr.get(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); std::string id = MWMechanics::getLevelledItem(ref->mBase, true, prng); if (!id.empty()) { // Delete the previous creature if (customData.mSpawnActorId != -1) { MWWorld::Ptr creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); if (!creature.isEmpty()) MWBase::Environment::get().getWorld()->deleteObject(creature); customData.mSpawnActorId = -1; } const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef manualRef(store, id); manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale()); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(manualRef.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } else customData.mSpawn = false; } void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const { if (!ptr.getRefData().getCustomData()) { std::unique_ptr data = std::make_unique(); data->mSpawnActorId = -1; data->mSpawn = true; ptr.getRefData().setCustomData(std::move(data)); } } void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); const ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); customData.mSpawnActorId = levListState.mSpawnActorId; customData.mSpawn = levListState.mSpawn; } void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); ESM::CreatureLevListState& levListState = state.asCreatureLevListState(); levListState.mSpawnActorId = customData.mSpawnActorId; levListState.mSpawn = customData.mSpawn; } } openmw-openmw-0.48.0/apps/openmw/mwclass/creaturelevlist.hpp000066400000000000000000000034531445372753700243100ustar00rootroot00000000000000#ifndef GAME_MWCLASS_CREATURELEVLIST_H #define GAME_MWCLASS_CREATURELEVLIST_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class CreatureLevList : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; CreatureLevList(); void ensureCustomData (const MWWorld::Ptr& ptr) const; public: std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; ///< Read additional state from \a state into \a ptr. void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. void respawn (const MWWorld::Ptr& ptr) const override; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/door.cpp000066400000000000000000000324651445372753700220360ustar00rootroot00000000000000#include "door.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actiontrap.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellutils.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.hpp" #include "classmodel.hpp" namespace MWClass { class DoorCustomData : public MWWorld::TypedCustomData { public: MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; DoorCustomData& asDoorCustomData() override { return *this; } const DoorCustomData& asDoorCustomData() const override { return *this; } }; Door::Door() : MWWorld::RegisteredClass(ESM::Door::sRecordId) { } void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, true); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { insertObjectPhysics(ptr, model, rotation, physics); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) { const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); if (customData.mDoorState != MWWorld::DoorState::Idle) { MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); } } } void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); } bool Door::isDoor() const { return true; } bool Door::useAnim() const { return true; } std::string Door::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Door::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Door::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { MWWorld::LiveCellRef *ref = ptr.get(); const std::string &openSound = ref->mBase->mOpenSound; const std::string &closeSound = ref->mBase->mCloseSound; const std::string lockedSound = "LockedDoor"; const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; std::string keyName; // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update. // Make such activation a no-op for now, like how it is in the vanilla game. if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport()) { std::unique_ptr action = std::make_unique(std::string(), ptr); action->setSound(lockedSound); return action; } // make door glow if player activates it with telekinesis if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if(animation) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); const ESM::MagicEffect *effect = store.get().find(index); animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing } } const std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { MWWorld::Ptr keyPtr = invStore.search(keyId); if (!keyPtr.isEmpty()) { hasKey = true; keyName = keyPtr.getClass().getName(keyPtr); } } if (isLocked && hasKey) { if(actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); ptr.getCellRef().unlock(); //Call the function here. because that makes sense. // using a key disarms the trap if(isTrapped) { ptr.getCellRef().setTrap(""); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); isTrapped = false; } } if (!isLocked || hasKey) { if(isTrapped) { // Trap activation std::unique_ptr action = std::make_unique(ptr.getCellRef().getTrap(), ptr); action->setSound(trapActivationSound); return action; } if (ptr.getCellRef().getTeleport()) { if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { // player activated teleport door with telekinesis return std::make_unique(); } else { std::unique_ptr action = std::make_unique(ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true); action->setSound(openSound); return action; } } else { // animated door std::unique_ptr action = std::make_unique(ptr); const auto doorState = getDoorState(ptr); bool opening = true; float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2]; if (doorState == MWWorld::DoorState::Opening) opening = false; if (doorState == MWWorld::DoorState::Idle && doorRot != 0) opening = false; if (opening) { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5f); // Doors rotate at 90 degrees per second, so start the sound at // where it would be at the current rotation. float offset = doorRot/(osg::PI * 0.5f); action->setSoundOffset(offset); action->setSound(openSound); } else { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, openSound, 0.5f); float offset = 1.0f - doorRot/(osg::PI * 0.5f); action->setSoundOffset(std::max(offset, 0.0f)); action->setSound(closeSound); } return action; } } else { // locked, and we can't open. std::unique_ptr action = std::make_unique(std::string(), ptr); action->setSound(lockedSound); return action; } } bool Door::canLock(const MWWorld::ConstPtr &ptr) const { return true; } bool Door::allowTelekinesis(const MWWorld::ConstPtr &ptr) const { if (ptr.getCellRef().getTeleport() && ptr.getCellRef().getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) return false; else return true; } std::string Door::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); std::string text; if (ptr.getCellRef().getTeleport()) { text += "\n#{sTo}"; text += "\n" + getDestination(*ref); } int lockLevel = ptr.getCellRef().getLockLevel(); if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel()); else if (ptr.getCellRef().getLockLevel() < 0) text += "\n#{sUnlocked}"; if (ptr.getCellRef().getTrap() != "") text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::string Door::getDestination (const MWWorld::LiveCellRef& door) { std::string dest = door.mRef.getDestCell(); if (dest.empty()) { // door leads to exterior, use cell name (if any), otherwise translated region name auto world = MWBase::Environment::get().getWorld(); const osg::Vec2i index = MWWorld::positionToCellIndex(door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1]); const ESM::Cell* cell = world->getStore().get().search(index.x(), index.y()); dest = world->getCellName(cell); } return "#{sCell=" + dest + "}"; } MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } void Door::ensureCustomData(const MWWorld::Ptr &ptr) const { if (!ptr.getRefData().getCustomData()) { ptr.getRefData().setCustomData(std::make_unique()); } } MWWorld::DoorState Door::getDoorState (const MWWorld::ConstPtr &ptr) const { if (!ptr.getRefData().getCustomData()) return MWWorld::DoorState::Idle; const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); return customData.mDoorState; } void Door::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const { if (ptr.getCellRef().getTeleport()) throw std::runtime_error("load doors can't be moved"); ensureCustomData(ptr); DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); customData.mDoorState = state; } void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; ensureCustomData(ptr); DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); const ESM::DoorState& doorState = state.asDoorState(); customData.mDoorState = MWWorld::DoorState(doorState.mDoorState); } void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); ESM::DoorState& doorState = state.asDoorState(); doorState.mDoorState = int(customData.mDoorState); } } openmw-openmw-0.48.0/apps/openmw/mwclass/door.hpp000066400000000000000000000056011445372753700220330ustar00rootroot00000000000000#ifndef GAME_MWCLASS_DOOR_H #define GAME_MWCLASS_DOOR_H #include #include "../mwworld/registeredclass.hpp" namespace MWClass { class Door : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Door(); void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; bool useAnim() const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. static std::string getDestination (const MWWorld::LiveCellRef& door); ///< @return destination cell name or token bool canLock(const MWWorld::ConstPtr &ptr) const override; bool allowTelekinesis(const MWWorld::ConstPtr &ptr) const override; ///< Return whether this class of object can be activated with telekinesis std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::string getModel(const MWWorld::ConstPtr &ptr) const override; MWWorld::DoorState getDoorState (const MWWorld::ConstPtr &ptr) const override; /// This does not actually cause the door to move. Use World::activateDoor instead. void setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const override; void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; ///< Read additional state from \a state into \a ptr. void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/ingredient.cpp000066400000000000000000000124771445372753700232240ustar00rootroot00000000000000#include "ingredient.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/actioneat.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Ingredient::Ingredient() : MWWorld::RegisteredClass(ESM::Ingredient::sRecordId) { } void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Ingredient::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Ingredient::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Ingredient::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::unique_ptr Ingredient::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr); action->setSound ("Swallow"); return action; } std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Up"); } std::string Ingredient::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Down"); } std::string Ingredient::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); float alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) { if (ref->mBase->mData.mEffectID[i] < 0) continue; MWGui::Widgets::SpellEffectParams params; params.mEffectID = ref->mBase->mData.mEffectID[i]; params.mAttribute = ref->mBase->mData.mAttributes[i]; params.mSkill = ref->mBase->mData.mSkills[i]; params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) || (i == 1 && alchemySkill >= fWortChanceValue*2) || (i == 2 && alchemySkill >= fWortChanceValue*3) || (i == 3 && alchemySkill >= fWortChanceValue*4)); list.push_back(params); } info.effects = list; info.text = text; info.isIngredient = true; return info; } MWWorld::Ptr Ingredient::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } bool Ingredient::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Ingredients) != 0; } float Ingredient::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/ingredient.hpp000066400000000000000000000044461445372753700232260ustar00rootroot00000000000000#ifndef GAME_MWCLASS_INGREDIENT_H #define GAME_MWCLASS_INGREDIENT_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Ingredient : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Ingredient(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getModel(const MWWorld::ConstPtr &ptr) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/itemlevlist.cpp000066400000000000000000000006501445372753700234230ustar00rootroot00000000000000#include "itemlevlist.hpp" #include namespace MWClass { ItemLevList::ItemLevList() : MWWorld::RegisteredClass(ESM::ItemLevList::sRecordId) { } std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } bool ItemLevList::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } } openmw-openmw-0.48.0/apps/openmw/mwclass/itemlevlist.hpp000066400000000000000000000012151445372753700234260ustar00rootroot00000000000000#ifndef GAME_MWCLASS_ITEMLEVLIST_H #define GAME_MWCLASS_ITEMLEVLIST_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class ItemLevList : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; ItemLevList(); public: std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/light.cpp000066400000000000000000000177041445372753700222010ustar00rootroot00000000000000#include "light.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Light::Light() : MWWorld::RegisteredClass(ESM::Light::sRecordId) { } void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWWorld::LiveCellRef *ref = ptr.get(); // Insert even if model is empty, so that the light is added renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != nullptr); insertObjectPhysics(ptr, model, rotation, physics); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); } void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects if ((ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } bool Light::useAnim() const { return true; } std::string Light::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Light::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mModel.empty()) return std::string(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Light::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::make_unique(); MWWorld::LiveCellRef *ref = ptr.get(); if(!(ref->mBase->mData.mFlags&ESM::Light::Carry)) return std::make_unique(); return defaultItemActivate(ptr, actor); } std::string Light::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Light::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; if (ref->mBase->mData.mFlags & ESM::Light::Carry) slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); return std::make_pair (slots_, false); } int Light::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Up"); } std::string Light::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Down"); } std::string Light::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } bool Light::hasToolTip (const MWWorld::ConstPtr& ptr) const { return showsInInventory(ptr); } MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; // Don't show duration for infinite light sources. if (Settings::Manager::getBool("show effect duration","Game") && ptr.getClass().getRemainingUsageTime(ptr) != -1) text += MWGui::ToolTips::getDurationString(ptr.getClass().getRemainingUsageTime(ptr), "\n#{sDuration}"); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } bool Light::showsInInventory (const MWWorld::ConstPtr& ptr) const { const ESM::Light* light = ptr.get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) return false; return Class::showsInInventory(ptr); } std::unique_ptr Light::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } void Light::setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const { ptr.getCellRef().setChargeFloat(duration); } float Light::getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (ptr.getCellRef().getCharge() == -1) return static_cast(ref->mBase->mData.mTime); else return ptr.getCellRef().getChargeFloat(); } MWWorld::Ptr Light::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } bool Light::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Lights) != 0; } float Light::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } std::pair Light::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { const MWWorld::LiveCellRef *ref = ptr.get(); if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) return std::make_pair(0,""); return std::make_pair(1,""); } std::string Light::getSound(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mSound; } } openmw-openmw-0.48.0/apps/openmw/mwclass/light.hpp000066400000000000000000000072101445372753700221750ustar00rootroot00000000000000#ifndef GAME_MWCLASS_LIGHT_H #define GAME_MWCLASS_LIGHT_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Light : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Light(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) 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. bool showsInInventory (const MWWorld::ConstPtr& ptr) const override; std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const override; ///< Sets the remaining duration of the object. float getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const override; ///< Returns the remaining duration of the object. std::string getModel(const MWWorld::ConstPtr &ptr) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; std::string getSound(const MWWorld::ConstPtr& ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/lockpick.cpp000066400000000000000000000124321445372753700226620ustar00rootroot00000000000000#include "lockpick.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Lockpick::Lockpick() : MWWorld::RegisteredClass(ESM::Lockpick::sRecordId) { } void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Lockpick::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Lockpick::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); return std::make_pair (slots_, false); } int Lockpick::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Up"); } std::string Lockpick::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Down"); } std::string Lockpick::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::unique_ptr Lockpick::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Lockpick::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } std::pair Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // Do not allow equip tools from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) return std::make_pair(0, "#{sCantEquipWeapWarning}"); return std::make_pair(1, ""); } bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; } int Lockpick::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } float Lockpick::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/lockpick.hpp000066400000000000000000000060051445372753700226660ustar00rootroot00000000000000#ifndef GAME_MWCLASS_LOCKPICK_H #define GAME_MWCLASS_LOCKPICK_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Lockpick : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Lockpick(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; ///< Return item max health or throw an exception, if class does not have item health bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override { return true; } ///< \return Item health data available? (default implementation: false) }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/misc.cpp000066400000000000000000000202121445372753700220110ustar00rootroot00000000000000#include "misc.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/actionsoulgem.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Miscellaneous::Miscellaneous() : MWWorld::RegisteredClass(ESM::Miscellaneous::sRecordId) { } bool Miscellaneous::isGold (const MWWorld::ConstPtr& ptr) const { return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); } void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Miscellaneous::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Miscellaneous::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Miscellaneous::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); int value = ref->mBase->mData.mValue; if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) value = ptr.getCellRef().getGoldValue(); if (ptr.getCellRef().getSoul() != "") { const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().search(ref->mRef.getSoul()); if (creature) { int soul = creature->mData.mSoul; if (Settings::Manager::getBool("rebalance soul gem values", "Game")) { // use the 'soul gem value rebalance' formula from the Morrowind Code Patch float soulValue = 0.0001 * pow(soul, 3) + 2 * soul; // for Azura's star add the unfilled value if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "Misc_SoulGem_Azura")) value += soulValue; else value = soulValue; } else value *= soul; } } return value; } std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Up"); return std::string("Item Misc Up"); } std::string Miscellaneous::getDownSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Down"); return std::string("Item Misc Down"); } std::string Miscellaneous::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; bool gold = isGold(ptr); if (gold) count *= getValue(ptr); std::string countString; if (!gold) countString = MWGui::ToolTips::getCountString(count); else // gold displays its count also if it's 1. countString = " (" + std::to_string(count) + ")"; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + countString + MWGui::ToolTips::getSoulString(ptr.getCellRef()); info.icon = ref->mBase->mIcon; std::string text; text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); if (!gold && !ref->mBase->mData.mIsKey) text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } MWWorld::Ptr Miscellaneous::copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const { MWWorld::Ptr newPtr; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); if (isGold(ptr)) { int goldAmount = getValue(ptr) * count; std::string base = "Gold_001"; if (goldAmount >= 100) base = "Gold_100"; else if (goldAmount >= 25) base = "Gold_025"; else if (goldAmount >= 10) base = "Gold_010"; else if (goldAmount >= 5) base = "Gold_005"; // Really, I have no idea why moving ref out of conditional // scope causes list::push_back throwing std::bad_alloc MWWorld::ManualRef newRef(store, base); const MWWorld::LiveCellRef *ref = newRef.getPtr().get(); newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getCellRef().setGoldValue(goldAmount); newPtr.getRefData().setCount(1); } else { const MWWorld::LiveCellRef *ref = ptr.get(); newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getRefData().setCount(count); } newPtr.getCellRef().unsetRefNum(); return newPtr; } std::unique_ptr Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const { const std::string soulgemPrefix = "misc_soulgem"; if (::Misc::StringUtils::ciCompareLen(ptr.getCellRef().getRefId(), soulgemPrefix, soulgemPrefix.length()) == 0) return std::make_unique(ptr); return std::make_unique(); } bool Miscellaneous::canSell (const MWWorld::ConstPtr& item, int npcServices) const { const MWWorld::LiveCellRef *ref = item.get(); return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) && !isGold(item); } float Miscellaneous::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } bool Miscellaneous::isKey(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mIsKey != 0; } } openmw-openmw-0.48.0/apps/openmw/mwclass/misc.hpp000066400000000000000000000046741445372753700220340ustar00rootroot00000000000000#ifndef GAME_MWCLASS_MISC_H #define GAME_MWCLASS_MISC_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Miscellaneous : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Miscellaneous(); public: MWWorld::Ptr copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const override; void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getModel(const MWWorld::ConstPtr &ptr) const override; std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; bool isKey (const MWWorld::ConstPtr &ptr) const override; bool isGold (const MWWorld::ConstPtr& ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/npc.cpp000066400000000000000000001746751445372753700216650ustar00rootroot00000000000000#include "npc.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturecustomdataresetter.hpp" #include "../mwmechanics/inventory.hpp" #include "../mwmechanics/aisetting.hpp" #include "../mwmechanics/setbaseaisetting.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/localscripts.hpp" #include "../mwworld/esmstore.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/npcanimation.hpp" #include "../mwgui/tooltips.hpp" namespace { int is_even(double d) { double int_part; modf(d / 2.0, &int_part); return 2.0 * int_part == d; } int round_ieee_754(double d) { double i = floor(d); d -= i; if(d < 0.5) return static_cast(i); if(d > 0.5) return static_cast(i) + 1; if(is_even(i)) return static_cast(i); return static_cast(i) + 1; } void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) { // race bonus const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); bool male = (npc->mFlags & ESM::NPC::Female) == 0; int level = creatureStats.getLevel(); for (int i=0; imData.mAttributeValues[i]; creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } // class bonus const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); for (int i=0; i<2; ++i) { int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } // skill bonus for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) { float modifierSum = 0; for (int j=0; jgetStore().get().find(j); if (skill->mData.mAttribute != attribute) continue; // is this a minor or major skill? float add=0.2f; for (int k=0; k<5; ++k) { if (class_->mData.mSkills[k][0] == j) add=0.5; } for (int k=0; k<5; ++k) { if (class_->mData.mSkills[k][1] == j) add=1.0; } modifierSum += add; } creatureStats.setAttribute(attribute, std::min( round_ieee_754(creatureStats.getAttribute(attribute).getBase() + (level-1) * modifierSum), 100) ); } // initial health float strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); float endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); int multiplier = 3; if (class_->mData.mSpecialization == ESM::Class::Combat) multiplier += 2; else if (class_->mData.mSpecialization == ESM::Class::Stealth) multiplier += 1; if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) multiplier += 1; creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); } /** * @brief autoCalculateSkills * * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): * * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) * * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. * * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, * zero for other Skills. * * and by adding class, race, specialization bonus. */ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) { const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); unsigned int level = npcStats.getLevel(); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); for (int i = 0; i < 2; ++i) { int bonus = (i==0) ? 10 : 25; for (int i2 = 0; i2 < 5; ++i2) { int index = class_->mData.mSkills[i2][i]; if (index >= 0 && index < ESM::Skill::Length) { npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); } } } for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) { float majorMultiplier = 0.1f; float specMultiplier = 0.0f; int raceBonus = 0; int specBonus = 0; for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) { if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) { raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; break; } } for (int k = 0; k < 5; ++k) { // is this a minor or major skill? if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) { majorMultiplier = 1.0f; break; } } // is this skill in the same Specialization as the class? const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); if (skill->mData.mSpecialization == class_->mData.mSpecialization) { specMultiplier = 0.5f; specBonus = 5; } npcStats.getSkill(skillIndex).setBase( std::min( round_ieee_754( npcStats.getSkill(skillIndex).getBase() + 5 + raceBonus + specBonus +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 } int skills[ESM::Skill::Length]; for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); npcStats.getSpells().addAllToInstance(spells); } } } namespace MWClass { Npc::Npc() : MWWorld::RegisteredClass(ESM::NPC::sRecordId) { } class NpcCustomData : public MWWorld::TypedCustomData { public: MWMechanics::NpcStats mNpcStats; MWMechanics::Movement mMovement; MWWorld::InventoryStore mInventoryStore; NpcCustomData& asNpcCustomData() override { return *this; } const NpcCustomData& asNpcCustomData() const override { return *this; } }; const Npc::GMST& Npc::getGmst() { static const GMST staticGmst = [] { GMST gmst; const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); gmst.fMinWalkSpeed = store.find("fMinWalkSpeed"); gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed"); gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); gmst.fMinFlySpeed = store.find("fMinFlySpeed"); gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); gmst.fSwimRunBase = store.find("fSwimRunBase"); gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase"); gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier"); gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase"); gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier"); gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier"); gmst.fWereWolfRunMult = store.find("fWereWolfRunMult"); gmst.fKnockDownMult = store.find("fKnockDownMult"); gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); return gmst; } (); return staticGmst; } void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { bool recalculate = false; auto tempData = std::make_unique(); NpcCustomData* data = tempData.get(); MWMechanics::CreatureCustomDataResetter resetter {ptr}; ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId); // creature stats int gold=0; if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { gold = ref->mBase->mNpdt.mGold; for (unsigned int i=0; i< ESM::Skill::Length; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]); data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower); data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility); data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed); data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance); data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality); data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck); data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth); data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana); data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); } else { gold = ref->mBase->mNpdt.mGold; for (int i=0; i<3; ++i) data->mNpcStats.setDynamic (i, 10); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); recalculate = true; } // Persistent actors with 0 health do not play death animation if (data->mNpcStats.isDead()) data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr)); // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList); if (!ref->mBase->mFaction.empty()) { static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get() .find("iAutoRepFacMod")->mValue.getInteger(); static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get() .find("iAutoRepLevMod")->mValue.getInteger(); int rank = ref->mBase->getFactionRank(); data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); } data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Hello, ref->mBase->mAiData.mHello); data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Fight, ref->mBase->mAiData.mFight); data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Flee, ref->mBase->mAiData.mFlee); data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Alarm, ref->mBase->mAiData.mAlarm); // spells if (!spellsInitialised) data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); data->mNpcStats.setGoldPool(gold); // store resetter.mPtr = {}; if(recalculate) data->mNpcStats.recalculateMagicka(); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items auto& prng = MWBase::Environment::get().getWorld()->getPrng(); getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); getInventoryStore(ptr).autoEquip(ptr); } } void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { renderingInterface.getObjects().insertNPC(ptr); } bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const { const MWWorld::LiveCellRef* ref = actor.get(); return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); std::string model = Settings::Manager::getString("baseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) model = Settings::Manager::getString("baseanimkna", "Models"); return model; } void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { const MWWorld::LiveCellRef *npc = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); if(race && race->mData.mFlags & ESM::Race::Beast) models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); // keep these always loaded just in case models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); if (!npc->mBase->mModel.empty()) models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel, vfs)); if (!npc->mBase->mHead.empty()) { const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHead); if (head) models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel, vfs)); } if (!npc->mBase->mHair.empty()) { const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHair); if (hair) models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel, vfs)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); // FIXME: use const version of InventoryStore functions once they are available // preload equipped items const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); if (equipped != invStore.end()) { std::vector parts; if(equipped->getType() == ESM::Clothing::sRecordId) { const ESM::Clothing *clothes = equipped->get()->mBase; parts = clothes->mParts.mParts; } else if(equipped->getType() == ESM::Armor::sRecordId) { const ESM::Armor *armor = equipped->get()->mBase; parts = armor->mParts.mParts; } else { std::string model = equipped->getClass().getModel(*equipped); if (!model.empty()) models.push_back(model); } for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { std::string partname = female ? it->mFemale : it->mMale; if (partname.empty()) partname = female ? it->mMale : it->mFemale; const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); if (part && !part->mModel.empty()) models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); } } } // preload body parts if (race) { const std::vector& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false); for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); } } } std::string Npc::getName (const MWWorld::ConstPtr& ptr) const { if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); return store.find("sWerewolfPopup")->mValue.getString(); } const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); if(!weapon.isEmpty() && weapon.getType() != ESM::Weapon::sRecordId) weapon = MWWorld::Ptr(); MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : store.find("fHandToHandReach")->mValue.getFloat()); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (ptr != MWMechanics::getPlayer()) getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist, targetActors); MWWorld::Ptr victim = result.first; osg::Vec3f hitPosition (result.second); if(victim.isEmpty()) // Didn't hit anything return; const MWWorld::Class &othercls = victim.getClass(); if(!othercls.isActor()) // Can't hit non-actors return; MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim); if(otherstats.isDead()) // Can't hit dead actors return; if(ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::HandToHand; if(!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill)); if (Misc::Rng::roll0to99(world->getPrng()) >= hitchance) { othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } bool healthdmg; float damage = 0.0f; if(!weapon.isEmpty()) { const unsigned char *attack = nullptr; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; else if(type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash; else if(type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust; if(attack) { damage = attack[0] + ((attack[1]-attack[0])*attackStrength); } MWMechanics::adjustWeaponDamage(damage, weapon, ptr); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); healthdmg = true; } else { MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } if(ptr == MWMechanics::getPlayer()) { skillUsageSucceeded(ptr, weapskill, 0); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); bool unaware = !seq.isInCombat() && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); if(unaware) { damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } } if (othercls.getCreatureStats(victim).getKnockedDown()) damage *= store.find("fCombatKODamageMult")->mValue.getFloat(); // Apply "On hit" enchanted weapons MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) damage = 0; MWMechanics::diseaseContact(victim, ptr); othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } 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 { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); // Note OnPcHitMe is not set for friendly hits. bool setOnPcHitMe = true; // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor if ((stats.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) stats.setHitAttemptActorId(statsAttacker.getActorId()); // Next handle the attacking actor if ((statsAttacker.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) statsAttacker.setHitAttemptActorId(stats.getActorId()); } if (!object.isEmpty()) stats.setLastHitAttemptObject(object.getCellRef().getRefId()); if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ if(!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } if (!successful) { // Missed if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } if (!object.isEmpty()) stats.setLastHitObject(object.getCellRef().getRefId()); if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); if (damage < 0.001f) damage = 0; bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (godmode) damage = 0; if (damage > 0.0f && !attacker.isEmpty()) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const GMST& gmst = getGmst(); int chance = store.get().find("iVoiceHitOdds")->mValue.getInteger(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) < chance) MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); // Check for knockdown float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); 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)) 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(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) { static const bool creatureDamage = Settings::Manager::getBool("unarmed creature attacks damage armor", "Game"); if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc() || creatureDamage) // Unarmed creature attacks don't affect armor condition unless it was explicitly requested. { 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, ptr); } if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); switch(armor.getClass().getEquipmentSkill(armor)) { case ESM::Skill::LightArmor: sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); break; case ESM::Skill::MediumArmor: sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); break; case ESM::Skill::HeavyArmor: sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); break; } } else if(ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } if (ishealth) { if (!attacker.isEmpty() && !godmode) damage = scaleDamage(damage, attacker, ptr); if (damage > 0.0f) { sndMgr->playSound3D(ptr, "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 health(getCreatureStats(ptr).getHealth()); health.setCurrent(health.getCurrent() - damage); stats.setHealth(health); } else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); fatigue.setCurrent(fatigue.getCurrent() - damage, true); stats.setFatigue(fatigue); } if (!wasDead && getCreatureStats(ptr).isDead()) { // NPC was killed if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()) { attacker.getClass().getNpcStats(attacker).addWerewolfKill(); } MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); } } std::unique_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { // player got activated by another NPC if(ptr == MWMechanics::getPlayer()) return std::make_unique(actor); // Werewolfs can't activate NPCs if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfNPC", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if(stats.isDead()) { bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) return std::make_unique(ptr); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::make_unique(ptr); } else if (!stats.getAiSequence().isInCombat()) { if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) return std::make_unique(ptr); // stealing // Can't talk to werewolves if (!getNpcStats(ptr).isWerewolf()) return std::make_unique(ptr); } else // In combat { const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); if (stealingInCombat && stats.getKnockedDown()) return std::make_unique(ptr); // stealing } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique(ptr); return std::make_unique(); } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } float Npc::getMaxSpeed(const MWWorld::Ptr& ptr) const { // TODO: This function is called several times per frame for each NPC. // It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats. const MWMechanics::NpcStats& stats = getNpcStats(ptr); bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) return 0.f; const MWBase::World *world = MWBase::Environment::get().getWorld(); const GMST& gmst = getGmst(); 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)); float moveSpeed; if(getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; else if(mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled()) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } else if (swimming) moveSpeed = getSwimSpeed(ptr); else if (running && !sneaking) moveSpeed = getRunSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); if(stats.isWerewolf() && running && stats.getDrawState() == MWMechanics::DrawState::Nothing) moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); return moveSpeed; } float Npc::getJump(const MWWorld::Ptr &ptr) const { if(getEncumbrance(ptr) > getCapacity(ptr)) return 0.f; const MWMechanics::NpcStats& stats = getNpcStats(ptr); bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) return 0.f; const GMST& gmst = getGmst(); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() + gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = getSkill(ptr, ESM::Skill::Acrobatics); float b = 0.0f; if(a > 50.0f) { b = a - 50.0f; a = 50.0f; } float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat()); x += 3.0f * b * gmst.fJumpAcroMultiplier->mValue.getFloat(); x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64; x *= encumbranceTerm; if(stats.getStance(MWMechanics::CreatureStats::Stance_Run)) x *= gmst.fJumpRunMultiplier->mValue.getFloat(); x *= stats.getFatigueTerm(); x -= -Constants::GravityConst * Constants::UnitsPerMeter; x /= 3.0f; return x; } MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement; } bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; } bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) return true; const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) return true; if (!customData.mNpcStats.getAiSequence().isInCombat()) return true; const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); if (stealingInCombat && customData.mNpcStats.getKnockedDown()) return true; return false; } MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); if(fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { info.caption += " ("; info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName); info.caption += ")"; } if(fullHelp) info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); return info; } float Npc::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->mValue.getFloat(); return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult; } float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const { // According to UESP, inventory weight is ignored in werewolf form. Does that include // feather and burden effects? return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); } bool Npc::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const { MWBase::Environment::get().getWorld()->breakInvisibility(actor); MWMechanics::CastSpell cast(actor, actor); std::string recordId = consumable.getCellRef().getRefId(); MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); actor.getClass().getContainerStore(actor).remove(consumable, 1, actor); return cast.cast(recordId); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const { MWMechanics::NpcStats& stats = getNpcStats (ptr); if (stats.isWerewolf()) return; MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find ( ref->mBase->mClass ); stats.useSkill (skill, *class_, usageType, extraFactor); } float Npc::getArmorRating (const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); MWMechanics::NpcStats &stats = getNpcStats(ptr); const MWWorld::InventoryStore &invStore = getInventoryStore(ptr); float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); float ratings[MWWorld::InventoryStore::Slots]; for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) { MWWorld::ConstContainerStoreIterator it = invStore.getSlot(i); if (it == invStore.end() || it->getType() != ESM::Armor::sRecordId) { // unarmored ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); } else { ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr); // Take in account armor condition const bool hasHealth = it->getClass().hasItemHealth(*it); if (hasHealth) { ratings[i] *= it->getClass().getItemNormalizedHealth(*it); } } } float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots] + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron] ) * 0.1f + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet]) * 0.05f + shield; } void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const { if (!rendering) return; // collision meshes are not scaled based on race height // having the same collision extents for all races makes the environments easier to test const MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming. if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) { if (ref->mBase->isMale()) scale *= race->mData.mHeight.mMale; else scale *= race->mData.mHeight.mFemale; return; } if (ref->mBase->isMale()) { scale.x() *= race->mData.mWeight.mMale; scale.y() *= race->mData.mWeight.mMale; scale.z() *= race->mData.mHeight.mMale; } else { scale.x() *= race->mData.mWeight.mFemale; scale.y() *= race->mData.mWeight.mFemale; scale.z() *= race->mData.mHeight.mFemale; } } int Npc::getServices(const MWWorld::ConstPtr &actor) const { return actor.get()->mBase->mAiData.mServices; } std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const { if(name == "left" || name == "right") { MWBase::World *world = MWBase::Environment::get().getWorld(); if(world->isFlying(ptr)) return std::string(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isSwimming(ptr)) return (name == "left") ? "Swim Left" : "Swim Right"; if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return (name == "left") ? "FootWaterLeft" : "FootWaterRight"; if(world->isOnGround(ptr)) { if (getNpcStats(ptr).isWerewolf() && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) { int weaponType = ESM::Weapon::None; MWMechanics::getActiveWeapon(ptr, &weaponType); if (weaponType == ESM::Weapon::None) return std::string(); } const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); if(boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) return (name == "left") ? "FootBareLeft" : "FootBareRight"; switch(boots->getClass().getEquipmentSkill(*boots)) { case ESM::Skill::LightArmor: return (name == "left") ? "FootLightLeft" : "FootLightRight"; case ESM::Skill::MediumArmor: return (name == "left") ? "FootMedLeft" : "FootMedRight"; case ESM::Skill::HeavyArmor: return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight"; } } return std::string(); } // Morrowind ignores land soundgen for NPCs if(name == "land") return std::string(); if(name == "swimleft") return "Swim Left"; if(name == "swimright") return "Swim Right"; // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? if(name == "moan") return std::string(); if(name == "roar") return std::string(); if(name == "scream") return std::string(); throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const { return getNpcStats(ptr).getSkill(skill).getModified(); } int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const { return ptr.get()->mBase->mBloodType; } void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; const ESM::NpcState& npcState = state.asNpcState(); if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { if (npcState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else // Create a CustomData, but don't fill it from ESM records (not needed) ptr.getRefData().setCustomData(std::make_unique()); } } else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); customData.mInventoryStore.readState (npcState.mInventory); customData.mNpcStats.readState (npcState.mNpcStats); bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get()->mBase->mId); if(spellsInitialised) customData.mNpcStats.getSpells().clear(); customData.mNpcStats.readState (npcState.mCreatureStats); } void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); if (ptr.getRefData().getCount() <= 0 && (!(ptr.get()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead())) { state.mHasCustomState = false; return; } ESM::NpcState& npcState = state.asNpcState(); customData.mInventoryStore.writeState (npcState.mInventory); customData.mNpcStats.writeState (npcState.mNpcStats); customData.mNpcStats.writeState (npcState.mCreatureStats); } int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mNpdt.mGold; } bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); } bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const { return true; } bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const { return true; } void Npc::respawn(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) return; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) { ptr.getRefData().setCount(1); const std::string& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const { return true; } std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mFaction; } int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { std::string factionID = ptr.getClass().getPrimaryFaction(ptr); if(factionID.empty()) return -1; // Search in the NPC data first if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) { int rank = data->asNpcCustomData().mNpcStats.getFactionRank(factionID); if (rank >= 0) return rank; } // Use base NPC record as a fallback const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->getFactionRank(); } void Npc::setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const { MWMechanics::setBaseAISetting(id, setting, value); } void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const { MWMechanics::modifyBaseInventory(actorId, itemId, amount); } float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const { const GMST& gmst = getGmst(); const MWMechanics::NpcStats& stats = getNpcStats(ptr); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() * (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; walkSpeed = std::max(0.0f, walkSpeed); if(sneaking) walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); return walkSpeed; } float Npc::getRunSpeed(const MWWorld::Ptr& ptr) const { const GMST& gmst = getGmst(); return getWalkSpeed(ptr) * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() + gmst.fBaseRunMultiplier->mValue.getFloat()); } 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)); } } openmw-openmw-0.48.0/apps/openmw/mwclass/npc.hpp000066400000000000000000000175511445372753700216570ustar00rootroot00000000000000#ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H #include "../mwworld/registeredclass.hpp" #include "actor.hpp" namespace ESM { struct GameSetting; } namespace MWClass { class Npc : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Npc(); void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; struct GMST { const ESM::GameSetting *fMinWalkSpeed; const ESM::GameSetting *fMaxWalkSpeed; const ESM::GameSetting *fEncumberedMoveEffect; const ESM::GameSetting *fSneakSpeedMultiplier; const ESM::GameSetting *fAthleticsRunBonus; const ESM::GameSetting *fBaseRunMultiplier; const ESM::GameSetting *fMinFlySpeed; const ESM::GameSetting *fMaxFlySpeed; const ESM::GameSetting *fSwimRunBase; const ESM::GameSetting *fSwimRunAthleticsMult; const ESM::GameSetting *fJumpEncumbranceBase; const ESM::GameSetting *fJumpEncumbranceMultiplier; const ESM::GameSetting *fJumpAcrobaticsBase; const ESM::GameSetting *fJumpAcroMultiplier; const ESM::GameSetting *fJumpRunMultiplier; const ESM::GameSetting *fWereWolfRunMult; const ESM::GameSetting *fKnockDownMult; const ESM::GameSetting *iKnockDownOddsMult; const ESM::GameSetting *iKnockDownOddsBase; const ESM::GameSetting *fCombatArmorMinMult; }; static const GMST& getGmst(); public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const override; ///< Return creature stats MWMechanics::NpcStats& getNpcStats (const MWWorld::Ptr& ptr) const override; ///< Return NPC stats MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const override; ///< Return container store bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) 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. MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const override; ///< Return inventory store bool hasInventoryStore(const MWWorld::Ptr &ptr) const override { return true; } void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) 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) const override; void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr float getMaxSpeed (const MWWorld::Ptr& ptr) const override; ///< Return maximal movement speed. float getJump(const MWWorld::Ptr &ptr) const override; ///< Return jump velocity (not accounting for movement) MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const override; ///< Return desired movement. float getCapacity (const MWWorld::Ptr& ptr) const override; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. float getEncumbrance (const MWWorld::Ptr& ptr) const override; ///< 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; ///< @return combined armor rating of this actor bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const override; void adjustScale (const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool rendering) const override; /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const override; ///< Inform actor \a ptr that a skill use has succeeded. bool isEssential (const MWWorld::ConstPtr& ptr) const override; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) int getServices (const MWWorld::ConstPtr& actor) const override; bool isPersistent (const MWWorld::ConstPtr& ptr) const override; std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; std::string getModel(const MWWorld::ConstPtr &ptr) const override; float getSkill(const MWWorld::Ptr& ptr, int skill) 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; ///< Read additional state from \a state into \a ptr. void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. int getBaseGold(const MWWorld::ConstPtr& ptr) const override; bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const override; bool canSwim (const MWWorld::ConstPtr &ptr) const override; bool canWalk (const MWWorld::ConstPtr &ptr) const override; bool isBipedal (const MWWorld::ConstPtr &ptr) const override; void respawn (const MWWorld::Ptr& ptr) const override; int getBaseFightRating (const MWWorld::ConstPtr& ptr) const override; std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const override; int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const override; void setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const override; void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const override; float getWalkSpeed(const MWWorld::Ptr& ptr) const override; float getRunSpeed(const MWWorld::Ptr& ptr) const override; float getSwimSpeed(const MWWorld::Ptr& ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/potion.cpp000066400000000000000000000111351445372753700223720ustar00rootroot00000000000000#include "potion.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/alchemy.hpp" #include "classmodel.hpp" namespace MWClass { Potion::Potion() : MWWorld::RegisteredClass(ESM::Potion::sRecordId) { } void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Potion::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Potion::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Potion::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Up"); } std::string Potion::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Down"); } std::string Potion::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); // hide effects the player doesn't know about MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); for (unsigned int i=0; igetFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::unique_ptr Potion::use (const MWWorld::Ptr& ptr, bool force) const { MWWorld::LiveCellRef *ref = ptr.get(); std::unique_ptr action ( new MWWorld::ActionApply (ptr, ref->mBase->mId)); action->setSound ("Drink"); return action; } MWWorld::Ptr Potion::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } bool Potion::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Potions) != 0; } float Potion::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/potion.hpp000066400000000000000000000044161445372753700224030ustar00rootroot00000000000000#ifndef GAME_MWCLASS_POTION_H #define GAME_MWCLASS_POTION_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Potion : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Potion(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getModel(const MWWorld::ConstPtr &ptr) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/probe.cpp000066400000000000000000000122531445372753700221730ustar00rootroot00000000000000#include "probe.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Probe::Probe() : MWWorld::RegisteredClass(ESM::Probe::sRecordId) { } void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Probe::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Probe::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Probe::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); return std::make_pair (slots_, false); } int Probe::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Up"); } std::string Probe::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Down"); } std::string Probe::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::unique_ptr Probe::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Probe::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } std::pair Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // Do not allow equip tools from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) return std::make_pair(0, "#{sCantEquipWeapWarning}"); return std::make_pair(1, ""); } bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; } int Probe::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } float Probe::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/probe.hpp000066400000000000000000000057631445372753700222100ustar00rootroot00000000000000#ifndef GAME_MWCLASS_PROBE_H #define GAME_MWCLASS_PROBE_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Probe : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Probe(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; ///< Return item max health or throw an exception, if class does not have item health bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override { return true; } ///< \return Item health data available? (default implementation: false) }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/repair.cpp000066400000000000000000000106041445372753700223440ustar00rootroot00000000000000#include "repair.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/actionrepair.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Repair::Repair() : MWWorld::RegisteredClass(ESM::Repair::sRecordId) { } void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Repair::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } std::string Repair::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } int Repair::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Up"); } std::string Repair::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Down"); } std::string Repair::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } bool Repair::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } int Repair::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } MWWorld::Ptr Repair::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } std::unique_ptr Repair::use (const MWWorld::Ptr& ptr, bool force) const { return std::make_unique(ptr, force); } bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::RepairItem) != 0; } float Repair::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/repair.hpp000066400000000000000000000053421445372753700223540ustar00rootroot00000000000000#ifndef GAME_MWCLASS_REPAIR_H #define GAME_MWCLASS_REPAIR_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Repair : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Repair(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getModel(const MWWorld::ConstPtr &ptr) const override; std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu (default implementation: return a /// null action). bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; ///< \return Item health data available? (default implementation: false) int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) float getWeight (const MWWorld::ConstPtr& ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/static.cpp000066400000000000000000000035651445372753700223610ustar00rootroot00000000000000#include "static.hpp" #include #include #include "../mwworld/ptr.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/vismask.hpp" #include "classmodel.hpp" namespace MWClass { Static::Static() : MWWorld::RegisteredClass(ESM::Static::sRecordId) { } void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { insertObjectPhysics(ptr, model, rotation, physics); } void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Static::getName (const MWWorld::ConstPtr& ptr) const { return ""; } bool Static::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } } openmw-openmw-0.48.0/apps/openmw/mwclass/static.hpp000066400000000000000000000025151445372753700223600ustar00rootroot00000000000000#ifndef GAME_MWCLASS_STATIC_H #define GAME_MWCLASS_STATIC_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Static : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; Static(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwclass/weapon.cpp000066400000000000000000000302751445372753700223610ustar00rootroot00000000000000#include "weapon.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "classmodel.hpp" namespace MWClass { Weapon::Weapon() : MWWorld::RegisteredClass(ESM::Weapon::sRecordId) { } void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { return getClassModel(ptr); } std::string Weapon::getName (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); const std::string& name = ref->mBase->mName; return !name.empty() ? name : ref->mBase->mId; } std::unique_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; return MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::HasHealth; } int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } std::string Weapon::getScript (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); ESM::WeaponType::Class weapClass = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mWeaponClass; std::vector slots_; bool stack = false; if (weapClass == ESM::WeaponType::Ammo) { slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } else if (weapClass == ESM::WeaponType::Thrown) { slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; } else slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); return std::make_pair (slots_, stack); } int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; return MWMechanics::getWeaponType(type)->mSkill; } int Weapon::getValue (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; return soundId + " Up"; } std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; return soundId + " Down"; } std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType); MWGui::ToolTipInfo info; info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); std::string text; // weapon type & damage if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::Manager::getBool("show projectile damage", "Game")) { text += "\n#{sType} "; int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill; const std::string type = ESM::Skill::sSkillNameIds[skill]; std::string oneOrTwoHanded; if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { if (weaponType->mFlags & ESM::WeaponType::TwoHanded) oneOrTwoHanded = "sTwoHanded"; else oneOrTwoHanded = "sOneHanded"; } text += store.get().find(type)->mValue.getString() + ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); // weapon damage if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons have 2x real damage applied // as they're both the weapon and the ammo text += "\n#{sAttack}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1] * 2)); } else if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { // Chop text += "\n#{sChop}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); // Slash text += "\n#{sSlash}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mSlash[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mSlash[1])); // Thrust text += "\n#{sThrust}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[1])); } else { // marksman text += "\n#{sAttack}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); } } if (hasItemHealth(ptr)) { int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } const bool verbose = Settings::Manager::getBool("show melee info", "Game"); // add reach for melee weapon if (weaponType->mWeaponClass == ESM::WeaponType::Melee && verbose) { // display value in feet const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; text += MWGui::ToolTips::getWeightString(combatDistance / Constants::UnitsPerFoot, "#{sRange}"); text += " #{sFeet}"; } // add attack speed for any weapon excepts arrows and bolts if (weaponType->mWeaponClass != ESM::WeaponType::Ammo && verbose) { text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); } text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; return info; } std::string Weapon::getEnchantment (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } std::string Weapon::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Weapon newItem = *ref->mBase; newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; newItem.mData.mFlags |= ESM::Weapon::Magical; const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); return record->mId; } std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // Do not allow equip weapons from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) return std::make_pair(0, "#{sCantEquipWeapWarning}"); std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair (0, ""); int type = ptr.get()->mBase->mData.mType; if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) { return std::make_pair (2, ""); } return std::make_pair(1, ""); } std::unique_ptr Weapon::use (const MWWorld::Ptr& ptr, bool force) const { std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Weapon::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } int Weapon::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } bool Weapon::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Weapon) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } float Weapon::getWeight(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } openmw-openmw-0.48.0/apps/openmw/mwclass/weapon.hpp000066400000000000000000000076521445372753700223710ustar00rootroot00000000000000#ifndef GAME_MWCLASS_WEAPON_H #define GAME_MWCLASS_WEAPON_H #include "../mwworld/registeredclass.hpp" namespace MWClass { class Weapon : public MWWorld::RegisteredClass { friend MWWorld::RegisteredClass; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: Weapon(); void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation 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. bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; ///< \return Item health data available? int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; ///< Return item max health or throw an exception, if class does not have item health std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the put down sound Id std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; float getWeight (const MWWorld::ConstPtr& ptr) const override; int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/000077500000000000000000000000001445372753700210415ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwdialogue/dialoguemanagerimp.cpp000066400000000000000000000654341445372753700254130ustar00rootroot00000000000000#include "dialoguemanagerimp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" #include "../mwscript/extensions.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "filter.hpp" #include "hypertextparser.hpp" namespace MWDialogue { DialogueManager::DialogueManager (const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage) : mTranslationDataStorage(translationDataStorage) , mCompilerContext (MWScript::CompilerContext::Type_Dialogue) , mErrorHandler() , mTalkedTo(false) , mOriginalDisposition(0) , mCurrentDisposition(0) , mPermanentDispositionChange(0) { mChoice = -1; mIsInChoice = false; mGoodbye = false; mCompilerContext.setExtensions (&extensions); } void DialogueManager::clear() { mKnownTopics.clear(); mTalkedTo = false; mOriginalDisposition = 0; mCurrentDisposition = 0; mPermanentDispositionChange = 0; } void DialogueManager::addTopic(std::string_view topic) { mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); } std::vector DialogueManager::parseTopicIdsFromText (const std::string& text) { std::vector topicIdList; std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) { std::string topicId = Misc::StringUtils::lowerCase(tok->mText); if (tok->isExplicitLink()) { // calculation of standard form for all hyperlinks size_t asterisk_count = HyperTextParser::removePseudoAsterisks(topicId); for(; asterisk_count > 0; --asterisk_count) topicId.append("*"); topicId = mTranslationDataStorage.topicStandardForm(topicId); } topicIdList.push_back(topicId); } return topicIdList; } void DialogueManager::addTopicsFromText (const std::string& text) { updateActorKnownTopics(); for (const auto& topicId : parseTopicIdsFromText(text)) { if (mActorKnownTopics.count( topicId )) mKnownTopics.insert( topicId ); } } void DialogueManager::updateOriginalDisposition() { if(mActor.getClass().isNpc()) { const auto& stats = mActor.getClass().getNpcStats(mActor); // Disposition changed by script; discard our preconceived notions if(stats.getBaseDisposition() != mCurrentDisposition) { mCurrentDisposition = stats.getBaseDisposition(); mOriginalDisposition = mCurrentDisposition; } } } bool DialogueManager::startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) { updateGlobals(); // Dialogue with dead actor (e.g. through script) should not be allowed. if (actor.getClass().getCreatureStats(actor).isDead()) return false; mLastTopic.clear(); // Note that we intentionally don't reset mPermanentDispositionChange mChoice = -1; mIsInChoice = false; mGoodbye = false; mChoices.clear(); mActor = actor; MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats (actor); mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); //greeting const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); Filter filter (actor, mChoice, mTalkedTo); for (MWWorld::Store::iterator it = dialogs.begin(); it != dialogs.end(); ++it) { if(it->mType == ESM::Dialogue::Greeting) { // Search a response (we do not accept a fallback to "Info refusal" here) if (const ESM::DialInfo *info = filter.search (*it, false)) { creatureStats.talkedToPlayer(); if (!info->mSound.empty()) { // TODO play sound } MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse("", Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); mLastTopic = it->mId; addTopicsFromText (info->mResponse); return true; } } } return false; } bool DialogueManager::compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor) { bool success = true; try { mErrorHandler.reset(); mErrorHandler.setContext("[dialogue script]"); std::istringstream input (cmd + "\n"); Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions()); Compiler::Locals locals; std::string actorScript = actor.getClass().getScript (actor); if (!actorScript.empty()) { // grab local variables from actor's script, if available. locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); } Compiler::ScriptParser parser(mErrorHandler,mCompilerContext, locals, false); scanner.scan (parser); if (!mErrorHandler.isGood()) success = false; if (success) parser.getCode (code); } catch (const Compiler::SourceException& /* error */) { // error has already been reported via error handler success = false; } catch (const std::exception& error) { Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); success = false; } if (!success) { Log(Debug::Error) << "Error: compiling failed (dialogue script): \n" << cmd << "\n"; } return success; } void DialogueManager::executeScript (const std::string& script, const MWWorld::Ptr& actor) { std::vector code; if(compile(script, code, actor)) { try { MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(), actor); Interpreter::Interpreter interpreter; MWScript::installOpcodes (interpreter); interpreter.run (&code[0], code.size(), interpreterContext); } catch (const std::exception& error) { Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); } } } bool DialogueManager::inJournal(const std::string& topicId, const std::string& infoId) const { const MWDialogue::Topic *topicHistory = nullptr; MWBase::Journal *journal = MWBase::Environment::get().getJournal(); for (auto it = journal->topicBegin(); it != journal->topicEnd(); ++it) { if (it->first == topicId) { topicHistory = &it->second; break; } } if (!topicHistory) return false; for(const auto& topic : *topicHistory) { if (topic.mInfoId == infoId) return true; } return false; } void DialogueManager::executeTopic (const std::string& topic, ResponseCallback* callback) { Filter filter (mActor, mChoice, mTalkedTo); const MWWorld::Store &dialogues = MWBase::Environment::get().getWorld()->getStore().get(); const ESM::Dialogue& dialogue = *dialogues.find (topic); const ESM::DialInfo* info = filter.search(dialogue, true); if (info) { std::string title; if (dialogue.mType==ESM::Dialogue::Persuasion) { // Determine GMST from dialogue topic. GMSTs are: // sAdmireSuccess, sAdmireFail, sIntimidateSuccess, sIntimidateFail, // sTauntSuccess, sTauntFail, sBribeSuccess, sBribeFail std::string modifiedTopic = "s" + topic; modifiedTopic.erase (std::remove (modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end()); const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); title = gmsts.find (modifiedTopic)->mValue.getString(); } else title = topic; MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse(title, Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); if (dialogue.mType == ESM::Dialogue::Topic) { // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, // in which case it should not be added to the journal. for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) { if (iter->mId == info->mId) { MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(topic), info->mId, mActor); break; } } } mLastTopic = topic; executeScript (info->mResultScript, mActor); addTopicsFromText (info->mResponse); } } const ESM::Dialogue *DialogueManager::searchDialogue(const std::string& id) { return MWBase::Environment::get().getWorld()->getStore().get().search(id); } void DialogueManager::updateGlobals() { MWBase::Environment::get().getWorld()->updateDialogueGlobals(); } void DialogueManager::updateActorKnownTopics() { updateGlobals(); mActorKnownTopics.clear(); const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get(); Filter filter (mActor, -1, mTalkedTo); for (const auto& dialog : dialogs) { if (dialog.mType == ESM::Dialogue::Topic) { const auto* answer = filter.search(dialog, true); auto topicId = Misc::StringUtils::lowerCase(dialog.mId); if (answer != nullptr) { int topicFlags = 0; if(!inJournal(topicId, answer->mId)) { // Does this dialogue contains some actor-specific answer? if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) topicFlags |= MWBase::DialogueManager::TopicType::Specific; } else topicFlags |= MWBase::DialogueManager::TopicType::Exhausted; mActorKnownTopics.insert (std::make_pair(dialog.mId, ActorKnownTopicInfo {topicFlags, answer})); } } } // If response to a topic leads to a new topic, the original topic is not exhausted. for (auto& [dialogId, topicInfo] : mActorKnownTopics) { // If the topic is not marked as exhausted, we don't need to do anything about it. // If the topic will not be shown to the player, the flag actually does not matter. if (!(topicInfo.mFlags & MWBase::DialogueManager::TopicType::Exhausted) || !mKnownTopics.count(dialogId)) continue; for (const auto& topicId : parseTopicIdsFromText(topicInfo.mInfo->mResponse)) { if (mActorKnownTopics.count( topicId ) && !mKnownTopics.count( topicId )) { topicInfo.mFlags &= ~MWBase::DialogueManager::TopicType::Exhausted; break; } } } } std::list DialogueManager::getAvailableTopics() { updateActorKnownTopics(); std::list keywordList; for (const auto& [topic, topicInfo] : mActorKnownTopics) { //does the player know the topic? if (mKnownTopics.count(topic)) keywordList.push_back(topic); } // sort again, because the previous sort was case-sensitive keywordList.sort(Misc::StringUtils::ciLess); return keywordList; } int DialogueManager::getTopicFlag(const std::string& topicId) const { auto known = mActorKnownTopics.find(topicId); if (known != mActorKnownTopics.end()) return known->second.mFlags; return 0; } void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) { if(!mIsInChoice) { const ESM::Dialogue* dialogue = searchDialogue(keyword); if (dialogue && dialogue->mType == ESM::Dialogue::Topic) { executeTopic (keyword, callback); } } } bool DialogueManager::isInChoice() const { return mIsInChoice; } void DialogueManager::goodbyeSelected() { // Apply disposition change to NPC's base disposition if we **think** we need to change something if ((mPermanentDispositionChange || mOriginalDisposition != mCurrentDisposition) && mActor.getClass().isNpc()) { 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); npcStats.setBaseDisposition(disposition); } mPermanentDispositionChange = 0; mOriginalDisposition = 0; mCurrentDisposition = 0; } void DialogueManager::questionAnswered (int answer, ResponseCallback* callback) { mChoice = answer; const ESM::Dialogue* dialogue = searchDialogue(mLastTopic); if (dialogue) { Filter filter (mActor, mChoice, mTalkedTo); if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting) { if (const ESM::DialInfo *info = filter.search (*dialogue, true)) { std::string text = info->mResponse; addTopicsFromText (text); mChoice = -1; mIsInChoice = false; mChoices.clear(); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse("", Interpreter::fixDefinesDialog(text, interpreterContext)); if (dialogue->mType == ESM::Dialogue::Topic) { // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, // in which case it should not be added to the journal for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue->mInfo.begin(); iter!=dialogue->mInfo.end(); ++iter) { if (iter->mId == info->mId) { MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor); break; } } } executeScript (info->mResultScript, mActor); } else { mChoice = -1; mIsInChoice = false; mChoices.clear(); } } } updateActorKnownTopics(); } void DialogueManager::addChoice(std::string_view text, int choice) { mIsInChoice = true; mChoices.emplace_back(text, choice); } const std::vector>& DialogueManager::getChoices() const { return mChoices; } bool DialogueManager::isGoodbye() const { return mGoodbye; } void DialogueManager::goodbye() { mIsInChoice = false; mGoodbye = true; } void DialogueManager::persuade(int type, ResponseCallback* callback) { bool success; int temp, perm; MWBase::Environment::get().getMechanicsManager()->getPersuasionDispositionChange( mActor, MWBase::MechanicsManager::PersuasionType(type), success, temp, perm); updateOriginalDisposition(); if(temp > 0 && perm > 0 && mOriginalDisposition + perm + mPermanentDispositionChange < 0) perm = -(mOriginalDisposition + mPermanentDispositionChange); mCurrentDisposition += temp; mActor.getClass().getNpcStats(mActor).setBaseDisposition(mCurrentDisposition); mPermanentDispositionChange += perm; MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); if (success) { int gold=0; if (type == MWBase::MechanicsManager::PT_Bribe10) gold = 10; else if (type == MWBase::MechanicsManager::PT_Bribe100) gold = 100; else if (type == MWBase::MechanicsManager::PT_Bribe1000) gold = 1000; if (gold) { player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player); mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor); } } std::string text; if (type == MWBase::MechanicsManager::PT_Admire) text = "Admire"; else if (type == MWBase::MechanicsManager::PT_Taunt) text = "Taunt"; else if (type == MWBase::MechanicsManager::PT_Intimidate) text = "Intimidate"; else{ text = "Bribe"; } executeTopic (text + (success ? " Success" : " Fail"), callback); } void DialogueManager::applyBarterDispositionChange(int delta) { if(mActor.getClass().isNpc()) { updateOriginalDisposition(); mCurrentDisposition += delta; mActor.getClass().getNpcStats(mActor).setBaseDisposition(mCurrentDisposition); if (Settings::Manager::getBool("barter disposition change is permanent", "Game")) mPermanentDispositionChange += delta; } } bool DialogueManager::checkServiceRefused(ResponseCallback* callback, ServiceType service) { Filter filter (mActor, service, mTalkedTo); const MWWorld::Store &dialogues = MWBase::Environment::get().getWorld()->getStore().get(); const ESM::Dialogue& dialogue = *dialogues.find ("Service Refusal"); std::vector infos = filter.list (dialogue, false, false, true); if (!infos.empty()) { const ESM::DialInfo* info = infos[0]; addTopicsFromText (info->mResponse); const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); callback->addResponse(gmsts.find ("sServiceRefusal")->mValue.getString(), Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); return true; } return false; } void DialogueManager::say(const MWWorld::Ptr &actor, const std::string &topic) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(sndMgr->sayActive(actor)) { // Actor is already saying something. return; } if (actor.getClass().isNpc() && MWBase::Environment::get().getWorld()->isSwimming(actor)) { // NPCs don't talk while submerged return; } if (actor.getClass().getCreatureStats(actor).getKnockedDown()) { // Unconscious actors can not speak return; } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Dialogue *dial = store.get().find(topic); const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); Filter filter(actor, 0, creatureStats.hasTalkedToPlayer()); const ESM::DialInfo *info = filter.search(*dial, false); if(info != nullptr) { MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if(winMgr->getSubtitlesEnabled()) winMgr->messageBox(info->mResponse); if (!info->mSound.empty()) sndMgr->say(actor, info->mSound); if (!info->mResultScript.empty()) executeScript(info->mResultScript, actor); } } int DialogueManager::countSavedGameRecords() const { return 1; // known topics } void DialogueManager::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { ESM::DialogueState state; state.mKnownTopics.reserve(mKnownTopics.size()); std::copy(mKnownTopics.begin(), mKnownTopics.end(), std::back_inserter(state.mKnownTopics)); state.mChangedFactionReaction = mChangedFactionReaction; writer.startRecord (ESM::REC_DIAS); state.save (writer); writer.endRecord (ESM::REC_DIAS); } void DialogueManager::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_DIAS) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); ESM::DialogueState state; state.load (reader); for (std::vector::const_iterator iter (state.mKnownTopics.begin()); iter!=state.mKnownTopics.end(); ++iter) if (store.get().search (*iter)) mKnownTopics.insert (*iter); mChangedFactionReaction = state.mChangedFactionReaction; } } void DialogueManager::modFactionReaction(std::string_view faction1, std::string_view faction2, int diff) { std::string fact1 = Misc::StringUtils::lowerCase(faction1); std::string fact2 = Misc::StringUtils::lowerCase(faction2); // Make sure the factions exist MWBase::Environment::get().getWorld()->getStore().get().find(fact1); MWBase::Environment::get().getWorld()->getStore().get().find(fact2); int newValue = getFactionReaction(faction1, faction2) + diff; std::map& map = mChangedFactionReaction[fact1]; map[fact2] = newValue; } void DialogueManager::setFactionReaction(std::string_view faction1, std::string_view faction2, int absolute) { std::string fact1 = Misc::StringUtils::lowerCase(faction1); std::string fact2 = Misc::StringUtils::lowerCase(faction2); // Make sure the factions exist MWBase::Environment::get().getWorld()->getStore().get().find(fact1); MWBase::Environment::get().getWorld()->getStore().get().find(fact2); std::map& map = mChangedFactionReaction[fact1]; map[fact2] = absolute; } int DialogueManager::getFactionReaction(std::string_view faction1, std::string_view faction2) const { std::string fact1 = Misc::StringUtils::lowerCase(faction1); std::string fact2 = Misc::StringUtils::lowerCase(faction2); ModFactionReactionMap::const_iterator map = mChangedFactionReaction.find(fact1); if (map != mChangedFactionReaction.end() && map->second.find(fact2) != map->second.end()) return map->second.at(fact2); const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(fact1); std::map::const_iterator it = faction->mReactions.begin(); for (; it != faction->mReactions.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, fact2)) return it->second; } return 0; } void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const { if (actor == mActor && !mLastTopic.empty()) { MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( Misc::StringUtils::lowerCase(mLastTopic), actor.getClass().getName(actor)); } } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/dialoguemanagerimp.hpp000066400000000000000000000113171445372753700254070ustar00rootroot00000000000000#ifndef GAME_MWDIALOG_DIALOGUEMANAGERIMP_H #define GAME_MWDIALOG_DIALOGUEMANAGERIMP_H #include "../mwbase/dialoguemanager.hpp" #include #include #include #include #include #include #include #include "../mwworld/ptr.hpp" #include "../mwscript/compilercontext.hpp" namespace ESM { struct Dialogue; } namespace MWDialogue { class DialogueManager : public MWBase::DialogueManager { struct ActorKnownTopicInfo { int mFlags; const ESM::DialInfo* mInfo; }; std::set mKnownTopics;// Those are the topics the player knows. // Modified faction reactions. > typedef std::map > ModFactionReactionMap; ModFactionReactionMap mChangedFactionReaction; std::map mActorKnownTopics; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; Compiler::StreamErrorHandler mErrorHandler; MWWorld::Ptr mActor; bool mTalkedTo; int mChoice; std::string mLastTopic; // last topic ID, lowercase bool mIsInChoice; bool mGoodbye; std::vector > mChoices; int mOriginalDisposition; int mCurrentDisposition; int mPermanentDispositionChange; std::vector parseTopicIdsFromText (const std::string& text); void addTopicsFromText (const std::string& text); void updateActorKnownTopics(); void updateGlobals(); bool compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor); void executeScript (const std::string& script, const MWWorld::Ptr& actor); void executeTopic (const std::string& topic, ResponseCallback* callback); const ESM::Dialogue* searchDialogue(const std::string& id); void updateOriginalDisposition(); public: DialogueManager (const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage); void clear() override; bool isInChoice() const override; bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) override; std::list getAvailableTopics() override; int getTopicFlag(const std::string& topicId) const override; bool inJournal (const std::string& topicId, const std::string& infoId) const override; void addTopic(std::string_view topic) override; void addChoice(std::string_view text,int choice) override; const std::vector >& getChoices() const override; bool isGoodbye() const override; void goodbye() override; bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) override; void say(const MWWorld::Ptr &actor, const std::string &topic) override; //calbacks for the GUI void keywordSelected (const std::string& keyword, ResponseCallback* callback) override; void goodbyeSelected() override; void questionAnswered (int answer, ResponseCallback* callback) override; void persuade (int type, ResponseCallback* callback) override; /// @note Controlled by an option, gets discarded when dialogue ends by default void applyBarterDispositionChange (int delta) override; int countSavedGameRecords() const override; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; void readRecord (ESM::ESMReader& reader, uint32_t type) override; /// Changes faction1's opinion of faction2 by \a diff. void modFactionReaction (std::string_view faction1, std::string_view faction2, int diff) override; void setFactionReaction (std::string_view faction1, std::string_view faction2, int absolute) override; /// @return faction1's opinion of faction2 int getFactionReaction (std::string_view faction1, std::string_view faction2) const override; /// Removes the last added topic response for the given actor from the journal void clearInfoActor (const MWWorld::Ptr& actor) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/filter.cpp000066400000000000000000000544641445372753700230470ustar00rootroot00000000000000#include "filter.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/actorutil.hpp" #include "selectwrapper.hpp" bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const { bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); // actor id if (!info.mActor.empty()) { if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getCellRef().getRefId())) return false; } else if (isCreature) { // Creatures must not have topics aside of those specific to their id return false; } // NPC race if (!info.mRace.empty()) { if (isCreature) return true; MWWorld::LiveCellRef *cellRef = mActor.get(); if (!Misc::StringUtils::ciEqual(info.mRace, cellRef->mBase->mRace)) return false; } // NPC class if (!info.mClass.empty()) { if (isCreature) return true; MWWorld::LiveCellRef *cellRef = mActor.get(); if ( !Misc::StringUtils::ciEqual(info.mClass, cellRef->mBase->mClass)) return false; } // NPC faction if (info.mFactionLess) { if (isCreature) return true; if (!mActor.getClass().getPrimaryFaction(mActor).empty()) return false; } else if (!info.mFaction.empty()) { if (isCreature) return true; if (!Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), info.mFaction)) return false; // check rank if (mActor.getClass().getPrimaryFactionRank(mActor) < info.mData.mRank) return false; } else if (info.mData.mRank != -1) { if (isCreature) return true; // Rank requirement, but no faction given. Use the actor's faction, if there is one. // check rank if (mActor.getClass().getPrimaryFactionRank(mActor) < info.mData.mRank) return false; } // Gender if (!isCreature) { MWWorld::LiveCellRef* npc = mActor.get(); if (info.mData.mGender==(npc->mBase->mFlags & npc->mBase->Female ? 0 : 1)) return false; } return true; } bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const { const MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player); // check player faction and rank if (!info.mPcFaction.empty()) { std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); if(iter==stats.getFactionRanks().end()) return false; // check rank if (iter->second < info.mData.mPCrank) return false; } else if (info.mData.mPCrank != -1) { // required PC faction is not specified but PC rank is; use speaker's faction std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (mActor.getClass().getPrimaryFaction(mActor))); if(iter==stats.getFactionRanks().end()) return false; // check rank if (iter->second < info.mData.mPCrank) return false; } // check cell if (!info.mCell.empty()) { // supports partial matches, just like getPcCell const std::string& playerCell = MWBase::Environment::get().getWorld()->getCellName(player.getCell()); bool match = playerCell.length()>=info.mCell.length() && Misc::StringUtils::ciEqual(playerCell.substr (0, info.mCell.length()), info.mCell); if (!match) return false; } return true; } bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const { for (std::vector::const_iterator iter (info.mSelects.begin()); iter != info.mSelects.end(); ++iter) if (!testSelectStruct (*iter)) return false; return true; } bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert) const { bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); if (isCreature) return true; int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); // For service refusal, the disposition check is inverted. However, a value of 0 still means "always succeed". return invert ? (info.mData.mDisposition == 0 || actorDisposition < info.mData.mDisposition) : (actorDisposition >= info.mData.mDisposition); } bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& select) const { std::string scriptName = mActor.getClass().getScript (mActor); if (scriptName.empty()) return false; // no script std::string name = Misc::StringUtils::lowerCase (select.getName()); const Compiler::Locals& localDefs = MWBase::Environment::get().getScriptManager()->getLocals (scriptName); char type = localDefs.getType (name); if (type==' ') return false; // script does not have a variable of this name. int index = localDefs.getIndex (name); if (index < 0) return false; // shouldn't happen, we checked that variable has a type above, so must exist const MWScript::Locals& locals = mActor.getRefData().getLocals(); if (locals.isEmpty()) return select.selectCompare(0); switch (type) { case 's': return select.selectCompare (static_cast (locals.mShorts[index])); case 'l': return select.selectCompare (locals.mLongs[index]); case 'f': return select.selectCompare (locals.mFloats[index]); } throw std::logic_error ("unknown local variable type in dialogue filter"); } bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && (mActor.getType() != ESM::NPC::sRecordId)) // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) // If not currently in a choice, we reject all conditions that test against choices. return false; if (select.getFunction() == SelectWrapper::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) // Reject weather conditions in interior cells // Note that the original engine doesn't include the "|| isCellQuasiExterior()" check, which could be considered a bug. return false; switch (select.getType()) { case SelectWrapper::Type_None: return true; case SelectWrapper::Type_Integer: return select.selectCompare (getSelectStructInteger (select)); case SelectWrapper::Type_Numeric: return testSelectStructNumeric (select); case SelectWrapper::Type_Boolean: return select.selectCompare (getSelectStructBoolean (select)); // We must not do the comparison for inverted functions (eg. Function_NotClass) case SelectWrapper::Type_Inverted: return getSelectStructBoolean (select); } return true; } bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) const { switch (select.getFunction()) { case SelectWrapper::Function_Global: // internally all globals are float :( return select.selectCompare ( MWBase::Environment::get().getWorld()->getGlobalFloat (select.getName())); case SelectWrapper::Function_Local: { return testFunctionLocal(select); } case SelectWrapper::Function_NotLocal: { return !testFunctionLocal(select); } case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); return select.selectCompare(static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } case SelectWrapper::Function_PcDynamicStat: { MWWorld::Ptr player = MWMechanics::getPlayer(); float value = player.getClass().getCreatureStats (player). getDynamic (select.getArgument()).getCurrent(); return select.selectCompare (value); } case SelectWrapper::Function_HealthPercent: { return select.selectCompare(static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); } default: throw std::runtime_error ("unknown numeric select function"); } } int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const { MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { case SelectWrapper::Function_Journal: return MWBase::Environment::get().getJournal()->getJournalIndex (select.getName()); case SelectWrapper::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore (player); return store.count(select.getName()); } case SelectWrapper::Function_Dead: return MWBase::Environment::get().getMechanicsManager()->countDeaths (select.getName()); case SelectWrapper::Function_Choice: return mChoice; case SelectWrapper::Function_AiSetting: return mActor.getClass().getCreatureStats (mActor).getAiSetting ( (MWMechanics::AiSetting)select.getArgument()).getModified(false); case SelectWrapper::Function_PcAttribute: return player.getClass().getCreatureStats (player). getAttribute (select.getArgument()).getModified(); case SelectWrapper::Function_PcSkill: return static_cast (player.getClass(). getNpcStats (player).getSkill (select.getArgument()).getModified()); case SelectWrapper::Function_FriendlyHit: { int hits = mActor.getClass().getCreatureStats (mActor).getFriendlyHits(); return hits>4 ? 4 : hits; } case SelectWrapper::Function_PcLevel: return player.getClass().getCreatureStats (player).getLevel(); case SelectWrapper::Function_PcGender: return player.get()->mBase->isMale() ? 0 : 1; case SelectWrapper::Function_PcClothingModifier: { const MWWorld::InventoryStore& store = player.getClass().getInventoryStore (player); int value = 0; for (int i=0; i<=15; ++i) // everything except things held in hands and ammunition { MWWorld::ConstContainerStoreIterator slot = store.getSlot (i); if (slot!=store.end()) value += slot->getClass().getValue (*slot); } return value; } case SelectWrapper::Function_PcCrimeLevel: return player.getClass().getNpcStats (player).getBounty(); case SelectWrapper::Function_RankRequirement: { std::string faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return 0; int rank = getFactionRank (player, faction); if (rank>=9) return 0; // max rank int result = 0; if (hasFactionRankSkillRequirements (player, faction, rank+1)) result += 1; if (hasFactionRankReputationRequirements (player, faction, rank+1)) result += 2; return result; } case SelectWrapper::Function_Level: return mActor.getClass().getCreatureStats (mActor).getLevel(); case SelectWrapper::Function_PCReputation: return player.getClass().getNpcStats (player).getReputation(); case SelectWrapper::Function_Weather: return MWBase::Environment::get().getWorld()->getCurrentWeather(); case SelectWrapper::Function_Reputation: return mActor.getClass().getNpcStats (mActor).getReputation(); case SelectWrapper::Function_FactionRankDiff: { std::string faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return 0; int rank = getFactionRank (player, faction); int npcRank = mActor.getClass().getPrimaryFactionRank(mActor); return rank-npcRank; } case SelectWrapper::Function_WerewolfKills: return player.getClass().getNpcStats (player).getWerewolfKills(); case SelectWrapper::Function_RankLow: case SelectWrapper::Function_RankHigh: { bool low = select.getFunction()==SelectWrapper::Function_RankLow; std::string factionId = mActor.getClass().getPrimaryFaction(mActor); if (factionId.empty()) return 0; int value = 0; MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats (player); std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) { int reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(factionId, playerFactionIt->first); if (low ? reaction < value : reaction > value) value = reaction; } return value; } case SelectWrapper::Function_CreatureTargetted: { MWWorld::Ptr target; mActor.getClass().getCreatureStats(mActor).getAiSequence().getCombatTarget(target); if (target) { if (target.getClass().isNpc() && target.getClass().getNpcStats(target).isWerewolf()) return 2; if (target.getType() == ESM::Creature::sRecordId) return 1; } } return 0; default: throw std::runtime_error ("unknown integer select function"); } } bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const { MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { case SelectWrapper::Function_False: return false; case SelectWrapper::Function_NotId: return !Misc::StringUtils::ciEqual(mActor.getCellRef().getRefId(), select.getName()); case SelectWrapper::Function_NotFaction: return !Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), select.getName()); case SelectWrapper::Function_NotClass: return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mClass, select.getName()); case SelectWrapper::Function_NotRace: return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, select.getName()); case SelectWrapper::Function_NotCell: { const std::string& actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); return !(actorCell.length() >= select.getName().length() && Misc::StringUtils::ciEqual(actorCell.substr(0, select.getName().length()), select.getName())); } case SelectWrapper::Function_SameGender: return (player.get()->mBase->mFlags & ESM::NPC::Female)== (mActor.get()->mBase->mFlags & ESM::NPC::Female); case SelectWrapper::Function_SameRace: return Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); case SelectWrapper::Function_SameFaction: return player.getClass().getNpcStats (player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); case SelectWrapper::Function_PcCommonDisease: return player.getClass().getCreatureStats (player).hasCommonDisease(); case SelectWrapper::Function_PcBlightDisease: return player.getClass().getCreatureStats (player).hasBlightDisease(); case SelectWrapper::Function_PcCorprus: return player.getClass().getCreatureStats (player). getMagicEffects().get (ESM::MagicEffect::Corprus).getMagnitude()!=0; case SelectWrapper::Function_PcExpelled: { std::string faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return false; return player.getClass().getNpcStats(player).getExpelled(faction); } case SelectWrapper::Function_PcVampire: return player.getClass().getCreatureStats(player).getMagicEffects(). get(ESM::MagicEffect::Vampirism).getMagnitude() > 0; case SelectWrapper::Function_TalkedToPc: return mTalkedToPlayer; case SelectWrapper::Function_Alarmed: return mActor.getClass().getCreatureStats (mActor).isAlarmed(); case SelectWrapper::Function_Detected: return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); case SelectWrapper::Function_Attacked: return mActor.getClass().getCreatureStats (mActor).getAttacked(); case SelectWrapper::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); case SelectWrapper::Function_Werewolf: return mActor.getClass().getNpcStats (mActor).isWerewolf(); default: throw std::runtime_error ("unknown boolean select function"); } } int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const { MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase(factionId)); if (iter==stats.getFactionRanks().end()) return -1; return iter->second; } bool MWDialogue::Filter::hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const { if (rank<0 || rank>=10) throw std::runtime_error ("rank index out of range"); if (!actor.getClass().getNpcStats (actor).hasSkillsForRank (factionId, rank)) return false; const ESM::Faction& faction = *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats (actor); return stats.getAttribute (faction.mData.mAttribute[0]).getBase()>=faction.mData.mRankData[rank].mAttribute1 && stats.getAttribute (faction.mData.mAttribute[1]).getBase()>=faction.mData.mRankData[rank].mAttribute2; } bool MWDialogue::Filter::hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const { if (rank<0 || rank>=10) throw std::runtime_error ("rank index out of range"); MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); const ESM::Faction& faction = *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); return stats.getFactionReputation (factionId)>=faction.mData.mRankData[rank].mFactReaction; } MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer) : mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer) {} const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const { std::vector suitableInfos = list (dialogue, fallbackToInfoRefusal, false); if (suitableInfos.empty()) return nullptr; else return suitableInfos[0]; } std::vector MWDialogue::Filter::listAll (const ESM::Dialogue& dialogue) const { std::vector infos; for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) { if (testActor (*iter)) infos.push_back(&*iter); } return infos; } std::vector MWDialogue::Filter::list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const { std::vector infos; bool infoRefusal = false; // Iterate over topic responses to find a matching one for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) { if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) { if (testDisposition (*iter, invertDisposition)) { infos.push_back(&*iter); if (!searchAll) break; } else infoRefusal = true; } } if (infos.empty() && infoRefusal && fallbackToInfoRefusal) { // No response is valid because of low NPC disposition, // search a response in the topic "Info Refusal" const MWWorld::Store &dialogues = MWBase::Environment::get().getWorld()->getStore().get(); const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal"); for (ESM::Dialogue::InfoContainer::const_iterator iter = infoRefusalDialogue.mInfo.begin(); iter!=infoRefusalDialogue.mInfo.end(); ++iter) if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter, invertDisposition)) { infos.push_back(&*iter); if (!searchAll) break; } } return infos; } openmw-openmw-0.48.0/apps/openmw/mwdialogue/filter.hpp000066400000000000000000000053061445372753700230430ustar00rootroot00000000000000#ifndef GAME_MWDIALOGUE_FILTER_H #define GAME_MWDIALOGUE_FILTER_H #include #include "../mwworld/ptr.hpp" namespace ESM { struct DialInfo; struct Dialogue; } namespace MWDialogue { class SelectWrapper; class Filter { MWWorld::Ptr mActor; int mChoice; bool mTalkedToPlayer; bool testActor (const ESM::DialInfo& info) const; ///< Is this the right actor for this \a info? bool testPlayer (const ESM::DialInfo& info) const; ///< Do the player and the cell the player is currently in match \a info? bool testSelectStructs (const ESM::DialInfo& info) const; ///< Are all select structs matching? bool testDisposition (const ESM::DialInfo& info, bool invert=false) const; ///< Is the actor disposition toward the player high enough (or low enough, if \a invert is true)? bool testFunctionLocal(const SelectWrapper& select) const; bool testSelectStruct (const SelectWrapper& select) const; bool testSelectStructNumeric (const SelectWrapper& select) const; int getSelectStructInteger (const SelectWrapper& select) const; bool getSelectStructBoolean (const SelectWrapper& select) const; int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const; bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const; bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId, int rank) const; public: Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); std::vector list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const; ///< List all infos that could be used on the given actor, using the current runtime state of the actor. /// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. std::vector listAll (const ESM::Dialogue& dialogue) const; ///< List all infos that could possibly be used on the given actor, ignoring runtime state filters and ignoring player filters. const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/hypertextparser.cpp000066400000000000000000000053471445372753700250270ustar00rootroot00000000000000#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" #include "keywordsearch.hpp" #include "hypertextparser.hpp" namespace MWDialogue { namespace HyperTextParser { std::vector parseHyperText(const std::string & text) { std::vector result; size_t pos_end = std::string::npos, iteration_pos = 0; for(;;) { size_t pos_begin = text.find('@', iteration_pos); if (pos_begin != std::string::npos) pos_end = text.find('#', pos_begin); if (pos_begin != std::string::npos && pos_end != std::string::npos) { if (pos_begin != iteration_pos) tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result); std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); result.emplace_back(link, Token::ExplicitLink); iteration_pos = pos_end + 1; } else { if (iteration_pos != text.size()) tokenizeKeywords(text.substr(iteration_pos), result); break; } } return result; } void tokenizeKeywords(const std::string & text, std::vector & tokens) { const auto& keywordSearch = MWBase::Environment::get().getWorld()->getStore().get().getDialogIdKeywordSearch(); std::vector::Match> matches; keywordSearch.highlightKeywords(text.begin(), text.end(), matches); for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) { tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); } } size_t removePseudoAsterisks(std::string & phrase) { size_t pseudoAsterisksCount = 0; if( !phrase.empty() ) { std::string::reverse_iterator rit = phrase.rbegin(); const char specialPseudoAsteriskCharacter = 127; while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) { pseudoAsterisksCount++; ++rit; } } phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); return pseudoAsterisksCount; } } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/hypertextparser.hpp000066400000000000000000000016331445372753700250260ustar00rootroot00000000000000#ifndef GAME_MWDIALOGUE_HYPERTEXTPARSER_H #define GAME_MWDIALOGUE_HYPERTEXTPARSER_H #include #include namespace MWDialogue { namespace HyperTextParser { struct Token { enum Type { ExplicitLink, // enclosed in @# ImplicitKeyword }; Token(const std::string & text, Type type) : mText(text), mType(type) {} bool isExplicitLink() { return mType == ExplicitLink; } std::string mText; Type mType; }; // In translations (at least Russian) the links are marked with @#, so // it should be a function to parse it std::vector parseHyperText(const std::string & text); void tokenizeKeywords(const std::string & text, std::vector & tokens); size_t removePseudoAsterisks(std::string & phrase); } } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/journalentry.cpp000066400000000000000000000102561445372753700243050ustar00rootroot00000000000000#include "journalentry.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwscript/interpretercontext.hpp" namespace MWDialogue { Entry::Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) : mInfoId (infoId) { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (topic); for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) { if (actor.isEmpty()) { MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); } else { MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(),actor); mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); } return; } throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + topic); } Entry::Entry (const ESM::JournalEntry& record) : mInfoId (record.mInfo), mText (record.mText), mActorName(record.mActorName) {} std::string Entry::getText() const { return mText; } void Entry::write (ESM::JournalEntry& entry) const { entry.mInfo = mInfoId; entry.mText = mText; entry.mActorName = mActorName; } JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) : Entry (topic, infoId, actor), mTopic (topic) {} JournalEntry::JournalEntry (const ESM::JournalEntry& record) : Entry (record), mTopic (record.mTopic) {} void JournalEntry::write (ESM::JournalEntry& entry) const { Entry::write (entry); entry.mTopic = mTopic; } JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) { return JournalEntry (topic, idFromIndex (topic, index), MWWorld::Ptr()); } std::string JournalEntry::idFromIndex (const std::string& topic, int index) { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (topic); for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mData.mJournalIndex==index) { return iter->mId; } throw std::runtime_error ("unknown journal index for topic " + topic); } StampedJournalEntry::StampedJournalEntry() : mDay (0), mMonth (0), mDayOfMonth (0) {} StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth, const MWWorld::Ptr& actor) : JournalEntry (topic, infoId, actor), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) : JournalEntry (record), mDay (record.mDay), mMonth (record.mMonth), mDayOfMonth (record.mDayOfMonth) {} void StampedJournalEntry::write (ESM::JournalEntry& entry) const { JournalEntry::write (entry); entry.mDay = mDay; entry.mMonth = mMonth; entry.mDayOfMonth = mDayOfMonth; } StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor) { int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth, actor); } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/journalentry.hpp000066400000000000000000000035071445372753700243130ustar00rootroot00000000000000#ifndef GAME_MWDIALOGUE_JOURNALENTRY_H #define GAME_MWDIALOGUE_JOURNALENTRY_H #include namespace ESM { struct JournalEntry; } namespace MWWorld { class Ptr; } namespace MWDialogue { /// \brief Basic quest/dialogue/topic entry struct Entry { std::string mInfoId; std::string mText; std::string mActorName; // optional Entry() = default; /// actor is optional Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); Entry (const ESM::JournalEntry& record); std::string getText() const; void write (ESM::JournalEntry& entry) const; }; /// \brief A dialogue entry /// /// Same as entry, but store TopicID struct JournalEntry : public Entry { std::string mTopic; JournalEntry() = default; JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); JournalEntry (const ESM::JournalEntry& record); void write (ESM::JournalEntry& entry) const; static JournalEntry makeFromQuest (const std::string& topic, int index); static std::string idFromIndex (const std::string& topic, int index); }; /// \brief A quest entry with a timestamp. struct StampedJournalEntry : public JournalEntry { int mDay; int mMonth; int mDayOfMonth; StampedJournalEntry(); StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth, const MWWorld::Ptr& actor); StampedJournalEntry (const ESM::JournalEntry& record); void write (ESM::JournalEntry& entry) const; static StampedJournalEntry makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/journalimp.cpp000066400000000000000000000211131445372753700237230ustar00rootroot00000000000000#include "journalimp.hpp" #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwgui/messagebox.hpp" namespace MWDialogue { Quest& Journal::getQuest (const std::string& id) { TQuestContainer::iterator iter = mQuests.find (id); if (iter==mQuests.end()) { std::pair result = mQuests.insert (std::make_pair (id, Quest (id))); iter = result.first; } return iter->second; } Topic& Journal::getTopic (const std::string& id) { TTopicContainer::iterator iter = mTopics.find (id); if (iter==mTopics.end()) { std::pair result = mTopics.insert (std::make_pair (id, Topic (id))); iter = result.first; } return iter->second; } bool Journal::isThere (const std::string& topicId, const std::string& infoId) const { if (const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().search (topicId)) { if (infoId.empty()) return true; for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == infoId) return true; } return false; } Journal::Journal() {} void Journal::clear() { mJournal.clear(); mQuests.clear(); mTopics.clear(); } void Journal::addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) { // bail out if we already have heard this... std::string infoId = JournalEntry::idFromIndex (id, index); for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i) if (i->mTopic == id && i->mInfoId == infoId) { if (getJournalIndex(id) < index) { setJournalIndex(id, index); MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); } return; } StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); Quest& quest = getQuest (id); if(quest.addEntry(entry)) // we are doing slicing on purpose here { // Restart all "other" quests with the same name as well std::string name = quest.getName(); for(auto& it : mQuests) { if(it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name)) it.second.setFinished(false); } } // there is no need to show empty entries in journal if (!entry.getText().empty()) { mJournal.push_back (entry); MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); } } void Journal::setJournalIndex (const std::string& id, int index) { Quest& quest = getQuest (id); quest.setIndex (index); } void Journal::addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) { Topic& topic = getTopic (topicId); JournalEntry entry(topicId, infoId, actor); entry.mActorName = actor.getClass().getName(actor); topic.addEntry (entry); } void Journal::removeLastAddedTopicResponse(const std::string &topicId, const std::string &actorName) { Topic& topic = getTopic (topicId); topic.removeLastAddedResponse(actorName); if (topic.begin() == topic.end()) mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic } int Journal::getJournalIndex (const std::string& id) const { TQuestContainer::const_iterator iter = mQuests.find (id); if (iter==mQuests.end()) return 0; return iter->second.getIndex(); } Journal::TEntryIter Journal::begin() const { return mJournal.begin(); } Journal::TEntryIter Journal::end() const { return mJournal.end(); } Journal::TQuestIter Journal::questBegin() const { return mQuests.begin(); } Journal::TQuestIter Journal::questEnd() const { return mQuests.end(); } Journal::TTopicIter Journal::topicBegin() const { return mTopics.begin(); } Journal::TTopicIter Journal::topicEnd() const { return mTopics.end(); } int Journal::countSavedGameRecords() const { int count = static_cast (mQuests.size()); for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) count += std::distance (iter->second.begin(), iter->second.end()); count += std::distance (mJournal.begin(), mJournal.end()); for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) count += std::distance (iter->second.begin(), iter->second.end()); return count; } void Journal::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) { const Quest& quest = iter->second; ESM::QuestState state; quest.write (state); writer.startRecord (ESM::REC_QUES); state.save (writer); writer.endRecord (ESM::REC_QUES); for (Topic::TEntryIter entryIter (quest.begin()); entryIter!=quest.end(); ++entryIter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Quest; entry.mTopic = quest.getTopic(); entryIter->write (entry); writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); } } for (TEntryIter iter (mJournal.begin()); iter!=mJournal.end(); ++iter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Journal; iter->write (entry); writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); } for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) { const Topic& topic = iter->second; for (Topic::TEntryIter entryIter (topic.begin()); entryIter!=topic.end(); ++entryIter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Topic; entry.mTopic = topic.getTopic(); entryIter->write (entry); writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); } } } void Journal::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_JOUR || type==ESM::REC_JOUR_LEGACY) { ESM::JournalEntry record; record.load (reader); if (isThere (record.mTopic, record.mInfo)) switch (record.mType) { case ESM::JournalEntry::Type_Quest: getQuest (record.mTopic).insertEntry (record); break; case ESM::JournalEntry::Type_Journal: mJournal.push_back (record); break; case ESM::JournalEntry::Type_Topic: getTopic (record.mTopic).insertEntry (record); break; } } else if (type==ESM::REC_QUES) { ESM::QuestState record; record.load (reader); if (isThere (record.mTopic)) { std::pair result = mQuests.insert (std::make_pair (record.mTopic, record)); // reapply quest index, this is to handle users upgrading from only // Morrowind.esm (no quest states) to Morrowind.esm + Tribunal.esm result.first->second.setIndex(record.mState); } } } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/journalimp.hpp000066400000000000000000000052111445372753700237310ustar00rootroot00000000000000#ifndef GAME_MWDIALOG_JOURNAL_H #define GAME_MWDIALOG_JOURNAL_H #include "../mwbase/journal.hpp" #include "quest.hpp" namespace MWDialogue { /// \brief The player's journal class Journal : public MWBase::Journal { TEntryContainer mJournal; TQuestContainer mQuests; TTopicContainer mTopics; private: Quest& getQuest (const std::string& id); Topic& getTopic (const std::string& id); bool isThere (const std::string& topicId, const std::string& infoId = "") const; public: Journal(); void clear() override; void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor) override; ///< Add a journal entry. /// @param actor Used as context for replacing of escape sequences (%name, etc). void setJournalIndex (const std::string& id, int index) override; ///< Set the journal index without adding an entry. int getJournalIndex (const std::string& id) const override; ///< Get the journal index. void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) override; /// \note topicId must be lowercase void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) override; ///< Removes the last topic response added for the given topicId and actor name. /// \note topicId must be lowercase TEntryIter begin() const override; ///< Iterator pointing to the begin of the main journal. /// /// \note Iterators to main journal entries will never become invalid. TEntryIter end() const override; ///< Iterator pointing past the end of the main journal. TQuestIter questBegin() const override; ///< Iterator pointing to the first quest (sorted by topic ID) TQuestIter questEnd() const override; ///< Iterator pointing past the last quest. TTopicIter topicBegin() const override; ///< Iterator pointing to the first topic (sorted by topic ID) /// /// \note The topic ID is identical with the user-visible topic string. TTopicIter topicEnd() const override; ///< Iterator pointing past the last topic. int countSavedGameRecords() const override; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; void readRecord (ESM::ESMReader& reader, uint32_t type) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/keywordsearch.cpp000066400000000000000000000000001445372753700244050ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwdialogue/keywordsearch.hpp000066400000000000000000000175561445372753700244420ustar00rootroot00000000000000#ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H #define GAME_MWDIALOGUE_KEYWORDSEARCH_H #include #include #include #include #include // std::reverse #include namespace MWDialogue { template class KeywordSearch { public: typedef typename string_t::const_iterator Point; struct Match { Point mBeg; Point mEnd; value_t mValue; }; void seed (string_t keyword, value_t value) { if (keyword.empty()) return; seed_impl (std::move(keyword), std::move (value), 0, mRoot); } void clear () { mRoot.mChildren.clear (); mRoot.mKeyword.clear (); } bool containsKeyword (const string_t& keyword, value_t& value) { typename Entry::childen_t::iterator current; typename Entry::childen_t::iterator next; current = mRoot.mChildren.find (Misc::StringUtils::toLower (*keyword.begin())); if (current == mRoot.mChildren.end()) return false; else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) { value = current->second.mValue; return true; } for (Point i = ++keyword.begin(); i != keyword.end(); ++i) { next = current->second.mChildren.find(Misc::StringUtils::toLower (*i)); if (next == current->second.mChildren.end()) return false; if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) { value = next->second.mValue; return true; } current = next; } return false; } static bool sortMatches(const Match& left, const Match& right) { return left.mBeg < right.mBeg; } void highlightKeywords (Point beg, Point end, std::vector& out) const { std::vector matches; for (Point i = beg; i != end; ++i) { // check first character typename Entry::childen_t::const_iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); // no match, on to next character if (candidate == mRoot.mChildren.end ()) continue; // see how far the match goes Point j = i; // some keywords might be longer variations of other keywords, so we definitely need a list of candidates // the first element in the pair is length of the match, i.e. depth from the first character on std::vector< typename std::pair > candidates; while ((j + 1) != end) { typename Entry::childen_t::const_iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); if (next == candidate->second.mChildren.end ()) { if (candidate->second.mKeyword.size() > 0) candidates.push_back(std::make_pair((j-i), candidate)); break; } candidate = next; if (candidate->second.mKeyword.size() > 0) candidates.push_back(std::make_pair((j-i), candidate)); } if (candidates.empty()) continue; // didn't match enough to disambiguate, on to next character // shorter candidates will be added to the vector first. however, we want to check against longer candidates first std::reverse(candidates.begin(), candidates.end()); for (typename std::vector< std::pair >::iterator it = candidates.begin(); it != candidates.end(); ++it) { candidate = it->second; // try to match the rest of the keyword Point k = i + it->first; typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (k - i); while (k != end && t != candidate->second.mKeyword.end ()) { if (Misc::StringUtils::toLower (*k) != Misc::StringUtils::toLower (*t)) break; ++k, ++t; } // didn't match full keyword, try the next candidate if (t != candidate->second.mKeyword.end ()) continue; // found a keyword, but there might still be longer keywords that start somewhere _within_ this keyword // we will resolve these overlapping keywords later, choosing the longest one in case of conflict Match match; match.mValue = candidate->second.mValue; match.mBeg = i; match.mEnd = k; matches.push_back(match); break; } } // resolve overlapping keywords while (!matches.empty()) { int longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { int size = it->mEnd - it->mBeg; if (size > longestKeywordSize) { longestKeywordSize = size; longestKeyword = it; } typename std::vector::iterator next = it; ++next; if (next == matches.end()) break; if (it->mEnd <= next->mBeg) { break; // no overlap } } Match keyword = *longestKeyword; matches.erase(longestKeyword); out.push_back(keyword); // erase anything that overlaps with the keyword we just added to the output for (typename std::vector::iterator it = matches.begin(); it != matches.end();) { if (it->mBeg < keyword.mEnd && it->mEnd > keyword.mBeg) it = matches.erase(it); else ++it; } } std::sort(out.begin(), out.end(), sortMatches); } private: struct Entry { typedef std::map childen_t; string_t mKeyword; value_t mValue; childen_t mChildren; }; void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) { int ch = Misc::StringUtils::toLower (keyword.at (depth)); typename Entry::childen_t::iterator j = entry.mChildren.find (ch); if (j == entry.mChildren.end ()) { entry.mChildren [ch].mValue = std::move (value); entry.mChildren [ch].mKeyword = std::move (keyword); } else { if (j->second.mKeyword.size () > 0) { if (keyword == j->second.mKeyword) throw std::runtime_error ("duplicate keyword inserted"); value_t pushValue = j->second.mValue; string_t pushKeyword = j->second.mKeyword; if (depth >= pushKeyword.size ()) throw std::runtime_error ("unexpected"); if (depth+1 < pushKeyword.size()) { seed_impl (std::move (pushKeyword), std::move (pushValue), depth+1, j->second); j->second.mKeyword.clear (); } } if (depth+1 == keyword.size()) j->second.mKeyword = value; else // depth+1 < keyword.size() seed_impl (std::move (keyword), std::move (value), depth+1, j->second); } } Entry mRoot; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/quest.cpp000066400000000000000000000051331445372753700227100ustar00rootroot00000000000000#include "quest.hpp" #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" namespace MWDialogue { Quest::Quest() : Topic(), mIndex (0), mFinished (false) {} Quest::Quest (const std::string& topic) : Topic (topic), mIndex (0), mFinished (false) {} Quest::Quest (const ESM::QuestState& state) : Topic (state.mTopic), mIndex (state.mState), mFinished (state.mFinished!=0) {} std::string Quest::getName() const { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mQuestStatus==ESM::DialInfo::QS_Name) return iter->mResponse; return ""; } int Quest::getIndex() const { return mIndex; } void Quest::setIndex (int index) { // The index must be set even if no related journal entry was found mIndex = index; } bool Quest::isFinished() const { return mFinished; } void Quest::setFinished(bool finished) { mFinished = finished; } bool Quest::addEntry (const JournalEntry& entry) { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; }); if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart) mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished; if (info->mData.mJournalIndex > mIndex) mIndex = info->mData.mJournalIndex; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) return info->mQuestStatus == ESM::DialInfo::QS_Restart; mEntries.push_back (entry); // we want slicing here return info->mQuestStatus == ESM::DialInfo::QS_Restart; } void Quest::write (ESM::QuestState& state) const { state.mTopic = getTopic(); state.mState = mIndex; state.mFinished = mFinished; } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/quest.hpp000066400000000000000000000021311445372753700227100ustar00rootroot00000000000000#ifndef GAME_MWDIALOG_QUEST_H #define GAME_MWDIALOG_QUEST_H #include "topic.hpp" namespace ESM { struct QuestState; } namespace MWDialogue { /// \brief A quest in progress or a completed quest class Quest : public Topic { int mIndex; bool mFinished; public: Quest(); Quest (const std::string& topic); Quest (const ESM::QuestState& state); std::string getName() const override; ///< May be an empty string int getIndex() const; void setIndex (int index); ///< Calling this function with a non-existent index will throw an exception. bool isFinished() const; void setFinished(bool finished); bool addEntry (const JournalEntry& entry) override; ///< Add entry and adjust index accordingly. Returns true if the quest should be restarted. /// /// \note Redundant entries are ignored, but the index is still adjusted. void write (ESM::QuestState& state) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/scripttest.cpp000066400000000000000000000103021445372753700237450ustar00rootroot00000000000000#include "scripttest.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwscript/compilercontext.hpp" #include #include #include #include #include #include #include "filter.hpp" #include namespace { void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions, int warningsMode) { MWDialogue::Filter filter(actor, 0, false); MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue); compilerContext.setExtensions(extensions); Compiler::StreamErrorHandler errorHandler; errorHandler.setWarningsMode (warningsMode); const MWWorld::Store& dialogues = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = dialogues.begin(); it != dialogues.end(); ++it) { std::vector infos = filter.listAll(*it); for (std::vector::iterator iter = infos.begin(); iter != infos.end(); ++iter) { const ESM::DialInfo* info = *iter; if (!info->mResultScript.empty()) { bool success = true; ++total; try { errorHandler.reset(); std::istringstream input (info->mResultScript + "\n"); Compiler::Scanner scanner (errorHandler, input, extensions); Compiler::Locals locals; std::string actorScript = actor.getClass().getScript(actor); if (!actorScript.empty()) { // grab local variables from actor's script, if available. locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); } Compiler::ScriptParser parser(errorHandler, compilerContext, locals, false); scanner.scan (parser); if (!errorHandler.isGood()) success = false; ++compiled; } catch (const Compiler::SourceException& /* error */) { // error has already been reported via error handler success = false; } catch (const std::exception& error) { Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); success = false; } if (!success) { Log(Debug::Error) << "Error: compiling failed (dialogue script): \n" << info->mResultScript << "\n"; } } } } } } namespace MWDialogue { namespace ScriptTest { std::pair compileAll(const Compiler::Extensions *extensions, int warningsMode) { int compiled = 0, total = 0; const MWWorld::Store& npcs = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = npcs.begin(); it != npcs.end(); ++it) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); test(ref.getPtr(), compiled, total, extensions, warningsMode); } const MWWorld::Store& creatures = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = creatures.begin(); it != creatures.end(); ++it) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); test(ref.getPtr(), compiled, total, extensions, warningsMode); } return std::make_pair(total, compiled); } } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/scripttest.hpp000066400000000000000000000007021445372753700237550ustar00rootroot00000000000000#ifndef OPENMW_MWDIALOGUE_SCRIPTTEST_H #define OPENMW_MWDIALOGUE_SCRIPTTEST_H #include namespace MWDialogue { namespace ScriptTest { /// Attempt to compile all dialogue scripts, use for verification purposes /// @return A pair containing std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode); } } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/selectwrapper.cpp000066400000000000000000000220671445372753700244340ustar00rootroot00000000000000#include "selectwrapper.hpp" #include #include #include #include namespace { template bool selectCompareImp (char comp, T1 value1, T2 value2) { switch (comp) { case '0': return value1==value2; case '1': return value1!=value2; case '2': return value1>value2; case '3': return value1>=value2; case '4': return value1 bool selectCompareImp (const ESM::DialInfo::SelectStruct& select, T value1) { if (select.mValue.getType()==ESM::VT_Int) { return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getInteger()); } else if (select.mValue.getType()==ESM::VT_Float) { return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getFloat()); } else throw std::runtime_error ( "unsupported variable type in dialogue info select"); } } MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const { int index = 0; std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; switch (index) { case 0: return Function_RankLow; case 1: return Function_RankHigh; case 2: return Function_RankRequirement; case 3: return Function_Reputation; case 4: return Function_HealthPercent; case 5: return Function_PCReputation; case 6: return Function_PcLevel; case 7: return Function_PcHealthPercent; case 8: case 9: return Function_PcDynamicStat; case 10: return Function_PcAttribute; case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: return Function_PcSkill; case 38: return Function_PcGender; case 39: return Function_PcExpelled; case 40: return Function_PcCommonDisease; case 41: return Function_PcBlightDisease; case 42: return Function_PcClothingModifier; case 43: return Function_PcCrimeLevel; case 44: return Function_SameGender; case 45: return Function_SameRace; case 46: return Function_SameFaction; case 47: return Function_FactionRankDiff; case 48: return Function_Detected; case 49: return Function_Alarmed; case 50: return Function_Choice; case 51: case 52: case 53: case 54: case 55: case 56: case 57: return Function_PcAttribute; case 58: return Function_PcCorprus; case 59: return Function_Weather; case 60: return Function_PcVampire; case 61: return Function_Level; case 62: return Function_Attacked; case 63: return Function_TalkedToPc; case 64: return Function_PcDynamicStat; case 65: return Function_CreatureTargetted; case 66: return Function_FriendlyHit; case 67: case 68: case 69: case 70: return Function_AiSetting; case 71: return Function_ShouldAttack; case 72: return Function_Werewolf; case 73: return Function_WerewolfKills; } return Function_False; } MWDialogue::SelectWrapper::SelectWrapper (const ESM::DialInfo::SelectStruct& select) : mSelect (select) {} MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const { char type = mSelect.mSelectRule[1]; switch (type) { case '1': return decodeFunction(); case '2': return Function_Global; case '3': return Function_Local; case '4': return Function_Journal; case '5': return Function_Item; case '6': return Function_Dead; case '7': return Function_NotId; case '8': return Function_NotFaction; case '9': return Function_NotClass; case 'A': return Function_NotRace; case 'B': return Function_NotCell; case 'C': return Function_NotLocal; } return Function_None; } int MWDialogue::SelectWrapper::getArgument() const { if (mSelect.mSelectRule[1]!='1') return 0; int index = 0; std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; switch (index) { // AI settings case 67: return 1; case 68: return 0; case 69: return 3; case 70: return 2; // attributes case 10: return 0; case 51: return 1; case 52: return 2; case 53: return 3; case 54: return 4; case 55: return 5; case 56: return 6; case 57: return 7; // skills case 11: return 0; case 12: return 1; case 13: return 2; case 14: return 3; case 15: return 4; case 16: return 5; case 17: return 6; case 18: return 7; case 19: return 8; case 20: return 9; case 21: return 10; case 22: return 11; case 23: return 12; case 24: return 13; case 25: return 14; case 26: return 15; case 27: return 16; case 28: return 17; case 29: return 18; case 30: return 19; case 31: return 20; case 32: return 21; case 33: return 22; case 34: return 23; case 35: return 24; case 36: return 25; case 37: return 26; // dynamic stats case 8: return 1; case 9: return 2; case 64: return 0; } return 0; } MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const { static const Function integerFunctions[] = { Function_Journal, Function_Item, Function_Dead, Function_Choice, Function_AiSetting, Function_PcAttribute, Function_PcSkill, Function_FriendlyHit, Function_PcLevel, Function_PcGender, Function_PcClothingModifier, Function_PcCrimeLevel, Function_RankRequirement, Function_Level, Function_PCReputation, Function_Weather, Function_Reputation, Function_FactionRankDiff, Function_WerewolfKills, Function_RankLow, Function_RankHigh, Function_CreatureTargetted, Function_None // end marker }; static const Function numericFunctions[] = { Function_Global, Function_Local, Function_NotLocal, Function_PcDynamicStat, Function_PcHealthPercent, Function_HealthPercent, Function_None // end marker }; static const Function booleanFunctions[] = { Function_False, Function_SameGender, Function_SameRace, Function_SameFaction, Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, Function_PcExpelled, Function_PcVampire, Function_TalkedToPc, Function_Alarmed, Function_Detected, Function_Attacked, Function_ShouldAttack, Function_Werewolf, Function_None // end marker }; static const Function invertedBooleanFunctions[] = { Function_NotId, Function_NotFaction, Function_NotClass, Function_NotRace, Function_NotCell, Function_None // end marker }; Function function = getFunction(); for (int i=0; integerFunctions[i]!=Function_None; ++i) if (integerFunctions[i]==function) return Type_Integer; for (int i=0; numericFunctions[i]!=Function_None; ++i) if (numericFunctions[i]==function) return Type_Numeric; for (int i=0; booleanFunctions[i]!=Function_None; ++i) if (booleanFunctions[i]==function) return Type_Boolean; for (int i=0; invertedBooleanFunctions[i]!=Function_None; ++i) if (invertedBooleanFunctions[i]==function) return Type_Inverted; return Type_None; } bool MWDialogue::SelectWrapper::isNpcOnly() const { static const Function functions[] = { Function_NotFaction, Function_NotClass, Function_NotRace, Function_SameGender, Function_SameRace, Function_SameFaction, Function_RankRequirement, Function_Reputation, Function_FactionRankDiff, Function_Werewolf, Function_WerewolfKills, Function_RankLow, Function_RankHigh, Function_None // end marker }; Function function = getFunction(); for (int i=0; functions[i]!=Function_None; ++i) if (functions[i]==function) return true; return false; } bool MWDialogue::SelectWrapper::selectCompare (int value) const { return selectCompareImp (mSelect, value); } bool MWDialogue::SelectWrapper::selectCompare (float value) const { return selectCompareImp (mSelect, value); } bool MWDialogue::SelectWrapper::selectCompare (bool value) const { return selectCompareImp (mSelect, static_cast (value)); } std::string MWDialogue::SelectWrapper::getName() const { return Misc::StringUtils::lowerCase (mSelect.mSelectRule.substr (5)); } openmw-openmw-0.48.0/apps/openmw/mwdialogue/selectwrapper.hpp000066400000000000000000000051341445372753700244350ustar00rootroot00000000000000#ifndef GAME_MWDIALOGUE_SELECTWRAPPER_H #define GAME_MWDIALOGUE_SELECTWRAPPER_H #include namespace MWDialogue { class SelectWrapper { const ESM::DialInfo::SelectStruct& mSelect; public: enum Function { Function_None, Function_False, Function_Journal, Function_Item, Function_Dead, Function_NotId, Function_NotFaction, Function_NotClass, Function_NotRace, Function_NotCell, Function_NotLocal, Function_Local, Function_Global, Function_SameGender, Function_SameRace, Function_SameFaction, Function_Choice, Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, Function_AiSetting, Function_PcAttribute, Function_PcSkill, Function_PcExpelled, Function_PcVampire, Function_FriendlyHit, Function_TalkedToPc, Function_PcLevel, Function_PcHealthPercent, Function_PcDynamicStat, Function_PcGender, Function_PcClothingModifier, Function_PcCrimeLevel, Function_RankRequirement, Function_HealthPercent, Function_Level, Function_PCReputation, Function_Weather, Function_Reputation, Function_Alarmed, Function_FactionRankDiff, Function_Detected, Function_Attacked, Function_ShouldAttack, Function_CreatureTargetted, Function_Werewolf, Function_WerewolfKills, Function_RankLow, Function_RankHigh }; enum Type { Type_None, Type_Integer, Type_Numeric, Type_Boolean, Type_Inverted }; private: Function decodeFunction() const; public: SelectWrapper (const ESM::DialInfo::SelectStruct& select); Function getFunction() const; int getArgument() const; Type getType() const; bool isNpcOnly() const; ///< \attention Do not call any of the select functions for this select struct! bool selectCompare (int value) const; bool selectCompare (float value) const; bool selectCompare (bool value) const; std::string getName() const; ///< Return case-smashed name. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwdialogue/topic.cpp000066400000000000000000000032731445372753700226700ustar00rootroot00000000000000#include "topic.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWDialogue { Topic::Topic() {} Topic::Topic (const std::string& topic) : mTopic (topic), mName ( MWBase::Environment::get().getWorld()->getStore().get().find (topic)->mId) {} Topic::~Topic() {} bool Topic::addEntry (const JournalEntry& entry) { if (entry.mTopic!=mTopic) throw std::runtime_error ("topic does not match: " + mTopic); // bail out if we already have heard this for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) { if (it->mInfoId == entry.mInfoId) return false; } mEntries.push_back (entry); // we want slicing here return false; } void Topic::insertEntry (const ESM::JournalEntry& entry) { mEntries.push_back (entry); } std::string Topic::getTopic() const { return mTopic; } std::string Topic::getName() const { return mName; } Topic::TEntryIter Topic::begin() const { return mEntries.begin(); } Topic::TEntryIter Topic::end() const { return mEntries.end(); } void Topic::removeLastAddedResponse (const std::string& actorName) { for (std::vector::reverse_iterator it = mEntries.rbegin(); it != mEntries.rend(); ++it) { if (it->mActorName == actorName) { mEntries.erase( (++it).base() ); // erase doesn't take a reverse_iterator return; } } } } openmw-openmw-0.48.0/apps/openmw/mwdialogue/topic.hpp000066400000000000000000000026121445372753700226710ustar00rootroot00000000000000#ifndef GAME_MWDIALOG_TOPIC_H #define GAME_MWDIALOG_TOPIC_H #include #include #include "journalentry.hpp" namespace ESM { struct JournalEntry; } namespace MWDialogue { /// \brief Collection of seen responses for a topic class Topic { public: typedef std::vector TEntryContainer; typedef TEntryContainer::const_iterator TEntryIter; protected: std::string mTopic; std::string mName; TEntryContainer mEntries; public: Topic(); Topic (const std::string& topic); virtual ~Topic(); virtual bool addEntry (const JournalEntry& entry); ///< Add entry /// /// \note Redundant entries are ignored. void insertEntry (const ESM::JournalEntry& entry); ///< Add entry without checking for redundant entries or modifying the state of the /// topic otherwise std::string getTopic() const; virtual std::string getName() const; void removeLastAddedResponse (const std::string& actorName); TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. TEntryIter end() const; ///< Iterator pointing past the end of the journal for this topic. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/000077500000000000000000000000001445372753700200345ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwgui/alchemywindow.cpp000066400000000000000000000377631445372753700234320ustar00rootroot00000000000000#include "alchemywindow.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/alchemy.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include #include #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "itemview.hpp" #include "itemwidget.hpp" #include "widgets.hpp" namespace MWGui { AlchemyWindow::AlchemyWindow() : WindowBase("openmw_alchemy_window.layout") , mCurrentFilter(FilterType::ByName) , mModel(nullptr) , mSortModel(nullptr) , mAlchemy(new MWMechanics::Alchemy()) , mApparatus (4) , mIngredients (4) { getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mIngredients[0], "Ingredient1"); getWidget(mIngredients[1], "Ingredient2"); getWidget(mIngredients[2], "Ingredient3"); getWidget(mIngredients[3], "Ingredient4"); getWidget(mApparatus[0], "Apparatus1"); getWidget(mApparatus[1], "Apparatus2"); getWidget(mApparatus[2], "Apparatus3"); getWidget(mApparatus[3], "Apparatus4"); getWidget(mEffectsBox, "CreatedEffects"); getWidget(mBrewCountEdit, "BrewCount"); getWidget(mIncreaseButton, "IncreaseButton"); getWidget(mDecreaseButton, "DecreaseButton"); getWidget(mNameEdit, "NameEdit"); getWidget(mItemView, "ItemView"); getWidget(mFilterValue, "FilterValue"); getWidget(mFilterType, "FilterType"); mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged); mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); mBrewCountEdit->setMinValue(1); mBrewCountEdit->setValue(1); mIncreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &AlchemyWindow::onIncreaseButtonPressed); mIncreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &AlchemyWindow::onCountButtonReleased); mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &AlchemyWindow::onDecreaseButtonPressed); mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &AlchemyWindow::onCountButtonReleased); mItemView->eventItemClicked += MyGUI::newDelegate(this, &AlchemyWindow::onSelectedItem); mIngredients[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); mFilterValue->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged); mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited); mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType); center(); } void AlchemyWindow::onAccept(MyGUI::EditBox* sender) { onCreateButtonClicked(sender); // To do not spam onAccept() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); } void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) { mAlchemy->setPotionName(mNameEdit->getCaption()); int count = mAlchemy->countPotionsToBrew(); count = std::min(count, mBrewCountEdit->getValue()); createPotions(count); } void AlchemyWindow::createPotions(int count) { MWMechanics::Alchemy::Result result = mAlchemy->create(mNameEdit->getCaption(), count); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); switch (result) { case MWMechanics::Alchemy::Result_NoName: winMgr->messageBox("#{sNotifyMessage37}"); break; case MWMechanics::Alchemy::Result_NoMortarAndPestle: winMgr->messageBox("#{sNotifyMessage45}"); break; case MWMechanics::Alchemy::Result_LessThanTwoIngredients: winMgr->messageBox("#{sNotifyMessage6a}"); break; case MWMechanics::Alchemy::Result_Success: winMgr->playSound("potion success"); if (count == 1) winMgr->messageBox("#{sPotionSuccess}"); else winMgr->messageBox("#{sPotionSuccess} "+mNameEdit->getCaption()+" ("+std::to_string(count)+")"); break; case MWMechanics::Alchemy::Result_NoEffects: case MWMechanics::Alchemy::Result_RandomFailure: winMgr->messageBox("#{sNotifyMessage8}"); winMgr->playSound("potion fail"); break; } // remove ingredient slots that have been fully used up for (int i=0; i<4; ++i) if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); if (ingred.getRefData().getCount() == 0) removeIngredient(mIngredients[i]); } updateFilters(); update(); } void AlchemyWindow::initFilter() { auto const& wm = MWBase::Environment::get().getWindowManager(); auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); if (mFilterType->getCaption() == ingredient) mCurrentFilter = FilterType::ByName; else mCurrentFilter = FilterType::ByEffect; updateFilters(); mFilterValue->clearIndexSelected(); updateFilters(); } void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender) { auto const& wm = MWBase::Environment::get().getWindowManager(); auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); auto *button = _sender->castType(); if (button->getCaption() == ingredient) { button->setCaption(effect); mCurrentFilter = FilterType::ByEffect; } else { button->setCaption(ingredient); mCurrentFilter = FilterType::ByName; } mSortModel->setNameFilter({}); mSortModel->setEffectFilter({}); mFilterValue->clearIndexSelected(); updateFilters(); mItemView->update(); } void AlchemyWindow::updateFilters() { std::set itemNames, itemEffects; for (size_t i = 0; i < mModel->getItemCount(); ++i) { MWWorld::Ptr item = mModel->getItem(i).mBase; if (item.getType() != ESM::Ingredient::sRecordId) continue; itemNames.insert(item.getClass().getName(item)); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); auto const effects = MWMechanics::Alchemy::effectsDescription(item, alchemySkill); itemEffects.insert(effects.begin(), effects.end()); } mFilterValue->removeAllItems(); auto const addItems = [&](auto const& container) { for (auto const& item : container) mFilterValue->addItem(item); }; switch (mCurrentFilter) { case FilterType::ByName: addItems(itemNames); break; case FilterType::ByEffect: addItems(itemEffects); break; } } void AlchemyWindow::applyFilter(const std::string& filter) { switch (mCurrentFilter) { case FilterType::ByName: mSortModel->setNameFilter(filter); break; case FilterType::ByEffect: mSortModel->setEffectFilter(filter); break; } mItemView->update(); } void AlchemyWindow::onFilterChanged(MyGUI::ComboBox* _sender, size_t _index) { // ignore spurious event fired when one edit the content after selection. // onFilterEdited will handle it. if (_index != MyGUI::ITEM_NONE) applyFilter(_sender->getItemNameAt(_index)); } void AlchemyWindow::onFilterEdited(MyGUI::EditBox* _sender) { applyFilter(_sender->getCaption()); } void AlchemyWindow::onOpen() { mAlchemy->clear(); mAlchemy->setAlchemist (MWMechanics::getPlayer()); mModel = new InventoryItemModel(MWMechanics::getPlayer()); mSortModel = new SortFilterItemModel(mModel); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); mItemView->resetScrollBars(); mNameEdit->setCaption(""); mBrewCountEdit->setValue(1); int index = 0; for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy->beginTools()); iter!=mAlchemy->endTools() && index (mApparatus.size()); ++iter, ++index) { mApparatus.at (index)->setItem(*iter); mApparatus.at (index)->clearUserStrings(); if (!iter->isEmpty()) { mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr"); mApparatus.at (index)->setUserData (MWWorld::Ptr(*iter)); } } update(); initFilter(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) { removeIngredient(_sender); update(); } void AlchemyWindow::onSelectedItem(int index) { MWWorld::Ptr item = mSortModel->getItem(index).mBase; int res = mAlchemy->addIngredient(item); if (res != -1) { update(); std::string sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getWindowManager()->playSound(sound); } } void AlchemyWindow::update() { std::string suggestedName = mAlchemy->suggestPotionName(); if (suggestedName != mSuggestedPotionName) mNameEdit->setCaptionWithReplacing(suggestedName); mSuggestedPotionName = suggestedName; mSortModel->clearDragItems(); MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy->beginIngredients (); for (int i=0; i<4; ++i) { ItemWidget* ingredient = mIngredients[i]; MWWorld::Ptr item; if (it != mAlchemy->endIngredients ()) { item = *it; ++it; } if (!item.isEmpty()) mSortModel->addDragItem(item, item.getRefData().getCount()); if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); ingredient->clearUserStrings (); ingredient->setItem(item); if (item.isEmpty ()) continue; ingredient->setUserString("ToolTipType", "ItemPtr"); ingredient->setUserData(MWWorld::Ptr(item)); ingredient->setCount(item.getRefData().getCount()); } mItemView->update(); std::set effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; unsigned int effectIndex=0; for (const MWMechanics::EffectKey& effectKey : effectIds) { Widgets::SpellEffectParams params; params.mEffectID = effectKey.mId; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectKey.mId); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) params.mSkill = effectKey.mArg; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) params.mAttribute = effectKey.mArg; params.mIsConstant = true; params.mNoTarget = true; params.mNoMagnitude = true; params.mKnown = mAlchemy->knownEffect(effectIndex, MWBase::Environment::get().getWorld()->getPlayerPtr()); list.push_back(params); ++effectIndex; } while (mEffectsBox->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mEffectsBox->getChildAt(0)); MyGUI::IntCoord coord(0, 0, mEffectsBox->getWidth(), 24); Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget ("MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top); effectsWidget->setEffectList(list); std::vector effectItems; effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0); effectsWidget->setCoord(coord); } void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) { for (int i=0; i<4; ++i) if (mIngredients[i] == ingredient) mAlchemy->removeIngredient (i); update(); } void AlchemyWindow::addRepeatController(MyGUI::Widget *widget) { MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); MyGUI::ControllerRepeatClick* controller = static_cast(item); controller->eventRepeatClick += newDelegate(this, &AlchemyWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); } void AlchemyWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { addRepeatController(_sender); onIncreaseButtonTriggered(); } void AlchemyWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { addRepeatController(_sender); onDecreaseButtonTriggered(); } void AlchemyWindow::onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller) { if (widget == mIncreaseButton) onIncreaseButtonTriggered(); else if (widget == mDecreaseButton) onDecreaseButtonTriggered(); } void AlchemyWindow::onCountButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) { MyGUI::ControllerManager::getInstance().removeItem(_sender); } void AlchemyWindow::onCountValueChanged(int value) { mBrewCountEdit->setValue(std::abs(value)); } void AlchemyWindow::onIncreaseButtonTriggered() { int currentCount = mBrewCountEdit->getValue(); // prevent overflows if (currentCount == std::numeric_limits::max()) return; mBrewCountEdit->setValue(currentCount+1); } void AlchemyWindow::onDecreaseButtonTriggered() { int currentCount = mBrewCountEdit->getValue(); if (currentCount > 1) mBrewCountEdit->setValue(currentCount-1); } } openmw-openmw-0.48.0/apps/openmw/mwgui/alchemywindow.hpp000066400000000000000000000053411445372753700234220ustar00rootroot00000000000000#ifndef MWGUI_ALCHEMY_H #define MWGUI_ALCHEMY_H #include #include #include #include #include #include #include "windowbase.hpp" namespace MWMechanics { class Alchemy; } namespace MWGui { class ItemView; class ItemWidget; class InventoryItemModel; class SortFilterItemModel; class AlchemyWindow : public WindowBase { public: AlchemyWindow(); void onOpen() override; void onResChange(int, int) override { center(); } private: static const float sCountChangeInitialPause; // in seconds static const float sCountChangeInterval; // in seconds std::string mSuggestedPotionName; enum class FilterType { ByName, ByEffect }; FilterType mCurrentFilter; ItemView* mItemView; InventoryItemModel* mModel; SortFilterItemModel* mSortModel; MyGUI::Button* mCreateButton; MyGUI::Button* mCancelButton; MyGUI::Widget* mEffectsBox; MyGUI::Button* mIncreaseButton; MyGUI::Button* mDecreaseButton; Gui::AutoSizedButton* mFilterType; MyGUI::ComboBox* mFilterValue; MyGUI::EditBox* mNameEdit; Gui::NumericEditBox* mBrewCountEdit; void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox*); void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onCountButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onCountValueChanged(int value); void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); void applyFilter(const std::string& filter); void initFilter(); void onFilterChanged(MyGUI::ComboBox* _sender, size_t _index); void onFilterEdited(MyGUI::EditBox* _sender); void switchFilterType(MyGUI::Widget* _sender); void updateFilters(); void addRepeatController(MyGUI::Widget* widget); void onIncreaseButtonTriggered(); void onDecreaseButtonTriggered(); void onSelectedItem(int index); void removeIngredient(MyGUI::Widget* ingredient); void createPotions(int count); void update(); std::unique_ptr mAlchemy; std::vector mApparatus; std::vector mIngredients; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/backgroundimage.cpp000066400000000000000000000026111445372753700236620ustar00rootroot00000000000000#include "backgroundimage.hpp" #include namespace MWGui { void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool stretch) { if (mChild) { MyGUI::Gui::getInstance().destroyWidget(mChild); mChild = nullptr; } if (!stretch) { setImageTexture("black"); if (fixedRatio) mAspect = 4.0/3.0; else mAspect = 0; // TODO mChild = createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); mChild->setImageTexture(image); adjustSize(); } else { mAspect = 0; setImageTexture(image); } } void BackgroundImage::adjustSize() { if (mAspect == 0) return; MyGUI::IntSize screenSize = getSize(); int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * mAspect) / 2); int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / mAspect) / 2); mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2); } void BackgroundImage::setSize (const MyGUI::IntSize& _value) { MyGUI::Widget::setSize (_value); adjustSize(); } void BackgroundImage::setCoord (const MyGUI::IntCoord& _value) { MyGUI::Widget::setCoord (_value); adjustSize(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/backgroundimage.hpp000066400000000000000000000016621445372753700236740ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_BACKGROUNDIMAGE_H #define OPENMW_MWGUI_BACKGROUNDIMAGE_H #include namespace MWGui { /** * @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars */ class BackgroundImage final : public MyGUI::ImageBox { MYGUI_RTTI_DERIVED(BackgroundImage) public: BackgroundImage() : mChild(nullptr), mAspect(0) {} /** * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions * @param stretch Stretch to fill the whole screen, or add black bars? */ void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool stretch=true); void setSize (const MyGUI::IntSize &_value) override; void setCoord (const MyGUI::IntCoord &_value) override; private: MyGUI::ImageBox* mChild; double mAspect; void adjustSize(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/birth.cpp000066400000000000000000000217761445372753700216650ustar00rootroot00000000000000#include "birth.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "widgets.hpp" namespace { bool sortBirthSigns(const std::pair& left, const std::pair& right) { return left.second->mName.compare (right.second->mName) < 0; } } namespace MWGui { BirthDialog::BirthDialog() : WindowModal("openmw_chargen_birth.layout") { // Centre dialog center(); getWidget(mSpellArea, "SpellArea"); getWidget(mBirthImage, "BirthsignImage"); getWidget(mBirthList, "BirthsignList"); mBirthList->setScrollVisible(true); mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onAccept); mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked); updateBirths(); updateSpells(); } void BirthDialog::setNextButtonShow(bool shown) { MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); else okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } void BirthDialog::onOpen() { WindowModal::onOpen(); updateBirths(); updateSpells(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); // Show the current birthsign by default const std::string &signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!signId.empty()) setBirthId(signId); } void BirthDialog::setBirthId(const std::string &birthId) { mCurrentBirthId = birthId; mBirthList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mBirthList->getItemCount(); for (size_t i = 0; i < count; ++i) { if (Misc::StringUtils::ciEqual(*mBirthList->getItemDataAt(i), birthId)) { mBirthList->setIndexSelected(i); break; } } updateSpells(); } // widget controls void BirthDialog::onOkClicked(MyGUI::Widget* _sender) { if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void BirthDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) { onSelectBirth(_sender, _index); if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void BirthDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } void BirthDialog::onSelectBirth(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) return; const std::string *birthId = mBirthList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentBirthId, *birthId)) return; mCurrentBirthId = *birthId; updateSpells(); } // update widget content void BirthDialog::updateBirths() { mBirthList->removeAllItems(); const MWWorld::Store &signs = MWBase::Environment::get().getWorld()->getStore().get(); // sort by name std::vector < std::pair > birthSigns; for (const ESM::BirthSign& sign : signs) { birthSigns.emplace_back(sign.mId, &sign); } std::sort(birthSigns.begin(), birthSigns.end(), sortBirthSigns); int index = 0; for (auto& birthsignPair : birthSigns) { mBirthList->addItem(birthsignPair.second->mName, birthsignPair.first); if (mCurrentBirthId.empty()) { mBirthList->setIndexSelected(index); mCurrentBirthId = birthsignPair.first; } else if (Misc::StringUtils::ciEqual(birthsignPair.first, mCurrentBirthId)) { mBirthList->setIndexSelected(index); } index++; } } void BirthDialog::updateSpells() { for (MyGUI::Widget* widget : mSpellItems) { MyGUI::Gui::getInstance().destroyWidget(widget); } mSpellItems.clear(); if (mCurrentBirthId.empty()) return; Widgets::MWSpellPtr spellWidget; const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), lineHeight); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::BirthSign *birth = store.get().find(mCurrentBirthId); mBirthImage->setImageTexture(Misc::ResourceHelpers::correctTexturePath(birth->mTexture, MWBase::Environment::get().getResourceSystem()->getVFS())); std::vector abilities, powers, spells; std::vector::const_iterator it = birth->mPowers.mList.begin(); std::vector::const_iterator end = birth->mPowers.mList.end(); for (; it != end; ++it) { const std::string &spellId = *it; const ESM::Spell *spell = store.get().search(spellId); if (!spell) continue; // Skip spells which cannot be found ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power) continue; // We only want spell, ability and powers. if (type == ESM::Spell::ST_Ability) abilities.push_back(spellId); else if (type == ESM::Spell::ST_Power) powers.push_back(spellId); else if (type == ESM::Spell::ST_Spell) spells.push_back(spellId); } int i = 0; struct { const std::vector &spells; const char *label; } categories[3] = { {abilities, "sBirthsignmenu1"}, {powers, "sPowers"}, {spells, "sBirthsignmenu2"} }; for (int category = 0; category < 3; ++category) { if (!categories[category].spells.empty()) { MyGUI::TextBox* label = mSpellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, std::string("Label")); label->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString(categories[category].label, "")); mSpellItems.push_back(label); coord.top += lineHeight; end = categories[category].spells.end(); for (it = categories[category].spells.begin(); it != end; ++it) { const std::string &spellId = *it; spellWidget = mSpellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + MyGUI::utility::toString(i)); spellWidget->setSpellId(spellId); mSpellItems.push_back(spellWidget); coord.top += lineHeight; MyGUI::IntCoord spellCoord = coord; spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget in the layout as a template? spellWidget->createEffectWidgets(mSpellItems, mSpellArea, spellCoord, (category == 0) ? Widgets::MWEffectList::EF_Constant : 0); coord.top = spellCoord.top; ++i; } } } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mSpellArea->setVisibleVScroll(false); mSpellArea->setCanvasSize(MyGUI::IntSize(mSpellArea->getWidth(), std::max(mSpellArea->getHeight(), coord.top))); mSpellArea->setVisibleVScroll(true); mSpellArea->setViewOffset(MyGUI::IntPoint(0, 0)); } } openmw-openmw-0.48.0/apps/openmw/mwgui/birth.hpp000066400000000000000000000026131445372753700216570ustar00rootroot00000000000000#ifndef MWGUI_BIRTH_H #define MWGUI_BIRTH_H #include "windowbase.hpp" namespace MWGui { class BirthDialog : public WindowModal { public: BirthDialog(); enum Gender { GM_Male, GM_Female }; const std::string &getBirthId() const { return mCurrentBirthId; } void setBirthId(const std::string &raceId); void setNextButtonShow(bool shown); void onOpen() override; bool exit() override { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onSelectBirth(MyGUI::ListBox* _sender, size_t _index); void onAccept(MyGUI::ListBox* _sender, size_t index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); private: void updateBirths(); void updateSpells(); MyGUI::ListBox* mBirthList; MyGUI::ScrollView* mSpellArea; MyGUI::ImageBox* mBirthImage; std::vector mSpellItems; std::string mCurrentBirthId; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/bookpage.cpp000066400000000000000000001231451445372753700223350ustar00rootroot00000000000000#include "bookpage.hpp" #include #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" #include "MyGUI_FactoryManager.h" #include #include namespace MWGui { struct TypesetBookImpl; class PageDisplay; class BookPageImpl; static bool ucsSpace (int codePoint); static bool ucsLineBreak (int codePoint); static bool ucsCarriageReturn (int codePoint); static bool ucsBreakingSpace (int codePoint); struct BookTypesetter::Style { virtual ~Style () {} }; struct TypesetBookImpl : TypesetBook { typedef std::vector Content; typedef std::list Contents; typedef Utf8Stream::Point Utf8Point; typedef std::pair Range; struct StyleImpl : BookTypesetter::Style { MyGUI::IFont* mFont; MyGUI::Colour mHotColour; MyGUI::Colour mActiveColour; MyGUI::Colour mNormalColour; InteractiveId mInteractiveId; bool match (MyGUI::IFont* tstFont, const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) { return (mFont == tstFont) && partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); } bool match (char const * tstFont, const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) { return (mFont->getResourceName () == tstFont) && partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); } bool partal_match (const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) { return (mHotColour == tstHotColour ) && (mActiveColour == tstActiveColour ) && (mNormalColour == tstNormalColour ) && (mInteractiveId == tstInteractiveId ) ; } }; typedef std::list Styles; struct Run { StyleImpl* mStyle; Range mRange; int mLeft, mRight; int mPrintableChars; }; typedef std::vector Runs; struct Line { Runs mRuns; MyGUI::IntRect mRect; }; typedef std::vector Lines; struct Section { Lines mLines; MyGUI::IntRect mRect; }; typedef std::vector

Sections; // Holds "top" and "bottom" vertical coordinates in the source text. // A page is basically a "window" into a portion of the source text, similar to a ScrollView. typedef std::pair Page; typedef std::vector Pages; Pages mPages; Sections mSections; Contents mContents; Styles mStyles; MyGUI::IntRect mRect; virtual ~TypesetBookImpl () {} Range addContent (const BookTypesetter::Utf8Span &text) { Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); if (i->empty()) return Range (Utf8Point (nullptr), Utf8Point (nullptr)); return Range (i->data(), i->data() + i->size()); } size_t pageCount () const override { return mPages.size (); } std::pair getSize () const override { return std::make_pair (mRect.width (), mRect.height ()); } template void visitRuns (int top, int bottom, MyGUI::IFont* Font, Visitor const & visitor) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) { if (top >= mRect.bottom || bottom <= i->mRect.top) continue; for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (top >= j->mRect.bottom || bottom <= j->mRect.top) continue; for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) if (!Font || k->mStyle->mFont == Font) visitor (*i, *j, *k); } } } template void visitRuns (int top, int bottom, Visitor const & visitor) const { visitRuns (top, bottom, nullptr, visitor); } /// hit test with a margin for error. only hits on interactive text fragments are reported. StyleImpl * hitTestWithMargin (int left, int top) { StyleImpl * hit = hitTest(left, top); if (hit && hit->mInteractiveId != 0) return hit; const int maxMargin = 10; for (int margin=1; margin < maxMargin; ++margin) { for (int i=0; i<4; ++i) { if (i==0) hit = hitTest(left, top-margin); else if (i==1) hit = hitTest(left, top+margin); else if (i==2) hit = hitTest(left-margin, top); else hit = hitTest(left+margin, top); if (hit && hit->mInteractiveId != 0) return hit; } } return nullptr; } StyleImpl * hitTest (int left, int top) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) { if (top < i->mRect.top || top >= i->mRect.bottom) continue; int left1 = left - i->mRect.left; for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (top < j->mRect.top || top >= j->mRect.bottom) continue; int left2 = left1 - j->mRect.left; for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) { if (left2 < k->mLeft || left2 >= k->mRight) continue; return k->mStyle; } } } return nullptr; } MyGUI::IFont* affectedFont (StyleImpl* style) { for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i) if (&*i == style) return i->mFont; return nullptr; } struct Typesetter; }; struct TypesetBookImpl::Typesetter : BookTypesetter { struct PartialText { StyleImpl *mStyle; Utf8Stream::Point mBegin; Utf8Stream::Point mEnd; int mWidth; PartialText( StyleImpl *style, Utf8Stream::Point begin, Utf8Stream::Point end, int width) : mStyle(style), mBegin(begin), mEnd(end), mWidth(width) {} }; typedef TypesetBookImpl Book; typedef std::shared_ptr BookPtr; typedef std::vector::const_iterator PartialTextConstIterator; int mPageWidth; int mPageHeight; BookPtr mBook; Section * mSection; Line * mLine; Run * mRun; std::vector mSectionAlignment; std::vector mPartialWhitespace; std::vector mPartialWord; Book::Content const * mCurrentContent; Alignment mCurrentAlignment; Typesetter (size_t width, size_t height) : mPageWidth (width), mPageHeight(height), mSection (nullptr), mLine (nullptr), mRun (nullptr), mCurrentContent (nullptr), mCurrentAlignment (AlignLeft) { mBook = std::make_shared (); } virtual ~Typesetter () { } Style * createStyle (const std::string& fontName, const Colour& fontColour, bool useBookFont) override { std::string fullFontName; if (fontName.empty()) fullFontName = MyGUI::FontManager::getInstance().getDefaultFont(); else fullFontName = fontName; if (useBookFont) fullFontName = "Journalbook " + fullFontName; for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) if (i->match (fullFontName.c_str(), fontColour, fontColour, fontColour, 0)) return &*i; MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fullFontName); if (!font) throw std::runtime_error(std::string("can't find font ") + fullFontName); StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); style.mFont = font; style.mHotColour = fontColour; style.mActiveColour = fontColour; style.mNormalColour = fontColour; style.mInteractiveId = 0; return &style; } Style* createHotStyle (Style* baseStyle, const Colour& normalColour, const Colour& hoverColour, const Colour& activeColour, InteractiveId id, bool unique) override { StyleImpl* BaseStyle = static_cast (baseStyle); if (!unique) for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) if (i->match (BaseStyle->mFont, hoverColour, activeColour, normalColour, id)) return &*i; StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); style.mFont = BaseStyle->mFont; style.mHotColour = hoverColour; style.mActiveColour = activeColour; style.mNormalColour = normalColour; style.mInteractiveId = id; return &style; } void write (Style * style, Utf8Span text) override { Range range = mBook->addContent (text); writeImpl (static_cast (style), range.first, range.second); } intptr_t addContent (Utf8Span text, bool select) override { add_partial_text(); Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second)); if (select) mCurrentContent = &(*i); return reinterpret_cast (&(*i)); } void selectContent (intptr_t contentHandle) override { add_partial_text(); mCurrentContent = reinterpret_cast (contentHandle); } void write (Style * style, size_t begin, size_t end) override { assert (mCurrentContent != nullptr); assert (end <= mCurrentContent->size ()); assert (begin <= mCurrentContent->size ()); Utf8Point begin_ = mCurrentContent->data() + begin; Utf8Point end_ = mCurrentContent->data() + end; writeImpl (static_cast (style), begin_, end_); } void lineBreak (float margin) override { assert (margin == 0); //TODO: figure out proper behavior here... add_partial_text(); mRun = nullptr; mLine = nullptr; } void sectionBreak (int margin) override { add_partial_text(); if (mBook->mSections.size () > 0) { mRun = nullptr; mLine = nullptr; mSection = nullptr; if (mBook->mRect.bottom < (mBook->mSections.back ().mRect.bottom + margin)) mBook->mRect.bottom = (mBook->mSections.back ().mRect.bottom + margin); } } void setSectionAlignment (Alignment sectionAlignment) override { add_partial_text(); if (mSection != nullptr) mSectionAlignment.back () = sectionAlignment; mCurrentAlignment = sectionAlignment; } TypesetBook::Ptr complete () override { int curPageStart = 0; int curPageStop = 0; add_partial_text(); std::vector ::iterator sa = mSectionAlignment.begin (); for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa) { // apply alignment to individual lines... for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { int width = j->mRect.width (); int excess = mPageWidth - width; switch (*sa) { default: case AlignLeft: j->mRect.left = 0; break; case AlignCenter: j->mRect.left = excess/2; break; case AlignRight: j->mRect.left = excess; break; } j->mRect.right = j->mRect.left + width; } if (curPageStop == curPageStart) { curPageStart = i->mRect.top; curPageStop = i->mRect.top; } int spaceLeft = mPageHeight - (curPageStop - curPageStart); int sectionHeight = i->mRect.height (); // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. int spaceRequired = (i->mRect.bottom - curPageStop); if (curPageStart == curPageStop) // If this is a new page, the section break is not needed spaceRequired = i->mRect.height(); if (spaceRequired <= mPageHeight) { if (spaceRequired > spaceLeft) { // The section won't completely fit on the current page. Finish the current page and start a new one. assert (curPageStart != curPageStop); mBook->mPages.push_back (Page (curPageStart, curPageStop)); curPageStart = i->mRect.top; curPageStop = i->mRect.bottom; } else curPageStop = i->mRect.bottom; } else { // The section won't completely fit on the current page. Finish the current page and start a new one. mBook->mPages.push_back (Page (curPageStart, curPageStop)); curPageStart = i->mRect.top; curPageStop = i->mRect.bottom; //split section int sectionHeightLeft = sectionHeight; while (sectionHeightLeft >= mPageHeight) { // Adjust to the top of the first line that does not fit on the current page anymore int splitPos = curPageStop; for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) { if (j->mRect.bottom > curPageStart + mPageHeight) { splitPos = j->mRect.top; break; } } mBook->mPages.push_back (Page (curPageStart, splitPos)); curPageStart = splitPos; curPageStop = splitPos; sectionHeightLeft = (i->mRect.bottom - splitPos); } curPageStop = i->mRect.bottom; } } if (curPageStart != curPageStop) mBook->mPages.push_back (Page (curPageStart, curPageStop)); return mBook; } void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end) { Utf8Stream stream (_begin, _end); while (!stream.eof ()) { if (ucsLineBreak (stream.peek ())) { add_partial_text(); stream.consume (); mLine = nullptr; mRun = nullptr; continue; } if (ucsBreakingSpace (stream.peek ()) && !mPartialWord.empty()) add_partial_text(); int word_width = 0; int space_width = 0; Utf8Stream::Point lead = stream.current (); while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) { MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); if (info.charFound) space_width += static_cast(info.advance + info.bearingX); stream.consume (); } Utf8Stream::Point origin = stream.current (); while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) { MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); if (info.charFound) word_width += static_cast(info.advance + info.bearingX); stream.consume (); } Utf8Stream::Point extent = stream.current (); if (lead == extent) break; if ( lead != origin ) mPartialWhitespace.emplace_back(style, lead, origin, space_width); if ( origin != extent ) mPartialWord.emplace_back(style, origin, extent, word_width); } } void add_partial_text () { if (mPartialWhitespace.empty() && mPartialWord.empty()) return; int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); int space_width = 0; int word_width = 0; for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) space_width += i->mWidth; for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) word_width += i->mWidth; int left = mLine ? mLine->mRect.right : 0; if (left + space_width + word_width > mPageWidth) { mLine = nullptr; mRun = nullptr; left = 0; } else { for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + fontHeight); left = mLine->mRect.right; } } for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + fontHeight); left = mLine->mRect.right; } mPartialWhitespace.clear(); mPartialWord.clear(); } void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) { if (mSection == nullptr) { mBook->mSections.push_back (Section ()); mSection = &mBook->mSections.back (); mSection->mRect = MyGUI::IntRect (0, mBook->mRect.bottom, 0, mBook->mRect.bottom); mSectionAlignment.push_back (mCurrentAlignment); } if (mLine == nullptr) { mSection->mLines.push_back (Line ()); mLine = &mSection->mLines.back (); mLine->mRect = MyGUI::IntRect (0, mSection->mRect.bottom, 0, mBook->mRect.bottom); } if (mBook->mRect.right < right) mBook->mRect.right = right; if (mBook->mRect.bottom < bottom) mBook->mRect.bottom = bottom; if (mSection->mRect.right < right) mSection->mRect.right = right; if (mSection->mRect.bottom < bottom) mSection->mRect.bottom = bottom; if (mLine->mRect.right < right) mLine->mRect.right = right; if (mLine->mRect.bottom < bottom) mLine->mRect.bottom = bottom; if (mRun == nullptr || mRun->mStyle != style || mRun->mRange.second != begin) { int left = mRun ? mRun->mRight : mLine->mRect.left; mLine->mRuns.push_back (Run ()); mRun = &mLine->mRuns.back (); mRun->mStyle = style; mRun->mLeft = left; mRun->mRight = right; mRun->mRange.first = begin; mRun->mRange.second = end; mRun->mPrintableChars = pc; //Run->Locale = Locale; } else { mRun->mRight = right; mRun->mRange.second = end; mRun->mPrintableChars += pc; } } }; BookTypesetter::Ptr BookTypesetter::create (int pageWidth, int pageHeight) { return std::make_shared (pageWidth, pageHeight); } namespace { struct RenderXform { public: float clipTop; float clipLeft; float clipRight; float clipBottom; float absoluteLeft; float absoluteTop; float leftOffset; float topOffset; float pixScaleX; float pixScaleY; float hOffset; float vOffset; RenderXform (MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const & renderTargetInfo) { clipTop = static_cast(croppedParent->_getMarginTop()); clipLeft = static_cast(croppedParent->_getMarginLeft ()); clipRight = static_cast(croppedParent->getWidth () - croppedParent->_getMarginRight ()); clipBottom = static_cast(croppedParent->getHeight() - croppedParent->_getMarginBottom()); absoluteLeft = static_cast(croppedParent->getAbsoluteLeft()); absoluteTop = static_cast(croppedParent->getAbsoluteTop()); leftOffset = static_cast(renderTargetInfo.leftOffset); topOffset = static_cast(renderTargetInfo.topOffset); pixScaleX = renderTargetInfo.pixScaleX; pixScaleY = renderTargetInfo.pixScaleY; hOffset = renderTargetInfo.hOffset; vOffset = renderTargetInfo.vOffset; } bool clip (MyGUI::FloatRect & vr, MyGUI::FloatRect & tr) { if (vr.bottom <= clipTop || vr.right <= clipLeft || vr.left >= clipRight || vr.top >= clipBottom ) return false; if (vr.top < clipTop) { tr.top += tr.height () * (clipTop - vr.top) / vr.height (); vr.top = clipTop; } if (vr.left < clipLeft) { tr.left += tr.width () * (clipLeft - vr.left) / vr.width (); vr.left = clipLeft; } if (vr.right > clipRight) { tr.right -= tr.width () * (vr.right - clipRight) / vr.width (); vr.right = clipRight; } if (vr.bottom > clipBottom) { tr.bottom -= tr.height () * (vr.bottom - clipBottom) / vr.height (); vr.bottom = clipBottom; } return true; } MyGUI::FloatPoint operator () (MyGUI::FloatPoint pt) { pt.left = absoluteLeft - leftOffset + pt.left; pt.top = absoluteTop - topOffset + pt.top; pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f); pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f); return pt; } }; struct GlyphStream { float mZ; uint32_t mC; MyGUI::IFont* mFont; MyGUI::FloatPoint mOrigin; MyGUI::FloatPoint mCursor; MyGUI::Vertex* mVertices; RenderXform mRenderXform; MyGUI::VertexColourType mVertexColourType; GlyphStream (MyGUI::IFont* font, float left, float top, float Z, MyGUI::Vertex* vertices, RenderXform const & renderXform) : mZ(Z), mC(0), mFont (font), mOrigin (left, top), mVertices (vertices), mRenderXform (renderXform) { assert(font != nullptr); mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); } ~GlyphStream () = default; MyGUI::Vertex* end () const { return mVertices; } void reset (float left, float top, MyGUI::Colour colour) { mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000; MyGUI::texture_utility::convertColour(mC, mVertexColourType); mCursor.left = mOrigin.left + left; mCursor.top = mOrigin.top + top; } void emitGlyph (wchar_t ch) { MWGui::GlyphInfo info = GlyphInfo(mFont, ch); if (!info.charFound) return; MyGUI::FloatRect vr; vr.left = mCursor.left + info.bearingX; vr.top = mCursor.top + info.bearingY; vr.right = vr.left + info.width; vr.bottom = vr.top + info.height; MyGUI::FloatRect tr = info.uvRect; if (mRenderXform.clip (vr, tr)) quad (vr, tr); mCursor.left += static_cast(info.bearingX + info.advance); } void emitSpace (wchar_t ch) { MWGui::GlyphInfo info = GlyphInfo(mFont, ch); if (info.charFound) mCursor.left += static_cast(info.bearingX + info.advance); } private: void quad (const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr) { vertex (vr.left, vr.top, tr.left, tr.top); vertex (vr.right, vr.top, tr.right, tr.top); vertex (vr.left, vr.bottom, tr.left, tr.bottom); vertex (vr.right, vr.top, tr.right, tr.top); vertex (vr.left, vr.bottom, tr.left, tr.bottom); vertex (vr.right, vr.bottom, tr.right, tr.bottom); } void vertex (float x, float y, float u, float v) { MyGUI::FloatPoint pt = mRenderXform (MyGUI::FloatPoint (x, y)); mVertices->x = pt.left; mVertices->y = pt.top ; mVertices->z = mZ; mVertices->u = u; mVertices->v = v; mVertices->colour = mC; ++mVertices; } }; } class PageDisplay final : public MyGUI::ISubWidgetText { MYGUI_RTTI_DERIVED(PageDisplay) protected: typedef TypesetBookImpl::Section Section; typedef TypesetBookImpl::Line Line; typedef TypesetBookImpl::Run Run; bool mIsPageReset; size_t mPage; struct TextFormat : ISubWidget { typedef MyGUI::IFont* Id; Id mFont; int mCountVertex; MyGUI::ITexture* mTexture; MyGUI::RenderItem* mRenderItem; PageDisplay * mDisplay; TextFormat (MyGUI::IFont* id, PageDisplay * display) : mFont (id), mCountVertex (0), mTexture (nullptr), mRenderItem (nullptr), mDisplay (display) { } void createDrawItem (MyGUI::ILayerNode* node) { assert (mRenderItem == nullptr); if (mTexture != nullptr) { mRenderItem = node->addToRenderItem(mTexture, false, false); mRenderItem->addDrawItem(this, mCountVertex); } } void destroyDrawItem (MyGUI::ILayerNode* node) { assert (mTexture != nullptr ? mRenderItem != nullptr : mRenderItem == nullptr); if (mTexture != nullptr) { mRenderItem->removeDrawItem (this); mRenderItem = nullptr; } } void doRender() override { mDisplay->doRender (*this); } // this isn't really a sub-widget, its just a "drawitem" which // should have its own interface void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) override {} void destroyDrawItem() override {} }; void resetPage() { mIsPageReset = true; mPage = 0; } void setPage(size_t page) { mIsPageReset = false; mPage = page; } bool isPageDifferent(size_t page) { return mIsPageReset || (mPage != page); } std::optional getAdjustedPos(int left, int top, bool move = false) { if (!mBook) return {}; if (mPage >= mBook->mPages.size()) return {}; MyGUI::IntPoint pos (left, top); pos.left -= mCroppedParent->getAbsoluteLeft (); pos.top -= mCroppedParent->getAbsoluteTop (); pos.top += mViewTop; return pos; } public: typedef TypesetBookImpl::StyleImpl Style; typedef std::map > ActiveTextFormats; int mViewTop; int mViewBottom; Style* mFocusItem; bool mItemActive; MyGUI::MouseButton mLastDown; std::function mLinkClicked; std::shared_ptr mBook; MyGUI::ILayerNode* mNode; ActiveTextFormats mActiveTextFormats; PageDisplay () { resetPage (); mViewTop = 0; mViewBottom = 0; mFocusItem = nullptr; mItemActive = false; mNode = nullptr; } void dirtyFocusItem () { if (mFocusItem != nullptr) { MyGUI::IFont* Font = mBook->affectedFont (mFocusItem); ActiveTextFormats::iterator i = mActiveTextFormats.find (Font); if (mNode) mNode->outOfDate (i->second->mRenderItem); } } void onMouseLostFocus () { if (!mBook) return; if (mPage >= mBook->mPages.size()) return; dirtyFocusItem (); mFocusItem = nullptr; mItemActive = false; } void onMouseMove (int left, int top) { Style * hit = nullptr; if(auto pos = getAdjustedPos(left, top, true)) if(pos->top <= mViewBottom) hit = mBook->hitTestWithMargin (pos->left, pos->top); if (mLastDown == MyGUI::MouseButton::None) { if (hit != mFocusItem) { dirtyFocusItem (); mFocusItem = hit; mItemActive = false; dirtyFocusItem (); } } else if (mFocusItem != nullptr) { bool newItemActive = hit == mFocusItem; if (newItemActive != mItemActive) { mItemActive = newItemActive; dirtyFocusItem (); } } } void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { auto pos = getAdjustedPos(left, top); if (pos && mLastDown == MyGUI::MouseButton::None) { mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; mItemActive = true; dirtyFocusItem (); mLastDown = id; } } void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { auto pos = getAdjustedPos(left, top); if (pos && mLastDown == id) { Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; bool clicked = mFocusItem == item; mItemActive = false; dirtyFocusItem (); mLastDown = MyGUI::MouseButton::None; if (clicked && mLinkClicked && item && item->mInteractiveId != 0) mLinkClicked (item->mInteractiveId); } } void showPage (TypesetBook::Ptr book, size_t newPage) { std::shared_ptr newBook = std::dynamic_pointer_cast (book); if (mBook != newBook) { mFocusItem = nullptr; mItemActive = 0; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) { if (mNode != nullptr && i->second != nullptr) i->second->destroyDrawItem (mNode); i->second.reset(); } mActiveTextFormats.clear (); if (newBook != nullptr) { createActiveFormats (newBook); mBook = newBook; setPage (newPage); if (newPage < mBook->mPages.size ()) { mViewTop = mBook->mPages [newPage].first; mViewBottom = mBook->mPages [newPage].second; } else { mViewTop = 0; mViewBottom = 0; } } else { mBook.reset (); resetPage (); mViewTop = 0; mViewBottom = 0; } } else if (mBook && isPageDifferent (newPage)) { if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); setPage (newPage); if (newPage < mBook->mPages.size ()) { mViewTop = mBook->mPages [newPage].first; mViewBottom = mBook->mPages [newPage].second; } else { mViewTop = 0; mViewBottom = 0; } } } struct CreateActiveFormat { PageDisplay * this_; CreateActiveFormat (PageDisplay * this_) : this_ (this_) {} void operator () (Section const & section, Line const & line, Run const & run) const { MyGUI::IFont* Font = run.mStyle->mFont; ActiveTextFormats::iterator j = this_->mActiveTextFormats.find (Font); if (j == this_->mActiveTextFormats.end ()) { auto textFormat = std::make_unique(Font, this_); textFormat->mTexture = Font->getTextureFont (); j = this_->mActiveTextFormats.insert (std::make_pair (Font, std::move(textFormat))).first; } j->second->mCountVertex += run.mPrintableChars * 6; } }; void createActiveFormats (std::shared_ptr newBook) { newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this)); if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->createDrawItem (mNode); } void setVisible (bool newVisible) override { if (mVisible == newVisible) return; mVisible = newVisible; if (mVisible) { // reset input state mLastDown = MyGUI::MouseButton::None; mFocusItem = nullptr; mItemActive = 0; } if (nullptr != mNode) { for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); } } void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) override { mNode = node; for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->createDrawItem (node); } struct RenderRun { PageDisplay * this_; GlyphStream &glyphStream; RenderRun (PageDisplay * this_, GlyphStream &glyphStream) : this_(this_), glyphStream (glyphStream) { } void operator () (Section const & section, Line const & line, Run const & run) const { bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem); MyGUI::Colour colour = isActive ? (this_->mItemActive ? run.mStyle->mActiveColour: run.mStyle->mHotColour) : run.mStyle->mNormalColour; glyphStream.reset(static_cast(section.mRect.left + line.mRect.left + run.mLeft), static_cast(line.mRect.top), colour); Utf8Stream stream (run.mRange); while (!stream.eof ()) { Utf8Stream::UnicodeChar code_point = stream.consume (); if (ucsCarriageReturn (code_point)) continue; if (!ucsSpace (code_point)) glyphStream.emitGlyph (code_point); else glyphStream.emitSpace (code_point); } } }; /* queue up rendering operations for this text format */ void doRender(TextFormat & textFormat) { if (!mVisible) return; MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer(); RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); float z = SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f; GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), z /*mNode->getNodeDepth()*/, vertices, renderXform); int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop )); int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom)); mBook->visitRuns (visit_top, visit_bottom, textFormat.mFont, RenderRun (this, glyphStream)); textFormat.mRenderItem->setLastVertexCount(glyphStream.end () - vertices); } // ISubWidget should not necessarily be a drawitem // in this case, it is not... void doRender() override { } void _updateView () override { _checkMargin(); if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); } void _correctView() override { _checkMargin (); if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); } void destroyDrawItem() override { for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->destroyDrawItem (mNode); mNode = nullptr; } }; class BookPageImpl final : public BookPage { MYGUI_RTTI_DERIVED(BookPage) public: BookPageImpl() : mPageDisplay(nullptr) { } void showPage (TypesetBook::Ptr book, size_t page) override { mPageDisplay->showPage (book, page); } void adviseLinkClicked (std::function linkClicked) override { mPageDisplay->mLinkClicked = linkClicked; } void unadviseLinkClicked () override { mPageDisplay->mLinkClicked = std::function (); } protected: void initialiseOverride() override { Base::initialiseOverride(); if (getSubWidgetText()) { mPageDisplay = getSubWidgetText()->castType(); } else { throw std::runtime_error("BookPage unable to find page display sub widget"); } } void onMouseLostFocus(Widget* _new) override { // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). // Child widgets may already be destroyed! So be careful. mPageDisplay->onMouseLostFocus (); } void onMouseMove(int left, int top) override { mPageDisplay->onMouseMove (left, top); } void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) override { mPageDisplay->onMouseButtonPressed (left, top, id); } void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) override { mPageDisplay->onMouseButtonReleased (left, top, id); } PageDisplay* mPageDisplay; }; void BookPage::registerMyGUIComponents () { MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance(); factory.registerFactory("Widget"); factory.registerFactory("BasisSkin"); } static bool ucsLineBreak (int codePoint) { return codePoint == '\n'; } static bool ucsCarriageReturn (int codePoint) { return codePoint == '\r'; } static bool ucsSpace (int codePoint) { switch (codePoint) { case 0x0020: // SPACE case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD case 0x2001: // EM QUAD case 0x2002: // EN SPACE case 0x2003: // EM SPACE case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x200B: // ZERO WIDTH SPACE case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x3000: // IDEOGRAPHIC SPACE case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE return true; default: return false; } } static bool ucsBreakingSpace (int codePoint) { switch (codePoint) { case 0x0020: // SPACE //case 0x00A0: // NO-BREAK SPACE case 0x1680: // OGHAM SPACE MARK case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x2000: // EN QUAD case 0x2001: // EM QUAD case 0x2002: // EN SPACE case 0x2003: // EM SPACE case 0x2004: // THREE-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE case 0x2007: // FIGURE SPACE case 0x2008: // PUNCTUATION SPACE case 0x2009: // THIN SPACE case 0x200A: // HAIR SPACE case 0x200B: // ZERO WIDTH SPACE case 0x202F: // NARROW NO-BREAK SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x3000: // IDEOGRAPHIC SPACE //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE return true; default: return false; } } } openmw-openmw-0.48.0/apps/openmw/mwgui/bookpage.hpp000066400000000000000000000145651445372753700223470ustar00rootroot00000000000000#ifndef MWGUI_BOOKPAGE_HPP #define MWGUI_BOOKPAGE_HPP #include "MyGUI_Colour.h" #include "MyGUI_Widget.h" #include "MyGUI_FontManager.h" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" namespace MWGui { /// A formatted and paginated document to be used with /// the book page widget. struct TypesetBook { typedef std::shared_ptr Ptr; typedef intptr_t InteractiveId; /// Returns the number of pages in the document. virtual size_t pageCount () const = 0; /// Return the area covered by the document. The first /// integer is the maximum with of any line. This is not /// the largest coordinate of the right edge of any line, /// it is the largest distance from the left edge to the /// right edge. The second integer is the height of all /// text combined prior to pagination. virtual std::pair getSize () const = 0; virtual ~TypesetBook() = default; }; struct GlyphInfo { char codePoint; float width; float height; float advance; float bearingX; float bearingY; bool charFound; MyGUI::FloatRect uvRect; GlyphInfo(MyGUI::IFont* font, MyGUI::Char ch) { static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); const MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); if (gi) { const float scale = font->getDefaultHeight() / (float) fontHeight; codePoint = gi->codePoint; bearingX = (int) gi->bearingX / scale; bearingY = (int) gi->bearingY / scale; width = (int) gi->width / scale; height = (int) gi->height / scale; advance = (int) gi->advance / scale; uvRect = gi->uvRect; charFound = true; } else { codePoint = 0; bearingX = 0; bearingY = 0; width = 0; height = 0; advance = 0; charFound = false; } } }; /// A factory class for creating a typeset book instance. struct BookTypesetter { typedef std::shared_ptr Ptr; typedef TypesetBook::InteractiveId InteractiveId; typedef MyGUI::Colour Colour; typedef uint8_t const * Utf8Point; typedef std::pair Utf8Span; virtual ~BookTypesetter() = default; enum Alignment { AlignLeft = -1, AlignCenter = 0, AlignRight = +1 }; /// Styles are used to control the character level formatting /// of text added to a typeset book. Their lifetime is equal /// to the lifetime of the book-typesetter instance that created /// them. struct Style; /// A factory function for creating the default implementation of a book typesetter static Ptr create (int pageWidth, int pageHeight); /// Create a simple text style consisting of a font and a text color. virtual Style* createStyle (const std::string& fontName, const Colour& colour, bool useBookFont=true) = 0; /// Create a hyper-link style with a user-defined identifier based on an /// existing style. The unique flag forces a new instance of this style /// to be created even if an existing instance is present. virtual Style* createHotStyle (Style * BaseStyle, const Colour& NormalColour, const Colour& HoverColour, const Colour& ActiveColour, InteractiveId Id, bool Unique = true) = 0; /// Insert a line break into the document. Newline characters in the input /// text have the same affect. The margin parameter adds additional space /// before the next line of text. virtual void lineBreak (float margin = 0) = 0; /// Insert a section break into the document. This causes a new section /// to begin when additional text is inserted. Pagination attempts to keep /// sections together on a single page. The margin parameter adds additional space /// before the next line of text. virtual void sectionBreak (int margin = 0) = 0; /// Changes the alignment for the current section of text. virtual void setSectionAlignment (Alignment sectionAlignment) = 0; // Layout a block of text with the specified style into the document. virtual void write (Style * Style, Utf8Span Text) = 0; /// Adds a content block to the document without laying it out. An /// identifier is returned that can be used to refer to it. If select /// is true, the block is activated to be references by future writes. virtual intptr_t addContent (Utf8Span Text, bool Select = true) = 0; /// Select a previously created content block for future writes. virtual void selectContent (intptr_t contentHandle) = 0; /// Layout a span of the selected content block into the document /// using the specified style. virtual void write (Style * Style, size_t Begin, size_t End) = 0; /// Finalize the document layout, and return a pointer to it. virtual TypesetBook::Ptr complete () = 0; }; /// An interface to the BookPage widget. class BookPage : public MyGUI::Widget { MYGUI_RTTI_DERIVED(BookPage) public: typedef TypesetBook::InteractiveId InteractiveId; typedef std::function ClickCallback; /// Make the widget display the specified page from the specified book. virtual void showPage (TypesetBook::Ptr Book, size_t Page) = 0; /// Set the callback for a clicking a hyper-link in the document. virtual void adviseLinkClicked (ClickCallback callback) = 0; /// Clear the hyper-link click callback. virtual void unadviseLinkClicked () = 0; /// Register the widget and associated sub-widget with MyGUI. Should be /// called once near the beginning of the program. static void registerMyGUIComponents (); }; } #endif // MWGUI_BOOKPAGE_HPP openmw-openmw-0.48.0/apps/openmw/mwgui/bookwindow.cpp000066400000000000000000000154211445372753700227250ustar00rootroot00000000000000#include "bookwindow.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/actiontake.hpp" #include "../mwworld/class.hpp" #include "formatting.hpp" namespace MWGui { BookWindow::BookWindow () : BookWindowBase("openmw_book.layout") , mCurrentPage(0) , mTakeButtonShow(true) , mTakeButtonAllowed(true) { getWidget(mCloseButton, "CloseButton"); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onCloseButtonClicked); getWidget(mTakeButton, "TakeButton"); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onTakeButtonClicked); getWidget(mNextPageButton, "NextPageBTN"); mNextPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onNextPageButtonClicked); getWidget(mPrevPageButton, "PrevPageBTN"); mPrevPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onPrevPageButtonClicked); getWidget(mLeftPageNumber, "LeftPageNumber"); getWidget(mRightPageNumber, "RightPageNumber"); getWidget(mLeftPage, "LeftPage"); getWidget(mRightPage, "RightPage"); adjustButton("CloseButton"); adjustButton("TakeButton"); adjustButton("PrevPageBTN"); float scale = adjustButton("NextPageBTN"); mLeftPage->setNeedMouseFocus(true); mLeftPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); mRightPage->setNeedMouseFocus(true); mRightPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); mNextPageButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); mPrevPageButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); mTakeButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &BookWindow::onKeyButtonPressed); if (mNextPageButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge mNextPageButton->setSize(64-7, mNextPageButton->getSize().height); mNextPageButton->setImageCoord(MyGUI::IntCoord(0,0,(64-7)*scale,mNextPageButton->getSize().height*scale)); } center(); } void BookWindow::onMouseWheel(MyGUI::Widget *_sender, int _rel) { if (_rel < 0) nextPage(); else prevPage(); } void BookWindow::clearPages() { mPages.clear(); } void BookWindow::setPtr (const MWWorld::Ptr& book) { mBook = book; MWWorld::Ptr player = MWMechanics::getPlayer(); bool showTakeButton = book.getContainerStore() != &player.getClass().getContainerStore(player); clearPages(); mCurrentPage = 0; MWWorld::LiveCellRef *ref = mBook.get(); Formatting::BookFormatter formatter; mPages = formatter.markupToWidget(mLeftPage, ref->mBase->mText); formatter.markupToWidget(mRightPage, ref->mBase->mText); updatePages(); setTakeButtonShow(showTakeButton); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); } void BookWindow::setTakeButtonShow(bool show) { mTakeButtonShow = show; mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } void BookWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) prevPage(); else if (key == MyGUI::KeyCode::ArrowDown) nextPage(); } void BookWindow::setInventoryAllowed(bool allowed) { mTakeButtonAllowed = allowed; mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->playSound("Item Book Up"); MWWorld::ActionTake take(mBook); take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } void BookWindow::onNextPageButtonClicked (MyGUI::Widget* sender) { nextPage(); } void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* sender) { prevPage(); } void BookWindow::updatePages() { mLeftPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 1) ); mRightPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 2) ); MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool nextPageVisible = (mCurrentPage+1)*2 < mPages.size(); mNextPageButton->setVisible(nextPageVisible); bool prevPageVisible = mCurrentPage != 0; mPrevPageButton->setVisible(prevPageVisible); if (focus == mNextPageButton && !nextPageVisible && prevPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mPrevPageButton); else if (focus == mPrevPageButton && !prevPageVisible && nextPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNextPageButton); if (mPages.empty()) return; MyGUI::Widget * paper; paper = mLeftPage->getChildAt(0); paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2].first, paper->getWidth(), mPages[mCurrentPage*2].second); paper = mRightPage->getChildAt(0); if ((mCurrentPage+1)*2 <= mPages.size()) { paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2+1].first, paper->getWidth(), mPages[mCurrentPage*2+1].second); paper->setVisible(true); } else { paper->setVisible(false); } } void BookWindow::nextPage() { if ((mCurrentPage+1)*2 < mPages.size()) { MWBase::Environment::get().getWindowManager()->playSound("book page2"); ++mCurrentPage; updatePages(); } } void BookWindow::prevPage() { if (mCurrentPage > 0) { MWBase::Environment::get().getWindowManager()->playSound("book page"); --mCurrentPage; updatePages(); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/bookwindow.hpp000066400000000000000000000033031445372753700227260ustar00rootroot00000000000000#ifndef MWGUI_BOOKWINDOW_H #define MWGUI_BOOKWINDOW_H #include "windowbase.hpp" #include "../mwworld/ptr.hpp" #include namespace MWGui { class BookWindow : public BookWindowBase { public: BookWindow(); void setPtr(const MWWorld::Ptr& book) override; void setInventoryAllowed(bool allowed); void onResChange(int, int) override { center(); } protected: void onNextPageButtonClicked (MyGUI::Widget* sender); void onPrevPageButtonClicked (MyGUI::Widget* sender); void onCloseButtonClicked (MyGUI::Widget* sender); void onTakeButtonClicked (MyGUI::Widget* sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void setTakeButtonShow(bool show); void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); void nextPage(); void prevPage(); void updatePages(); void clearPages(); private: typedef std::pair Page; typedef std::vector Pages; Gui::ImageButton* mCloseButton; Gui::ImageButton* mTakeButton; Gui::ImageButton* mNextPageButton; Gui::ImageButton* mPrevPageButton; MyGUI::TextBox* mLeftPageNumber; MyGUI::TextBox* mRightPageNumber; MyGUI::Widget* mLeftPage; MyGUI::Widget* mRightPage; unsigned int mCurrentPage; // 0 is first page Pages mPages; MWWorld::Ptr mBook; bool mTakeButtonShow; bool mTakeButtonAllowed; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/charactercreation.cpp000066400000000000000000000724721445372753700242350ustar00rootroot00000000000000#include "charactercreation.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "textinput.hpp" #include "race.hpp" #include "class.hpp" #include "birth.hpp" #include "review.hpp" #include "inventorywindow.hpp" namespace { struct Response { const std::string mText; const ESM::Class::Specialization mSpecialization; }; struct Step { const std::string mText; const Response mResponses[3]; const std::string mSound; }; Step sGenerateClassSteps(int number) { number++; std::string question = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question"); std::string answer0 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne"); std::string answer1 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo"); std::string answer2 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerThree"); std::string sound = "vo\\misc\\chargen qa" + MyGUI::utility::toString(number) + ".wav"; Response r0 = {answer0, ESM::Class::Combat}; Response r1 = {answer1, ESM::Class::Magic}; Response r2 = {answer2, ESM::Class::Stealth}; // randomize order in which responses are displayed int order = Misc::Rng::rollDice(6); switch (order) { case 0: return {question, {r0, r1, r2}, sound}; case 1: return {question, {r0, r2, r1}, sound}; case 2: return {question, {r1, r0, r2}, sound}; case 3: return {question, {r1, r2, r0}, sound}; case 4: return {question, {r2, r0, r1}, sound}; default: return {question, {r2, r1, r0}, sound}; } } } namespace MWGui { CharacterCreation::CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) , mResourceSystem(resourceSystem) , mNameDialog(nullptr) , mRaceDialog(nullptr) , mClassChoiceDialog(nullptr) , mGenerateClassQuestionDialog(nullptr) , mGenerateClassResultDialog(nullptr) , mPickClassDialog(nullptr) , mCreateClassDialog(nullptr) , mBirthSignDialog(nullptr) , mReviewDialog(nullptr) , mGenerateClassStep(0) { mCreationStage = CSE_NotStarted; mGenerateClassResponses[0] = ESM::Class::Combat; mGenerateClassResponses[1] = ESM::Class::Magic; mGenerateClassResponses[2] = ESM::Class::Stealth; mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; // Setup player stats for (int i = 0; i < ESM::Attribute::Length; ++i) mPlayerAttributes.emplace(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue()); for (int i = 0; i < ESM::Skill::Length; ++i) mPlayerSkillValues.emplace(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue()); } void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { static const char *ids[] = { "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", "AttribVal6", "AttribVal7", "AttribVal8", 0 }; for (int i=0; ids[i]; ++i) { if (ids[i]==id) { mPlayerAttributes[static_cast(i)] = value; if (mReviewDialog) mReviewDialog->setAttribute(static_cast(i), value); break; } } } void CharacterCreation::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { if (mReviewDialog) { if (id == "HBar") { mReviewDialog->setHealth (value); } else if (id == "MBar") { mReviewDialog->setMagicka (value); } else if (id == "FBar") { mReviewDialog->setFatigue (value); } } } void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { mPlayerSkillValues[parSkill] = value; if (mReviewDialog) mReviewDialog->setSkillValue(parSkill, value); } void CharacterCreation::configureSkills (const SkillList& major, const SkillList& minor) { if (mReviewDialog) mReviewDialog->configureSkills(major, minor); mPlayerMajorSkills = major; mPlayerMinorSkills = minor; } void CharacterCreation::onFrame(float duration) { if (mReviewDialog) mReviewDialog->onFrame(duration); } void CharacterCreation::spawnDialog(const char id) { try { switch (id) { case GM_Name: MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); mNameDialog = nullptr; mNameDialog = new TextInputDialog(); mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); mNameDialog->setTextInput(mPlayerName); mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); mNameDialog->setVisible(true); break; case GM_Race: MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); mRaceDialog = nullptr; mRaceDialog = new RaceDialog(mParent, mResourceSystem); mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); mRaceDialog->setRaceId(mPlayerRaceId); mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); mRaceDialog->setVisible(true); if (mCreationStage < CSE_NameChosen) mCreationStage = CSE_NameChosen; break; case GM_Class: MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); mClassChoiceDialog = nullptr; mClassChoiceDialog = new ClassChoiceDialog(); mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); mClassChoiceDialog->setVisible(true); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; break; case GM_ClassPick: MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); mPickClassDialog = nullptr; mPickClassDialog = new PickClassDialog(); mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mPickClassDialog->setClassId(mPlayerClass.mId); mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); mPickClassDialog->setVisible(true); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; break; case GM_Birth: MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); mBirthSignDialog = nullptr; mBirthSignDialog = new BirthDialog(); mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); mBirthSignDialog->setBirthId(mPlayerBirthSignId); mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); mBirthSignDialog->setVisible(true); if (mCreationStage < CSE_ClassChosen) mCreationStage = CSE_ClassChosen; break; case GM_ClassCreate: if (!mCreateClassDialog) { mCreateClassDialog = new CreateClassDialog(); mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); } mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mCreateClassDialog->setVisible(true); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; break; case GM_ClassGenerate: mGenerateClassStep = 0; mGenerateClass.clear(); mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; showClassQuestionDialog(); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; break; case GM_Review: MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = nullptr; mReviewDialog = new ReviewDialog(); MWBase::World *world = MWBase::Environment::get().getWorld(); const ESM::NPC *playerNpc = world->getPlayerPtr().get()->mBase; const MWWorld::Player player = world->getPlayer(); const ESM::Class *playerClass = world->getStore().get().find(playerNpc->mClass); mReviewDialog->setPlayerName(playerNpc->mName); mReviewDialog->setRace(playerNpc->mRace); mReviewDialog->setClass(*playerClass); mReviewDialog->setBirthSign(player.getBirthSign()); MWWorld::Ptr playerPtr = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = playerPtr.getClass().getCreatureStats(playerPtr); mReviewDialog->setHealth(stats.getHealth()); mReviewDialog->setMagicka(stats.getMagicka()); mReviewDialog->setFatigue(stats.getFatigue()); for (auto& attributePair : mPlayerAttributes) { mReviewDialog->setAttribute(static_cast (attributePair.first), attributePair.second); } for (auto& skillPair : mPlayerSkillValues) { mReviewDialog->setSkillValue(static_cast (skillPair.first), skillPair.second); } mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills); mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); mReviewDialog->setVisible(true); if (mCreationStage < CSE_BirthSignChosen) mCreationStage = CSE_BirthSignChosen; break; } } catch (std::exception& e) { Log(Debug::Error) << "Error: Failed to create chargen window: " << e.what(); } } void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = nullptr; MWBase::Environment::get().getWindowManager()->popGuiMode(); } void CharacterCreation::onReviewDialogBack() { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = nullptr; mCreationStage = CSE_ReviewBack; MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } void CharacterCreation::onReviewActivateDialog(int parDialog) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = nullptr; mCreationStage = CSE_ReviewNext; MWBase::Environment::get().getWindowManager()->popGuiMode(); switch(parDialog) { case ReviewDialog::NAME_DIALOG: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); break; case ReviewDialog::RACE_DIALOG: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race); break; case ReviewDialog::CLASS_DIALOG: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); break; case ReviewDialog::BIRTHSIGN_DIALOG: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); }; } void CharacterCreation::selectPickedClass() { if (mPickClassDialog) { const std::string &classId = mPickClassDialog->getClassId(); if (!classId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); const ESM::Class *klass = MWBase::Environment::get().getWorld()->getStore().get().find(classId); if (klass) { mPlayerClass = *klass; } MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); mPickClassDialog = nullptr; } } void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) { selectPickedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onPickClassDialogBack() { selectPickedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } void CharacterCreation::onClassChoice(int _index) { MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); mClassChoiceDialog = nullptr; MWBase::Environment::get().getWindowManager()->popGuiMode(); switch(_index) { case ClassChoiceDialog::Class_Generate: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassGenerate); break; case ClassChoiceDialog::Class_Pick: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassPick); break; case ClassChoiceDialog::Class_Create: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassCreate); break; case ClassChoiceDialog::Class_Back: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race); break; }; } void CharacterCreation::onNameDialogDone(WindowBase* parWindow) { if (mNameDialog) { mPlayerName = mNameDialog->getTextInput(); MWBase::Environment::get().getMechanicsManager()->setPlayerName(mPlayerName); MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); mNameDialog = nullptr; } handleDialogDone(CSE_NameChosen, GM_Race); } void CharacterCreation::selectRace() { if (mRaceDialog) { const ESM::NPC &data = mRaceDialog->getResult(); mPlayerRaceId = data.mRace; if (!mPlayerRaceId.empty()) { MWBase::Environment::get().getMechanicsManager()->setPlayerRace( data.mRace, data.isMale(), data.mHead, data.mHair ); } MWBase::Environment::get().getWindowManager()->getInventoryWindow()->rebuildAvatar(); MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); mRaceDialog = nullptr; } } void CharacterCreation::onRaceDialogBack() { selectRace(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); } void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) { selectRace(); handleDialogDone(CSE_RaceChosen, GM_Class); } void CharacterCreation::selectBirthSign() { if (mBirthSignDialog) { mPlayerBirthSignId = mBirthSignDialog->getBirthId(); if (!mPlayerBirthSignId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mPlayerBirthSignId); MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); mBirthSignDialog = nullptr; } } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) { selectBirthSign(); handleDialogDone(CSE_BirthSignChosen, GM_Review); } void CharacterCreation::onBirthSignDialogBack() { selectBirthSign(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } void CharacterCreation::selectCreatedClass() { if (mCreateClassDialog) { ESM::Class klass; klass.mName = mCreateClassDialog->getName(); klass.mDescription = mCreateClassDialog->getDescription(); klass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); klass.mData.mIsPlayable = 0x1; klass.mRecordFlags = 0; std::vector attributes = mCreateClassDialog->getFavoriteAttributes(); assert(attributes.size() == 2); klass.mData.mAttribute[0] = attributes[0]; klass.mData.mAttribute[1] = attributes[1]; std::vector majorSkills = mCreateClassDialog->getMajorSkills(); std::vector minorSkills = mCreateClassDialog->getMinorSkills(); assert(majorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0])); assert(minorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0])); for (size_t i = 0; i < sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0]); ++i) { klass.mData.mSkills[i][1] = majorSkills[i]; klass.mData.mSkills[i][0] = minorSkills[i]; } MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass); mPlayerClass = klass; // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } } void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) { selectCreatedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onCreateClassDialogBack() { // not done in MW, but we do it for consistency with the other dialogs selectCreatedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } void CharacterCreation::onClassQuestionChosen(int _index) { MWBase::Environment::get().getSoundManager()->stopSay(); MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); mGenerateClassQuestionDialog = nullptr; if (_index < 0 || _index >= 3) { MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); return; } ESM::Class::Specialization specialization = mGenerateClassResponses[_index]; if (specialization == ESM::Class::Combat) ++mGenerateClassSpecializations[0]; else if (specialization == ESM::Class::Magic) ++mGenerateClassSpecializations[1]; else if (specialization == ESM::Class::Stealth) ++mGenerateClassSpecializations[2]; ++mGenerateClassStep; showClassQuestionDialog(); } void CharacterCreation::showClassQuestionDialog() { if (mGenerateClassStep == 10) { unsigned combat = mGenerateClassSpecializations[0]; unsigned magic = mGenerateClassSpecializations[1]; unsigned stealth = mGenerateClassSpecializations[2]; if (combat > 7) { mGenerateClass = "Warrior"; } else if (magic > 7) { mGenerateClass = "Mage"; } else if (stealth > 7) { mGenerateClass = "Thief"; } else { switch (combat) { case 4: mGenerateClass = "Rogue"; break; case 5: if (stealth == 3) mGenerateClass = "Scout"; else mGenerateClass = "Archer"; break; case 6: if (stealth == 1) mGenerateClass = "Barbarian"; else if (stealth == 3) mGenerateClass = "Crusader"; else mGenerateClass = "Knight"; break; case 7: mGenerateClass = "Warrior"; break; default: switch (magic) { case 4: mGenerateClass = "Spellsword"; break; case 5: mGenerateClass = "Witchhunter"; break; case 6: if (combat == 2) mGenerateClass = "Sorcerer"; else if (combat == 3) mGenerateClass = "Healer"; else mGenerateClass = "Battlemage"; break; case 7: mGenerateClass = "Mage"; break; default: switch (stealth) { case 3: if (magic == 3) mGenerateClass = "Bard"; // unreachable else mGenerateClass = "Warrior"; break; case 5: if (magic == 3) mGenerateClass = "Monk"; else mGenerateClass = "Pilgrim"; break; case 6: if (magic == 1) mGenerateClass = "Agent"; else if (magic == 3) mGenerateClass = "Assassin"; else mGenerateClass = "Acrobat"; break; case 7: mGenerateClass = "Thief"; break; default: mGenerateClass = "Warrior"; } } } } MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); mGenerateClassResultDialog = nullptr; mGenerateClassResultDialog = new GenerateClassResultDialog(); mGenerateClassResultDialog->setClassId(mGenerateClass); mGenerateClassResultDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassBack); mGenerateClassResultDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassDone); mGenerateClassResultDialog->setVisible(true); return; } if (mGenerateClassStep > 10) { MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); return; } MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); mGenerateClassQuestionDialog = nullptr; mGenerateClassQuestionDialog = new InfoBoxDialog(); Step step = sGenerateClassSteps(mGenerateClassStep); mGenerateClassResponses[0] = step.mResponses[0].mSpecialization; mGenerateClassResponses[1] = step.mResponses[1].mSpecialization; mGenerateClassResponses[2] = step.mResponses[2].mSpecialization; InfoBoxDialog::ButtonList buttons; mGenerateClassQuestionDialog->setText(step.mText); buttons.push_back(step.mResponses[0].mText); buttons.push_back(step.mResponses[1].mText); buttons.push_back(step.mResponses[2].mText); mGenerateClassQuestionDialog->setButtons(buttons); mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); mGenerateClassQuestionDialog->setVisible(true); MWBase::Environment::get().getSoundManager()->say(step.mSound); } void CharacterCreation::selectGeneratedClass() { MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); mGenerateClassResultDialog = nullptr; MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); const ESM::Class *klass = MWBase::Environment::get().getWorld()->getStore().get().find(mGenerateClass); mPlayerClass = *klass; } void CharacterCreation::onGenerateClassBack() { selectGeneratedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) { selectGeneratedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } CharacterCreation::~CharacterCreation() { delete mNameDialog; delete mRaceDialog; delete mClassChoiceDialog; delete mGenerateClassQuestionDialog; delete mGenerateClassResultDialog; delete mPickClassDialog; delete mCreateClassDialog; delete mBirthSignDialog; delete mReviewDialog; } void CharacterCreation::handleDialogDone(CSE currentStage, int nextMode) { MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= currentStage) { MWBase::Environment::get().getWindowManager()->pushGuiMode((GuiMode)nextMode); } else { mCreationStage = currentStage; } } } openmw-openmw-0.48.0/apps/openmw/mwgui/charactercreation.hpp000066400000000000000000000074751445372753700242430ustar00rootroot00000000000000#ifndef CHARACTER_CREATION_HPP #define CHARACTER_CREATION_HPP #include #include #include #include "statswatcher.hpp" namespace osg { class Group; } namespace Resource { class ResourceSystem; } namespace MWGui { class WindowBase; class TextInputDialog; class InfoBoxDialog; class RaceDialog; class DialogueWindow; class ClassChoiceDialog; class GenerateClassResultDialog; class PickClassDialog; class CreateClassDialog; class BirthDialog; class ReviewDialog; class MessageBoxManager; class CharacterCreation : public StatsListener { public: typedef std::vector SkillList; CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem); virtual ~CharacterCreation(); //Show a dialog void spawnDialog(const char id); void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override; void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; void configureSkills(const SkillList& major, const SkillList& minor) override; void onFrame(float duration); private: osg::Group* mParent; Resource::ResourceSystem* mResourceSystem; SkillList mPlayerMajorSkills, mPlayerMinorSkills; std::map mPlayerAttributes; std::map mPlayerSkillValues; //Dialogs TextInputDialog* mNameDialog; RaceDialog* mRaceDialog; ClassChoiceDialog* mClassChoiceDialog; InfoBoxDialog* mGenerateClassQuestionDialog; GenerateClassResultDialog* mGenerateClassResultDialog; PickClassDialog* mPickClassDialog; CreateClassDialog* mCreateClassDialog; BirthDialog* mBirthSignDialog; ReviewDialog* mReviewDialog; //Player data std::string mPlayerName; std::string mPlayerRaceId; std::string mPlayerBirthSignId; ESM::Class mPlayerClass; //Class generation vars unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog ESM::Class::Specialization mGenerateClassResponses[3]; unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen std::string mGenerateClass; // In order: Combat, Magic, Stealth ////Dialog events //Name dialog void onNameDialogDone(WindowBase* parWindow); //Race dialog void onRaceDialogDone(WindowBase* parWindow); void onRaceDialogBack(); void selectRace(); //Class dialogs void onClassChoice(int _index); void onPickClassDialogDone(WindowBase* parWindow); void onPickClassDialogBack(); void onCreateClassDialogDone(WindowBase* parWindow); void onCreateClassDialogBack(); void showClassQuestionDialog(); void onClassQuestionChosen(int _index); void onGenerateClassBack(); void onGenerateClassDone(WindowBase* parWindow); void selectGeneratedClass(); void selectCreatedClass(); void selectPickedClass(); //Birthsign dialog void onBirthSignDialogDone(WindowBase* parWindow); void onBirthSignDialogBack(); void selectBirthSign(); //Review dialog void onReviewDialogDone(WindowBase* parWindow); void onReviewDialogBack(); void onReviewActivateDialog(int parDialog); enum CSE //Creation Stage Enum { CSE_NotStarted, CSE_NameChosen, CSE_RaceChosen, CSE_ClassChosen, CSE_BirthSignChosen, CSE_ReviewBack, CSE_ReviewNext }; CSE mCreationStage; // Which state the character creating is in, controls back/next/ok buttons void handleDialogDone(CSE currentStage, int nextMode); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/class.cpp000066400000000000000000001000421445372753700216420ustar00rootroot00000000000000#include "class.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include #include #include #include #include "tooltips.hpp" namespace { bool sortClasses(const std::pair& left, const std::pair& right) { return left.second.compare(right.second) < 0; } } namespace MWGui { /* GenerateClassResultDialog */ GenerateClassResultDialog::GenerateClassResultDialog() : WindowModal("openmw_chargen_generate_class_result.layout") { setText("ReflectT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sMessageQuestionAnswer1", "")); getWidget(mClassImage, "ClassImage"); getWidget(mClassName, "ClassName"); MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->setCaptionWithReplacing("#{sMessageQuestionAnswer3}"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); center(); } std::string GenerateClassResultDialog::getClassId() const { return mClassName->getCaption(); } void GenerateClassResultDialog::setClassId(const std::string &classId) { mCurrentClassId = classId; setClassImage(mClassImage, mCurrentClassId); mClassName->setCaption(MWBase::Environment::get().getWorld()->getStore().get().find(mCurrentClassId)->mName); center(); } // widget controls void GenerateClassResultDialog::onOkClicked(MyGUI::Widget* _sender) { eventDone(this); } void GenerateClassResultDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } /* PickClassDialog */ PickClassDialog::PickClassDialog() : WindowModal("openmw_chargen_class.layout") { // Centre dialog center(); getWidget(mSpecializationName, "SpecializationName"); getWidget(mFavoriteAttribute[0], "FavoriteAttribute0"); getWidget(mFavoriteAttribute[1], "FavoriteAttribute1"); for(int i = 0; i < 5; i++) { char theIndex = '0'+i; getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); } getWidget(mClassList, "ClassList"); mClassList->setScrollVisible(true); mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onAccept); mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); getWidget(mClassImage, "ClassImage"); MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onOkClicked); updateClasses(); updateStats(); } void PickClassDialog::setNextButtonShow(bool shown) { MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); else okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } void PickClassDialog::onOpen() { WindowModal::onOpen (); updateClasses(); updateStats(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); // Show the current class by default MWWorld::Ptr player = MWMechanics::getPlayer(); const std::string &classId = player.get()->mBase->mClass; if (!classId.empty()) setClassId(classId); } void PickClassDialog::setClassId(const std::string &classId) { mCurrentClassId = classId; mClassList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mClassList->getItemCount(); for (size_t i = 0; i < count; ++i) { if (Misc::StringUtils::ciEqual(*mClassList->getItemDataAt(i), classId)) { mClassList->setIndexSelected(i); break; } } updateStats(); } // widget controls void PickClassDialog::onOkClicked(MyGUI::Widget* _sender) { if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void PickClassDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } void PickClassDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) { onSelectClass(_sender, _index); if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) return; const std::string *classId = mClassList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentClassId, *classId)) return; mCurrentClassId = *classId; updateStats(); } // update widget content void PickClassDialog::updateClasses() { mClassList->removeAllItems(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); std::vector > items; // class id, class name for (const ESM::Class& classInfo : store.get()) { bool playable = (classInfo.mData.mIsPlayable != 0); if (!playable) // Only display playable classes continue; if (store.get().isDynamic(classInfo.mId)) continue; // custom-made class not relevant for this dialog items.emplace_back(classInfo.mId, classInfo.mName); } std::sort(items.begin(), items.end(), sortClasses); int index = 0; for (auto& itemPair : items) { const std::string &id = itemPair.first; mClassList->addItem(itemPair.second, id); if (mCurrentClassId.empty()) { mCurrentClassId = id; mClassList->setIndexSelected(index); } else if (Misc::StringUtils::ciEqual(id, mCurrentClassId)) { mClassList->setIndexSelected(index); } ++index; } } void PickClassDialog::updateStats() { if (mCurrentClassId.empty()) return; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Class *klass = store.get().search(mCurrentClassId); if (!klass) return; ESM::Class::Specialization specialization = static_cast(klass->mData.mSpecialization); static const char *specIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[specialization], specIds[specialization]); mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization); mFavoriteAttribute[0]->setAttributeId(klass->mData.mAttribute[0]); mFavoriteAttribute[1]->setAttributeId(klass->mData.mAttribute[1]); ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId()); ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId()); for (int i = 0; i < 5; ++i) { mMinorSkill[i]->setSkillNumber(klass->mData.mSkills[i][0]); mMajorSkill[i]->setSkillNumber(klass->mData.mSkills[i][1]); ToolTips::createSkillToolTip(mMinorSkill[i], klass->mData.mSkills[i][0]); ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]); } setClassImage(mClassImage, mCurrentClassId); } /* InfoBoxDialog */ void InfoBoxDialog::fitToText(MyGUI::TextBox* widget) { MyGUI::IntCoord inner = widget->getTextRegion(); MyGUI::IntCoord outer = widget->getCoord(); MyGUI::IntSize size = widget->getTextSize(); size.width += outer.width - inner.width; size.height += outer.height - inner.height; widget->setSize(size); } void InfoBoxDialog::layoutVertically(MyGUI::Widget* widget, int margin) { size_t count = widget->getChildCount(); int pos = 0; pos += margin; int width = 0; for (unsigned i = 0; i < count; ++i) { MyGUI::Widget* child = widget->getChildAt(i); if (!child->getVisible()) continue; child->setPosition(child->getLeft(), pos); width = std::max(width, child->getWidth()); pos += child->getHeight() + margin; } width += margin*2; widget->setSize(width, pos); } InfoBoxDialog::InfoBoxDialog() : WindowModal("openmw_infobox.layout") { getWidget(mTextBox, "TextBox"); getWidget(mText, "Text"); mText->getSubWidgetText()->setWordWrap(true); getWidget(mButtonBar, "ButtonBar"); center(); } void InfoBoxDialog::setText(const std::string &str) { mText->setCaption(str); mTextBox->setVisible(!str.empty()); fitToText(mText); } std::string InfoBoxDialog::getText() const { return mText->getCaption(); } void InfoBoxDialog::setButtons(ButtonList &buttons) { for (MyGUI::Button* button : this->mButtons) { MyGUI::Gui::getInstance().destroyWidget(button); } this->mButtons.clear(); // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget MyGUI::Button* button; MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10); for (const std::string &text : buttons) { button = mButtonBar->createWidget("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, ""); button->getSubWidgetText()->setWordWrap(true); button->setCaption(text); fitToText(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InfoBoxDialog::onButtonClicked); coord.top += button->getHeight(); this->mButtons.push_back(button); } } void InfoBoxDialog::onOpen() { WindowModal::onOpen(); // Fix layout layoutVertically(mTextBox, 4); layoutVertically(mButtonBar, 6); layoutVertically(mMainWidget, 4 + 6); center(); } void InfoBoxDialog::onButtonClicked(MyGUI::Widget* _sender) { int i = 0; for (MyGUI::Button* button : mButtons) { if (button == _sender) { eventButtonSelected(i); return; } ++i; } } /* ClassChoiceDialog */ ClassChoiceDialog::ClassChoiceDialog() : InfoBoxDialog() { setText(""); ButtonList buttons; buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu1", "")); buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu2", "")); buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu3", "")); buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBack", "")); setButtons(buttons); } /* CreateClassDialog */ CreateClassDialog::CreateClassDialog() : WindowModal("openmw_chargen_create_class.layout") , mSpecDialog(nullptr) , mAttribDialog(nullptr) , mSkillDialog(nullptr) , mDescDialog(nullptr) , mAffectedAttribute(nullptr) , mAffectedSkill(nullptr) { // Centre dialog center(); setText("SpecializationT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu1", "Specialization")); getWidget(mSpecializationName, "SpecializationName"); mSpecializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); setText("FavoriteAttributesT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu2", "Favorite Attributes:")); getWidget(mFavoriteAttribute0, "FavoriteAttribute0"); getWidget(mFavoriteAttribute1, "FavoriteAttribute1"); mFavoriteAttribute0->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); mFavoriteAttribute1->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); setText("MajorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMajor", "")); setText("MinorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMinor", "")); for(int i = 0; i < 5; i++) { char theIndex = '0'+i; getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); mSkills.push_back(mMajorSkill[i]); mSkills.push_back(mMinorSkill[i]); } for (Widgets::MWSkillPtr& skill : mSkills) { skill->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked); } setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "")); getWidget(mEditName, "EditName"); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mEditName); MyGUI::Button* descriptionButton; getWidget(descriptionButton, "DescriptionButton"); descriptionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionClicked); MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onOkClicked); // Set default skills, attributes mFavoriteAttribute0->setAttributeId(ESM::Attribute::Strength); mFavoriteAttribute1->setAttributeId(ESM::Attribute::Agility); mMajorSkill[0]->setSkillId(ESM::Skill::Block); mMajorSkill[1]->setSkillId(ESM::Skill::Armorer); mMajorSkill[2]->setSkillId(ESM::Skill::MediumArmor); mMajorSkill[3]->setSkillId(ESM::Skill::HeavyArmor); mMajorSkill[4]->setSkillId(ESM::Skill::BluntWeapon); mMinorSkill[0]->setSkillId(ESM::Skill::LongBlade); mMinorSkill[1]->setSkillId(ESM::Skill::Axe); mMinorSkill[2]->setSkillId(ESM::Skill::Spear); mMinorSkill[3]->setSkillId(ESM::Skill::Athletics); mMinorSkill[4]->setSkillId(ESM::Skill::Enchant); setSpecialization(0); update(); } CreateClassDialog::~CreateClassDialog() { delete mSpecDialog; delete mAttribDialog; delete mSkillDialog; delete mDescDialog; } void CreateClassDialog::update() { for (int i = 0; i < 5; ++i) { ToolTips::createSkillToolTip(mMajorSkill[i], mMajorSkill[i]->getSkillId()); ToolTips::createSkillToolTip(mMinorSkill[i], mMinorSkill[i]->getSkillId()); } ToolTips::createAttributeToolTip(mFavoriteAttribute0, mFavoriteAttribute0->getAttributeId()); ToolTips::createAttributeToolTip(mFavoriteAttribute1, mFavoriteAttribute1->getAttributeId()); } std::string CreateClassDialog::getName() const { return mEditName->getCaption(); } std::string CreateClassDialog::getDescription() const { return mDescription; } ESM::Class::Specialization CreateClassDialog::getSpecializationId() const { return mSpecializationId; } std::vector CreateClassDialog::getFavoriteAttributes() const { std::vector v; v.push_back(mFavoriteAttribute0->getAttributeId()); v.push_back(mFavoriteAttribute1->getAttributeId()); return v; } std::vector CreateClassDialog::getMajorSkills() const { std::vector v; v.reserve(5); for(int i = 0; i < 5; i++) { v.push_back(mMajorSkill[i]->getSkillId()); } return v; } std::vector CreateClassDialog::getMinorSkills() const { std::vector v; v.reserve(5); for(int i=0; i < 5; i++) { v.push_back(mMinorSkill[i]->getSkillId()); } return v; } void CreateClassDialog::setNextButtonShow(bool shown) { MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); else okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } // widget controls void CreateClassDialog::onDialogCancel() { MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); mSpecDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); mAttribDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); mSkillDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); mDescDialog = nullptr; } void CreateClassDialog::onSpecializationClicked(MyGUI::Widget* _sender) { delete mSpecDialog; mSpecDialog = new SelectSpecializationDialog(); mSpecDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mSpecDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected); mSpecDialog->setVisible(true); } void CreateClassDialog::onSpecializationSelected() { mSpecializationId = mSpecDialog->getSpecializationId(); setSpecialization(mSpecializationId); MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); mSpecDialog = nullptr; } void CreateClassDialog::setSpecialization(int id) { mSpecializationId = (ESM::Class::Specialization) id; static const char *specIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[mSpecializationId], specIds[mSpecializationId]); mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, mSpecializationId); } void CreateClassDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { delete mAttribDialog; mAttribDialog = new SelectAttributeDialog(); mAffectedAttribute = _sender; mAttribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mAttribDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeSelected); mAttribDialog->setVisible(true); } void CreateClassDialog::onAttributeSelected() { ESM::Attribute::AttributeID id = mAttribDialog->getAttributeId(); if (mAffectedAttribute == mFavoriteAttribute0) { if (mFavoriteAttribute1->getAttributeId() == id) mFavoriteAttribute1->setAttributeId(mFavoriteAttribute0->getAttributeId()); } else if (mAffectedAttribute == mFavoriteAttribute1) { if (mFavoriteAttribute0->getAttributeId() == id) mFavoriteAttribute0->setAttributeId(mFavoriteAttribute1->getAttributeId()); } mAffectedAttribute->setAttributeId(id); MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); mAttribDialog = nullptr; update(); } void CreateClassDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { delete mSkillDialog; mSkillDialog = new SelectSkillDialog(); mAffectedSkill = _sender; mSkillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSkillSelected); mSkillDialog->setVisible(true); } void CreateClassDialog::onSkillSelected() { ESM::Skill::SkillEnum id = mSkillDialog->getSkillId(); // Avoid duplicate skills by swapping any skill field that matches the selected one for (Widgets::MWSkillPtr& skill : mSkills) { if (skill == mAffectedSkill) continue; if (skill->getSkillId() == id) { skill->setSkillId(mAffectedSkill->getSkillId()); break; } } mAffectedSkill->setSkillId(mSkillDialog->getSkillId()); MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); mSkillDialog = nullptr; update(); } void CreateClassDialog::onDescriptionClicked(MyGUI::Widget* _sender) { mDescDialog = new DescriptionDialog(); mDescDialog->setTextInput(mDescription); mDescDialog->eventDone += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionEntered); mDescDialog->setVisible(true); } void CreateClassDialog::onDescriptionEntered(WindowBase* parWindow) { mDescription = mDescDialog->getTextInput(); MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); mDescDialog = nullptr; } void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender) { if(getName().size() <= 0) return; eventDone(this); } void CreateClassDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } /* SelectSpecializationDialog */ SelectSpecializationDialog::SelectSpecializationDialog() : WindowModal("openmw_chargen_select_specialization.layout") { // Centre dialog center(); getWidget(mSpecialization0, "Specialization0"); getWidget(mSpecialization1, "Specialization1"); getWidget(mSpecialization2, "Specialization2"); std::string combat = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Combat], ""); std::string magic = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Magic], ""); std::string stealth = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Stealth], ""); mSpecialization0->setCaption(combat); mSpecialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecialization1->setCaption(magic); mSpecialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecialization2->setCaption(stealth); mSpecialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecializationId = ESM::Class::Combat; ToolTips::createSpecializationToolTip(mSpecialization0, combat, ESM::Class::Combat); ToolTips::createSpecializationToolTip(mSpecialization1, magic, ESM::Class::Magic); ToolTips::createSpecializationToolTip(mSpecialization2, stealth, ESM::Class::Stealth); MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked); } SelectSpecializationDialog::~SelectSpecializationDialog() { } // widget controls void SelectSpecializationDialog::onSpecializationClicked(MyGUI::Widget* _sender) { if (_sender == mSpecialization0) mSpecializationId = ESM::Class::Combat; else if (_sender == mSpecialization1) mSpecializationId = ESM::Class::Magic; else if (_sender == mSpecialization2) mSpecializationId = ESM::Class::Stealth; else return; eventItemSelected(); } void SelectSpecializationDialog::onCancelClicked(MyGUI::Widget* _sender) { exit(); } bool SelectSpecializationDialog::exit() { eventCancel(); return true; } /* SelectAttributeDialog */ SelectAttributeDialog::SelectAttributeDialog() : WindowModal("openmw_chargen_select_attribute.layout") , mAttributeId(ESM::Attribute::Strength) { // Centre dialog center(); for (int i = 0; i < 8; ++i) { Widgets::MWAttributePtr attribute; char theIndex = '0'+i; getWidget(attribute, std::string("Attribute").append(1, theIndex)); attribute->setAttributeId(ESM::Attribute::sAttributeIds[i]); attribute->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked); ToolTips::createAttributeToolTip(attribute, attribute->getAttributeId()); } MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked); } SelectAttributeDialog::~SelectAttributeDialog() { } // widget controls void SelectAttributeDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { // TODO: Change MWAttribute to set and get AttributeID enum instead of int mAttributeId = static_cast(_sender->getAttributeId()); eventItemSelected(); } void SelectAttributeDialog::onCancelClicked(MyGUI::Widget* _sender) { exit(); } bool SelectAttributeDialog::exit() { eventCancel(); return true; } /* SelectSkillDialog */ SelectSkillDialog::SelectSkillDialog() : WindowModal("openmw_chargen_select_skill.layout") , mSkillId(ESM::Skill::Block) { // Centre dialog center(); for(int i = 0; i < 9; i++) { char theIndex = '0'+i; getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex)); getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex)); getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex)); } struct {Widgets::MWSkillPtr widget; ESM::Skill::SkillEnum skillId;} mSkills[3][9] = { { {mCombatSkill[0], ESM::Skill::Block}, {mCombatSkill[1], ESM::Skill::Armorer}, {mCombatSkill[2], ESM::Skill::MediumArmor}, {mCombatSkill[3], ESM::Skill::HeavyArmor}, {mCombatSkill[4], ESM::Skill::BluntWeapon}, {mCombatSkill[5], ESM::Skill::LongBlade}, {mCombatSkill[6], ESM::Skill::Axe}, {mCombatSkill[7], ESM::Skill::Spear}, {mCombatSkill[8], ESM::Skill::Athletics} }, { {mMagicSkill[0], ESM::Skill::Enchant}, {mMagicSkill[1], ESM::Skill::Destruction}, {mMagicSkill[2], ESM::Skill::Alteration}, {mMagicSkill[3], ESM::Skill::Illusion}, {mMagicSkill[4], ESM::Skill::Conjuration}, {mMagicSkill[5], ESM::Skill::Mysticism}, {mMagicSkill[6], ESM::Skill::Restoration}, {mMagicSkill[7], ESM::Skill::Alchemy}, {mMagicSkill[8], ESM::Skill::Unarmored} }, { {mStealthSkill[0], ESM::Skill::Security}, {mStealthSkill[1], ESM::Skill::Sneak}, {mStealthSkill[2], ESM::Skill::Acrobatics}, {mStealthSkill[3], ESM::Skill::LightArmor}, {mStealthSkill[4], ESM::Skill::ShortBlade}, {mStealthSkill[5] ,ESM::Skill::Marksman}, {mStealthSkill[6] ,ESM::Skill::Mercantile}, {mStealthSkill[7] ,ESM::Skill::Speechcraft}, {mStealthSkill[8] ,ESM::Skill::HandToHand} } }; for (int spec = 0; spec < 3; ++spec) { for (int i = 0; i < 9; ++i) { mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId); mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId()); } } MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked); } SelectSkillDialog::~SelectSkillDialog() { } // widget controls void SelectSkillDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { mSkillId = _sender->getSkillId(); eventItemSelected(); } void SelectSkillDialog::onCancelClicked(MyGUI::Widget* _sender) { exit(); } bool SelectSkillDialog::exit() { eventCancel(); return true; } /* DescriptionDialog */ DescriptionDialog::DescriptionDialog() : WindowModal("openmw_chargen_class_description.layout") { // Centre dialog center(); getWidget(mTextEdit, "TextEdit"); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked); okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", "")); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } DescriptionDialog::~DescriptionDialog() { } // widget controls void DescriptionDialog::onOkClicked(MyGUI::Widget* _sender) { eventDone(this); } void setClassImage(MyGUI::ImageBox* imageBox, const std::string &classId) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::string classImage = Misc::ResourceHelpers::correctTexturePath("textures\\levelup\\" + classId + ".dds", vfs); if (!vfs->exists(classImage)) { Log(Debug::Warning) << "No class image for " << classId << ", falling back to default"; classImage = "textures\\levelup\\warrior.dds"; } imageBox->setImageTexture(classImage); } } openmw-openmw-0.48.0/apps/openmw/mwgui/class.hpp000066400000000000000000000225231445372753700216560ustar00rootroot00000000000000#ifndef MWGUI_CLASS_H #define MWGUI_CLASS_H #include #include #include #include "widgets.hpp" #include "windowbase.hpp" namespace MWGui { void setClassImage(MyGUI::ImageBox* imageBox, const std::string& classId); class InfoBoxDialog : public WindowModal { public: InfoBoxDialog(); typedef std::vector ButtonList; void setText(const std::string &str); std::string getText() const; void setButtons(ButtonList &buttons); void onOpen() override; bool exit() override { return false; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; /** Event : Button was clicked.\n signature : void method(int index)\n */ EventHandle_Int eventButtonSelected; protected: void onButtonClicked(MyGUI::Widget* _sender); private: void fitToText(MyGUI::TextBox* widget); void layoutVertically(MyGUI::Widget* widget, int margin); MyGUI::Widget* mTextBox; MyGUI::TextBox* mText; MyGUI::Widget* mButtonBar; std::vector mButtons; }; // Lets the player choose between 3 ways of creating a class class ClassChoiceDialog : public InfoBoxDialog { public: // Corresponds to the buttons that can be clicked enum ClassChoice { Class_Generate = 0, Class_Pick = 1, Class_Create = 2, Class_Back = 3 }; ClassChoiceDialog(); }; class GenerateClassResultDialog : public WindowModal { public: GenerateClassResultDialog(); std::string getClassId() const; void setClassId(const std::string &classId); bool exit() override { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); private: MyGUI::ImageBox* mClassImage; MyGUI::TextBox* mClassName; std::string mCurrentClassId; }; class PickClassDialog : public WindowModal { public: PickClassDialog(); const std::string &getClassId() const { return mCurrentClassId; } void setClassId(const std::string &classId); void setNextButtonShow(bool shown); void onOpen() override; bool exit() override { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onSelectClass(MyGUI::ListBox* _sender, size_t _index); void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); private: void updateClasses(); void updateStats(); MyGUI::ImageBox* mClassImage; MyGUI::ListBox* mClassList; MyGUI::TextBox* mSpecializationName; Widgets::MWAttributePtr mFavoriteAttribute[2]; Widgets::MWSkillPtr mMajorSkill[5]; Widgets::MWSkillPtr mMinorSkill[5]; std::string mCurrentClassId; }; class SelectSpecializationDialog : public WindowModal { public: SelectSpecializationDialog(); ~SelectSpecializationDialog(); bool exit() override; ESM::Class::Specialization getSpecializationId() const { return mSpecializationId; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Cancel button clicked.\n signature : void method()\n */ EventHandle_Void eventCancel; /** Event : Dialog finished, specialization selected.\n signature : void method()\n */ EventHandle_Void eventItemSelected; protected: void onSpecializationClicked(MyGUI::Widget* _sender); void onCancelClicked(MyGUI::Widget* _sender); private: MyGUI::TextBox *mSpecialization0, *mSpecialization1, *mSpecialization2; ESM::Class::Specialization mSpecializationId; }; class SelectAttributeDialog : public WindowModal { public: SelectAttributeDialog(); ~SelectAttributeDialog(); bool exit() override; ESM::Attribute::AttributeID getAttributeId() const { return mAttributeId; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Cancel button clicked.\n signature : void method()\n */ EventHandle_Void eventCancel; /** Event : Dialog finished, attribute selected.\n signature : void method()\n */ EventHandle_Void eventItemSelected; protected: void onAttributeClicked(Widgets::MWAttributePtr _sender); void onCancelClicked(MyGUI::Widget* _sender); private: ESM::Attribute::AttributeID mAttributeId; }; class SelectSkillDialog : public WindowModal { public: SelectSkillDialog(); ~SelectSkillDialog(); bool exit() override; ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Cancel button clicked.\n signature : void method()\n */ EventHandle_Void eventCancel; /** Event : Dialog finished, skill selected.\n signature : void method()\n */ EventHandle_Void eventItemSelected; protected: void onSkillClicked(Widgets::MWSkillPtr _sender); void onCancelClicked(MyGUI::Widget* _sender); private: Widgets::MWSkillPtr mCombatSkill[9]; Widgets::MWSkillPtr mMagicSkill[9]; Widgets::MWSkillPtr mStealthSkill[9]; ESM::Skill::SkillEnum mSkillId; }; class DescriptionDialog : public WindowModal { public: DescriptionDialog(); ~DescriptionDialog(); std::string getTextInput() const { return mTextEdit->getCaption(); } void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onOkClicked(MyGUI::Widget* _sender); private: MyGUI::EditBox* mTextEdit; }; class CreateClassDialog : public WindowModal { public: CreateClassDialog(); virtual ~CreateClassDialog(); bool exit() override { return false; } std::string getName() const; std::string getDescription() const; ESM::Class::Specialization getSpecializationId() const; std::vector getFavoriteAttributes() const; std::vector getMajorSkills() const; std::vector getMinorSkills() const; void setNextButtonShow(bool shown); // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); void onSpecializationClicked(MyGUI::Widget* _sender); void onSpecializationSelected(); void onAttributeClicked(Widgets::MWAttributePtr _sender); void onAttributeSelected(); void onSkillClicked(Widgets::MWSkillPtr _sender); void onSkillSelected(); void onDescriptionClicked(MyGUI::Widget* _sender); void onDescriptionEntered(WindowBase* parWindow); void onDialogCancel(); void setSpecialization(int id); void update(); private: MyGUI::EditBox* mEditName; MyGUI::TextBox* mSpecializationName; Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; Widgets::MWSkillPtr mMajorSkill[5]; Widgets::MWSkillPtr mMinorSkill[5]; std::vector mSkills; std::string mDescription; SelectSpecializationDialog *mSpecDialog; SelectAttributeDialog *mAttribDialog; SelectSkillDialog *mSkillDialog; DescriptionDialog *mDescDialog; ESM::Class::Specialization mSpecializationId; Widgets::MWAttributePtr mAffectedAttribute; Widgets::MWSkillPtr mAffectedSkill; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/companionitemmodel.cpp000066400000000000000000000026711445372753700244310ustar00rootroot00000000000000#include "companionitemmodel.hpp" #include "../mwworld/class.hpp" namespace { void modifyProfit(const MWWorld::Ptr& actor, int diff) { std::string script = actor.getClass().getScript(actor); if (!script.empty()) { int profit = actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); profit += diff; actor.getRefData().getLocals().setVarByInt(script, "minimumprofit", profit); } } } namespace MWGui { CompanionItemModel::CompanionItemModel(const MWWorld::Ptr &actor) : InventoryItemModel(actor) { } MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { if (hasProfit(mActor)) modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count); return InventoryItemModel::copyItem(item, count, allowAutoEquip); } void CompanionItemModel::removeItem (const ItemStack& item, size_t count) { if (hasProfit(mActor)) modifyProfit(mActor, -item.mBase.getClass().getValue(item.mBase) * count); InventoryItemModel::removeItem(item, count); } bool CompanionItemModel::hasProfit(const MWWorld::Ptr &actor) { std::string script = actor.getClass().getScript(actor); if (script.empty()) return false; return actor.getRefData().getLocals().hasVar(script, "minimumprofit"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/companionitemmodel.hpp000066400000000000000000000012341445372753700244300ustar00rootroot00000000000000#ifndef MWGUI_COMPANION_ITEM_MODEL_H #define MWGUI_COMPANION_ITEM_MODEL_H #include "inventoryitemmodel.hpp" namespace MWGui { /// @brief The companion item model keeps track of the companion's profit by /// monitoring which items are being added to and removed from the model. class CompanionItemModel : public InventoryItemModel { public: CompanionItemModel (const MWWorld::Ptr& actor); MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem (const ItemStack& item, size_t count) override; bool hasProfit(const MWWorld::Ptr& actor); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/companionwindow.cpp000066400000000000000000000131751445372753700237620ustar00rootroot00000000000000#include "companionwindow.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "messagebox.hpp" #include "itemview.hpp" #include "sortfilteritemmodel.hpp" #include "companionitemmodel.hpp" #include "draganddrop.hpp" #include "countdialog.hpp" #include "widgets.hpp" #include "tooltips.hpp" namespace { int getProfit(const MWWorld::Ptr& actor) { std::string script = actor.getClass().getScript(actor); if (!script.empty()) { return actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); } return 0; } } namespace MWGui { CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* manager) : WindowBase("openmw_companion_window.layout") , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) , mDragAndDrop(dragAndDrop) , mMessageBoxManager(manager) { getWidget(mCloseButton, "CloseButton"); getWidget(mProfitLabel, "ProfitLabel"); getWidget(mEncumbranceBar, "EncumbranceBar"); getWidget(mFilterEdit, "FilterEdit"); getWidget(mItemView, "ItemView"); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected); mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &CompanionWindow::onNameFilterChanged); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked); setCoord(200,0,600,300); } void CompanionWindow::onItemSelected(int index) { if (mDragAndDrop->mIsOnDragAndDrop) { mDragAndDrop->drop(mModel, mItemView); updateEncumbranceBar(); return; } const ItemStack& item = mSortModel->getItem(index); // We can't take conjured items from a companion NPC if (item.mFlags & ItemStack::Flag_Bound) { MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); return; } MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; mSelectedItem = mSortModel->mapToSource(index); if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); } else dragItem (nullptr, count); } void CompanionWindow::onNameFilterChanged(MyGUI::EditBox* _sender) { mSortModel->setNameFilter(_sender->getCaption()); mItemView->update(); } void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) { mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); } void CompanionWindow::onBackgroundSelected() { if (mDragAndDrop->mIsOnDragAndDrop) { mDragAndDrop->drop(mModel, mItemView); updateEncumbranceBar(); } } void CompanionWindow::setPtr(const MWWorld::Ptr& npc) { mPtr = npc; updateEncumbranceBar(); mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); mFilterEdit->setCaption(std::string()); mItemView->setModel(mSortModel); mItemView->resetScrollBars(); setTitle(npc.getClass().getName(npc)); } void CompanionWindow::onFrame(float dt) { checkReferenceAvailable(); updateEncumbranceBar(); } void CompanionWindow::updateEncumbranceBar() { if (mPtr.isEmpty()) return; float capacity = mPtr.getClass().getCapacity(mPtr); float encumbrance = mPtr.getClass().getEncumbrance(mPtr); mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); if (mModel && mModel->hasProfit(mPtr)) { mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + MyGUI::utility::toString(getProfit(mPtr))); } else mProfitLabel->setCaption(""); } void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { if (exit()) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } bool CompanionWindow::exit() { if (mModel && mModel->hasProfit(mPtr) && getProfit(mPtr) < 0) { std::vector buttons; buttons.emplace_back("#{sCompanionWarningButtonOne}"); buttons.emplace_back("#{sCompanionWarningButtonTwo}"); mMessageBoxManager->createInteractiveMessageBox("#{sCompanionWarningMessage}", buttons); mMessageBoxManager->eventButtonPressed += MyGUI::newDelegate(this, &CompanionWindow::onMessageBoxButtonClicked); return false; } return true; } void CompanionWindow::onMessageBoxButtonClicked(int button) { if (button == 0) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); // Important for Calvus' contract script to work properly MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } } void CompanionWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } void CompanionWindow::resetReference() { ReferenceInterface::resetReference(); mItemView->setModel(nullptr); mModel = nullptr; mSortModel = nullptr; } } openmw-openmw-0.48.0/apps/openmw/mwgui/companionwindow.hpp000066400000000000000000000030201445372753700237530ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_COMPANIONWINDOW_H #define OPENMW_MWGUI_COMPANIONWINDOW_H #include "windowbase.hpp" #include "referenceinterface.hpp" namespace MWGui { namespace Widgets { class MWDynamicStat; } class MessageBoxManager; class ItemView; class DragAndDrop; class SortFilterItemModel; class CompanionItemModel; class CompanionWindow : public WindowBase, public ReferenceInterface { public: CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager); bool exit() override; void resetReference() override; void setPtr(const MWWorld::Ptr& npc) override; void onFrame (float dt) override; void clear() override { resetReference(); } private: ItemView* mItemView; SortFilterItemModel* mSortModel; CompanionItemModel* mModel; int mSelectedItem; DragAndDrop* mDragAndDrop; MyGUI::Button* mCloseButton; MyGUI::EditBox* mFilterEdit; MyGUI::TextBox* mProfitLabel; Widgets::MWDynamicStat* mEncumbranceBar; MessageBoxManager* mMessageBoxManager; void onItemSelected(int index); void onNameFilterChanged(MyGUI::EditBox* _sender); void onBackgroundSelected(); void dragItem(MyGUI::Widget* sender, int count); void onMessageBoxButtonClicked(int button); void updateEncumbranceBar(); void onCloseButtonClicked(MyGUI::Widget* _sender); void onReferenceUnavailable() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/confirmationdialog.cpp000066400000000000000000000030221445372753700244050ustar00rootroot00000000000000#include "confirmationdialog.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" namespace MWGui { ConfirmationDialog::ConfirmationDialog() : WindowModal("openmw_confirmation_dialog.layout") { getWidget(mMessage, "Message"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); } void ConfirmationDialog::askForConfirmation(const std::string& message) { setVisible(true); mMessage->setCaptionWithReplacing(message); int height = mMessage->getTextSize().height + 60; int width = mMessage->getTextSize().width + 24; mMainWidget->setSize(width, height); mMessage->setSize(mMessage->getWidth(), mMessage->getTextSize().height + 24); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); center(); } bool ConfirmationDialog::exit() { setVisible(false); eventCancelClicked(); return true; } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) { exit(); } void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) { setVisible(false); eventOkClicked(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/confirmationdialog.hpp000066400000000000000000000015541445372753700244220ustar00rootroot00000000000000#ifndef MWGUI_CONFIRMATIONDIALOG_H #define MWGUI_CONFIRMATIONDIALOG_H #include "windowbase.hpp" namespace MWGui { class ConfirmationDialog : public WindowModal { public: ConfirmationDialog(); void askForConfirmation(const std::string& message); bool exit() override; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Ok button was clicked.\n signature : void method()\n */ EventHandle_Void eventOkClicked; EventHandle_Void eventCancelClicked; private: MyGUI::EditBox* mMessage; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; void onCancelButtonClicked(MyGUI::Widget* _sender); void onOkButtonClicked(MyGUI::Widget* _sender); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/console.cpp000066400000000000000000000422311445372753700222040ustar00rootroot00000000000000#include "console.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "../mwscript/extensions.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/luamanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" namespace MWGui { class ConsoleInterpreterContext : public MWScript::InterpreterContext { Console& mConsole; public: ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference); void report (const std::string& message) override; }; ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference) : MWScript::InterpreterContext ( reference.isEmpty() ? nullptr : &reference.getRefData().getLocals(), reference), mConsole (console) {} void ConsoleInterpreterContext::report (const std::string& message) { mConsole.printOK (message); } bool Console::compile (const std::string& cmd, Compiler::Output& output) { try { ErrorHandler::reset(); std::istringstream input (cmd + '\n'); Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); Compiler::LineParser parser (*this, mCompilerContext, output.getLocals(), output.getLiterals(), output.getCode(), true); scanner.scan (parser); return isGood(); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { printError (std::string ("Error: ") + error.what()); } return false; } void Console::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream error; error << "column " << loc.mColumn << " (" << loc.mLiteral << "):"; printError (error.str()); printError ((type==ErrorMessage ? "error: " : "warning: ") + message); } void Console::report (const std::string& message, Type type) { printError ((type==ErrorMessage ? "error: " : "warning: ") + message); } void Console::listNames() { if (mNames.empty()) { // keywords std::istringstream input (""); Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); scanner.listKeywords (mNames); // identifier const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); for (MWWorld::ESMStore::iterator it = store.begin(); it != store.end(); ++it) { it->second->listIdentifier (mNames); } // exterior cell names aren't technically identifiers, but since the COC function accepts them, // we should list them too for (MWWorld::Store::iterator it = store.get().extBegin(); it != store.get().extEnd(); ++it) { if (!it->mName.empty()) mNames.push_back(it->mName); } // sort std::sort (mNames.begin(), mNames.end()); // remove duplicates mNames.erase( std::unique( mNames.begin(), mNames.end() ), mNames.end() ); } } Console::Console(int w, int h, bool consoleOnlyScripts) : WindowBase("openmw_console.layout"), mCompilerContext (MWScript::CompilerContext::Type_Console), mConsoleOnlyScripts (consoleOnlyScripts) { setCoord(10,10, w-10, h/2); getWidget(mCommandLine, "edit_Command"); getWidget(mHistory, "list_History"); // Set up the command line box mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand); mCommandLine->eventKeyButtonPressed += newDelegate(this, &Console::keyPress); // Set up the log window mHistory->setOverflowToTheLeft(true); // compiler Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts); mCompilerContext.setExtensions (&mExtensions); } void Console::onOpen() { // Give keyboard focus to the combo box whenever the console is // turned on and place it over other widgets MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); MyGUI::LayerManager::getInstance().upLayerItem(mMainWidget); } void Console::print(const std::string &msg, std::string_view color) { mHistory->addText(std::string(color) + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { print(msg + "\n", MWBase::WindowManager::sConsoleColor_Success); } void Console::printError(const std::string &msg) { print(msg + "\n", MWBase::WindowManager::sConsoleColor_Error); } void Console::execute (const std::string& command) { // Log the command if (mConsoleMode.empty()) print("> " + command + "\n"); else print(mConsoleMode + " " + command + "\n"); if (!mConsoleMode.empty() || (command.size() >= 3 && std::string_view(command).substr(0, 3) == "lua")) { MWBase::Environment::get().getLuaManager()->handleConsoleCommand(mConsoleMode, command, mPtr); return; } Compiler::Locals locals; if (!mPtr.isEmpty()) { std::string script = mPtr.getClass().getScript(mPtr); if (!script.empty()) locals = MWBase::Environment::get().getScriptManager()->getLocals(script); } Compiler::Output output (locals); if (compile (command + "\n", output)) { try { ConsoleInterpreterContext interpreterContext (*this, mPtr); Interpreter::Interpreter interpreter; MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); std::vector code; output.getCode (code); interpreter.run (&code[0], code.size(), interpreterContext); } catch (const std::exception& error) { printError (std::string ("Error: ") + error.what()); } } } void Console::executeFile (const std::string& path) { namespace bfs = boost::filesystem; bfs::ifstream stream ((bfs::path(path))); if (!stream.is_open()) printError ("failed to open file: " + path); else { std::string line; while (std::getline (stream, line)) execute (line); } } void Console::clear() { resetReference(); } bool isWhitespace(char c) { return c == ' ' || c == '\t'; } void Console::keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) { if(MyGUI::InputManager::getInstance().isControlPressed()) { if(key == MyGUI::KeyCode::W) { const auto& caption = mCommandLine->getCaption(); if(caption.empty()) return; size_t max = mCommandLine->getTextCursor(); while(max > 0 && (isWhitespace(caption[max - 1]) || caption[max - 1] == '>')) max--; while(max > 0 && !isWhitespace(caption[max - 1]) && caption[max - 1] != '>') max--; size_t length = mCommandLine->getTextCursor() - max; if(length > 0) { auto text = caption; text.erase(max, length); mCommandLine->setCaption(text); mCommandLine->setTextCursor(max); } } else if(key == MyGUI::KeyCode::U) { if(mCommandLine->getTextCursor() > 0) { auto text = mCommandLine->getCaption(); text.erase(0, mCommandLine->getTextCursor()); mCommandLine->setCaption(text); mCommandLine->setTextCursor(0); } } } else if(key == MyGUI::KeyCode::Tab && mConsoleMode.empty()) { std::vector matches; listNames(); std::string oldCaption = mCommandLine->getCaption(); std::string newCaption = complete( mCommandLine->getOnlyText(), matches ); mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab if (oldCaption == newCaption && !matches.empty()) { int i = 0; printOK(""); for(std::string& match : matches) { if(i == 50) break; printOK(match); i++; } } } if(mCommandHistory.empty()) return; // Traverse history with up and down arrows if(key == MyGUI::KeyCode::ArrowUp) { // If the user was editing a string, store it for later if(mCurrent == mCommandHistory.end()) mEditString = mCommandLine->getOnlyText(); if(mCurrent != mCommandHistory.begin()) { --mCurrent; mCommandLine->setCaption(*mCurrent); } } else if(key == MyGUI::KeyCode::ArrowDown) { if(mCurrent != mCommandHistory.end()) { ++mCurrent; if(mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); else // Restore the edit string mCommandLine->setCaption(mEditString); } } } void Console::acceptCommand(MyGUI::EditBox* _sender) { const std::string &cm = mCommandLine->getOnlyText(); if(cm.empty()) return; // Add the command to the history, and set the current pointer to // the end of the list if (mCommandHistory.empty() || mCommandHistory.back() != cm) mCommandHistory.push_back(cm); mCurrent = mCommandHistory.end(); mEditString.clear(); mHistory->setTextCursor(mHistory->getTextLength()); // Reset the command line before the command execution. // It prevents the re-triggering of the acceptCommand() event for the same command // during the actual command execution mCommandLine->setCaption(""); execute (cm); } std::string Console::complete( std::string input, std::vector &matches ) { std::string output = input; std::string tmp = input; bool has_front_quote = false; /* Does the input string contain things that don't have to be completed? If yes erase them. */ /* Erase a possible call to an explicit reference. */ size_t explicitPos = tmp.find("->"); if (explicitPos != std::string::npos) { tmp.erase(0, explicitPos+2); } /* Are there quotation marks? */ if( tmp.find('"') != std::string::npos ) { int numquotes=0; for(std::string::iterator it=tmp.begin(); it < tmp.end(); ++it) { if( *it == '"' ) numquotes++; } /* Is it terminated?*/ if( numquotes % 2 ) { tmp.erase( 0, tmp.rfind('"')+1 ); has_front_quote = true; } else { size_t pos; if( ( ((pos = tmp.rfind(' ')) != std::string::npos ) ) && ( pos > tmp.rfind('"') ) ) { tmp.erase( 0, tmp.rfind(' ')+1); } else { tmp.clear(); } has_front_quote = false; } } /* No quotation marks. Are there spaces?*/ else { size_t rpos; if( (rpos=tmp.rfind(' ')) != std::string::npos ) { if( rpos == 0 ) { tmp.clear(); } else { tmp.erase(0, rpos+1); } } } /* Erase the input from the output string so we can easily append the completed form later. */ output.erase(output.end()-tmp.length(), output.end()); /* Is there still something in the input string? If not just display all commands and return the unchanged input. */ if( tmp.length() == 0 ) { matches=mNames; return input; } /* Iterate through the vector. */ for(std::string& name : mNames) { bool string_different=false; /* Is the string shorter than the input string? If yes skip it. */ if(name.length() < tmp.length()) continue; /* Is the beginning of the string different from the input string? If yes skip it. */ for( std::string::iterator iter=tmp.begin(), iter2=name.begin(); iter < tmp.end();++iter, ++iter2) { if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) { string_different=true; break; } } if( string_different ) continue; /* The beginning of the string matches the input string, save it for the next test. */ matches.push_back(name); } /* There are no matches. Return the unchanged input. */ if( matches.empty() ) { return input; } /* Only one match. We're done. */ if( matches.size() == 1 ) { /* Adding quotation marks when the input string started with a quotation mark or has spaces in it*/ if( ( matches.front().find(' ') != std::string::npos ) ) { if( !has_front_quote ) output.append(std::string("\"")); return output.append(matches.front() + std::string("\" ")); } else if( has_front_quote ) { return output.append(matches.front() + std::string("\" ")); } else { return output.append(matches.front() + std::string(" ")); } } /* Check if all matching strings match further than input. If yes complete to this match. */ int i = tmp.length(); for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) { for(std::string& match : matches) { if(Misc::StringUtils::toLower(match[i]) != Misc::StringUtils::toLower(*iter)) { /* Append the longest match to the end of the output string*/ output.append(matches.front().substr(0, i)); return output; } } } /* All keywords match with the shortest. Append it to the output string and return it. */ return output.append(matches.front()); } void Console::onResChange(int width, int height) { setCoord(10, 10, width-10, height/2); } void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) { if (mPtr == currentPtr) mPtr = newPtr; } void Console::setSelectedObject(const MWWorld::Ptr& object) { if (!object.isEmpty()) { if (object == mPtr) mPtr = MWWorld::Ptr(); else mPtr = object; // User clicked on an object. Restore focus to the console command line. MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else mPtr = MWWorld::Ptr(); updateConsoleTitle(); } void Console::updateConsoleTitle() { std::string title = "#{sConsoleTitle}"; if (!mConsoleMode.empty()) title = mConsoleMode + " " + title; if (!mPtr.isEmpty()) title.append(" (" + mPtr.getCellRef().getRefId() + ")"); setTitle(title); } void Console::setConsoleMode(std::string_view mode) { mConsoleMode = std::string(mode); updateConsoleTitle(); } void Console::onReferenceUnavailable() { setSelectedObject(MWWorld::Ptr()); } void Console::resetReference() { ReferenceInterface::resetReference(); setSelectedObject(MWWorld::Ptr()); } } openmw-openmw-0.48.0/apps/openmw/mwgui/console.hpp000066400000000000000000000064471445372753700222220ustar00rootroot00000000000000#ifndef MWGUI_CONSOLE_H #define MWGUI_CONSOLE_H #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" #include "referenceinterface.hpp" #include "windowbase.hpp" namespace MWGui { class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface { public: /// Set the implicit object for script execution void setSelectedObject(const MWWorld::Ptr& object); MyGUI::EditBox* mCommandLine; MyGUI::EditBox* mHistory; typedef std::list StringList; // History of previous entered commands StringList mCommandHistory; StringList::iterator mCurrent; std::string mEditString; Console(int w, int h, bool consoleOnlyScripts); void onOpen() override; void onResChange(int width, int height) override; // Print a message to the console, in specified color. void print(const std::string &msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); // These are pre-colored versions that you should use. /// Output from successful console command void printOK(const std::string &msg); /// Error message void printError(const std::string &msg); void execute (const std::string& command); void executeFile (const std::string& path); void updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr); void clear() override; void resetReference () override; const std::string& getConsoleMode() const { return mConsoleMode; } void setConsoleMode(std::string_view mode); protected: void onReferenceUnavailable() override; private: std::string mConsoleMode; void updateConsoleTitle(); void keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char); void acceptCommand(MyGUI::EditBox* _sender); std::string complete( std::string input, std::vector &matches ); Compiler::Extensions mExtensions; MWScript::CompilerContext mCompilerContext; std::vector mNames; bool mConsoleOnlyScripts; bool compile (const std::string& cmd, Compiler::Output& output); /// Report error to the user. void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; /// Report a file related error void report (const std::string& message, Type type) override; /// Write all valid identifiers and keywords into mNames and sort them. /// \note If mNames is not empty, this function is a no-op. /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same /// time). void listNames(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/container.cpp000066400000000000000000000261511445372753700225270ustar00rootroot00000000000000#include "container.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/aipackage.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/summoning.hpp" #include "../mwscript/interpretercontext.hpp" #include "countdialog.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "containeritemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "pickpocketitemmodel.hpp" #include "draganddrop.hpp" #include "tooltips.hpp" namespace MWGui { ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) : WindowBase("openmw_container_window.layout") , mDragAndDrop(dragAndDrop) , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) , mTreatNextOpenAsLoot(false) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mTakeButton, "TakeButton"); getWidget(mCloseButton, "CloseButton"); getWidget(mItemView, "ItemView"); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &ContainerWindow::onBackgroundSelected); mItemView->eventItemClicked += MyGUI::newDelegate(this, &ContainerWindow::onItemSelected); mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); setCoord(200,0,600,300); } void ContainerWindow::onItemSelected(int index) { if (mDragAndDrop->mIsOnDragAndDrop) { dropItem(); return; } const ItemStack& item = mSortModel->getItem(index); // We can't take a conjured item from a container (some NPC we're pickpocketing, a box, etc) if (item.mFlags & ItemStack::Flag_Bound) { MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage1}"); return; } MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; mSelectedItem = mSortModel->mapToSource(index); if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } else dragItem (nullptr, count); } void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) { if (!mModel) return; if (!onTakeItem(mModel->getItem(mSelectedItem), count)) return; mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); } void ContainerWindow::dropItem() { if (!mModel) return; bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); if (success) mDragAndDrop->drop(mModel, mItemView); } void ContainerWindow::onBackgroundSelected() { if (mDragAndDrop->mIsOnDragAndDrop) dropItem(); } void ContainerWindow::setPtr(const MWWorld::Ptr& container) { bool lootAnyway = mTreatNextOpenAsLoot; mTreatNextOpenAsLoot = false; mPtr = container; bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); if (mPtr.getClass().hasInventoryStore(mPtr)) { if (mPtr.getClass().isNpc() && !loot && !lootAnyway) { // we are stealing stuff mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } else mModel = new InventoryItemModel(container); } else { mModel = new ContainerItemModel(container); } mDisposeCorpseButton->setVisible(loot); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel (mSortModel); mItemView->resetScrollBars(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); setTitle(container.getClass().getName(container)); } void ContainerWindow::resetReference() { ReferenceInterface::resetReference(); mItemView->setModel(nullptr); mModel = nullptr; mSortModel = nullptr; } 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; if (mModel) mModel->onClose(); if (!mPtr.isEmpty()) MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); resetReference(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) { if(mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop) return; MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); // transfer everything into the player's inventory ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); assert(mModel); mModel->update(); // unequip all items to avoid unequipping/reequipping if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); for (size_t i=0; igetItemCount(); ++i) { const ItemStack& item = mModel->getItem(i); if (invStore.isEquipped(item.mBase) == false) continue; invStore.unequipItem(item.mBase, mPtr); } } mModel->update(); for (size_t i=0; igetItemCount(); ++i) { if (i==0) { // play the sound of the first object MWWorld::Ptr item = mModel->getItem(i).mBase; std::string sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getWindowManager()->playSound(sound); } const ItemStack& item = mModel->getItem(i); if (!onTakeItem(item, item.mCount)) break; mModel->moveItem(item, item.mCount, playerModel); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) { if(mDragAndDrop == nullptr || !mDragAndDrop->mIsOnDragAndDrop) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); // Copy mPtr because onTakeAllButtonClicked closes the window which resets the reference MWWorld::Ptr ptr = mPtr; onTakeAllButtonClicked(mTakeButton); if (ptr.getClass().isPersistent(ptr)) MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); else { MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); // If we dispose corpse before end of death animation, we should update death counter counter manually. // Also we should run actor's script - it may react on actor's death. if (creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()) { creatureStats.setDeathAnimationFinished(true); MWBase::Environment::get().getMechanicsManager()->notifyDied(ptr); const std::string script = ptr.getClass().getScript(ptr); if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) { MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); } // Clean up summoned creatures as well auto& creatureMap = creatureStats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second); creatureMap.clear(); // Check if we are a summon and inform our master we've bit the dust for(const auto& package : creatureStats.getAiSequence()) { if(package->followTargetThroughDoors() && !package->getTarget().isEmpty()) { const auto& summoner = package->getTarget(); auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap(); auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); }); if(it != summons.end()) { auto summon = *it; summons.erase(it); MWMechanics::purgeSummonEffect(summoner, summon); break; } } } } MWBase::Environment::get().getWorld()->deleteObject(ptr); } mPtr = MWWorld::Ptr(); } } void ContainerWindow::onReferenceUnavailable() { 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); } } openmw-openmw-0.48.0/apps/openmw/mwgui/container.hpp000066400000000000000000000032651445372753700225350ustar00rootroot00000000000000#ifndef MGUI_CONTAINER_H #define MGUI_CONTAINER_H #include "windowbase.hpp" #include "referenceinterface.hpp" #include "itemmodel.hpp" namespace MyGUI { class Gui; class Widget; } namespace MWGui { class ContainerWindow; class ItemView; class SortFilterItemModel; } namespace MWGui { class ContainerWindow : public WindowBase, public ReferenceInterface { public: ContainerWindow(DragAndDrop* dragAndDrop); void setPtr(const MWWorld::Ptr& container) override; void onClose() override; void clear() override { resetReference(); } void onFrame(float dt) override { checkReferenceAvailable(); } void resetReference() override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; }; private: DragAndDrop* mDragAndDrop; MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; int mSelectedItem; bool mTreatNextOpenAsLoot; MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; MyGUI::Button* mCloseButton; void onItemSelected(int index); void onBackgroundSelected(); void dragItem(MyGUI::Widget* sender, int count); void dropItem(); void onCloseButtonClicked(MyGUI::Widget* _sender); void onTakeAllButtonClicked(MyGUI::Widget* _sender); void onDisposeCorpseButtonClicked(MyGUI::Widget* sender); /// @return is taking the item allowed? bool onTakeItem(const ItemStack& item, int count); void onReferenceUnavailable() override; }; } #endif // CONTAINER_H openmw-openmw-0.48.0/apps/openmw/mwgui/containeritemmodel.cpp000066400000000000000000000175221445372753700244310ustar00rootroot00000000000000#include "containeritemmodel.hpp" #include #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" namespace { bool stacks (const MWWorld::Ptr& left, const MWWorld::Ptr& right) { if (left == right) return true; // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure if (left.getContainerStore() && right.getContainerStore()) return left.getContainerStore()->stacks(left, right) && right.getContainerStore()->stacks(left, right); if (left.getContainerStore()) return left.getContainerStore()->stacks(left, right); if (right.getContainerStore()) return right.getContainerStore()->stacks(left, right); MWWorld::ContainerStore store; return store.stacks(left, right); } } namespace MWGui { ContainerItemModel::ContainerItemModel(const std::vector& itemSources, const std::vector& worldItems) : mWorldItems(worldItems) , mTrading(true) { assert (!itemSources.empty()); // Tie resolution lifetimes to the ItemModel mItemSources.reserve(itemSources.size()); for(const MWWorld::Ptr& source : itemSources) { MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); mItemSources.emplace_back(source, store.resolveTemporarily()); } } ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) : mTrading(false) { MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); mItemSources.emplace_back(source, store.resolveTemporarily()); } bool ContainerItemModel::allowedToUseItems() const { if (mItemSources.empty()) return true; MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWWorld::Ptr victim; // Check if the player is allowed to use items from opened container MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); return mm->isAllowedToUse(ptr, mItemSources[0].first, victim); } ItemStack ContainerItemModel::getItem (ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); if (mItems.size() <= static_cast(index)) throw std::runtime_error("Item index out of range"); return mItems[index]; } size_t ContainerItemModel::getItemCount() { return mItems.size(); } ItemModel::ModelIndex ContainerItemModel::getIndex (const ItemStack& item) { size_t i = 0; for (ItemStack& itemStack : mItems) { if (itemStack == item) return i; ++i; } return -1; } MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { auto& source = mItemSources[0]; MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); if (item.mBase.getContainerStore() == &store) throw std::runtime_error("Item to copy needs to be from a different container!"); return *store.add(item.mBase, count, source.first, allowAutoEquip); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) { int toRemove = count; for (auto& source : mItemSources) { MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (stacks(*it, item.mBase)) { int quantity = it->mRef->mData.getCount(false); // If this is a restocking quantity, just don't remove it if(quantity < 0 && mTrading) toRemove += quantity; else toRemove -= store.remove(*it, toRemove, source.first); if (toRemove <= 0) return; } } } for (MWWorld::Ptr& source : mWorldItems) { if (stacks(source, item.mBase)) { int refCount = source.getRefData().getCount(); if (refCount - toRemove <= 0) MWBase::Environment::get().getWorld()->deleteObject(source); else source.getRefData().setCount(std::max(0, refCount - toRemove)); toRemove -= refCount; if (toRemove <= 0) return; } } throw std::runtime_error("Not enough items to remove could be found"); } void ContainerItemModel::update() { mItems.clear(); for (auto& source : mItemSources) { MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (!(*it).getClass().showsInInventory(*it)) continue; bool found = false; for (ItemStack& itemStack : mItems) { if (stacks(*it, itemStack.mBase)) { // we already have an item stack of this kind, add to it itemStack.mCount += it->getRefData().getCount(); found = true; break; } } if (!found) { // no stack yet, create one ItemStack newItem (*it, this, it->getRefData().getCount()); mItems.push_back(newItem); } } } for (MWWorld::Ptr& source : mWorldItems) { bool found = false; for (ItemStack& itemStack : mItems) { if (stacks(source, itemStack.mBase)) { // we already have an item stack of this kind, add to it itemStack.mCount += source.getRefData().getCount(); found = true; break; } } if (!found) { // no stack yet, create one ItemStack newItem (source, this, source.getRefData().getCount()); mItems.push_back(newItem); } } } bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) { if (mItemSources.empty()) return false; MWWorld::Ptr target = mItemSources[0].first; if (target.getType() != ESM::Container::sRecordId) return true; // check container organic flag MWWorld::LiveCellRef* ref = target.get(); if (ref->mBase->mFlags & ESM::Container::Organic) { MWBase::Environment::get().getWindowManager()-> messageBox("#{sContentsMessage2}"); return false; } // check that we don't exceed container capacity float weight = item.getClass().getWeight(item) * count; if (target.getClass().getCapacity(target) < target.getClass().getEncumbrance(target) + weight) { MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); return false; } return true; } bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { if (mItemSources.empty()) return false; MWWorld::Ptr target = mItemSources[0].first; // Looting a dead corpse is considered OK if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) return true; MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, target, count); return true; } bool ContainerItemModel::usesContainer(const MWWorld::Ptr& container) { for(const auto& source : mItemSources) { if(source.first == container) return true; } return false; } } openmw-openmw-0.48.0/apps/openmw/mwgui/containeritemmodel.hpp000066400000000000000000000031731445372753700244330ustar00rootroot00000000000000#ifndef MWGUI_CONTAINER_ITEM_MODEL_H #define MWGUI_CONTAINER_ITEM_MODEL_H #include #include #include "itemmodel.hpp" #include "../mwworld/containerstore.hpp" namespace MWGui { /// @brief The container item model supports multiple item sources, which are needed for /// making NPCs sell items from containers owned by them class ContainerItemModel : public ItemModel { public: ContainerItemModel (const std::vector& itemSources, const std::vector& worldItems); ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for removal, /// while the last element will be used to add new items to. ContainerItemModel (const MWWorld::Ptr& source); bool allowedToUseItems() const override; bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; ItemStack getItem (ModelIndex index) override; ModelIndex getIndex (const ItemStack &item) override; size_t getItemCount() override; MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem (const ItemStack& item, size_t count) override; void update() override; bool usesContainer(const MWWorld::Ptr& container) override; private: std::vector> mItemSources; std::vector mWorldItems; const bool mTrading; std::vector mItems; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/controllers.cpp000066400000000000000000000007061445372753700231110ustar00rootroot00000000000000#include "controllers.hpp" #include #include namespace MWGui { namespace Controllers { void ControllerFollowMouse::prepareItem(MyGUI::Widget *_widget) { } bool ControllerFollowMouse::addTime(MyGUI::Widget *_widget, float _time) { _widget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); return true; } } } openmw-openmw-0.48.0/apps/openmw/mwgui/controllers.hpp000066400000000000000000000010721445372753700231130ustar00rootroot00000000000000#ifndef MWGUI_CONTROLLERS_H #define MWGUI_CONTROLLERS_H #include #include namespace MyGUI { class Widget; } namespace MWGui::Controllers { /// Automatically positions a widget below the mouse cursor. class ControllerFollowMouse final : public MyGUI::ControllerItem { MYGUI_RTTI_DERIVED( ControllerFollowMouse ) private: bool addTime(MyGUI::Widget* _widget, float _time) override; void prepareItem(MyGUI::Widget* _widget) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/countdialog.cpp000066400000000000000000000060451445372753700230550ustar00rootroot00000000000000#include "countdialog.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" namespace MWGui { CountDialog::CountDialog() : WindowModal("openmw_count_window.layout") { getWidget(mSlider, "CountSlider"); getWidget(mItemEdit, "ItemEdit"); getWidget(mItemText, "ItemText"); getWidget(mLabelText, "LabelText"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onOkButtonClicked); mItemEdit->eventValueChanged += MyGUI::newDelegate(this, &CountDialog::onEditValueChanged); mSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &CountDialog::onSliderMoved); // make sure we read the enter key being pressed to accept multiple items mItemEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &CountDialog::onEnterKeyPressed); } void CountDialog::openCountDialog(const std::string& item, const std::string& message, const int maxCount) { setVisible(true); mLabelText->setCaptionWithReplacing(message); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); mSlider->setScrollRange(maxCount); mItemText->setCaption(item); int width = std::max(mItemText->getTextSize().width + 160, 320); setCoord(viewSize.width/2 - width/2, viewSize.height/2 - mMainWidget->getHeight()/2, width, mMainWidget->getHeight()); // by default, the text edit field has the focus of the keyboard MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mItemEdit); mSlider->setScrollPosition(maxCount-1); mItemEdit->setMinValue(1); mItemEdit->setMaxValue(maxCount); mItemEdit->setValue(maxCount); } void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender) { setVisible(false); } void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) { eventOkClicked(nullptr, mSlider->getScrollPosition()+1); setVisible(false); } // essentially duplicating what the OK button does if user presses // Enter key void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender) { eventOkClicked(nullptr, mSlider->getScrollPosition()+1); setVisible(false); // To do not spam onEnterKeyPressed() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void CountDialog::onEditValueChanged(int value) { mSlider->setScrollPosition(value-1); } void CountDialog::onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position) { mItemEdit->setValue(_position+1); } } openmw-openmw-0.48.0/apps/openmw/mwgui/countdialog.hpp000066400000000000000000000023131445372753700230540ustar00rootroot00000000000000#ifndef MWGUI_COUNTDIALOG_H #define MWGUI_COUNTDIALOG_H #include "windowbase.hpp" namespace Gui { class NumericEditBox; } namespace MWGui { class CountDialog : public WindowModal { public: CountDialog(); void openCountDialog(const std::string& item, const std::string& message, const int maxCount); typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; /** Event : Ok button was clicked.\n signature : void method(MyGUI::Widget* _sender, int _count)\n */ EventHandle_WidgetInt eventOkClicked; private: MyGUI::ScrollBar* mSlider; Gui::NumericEditBox* mItemEdit; MyGUI::TextBox* mItemText; MyGUI::TextBox* mLabelText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; void onCancelButtonClicked(MyGUI::Widget* _sender); void onOkButtonClicked(MyGUI::Widget* _sender); void onEditValueChanged(int value); void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position); void onEnterKeyPressed(MyGUI::EditBox* _sender); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/cursor.cpp000066400000000000000000000040031445372753700220520ustar00rootroot00000000000000#include "cursor.hpp" #include #include #include #include namespace MWGui { ResourceImageSetPointerFix::ResourceImageSetPointerFix() : mImageSet(nullptr) , mRotation(0) { } ResourceImageSetPointerFix::~ResourceImageSetPointerFix() { } void ResourceImageSetPointerFix::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) { Base::deserialization(_node, _version); MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); while (info.next("Property")) { const std::string& key = info->findAttribute("key"); const std::string& value = info->findAttribute("value"); if (key == "Point") mPoint = MyGUI::IntPoint::parse(value); else if (key == "Size") mSize = MyGUI::IntSize::parse(value); else if (key == "Rotation") mRotation = MyGUI::utility::parseInt(value); else if (key == "Resource") mImageSet = MyGUI::ResourceManager::getInstance().getByName(value)->castType(); } } int ResourceImageSetPointerFix::getRotation() { return mRotation; } void ResourceImageSetPointerFix::setImage(MyGUI::ImageBox* _image) { if (mImageSet != nullptr) _image->setItemResourceInfo(mImageSet->getIndexInfo(0, 0)); } void ResourceImageSetPointerFix::setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point) { _image->setCoord(_point.left - mPoint.left, _point.top - mPoint.top, mSize.width, mSize.height); } MyGUI::ResourceImageSetPtr ResourceImageSetPointerFix:: getImageSet() { return mImageSet; } MyGUI::IntPoint ResourceImageSetPointerFix::getHotSpot() { return mPoint; } MyGUI::IntSize ResourceImageSetPointerFix::getSize() { return mSize; } } openmw-openmw-0.48.0/apps/openmw/mwgui/cursor.hpp000066400000000000000000000025561445372753700220720ustar00rootroot00000000000000#ifndef MWGUI_CURSOR_H #define MWGUI_CURSOR_H #include #include namespace MWGui { /// \brief Allows us to get the members of /// ResourceImageSetPointer that we need. /// \example MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); /// MyGUI::ResourceManager::getInstance().load("core.xml"); class ResourceImageSetPointerFix final : public MyGUI::IPointer { MYGUI_RTTI_DERIVED( ResourceImageSetPointerFix ) public: ResourceImageSetPointerFix(); virtual ~ResourceImageSetPointerFix(); void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) override; void setImage(MyGUI::ImageBox* _image) override; void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point) override; //and now for the whole point of this class, allow us to get //the hot spot, the image and the size of the cursor. MyGUI::ResourceImageSetPtr getImageSet(); MyGUI::IntPoint getHotSpot(); MyGUI::IntSize getSize(); int getRotation(); private: MyGUI::IntPoint mPoint; MyGUI::IntSize mSize; MyGUI::ResourceImageSetPtr mImageSet; int mRotation; // rotation in degrees }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/debugwindow.cpp000066400000000000000000000174261445372753700230700ustar00rootroot00000000000000#include "debugwindow.hpp" #include #include #include #include #include #include #include #ifndef BT_NO_PROFILE namespace { void bulletDumpRecursive(CProfileIterator* pit, int spacing, std::stringstream& os) { pit->First(); if (pit->Is_Done()) return; float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); int i,j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; os << s; //float totalTime = 0.f; int numChildren = 0; for (i = 0; !pit->Is_Done(); i++,pit->Next()) { numChildren++; float current_total_time = pit->Get_Current_Total_Time(); accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; os << s; //totalTime += current_total_time; //recurse into children } if (parent_time < accumulated_time) { os << "what's wrong\n"; } for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; os << s; for (i=0;iEnter_Child(i); bulletDumpRecursive(pit, spacing+3, os); pit->Enter_Parent(); } } void bulletDumpAll(std::stringstream& os) { CProfileIterator* profileIterator = 0; profileIterator = CProfileManager::Get_Iterator(); bulletDumpRecursive(profileIterator, 0, os); CProfileManager::Release_Iterator(profileIterator); } } #endif // BT_NO_PROFILE namespace MWGui { DebugWindow::DebugWindow() : WindowBase("openmw_debug_window.layout") { getWidget(mTabControl, "TabControl"); // Ideas for other tabs: // - Texture / compositor texture viewer // - Material editor // - Shader editor MyGUI::TabItem* itemLV = mTabControl->addItem("Log Viewer"); itemLV->setCaptionWithReplacing(" #{DebugMenu:LogViewer} "); mLogView = itemLV->createWidgetReal ("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch); mLogView->setEditReadOnly(true); #ifndef BT_NO_PROFILE MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler"); item->setCaptionWithReplacing(" #{DebugMenu:PhysicsProfiler} "); mBulletProfilerEdit = item->createWidgetReal ("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch); #else mBulletProfilerEdit = nullptr; #endif } static std::vector sLogCircularBuffer; static std::mutex sBufferMutex; static int64_t sLogStartIndex; static int64_t sLogEndIndex; void DebugWindow::startLogRecording() { sLogCircularBuffer.resize(std::max(0, Settings::Manager::getInt64("log buffer size", "General"))); Debug::setLogListener([](Debug::Level level, std::string_view prefix, std::string_view msg) { if (sLogCircularBuffer.empty()) return; // Log viewer is disabled. std::string_view color; switch (level) { case Debug::Error: color = "#FF0000"; break; case Debug::Warning: color = "#FFFF00"; break; case Debug::Info: color = "#FFFFFF"; break; case Debug::Verbose: case Debug::Debug: color = "#666666"; break; default: color = "#FFFFFF"; } bool bufferOverflow = false; std::lock_guard lock(sBufferMutex); const int64_t bufSize = sLogCircularBuffer.size(); auto addChar = [&](char c) { sLogCircularBuffer[sLogEndIndex++] = c; if (sLogEndIndex == bufSize) sLogEndIndex = 0; bufferOverflow = bufferOverflow || sLogEndIndex == sLogStartIndex; }; auto addShieldedStr = [&](std::string_view s) { for (char c : s) { addChar(c); if (c == '#') addChar(c); } }; for (char c : color) addChar(c); addShieldedStr(prefix); addShieldedStr(msg); if (bufferOverflow) sLogStartIndex = (sLogEndIndex + 1) % bufSize; }); } void DebugWindow::updateLogView() { std::lock_guard lock(sBufferMutex); if (!mLogView || sLogCircularBuffer.empty() || sLogStartIndex == sLogEndIndex) return; if (mLogView->isTextSelection()) return; // Don't change text while player is trying to copy something std::string addition; const int64_t bufSize = sLogCircularBuffer.size(); { if (sLogStartIndex < sLogEndIndex) addition = std::string(sLogCircularBuffer.data() + sLogStartIndex, sLogEndIndex - sLogStartIndex); else { addition = std::string(sLogCircularBuffer.data() + sLogStartIndex, bufSize - sLogStartIndex); addition.append(sLogCircularBuffer.data(), sLogEndIndex); } sLogStartIndex = sLogEndIndex; } size_t scrollPos = mLogView->getVScrollPosition(); bool scrolledToTheEnd = scrollPos+1 >= mLogView->getVScrollRange(); int64_t newSizeEstimation = mLogView->getTextLength() + addition.size(); if (newSizeEstimation > bufSize) mLogView->eraseText(0, newSizeEstimation - bufSize); mLogView->addText(addition); if (scrolledToTheEnd && mLogView->getVScrollRange() > 0) mLogView->setVScrollPosition(mLogView->getVScrollRange() - 1); else mLogView->setVScrollPosition(scrollPos); } void DebugWindow::updateBulletProfile() { #ifndef BT_NO_PROFILE std::stringstream stream; bulletDumpAll(stream); if (mBulletProfilerEdit->isTextSelection()) // pause updating while user is trying to copy text return; size_t previousPos = mBulletProfilerEdit->getVScrollPosition(); mBulletProfilerEdit->setCaption(stream.str()); mBulletProfilerEdit->setVScrollPosition(std::min(previousPos, mBulletProfilerEdit->getVScrollRange()-1)); #endif } void DebugWindow::onFrame(float dt) { static float timer = 0; timer -= dt; if (timer > 0 || !isVisible()) return; timer = 0.25; if (mTabControl->getIndexSelected() == 0) updateLogView(); else updateBulletProfile(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/debugwindow.hpp000066400000000000000000000007661445372753700230740ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_DEBUGWINDOW_H #define OPENMW_MWGUI_DEBUGWINDOW_H #include "windowbase.hpp" namespace MWGui { class DebugWindow : public WindowBase { public: DebugWindow(); void onFrame(float dt) override; static void startLogRecording(); private: void updateLogView(); void updateBulletProfile(); MyGUI::TabControl* mTabControl; MyGUI::EditBox* mLogView; MyGUI::EditBox* mBulletProfilerEdit; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/dialogue.cpp000066400000000000000000001006651445372753700223410ustar00rootroot00000000000000#include "dialogue.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "bookpage.hpp" #include "textcolours.hpp" #include "tooltips.hpp" #include "journalbooks.hpp" // to_utf8_span namespace MWGui { class ResponseCallback : public MWBase::DialogueManager::ResponseCallback { public: ResponseCallback(DialogueWindow* win, bool needMargin=true) : mWindow(win) , mNeedMargin(needMargin) { } void addResponse(const std::string& title, const std::string& text) override { mWindow->addResponse(title, text, mNeedMargin); } void updateTopics() { mWindow->updateTopics(); } private: DialogueWindow* mWindow; bool mNeedMargin; }; PersuasionDialog::PersuasionDialog(ResponseCallback* callback) : WindowModal("openmw_persuasion_dialog.layout") , mCallback(callback) , mInitialGoldLabelWidth(0) , mInitialMainWidgetWidth(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mAdmireButton, "AdmireButton"); getWidget(mIntimidateButton, "IntimidateButton"); getWidget(mTauntButton, "TauntButton"); getWidget(mBribe10Button, "Bribe10Button"); getWidget(mBribe100Button, "Bribe100Button"); getWidget(mBribe1000Button, "Bribe1000Button"); getWidget(mGoldLabel, "GoldLabel"); getWidget(mActionsBox, "ActionsBox"); int totalHeight = 3; adjustAction(mAdmireButton, totalHeight); adjustAction(mIntimidateButton, totalHeight); adjustAction(mTauntButton, totalHeight); adjustAction(mBribe10Button, totalHeight); adjustAction(mBribe100Button, totalHeight); adjustAction(mBribe1000Button, totalHeight); totalHeight += 3; int diff = totalHeight - mActionsBox->getSize().height; if (diff > 0) { auto mainWidgetSize = mMainWidget->getSize(); mMainWidget->setSize(mainWidgetSize.width, mainWidgetSize.height + diff); } mInitialGoldLabelWidth = mActionsBox->getSize().width - mCancelButton->getSize().width - 8; mInitialMainWidgetWidth = mMainWidget->getSize().width; mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onCancel); mAdmireButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); mIntimidateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); mTauntButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); mBribe10Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); mBribe100Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); } void PersuasionDialog::adjustAction(MyGUI::Widget* action, int& totalHeight) { const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; auto currentCoords = action->getCoord(); action->setCoord(currentCoords.left, totalHeight, currentCoords.width, lineHeight); totalHeight += lineHeight; } void PersuasionDialog::onCancel(MyGUI::Widget *sender) { setVisible(false); } void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) type = MWBase::MechanicsManager::PT_Bribe10; else if (sender == mBribe100Button) type = MWBase::MechanicsManager::PT_Bribe100; else /*if (sender == mBribe1000Button)*/ type = MWBase::MechanicsManager::PT_Bribe1000; MWBase::Environment::get().getDialogueManager()->persuade(type, mCallback.get()); mCallback->updateTopics(); setVisible(false); } void PersuasionDialog::onOpen() { center(); MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mBribe10Button->setEnabled (playerGold >= 10); mBribe100Button->setEnabled (playerGold >= 100); mBribe1000Button->setEnabled (playerGold >= 1000); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); int diff = mGoldLabel->getRequestedSize().width - mInitialGoldLabelWidth; if (diff > 0) mMainWidget->setSize(mInitialMainWidgetWidth + diff, mMainWidget->getSize().height); else mMainWidget->setSize(mInitialMainWidgetWidth, mMainWidget->getSize().height); WindowModal::onOpen(); } MyGUI::Widget* PersuasionDialog::getDefaultKeyFocus() { return mAdmireButton; } // -------------------------------------------------------------------------------------------------- Response::Response(const std::string &text, const std::string &title, bool needMargin) : mTitle(title), mNeedMargin(needMargin) { mText = text; } void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { typesetter->sectionBreak(mNeedMargin ? 9 : 0); if (mTitle != "") { const MyGUI::Colour& headerColour = MWBase::Environment::get().getWindowManager()->getTextColours().header; BookTypesetter::Style* title = typesetter->createStyle("", headerColour, false); typesetter->write(title, to_utf8_span(mTitle.c_str())); typesetter->sectionBreak(); } typedef std::pair Range; std::map hyperLinks; // We need this copy for when @# hyperlinks are replaced std::string text = mText; size_t pos_end = std::string::npos; for(;;) { size_t pos_begin = text.find('@'); if (pos_begin != std::string::npos) pos_end = text.find('#', pos_begin); if (pos_begin != std::string::npos && pos_end != std::string::npos) { std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); const char specialPseudoAsteriskCharacter = 127; std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); std::string topicName = MWBase::Environment::get().getWindowManager()-> getTranslationDataStorage().topicStandardForm(link); std::string displayName = link; while (displayName[displayName.size()-1] == '*') displayName.erase(displayName.size()-1, 1); text.replace(pos_begin, pos_end+1-pos_begin, displayName); if (topicLinks.find(Misc::StringUtils::lowerCase(topicName)) != topicLinks.end()) hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[Misc::StringUtils::lowerCase(topicName)]); } else break; } typesetter->addContent(to_utf8_span(text.c_str())); if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); size_t formatted = 0; // points to the first character that is not laid out yet for (auto& hyperLink : hyperLinks) { intptr_t topicId = hyperLink.second; BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); if (formatted < hyperLink.first.first) typesetter->write(style, formatted, hyperLink.first.first); typesetter->write(hotStyle, hyperLink.first.first, hyperLink.first.second); formatted = hyperLink.first.second; } if (formatted < text.size()) typesetter->write(style, formatted, text.size()); } else { std::vector matches; keywordSearch->highlightKeywords(text.begin(), text.end(), matches); std::string::const_iterator i = text.begin (); for (KeywordSearchT::Match& match : matches) { if (i != match.mBeg) addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); addTopicLink (typesetter, match.mValue, match.mBeg - text.begin (), match.mEnd - text.begin ()); i = match.mEnd; } if (i != text.end ()) addTopicLink (typesetter, 0, i - text.begin (), text.size ()); } } void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); if (topicId) style = typesetter->createHotStyle (style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); typesetter->write (style, begin, end); } Message::Message(const std::string& text) { mText = text; } void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { const MyGUI::Colour& textColour = MWBase::Environment::get().getWindowManager()->getTextColours().notify; BookTypesetter::Style* title = typesetter->createStyle("", textColour, false); typesetter->sectionBreak(9); typesetter->write(title, to_utf8_span(mText.c_str())); } // -------------------------------------------------------------------------------------------------- void Choice::activated() { MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); eventChoiceActivated(mChoiceId); } void Topic::activated() { MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); eventTopicActivated(mTopicId); } void Goodbye::activated() { MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); eventActivated(); } // -------------------------------------------------------------------------------------------------- DialogueWindow::DialogueWindow() : WindowBase("openmw_dialogue_window.layout") , mIsCompanion(false) , mGoodbye(false) , mPersuasionDialog(new ResponseCallback(this)) , mCallback(new ResponseCallback(this)) , mGreetingCallback(new ResponseCallback(this, false)) { // Centre dialog center(); mPersuasionDialog.setVisible(false); //History view getWidget(mHistory, "History"); //Topics list getWidget(mTopicsList, "TopicsList"); mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectListItem); getWidget(mGoodbyeButton, "ByeButton"); mGoodbyeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); getWidget(mDispositionBar, "Disposition"); getWidget(mDispositionText,"DispositionText"); getWidget(mScrollBar, "VScroll"); mScrollBar->eventScrollChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onScrollbarMoved); mHistory->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); BookPage::ClickCallback callback = std::bind (&DialogueWindow::notifyLinkClicked, this, std::placeholders::_1); mHistory->adviseLinkClicked(callback); mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } DialogueWindow::~DialogueWindow() { deleteLater(); for (Link* link : mLinks) delete link; for (const auto& link : mTopicLinks) delete link.second; for (auto history : mHistoryContents) delete history; } void DialogueWindow::onTradeComplete() { addResponse("", MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}")); } bool DialogueWindow::exit() { if ((MWBase::Environment::get().getDialogueManager()->isInChoice())) { return false; } else { resetReference(); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); mTopicsList->scrollToTop(); return true; } } void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { // if the window has only been moved, not resized, we don't need to update if (mCurrentWindowSize == _sender->getSize()) return; mTopicsList->adjustSize(); updateHistory(); updateTopicFormat(); mCurrentWindowSize = _sender->getSize(); } void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (!mScrollBar->getVisible()) return; mScrollBar->setScrollPosition(std::clamp(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1)); onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition()); } void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { if (exit()) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); } } void DialogueWindow::onSelectListItem(const std::string& topic, int id) { MWBase::DialogueManager* dialogueManager = MWBase::Environment::get().getDialogueManager(); if (mGoodbye || dialogueManager->isInChoice()) return; const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); const std::string& sPersuasion = gmst.find("sPersuasion")->mValue.getString(); const std::string& sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); const std::string& sBarter = gmst.find("sBarter")->mValue.getString(); const std::string& sSpells = gmst.find("sSpells")->mValue.getString(); const std::string& sTravel = gmst.find("sTravel")->mValue.getString(); const std::string& sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); const std::string& sEnchanting = gmst.find("sEnchanting")->mValue.getString(); const std::string& sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); const std::string& sRepair = gmst.find("sRepair")->mValue.getString(); if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter && topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle && topic != sEnchanting && topic != sServiceTrainingTitle && topic != sRepair) { onTopicActivated(topic); if (mGoodbyeButton->getEnabled()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); } else if (topic == sPersuasion) mPersuasionDialog.setVisible(true); else if (topic == sCompanionShare) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); else if (!dialogueManager->checkServiceRefused(mCallback.get())) { if (topic == sBarter && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Barter)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); else if (topic == sSpells && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spells)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); else if (topic == sTravel && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Travel)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); else if (topic == sSpellMakingMenuTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spellmaking)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); else if (topic == sEnchanting && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Enchanting)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); else if (topic == sServiceTrainingTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Training)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); else if (topic == sRepair && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Repair)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); } else updateTopics(); } void DialogueWindow::setPtr(const MWWorld::Ptr& actor) { if (!actor.getClass().isActor()) { Log(Debug::Warning) << "Warning: can not talk with non-actor object."; return; } bool sameActor = (mPtr == actor); if (!sameActor) { // The history is not reset here mKeywords.clear(); mTopicsList->clear(); for (Link* link : mLinks) mDeleteLater.push_back(link); // Links are not deleted right away to prevent issues with event handlers mLinks.clear(); } mPtr = actor; mGoodbye = false; mTopicsList->setEnabled(true); if (!MWBase::Environment::get().getDialogueManager()->startDialogue(actor, mGreetingCallback.get())) { // No greetings found. The dialogue window should not be shown. // If this is a companion, we must show the companion window directly (used by BM_bear_be_unique). MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); mPtr = MWWorld::Ptr(); if (isCompanion(actor)) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Companion, actor); return; } MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); setTitle(mPtr.getClass().getName(mPtr)); updateTopics(); updateTopicsPane(); // force update for new services updateDisposition(); restock(); } void DialogueWindow::restock() { MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->mValue.getFloat(); // Gold is restocked every 24h if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) { sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } void DialogueWindow::deleteLater() { for (Link* link : mDeleteLater) delete link; mDeleteLater.clear(); } void DialogueWindow::onClose() { if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Dialogue)) return; // Reset history for (DialogueText* text : mHistoryContents) delete text; mHistoryContents.clear(); } bool DialogueWindow::setKeywords(const std::list& keyWords) { if (mKeywords == keyWords && isCompanion() == mIsCompanion) return false; mIsCompanion = isCompanion(); mKeywords = keyWords; updateTopicsPane(); return true; } void DialogueWindow::updateTopicsPane() { mTopicsList->clear(); for (auto& linkPair : mTopicLinks) mDeleteLater.push_back(linkPair.second); mTopicLinks.clear(); mKeywordSearch.clear(); int services = mPtr.getClass().getServices(mPtr); bool travel = (mPtr.getType() == ESM::NPC::sRecordId && !mPtr.get()->mBase->getTransport().empty()) || (mPtr.getType() == ESM::Creature::sRecordId && !mPtr.get()->mBase->getTransport().empty()); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); if (mPtr.getType() == ESM::NPC::sRecordId) mTopicsList->addItem(gmst.find("sPersuasion")->mValue.getString()); if (services & ESM::NPC::AllItems) mTopicsList->addItem(gmst.find("sBarter")->mValue.getString()); if (services & ESM::NPC::Spells) mTopicsList->addItem(gmst.find("sSpells")->mValue.getString()); if (travel) mTopicsList->addItem(gmst.find("sTravel")->mValue.getString()); if (services & ESM::NPC::Spellmaking) mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->mValue.getString()); if (services & ESM::NPC::Enchanting) mTopicsList->addItem(gmst.find("sEnchanting")->mValue.getString()); if (services & ESM::NPC::Training) mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->mValue.getString()); if (services & ESM::NPC::Repair) mTopicsList->addItem(gmst.find("sRepair")->mValue.getString()); if (isCompanion()) mTopicsList->addItem(gmst.find("sCompanionShare")->mValue.getString()); if (mTopicsList->getItemCount() > 0) mTopicsList->addSeparator(); for(const auto& keyword : mKeywords) { std::string topicId = Misc::StringUtils::lowerCase(keyword); mTopicsList->addItem(keyword); Topic* t = new Topic(keyword); t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); mTopicLinks[topicId] = t; mKeywordSearch.seed(topicId, intptr_t(t)); } mTopicsList->adjustSize(); updateHistory(); // The topics list has been regenerated so topic formatting needs to be updated updateTopicFormat(); } void DialogueWindow::updateHistory(bool scrollbar) { if (!scrollbar && mScrollBar->getVisible()) { mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0)); mScrollBar->setVisible(false); } if (scrollbar && !mScrollBar->getVisible()) { mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0)); mScrollBar->setVisible(true); } BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits::max()); for (DialogueText* text : mHistoryContents) text->write(typesetter, &mKeywordSearch, mTopicLinks); BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White, false); typesetter->sectionBreak(9); // choices const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); mChoices = MWBase::Environment::get().getDialogueManager()->getChoices(); for (std::pair& choice : mChoices) { Choice* link = new Choice(choice.second); link->eventChoiceActivated += MyGUI::newDelegate(this, &DialogueWindow::onChoiceActivated); mLinks.push_back(link); typesetter->lineBreak(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, textColours.answerPressed, TypesetBook::InteractiveId(link)); typesetter->write(questionStyle, to_utf8_span(choice.first.c_str())); } mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye(); if (mGoodbye) { Goodbye* link = new Goodbye(); link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated); mLinks.push_back(link); const std::string& goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->mValue.getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, textColours.answerPressed, TypesetBook::InteractiveId(link)); typesetter->lineBreak(); typesetter->write(questionStyle, to_utf8_span(goodbye.c_str())); } TypesetBook::Ptr book = typesetter->complete(); mHistory->showPage(book, 0); size_t viewHeight = mHistory->getParent()->getHeight(); if (!scrollbar && book->getSize().second > viewHeight) updateHistory(true); else if (scrollbar) { mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); size_t range = book->getSize().second - viewHeight; mScrollBar->setScrollRange(range); mScrollBar->setScrollPosition(range-1); mScrollBar->setTrackSize(static_cast(viewHeight / static_cast(book->getSize().second) * mScrollBar->getLineSize())); onScrollbarMoved(mScrollBar, range-1); } else { // no scrollbar onScrollbarMoved(mScrollBar, 0); } bool goodbyeEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() || mGoodbye; bool goodbyeWasEnabled = mGoodbyeButton->getEnabled(); mGoodbyeButton->setEnabled(goodbyeEnabled); if (goodbyeEnabled && !goodbyeWasEnabled) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); bool topicsEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() && !mGoodbye; mTopicsList->setEnabled(topicsEnabled); } void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link) { reinterpret_cast(link)->activated(); } void DialogueWindow::onTopicActivated(const std::string &topicId) { if (mGoodbye) return; MWBase::Environment::get().getDialogueManager()->keywordSelected(topicId, mCallback.get()); updateTopics(); } void DialogueWindow::onChoiceActivated(int id) { if (mGoodbye) { onGoodbyeActivated(); return; } MWBase::Environment::get().getDialogueManager()->questionAnswered(id, mCallback.get()); updateTopics(); } void DialogueWindow::onGoodbyeActivated() { MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); resetReference(); } void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) { mHistory->setPosition(0, static_cast(pos) * -1); } void DialogueWindow::addResponse(const std::string &title, const std::string &text, bool needMargin) { mHistoryContents.push_back(new Response(text, title, needMargin)); updateHistory(); } void DialogueWindow::addMessageBox(const std::string& text) { mHistoryContents.push_back(new Message(text)); updateHistory(); } void DialogueWindow::updateDisposition() { bool dispositionVisible = false; if (!mPtr.isEmpty() && mPtr.getClass().isNpc()) { dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); mDispositionText->setCaption(MyGUI::utility::toString(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")); } bool dispositionWasVisible = mDispositionBar->getVisible(); if (dispositionVisible && !dispositionWasVisible) { mDispositionBar->setVisible(true); int offset = mDispositionBar->getHeight()+5; mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset)); mTopicsList->adjustSize(); } else if (!dispositionVisible && dispositionWasVisible) { mDispositionBar->setVisible(false); int offset = mDispositionBar->getHeight()+5; mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset)); mTopicsList->adjustSize(); } } void DialogueWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); } void DialogueWindow::onFrame(float dt) { checkReferenceAvailable(); if (mPtr.isEmpty()) return; updateDisposition(); deleteLater(); if (mChoices != MWBase::Environment::get().getDialogueManager()->getChoices() || mGoodbye != MWBase::Environment::get().getDialogueManager()->isGoodbye()) updateHistory(); } void DialogueWindow::updateTopicFormat() { if (!Settings::Manager::getBool("color topic enable", "GUI")) return; std::string specialColour = Settings::Manager::getString("color topic specific", "GUI"); std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI"); for (const std::string& keyword : mKeywords) { int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(keyword); MyGUI::Button* button = mTopicsList->getItemWidget(keyword); if (!specialColour.empty() && flag & MWBase::DialogueManager::TopicType::Specific) button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(specialColour)); else if (!oldColour.empty() && flag & MWBase::DialogueManager::TopicType::Exhausted) button->getSubWidgetText()->setTextColour(MyGUI::Colour::parse(oldColour)); } } void DialogueWindow::updateTopics() { // Topic formatting needs to be updated regardless of whether the topic list has changed if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics())) updateTopicFormat(); } bool DialogueWindow::isCompanion() { return isCompanion(mPtr); } bool DialogueWindow::isCompanion(const MWWorld::Ptr& actor) { if (actor.isEmpty()) return false; return !actor.getClass().getScript(actor).empty() && actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/dialogue.hpp000066400000000000000000000126541445372753700223460ustar00rootroot00000000000000#ifndef MWGUI_DIALOGE_H #define MWGUI_DIALOGE_H #include "windowbase.hpp" #include "referenceinterface.hpp" #include "bookpage.hpp" #include "../mwdialogue/keywordsearch.hpp" #include namespace Gui { class AutoSizedTextBox; class MWList; } namespace MWGui { class ResponseCallback; class PersuasionDialog : public WindowModal { public: PersuasionDialog(ResponseCallback* callback); void onOpen() override; MyGUI::Widget* getDefaultKeyFocus() override; private: std::unique_ptr mCallback; int mInitialGoldLabelWidth; int mInitialMainWidgetWidth; MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; MyGUI::Button* mIntimidateButton; MyGUI::Button* mTauntButton; MyGUI::Button* mBribe10Button; MyGUI::Button* mBribe100Button; MyGUI::Button* mBribe1000Button; MyGUI::Widget* mActionsBox; Gui::AutoSizedTextBox* mGoldLabel; void adjustAction(MyGUI::Widget* action, int& totalHeight); void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); }; struct Link { virtual ~Link() {} virtual void activated () = 0; }; struct Topic : Link { typedef MyGUI::delegates::CMultiDelegate1 EventHandle_TopicId; EventHandle_TopicId eventTopicActivated; Topic(const std::string& id) : mTopicId(id) {} std::string mTopicId; void activated () override; }; struct Choice : Link { typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ChoiceId; EventHandle_ChoiceId eventChoiceActivated; Choice(int id) : mChoiceId(id) {} int mChoiceId; void activated () override; }; struct Goodbye : Link { typedef MyGUI::delegates::CMultiDelegate0 Event_Activated; Event_Activated eventActivated; void activated () override; }; typedef MWDialogue::KeywordSearch KeywordSearchT; struct DialogueText { virtual ~DialogueText() {} virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const = 0; std::string mText; }; struct Response : DialogueText { Response(const std::string& text, const std::string& title = "", bool needMargin = true); void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const override; void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const; std::string mTitle; bool mNeedMargin; }; struct Message : DialogueText { Message(const std::string& text); void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const override; }; class DialogueWindow: public WindowBase, public ReferenceInterface { public: DialogueWindow(); ~DialogueWindow(); void onTradeComplete(); bool exit() override; // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; void notifyLinkClicked (TypesetBook::InteractiveId link); void setPtr(const MWWorld::Ptr& actor) override; /// @return true if stale keywords were updated successfully bool setKeywords(const std::list& keyWord); void addResponse (const std::string& title, const std::string& text, bool needMargin = true); void addMessageBox(const std::string& text); void onFrame(float dt) override; void clear() override { resetReference(); } void updateTopics(); void onClose() override; protected: void updateTopicsPane(); bool isCompanion(const MWWorld::Ptr& actor); bool isCompanion(); void onSelectListItem(const std::string& topic, int id); void onByeClicked(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onWindowResize(MyGUI::Window* _sender); void onTopicActivated(const std::string& topicId); void onChoiceActivated(int id); void onGoodbyeActivated(); void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos); void updateHistory(bool scrollbar=false); void onReferenceUnavailable() override; private: void updateDisposition(); void restock(); void deleteLater(); bool mIsCompanion; std::list mKeywords; std::vector mHistoryContents; std::vector > mChoices; bool mGoodbye; std::vector mLinks; std::map mTopicLinks; std::vector mDeleteLater; KeywordSearchT mKeywordSearch; BookPage* mHistory; Gui::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; MyGUI::ProgressBar* mDispositionBar; MyGUI::TextBox* mDispositionText; MyGUI::Button* mGoodbyeButton; PersuasionDialog mPersuasionDialog; MyGUI::IntSize mCurrentWindowSize; std::unique_ptr mCallback; std::unique_ptr mGreetingCallback; void updateTopicFormat(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/draganddrop.cpp000066400000000000000000000110061445372753700230230ustar00rootroot00000000000000#include "draganddrop.hpp" #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "sortfilteritemmodel.hpp" #include "inventorywindow.hpp" #include "itemwidget.hpp" #include "itemview.hpp" #include "controllers.hpp" namespace MWGui { DragAndDrop::DragAndDrop() : mIsOnDragAndDrop(false) , mDraggedWidget(nullptr) , mSourceModel(nullptr) , mSourceView(nullptr) , mSourceSortModel(nullptr) , mDraggedCount(0) { } void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) { mItem = sourceModel->getItem(index); mDraggedCount = count; mSourceModel = sourceModel; mSourceView = sourceView; mSourceSortModel = sortModel; // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); if (mSourceModel != playerModel) { MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); playerModel->update(); ItemModel::ModelIndex newIndex = -1; for (unsigned int i=0; igetItemCount(); ++i) { if (playerModel->getItem(i).mBase == item) { newIndex = i; break; } } mItem = playerModel->getItem(newIndex); mSourceModel = playerModel; SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); mSourceSortModel = playerFilterModel; } std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); MWBase::Environment::get().getWindowManager()->playSound (sound); if (mSourceSortModel) { mSourceSortModel->clearDragItems(); mSourceSortModel->addDragItem(mItem.mBase, count); } ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); Controllers::ControllerFollowMouse* controller = MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) ->castType(); MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); mDraggedWidget = baseWidget; baseWidget->setItem(mItem.mBase); baseWidget->setNeedMouseFocus(false); baseWidget->setCount(count); sourceView->update(); MWBase::Environment::get().getWindowManager()->setDragDrop(true); mIsOnDragAndDrop = true; } void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) { std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); MWBase::Environment::get().getWindowManager()->playSound(sound); // We can't drop a conjured item to the ground; the target container should always be the source container if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) { MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); return; } // If item is dropped where it was taken from, we don't need to do anything - // otherwise, do the transfer if (targetModel != mSourceModel) { mSourceModel->moveItem(mItem, mDraggedCount, targetModel); } mSourceModel->update(); finish(); if (targetView) targetView->update(); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); // We need to update the view since an other item could be auto-equipped. mSourceView->update(); } void DragAndDrop::onFrame() { if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) finish(); } void DragAndDrop::finish() { mIsOnDragAndDrop = false; mSourceSortModel->clearDragItems(); // since mSourceView doesn't get updated in drag() MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); mDraggedWidget = nullptr; MWBase::Environment::get().getWindowManager()->setDragDrop(false); } } openmw-openmw-0.48.0/apps/openmw/mwgui/draganddrop.hpp000066400000000000000000000013721445372753700230350ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_DRAGANDDROP_H #define OPENMW_MWGUI_DRAGANDDROP_H #include "itemmodel.hpp" namespace MyGUI { class Widget; } namespace MWGui { class ItemView; class SortFilterItemModel; class DragAndDrop { public: bool mIsOnDragAndDrop; MyGUI::Widget* mDraggedWidget; ItemModel* mSourceModel; ItemView* mSourceView; SortFilterItemModel* mSourceSortModel; ItemStack mItem; int mDraggedCount; DragAndDrop(); void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); void drop (ItemModel* targetModel, ItemView* targetView); void onFrame(); void finish(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/enchantingdialog.cpp000066400000000000000000000326701445372753700240460ustar00rootroot00000000000000#include "enchantingdialog.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "itemselection.hpp" #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" namespace MWGui { EnchantingDialog::EnchantingDialog() : WindowBase("openmw_enchanting_dialog.layout") , EffectEditorBase(EffectEditorBase::Enchanting) , mItemSelectionDialog(nullptr) { getWidget(mName, "NameEdit"); getWidget(mCancelButton, "CancelButton"); getWidget(mAvailableEffectsList, "AvailableEffects"); getWidget(mUsedEffectsView, "UsedEffects"); getWidget(mItemBox, "ItemBox"); getWidget(mSoulBox, "SoulBox"); getWidget(mEnchantmentPoints, "Enchantment"); getWidget(mCastCost, "CastCost"); getWidget(mCharge, "Charge"); getWidget(mSuccessChance, "SuccessChance"); getWidget(mChanceLayout, "ChanceLayout"); getWidget(mTypeButton, "TypeButton"); getWidget(mBuyButton, "BuyButton"); getWidget(mPrice, "PriceLabel"); getWidget(mPriceText, "PriceTextLabel"); setWidgets(mAvailableEffectsList, mUsedEffectsView); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onCancelButtonClicked); mItemBox->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onSelectItem); mSoulBox->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onSelectSoul); mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onBuyButtonClicked); mTypeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onTypeButtonClicked); mName->eventEditSelectAccept += MyGUI::newDelegate(this, &EnchantingDialog::onAccept); } EnchantingDialog::~EnchantingDialog() { delete mItemSelectionDialog; } void EnchantingDialog::onOpen() { center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) { if (gem.isEmpty()) { mSoulBox->setItem(MWWorld::Ptr()); mSoulBox->clearUserStrings(); mEnchanting.setSoulGem(MWWorld::Ptr()); } else { mSoulBox->setItem(gem); mSoulBox->setUserString ("ToolTipType", "ItemPtr"); mSoulBox->setUserData(MWWorld::Ptr(gem)); mEnchanting.setSoulGem(gem); } } void EnchantingDialog::setItem(const MWWorld::Ptr &item) { if (item.isEmpty()) { mItemBox->setItem(MWWorld::Ptr()); mItemBox->clearUserStrings(); mEnchanting.setOldItem(MWWorld::Ptr()); } else { mName->setCaption(item.getClass().getName(item)); mItemBox->setItem(item); mItemBox->setUserString ("ToolTipType", "ItemPtr"); mItemBox->setUserData(MWWorld::Ptr(item)); mEnchanting.setOldItem(item); } } void EnchantingDialog::updateLabels() { mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); mSuccessChance->setCaption(std::to_string(std::clamp(mEnchanting.getEnchantChance(), 0, 100))); mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); switch(mEnchanting.getCastStyle()) { case ESM::Enchantment::CastOnce: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce","Cast Once")); setConstantEffect(false); break; case ESM::Enchantment::WhenStrikes: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenStrikes", "When Strikes")); setConstantEffect(false); break; case ESM::Enchantment::WhenUsed: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenUsed", "When Used")); setConstantEffect(false); break; case ESM::Enchantment::ConstantEffect: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastConstant", "Cast Constant")); setConstantEffect(true); break; } } void EnchantingDialog::setPtr (const MWWorld::Ptr& ptr) { mName->setCaption(""); if (ptr.getClass().isActor()) { mEnchanting.setSelfEnchanting(false); mEnchanting.setEnchanter(ptr); mBuyButton->setCaptionWithReplacing("#{sBuy}"); mChanceLayout->setVisible(false); mPtr = ptr; setSoulGem(MWWorld::Ptr()); mPrice->setVisible(true); mPriceText->setVisible(true); } else { mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(MWMechanics::getPlayer()); mBuyButton->setCaptionWithReplacing("#{sCreate}"); bool enabled = Settings::Manager::getBool("show enchant chance","Game"); mChanceLayout->setVisible(enabled); mPtr = MWMechanics::getPlayer(); setSoulGem(ptr); mPrice->setVisible(false); mPriceText->setVisible(false); } setItem(MWWorld::Ptr()); startEditing (); updateLabels(); } void EnchantingDialog::onReferenceUnavailable () { MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); resetReference(); } void EnchantingDialog::resetReference() { ReferenceInterface::resetReference(); setItem(MWWorld::Ptr()); setSoulGem(MWWorld::Ptr()); mPtr = MWWorld::Ptr(); mEnchanting.setEnchanter(MWWorld::Ptr()); } void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); } void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) { if (mEnchanting.getOldItem().isEmpty()) { delete mItemSelectionDialog; mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); } else { setItem(MWWorld::Ptr()); updateLabels(); } } void EnchantingDialog::onItemSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); setItem(item); MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); mEnchanting.nextCastStyle(); updateLabels(); } void EnchantingDialog::onItemCancel() { mItemSelectionDialog->setVisible(false); } void EnchantingDialog::onSoulSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); mEnchanting.setSoulGem(item); if(mEnchanting.getGemCharge()==0) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage32}"); return; } setSoulGem(item); MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); updateLabels(); } void EnchantingDialog::onSoulCancel() { mItemSelectionDialog->setVisible(false); } void EnchantingDialog::onSelectSoul(MyGUI::Widget *sender) { if (mEnchanting.getGem().isEmpty()) { delete mItemSelectionDialog; mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); } else { setSoulGem(MWWorld::Ptr()); mEnchanting.nextCastStyle(); updateLabels(); updateEffectsView(); } } void EnchantingDialog::notifyEffectsChanged () { mEffectList.mList = mEffects; mEnchanting.setEffect(mEffectList); updateLabels(); } void EnchantingDialog::onTypeButtonClicked(MyGUI::Widget* sender) { mEnchanting.nextCastStyle(); updateLabels(); updateEffectsView(); } void EnchantingDialog::onAccept(MyGUI::EditBox *sender) { onBuyButtonClicked(sender); // To do not spam onAccept() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void EnchantingDialog::onBuyButtonClicked(MyGUI::Widget* sender) { if (mEffects.size() <= 0) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu11}"); return; } if (mName->getCaption ().empty()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}"); return; } if (mEnchanting.soulEmpty()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage52}"); return; } if (mEnchanting.itemEmpty()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage11}"); return; } if (static_cast(mEnchanting.getEnchantPoints(false)) > mEnchanting.getMaxEnchantValue()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage29}"); return; } mEnchanting.setNewItemName(mName->getCaption()); mEnchanting.setEffect(mEffectList); MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; } // check if the player is attempting to use a soulstone or item that was stolen from this actor if (mPtr != player) { for (int i=0; i<2; ++i) { MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->mValue.getString(); msg = Misc::StringUtils::format(msg, item.getClass().getName(item)); MWBase::Environment::get().getWindowManager()->messageBox(msg); MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } } } int result = mEnchanting.create(); if(result==1) { MWBase::Environment::get().getWindowManager()->playSound("enchant success"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } else { MWBase::Environment::get().getWindowManager()->playSound("enchant fail"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}"); if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getRefData().getCount()) { setSoulGem(MWWorld::Ptr()); mEnchanting.nextCastStyle(); updateLabels(); updateEffectsView(); } } } } openmw-openmw-0.48.0/apps/openmw/mwgui/enchantingdialog.hpp000066400000000000000000000041161445372753700240450ustar00rootroot00000000000000#ifndef MWGUI_ENCHANTINGDIALOG_H #define MWGUI_ENCHANTINGDIALOG_H #include "spellcreationdialog.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/enchanting.hpp" namespace MWGui { class ItemSelectionDialog; class ItemWidget; class EnchantingDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase { public: EnchantingDialog(); virtual ~EnchantingDialog(); void onOpen() override; void onFrame(float dt) override { checkReferenceAvailable(); } void clear() override { resetReference(); } void setSoulGem (const MWWorld::Ptr& gem); void setItem (const MWWorld::Ptr& item); /// Actor Ptr: buy enchantment from this actor /// Soulgem Ptr: player self-enchant void setPtr(const MWWorld::Ptr& ptr) override; void resetReference() override; protected: void onReferenceUnavailable() override; void notifyEffectsChanged() override; void onCancelButtonClicked(MyGUI::Widget* sender); void onSelectItem (MyGUI::Widget* sender); void onSelectSoul (MyGUI::Widget* sender); void onItemSelected(MWWorld::Ptr item); void onItemCancel(); void onSoulSelected(MWWorld::Ptr item); void onSoulCancel(); void onBuyButtonClicked(MyGUI::Widget* sender); void updateLabels(); void onTypeButtonClicked(MyGUI::Widget* sender); void onAccept(MyGUI::EditBox* sender); ItemSelectionDialog* mItemSelectionDialog; MyGUI::Widget* mChanceLayout; MyGUI::Button* mCancelButton; ItemWidget* mItemBox; ItemWidget* mSoulBox; MyGUI::Button* mTypeButton; MyGUI::Button* mBuyButton; MyGUI::EditBox* mName; MyGUI::TextBox* mEnchantmentPoints; MyGUI::TextBox* mCastCost; MyGUI::TextBox* mCharge; MyGUI::TextBox* mSuccessChance; MyGUI::TextBox* mPrice; MyGUI::TextBox* mPriceText; MWMechanics::Enchanting mEnchanting; ESM::EffectList mEffectList; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/exposedwindow.cpp000066400000000000000000000011661445372753700234430ustar00rootroot00000000000000#include "exposedwindow.hpp" namespace MWGui { MyGUI::VectorWidgetPtr Window::getSkinWidgetsByName (const std::string &name) { return MyGUI::Widget::getSkinWidgetsByName (name); } MyGUI::Widget* Window::getSkinWidget(const std::string & _name, bool _throw) { MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName (_name); if (widgets.empty()) { MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'"); return nullptr; } else { return widgets[0]; } } } openmw-openmw-0.48.0/apps/openmw/mwgui/exposedwindow.hpp000066400000000000000000000010301445372753700234360ustar00rootroot00000000000000#ifndef MWGUI_EXPOSEDWINDOW_H #define MWGUI_EXPOSEDWINDOW_H #include namespace MWGui { /** * @brief subclass to provide access to some Widget internals. */ class Window : public MyGUI::Window { MYGUI_RTTI_DERIVED(Window) public: MyGUI::VectorWidgetPtr getSkinWidgetsByName (const std::string &name); MyGUI::Widget* getSkinWidget(const std::string & _name, bool _throw = true); ///< Get a widget defined in the inner skin of this window. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/formatting.cpp000066400000000000000000000441721445372753700227220ustar00rootroot00000000000000#include "formatting.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwscript/interpretercontext.hpp" namespace MWGui::Formatting { /* BookTextParser */ BookTextParser::BookTextParser(const std::string & text) : mIndex(0), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true), mClosingTag(false) { MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); // empty arguments, because there is no locals or actor mText = Interpreter::fixDefinesBook(mText, interpreterContext); Misc::StringUtils::replaceAll(mText, "\r", ""); // vanilla game does not show any text after the last EOL tag. const std::string lowerText = Misc::StringUtils::lowerCase(mText); size_t brIndex = lowerText.rfind("
"); size_t pIndex = lowerText.rfind("

"); mPlainTextEnd = 0; if (brIndex != pIndex) { if (brIndex != std::string::npos && pIndex != std::string::npos) mPlainTextEnd = std::max(brIndex, pIndex); else if (brIndex != std::string::npos) mPlainTextEnd = brIndex; else mPlainTextEnd = pIndex; } registerTag("br", Event_BrTag); registerTag("p", Event_PTag); registerTag("img", Event_ImgTag); registerTag("div", Event_DivTag); registerTag("font", Event_FontTag); } void BookTextParser::registerTag(const std::string & tag, BookTextParser::Events type) { mTagTypes[tag] = type; } std::string BookTextParser::getReadyText() const { return mReadyText; } BookTextParser::Events BookTextParser::next() { while (mIndex < mText.size()) { char ch = mText[mIndex]; if (ch == '<') { const size_t tagStart = mIndex + 1; const size_t tagEnd = mText.find('>', tagStart); if (tagEnd == std::string::npos) throw std::runtime_error("BookTextParser Error: Tag is not terminated"); parseTag(mText.substr(tagStart, tagEnd - tagStart)); mIndex = tagEnd; if (mTagTypes.find(mTag) != mTagTypes.end()) { Events type = mTagTypes.at(mTag); if (type == Event_BrTag || type == Event_PTag) { if (!mIgnoreNewlineTags) { if (type == Event_BrTag) mBuffer.push_back('\n'); else { mBuffer.append("\n\n"); } } mIgnoreLineEndings = true; } else flushBuffer(); if (type == Event_ImgTag) { mIgnoreNewlineTags = false; } ++mIndex; return type; } } else { if (!mIgnoreLineEndings || ch != '\n') { if (mIndex < mPlainTextEnd) mBuffer.push_back(ch); mIgnoreLineEndings = false; mIgnoreNewlineTags = false; } } ++mIndex; } flushBuffer(); return Event_EOF; } void BookTextParser::flushBuffer() { mReadyText = mBuffer; mBuffer.clear(); } const BookTextParser::Attributes & BookTextParser::getAttributes() const { return mAttributes; } bool BookTextParser::isClosingTag() const { return mClosingTag; } void BookTextParser::parseTag(std::string tag) { size_t tagNameEndPos = tag.find(' '); mAttributes.clear(); mTag = tag.substr(0, tagNameEndPos); Misc::StringUtils::lowerCaseInPlace(mTag); if (mTag.empty()) return; mClosingTag = (mTag[0] == '/'); if (mClosingTag) { mTag.erase(mTag.begin()); return; } if (tagNameEndPos == std::string::npos) return; tag.erase(0, tagNameEndPos+1); while (!tag.empty()) { size_t sepPos = tag.find('='); if (sepPos == std::string::npos) return; std::string key = tag.substr(0, sepPos); Misc::StringUtils::lowerCaseInPlace(key); tag.erase(0, sepPos+1); std::string value; if (tag.empty()) return; if (tag[0] == '"') { size_t quoteEndPos = tag.find('"', 1); if (quoteEndPos == std::string::npos) throw std::runtime_error("BookTextParser Error: Missing end quote in tag"); value = tag.substr(1, quoteEndPos-1); tag.erase(0, quoteEndPos+2); } else { size_t valEndPos = tag.find(' '); if (valEndPos == std::string::npos) { value = tag; tag.erase(); } else { value = tag.substr(0, valEndPos); tag.erase(0, valEndPos+1); } } mAttributes[key] = value; } } /* BookFormatter */ Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight) { Paginator pag(pageWidth, pageHeight); while (parent->getChildCount()) { MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); } mTextStyle = TextStyle(); mBlockStyle = BlockStyle(); MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); paper->setNeedMouseFocus(false); BookTextParser parser(markup); bool brBeforeLastTag = false; bool isPrevImg = false; for (;;) { BookTextParser::Events event = parser.next(); if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) continue; std::string plainText = parser.getReadyText(); // for cases when linebreaks are used to cause a shift to the next page // if the split text block ends in an empty line, proceeding text block(s) should have leading empty lines removed if (pag.getIgnoreLeadingEmptyLines()) { while (!plainText.empty()) { if (plainText[0] == '\n') plainText.erase(plainText.begin()); else { pag.setIgnoreLeadingEmptyLines(false); break; } } } if (plainText.empty()) brBeforeLastTag = true; else { // Each block of text (between two tags / boundary and tag) will be displayed in a separate editbox widget, // which means an additional linebreak will be created between them. // ^ This is not what vanilla MW assumes, so we must deal with line breaks around tags appropriately. bool brAtStart = (plainText[0] == '\n'); bool brAtEnd = (plainText[plainText.size()-1] == '\n'); if (brAtStart && !brBeforeLastTag && !isPrevImg) plainText.erase(plainText.begin()); if (plainText.size() && brAtEnd) plainText.erase(plainText.end()-1); if (!plainText.empty() || brBeforeLastTag || isPrevImg) { TextElement elem(paper, pag, mBlockStyle, mTextStyle, plainText); elem.paginate(); } brBeforeLastTag = brAtEnd; } if (event == BookTextParser::Event_EOF) break; isPrevImg = (event == BookTextParser::Event_ImgTag); switch (event) { case BookTextParser::Event_ImgTag: { const BookTextParser::Attributes & attr = parser.getAttributes(); if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) continue; std::string src = attr.at("src"); int width = MyGUI::utility::parseInt(attr.at("width")); int height = MyGUI::utility::parseInt(attr.at("height")); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::string correctedSrc = Misc::ResourceHelpers::correctBookartPath(src, width, height, vfs); bool exists = vfs->exists(correctedSrc); if (!exists) { Log(Debug::Warning) << "Warning: Could not find \"" << src << "\" referenced by an tag."; break; } pag.setIgnoreLeadingEmptyLines(false); ImageElement elem(paper, pag, mBlockStyle, correctedSrc, width, height); elem.paginate(); break; } case BookTextParser::Event_FontTag: if (parser.isClosingTag()) resetFontProperties(); else handleFont(parser.getAttributes()); break; case BookTextParser::Event_DivTag: handleDiv(parser.getAttributes()); break; default: break; } } // insert last page if (pag.getStartTop() != pag.getCurrentTop()) pag << Paginator::Page(pag.getStartTop(), pag.getStartTop() + pag.getPageHeight()); paper->setSize(paper->getWidth(), pag.getCurrentTop()); return pag.getPages(); } Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup) { return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight()); } void BookFormatter::resetFontProperties() { mTextStyle = TextStyle(); } void BookFormatter::handleDiv(const BookTextParser::Attributes & attr) { if (attr.find("align") == attr.end()) return; std::string align = attr.at("align"); if (Misc::StringUtils::ciEqual(align, "center")) mBlockStyle.mAlign = MyGUI::Align::HCenter; else if (Misc::StringUtils::ciEqual(align, "left")) mBlockStyle.mAlign = MyGUI::Align::Left; else if (Misc::StringUtils::ciEqual(align, "right")) mBlockStyle.mAlign = MyGUI::Align::Right; } void BookFormatter::handleFont(const BookTextParser::Attributes & attr) { if (attr.find("color") != attr.end()) { unsigned int color; std::stringstream ss; ss << attr.at("color"); ss >> std::hex >> color; mTextStyle.mColour = MyGUI::Colour( (color>>16 & 0xFF) / 255.f, (color>>8 & 0xFF) / 255.f, (color & 0xFF) / 255.f); } if (attr.find("face") != attr.end()) { std::string face = attr.at("face"); std::string name = Gui::FontLoader::getFontForFace(face); mTextStyle.mFont = "Journalbook "+name; } if (attr.find("size") != attr.end()) { /// \todo } } /* GraphicElement */ GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle) : mParent(parent), mPaginator(pag), mBlockStyle(blockStyle) { } void GraphicElement::paginate() { int newTop = mPaginator.getCurrentTop() + getHeight(); while (newTop-mPaginator.getStartTop() > mPaginator.getPageHeight()) { int newStartTop = pageSplit(); mPaginator << Paginator::Page(mPaginator.getStartTop(), newStartTop); mPaginator.setStartTop(newStartTop); } mPaginator.setCurrentTop(newTop); } int GraphicElement::pageSplit() { return mPaginator.getStartTop() + mPaginator.getPageHeight(); } /* TextElement */ TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, const TextStyle & textStyle, const std::string & text) : GraphicElement(parent, pag, blockStyle), mTextStyle(textStyle) { Gui::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); box->setEditStatic(true); box->setEditMultiLine(true); box->setEditWordWrap(true); box->setNeedMouseFocus(false); box->setNeedKeyFocus(false); box->setMaxTextLength(text.size()); box->setTextAlign(mBlockStyle.mAlign); box->setTextColour(mTextStyle.mColour); box->setFontName(mTextStyle.mFont); box->setCaption(MyGUI::TextIterator::toTagsString(text)); box->setSize(box->getSize().width, box->getTextSize().height); mEditBox = box; } int TextElement::getHeight() { return mEditBox->getTextSize().height; } int TextElement::pageSplit() { // split lines const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()); if (lineHeight > 0) lastLine /= lineHeight; int ret = mPaginator.getCurrentTop() + lastLine * lineHeight; // first empty lines that would go to the next page should be ignored mPaginator.setIgnoreLeadingEmptyLines(true); const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); for (unsigned int i = lastLine; i < lines.size(); ++i) { if (lines[i].width == 0) ret += lineHeight; else { mPaginator.setIgnoreLeadingEmptyLines(false); break; } } return ret; } /* ImageElement */ ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, const std::string & src, int width, int height) : GraphicElement(parent, pag, blockStyle), mImageHeight(height) { int left = 0; if (mBlockStyle.mAlign.isHCenter()) left += (pag.getPageWidth() - width) / 2; else if (mBlockStyle.mAlign.isLeft()) left = 0; else if (mBlockStyle.mAlign.isRight()) left += pag.getPageWidth() - width; mImageBox = parent->createWidget ("ImageBox", MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); mImageBox->setImageTexture(src); mImageBox->setProperty("NeedMouse", "false"); } int ImageElement::getHeight() { return mImageHeight; } int ImageElement::pageSplit() { // if the image is larger than the page, fall back to the default pageSplit implementation if (mImageHeight > mPaginator.getPageHeight()) return GraphicElement::pageSplit(); return mPaginator.getCurrentTop(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/formatting.hpp000066400000000000000000000127201445372753700227210ustar00rootroot00000000000000#ifndef MWGUI_FORMATTING_H #define MWGUI_FORMATTING_H #include #include #include namespace MWGui { namespace Formatting { struct TextStyle { TextStyle() : mColour(0,0,0) , mFont("Journalbook DefaultFont") , mTextSize(16) { } MyGUI::Colour mColour; std::string mFont; int mTextSize; }; struct BlockStyle { BlockStyle() : mAlign(MyGUI::Align::Left | MyGUI::Align::Top) { } MyGUI::Align mAlign; }; class BookTextParser { public: typedef std::map Attributes; enum Events { Event_None = -2, Event_EOF = -1, Event_BrTag, Event_PTag, Event_ImgTag, Event_DivTag, Event_FontTag }; BookTextParser(const std::string & text); Events next(); const Attributes & getAttributes() const; std::string getReadyText() const; bool isClosingTag() const; private: void registerTag(const std::string & tag, Events type); void flushBuffer(); void parseTag(std::string tag); size_t mIndex; std::string mText; std::string mReadyText; bool mIgnoreNewlineTags; bool mIgnoreLineEndings; Attributes mAttributes; std::string mTag; bool mClosingTag; std::map mTagTypes; std::string mBuffer; size_t mPlainTextEnd; }; class Paginator { public: typedef std::pair Page; typedef std::vector Pages; Paginator(int pageWidth, int pageHeight) : mStartTop(0), mCurrentTop(0), mPageWidth(pageWidth), mPageHeight(pageHeight), mIgnoreLeadingEmptyLines(false) { } int getStartTop() const { return mStartTop; } int getCurrentTop() const { return mCurrentTop; } int getPageWidth() const { return mPageWidth; } int getPageHeight() const { return mPageHeight; } bool getIgnoreLeadingEmptyLines() const { return mIgnoreLeadingEmptyLines; } Pages getPages() const { return mPages; } void setStartTop(int top) { mStartTop = top; } void setCurrentTop(int top) { mCurrentTop = top; } void setIgnoreLeadingEmptyLines(bool ignore) { mIgnoreLeadingEmptyLines = ignore; } Paginator & operator<<(const Page & page) { mPages.push_back(page); return *this; } private: int mStartTop, mCurrentTop; int mPageWidth, mPageHeight; bool mIgnoreLeadingEmptyLines; Pages mPages; }; /// \brief utilities for parsing book/scroll text as mygui widgets class BookFormatter { public: Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight); Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup); private: void resetFontProperties(); void handleDiv(const BookTextParser::Attributes & attr); void handleFont(const BookTextParser::Attributes & attr); TextStyle mTextStyle; BlockStyle mBlockStyle; }; class GraphicElement { public: GraphicElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle); virtual int getHeight() = 0; virtual void paginate(); virtual int pageSplit(); protected: virtual ~GraphicElement() {} MyGUI::Widget * mParent; Paginator & mPaginator; BlockStyle mBlockStyle; }; class TextElement : public GraphicElement { public: TextElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, const TextStyle & textStyle, const std::string & text); int getHeight() override; int pageSplit() override; private: int currentFontHeight() const; TextStyle mTextStyle; Gui::EditBox * mEditBox; }; class ImageElement : public GraphicElement { public: ImageElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, const std::string & src, int width, int height); int getHeight() override; int pageSplit() override; private: int mImageHeight; MyGUI::ImageBox * mImageBox; }; } } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/hud.cpp000066400000000000000000000543751445372753700213360ustar00rootroot00000000000000#include "hud.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "inventorywindow.hpp" #include "spellicons.hpp" #include "itemmodel.hpp" #include "draganddrop.hpp" #include "itemwidget.hpp" namespace MWGui { /** * Makes it possible to use ItemModel::moveItem to move an item from an inventory to the world. */ class WorldItemModel : public ItemModel { public: WorldItemModel(float left, float top) : mLeft(left), mTop(top) {} virtual ~WorldItemModel() override {} MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr dropped; if (world->canPlaceObject(mLeft, mTop)) dropped = world->placeObject(item.mBase, mLeft, mTop, count); else dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count); dropped.getCellRef().setOwner(""); return dropped; } void removeItem (const ItemStack& item, size_t count) override { throw std::runtime_error("removeItem not implemented"); } ModelIndex getIndex (const ItemStack &item) override { throw std::runtime_error("getIndex not implemented"); } void update() override {} size_t getItemCount() override { return 0; } ItemStack getItem (ModelIndex index) override { throw std::runtime_error("getItem not implemented"); } bool usesContainer(const MWWorld::Ptr&) override { return false; } private: // Where to drop the item float mLeft; float mTop; }; HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : WindowBase("openmw_hud.layout") , LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map")) , mHealth(nullptr) , mMagicka(nullptr) , mStamina(nullptr) , mDrowning(nullptr) , mWeapImage(nullptr) , mSpellImage(nullptr) , mWeapStatus(nullptr) , mSpellStatus(nullptr) , mEffectBox(nullptr) , mMinimap(nullptr) , mCrosshair(nullptr) , mCellNameBox(nullptr) , mDrowningBar(nullptr) , mDrowningFlash(nullptr) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) , mSpellBoxBaseLeft(0) , mMinimapBoxBaseRight(0) , mEffectBoxBaseRight(0) , mDragAndDrop(dragAndDrop) , mCellNameTimer(0.0f) , mWeaponSpellTimer(0.f) , mMapVisible(true) , mWeaponVisible(true) , mSpellVisible(true) , mWorldMouseOver(false) , mEnemyActorId(-1) , mEnemyHealthTimer(-1) , mIsDrowning(false) , mDrowningFlashTheta(0.f) { // Energy bars getWidget(mHealthFrame, "HealthFrame"); getWidget(mHealth, "Health"); getWidget(mMagicka, "Magicka"); getWidget(mStamina, "Stamina"); getWidget(mEnemyHealth, "EnemyHealth"); mHealthManaStaminaBaseLeft = mHealthFrame->getLeft(); MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame; getWidget(healthFrame, "HealthFrame"); getWidget(magickaFrame, "MagickaFrame"); getWidget(fatigueFrame, "FatigueFrame"); healthFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); magickaFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); //Drowning bar getWidget(mDrowningBar, "DrowningBar"); getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowning, "Drowning"); getWidget(mDrowningFlash, "Flash"); mDrowning->setProgressRange(200); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); // Item and spell images and status bars getWidget(mWeapBox, "WeapBox"); getWidget(mWeapImage, "WeapImage"); getWidget(mWeapStatus, "WeapStatus"); mWeapBoxBaseLeft = mWeapBox->getLeft(); mWeapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWeaponClicked); getWidget(mSpellBox, "SpellBox"); getWidget(mSpellImage, "SpellImage"); getWidget(mSpellStatus, "SpellStatus"); mSpellBoxBaseLeft = mSpellBox->getLeft(); mSpellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); getWidget(mSneakBox, "SneakBox"); mSneakBoxBaseLeft = mSneakBox->getLeft(); getWidget(mEffectBox, "EffectBox"); mEffectBoxBaseRight = viewSize.width - mEffectBox->getRight(); getWidget(mMinimapBox, "MiniMapBox"); mMinimapBoxBaseRight = viewSize.width - mMinimapBox->getRight(); getWidget(mMinimap, "MiniMap"); getWidget(mCompass, "Compass"); getWidget(mMinimapButton, "MiniMapButton"); mMinimapButton->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); getWidget(mCellNameBox, "CellName"); getWidget(mWeaponSpellBox, "WeaponSpellName"); getWidget(mCrosshair, "Crosshair"); LocalMapBase::init(mMinimap, mCompass); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); mMainWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &HUD::onWorldMouseLostFocus); mSpellIcons = new SpellIcons(); } HUD::~HUD() { mMainWidget->eventMouseLostFocus.clear(); mMainWidget->eventMouseMove.clear(); mMainWidget->eventMouseButtonClick.clear(); delete mSpellIcons; } void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); // Fatigue can be negative if (id != "FBar") current = std::max(0, current); MyGUI::Widget* w; std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); if (id == "HBar") { mHealth->setProgressRange(std::max(0, modified)); mHealth->setProgressPosition(std::max(0, current)); getWidget(w, "HealthFrame"); w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } else if (id == "MBar") { mMagicka->setProgressRange(std::max(0, modified)); mMagicka->setProgressPosition(std::max(0, current)); getWidget(w, "MagickaFrame"); w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { mStamina->setProgressRange(std::max(0, modified)); mStamina->setProgressPosition(std::max(0, current)); getWidget(w, "FatigueFrame"); w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } } void HUD::setDrowningTimeLeft(float time, float maxTime) { size_t progress = static_cast(time / maxTime * 200); mDrowning->setProgressPosition(progress); bool isDrowning = (progress == 0); if (isDrowning && !mIsDrowning) // Just started drowning mDrowningFlashTheta = 0.0f; // Start out on bright red every time. mDrowningFlash->setVisible(isDrowning); mIsDrowning = isDrowning; } void HUD::setDrowningBarVisible(bool visible) { mDrowningBar->setVisible(visible); } void HUD::onWorldClicked(MyGUI::Widget* _sender) { if (!MWBase::Environment::get().getWindowManager ()->isGuiMode ()) return; MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld MWBase::Environment::get().getWorld()->breakInvisibility( MWMechanics::getPlayer()); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); WorldItemModel drop (mouseX, mouseY); mDragAndDrop->drop(&drop, nullptr); winMgr->changePointer("arrow"); } else { GuiMode mode = winMgr->getMode(); if (!winMgr->isConsoleMode() && (mode != GM_Container) && (mode != GM_Inventory)) return; MWWorld::Ptr object = MWBase::Environment::get().getWorld()->getFacedObject(); if (winMgr->isConsoleMode()) winMgr->setConsoleSelectedObject(object); else //if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object if (!object.isEmpty()) winMgr->getInventoryWindow()->pickUpObject(object); } } } void HUD::onWorldMouseOver(MyGUI::Widget* _sender, int x, int y) { if (mDragAndDrop->mIsOnDragAndDrop) { mWorldMouseOver = false; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); MWBase::World* world = MWBase::Environment::get().getWorld(); // if we can't drop the object at the wanted position, show the "drop on ground" cursor. bool canDrop = world->canPlaceObject(mouseX, mouseY); if (!canDrop) MWBase::Environment::get().getWindowManager()->changePointer("drop_ground"); else MWBase::Environment::get().getWindowManager()->changePointer("arrow"); } else { MWBase::Environment::get().getWindowManager()->changePointer("arrow"); mWorldMouseOver = true; } } void HUD::onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new) { MWBase::Environment::get().getWindowManager()->changePointer("arrow"); mWorldMouseOver = false; } void HUD::onHMSClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); } void HUD::onMapClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); } void HUD::onWeaponClicked(MyGUI::Widget* _sender) { const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return; } MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); } void HUD::onMagicClicked(MyGUI::Widget* _sender) { const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return; } MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); } void HUD::setCellName(const std::string& cellName) { if (mCellName != cellName) { mCellNameTimer = 5.0f; mCellName = cellName; mCellNameBox->setCaptionWithReplacing("#{sCell=" + mCellName + "}"); mCellNameBox->setVisible(mMapVisible); } } void HUD::onFrame(float dt) { LocalMapBase::onFrame(dt); mCellNameTimer -= dt; mWeaponSpellTimer -= dt; if (mCellNameTimer < 0) mCellNameBox->setVisible(false); if (mWeaponSpellTimer < 0) mWeaponSpellBox->setVisible(false); mEnemyHealthTimer -= dt; if (mEnemyHealth->getVisible() && mEnemyHealthTimer < 0) { mEnemyHealth->setVisible(false); mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); } mSpellIcons->updateWidgets(mEffectBox, true); if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) { updateEnemyHealthBar(); } if (mDrowningBar->getVisible()) mDrowningBar->setPosition(mMainWidget->getWidth()/2 - mDrowningFrame->getWidth()/2, mMainWidget->getTop()); if (mIsDrowning) { mDrowningFlashTheta += dt * osg::PI*2; float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; mDrowningFlash->setAlpha(intensity); } } void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); std::string spellName = spell->mName; if (spellName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; mSpellName = spellName; mWeaponSpellBox->setCaption(mSpellName); mWeaponSpellBox->setVisible(true); } mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(successChancePercent); mSpellBox->setUserString("ToolTipType", "Spell"); mSpellBox->setUserString("Spell", spellId); // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(spell->mEffects.mList.front().mEffectID); std::string icon = effect->mIcon; int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); mSpellImage->setSpellIcon(icon); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) { std::string itemName = item.getClass().getName(item); if (itemName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; mSpellName = itemName; mWeaponSpellBox->setCaption(mSpellName); mWeaponSpellBox->setVisible(true); } mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(chargePercent); mSpellBox->setUserString("ToolTipType", "ItemPtr"); mSpellBox->setUserData(MWWorld::Ptr(item)); mSpellImage->setItem(item); } void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) { std::string itemName = item.getClass().getName(item); if (itemName != mWeaponName && mWeaponVisible) { mWeaponSpellTimer = 5.0f; mWeaponName = itemName; mWeaponSpellBox->setCaption(mWeaponName); mWeaponSpellBox->setVisible(true); } mWeapBox->clearUserStrings(); mWeapBox->setUserString("ToolTipType", "ItemPtr"); mWeapBox->setUserData(MWWorld::Ptr(item)); mWeapStatus->setProgressRange(100); mWeapStatus->setProgressPosition(durabilityPercent); mWeapImage->setItem(item); } void HUD::unsetSelectedSpell() { std::string spellName = "#{sNone}"; if (spellName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; mSpellName = spellName; mWeaponSpellBox->setCaptionWithReplacing(mSpellName); mWeaponSpellBox->setVisible(true); } mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(0); mSpellImage->setItem(MWWorld::Ptr()); mSpellBox->clearUserStrings(); } void HUD::unsetSelectedWeapon() { std::string itemName = "#{sSkillHandtohand}"; if (itemName != mWeaponName && mWeaponVisible) { mWeaponSpellTimer = 5.0f; mWeaponName = itemName; mWeaponSpellBox->setCaptionWithReplacing(mWeaponName); mWeaponSpellBox->setVisible(true); } mWeapStatus->setProgressRange(100); mWeapStatus->setProgressPosition(0); MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); mWeapImage->setItem(MWWorld::Ptr()); std::string icon = (player.getClass().getNpcStats(player).isWerewolf()) ? "icons\\k\\tx_werewolf_hand.dds" : "icons\\k\\stealth_handtohand.dds"; mWeapImage->setIcon(icon); mWeapBox->clearUserStrings(); mWeapBox->setUserString("ToolTipType", "Layout"); mWeapBox->setUserString("ToolTipLayout", "HandToHandToolTip"); mWeapBox->setUserString("Caption_HandToHandText", itemName); mWeapBox->setUserString("ImageTexture_HandToHandImage", icon); } void HUD::setCrosshairVisible(bool visible) { mCrosshair->setVisible (visible); } void HUD::setCrosshairOwned(bool owned) { if(owned) { mCrosshair->changeWidgetSkin("HUD_Crosshair_Owned"); } else { mCrosshair->changeWidgetSkin("HUD_Crosshair"); } } void HUD::setHmsVisible(bool visible) { mHealth->setVisible(visible); mMagicka->setVisible(visible); mStamina->setVisible(visible); updatePositions(); } void HUD::setWeapVisible(bool visible) { mWeapBox->setVisible(visible); updatePositions(); } void HUD::setSpellVisible(bool visible) { mSpellBox->setVisible(visible); updatePositions(); } void HUD::setSneakVisible(bool visible) { mSneakBox->setVisible(visible); updatePositions(); } void HUD::setEffectVisible(bool visible) { mEffectBox->setVisible (visible); updatePositions(); } void HUD::setMinimapVisible(bool visible) { mMinimapBox->setVisible (visible); updatePositions(); } void HUD::updatePositions() { int weapDx = 0, spellDx = 0, sneakDx = 0; if (!mHealth->getVisible()) sneakDx = spellDx = weapDx = mWeapBoxBaseLeft - mHealthManaStaminaBaseLeft; if (!mWeapBox->getVisible()) { spellDx += mSpellBoxBaseLeft - mWeapBoxBaseLeft; sneakDx = spellDx; } if (!mSpellBox->getVisible()) sneakDx += mSneakBoxBaseLeft - mSpellBoxBaseLeft; mWeaponVisible = mWeapBox->getVisible(); mSpellVisible = mSpellBox->getVisible(); if (!mWeaponVisible && !mSpellVisible) mWeaponSpellBox->setVisible(false); mWeapBox->setPosition(mWeapBoxBaseLeft - weapDx, mWeapBox->getTop()); mSpellBox->setPosition(mSpellBoxBaseLeft - spellDx, mSpellBox->getTop()); mSneakBox->setPosition(mSneakBoxBaseLeft - sneakDx, mSneakBox->getTop()); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!mMinimapBox->getVisible ()) effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight; mMapVisible = mMinimapBox->getVisible (); if (!mMapVisible) mCellNameBox->setVisible(false); mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } void HUD::updateEnemyHealthBar() { MWWorld::Ptr enemy = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mEnemyActorId); if (enemy.isEmpty()) return; MWMechanics::CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getRatio() * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) mEnemyHealth->setAlpha(std::clamp(mEnemyHealthTimer / fNPCHealthBarFade, 0.f, 1.f)); } void HUD::setEnemy(const MWWorld::Ptr &enemy) { mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); mEnemyHealthTimer = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarTime")->mValue.getFloat(); if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); updateEnemyHealthBar(); } void HUD::resetEnemy() { mEnemyActorId = -1; mEnemyHealthTimer = -1; } void HUD::clear() { unsetSelectedSpell(); unsetSelectedWeapon(); resetEnemy(); } void HUD::customMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); } void HUD::doorMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); } } openmw-openmw-0.48.0/apps/openmw/mwgui/hud.hpp000066400000000000000000000075561445372753700213420ustar00rootroot00000000000000#ifndef OPENMW_GAME_MWGUI_HUD_H #define OPENMW_GAME_MWGUI_HUD_H #include "mapwindow.hpp" #include "statswatcher.hpp" namespace MWWorld { class Ptr; } namespace MWGui { class DragAndDrop; class SpellIcons; class ItemWidget; class SpellWidget; class HUD : public WindowBase, public LocalMapBase, public StatsListener { public: HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; /// Set time left for the player to start drowning /// @param time time left to start drowning /// @param maxTime how long we can be underwater (in total) until drowning starts void setDrowningTimeLeft(float time, float maxTime); void setDrowningBarVisible(bool visible); void setHmsVisible(bool visible); void setWeapVisible(bool visible); void setSpellVisible(bool visible); void setSneakVisible(bool visible); void setEffectVisible(bool visible); void setMinimapVisible(bool visible); void setSelectedSpell(const std::string& spellId, int successChancePercent); void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); const MWWorld::Ptr& getSelectedEnchantItem(); void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); void unsetSelectedSpell(); void unsetSelectedWeapon(); void setCrosshairVisible(bool visible); void setCrosshairOwned(bool owned); void onFrame(float dt) override; void setCellName(const std::string& cellName); bool getWorldMouseOver() { return mWorldMouseOver; } MyGUI::Widget* getEffectBox() { return mEffectBox; } void setEnemy(const MWWorld::Ptr& enemy); void resetEnemy(); void clear() override; private: MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; MyGUI::Widget* mHealthFrame; MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox; ItemWidget *mWeapImage; SpellWidget *mSpellImage; MyGUI::ProgressBar *mWeapStatus, *mSpellStatus; MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; MyGUI::ScrollView* mMinimap; MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash; // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; // bottom right elements int mMinimapBoxBaseRight, mEffectBoxBaseRight; DragAndDrop* mDragAndDrop; std::string mCellName; float mCellNameTimer; std::string mWeaponName; std::string mSpellName; float mWeaponSpellTimer; bool mMapVisible; bool mWeaponVisible; bool mSpellVisible; bool mWorldMouseOver; SpellIcons* mSpellIcons; int mEnemyActorId; float mEnemyHealthTimer; bool mIsDrowning; float mDrowningFlashTheta; void onWorldClicked(MyGUI::Widget* _sender); void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new); void onHMSClicked(MyGUI::Widget* _sender); void onWeaponClicked(MyGUI::Widget* _sender); void onMagicClicked(MyGUI::Widget* _sender); void onMapClicked(MyGUI::Widget* _sender); // LocalMapBase void customMarkerCreated(MyGUI::Widget* marker) override; void doorMarkerCreated(MyGUI::Widget* marker) override; void updateEnemyHealthBar(); void updatePositions(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/inventoryitemmodel.cpp000066400000000000000000000100631445372753700244750ustar00rootroot00000000000000#include "inventoryitemmodel.hpp" #include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" namespace MWGui { InventoryItemModel::InventoryItemModel(const MWWorld::Ptr &actor) : mActor(actor) { } ItemStack InventoryItemModel::getItem (ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); if (mItems.size() <= static_cast(index)) throw std::runtime_error("Item index out of range"); return mItems[index]; } size_t InventoryItemModel::getItemCount() { return mItems.size(); } ItemModel::ModelIndex InventoryItemModel::getIndex (const ItemStack& item) { size_t i = 0; for (ItemStack& itemStack : mItems) { if (itemStack == item) return i; ++i; } return -1; } MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, allowAutoEquip); } void InventoryItemModel::removeItem (const ItemStack& item, size_t count) { int removed = 0; // Re-equipping makes sense only if a target has inventory if (mActor.getClass().hasInventoryStore(mActor)) { MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); removed = store.remove(item.mBase, count, mActor, true); } else { MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); removed = store.remove(item.mBase, count, mActor); } std::stringstream error; if (removed == 0) { error << "Item '" << item.mBase.getCellRef().getRefId() << "' was not found in container store to remove"; throw std::runtime_error(error.str()); } else if (removed < static_cast(count)) { error << "Not enough items '" << item.mBase.getCellRef().getRefId() << "' in the stack to remove (" << static_cast(count) << " requested, " << removed << " found)"; throw std::runtime_error(error.str()); } } MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) { // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items via the 'Take All' button. if (item.mFlags & ItemStack::Flag_Bound) return MWWorld::Ptr(); MWWorld::Ptr ret = otherModel->copyItem(item, count); removeItem(item, count); return ret; } void InventoryItemModel::update() { MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); mItems.clear(); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { MWWorld::Ptr item = *it; if (!item.getClass().showsInInventory(item)) continue; ItemStack newItem (item, this, item.getRefData().getCount()); if (mActor.getClass().hasInventoryStore(mActor)) { MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); if (invStore.isEquipped(newItem.mBase)) newItem.mType = ItemStack::Type_Equipped; } mItems.push_back(newItem); } } bool InventoryItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { // Looting a dead corpse is considered OK if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead()) return true; MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count); return true; } bool InventoryItemModel::usesContainer(const MWWorld::Ptr& container) { return mActor == container; } } openmw-openmw-0.48.0/apps/openmw/mwgui/inventoryitemmodel.hpp000066400000000000000000000020031445372753700244750ustar00rootroot00000000000000#ifndef MWGUI_INVENTORY_ITEM_MODEL_H #define MWGUI_INVENTORY_ITEM_MODEL_H #include "itemmodel.hpp" namespace MWGui { class InventoryItemModel : public ItemModel { public: InventoryItemModel (const MWWorld::Ptr& actor); ItemStack getItem (ModelIndex index) override; ModelIndex getIndex (const ItemStack &item) override; size_t getItemCount() override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem (const ItemStack& item, size_t count) override; /// Move items from this model to \a otherModel. MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel) override; void update() override; bool usesContainer(const MWWorld::Ptr& container) override; protected: MWWorld::Ptr mActor; private: std::vector mItems; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/inventorywindow.cpp000066400000000000000000000762601445372753700240400ustar00rootroot00000000000000#include "inventorywindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/actionequip.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" #include "draganddrop.hpp" #include "tooltips.hpp" namespace { bool isRightHandWeapon(const MWWorld::Ptr& item) { if (item.getClass().getType() != ESM::Weapon::sRecordId) return false; std::vector equipmentSlots = item.getClass().getEquipmentSlots(item).first; return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight); } } namespace MWGui { InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem) : WindowPinnableBase("openmw_inventory_window.layout") , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) , mSortModel(nullptr) , mTradeModel(nullptr) , mGuiMode(GM_Inventory) , mLastXSize(0) , mLastYSize(0) , mPreview(std::make_unique(parent, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) , mUpdateTimer(0.f) { mPreviewTexture = std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet()); mPreview->rebuild(); mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); getWidget(mAvatar, "Avatar"); getWidget(mAvatarImage, "AvatarImage"); getWidget(mEncumbranceBar, "EncumbranceBar"); getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); getWidget(mFilterApparel, "ApparelButton"); getWidget(mFilterMagic, "MagicButton"); getWidget(mFilterMisc, "MiscButton"); getWidget(mLeftPane, "LeftPane"); getWidget(mRightPane, "RightPane"); getWidget(mArmorRating, "ArmorRating"); getWidget(mFilterEdit, "FilterEdit"); mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); mAvatarImage->setRenderItemTexture(mPreviewTexture.get()); mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected); mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &InventoryWindow::onNameFilterChanged); mFilterAll->setStateSelected(true); setGuiMode(mGuiMode); adjustPanes(); } void InventoryWindow::adjustPanes() { const float aspect = 0.5; // fixed aspect ratio for the avatar image int leftPaneWidth = static_cast((mMainWidget->getSize().height - 44 - mArmorRating->getHeight()) * aspect); mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 ); mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4, mRightPane->getPosition().top, mMainWidget->getSize().width - 12 - leftPaneWidth - 15, mMainWidget->getSize().height-44 ); } void InventoryWindow::updatePlayer() { mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings mSortModel->setSourceModel(mTradeModel); else mSortModel = new SortFilterItemModel(mTradeModel); mSortModel->setNameFilter(mFilterEdit->getCaption()); mItemView->setModel(mSortModel); mFilterAll->setStateSelected(true); mFilterWeapon->setStateSelected(false); mFilterApparel->setStateSelected(false); mFilterMagic->setStateSelected(false); mFilterMisc->setStateSelected(false); mPreview->updatePtr(mPtr); mPreview->rebuild(); mPreview->update(); dirtyPreview(); updatePreviewSize(); updateEncumbranceBar(); mItemView->update(); notifyContentChanged(); } void InventoryWindow::clear() { mPtr = MWWorld::Ptr(); mTradeModel = nullptr; mSortModel = nullptr; mItemView->setModel(nullptr); } void InventoryWindow::toggleMaximized() { std::string setting = getModeSetting(); bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); if (maximized) setting += " maximized"; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); MyGUI::Window* window = mMainWidget->castType(); window->setCoord(x, y, w, h); if (maximized) Settings::Manager::setBool(setting, "Windows", maximized); else Settings::Manager::setBool(setting + " maximized", "Windows", maximized); adjustPanes(); updatePreviewSize(); } void InventoryWindow::setGuiMode(GuiMode mode) { mGuiMode = mode; std::string setting = getModeSetting(); setPinButtonVisible(mode == GM_Inventory); if (Settings::Manager::getBool(setting + " maximized", "Windows")) setting += " maximized"; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height)); MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height)); bool needUpdate = (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()); mMainWidget->setPosition(pos); mMainWidget->setSize(size); adjustPanes(); if (needUpdate) updatePreviewSize(); } SortFilterItemModel* InventoryWindow::getSortFilterModel() { return mSortModel; } TradeItemModel* InventoryWindow::getTradeModel() { return mTradeModel; } ItemModel* InventoryWindow::getModel() { return mTradeModel; } void InventoryWindow::onBackgroundSelected() { if (mDragAndDrop->mIsOnDragAndDrop) mDragAndDrop->drop(mTradeModel, mItemView); } void InventoryWindow::onItemSelected (int index) { onItemSelectedFromSourceModel (mSortModel->mapToSource(index)); } void InventoryWindow::onItemSelectedFromSourceModel (int index) { if (mDragAndDrop->mIsOnDragAndDrop) { mDragAndDrop->drop(mTradeModel, mItemView); return; } const ItemStack& item = mTradeModel->getItem(index); std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; if (mTrading) { // Can't give conjured items to a merchant if (item.mFlags & ItemStack::Flag_Bound) { MWBase::Environment::get().getWindowManager()->playSound(sound); MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog9}"); return; } // check if merchant accepts item int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices(); if (!object.getClass().canSell(object, services)) { MWBase::Environment::get().getWindowManager()->playSound(sound); MWBase::Environment::get().getWindowManager()-> messageBox("#{sBarterDialog4}"); return; } } // If we unequip weapon during attack, it can lead to unexpected behaviour if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) { bool isWeapon = item.mBase.getType() == ESM::Weapon::sRecordId; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); if (isWeapon && invStore.isEquipped(item.mBase)) { MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}"); return; } } if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, message, count); dialog->eventOkClicked.clear(); if (mTrading) dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem); else dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem); mSelectedItem = index; } else { mSelectedItem = index; if (mTrading) sellItem (nullptr, count); else dragItem (nullptr, count); } } void InventoryWindow::ensureSelectedItemUnequipped(int count) { const ItemStack& item = mTradeModel->getItem(mSelectedItem); if (item.mType == ItemStack::Type_Equipped) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, mPtr, count); // The unequipped item was re-stacked. We have to update the index // since the item pointed does not exist anymore. if (item.mBase != newStack) { updateItemView(); // Unequipping can produce a new stack, not yet in the window... // newIndex will store the index of the ItemStack the item was stacked on int newIndex = -1; for (size_t i=0; i < mTradeModel->getItemCount(); ++i) { if (mTradeModel->getItem(i).mBase == newStack) { newIndex = i; break; } } if (newIndex == -1) throw std::runtime_error("Can't find restacked item"); mSelectedItem = newIndex; } } } void InventoryWindow::dragItem(MyGUI::Widget* sender, int count) { ensureSelectedItemUnequipped(count); mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); notifyContentChanged(); } void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) { ensureSelectedItemUnequipped(count); const ItemStack& item = mTradeModel->getItem(mSelectedItem); std::string sound = item.mBase.getClass().getUpSoundId(item.mBase); MWBase::Environment::get().getWindowManager()->playSound(sound); if (item.mType == ItemStack::Type_Barter) { // this was an item borrowed to us by the merchant mTradeModel->returnItemBorrowedToUs(mSelectedItem, count); MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count); } else { // borrow item to the merchant mTradeModel->borrowItemFromUs(mSelectedItem, count); MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count); } mItemView->update(); notifyContentChanged(); } void InventoryWindow::updateItemView() { MWBase::Environment::get().getWindowManager()->updateSpellWindow(); mItemView->update(); dirtyPreview(); } void InventoryWindow::onOpen() { // Reset the filter focus when opening the window MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (focus == mFilterEdit) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); if (!mPtr.isEmpty()) { updateEncumbranceBar(); mItemView->update(); notifyContentChanged(); } adjustPanes(); } std::string InventoryWindow::getModeSetting() const { std::string setting = "inventory"; switch(mGuiMode) { case GM_Container: setting += " container"; break; case GM_Companion: setting += " companion"; break; case GM_Barter: setting += " barter"; break; default: break; } return setting; } void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { adjustPanes(); std::string setting = getModeSetting(); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float x = _sender->getPosition().left / float(viewSize.width); float y = _sender->getPosition().top / float(viewSize.height); float w = _sender->getSize().width / float(viewSize.width); float h = _sender->getSize().height / float(viewSize.height); Settings::Manager::setFloat(setting + " x", "Windows", x); Settings::Manager::setFloat(setting + " y", "Windows", y); Settings::Manager::setFloat(setting + " w", "Windows", w); Settings::Manager::setFloat(setting + " h", "Windows", h); bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); if (maximized) Settings::Manager::setBool(setting + " maximized", "Windows", false); if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; updatePreviewSize(); updateArmorRating(); } } void InventoryWindow::updateArmorRating() { mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) mArmorRating->setCaptionWithReplacing (MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); } void InventoryWindow::updatePreviewSize() { const MyGUI::IntSize viewport = getPreviewViewportSize(); mPreview->setViewport(viewport.width, viewport.height); mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, viewport.width / float(mPreview->getTextureWidth()), viewport.height / float(mPreview->getTextureHeight()))); } void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender) { mSortModel->setNameFilter(_sender->getCaption()); mItemView->update(); } void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) mSortModel->setCategory(SortFilterItemModel::Category_All); else if (_sender == mFilterWeapon) mSortModel->setCategory(SortFilterItemModel::Category_Weapon); else if (_sender == mFilterApparel) mSortModel->setCategory(SortFilterItemModel::Category_Apparel); else if (_sender == mFilterMagic) mSortModel->setCategory(SortFilterItemModel::Category_Magic); else if (_sender == mFilterMisc) mSortModel->setCategory(SortFilterItemModel::Category_Misc); mFilterAll->setStateSelected(false); mFilterWeapon->setStateSelected(false); mFilterApparel->setStateSelected(false); mFilterMagic->setStateSelected(false); mFilterMisc->setStateSelected(false); mItemView->update(); _sender->castType()->setStateSelected(true); } void InventoryWindow::onPinToggled() { Settings::Manager::setBool("inventory pin", "Windows", mPinned); MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } void InventoryWindow::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) toggleMaximized(); else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); } void InventoryWindow::useItem(const MWWorld::Ptr &ptr, bool force) { const std::string& script = ptr.getClass().getScript(ptr); if (!script.empty()) { // Don't try to equip the item if PCSkipEquip is set to 1 if (ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1) { ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); return; } ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 0); } MWWorld::Ptr player = MWMechanics::getPlayer(); // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case if (!ptr.getClass().getEquipmentSlots(ptr).first.empty()) { if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); updateItemView(); return; } if (!force) { std::pair canEquip = ptr.getClass().canBeEquipped(ptr, player); if (canEquip.first == 0) { MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); updateItemView(); return; } } } // If the item has a script, set OnPCEquip or PCSkipEquip to 1 if (!script.empty()) { // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here auto type = ptr.getType(); bool isBook = type == ESM::Book::sRecordId; if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId) ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); // Books must have PCSkipEquip set to 1 instead else if (isBook) ptr.getRefData().getLocals().setVarByInt(script, "pcskipequip", 1); } std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); if (isVisible()) { mItemView->update(); notifyContentChanged(); } // else: will be updated in open() } void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) { if (mDragAndDrop->mIsOnDragAndDrop) { MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; mDragAndDrop->finish(); if (mDragAndDrop->mSourceModel != mTradeModel) { // Move item to the player's inventory ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); } useItem(ptr); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 item if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) && mDragAndDrop->mDraggedCount > 1) { // Item can be provided from other window for example container. // But after DragAndDrop::startDrag item automaticly always gets to player inventory. mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem); dragItem(nullptr, mDragAndDrop->mDraggedCount - 1); } } else { MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition (); MWWorld::Ptr itemSelected = getAvatarSelectedItem (relPos.left, relPos.top); if (itemSelected.isEmpty ()) return; for (size_t i=0; i < mTradeModel->getItemCount (); ++i) { if (mTradeModel->getItem(i).mBase == itemSelected) { onItemSelectedFromSourceModel(i); return; } } throw std::runtime_error("Can't find clicked item"); } } MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y) { const osg::Vec2f viewport_coords = mapPreviewWindowToViewport(x, y); int slot = mPreview->getSlotSelected(viewport_coords.x(), viewport_coords.y()); if (slot == -1) return MWWorld::Ptr(); MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); if(invStore.getSlot(slot) != invStore.end()) { MWWorld::Ptr item = *invStore.getSlot(slot); if (!item.getClass().showsInInventory(item)) return MWWorld::Ptr(); return item; } return MWWorld::Ptr(); } void InventoryWindow::updateEncumbranceBar() { MWWorld::Ptr player = MWMechanics::getPlayer(); float capacity = player.getClass().getCapacity(player); float encumbrance = player.getClass().getEncumbrance(player); mTradeModel->adjustEncumbrance(encumbrance); mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); } void InventoryWindow::onFrame(float dt) { updateEncumbranceBar(); if (mPinned) { mUpdateTimer += dt; if (0.1f < mUpdateTimer) { mUpdateTimer = 0; // Update pinned inventory in-game if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) { mItemView->update(); notifyContentChanged(); } } } } void InventoryWindow::setTrading(bool trading) { mTrading = trading; } void InventoryWindow::dirtyPreview() { mPreview->update(); updateArmorRating(); } void InventoryWindow::notifyContentChanged() { // update the spell window just in case new enchanted items were added to inventory MWBase::Environment::get().getWindowManager()->updateSpellWindow(); MWBase::Environment::get().getMechanicsManager()->updateMagicEffects( MWMechanics::getPlayer()); dirtyPreview(); } void InventoryWindow::pickUpObject (MWWorld::Ptr object) { // If the inventory is not yet enabled, don't pick anything up if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) return; // make sure the object is of a type that can be picked up auto type = object.getType(); if ( (type != ESM::Apparatus::sRecordId) && (type != ESM::Armor::sRecordId) && (type != ESM::Book::sRecordId) && (type != ESM::Clothing::sRecordId) && (type != ESM::Ingredient::sRecordId) && (type != ESM::Light::sRecordId) && (type != ESM::Miscellaneous::sRecordId) && (type != ESM::Lockpick::sRecordId) && (type != ESM::Probe::sRecordId) && (type != ESM::Repair::sRecordId) && (type != ESM::Weapon::sRecordId) && (type != ESM::Potion::sRecordId)) return; // An object that can be picked up must have a tooltip. if (!object.getClass().hasToolTip(object)) return; int count = object.getRefData().getCount(); if (object.getClass().isGold(object)) count *= object.getClass().getValue(object); MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); if (!object.getRefData().activate()) return; // Player must not be paralyzed, knocked down, or dead to pick up an item. const MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats(player); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && playerStats.isParalyzed()) || playerStats.getKnockedDown() || playerStats.isDead()) return; MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count); // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player); // remove from world MWBase::Environment::get().getWorld()->deleteObject (object); // get ModelIndex to the item mTradeModel->update(); size_t i=0; for (; igetItemCount(); ++i) { if (mTradeModel->getItem(i).mBase == newObject) break; } if (i == mTradeModel->getItemCount()) throw std::runtime_error("Added item not found"); mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count); MWBase::Environment::get().getWindowManager()->updateSpellWindow(); } void InventoryWindow::cycle(bool next) { MWWorld::Ptr player = MWMechanics::getPlayer(); if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) return; const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) return; ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering SortFilterItemModel model(new InventoryItemModel(player)); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) return; for (ItemModel::ModelIndex i=0; irebuild(); } MyGUI::IntSize InventoryWindow::getPreviewViewportSize() const { const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize(); const float scale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); return MyGUI::IntSize(std::min(mPreview->getTextureWidth(), previewWindowSize.width * scale), std::min(mPreview->getTextureHeight(), previewWindowSize.height * scale)); } osg::Vec2f InventoryWindow::mapPreviewWindowToViewport(int x, int y) const { const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize(); const float normalisedX = x / std::max(1.0f, previewWindowSize.width); const float normalisedY = y / std::max(1.0f, previewWindowSize.height); const MyGUI::IntSize viewport = getPreviewViewportSize(); return osg::Vec2f( normalisedX * float(viewport.width - 1), (1.0 - normalisedY) * float(viewport.height - 1) ); } } openmw-openmw-0.48.0/apps/openmw/mwgui/inventorywindow.hpp000066400000000000000000000072661445372753700240450ustar00rootroot00000000000000#ifndef MGUI_Inventory_H #define MGUI_Inventory_H #include "windowpinnablebase.hpp" #include "mode.hpp" #include "../mwworld/ptr.hpp" #include "../mwrender/characterpreview.hpp" namespace osg { class Group; } namespace Resource { class ResourceSystem; } namespace MWGui { namespace Widgets { class MWDynamicStat; } class ItemView; class SortFilterItemModel; class TradeItemModel; class DragAndDrop; class ItemModel; class InventoryWindow : public WindowPinnableBase { public: InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem); void onOpen() override; /// start trading, disables item drag&drop void setTrading(bool trading); void onFrame(float dt) override; void pickUpObject (MWWorld::Ptr object); MWWorld::Ptr getAvatarSelectedItem(int x, int y); void rebuildAvatar(); SortFilterItemModel* getSortFilterModel(); TradeItemModel* getTradeModel(); ItemModel* getModel(); void updateItemView(); void updatePlayer(); void clear() override; void useItem(const MWWorld::Ptr& ptr, bool force=false); void setGuiMode(GuiMode mode); /// Cycle to previous/next weapon void cycle(bool next); protected: void onTitleDoubleClicked() override; private: DragAndDrop* mDragAndDrop; int mSelectedItem; MWWorld::Ptr mPtr; MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; MyGUI::Widget* mAvatar; MyGUI::ImageBox* mAvatarImage; MyGUI::TextBox* mArmorRating; Widgets::MWDynamicStat* mEncumbranceBar; MyGUI::Widget* mLeftPane; MyGUI::Widget* mRightPane; MyGUI::Button* mFilterAll; MyGUI::Button* mFilterWeapon; MyGUI::Button* mFilterApparel; MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; MyGUI::EditBox* mFilterEdit; GuiMode mGuiMode; int mLastXSize; int mLastYSize; std::unique_ptr mPreviewTexture; std::unique_ptr mPreview; bool mTrading; float mUpdateTimer; void toggleMaximized(); void onItemSelected(int index); void onItemSelectedFromSourceModel(int index); void onBackgroundSelected(); std::string getModeSetting() const; void sellItem(MyGUI::Widget* sender, int count); void dragItem(MyGUI::Widget* sender, int count); void onWindowResize(MyGUI::Window* _sender); void onFilterChanged(MyGUI::Widget* _sender); void onNameFilterChanged(MyGUI::EditBox* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled() override; void updateEncumbranceBar(); void notifyContentChanged(); void dirtyPreview(); void updatePreviewSize(); void updateArmorRating(); MyGUI::IntSize getPreviewViewportSize() const; osg::Vec2f mapPreviewWindowToViewport(int x, int y) const; void adjustPanes(); /// Unequips count items from mSelectedItem, if it is equipped, and then updates mSelectedItem in case the items were re-stacked void ensureSelectedItemUnequipped(int count); }; } #endif // Inventory_H openmw-openmw-0.48.0/apps/openmw/mwgui/itemchargeview.cpp000066400000000000000000000154201445372753700235450ustar00rootroot00000000000000#include "itemchargeview.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "itemmodel.hpp" #include "itemwidget.hpp" namespace MWGui { ItemChargeView::ItemChargeView() : mScrollView(nullptr), mDisplayMode(DisplayMode_Health) { } void ItemChargeView::registerComponents() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } void ItemChargeView::initialiseOverride() { Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); if (mScrollView == nullptr) throw std::runtime_error("Item charge view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); } void ItemChargeView::setModel(ItemModel* model) { mModel.reset(model); } void ItemChargeView::setDisplayMode(ItemChargeView::DisplayMode type) { mDisplayMode = type; update(); } void ItemChargeView::update() { if (!mModel.get()) { layoutWidgets(); return; } mModel->update(); Lines lines; std::set visitedLines; for (size_t i = 0; i < mModel->getItemCount(); ++i) { ItemStack stack = mModel->getItem(static_cast(i)); bool found = false; for (Lines::const_iterator iter = mLines.begin(); iter != mLines.end(); ++iter) { if (iter->mItemPtr == stack.mBase) { found = true; visitedLines.insert(iter); // update line widgets updateLine(*iter); lines.push_back(*iter); break; } } if (!found) { // add line widgets Line line; line.mItemPtr = stack.mBase; line.mText = mScrollView->createWidget("SandText", MyGUI::IntCoord(), MyGUI::Align::Default); line.mText->setNeedMouseFocus(false); line.mIcon = mScrollView->createWidget("MW_ItemIconSmall", MyGUI::IntCoord(), MyGUI::Align::Default); line.mIcon->setItem(line.mItemPtr); line.mIcon->setUserString("ToolTipType", "ItemPtr"); line.mIcon->setUserData(MWWorld::Ptr(line.mItemPtr)); line.mIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemChargeView::onIconClicked); line.mIcon->eventMouseWheel += MyGUI::newDelegate(this, &ItemChargeView::onMouseWheelMoved); line.mCharge = mScrollView->createWidget("MW_ChargeBar", MyGUI::IntCoord(), MyGUI::Align::Default); line.mCharge->setNeedMouseFocus(false); updateLine(line); lines.push_back(line); } } for (Lines::iterator iter = mLines.begin(); iter != mLines.end(); ++iter) { if (visitedLines.count(iter)) continue; // remove line widgets MyGUI::Gui::getInstance().destroyWidget(iter->mText); MyGUI::Gui::getInstance().destroyWidget(iter->mIcon); MyGUI::Gui::getInstance().destroyWidget(iter->mCharge); } mLines.swap(lines); layoutWidgets(); } void ItemChargeView::layoutWidgets() { int currentY = 0; for (Line& line : mLines) { line.mText->setCoord(8, currentY, mScrollView->getWidth()-8, 18); currentY += 19; line.mIcon->setCoord(16, currentY, 32, 32); line.mCharge->setCoord(72, currentY+2, std::max(199, mScrollView->getWidth()-72-38), 20); currentY += 32 + 4; } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(MyGUI::IntSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), currentY))); mScrollView->setVisibleVScroll(true); } void ItemChargeView::resetScrollbars() { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); } void ItemChargeView::setSize(const MyGUI::IntSize& value) { bool changed = (value.width != getWidth() || value.height != getHeight()); Base::setSize(value); if (changed) layoutWidgets(); } void ItemChargeView::setCoord(const MyGUI::IntCoord& value) { bool changed = (value.width != getWidth() || value.height != getHeight()); Base::setCoord(value); if (changed) layoutWidgets(); } void ItemChargeView::updateLine(const ItemChargeView::Line& line) { line.mText->setCaption(line.mItemPtr.getClass().getName(line.mItemPtr)); line.mCharge->setVisible(false); switch (mDisplayMode) { case DisplayMode_Health: if (!line.mItemPtr.getClass().hasItemHealth(line.mItemPtr)) break; line.mCharge->setVisible(true); line.mCharge->setValue(line.mItemPtr.getClass().getItemHealth(line.mItemPtr), line.mItemPtr.getClass().getItemMaxHealth(line.mItemPtr)); break; case DisplayMode_EnchantmentCharge: std::string enchId = line.mItemPtr.getClass().getEnchantment(line.mItemPtr); if (enchId.empty()) break; const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(enchId); if (!ench) break; line.mCharge->setVisible(true); line.mCharge->setValue(static_cast(line.mItemPtr.getCellRef().getEnchantmentCharge()), ench->mData.mCharge); break; } } void ItemChargeView::onIconClicked(MyGUI::Widget* sender) { eventItemClicked(this, *sender->getUserData()); } void ItemChargeView::onMouseWheelMoved(MyGUI::Widget* /*sender*/, int rel) { if (mScrollView->getViewOffset().top + rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + rel*0.3f))); } } openmw-openmw-0.48.0/apps/openmw/mwgui/itemchargeview.hpp000066400000000000000000000035071445372753700235550ustar00rootroot00000000000000#ifndef MWGUI_ITEMCHARGEVIEW_H #define MWGUI_ITEMCHARGEVIEW_H #include #include #include #include "../mwworld/ptr.hpp" #include "widgets.hpp" namespace MyGUI { class TextBox; class ScrollView; } namespace MWGui { class ItemModel; class ItemWidget; class ItemChargeView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemChargeView) public: enum DisplayMode { DisplayMode_Health, DisplayMode_EnchantmentCharge }; ItemChargeView(); /// Register needed components with MyGUI's factory manager static void registerComponents(); void initialiseOverride() override; /// Takes ownership of \a model void setModel(ItemModel* model); void setDisplayMode(DisplayMode type); void update(); void layoutWidgets(); void resetScrollbars(); void setSize(const MyGUI::IntSize& value) override; void setCoord(const MyGUI::IntCoord& value) override; MyGUI::delegates::CMultiDelegate2 eventItemClicked; private: struct Line { MWWorld::Ptr mItemPtr; MyGUI::TextBox* mText; ItemWidget* mIcon; Widgets::MWDynamicStatPtr mCharge; }; void updateLine(const Line& line); void onIconClicked(MyGUI::Widget* sender); void onMouseWheelMoved(MyGUI::Widget* sender, int rel); typedef std::vector Lines; Lines mLines; std::unique_ptr mModel; MyGUI::ScrollView* mScrollView; DisplayMode mDisplayMode; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/itemmodel.cpp000066400000000000000000000106521445372753700225230ustar00rootroot00000000000000#include "itemmodel.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" namespace MWGui { ItemStack::ItemStack(const MWWorld::Ptr &base, ItemModel *creator, size_t count) : mType(Type_Normal) , mFlags(0) , mCreator(creator) , mCount(count) , mBase(base) { if (base.getClass().getEnchantment(base) != "") mFlags |= Flag_Enchanted; if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(base)) mFlags |= Flag_Bound; } ItemStack::ItemStack() : mType(Type_Normal) , mFlags(0) , mCreator(nullptr) , mCount(0) { } bool operator == (const ItemStack& left, const ItemStack& right) { if (left.mType != right.mType) return false; if(left.mBase == right.mBase) return true; // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure if (left.mBase.getContainerStore() && right.mBase.getContainerStore()) return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase) && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); if (left.mBase.getContainerStore()) return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase); if (right.mBase.getContainerStore()) return right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); MWWorld::ContainerStore store; return store.stacks(left.mBase, right.mBase); } ItemModel::ItemModel() { } MWWorld::Ptr ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) { MWWorld::Ptr ret = otherModel->copyItem(item, count); removeItem(item, count); return ret; } bool ItemModel::allowedToUseItems() const { return true; } bool ItemModel::onDropItem(const MWWorld::Ptr &item, int count) { return true; } bool ItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { return true; } ProxyItemModel::ProxyItemModel() : mSourceModel(nullptr) { } ProxyItemModel::~ProxyItemModel() { delete mSourceModel; } bool ProxyItemModel::allowedToUseItems() const { return mSourceModel->allowedToUseItems(); } MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { return mSourceModel->copyItem (item, count, allowAutoEquip); } void ProxyItemModel::removeItem (const ItemStack& item, size_t count) { mSourceModel->removeItem (item, count); } ItemModel::ModelIndex ProxyItemModel::mapToSource (ModelIndex index) { const ItemStack& itemToSearch = getItem(index); for (size_t i=0; igetItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); if (item.mBase == itemToSearch.mBase) return i; } return -1; } ItemModel::ModelIndex ProxyItemModel::mapFromSource (ModelIndex index) { const ItemStack& itemToSearch = mSourceModel->getItem(index); for (size_t i=0; igetIndex(item); } void ProxyItemModel::setSourceModel(ItemModel *sourceModel) { if (mSourceModel == sourceModel) return; if (mSourceModel) { delete mSourceModel; mSourceModel = nullptr; } mSourceModel = sourceModel; } void ProxyItemModel::onClose() { mSourceModel->onClose(); } bool ProxyItemModel::onDropItem(const MWWorld::Ptr &item, int count) { return mSourceModel->onDropItem(item, count); } bool ProxyItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { return mSourceModel->onTakeItem(item, count); } bool ProxyItemModel::usesContainer(const MWWorld::Ptr& container) { return mSourceModel->usesContainer(container); } } openmw-openmw-0.48.0/apps/openmw/mwgui/itemmodel.hpp000066400000000000000000000071751445372753700225360ustar00rootroot00000000000000#ifndef MWGUI_ITEM_MODEL_H #define MWGUI_ITEM_MODEL_H #include "../mwworld/ptr.hpp" namespace MWGui { class ItemModel; /// @brief A single item stack managed by an item model struct ItemStack { ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count); ItemStack(); ///< like operator==, only without checking mType enum Type { Type_Barter, Type_Equipped, Type_Normal }; Type mType; enum Flags { Flag_Enchanted = (1<<0), Flag_Bound = (1<<1) }; int mFlags; ItemModel* mCreator; size_t mCount; MWWorld::Ptr mBase; }; bool operator == (const ItemStack& left, const ItemStack& right); /// @brief The base class that all item models should derive from. class ItemModel { public: ItemModel(); virtual ~ItemModel() {} typedef int ModelIndex; // -1 means invalid index /// Throws for invalid index or out of range index virtual ItemStack getItem (ModelIndex index) = 0; /// The number of items in the model, this specifies the range of indices you can pass to /// the getItem function (but this range is only valid until the next call to update()) virtual size_t getItemCount() = 0; /// Returns an invalid index if the item was not found virtual ModelIndex getIndex (const ItemStack &item) = 0; /// Rebuild the item model, this will invalidate existing model indices virtual void update() = 0; /// Move items from this model to \a otherModel. /// @note Derived implementations may return an empty Ptr if the move was unsuccessful. virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; /// Is the player allowed to use items from this item model? (default true) virtual bool allowedToUseItems() const; virtual void onClose() { } virtual bool onDropItem(const MWWorld::Ptr &item, int count); virtual bool onTakeItem(const MWWorld::Ptr &item, int count); virtual bool usesContainer(const MWWorld::Ptr& container) = 0; private: ItemModel(const ItemModel&); ItemModel& operator=(const ItemModel&); }; /// @brief A proxy item model can be used to filter or rearrange items from a source model (or even add new items to it). /// The neat thing is that this does not actually alter the source model. class ProxyItemModel : public ItemModel { public: ProxyItemModel(); virtual ~ProxyItemModel(); bool allowedToUseItems() const override; void onClose() override; bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem (const ItemStack& item, size_t count) override; ModelIndex getIndex (const ItemStack &item) override; /// @note Takes ownership of the passed pointer. void setSourceModel(ItemModel* sourceModel); ModelIndex mapToSource (ModelIndex index); ModelIndex mapFromSource (ModelIndex index); bool usesContainer(const MWWorld::Ptr& container) override; protected: ItemModel* mSourceModel; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/itemselection.cpp000066400000000000000000000033711445372753700234100ustar00rootroot00000000000000#include "itemselection.hpp" #include #include #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" namespace MWGui { ItemSelectionDialog::ItemSelectionDialog(const std::string &label) : WindowModal("openmw_itemselection_dialog.layout") , mSortModel(nullptr) , mModel(nullptr) { getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &ItemSelectionDialog::onSelectedItem); MyGUI::TextBox* l; getWidget(l, "Label"); l->setCaptionWithReplacing (label); MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemSelectionDialog::onCancelButtonClicked); center(); } bool ItemSelectionDialog::exit() { eventDialogCanceled(); return true; } void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container) { mModel = new InventoryItemModel(container); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); mItemView->resetScrollBars(); } void ItemSelectionDialog::setCategory(int category) { mSortModel->setCategory(category); mItemView->update(); } void ItemSelectionDialog::setFilter(int filter) { mSortModel->setFilter(filter); mItemView->update(); } void ItemSelectionDialog::onSelectedItem(int index) { ItemStack item = mSortModel->getItem(index); eventItemSelected(item.mBase); } void ItemSelectionDialog::onCancelButtonClicked(MyGUI::Widget* sender) { exit(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/itemselection.hpp000066400000000000000000000020341445372753700234100ustar00rootroot00000000000000#ifndef OPENMW_GAME_MWGUI_ITEMSELECTION_H #define OPENMW_GAME_MWGUI_ITEMSELECTION_H #include #include "windowbase.hpp" namespace MWWorld { class Ptr; } namespace MWGui { class ItemView; class SortFilterItemModel; class InventoryItemModel; class ItemSelectionDialog : public WindowModal { public: ItemSelectionDialog(const std::string& label); bool exit() override; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Item; EventHandle_Item eventItemSelected; EventHandle_Void eventDialogCanceled; void openContainer (const MWWorld::Ptr& container); void setCategory(int category); void setFilter(int filter); private: ItemView* mItemView; SortFilterItemModel* mSortModel; InventoryItemModel* mModel; void onSelectedItem(int index); void onCancelButtonClicked(MyGUI::Widget* sender); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/itemview.cpp000066400000000000000000000115221445372753700223720ustar00rootroot00000000000000#include "itemview.hpp" #include #include #include #include #include #include "../mwworld/class.hpp" #include "itemmodel.hpp" #include "itemwidget.hpp" namespace MWGui { ItemView::ItemView() : mModel(nullptr) , mScrollView(nullptr) { } ItemView::~ItemView() { delete mModel; } void ItemView::setModel(ItemModel *model) { if (mModel == model) return; delete mModel; mModel = model; update(); } void ItemView::initialiseOverride() { Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); if (mScrollView == nullptr) throw std::runtime_error("Item view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); } void ItemView::layoutWidgets() { if (!mScrollView->getChildCount()) return; int x = 0; int y = 0; MyGUI::Widget* dragArea = mScrollView->getChildAt(0); int maxHeight = mScrollView->getHeight(); int rows = maxHeight/42; rows = std::max(rows, 1); bool showScrollbar = int(std::ceil(dragArea->getChildCount()/float(rows))) > mScrollView->getWidth()/42; if (showScrollbar) maxHeight -= 18; for (unsigned int i=0; igetChildCount(); ++i) { MyGUI::Widget* w = dragArea->getChildAt(i); w->setPosition(x, y); y += 42; if (y > maxHeight-42 && i < dragArea->getChildCount()-1) { x += 42; y = 0; } } x += 42; MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mScrollView->setVisibleVScroll(false); mScrollView->setVisibleHScroll(false); mScrollView->setCanvasSize(size); mScrollView->setVisibleVScroll(true); mScrollView->setVisibleHScroll(true); dragArea->setSize(size); } void ItemView::update() { while (mScrollView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); if (!mModel) return; mModel->update(); MyGUI::Widget* dragArea = mScrollView->createWidget("",0,0,mScrollView->getWidth(),mScrollView->getHeight(), MyGUI::Align::Stretch); dragArea->setNeedMouseFocus(true); dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground); dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); for (ItemModel::ModelIndex i=0; i(mModel->getItemCount()); ++i) { const ItemStack& item = mModel->getItem(i); ItemWidget* itemWidget = dragArea->createWidget("MW_ItemIcon", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); itemWidget->setUserString("ToolTipType", "ItemModelIndex"); itemWidget->setUserData(std::make_pair(i, mModel)); ItemWidget::ItemState state = ItemWidget::None; if (item.mType == ItemStack::Type_Barter) state = ItemWidget::Barter; if (item.mType == ItemStack::Type_Equipped) state = ItemWidget::Equip; itemWidget->setItem(item.mBase, state); itemWidget->setCount(item.mCount); itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); } layoutWidgets(); } void ItemView::resetScrollBars() { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); } void ItemView::onSelectedItem(MyGUI::Widget *sender) { ItemModel::ModelIndex index = (*sender->getUserData >()).first; eventItemClicked(index); } void ItemView::onSelectedBackground(MyGUI::Widget *sender) { eventBackgroundClicked(); } void ItemView::onMouseWheelMoved(MyGUI::Widget *_sender, int _rel) { if (mScrollView->getViewOffset().left + _rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else mScrollView->setViewOffset(MyGUI::IntPoint(static_cast(mScrollView->getViewOffset().left + _rel*0.3f), 0)); } void ItemView::setSize(const MyGUI::IntSize &_value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); if (changed) layoutWidgets(); } void ItemView::setCoord(const MyGUI::IntCoord &_value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); if (changed) layoutWidgets(); } void ItemView::registerComponents() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/itemview.hpp000066400000000000000000000025541445372753700224040ustar00rootroot00000000000000#ifndef MWGUI_ITEMVIEW_H #define MWGUI_ITEMVIEW_H #include #include "itemmodel.hpp" namespace MWGui { class ItemView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemView) public: ItemView(); ~ItemView() override; /// Register needed components with MyGUI's factory manager static void registerComponents (); /// Takes ownership of \a model void setModel (ItemModel* model); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /// Fired when an item was clicked EventHandle_ModelIndex eventItemClicked; /// Fired when the background was clicked (useful for drag and drop) EventHandle_Void eventBackgroundClicked; void update(); void resetScrollBars(); private: void initialiseOverride() override; void layoutWidgets(); void setSize(const MyGUI::IntSize& _value) override; void setCoord(const MyGUI::IntCoord& _value) override; void onSelectedItem (MyGUI::Widget* sender); void onSelectedBackground (MyGUI::Widget* sender); void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); ItemModel* mModel; MyGUI::ScrollView* mScrollView; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/itemwidget.cpp000066400000000000000000000151021445372753700227010ustar00rootroot00000000000000#include "itemwidget.hpp" #include #include #include #include #include // correctIconPath #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" namespace { std::string getCountString(int count) { static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); if (count == 1) return ""; // With small text size we can use up to 4 characters, while with large ones - only up to 3. if (fontHeight > 16) { if (count > 999999999) return MyGUI::utility::toString(count/1000000000) + "b"; else if (count > 99999999) return ">9m"; else if (count > 999999) return MyGUI::utility::toString(count/1000000) + "m"; else if (count > 99999) return ">9k"; else if (count > 999) return MyGUI::utility::toString(count/1000) + "k"; else return MyGUI::utility::toString(count); } if (count > 999999999) return MyGUI::utility::toString(count/1000000000) + "b"; else if (count > 999999) return MyGUI::utility::toString(count/1000000) + "m"; else if (count > 9999) return MyGUI::utility::toString(count/1000) + "k"; else return MyGUI::utility::toString(count); } } namespace MWGui { std::map ItemWidget::mScales; ItemWidget::ItemWidget() : mItem(nullptr) , mItemShadow(nullptr) , mFrame(nullptr) , mText(nullptr) { } void ItemWidget::registerComponents() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } void ItemWidget::initialiseOverride() { assignWidget(mItem, "Item"); if (mItem) mItem->setNeedMouseFocus(false); assignWidget(mItemShadow, "ItemShadow"); if (mItemShadow) mItemShadow->setNeedMouseFocus(false); assignWidget(mFrame, "Frame"); if (mFrame) mFrame->setNeedMouseFocus(false); assignWidget(mText, "Text"); if (mText) mText->setNeedMouseFocus(false); Base::initialiseOverride(); } void ItemWidget::setCount(int count) { if (!mText) return; mText->setCaption(getCountString(count)); } void ItemWidget::setIcon(const std::string &icon) { if (mCurrentIcon != icon) { mCurrentIcon = icon; if (mItemShadow) mItemShadow->setImageTexture(icon); if (mItem) mItem->setImageTexture(icon); } } void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) { if (mFrame) { mFrame->setImageTile(MyGUI::IntSize(coord.width, coord.height)); // Why is this needed? MyGUI bug? mFrame->setImageCoord(coord); } if (mCurrentFrame != frame) { mCurrentFrame = frame; mFrame->setImageTexture(frame); } } void ItemWidget::setIcon(const MWWorld::Ptr &ptr) { std::string invIcon = ptr.getClass().getInventoryIcon(ptr); if (invIcon.empty()) invIcon = "default icon.tga"; const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); invIcon = Misc::ResourceHelpers::correctIconPath(invIcon, vfs); if (!vfs->exists(invIcon)) { Log(Debug::Error) << "Failed to open image: '" << invIcon << "' not found, falling back to 'default-icon.tga'"; invIcon = Misc::ResourceHelpers::correctIconPath("default icon.tga", vfs); } setIcon(invIcon); } void ItemWidget::setItem(const MWWorld::Ptr &ptr, ItemState state) { if (!mItem) return; if (ptr.isEmpty()) { if (mFrame) mFrame->setImageTexture(""); if (mItemShadow) mItemShadow->setImageTexture(""); mItem->setImageTexture(""); mText->setCaption(""); mCurrentIcon.clear(); mCurrentFrame.clear(); return; } bool isMagic = !ptr.getClass().getEnchantment(ptr).empty(); std::string backgroundTex = "textures\\menu_icon"; if (isMagic) backgroundTex += "_magic"; if (state == None) { if (!isMagic) backgroundTex.clear(); } else if (state == Equip) { backgroundTex += "_equip"; } else if (state == Barter) backgroundTex += "_barter"; if (backgroundTex != "") backgroundTex += ".dds"; float scale = 1.f; if (!backgroundTex.empty()) { auto found = mScales.find(backgroundTex); if (found == mScales.end()) { // By default, background icons are supposed to use the 42x42 part of 64x64 image. // If the image has a different size, we should use a different chunk size // Cache result to do not retrieve background texture every frame. MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(backgroundTex); if (texture) scale = texture->getHeight() / 64.f; mScales[backgroundTex] = scale; } else scale = found->second; } if (state == Barter && !isMagic) setFrame(backgroundTex, MyGUI::IntCoord(2*scale,2*scale,44*scale,44*scale)); else setFrame(backgroundTex, MyGUI::IntCoord(0,0,44*scale,44*scale)); setIcon(ptr); } void SpellWidget::setSpellIcon(const std::string& icon) { if (mFrame && !mCurrentFrame.empty()) { mCurrentFrame.clear(); mFrame->setImageTexture(""); } if (mCurrentIcon != icon) { mCurrentIcon = icon; if (mItemShadow) mItemShadow->setImageTexture(icon); if (mItem) mItem->setImageTexture(icon); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/itemwidget.hpp000066400000000000000000000027131445372753700227120ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_ITEMWIDGET_H #define OPENMW_MWGUI_ITEMWIDGET_H #include namespace MWWorld { class Ptr; } namespace MWGui { /// @brief A widget that shows an icon for an MWWorld::Ptr class ItemWidget : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemWidget) public: ItemWidget(); /// Register needed components with MyGUI's factory manager static void registerComponents (); enum ItemState { None, Equip, Barter, Magic }; /// Set count to be displayed in a textbox over the item void setCount(int count); /// \a ptr may be empty void setItem (const MWWorld::Ptr& ptr, ItemState state = None); // Set icon and frame manually void setIcon (const std::string& icon); void setIcon (const MWWorld::Ptr& ptr); void setFrame (const std::string& frame, const MyGUI::IntCoord& coord); protected: void initialiseOverride() override; MyGUI::ImageBox* mItem; MyGUI::ImageBox* mItemShadow; MyGUI::ImageBox* mFrame; MyGUI::TextBox* mText; std::string mCurrentIcon; std::string mCurrentFrame; static std::map mScales; }; class SpellWidget : public ItemWidget { MYGUI_RTTI_DERIVED(SpellWidget) public: void setSpellIcon (const std::string& icon); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/jailscreen.cpp000066400000000000000000000105601445372753700226610ustar00rootroot00000000000000#include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/store.hpp" #include "../mwworld/class.hpp" #include "jailscreen.hpp" namespace MWGui { JailScreen::JailScreen() : WindowBase("openmw_jail_screen.layout"), mDays(1), mFadeTimeRemaining(0), mTimeAdvancer(0.01f) { getWidget(mProgressBar, "ProgressBar"); mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &JailScreen::onJailProgressChanged); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &JailScreen::onJailFinished); center(); } void JailScreen::goToJail(int days) { mDays = days; MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); mFadeTimeRemaining = 0.5; setVisible(false); mProgressBar->setScrollRange(100+1); mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(0); } void JailScreen::onFrame(float dt) { mTimeAdvancer.onFrame(dt); if (mFadeTimeRemaining <= 0) return; mFadeTimeRemaining -= dt; if (mFadeTimeRemaining <= 0) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.f); // override fade-in caused by cell transition setVisible(true); mTimeAdvancer.run(100); } } void JailScreen::onJailProgressChanged(int cur, int /*total*/) { mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(static_cast(cur / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); } void JailScreen::onJailFinished() { MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Jail); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); // We should not worsen corprus when in prison player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); std::set skills; for (int day=0; daygetPrng(); int skill = Misc::Rng::rollDice(ESM::Skill::Length, prng); skills.insert(skill); MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill); if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak) value.setBase(std::min(100.f, value.getBase()+1)); else value.setBase(std::max(0.f, value.getBase()-1)); } const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); std::string message; if (mDays == 1) message = gmst.find("sNotifyMessage42")->mValue.getString(); else message = gmst.find("sNotifyMessage43")->mValue.getString(); message = Misc::StringUtils::format(message, mDays); for (const int& skill : skills) { const std::string& skillName = gmst.find(ESM::Skill::sSkillNameIds[skill])->mValue.getString(); int skillValue = player.getClass().getNpcStats(player).getSkill(skill).getBase(); std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); if (skill == ESM::Skill::Sneak || skill == ESM::Skill::Security) skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); skillMsg = Misc::StringUtils::format(skillMsg, skillName, skillValue); message += "\n" + skillMsg; } std::vector buttons; buttons.emplace_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); } } openmw-openmw-0.48.0/apps/openmw/mwgui/jailscreen.hpp000066400000000000000000000011671445372753700226710ustar00rootroot00000000000000#ifndef MWGUI_JAILSCREEN_H #define MWGUI_JAILSCREEN_H #include "windowbase.hpp" #include "timeadvancer.hpp" namespace MWGui { class JailScreen : public WindowBase { public: JailScreen(); void goToJail(int days); void onFrame(float dt) override; bool exit() override { return false; } private: int mDays; float mFadeTimeRemaining; MyGUI::ScrollBar* mProgressBar; void onJailProgressChanged(int cur, int total); void onJailFinished(); TimeAdvancer mTimeAdvancer; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/journalbooks.cpp000066400000000000000000000237551445372753700232640ustar00rootroot00000000000000#include "journalbooks.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include #include #include "textcolours.hpp" namespace { struct AddContent { MWGui::BookTypesetter::Ptr mTypesetter; MWGui::BookTypesetter::Style* mBodyStyle; AddContent (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : mTypesetter (typesetter), mBodyStyle (body_style) { } }; struct AddSpan : AddContent { AddSpan (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : AddContent (typesetter, body_style) { } void operator () (intptr_t topicId, size_t begin, size_t end) { MWGui::BookTypesetter::Style* style = mBodyStyle; const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); if (topicId) style = mTypesetter->createHotStyle (mBodyStyle, textColours.journalLink, textColours.journalLinkOver, textColours.journalLinkPressed, topicId); mTypesetter->write (style, begin, end); } }; struct AddEntry { MWGui::BookTypesetter::Ptr mTypesetter; MWGui::BookTypesetter::Style* mBodyStyle; AddEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : mTypesetter (typesetter), mBodyStyle (body_style) { } void operator () (MWGui::JournalViewModel::Entry const & entry) { mTypesetter->addContent (entry.body ()); entry.visitSpans (AddSpan (mTypesetter, mBodyStyle)); } }; struct AddJournalEntry : AddEntry { bool mAddHeader; MWGui::BookTypesetter::Style* mHeaderStyle; AddJournalEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, MWGui::BookTypesetter::Style* header_style, bool add_header) : AddEntry (typesetter, body_style), mAddHeader (add_header), mHeaderStyle (header_style) { } void operator () (MWGui::JournalViewModel::JournalEntry const & entry) { if (mAddHeader) { mTypesetter->write (mHeaderStyle, entry.timestamp ()); mTypesetter->lineBreak (); } AddEntry::operator () (entry); mTypesetter->sectionBreak (30); } }; struct AddTopicEntry : AddEntry { intptr_t mContentId; MWGui::BookTypesetter::Style* mHeaderStyle; AddTopicEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, MWGui::BookTypesetter::Style* header_style, intptr_t contentId) : AddEntry (typesetter, body_style), mContentId (contentId), mHeaderStyle (header_style) { } void operator () (MWGui::JournalViewModel::TopicEntry const & entry) { mTypesetter->write (mBodyStyle, entry.source ()); mTypesetter->write (mBodyStyle, 0, 3);// begin AddEntry::operator() (entry); mTypesetter->selectContent (mContentId); mTypesetter->write (mBodyStyle, 2, 3);// end quote mTypesetter->sectionBreak (30); } }; struct AddTopicName : AddContent { AddTopicName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : AddContent (typesetter, style) { } void operator () (MWGui::JournalViewModel::Utf8Span topicName) { mTypesetter->write (mBodyStyle, topicName); mTypesetter->sectionBreak (); } }; struct AddQuestName : AddContent { AddQuestName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : AddContent (typesetter, style) { } void operator () (MWGui::JournalViewModel::Utf8Span topicName) { mTypesetter->write (mBodyStyle, topicName); mTypesetter->sectionBreak (); } }; } namespace MWGui { MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) { typedef MWGui::BookTypesetter::Utf8Point point; point begin = reinterpret_cast (text); return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); } typedef TypesetBook::Ptr book; JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) : mModel (model), mEncoding(encoding), mIndexPagesCount(0) { } book JournalBooks::createEmptyJournalBook () { BookTypesetter::Ptr typesetter = createTypesetter (); BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); typesetter->write (header, to_utf8_span ("You have no journal entries!")); typesetter->lineBreak (); typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest.")); return typesetter->complete (); } book JournalBooks::createJournalBook () { BookTypesetter::Ptr typesetter = createTypesetter (); BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); mModel->visitJournalEntries ("", AddJournalEntry (typesetter, body, header, true)); return typesetter->complete (); } book JournalBooks::createTopicBook (uintptr_t topicId) { BookTypesetter::Ptr typesetter = createTypesetter (); BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); mModel->visitTopicName (topicId, AddTopicName (typesetter, header)); intptr_t contentId = typesetter->addContent (to_utf8_span (", \"")); mModel->visitTopicEntries (topicId, AddTopicEntry (typesetter, body, header, contentId)); return typesetter->complete (); } book JournalBooks::createQuestBook (const std::string& questName) { BookTypesetter::Ptr typesetter = createTypesetter (); BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); AddQuestName addName (typesetter, header); addName(to_utf8_span(questName.c_str())); mModel->visitJournalEntries (questName, AddJournalEntry (typesetter, body, header, false)); return typesetter->complete (); } book JournalBooks::createTopicIndexBook () { bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251); BookTypesetter::Ptr typesetter = isRussian ? createCyrillicJournalIndex() : createLatinJournalIndex(); return typesetter->complete (); } BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () { BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 260); typesetter->setSectionAlignment (BookTypesetter::AlignCenter); // Latin journal index always has two columns for now. mIndexPagesCount = 2; char ch = 'A'; BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); for (int i = 0; i < 26; ++i) { char buffer [32]; sprintf (buffer, "( %c )", ch); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, textColours.journalTopicOver, textColours.journalTopicPressed, (Utf8Stream::UnicodeChar) ch); if (i == 13) typesetter->sectionBreak (); typesetter->write (style, to_utf8_span (buffer)); typesetter->lineBreak (); ch++; } return typesetter; } BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () { BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 260); typesetter->setSectionAlignment (BookTypesetter::AlignCenter); BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); // for small font size split alphabet to two columns (2x15 characers), for big font size split it to three colums (3x10 characters). int sectionBreak = 10; mIndexPagesCount = 3; if (fontHeight < 18) { sectionBreak = 15; mIndexPagesCount = 2; } unsigned char ch[3] = {0xd0, 0x90, 0x00}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 for (int i = 0; i < 32; ++i) { char buffer [32]; sprintf(buffer, "( %c%c )", ch[0], ch[1]); Utf8Stream stream ((char*) ch); Utf8Stream::UnicodeChar first = stream.peek(); const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, textColours.journalTopicOver, textColours.journalTopicPressed, first); ch[1]++; // Words can not be started with these characters if (i == 26 || i == 28) continue; if (i % sectionBreak == 0) typesetter->sectionBreak (); typesetter->write (style, to_utf8_span (buffer)); typesetter->lineBreak (); } return typesetter; } BookTypesetter::Ptr JournalBooks::createTypesetter () { //TODO: determine page size from layout... return BookTypesetter::create (240, 320); } } openmw-openmw-0.48.0/apps/openmw/mwgui/journalbooks.hpp000066400000000000000000000017631445372753700232640ustar00rootroot00000000000000#ifndef MWGUI_JOURNALBOOKS_HPP #define MWGUI_JOURNALBOOKS_HPP #include "bookpage.hpp" #include "journalviewmodel.hpp" #include namespace MWGui { MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text); struct JournalBooks { typedef TypesetBook::Ptr Book; JournalViewModel::Ptr mModel; JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding); Book createEmptyJournalBook (); Book createJournalBook (); Book createTopicBook (uintptr_t topicId); Book createTopicBook (const std::string& topicId); Book createQuestBook (const std::string& questName); Book createTopicIndexBook (); ToUTF8::FromType mEncoding; int mIndexPagesCount; private: BookTypesetter::Ptr createTypesetter (); BookTypesetter::Ptr createLatinJournalIndex (); BookTypesetter::Ptr createCyrillicJournalIndex (); }; } #endif // MWGUI_JOURNALBOOKS_HPP openmw-openmw-0.48.0/apps/openmw/mwgui/journalviewmodel.cpp000066400000000000000000000301661445372753700241340ustar00rootroot00000000000000#include "journalviewmodel.hpp" #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwdialogue/keywordsearch.hpp" namespace MWGui { struct JournalViewModelImpl; struct JournalViewModelImpl : JournalViewModel { typedef MWDialogue::KeywordSearch KeywordSearchT; mutable bool mKeywordSearchLoaded; mutable KeywordSearchT mKeywordSearch; JournalViewModelImpl () { mKeywordSearchLoaded = false; } virtual ~JournalViewModelImpl () { } /// \todo replace this nasty BS static Utf8Span toUtf8Span (std::string const & str) { if (str.size () == 0) return Utf8Span (Utf8Point (nullptr), Utf8Point (nullptr)); Utf8Point point = reinterpret_cast (str.c_str ()); return Utf8Span (point, point + str.size ()); } void load () override { } void unload () override { mKeywordSearch.clear (); mKeywordSearchLoaded = false; } void ensureKeyWordSearchLoaded () const { if (!mKeywordSearchLoaded) { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i) mKeywordSearch.seed (i->first, intptr_t (&i->second)); mKeywordSearchLoaded = true; } } bool isEmpty () const override { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); return journal->begin () == journal->end (); } template struct BaseEntry : Interface { typedef t_iterator iterator_t; iterator_t itr; JournalViewModelImpl const * mModel; BaseEntry (JournalViewModelImpl const * model, iterator_t itr) : itr (itr), mModel (model), loaded (false) {} virtual ~BaseEntry () {} mutable bool loaded; mutable std::string utf8text; typedef std::pair Range; // hyperlinks in @link# notation mutable std::map mHyperLinks; virtual std::string getText () const = 0; void ensureLoaded () const { if (!loaded) { mModel->ensureKeyWordSearchLoaded (); utf8text = getText (); size_t pos_end = 0; for(;;) { size_t pos_begin = utf8text.find('@'); if (pos_begin != std::string::npos) pos_end = utf8text.find('#', pos_begin); if (pos_begin != std::string::npos && pos_end != std::string::npos) { std::string link = utf8text.substr(pos_begin + 1, pos_end - pos_begin - 1); const char specialPseudoAsteriskCharacter = 127; std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); std::string topicName = MWBase::Environment::get().getWindowManager()-> getTranslationDataStorage().topicStandardForm(link); std::string displayName = link; while (displayName[displayName.size()-1] == '*') displayName.erase(displayName.size()-1, 1); utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName); intptr_t value = 0; if (mModel->mKeywordSearch.containsKeyword(topicName, value)) mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value; } else break; } loaded = true; } } Utf8Span body () const override { ensureLoaded (); return toUtf8Span (utf8text); } void visitSpans (std::function < void (TopicId, size_t, size_t)> visitor) const override { ensureLoaded (); mModel->ensureKeyWordSearchLoaded (); if (mHyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { size_t formatted = 0; // points to the first character that is not laid out yet for (std::map::const_iterator it = mHyperLinks.begin(); it != mHyperLinks.end(); ++it) { intptr_t topicId = it->second; if (formatted < it->first.first) visitor (0, formatted, it->first.first); visitor (topicId, it->first.first, it->first.second); formatted = it->first.second; } if (formatted < utf8text.size()) visitor (0, formatted, utf8text.size()); } else { std::vector matches; mModel->mKeywordSearch.highlightKeywords(utf8text.begin(), utf8text.end(), matches); std::string::const_iterator i = utf8text.begin (); for (std::vector::const_iterator it = matches.begin(); it != matches.end(); ++it) { const KeywordSearchT::Match& match = *it; if (i != match.mBeg) visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ()); visitor (match.mValue, match.mBeg - utf8text.begin (), match.mEnd - utf8text.begin ()); i = match.mEnd; } if (i != utf8text.end ()) visitor (0, i - utf8text.begin (), utf8text.size ()); } } }; void visitQuestNames (bool active_only, std::function visitor) const override { MWBase::Journal * journal = MWBase::Environment::get ().getJournal (); std::set visitedQuests; // Note that for purposes of the journal GUI, quests are identified by the name, not the ID, so several // different quest IDs can end up in the same quest log. A quest log should be considered finished // when any quest ID in that log is finished. for (MWBase::Journal::TQuestIter i = journal->questBegin (); i != journal->questEnd (); ++i) { const MWDialogue::Quest& quest = i->second; bool isFinished = false; for (MWBase::Journal::TQuestIter j = journal->questBegin (); j != journal->questEnd (); ++j) { if (quest.getName() == j->second.getName() && j->second.isFinished()) isFinished = true; } if (active_only && isFinished) continue; // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not supposed // to appear in the quest book. if (!quest.getName().empty()) { // Don't list the same quest name twice if (visitedQuests.find(quest.getName()) != visitedQuests.end()) continue; visitor (quest.getName(), isFinished); visitedQuests.insert(quest.getName()); } } } template struct JournalEntryImpl : BaseEntry { using BaseEntry ::itr; mutable std::string timestamp_buffer; JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) : BaseEntry (model, itr) {} std::string getText () const override { return itr->getText(); } Utf8Span timestamp () const override { if (timestamp_buffer.empty ()) { std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); std::ostringstream os; os << itr->mDayOfMonth << ' ' << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) << " (" << dayStr << " " << (itr->mDay) << ')'; timestamp_buffer = os.str (); } return toUtf8Span (timestamp_buffer); } }; void visitJournalEntries (const std::string& questName, std::function visitor) const override { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); if (!questName.empty()) { std::vector quests; for (MWBase::Journal::TQuestIter questIt = journal->questBegin(); questIt != journal->questEnd(); ++questIt) { if (Misc::StringUtils::ciEqual(questIt->second.getName(), questName)) quests.push_back(&questIt->second); } for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) { for (std::vector::iterator questIt = quests.begin(); questIt != quests.end(); ++questIt) { MWDialogue::Quest const* quest = *questIt; for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j) { if (i->mInfoId == j->mInfoId) visitor (JournalEntryImpl (this, i)); } } } } else { for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) visitor (JournalEntryImpl (this, i)); } } void visitTopicName (TopicId topicId, std::function visitor) const override { MWDialogue::Topic const & topic = * reinterpret_cast (topicId); visitor (toUtf8Span (topic.getName())); } void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const override { MWBase::Journal * journal = MWBase::Environment::get().getJournal(); for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { Utf8Stream stream (i->first.c_str()); Utf8Stream::UnicodeChar first = Utf8Stream::toLowerUtf8(stream.peek()); if (first != Utf8Stream::toLowerUtf8(character)) continue; visitor (i->second.getName()); } } struct TopicEntryImpl : BaseEntry { MWDialogue::Topic const & mTopic; TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) : BaseEntry (model, itr), mTopic (topic) {} std::string getText () const override { return itr->getText(); } Utf8Span source () const override { return toUtf8Span (itr->mActorName); } }; void visitTopicEntries (TopicId topicId, std::function visitor) const override { typedef MWDialogue::Topic::TEntryIter iterator_t; MWDialogue::Topic const & topic = * reinterpret_cast (topicId); for (iterator_t i = topic.begin (); i != topic.end (); ++i) visitor (TopicEntryImpl (this, topic, i)); } }; JournalViewModel::Ptr JournalViewModel::create () { return std::make_shared (); } } openmw-openmw-0.48.0/apps/openmw/mwgui/journalviewmodel.hpp000066400000000000000000000073561445372753700241460ustar00rootroot00000000000000#ifndef MWGUI_JOURNALVIEWMODEL_HPP #define MWGUI_JOURNALVIEWMODEL_HPP #include #include #include #include #include namespace MWGui { /// View-Model for the journal GUI /// /// This interface defines an abstract data model suited /// specifically to the needs of the journal GUI. It isolates /// the journal GUI from the implementation details of the /// game data store. struct JournalViewModel { typedef std::shared_ptr Ptr; typedef intptr_t QuestId; typedef intptr_t TopicId; typedef uint8_t const * Utf8Point; typedef std::pair Utf8Span; /// The base interface for both journal entries and topics. struct Entry { /// returns the body text for the journal entry /// /// This function returns a borrowed reference to the body of the /// journal entry. The returned reference becomes invalid when the /// entry is destroyed. virtual Utf8Span body () const = 0; /// Visits each subset of text in the body, delivering the beginning /// and end of the span relative to the body, and a valid topic ID if /// the span represents a keyword, or zero if not. virtual void visitSpans (std::function visitor) const = 0; virtual ~Entry() = default; }; /// An interface to topic data. struct TopicEntry : Entry { /// Returns a pre-formatted span of UTF8 encoded text representing /// the name of the NPC this portion of dialog was heard from. virtual Utf8Span source () const = 0; virtual ~TopicEntry() = default; }; /// An interface to journal data. struct JournalEntry : Entry { /// Returns a pre-formatted span of UTF8 encoded text representing /// the in-game date this entry was added to the journal. virtual Utf8Span timestamp () const = 0; virtual ~JournalEntry() = default; }; /// called prior to journal opening virtual void load () = 0; /// called prior to journal closing virtual void unload () = 0; /// returns true if their are no journal entries to display virtual bool isEmpty () const = 0; /// walks the active and optionally completed, quests providing the name and completed status virtual void visitQuestNames (bool active_only, std::function visitor) const = 0; /// walks over the journal entries related to all quests with the given name /// If \a questName is empty, simply visits all journal entries virtual void visitJournalEntries (const std::string& questName, std::function visitor) const = 0; /// provides the name of the topic specified by its id virtual void visitTopicName (TopicId topicId, std::function visitor) const = 0; /// walks over the topics whose names start with the character virtual void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const = 0; /// walks over the topic entries for the topic specified by its identifier virtual void visitTopicEntries (TopicId topicId, std::function visitor) const = 0; // create an instance of the default journal view model implementation static Ptr create (); virtual ~JournalViewModel() = default; }; } #endif // MWGUI_JOURNALVIEWMODEL_HPP openmw-openmw-0.48.0/apps/openmw/mwgui/journalwindow.cpp000066400000000000000000000557501445372753700234560ustar00rootroot00000000000000#include "journalwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/journal.hpp" #include "bookpage.hpp" #include "windowbase.hpp" #include "journalviewmodel.hpp" #include "journalbooks.hpp" namespace { static char const OptionsOverlay [] = "OptionsOverlay"; static char const OptionsBTN [] = "OptionsBTN"; static char const PrevPageBTN [] = "PrevPageBTN"; static char const NextPageBTN [] = "NextPageBTN"; static char const CloseBTN [] = "CloseBTN"; static char const JournalBTN [] = "JournalBTN"; static char const TopicsBTN [] = "TopicsBTN"; static char const QuestsBTN [] = "QuestsBTN"; static char const CancelBTN [] = "CancelBTN"; static char const ShowAllBTN [] = "ShowAllBTN"; static char const ShowActiveBTN [] = "ShowActiveBTN"; static char const PageOneNum [] = "PageOneNum"; static char const PageTwoNum [] = "PageTwoNum"; static char const TopicsList [] = "TopicsList"; static char const QuestsList [] = "QuestsList"; static char const LeftBookPage [] = "LeftBookPage"; static char const RightBookPage [] = "RightBookPage"; static char const LeftTopicIndex [] = "LeftTopicIndex"; static char const CenterTopicIndex [] = "CenterTopicIndex"; static char const RightTopicIndex [] = "RightTopicIndex"; struct JournalWindowImpl : MWGui::JournalBooks, MWGui::JournalWindow { struct DisplayState { unsigned int mPage; Book mBook; }; typedef std::stack DisplayStateStack; DisplayStateStack mStates; Book mTopicIndexBook; bool mQuestMode; bool mOptionsMode; bool mTopicsMode; bool mAllQuests; template T * getWidget (char const * name) { T * widget; WindowBase::getWidget (widget, name); return widget; } template void setText (char const * name, value_type const & value) { getWidget (name) -> setCaption (MyGUI::utility::toString (value)); } void setVisible (char const * name, bool visible) { getWidget (name) -> setVisible (visible); } void adviseButtonClick (char const * name, void (JournalWindowImpl::*handler)(MyGUI::Widget*)) { getWidget (name) -> eventMouseButtonClick += newDelegate(this, handler); } void adviseKeyPress (char const * name, void (JournalWindowImpl::*handler)(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char)) { getWidget (name) -> eventKeyButtonPressed += newDelegate(this, handler); } MWGui::BookPage* getPage (char const * name) { return getWidget (name); } JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) : JournalBooks (Model, encoding), JournalWindow() { center(); adviseButtonClick (OptionsBTN, &JournalWindowImpl::notifyOptions ); adviseButtonClick (PrevPageBTN, &JournalWindowImpl::notifyPrevPage ); adviseButtonClick (NextPageBTN, &JournalWindowImpl::notifyNextPage ); adviseButtonClick (CloseBTN, &JournalWindowImpl::notifyClose ); adviseButtonClick (JournalBTN, &JournalWindowImpl::notifyJournal ); adviseButtonClick (TopicsBTN, &JournalWindowImpl::notifyTopics ); adviseButtonClick (QuestsBTN, &JournalWindowImpl::notifyQuests ); adviseButtonClick (CancelBTN, &JournalWindowImpl::notifyCancel ); adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll ); adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive); adviseKeyPress (OptionsBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress (PrevPageBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress (NextPageBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress (CloseBTN, &JournalWindowImpl::notifyKeyPress); adviseKeyPress (JournalBTN, &JournalWindowImpl::notifyKeyPress); Gui::MWList* list = getWidget(QuestsList); list->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyQuestClicked); Gui::MWList* topicsList = getWidget(TopicsList); topicsList->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyTopicSelected); { MWGui::BookPage::ClickCallback callback; callback = std::bind (&JournalWindowImpl::notifyTopicClicked, this, std::placeholders::_1); getPage (LeftBookPage)->adviseLinkClicked (callback); getPage (RightBookPage)->adviseLinkClicked (callback); getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { MWGui::BookPage::ClickCallback callback; callback = std::bind(&JournalWindowImpl::notifyIndexLinkClicked, this, std::placeholders::_1); getPage (LeftTopicIndex)->adviseLinkClicked (callback); getPage (CenterTopicIndex)->adviseLinkClicked (callback); getPage (RightTopicIndex)->adviseLinkClicked (callback); } adjustButton(PrevPageBTN); float nextButtonScale = adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); adjustButton(JournalBTN); Gui::ImageButton* optionsButton = getWidget(OptionsBTN); Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); Gui::ImageButton* questsButton = getWidget(QuestsBTN); Gui::ImageButton* nextButton = getWidget(NextPageBTN); if (nextButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge nextButton->setSize(64-7, nextButton->getSize().height); nextButton->setImageCoord(MyGUI::IntCoord(0,0,(64-7)*nextButtonScale,nextButton->getSize().height*nextButtonScale)); } if (!questList) { // If tribunal is not installed (-> no options button), we still want the Topics button available, // so place it where the options button would have been Gui::ImageButton* topicsButton = getWidget(TopicsBTN); topicsButton->detachFromWidget(); topicsButton->attachToWidget(optionsButton->getParent()); topicsButton->setPosition(optionsButton->getPosition()); topicsButton->eventMouseButtonClick.clear(); topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); optionsButton->setVisible(false); showActiveButton->setVisible(false); showAllButton->setVisible(false); questsButton->setVisible(false); adjustButton(TopicsBTN); } else { optionsButton->setImage("textures/tx_menubook_options.dds"); showActiveButton->setImage("textures/tx_menubook_quests_active.dds"); showAllButton->setImage("textures/tx_menubook_quests_all.dds"); questsButton->setImage("textures/tx_menubook_quests.dds"); adjustButton(ShowAllBTN); adjustButton(ShowActiveBTN); adjustButton(OptionsBTN); adjustButton(QuestsBTN); adjustButton(TopicsBTN); int topicsWidth = getWidget(TopicsBTN)->getSize().width; int cancelLeft = getWidget(CancelBTN)->getPosition().left; int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); // Usually Topics, Quests, and Cancel buttons have the 64px width, so we can place the Topics left-up from the Cancel button, and the Quests right-up from the Cancel button. // But in some installations, e.g. German one, the Topics button has the 128px width, so we should place it exactly left from the Quests button. if (topicsWidth == 64) { getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); } else { int questLeft = getWidget(QuestsBTN)->getPosition().left; getWidget(TopicsBTN)->setPosition(questLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); } } mQuestMode = false; mAllQuests = false; mOptionsMode = false; mTopicsMode = false; } void onOpen() override { if (!MWBase::Environment::get().getWindowManager ()->getJournalAllowed ()) { MWBase::Environment::get().getWindowManager()->popGuiMode (); } mModel->load (); setBookMode (); Book journalBook; if (mModel->isEmpty ()) journalBook = createEmptyJournalBook (); else journalBook = createJournalBook (); pushBook (journalBook, 0); // fast forward to the last page if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; page = mStates.top().mBook->pageCount()-1; if (page%2) --page; } updateShowingPages(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(getWidget(CloseBTN)); } void onClose() override { mModel->unload (); getPage (LeftBookPage)->showPage (Book (), 0); getPage (RightBookPage)->showPage (Book (), 0); while (!mStates.empty ()) mStates.pop (); mTopicIndexBook.reset (); } void setVisible (bool newValue) override { WindowBase::setVisible (newValue); } void setBookMode () { mOptionsMode = false; mTopicsMode = false; setVisible (OptionsBTN, true); setVisible (OptionsOverlay, false); updateShowingPages (); updateCloseJournalButton (); } void setOptionsMode () { mOptionsMode = true; mTopicsMode = false; setVisible (OptionsBTN, false); setVisible (OptionsOverlay, true); setVisible (PrevPageBTN, false); setVisible (NextPageBTN, false); setVisible (CloseBTN, false); setVisible (JournalBTN, false); setVisible (TopicsList, false); setVisible (QuestsList, mQuestMode); setVisible (LeftTopicIndex, !mQuestMode); setVisible (CenterTopicIndex, !mQuestMode); setVisible (RightTopicIndex, !mQuestMode); setVisible (ShowAllBTN, mQuestMode && !mAllQuests); setVisible (ShowActiveBTN, mQuestMode && mAllQuests); //TODO: figure out how to make "options" page overlay book page // correctly, so that text may show underneath getPage (RightBookPage)->showPage (Book (), 0); // If in quest mode, ensure the quest list is updated if (mQuestMode) notifyQuests(getWidget(QuestsList)); else notifyTopics(getWidget(TopicsList)); } void pushBook (Book book, unsigned int page) { DisplayState bs; bs.mPage = page; bs.mBook = book; mStates.push (bs); updateShowingPages (); updateCloseJournalButton (); } void replaceBook (Book book, unsigned int page) { assert (!mStates.empty ()); mStates.top ().mBook = book; mStates.top ().mPage = page; updateShowingPages (); } void popBook () { mStates.pop (); updateShowingPages (); updateCloseJournalButton (); } void updateCloseJournalButton () { setVisible (CloseBTN, mStates.size () < 2); setVisible (JournalBTN, mStates.size () >= 2); } void updateShowingPages () { Book book; unsigned int page; unsigned int relPages; if (!mStates.empty ()) { book = mStates.top ().mBook; page = mStates.top ().mPage; relPages = book->pageCount () - page; } else { page = 0; relPages = 0; } MyGUI::Widget* nextPageBtn = getWidget(NextPageBTN); MyGUI::Widget* prevPageBtn = getWidget(PrevPageBTN); MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool nextPageVisible = relPages > 2; nextPageBtn->setVisible(nextPageVisible); bool prevPageVisible = page > 0; prevPageBtn->setVisible(prevPageVisible); if (focus == nextPageBtn && !nextPageVisible && prevPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(prevPageBtn); else if (focus == prevPageBtn && !prevPageVisible && nextPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nextPageBtn); setVisible (PageOneNum, relPages > 0); setVisible (PageTwoNum, relPages > 1); getPage (LeftBookPage)->showPage ((relPages > 0) ? book : Book (), page+0); getPage (RightBookPage)->showPage ((relPages > 0) ? book : Book (), page+1); setText (PageOneNum, page + 1); setText (PageTwoNum, page + 2); } void notifyKeyPress(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) notifyPrevPage(sender); else if (key == MyGUI::KeyCode::ArrowDown) notifyNextPage(sender); } void notifyTopicClicked (intptr_t linkId) { Book topicBook = createTopicBook (linkId); if (mStates.size () > 1) replaceBook (topicBook, 0); else pushBook (topicBook, 0); setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); mOptionsMode = false; mTopicsMode = false; MWBase::Environment::get().getWindowManager()->playSound("book page"); } void notifyTopicSelected (const std::string& topic, int id) { const MWBase::Journal* journal = MWBase::Environment::get().getJournal(); intptr_t topicId = 0; /// \todo get rid of intptr ids for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i) { if (Misc::StringUtils::ciEqual(i->first, topic)) topicId = intptr_t (&i->second); } notifyTopicClicked(topicId); } void notifyQuestClicked (const std::string& name, int id) { Book book = createQuestBook (name); if (mStates.size () > 1) replaceBook (book, 0); else pushBook (book, 0); setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); mOptionsMode = false; MWBase::Environment::get().getWindowManager()->playSound("book page"); } void notifyOptions(MyGUI::Widget* _sender) { setOptionsMode (); if (!mTopicIndexBook) mTopicIndexBook = createTopicIndexBook (); if (mIndexPagesCount == 3) { getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); getPage (CenterTopicIndex)->showPage (mTopicIndexBook, 1); getPage (RightTopicIndex)->showPage (mTopicIndexBook, 2); } else { getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); getPage (RightTopicIndex)->showPage (mTopicIndexBook, 1); } } void notifyJournal(MyGUI::Widget* _sender) { assert (mStates.size () > 1); popBook (); MWBase::Environment::get().getWindowManager()->playSound("book page"); } void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index) { setVisible (LeftTopicIndex, false); setVisible (CenterTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, true); mTopicsMode = true; Gui::MWList* list = getWidget(TopicsList); list->clear(); AddNamesToList add(list); mModel->visitTopicNamesStartingWith(index, add); list->adjustSize(); MWBase::Environment::get().getWindowManager()->playSound("book page"); } void notifyTopics(MyGUI::Widget* _sender) { mQuestMode = false; mTopicsMode = false; setVisible (LeftTopicIndex, true); setVisible (CenterTopicIndex, true); setVisible (RightTopicIndex, true); setVisible (TopicsList, false); setVisible (QuestsList, false); setVisible (ShowAllBTN, false); setVisible (ShowActiveBTN, false); MWBase::Environment::get().getWindowManager()->playSound("book page"); } struct AddNamesToList { AddNamesToList(Gui::MWList* list) : mList(list) {} Gui::MWList* mList; void operator () (const std::string& name, bool finished=false) { mList->addItem(name); } }; struct SetNamesInactive { SetNamesInactive(Gui::MWList* list) : mList(list) {} Gui::MWList* mList; void operator () (const std::string& name, bool finished) { if (finished) { mList->getItemWidget(name)->setStateSelected(true); } } }; void notifyQuests(MyGUI::Widget* _sender) { mQuestMode = true; setVisible (LeftTopicIndex, false); setVisible (CenterTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, false); setVisible (QuestsList, true); setVisible (ShowAllBTN, !mAllQuests); setVisible (ShowActiveBTN, mAllQuests); Gui::MWList* list = getWidget(QuestsList); list->clear(); AddNamesToList add(list); mModel->visitQuestNames(!mAllQuests, add); list->adjustSize(); if (mAllQuests) { SetNamesInactive setInactive(list); mModel->visitQuestNames(false, setInactive); } MWBase::Environment::get().getWindowManager()->playSound("book page"); } void notifyShowAll(MyGUI::Widget* _sender) { mAllQuests = true; notifyQuests(_sender); } void notifyShowActive(MyGUI::Widget* _sender) { mAllQuests = false; notifyQuests(_sender); } void notifyCancel(MyGUI::Widget* _sender) { if (mTopicsMode) { notifyTopics(_sender); } else { setBookMode(); MWBase::Environment::get().getWindowManager()->playSound("book page"); } } void notifyClose(MyGUI::Widget* _sender) { MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); winMgr->playSound("book close"); winMgr->popGuiMode(); } void notifyMouseWheel(MyGUI::Widget* sender, int rel) { if (rel < 0) notifyNextPage(sender); else notifyPrevPage(sender); } void notifyNextPage(MyGUI::Widget* _sender) { if (mOptionsMode) return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; Book book = mStates.top ().mBook; if (page+2 < book->pageCount()) { MWBase::Environment::get().getWindowManager()->playSound("book page"); page += 2; updateShowingPages (); } } } void notifyPrevPage(MyGUI::Widget* _sender) { if (mOptionsMode) return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; if(page >= 2) { MWBase::Environment::get().getWindowManager()->playSound("book page"); page -= 2; updateShowingPages (); } } } }; } // glue the implementation to the interface MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) { return new JournalWindowImpl (Model, questList, encoding); } MWGui::JournalWindow::JournalWindow() : BookWindowBase("openmw_journal.layout") { } openmw-openmw-0.48.0/apps/openmw/mwgui/journalwindow.hpp000066400000000000000000000013351445372753700234510ustar00rootroot00000000000000#ifndef MWGUI_JOURNAL_H #define MWGUI_JOURNAL_H #include "windowbase.hpp" #include #include namespace MWBase { class WindowManager; } namespace MWGui { struct JournalViewModel; struct JournalWindow : public BookWindowBase { JournalWindow(); /// construct a new instance of the one JournalWindow implementation static JournalWindow * create (std::shared_ptr Model, bool questList, ToUTF8::FromType encoding); /// destroy this instance of the JournalWindow implementation virtual ~JournalWindow () {} /// show/hide the journal window void setVisible (bool newValue) override = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/keyboardnavigation.cpp000066400000000000000000000205261445372753700244250ustar00rootroot00000000000000#include "keyboardnavigation.hpp" #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" namespace MWGui { bool shouldAcceptKeyFocus(MyGUI::Widget* w) { return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); } /// Recursively get all child widgets that accept keyboard input void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& results) { assert(parent != nullptr); if (!parent->getVisible() || !parent->getEnabled()) return; MyGUI::EnumeratorWidgetPtr enumerator = parent->getEnumerator(); while (enumerator.next()) { MyGUI::Widget* w = enumerator.current(); if (!w->getVisible() || !w->getEnabled()) continue; if (w->getNeedKeyFocus() && shouldAcceptKeyFocus(w)) results.push_back(w); else getKeyFocusWidgets(w, results); } } KeyboardNavigation::KeyboardNavigation() : mCurrentFocus(nullptr) , mModalWindow(nullptr) , mEnabled(true) { MyGUI::WidgetManager::getInstance().registerUnlinker(this); } KeyboardNavigation::~KeyboardNavigation() { try { MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } void KeyboardNavigation::saveFocus(int mode) { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (shouldAcceptKeyFocus(focus)) { mKeyFocus[mode] = focus; } else if(shouldAcceptKeyFocus(mCurrentFocus)) { mKeyFocus[mode] = mCurrentFocus; } } void KeyboardNavigation::restoreFocus(int mode) { std::map::const_iterator found = mKeyFocus.find(mode); if (found != mKeyFocus.end()) { MyGUI::Widget* w = found->second; if (w && w->getVisible() && w->getEnabled() && w->getInheritedVisible() && w->getInheritedEnabled()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); } } void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget) { for (std::pair& w : mKeyFocus) if (w.second == widget) w.second = nullptr; if (widget == mCurrentFocus) mCurrentFocus = nullptr; } bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) { while (widget && widget->getParent()) widget = widget->getParent(); return widget == root; } void KeyboardNavigation::onFrame() { if (!mEnabled) return; if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); return; } MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (focus == mCurrentFocus) { return; } // workaround incorrect key focus resets (fix in MyGUI TBD) if (!shouldAcceptKeyFocus(focus) && shouldAcceptKeyFocus(mCurrentFocus) && (!mModalWindow || isRootParent(mCurrentFocus, mModalWindow))) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCurrentFocus); focus = mCurrentFocus; } if (focus != mCurrentFocus) { mCurrentFocus = focus; } } void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (!focus || !shouldAcceptKeyFocus(focus)) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); } else { if (!isRootParent(focus, window)) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); } } void KeyboardNavigation::setModalWindow(MyGUI::Widget *window) { mModalWindow = window; } void KeyboardNavigation::setEnabled(bool enabled) { mEnabled = enabled; } enum Direction { D_Left, D_Up, D_Right, D_Down, D_Next, D_Prev }; bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) { if (!mEnabled) return false; switch (key.getValue()) { case MyGUI::KeyCode::ArrowLeft: return switchFocus(D_Left, false); case MyGUI::KeyCode::ArrowRight: return switchFocus(D_Right, false); case MyGUI::KeyCode::ArrowUp: return switchFocus(D_Up, false); case MyGUI::KeyCode::ArrowDown: return switchFocus(D_Down, false); case MyGUI::KeyCode::Tab: return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); case MyGUI::KeyCode::Return: case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::Space: { // We should disable repeating for activation keys MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::None); if (repeat) return true; return accept(); } default: return false; } } bool KeyboardNavigation::switchFocus(int direction, bool wrap) { if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) return false; MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool isCycle = (direction == D_Prev || direction == D_Next); if ((focus && focus->getTypeName().find("Button") == std::string::npos) && !isCycle) return false; if (focus && isCycle && focus->getUserString("AcceptTab") == "true") return false; if ((!focus || !focus->getNeedKeyFocus()) && isCycle) { // if nothing is selected, select the first widget return selectFirstWidget(); } if (!focus) return false; MyGUI::Widget* window = focus; while (window && window->getParent()) window = window->getParent(); MyGUI::VectorWidgetPtr keyFocusList; getKeyFocusWidgets(window, keyFocusList); if (keyFocusList.empty()) return false; MyGUI::VectorWidgetPtr::iterator found = std::find(keyFocusList.begin(), keyFocusList.end(), focus); if (found == keyFocusList.end()) { if (isCycle) return selectFirstWidget(); else return false; } bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); int index = found - keyFocusList.begin(); index = forward ? (index+1) : (index-1); if (wrap) index = (index + keyFocusList.size())%keyFocusList.size(); else index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); int horizdiff = next->getLeft() - focus->getLeft(); bool isVertical = std::abs(vertdiff) > std::abs(horizdiff); if (direction == D_Right && (horizdiff <= 0 || isVertical)) return false; else if (direction == D_Left && (horizdiff >= 0 || isVertical)) return false; else if (direction == D_Down && (vertdiff <= 0 || !isVertical)) return false; else if (direction == D_Up && (vertdiff >= 0 || !isVertical)) return false; MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); return true; } bool KeyboardNavigation::selectFirstWidget() { MyGUI::VectorWidgetPtr keyFocusList; MyGUI::EnumeratorWidgetPtr enumerator = MyGUI::Gui::getInstance().getEnumerator(); if (mModalWindow) enumerator = mModalWindow->getEnumerator(); while (enumerator.next()) getKeyFocusWidgets(enumerator.current(), keyFocusList); if (!keyFocusList.empty()) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[0]); return true; } return false; } bool KeyboardNavigation::accept() { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (!focus) return false; //MyGUI::Button* button = focus->castType(false); //if (button && button->getEnabled()) if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled()) { focus->eventMouseButtonClick(focus); return true; } return false; } } openmw-openmw-0.48.0/apps/openmw/mwgui/keyboardnavigation.hpp000066400000000000000000000022571445372753700244330ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_KEYBOARDNAVIGATION_H #define OPENMW_MWGUI_KEYBOARDNAVIGATION_H #include #include namespace MWGui { class KeyboardNavigation : public MyGUI::IUnlinkWidget { public: KeyboardNavigation(); ~KeyboardNavigation(); /// @return Was the key handled by this class? bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat); void saveFocus(int mode); void restoreFocus(int mode); void _unlinkWidget(MyGUI::Widget* widget) override; void onFrame(); /// Set a key focus widget for this window, if one isn't already set. void setDefaultFocus(MyGUI::Widget* window, MyGUI::Widget* defaultFocus); void setModalWindow(MyGUI::Widget* window); void setEnabled(bool enabled); private: bool switchFocus(int direction, bool wrap); bool selectFirstWidget(); /// Send button press event to focused button bool accept(); std::map mKeyFocus; MyGUI::Widget* mCurrentFocus; MyGUI::Widget* mModalWindow; bool mEnabled; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/layout.cpp000066400000000000000000000042431445372753700220600ustar00rootroot00000000000000#include "layout.hpp" #include #include #include #include #include namespace MWGui { void Layout::initialise(std::string_view _layout) { const auto MAIN_WINDOW = "_Main"; mLayoutName = _layout; mPrefix = MyGUI::utility::toString(this, "_"); mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix); const std::string main_name = mPrefix + MAIN_WINDOW; for (MyGUI::Widget* widget : mListWindowRoot) { if (widget->getName() == main_name) mMainWidget = widget; // Force the alignment to update immediately widget->_setAlign(widget->getSize(), widget->getParentSize()); } MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); } void Layout::shutdown() { setVisible(false); MyGUI::Gui::getInstance().destroyWidget(mMainWidget); mListWindowRoot.clear(); } void Layout::setCoord(int x, int y, int w, int h) { mMainWidget->setCoord(x,y,w,h); } void Layout::setVisible(bool b) { mMainWidget->setVisible(b); } void Layout::setText(std::string_view name, const std::string &caption) { MyGUI::Widget* pt; getWidget(pt, name); static_cast(pt)->setCaption(caption); } void Layout::setTitle(const std::string& title) { MyGUI::Window* window = static_cast(mMainWidget); if (window->getCaption() != title) window->setCaptionWithReplacing(title); } MyGUI::Widget* Layout::getWidget(std::string_view _name) { std::string target = mPrefix; target += _name; for (MyGUI::Widget* widget : mListWindowRoot) { MyGUI::Widget* find = widget->findWidget(target); if (nullptr != find) { return find; } } MYGUI_EXCEPT("widget name '" << _name << "' in layout '" << mLayoutName << "' not found."); } } openmw-openmw-0.48.0/apps/openmw/mwgui/layout.hpp000066400000000000000000000033711445372753700220660ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_LAYOUT_H #define OPENMW_MWGUI_LAYOUT_H #include #include #include #include namespace MWGui { /** The Layout class is an utility class used to load MyGUI layouts from xml files, and to manipulate member widgets. */ class Layout { public: Layout(std::string_view layout) : mMainWidget(nullptr) { initialise(layout); assert(mMainWidget); } virtual ~Layout() { try { shutdown(); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } MyGUI::Widget* getWidget(std::string_view name); template void getWidget(T * & _widget, std::string_view _name) { MyGUI::Widget* w = getWidget(_name); T* cast = w->castType(false); if (!cast) { MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() << "' source name = '" << w->getName() << "' source type = '" << w->getTypeName() << "' in layout '" << mLayoutName << "'"); } else _widget = cast; } private: void initialise(std::string_view layout); void shutdown(); public: void setCoord(int x, int y, int w, int h); virtual void setVisible(bool b); void setText(std::string_view name, const std::string& caption); // NOTE: this assume that mMainWidget is of type Window. void setTitle(const std::string& title); MyGUI::Widget* mMainWidget; protected: std::string mPrefix; std::string mLayoutName; MyGUI::VectorWidgetPtr mListWindowRoot; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/levelupdialog.cpp000066400000000000000000000252621445372753700234030ustar00rootroot00000000000000#include "levelupdialog.hpp" #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "class.hpp" namespace MWGui { const unsigned int LevelupDialog::sMaxCoins = 3; LevelupDialog::LevelupDialog() : WindowBase("openmw_levelup_dialog.layout"), mCoinCount(sMaxCoins) { getWidget(mOkButton, "OkButton"); getWidget(mClassImage, "ClassImage"); getWidget(mLevelText, "LevelText"); getWidget(mLevelDescription, "LevelDescription"); getWidget(mCoinBox, "Coins"); getWidget(mAssignWidget, "AssignWidget"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onOkButtonClicked); for (int i=1; i<9; ++i) { MyGUI::TextBox* t; getWidget(t, "AttribVal" + MyGUI::utility::toString(i)); mAttributeValues.push_back(t); MyGUI::Button* b; getWidget(b, "Attrib" + MyGUI::utility::toString(i)); b->setUserData (i-1); b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked); mAttributes.push_back(b); getWidget(t, "AttribMultiplier" + MyGUI::utility::toString(i)); mAttributeMultipliers.push_back(t); } for (unsigned int i = 0; i < mCoinCount; ++i) { MyGUI::ImageBox* image = mCoinBox->createWidget("ImageBox", MyGUI::IntCoord(0,0,16,16), MyGUI::Align::Default); image->setImageTexture ("icons\\tx_goldicon.dds"); mCoins.push_back(image); } center(); } void LevelupDialog::setAttributeValues() { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); for (int i = 0; i < 8; ++i) { int val = creatureStats.getAttribute(i).getBase(); if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), i) != mSpentAttributes.end()) { val += pcStats.getLevelupAttributeMultiplier(i); } if (val >= 100) val = 100; mAttributeValues[i]->setCaption(MyGUI::utility::toString(val)); } } void LevelupDialog::resetCoins() { const int coinSpacing = 33; int curX = mCoinBox->getWidth()/2 - (coinSpacing*(mCoinCount - 1) + 16*mCoinCount)/2; for (unsigned int i=0; idetachFromWidget(); image->attachToWidget(mCoinBox); if (i < mCoinCount) { mCoins[i]->setVisible(true); image->setCoord(MyGUI::IntCoord(curX,0,16,16)); curX += 16+coinSpacing; } else mCoins[i]->setVisible(false); } } void LevelupDialog::assignCoins() { resetCoins(); for (unsigned int i=0; idetachFromWidget(); image->attachToWidget(mAssignWidget); int attribute = mSpentAttributes[i]; int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 20; MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mAssignWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); pos.top += (mAttributes[attribute]->getHeight() - image->getHeight())/2; image->setPosition(pos); } setAttributeValues(); } void LevelupDialog::onOpen() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); setClassImage(mClassImage, getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2))); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); std::string levelupdescription; levelupdescription = Fallback::Map::getString("Level_Up_Level"+MyGUI::utility::toString(level)); if (levelupdescription == "") levelupdescription = Fallback::Map::getString("Level_Up_Default"); mLevelDescription->setCaption (levelupdescription); unsigned int availableAttributes = 0; for (int i = 0; i < 8; ++i) { MyGUI::TextBox* text = mAttributeMultipliers[i]; if (pcStats.getAttribute(i).getBase() < 100) { mAttributes[i]->setEnabled(true); mAttributeValues[i]->setEnabled(true); availableAttributes++; float mult = pcStats.getLevelupAttributeMultiplier (i); mult = std::min(mult, 100-pcStats.getAttribute(i).getBase()); text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult)); } else { mAttributes[i]->setEnabled(false); mAttributeValues[i]->setEnabled(false); text->setCaption(""); } } mCoinCount = std::min(sMaxCoins, availableAttributes); mSpentAttributes.clear(); resetCoins(); setAttributeValues(); center(); // Play LevelUp Music MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3"); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); if (mSpentAttributes.size() < mCoinCount) MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage36}"); else { // increase attributes for (unsigned int i = 0; i < mCoinCount; ++i) { MWMechanics::AttributeValue attribute = pcStats.getAttribute(mSpentAttributes[i]); attribute.setBase(attribute.getBase() + pcStats.getLevelupAttributeMultiplier(mSpentAttributes[i])); if (attribute.getBase() >= 100) attribute.setBase(100); pcStats.setAttribute(mSpentAttributes[i], attribute); } pcStats.levelUp(); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Levelup); } } void LevelupDialog::onAttributeClicked(MyGUI::Widget *sender) { int attribute = *sender->getUserData(); std::vector::iterator found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute); if (found != mSpentAttributes.end()) mSpentAttributes.erase(found); else { if (mSpentAttributes.size() == mCoinCount) mSpentAttributes[mCoinCount - 1] = attribute; else mSpentAttributes.push_back(attribute); } assignCoins(); } std::string LevelupDialog::getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases) { std::string ret = "acrobat"; int total = combatIncreases + magicIncreases + stealthIncreases; if (total == 0) return ret; int combatFraction = static_cast(static_cast(combatIncreases) / total * 10.f); int magicFraction = static_cast(static_cast(magicIncreases) / total * 10.f); int stealthFraction = static_cast(static_cast(stealthIncreases) / total * 10.f); if (combatFraction > 7) ret = "warrior"; else if (magicFraction > 7) ret = "mage"; else if (stealthFraction > 7) ret = "thief"; switch (combatFraction) { case 7: ret = "warrior"; break; case 6: if (stealthFraction == 1) ret = "barbarian"; else if (stealthFraction == 3) ret = "crusader"; else ret = "knight"; break; case 5: if (stealthFraction == 3) ret = "scout"; else ret = "archer"; break; case 4: ret = "rogue"; break; default: break; } switch (magicFraction) { case 7: ret = "mage"; break; case 6: if (combatFraction == 2) ret = "sorcerer"; else if (combatIncreases == 3) ret = "healer"; else ret = "battlemage"; break; case 5: ret = "witchhunter"; break; case 4: ret = "spellsword"; // In vanilla there's also code for "nightblade", however it seems to be unreachable. break; default: break; } switch (stealthFraction) { case 7: ret = "thief"; break; case 6: if (magicFraction == 1) ret = "agent"; else if (magicIncreases == 3) ret = "assassin"; else ret = "acrobat"; break; case 5: if (magicIncreases == 3) ret = "monk"; else ret = "pilgrim"; break; case 3: if (magicFraction == 3) ret = "bard"; break; default: break; } return ret; } } openmw-openmw-0.48.0/apps/openmw/mwgui/levelupdialog.hpp000066400000000000000000000021701445372753700234010ustar00rootroot00000000000000#ifndef MWGUI_LEVELUPDIALOG_H #define MWGUI_LEVELUPDIALOG_H #include "windowbase.hpp" namespace MWGui { class LevelupDialog : public WindowBase { public: LevelupDialog(); void onOpen() override; private: MyGUI::Button* mOkButton; MyGUI::ImageBox* mClassImage; MyGUI::TextBox* mLevelText; MyGUI::EditBox* mLevelDescription; MyGUI::Widget* mCoinBox; MyGUI::Widget* mAssignWidget; std::vector mAttributes; std::vector mAttributeValues; std::vector mAttributeMultipliers; std::vector mCoins; std::vector mSpentAttributes; unsigned int mCoinCount; static const unsigned int sMaxCoins; void onOkButtonClicked(MyGUI::Widget* sender); void onAttributeClicked(MyGUI::Widget* sender); void assignCoins(); void resetCoins(); void setAttributeValues(); std::string getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/loadingscreen.cpp000066400000000000000000000312421445372753700233570ustar00rootroot00000000000000#include "loadingscreen.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "backgroundimage.hpp" namespace MWGui { LoadingScreen::LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer) : WindowBase("openmw_loading_screen.layout") , mResourceSystem(resourceSystem) , mViewer(viewer) , mTargetFrameRate(120.0) , mLastWallpaperChangeTime(0.0) , mLastRenderTime(0.0) , mLoadingOnTime(0.0) , mImportantLabel(false) , mVisible(false) , mNestedLoadingCount(0) , mProgress(0) , mShowWallpaper(true) { getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); getWidget(mLoadingBox, "LoadingBox"); getWidget(mSceneImage, "Scene"); getWidget(mSplashImage, "Splash"); mProgressBar->setScrollViewPage(1); findSplashScreens(); } LoadingScreen::~LoadingScreen() { } void LoadingScreen::findSplashScreens() { auto isSupportedExtension = [](const std::string_view& ext) { static const std::array supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} }; return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end(); }; for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { if (isSupportedExtension(Misc::getFileExtension(name))) mSplashScreens.push_back(name); } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; } void LoadingScreen::setLabel(const std::string &label, bool important) { mImportantLabel = important; mLoadingText->setCaptionWithReplacing(label); int padding = mLoadingBox->getWidth() - mLoadingText->getWidth(); MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); size.width = std::max(300, size.width); mLoadingBox->setSize(size); if (MWBase::Environment::get().getWindowManager()->getMessagesCount() > 0) mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); else mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); } void LoadingScreen::setVisible(bool visible) { WindowBase::setVisible(visible); mSplashImage->setVisible(visible); mSceneImage->setVisible(visible); } double LoadingScreen::getTargetFrameRate() const { double frameRateLimit = MWBase::Environment::get().getFrameRateLimit(); if (frameRateLimit > 0) return std::min(frameRateLimit, mTargetFrameRate); else return mTargetFrameRate; } class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback { public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) : mOneshot(true) , mTexture(texture) { } void operator () (osg::RenderInfo& renderInfo) const override { int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); mOneshot = false; } void reset() { mOneshot = true; } private: mutable bool mOneshot; osg::ref_ptr mTexture; }; class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback { public: osg::BoundingSphere computeBound(const osg::Node&) const override { return osg::BoundingSphere(); } }; void LoadingScreen::loadingOn(bool visible) { // Early-out if already on if (mNestedLoadingCount++ > 0 && mMainWidget->getVisible()) return; mLoadingOnTime = mTimer.time_m(); // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { mOldIcoMin = ico->getMinimumTimeAvailableForGLCompileAndDeletePerFrame(); mOldIcoMax = ico->getMaximumNumOfObjectsToCompilePerFrame(); } mVisible = visible; mLoadingBox->setVisible(mVisible); setVisible(true); if (!mVisible) { mShowWallpaper = false; draw(); return; } mShowWallpaper = MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; if (mShowWallpaper) { changeWallpaper(); } MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); } void LoadingScreen::loadingOff() { if (--mNestedLoadingCount > 0) return; mLoadingBox->setVisible(true); // restore if (mLastRenderTime < mLoadingOnTime) { // the loading was so fast that we didn't show loading screen at all // we may still want to show the label if the caller requested it if (mImportantLabel) { MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption()); mImportantLabel = false; } } else mImportantLabel = false; // label was already shown on loading screen mViewer->getSceneData()->setComputeBoundingSphereCallback(nullptr); mViewer->getSceneData()->dirtyBound(); setVisible(false); if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(mOldIcoMin); ico->setMaximumNumOfObjectsToCompilePerFrame(mOldIcoMax); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); } void LoadingScreen::changeWallpaper () { if (!mSplashScreens.empty()) { std::string const & randomSplash = mSplashScreens.at(Misc::Rng::rollDice(mSplashScreens.size())); // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); mSplashImage->setVisible(true); mSplashImage->setBackgroundImage(randomSplash, true, stretch); } mSceneImage->setBackgroundImage(""); mSceneImage->setVisible(false); } void LoadingScreen::setProgressRange (size_t range) { mProgressBar->setScrollRange(range+1); mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(0); mProgress = 0; } void LoadingScreen::setProgress (size_t value) { // skip expensive update if there isn't enough visible progress if (mProgressBar->getWidth() <= 0 || value - mProgress < mProgressBar->getScrollRange()/mProgressBar->getWidth()) return; value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } void LoadingScreen::increaseProgress (size_t increase) { mProgressBar->setScrollPosition(0); size_t value = mProgress + increase; value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } bool LoadingScreen::needToDrawLoadingScreen() { if ( mTimer.time_m() <= mLastRenderTime + (1.0/getTargetFrameRate()) * 1000.0) return false; // the minimal delay before a loading screen shows const float initialDelay = 0.05; bool alreadyShown = (mLastRenderTime > mLoadingOnTime); float diff = (mTimer.time_m() - mLoadingOnTime); if (!alreadyShown) { // bump the delay by the current progress - i.e. if during the initial delay the loading // has almost finished, no point showing the loading screen now diff -= mProgress / static_cast(mProgressBar->getScrollRange()) * 100.f; } if (!mShowWallpaper && diff < initialDelay*1000) return false; return true; } void LoadingScreen::setupCopyFramebufferToTextureCallback() { // Copy the current framebuffer onto a texture and display that texture as the background image // Note, we could also set the camera to disable clearing and have the background image transparent, // but then we get shaking effects on buffer swaps. if (!mTexture) { mTexture = new osg::Texture2D; mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mTexture->setInternalFormat(GL_RGB); mTexture->setResizeNonPowerOfTwoHint(false); } if (!mGuiTexture.get()) { mGuiTexture = std::make_unique(mTexture); } if (!mCopyFramebufferToTextureCallback) { mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); } mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); mCopyFramebufferToTextureCallback->reset(); mSplashImage->setBackgroundImage(""); mSplashImage->setVisible(false); mSceneImage->setRenderItemTexture(mGuiTexture.get()); mSceneImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mSceneImage->setVisible(true); } void LoadingScreen::draw() { if (mVisible && !needToDrawLoadingScreen()) return; if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) { mLastWallpaperChangeTime = mTimer.time_m(); changeWallpaper(); } if (!mShowWallpaper && mLastRenderTime < mLoadingOnTime) { setupCopyFramebufferToTextureCallback(); } MWBase::Environment::get().getInputManager()->update(0, true, true); mResourceSystem->reportStats(mViewer->getFrameStamp()->getFrameNumber(), mViewer->getViewerStats()); if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f/getTargetFrameRate()); ico->setMaximumNumOfObjectsToCompilePerFrame(1000); } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mLastRenderTime = mTimer.time_m(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/loadingscreen.hpp000066400000000000000000000044041445372753700233640ustar00rootroot00000000000000#ifndef MWGUI_LOADINGSCREEN_H #define MWGUI_LOADINGSCREEN_H #include #include #include #include "windowbase.hpp" #include namespace osgViewer { class Viewer; } namespace osg { class Texture2D; } namespace Resource { class ResourceSystem; } namespace MWGui { class BackgroundImage; class CopyFramebufferToTextureCallback; class LoadingScreen : public WindowBase, public Loading::Listener { public: LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer); virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details void setLabel (const std::string& label, bool important) override; void loadingOn(bool visible=true) override; void loadingOff() override; void setProgressRange (size_t range) override; void setProgress (size_t value) override; void increaseProgress (size_t increase=1) override; void setVisible(bool visible) override; double getTargetFrameRate() const; private: void findSplashScreens(); bool needToDrawLoadingScreen(); void setupCopyFramebufferToTextureCallback(); Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mViewer; double mTargetFrameRate; double mLastWallpaperChangeTime; double mLastRenderTime; osg::Timer mTimer; double mLoadingOnTime; bool mImportantLabel; bool mVisible; int mNestedLoadingCount; size_t mProgress; bool mShowWallpaper; float mOldIcoMin = 0.f; unsigned int mOldIcoMax = 0; MyGUI::Widget* mLoadingBox; MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; BackgroundImage* mSplashImage; BackgroundImage* mSceneImage; std::vector mSplashScreens; osg::ref_ptr mTexture; osg::ref_ptr mCopyFramebufferToTextureCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); void draw(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/mainmenu.cpp000066400000000000000000000254031445372753700223550ustar00rootroot00000000000000#include "mainmenu.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/statemanager.hpp" #include "savegamedialog.hpp" #include "confirmationdialog.hpp" #include "backgroundimage.hpp" #include "videowidget.hpp" namespace MWGui { MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) : WindowBase("openmw_mainmenu.layout") , mWidth (w), mHeight (h) , mVFS(vfs), mButtonBox(nullptr) , mBackground(nullptr) , mVideoBackground(nullptr) , mVideo(nullptr) , mSaveGameDialog(nullptr) { getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); updateMenu(); } MainMenu::~MainMenu() { delete mSaveGameDialog; } void MainMenu::onResChange(int w, int h) { mWidth = w; mHeight = h; updateMenu(); } void MainMenu::setVisible (bool visible) { if (visible) updateMenu(); bool isMainMenu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; showBackground(isMainMenu); if (visible) { if (isMainMenu) { if (mButtons["loadgame"]->getVisible()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["loadgame"]); else MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["newgame"]); } else MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["return"]); } Layout::setVisible (visible); } void MainMenu::onNewGameConfirmed() { MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); MWBase::Environment::get().getStateManager()->newGame(); } void MainMenu::onExitConfirmed() { MWBase::Environment::get().getStateManager()->requestQuit(); } void MainMenu::onButtonClicked(MyGUI::Widget *sender) { MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); std::string name = *sender->getUserData(); winMgr->playSound("Menu Click"); if (name == "return") { winMgr->removeGuiMode (GM_MainMenu); } else if (name == "options") winMgr->pushGuiMode (GM_Settings); else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); else if (name == "exitgame") { if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) onExitConfirmed(); else { ConfirmationDialog* dialog = winMgr->getConfirmationDialog(); dialog->askForConfirmation("#{sMessage2}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onExitConfirmed); dialog->eventCancelClicked.clear(); } } else if (name == "newgame") { if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) onNewGameConfirmed(); else { ConfirmationDialog* dialog = winMgr->getConfirmationDialog(); dialog->askForConfirmation("#{sNotifyMessage54}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onNewGameConfirmed); dialog->eventCancelClicked.clear(); } } else { if (!mSaveGameDialog) mSaveGameDialog = new SaveGameDialog(); if (name == "loadgame") mSaveGameDialog->setLoadOrSave(true); else if (name == "savegame") mSaveGameDialog->setLoadOrSave(false); mSaveGameDialog->setVisible(true); } } void MainMenu::showBackground(bool show) { if (mVideo && !show) { MyGUI::Gui::getInstance().destroyWidget(mVideoBackground); mVideoBackground = nullptr; mVideo = nullptr; } if (mBackground && !show) { MyGUI::Gui::getInstance().destroyWidget(mBackground); mBackground = nullptr; } if (!show) return; bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); if (mHasAnimatedMenu) { if (!mVideo) { // Use black background to correct aspect ratio mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default, "MainMenuBackground"); mVideoBackground->setImageTexture("black"); mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "MainMenuBackground"); mVideo->setVFS(mVFS); mVideo->playVideo("video\\menu_background.bik"); } MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); int screenWidth = viewSize.width; int screenHeight = viewSize.height; mVideoBackground->setSize(screenWidth, screenHeight); mVideo->autoResize(stretch); mVideo->setVisible(true); } else { if (!mBackground) { mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "MainMenuBackground"); mBackground->setBackgroundImage("textures\\menu_morrowind.dds", true, stretch); } mBackground->setVisible(true); } } void MainMenu::onFrame(float dt) { if (mVideo) { if (!mVideo->update()) { // If finished playing, start again mVideo->playVideo("video\\menu_background.bik"); } } } bool MainMenu::exit() { return MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running; } void MainMenu::updateMenu() { setCoord(0,0, mWidth, mHeight); if (!mButtonBox) mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); int curH = 0; MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); mVersionText->setVisible(state == MWBase::StateManager::State_NoGame); std::vector buttons; if (state==MWBase::StateManager::State_Running) buttons.emplace_back("return"); buttons.emplace_back("newgame"); if (state==MWBase::StateManager::State_Running && MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 && MWBase::Environment::get().getWindowManager()->isSavingAllowed()) buttons.emplace_back("savegame"); if (MWBase::Environment::get().getStateManager()->characterBegin()!= MWBase::Environment::get().getStateManager()->characterEnd()) buttons.emplace_back("loadgame"); buttons.emplace_back("options"); if (state==MWBase::StateManager::State_NoGame) buttons.emplace_back("credits"); buttons.emplace_back("exitgame"); // Create new buttons if needed std::vector allButtons { "return", "newgame", "savegame", "loadgame", "options", "credits", "exitgame"}; for (std::string& buttonId : allButtons) { if (mButtons.find(buttonId) == mButtons.end()) { Gui::ImageButton* button = mButtonBox->createWidget ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); button->setProperty("ImageHighlighted", "textures\\menu_" + buttonId + "_over.dds"); button->setProperty("ImageNormal", "textures\\menu_" + buttonId + ".dds"); button->setProperty("ImagePushed", "textures\\menu_" + buttonId + "_pressed.dds"); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); button->setUserData(std::string(buttonId)); mButtons[buttonId] = button; } } // Start by hiding all buttons int maxwidth = 0; for (auto& buttonPair : mButtons) { buttonPair.second->setVisible(false); MyGUI::IntSize requested = buttonPair.second->getRequestedSize(); if (requested.width > maxwidth) maxwidth = requested.width; } // Now show and position the ones we want for (std::string& buttonId : buttons) { assert(mButtons.find(buttonId) != mButtons.end()); Gui::ImageButton* button = mButtons[buttonId]; button->setVisible(true); // By default, assume that all menu buttons textures should have 64 height. // If they have a different resolution, scale them. MyGUI::IntSize requested = button->getRequestedSize(); float scale = requested.height / 64.f; button->setImageCoord(MyGUI::IntCoord(0, 0, requested.width, requested.height)); // Trim off some of the excessive padding // TODO: perhaps do this within ImageButton? int height = requested.height; button->setImageTile(MyGUI::IntSize(requested.width, requested.height-16*scale)); button->setCoord((maxwidth-requested.width/scale) / 2, curH, requested.width/scale, height/scale-16); curH += height/scale-16; } if (state == MWBase::StateManager::State_NoGame) { // Align with the background image int bottomPadding=24; mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight - curH - bottomPadding, maxwidth, curH); } else mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); } } openmw-openmw-0.48.0/apps/openmw/mwgui/mainmenu.hpp000066400000000000000000000025301445372753700223560ustar00rootroot00000000000000#ifndef OPENMW_GAME_MWGUI_MAINMENU_H #define OPENMW_GAME_MWGUI_MAINMENU_H #include "windowbase.hpp" namespace Gui { class ImageButton; } namespace VFS { class Manager; } namespace MWGui { class BackgroundImage; class SaveGameDialog; class VideoWidget; class MainMenu : public WindowBase { int mWidth; int mHeight; bool mHasAnimatedMenu; public: MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); ~MainMenu(); void onResChange(int w, int h) override; void setVisible (bool visible) override; void onFrame(float dt) override; bool exit() override; private: const VFS::Manager* mVFS; MyGUI::Widget* mButtonBox; MyGUI::TextBox* mVersionText; BackgroundImage* mBackground; MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideo; // For animated main menus std::map mButtons; void onButtonClicked (MyGUI::Widget* sender); void onNewGameConfirmed(); void onExitConfirmed(); void showBackground(bool show); void updateMenu(); SaveGameDialog* mSaveGameDialog; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/mapwindow.cpp000066400000000000000000001504251445372753700225540ustar00rootroot00000000000000#include "mapwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellutils.hpp" #include "../mwrender/globalmap.hpp" #include "../mwrender/localmap.hpp" #include "confirmationdialog.hpp" #include "tooltips.hpp" #include namespace { const int cellSize = Constants::CellSizeInUnits; constexpr float speed = 1.08f; //the zoom speed, it should be greater than 1 enum LocalMapWidgetDepth { Local_MarkerAboveFogLayer = 0, Local_CompassLayer = 1, Local_FogLayer = 2, Local_MarkerLayer = 3, Local_MapLayer = 4 }; enum GlobalMapWidgetDepth { Global_CompassLayer = 0, Global_MarkerLayer = 1, Global_ExploreOverlayLayer = 2, Global_MapLayer = 3 }; /// @brief A widget that changes its color when hovered. class MarkerWidget final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(MarkerWidget) public: void setNormalColour(const MyGUI::Colour& colour) { mNormalColour = colour; setColour(colour); } void setHoverColour(const MyGUI::Colour& colour) { mHoverColour = colour; } private: MyGUI::Colour mNormalColour; MyGUI::Colour mHoverColour; void onMouseLostFocus(MyGUI::Widget* _new) override { setColour(mNormalColour); } void onMouseSetFocus(MyGUI::Widget* _old) override { setColour(mHoverColour); } }; MyGUI::IntRect createRect(const MyGUI::IntPoint& center, int radius) { return { center.left - radius, center.top + radius, center.left + radius, center.top - radius }; } int getLocalViewingDistance() { if (!Settings::Manager::getBool("allow zooming", "Map")) return Constants::CellGridRadius; if (!Settings::Manager::getBool("distant terrain", "Terrain")) return Constants::CellGridRadius; const int maxLocalViewingDistance = std::max(Settings::Manager::getInt("max local viewing distance", "Map"), Constants::CellGridRadius); const int viewingDistanceInCells = Settings::Manager::getFloat("viewing distance", "Camera") / Constants::CellSizeInUnits; return std::clamp(viewingDistanceInCells, Constants::CellGridRadius, maxLocalViewingDistance); } } namespace MWGui { void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) { mMarkers.insert(std::make_pair(marker.mCell, marker)); if (triggerEvent) eventMarkersChanged(); } void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) { std::pair range = mMarkers.equal_range(marker.mCell); for (ContainerType::iterator it = range.first; it != range.second; ++it) { if (it->second == marker) { mMarkers.erase(it); eventMarkersChanged(); return; } } throw std::runtime_error("can't find marker to delete"); } void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) { std::pair range = mMarkers.equal_range(marker.mCell); for (ContainerType::iterator it = range.first; it != range.second; ++it) { if (it->second == marker) { it->second.mNote = newNote; eventMarkersChanged(); return; } } throw std::runtime_error("can't find marker to update"); } void CustomMarkerCollection::clear() { mMarkers.clear(); eventMarkersChanged(); } CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::begin() const { return mMarkers.begin(); } CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::end() const { return mMarkers.end(); } CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const { return mMarkers.equal_range(cellId); } size_t CustomMarkerCollection::size() const { return mMarkers.size(); } // ------------------------------------------------------ LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) , mCurX(0) , mCurY(0) , mInterior(false) , mLocalMap(nullptr) , mCompass(nullptr) , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) , mNumCells(1) , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) , mNeedDoorMarkersUpdate(false) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } LocalMapBase::~LocalMapBase() { mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); mCellDistance = cellDistance; mNumCells = mCellDistance * 2 + 1; mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); for (int mx=0; mxcreateWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); map->setDepth(Local_MapLayer); MyGUI::ImageBox* fog = mLocalMap->createWidget("ImageBox", MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); fog->setDepth(Local_FogLayer); fog->setColour(MyGUI::Colour(0, 0, 0)); map->setNeedMouseFocus(false); fog->setNeedMouseFocus(false); mMaps.emplace_back(map, fog); } } } void LocalMapBase::setCellPrefix(const std::string& prefix) { mPrefix = prefix; mChanged = true; } bool LocalMapBase::toggleFogOfWar() { mFogOfWarToggled = !mFogOfWarToggled; applyFogOfWar(); return mFogOfWarToggled; } void LocalMapBase::applyFogOfWar() { if (!mFogOfWarToggled || !mFogOfWarEnabled) { for (auto& entry : mMaps) { entry.mFogWidget->setImageTexture(""); entry.mFogTexture.reset(); } } redraw(); } MyGUI::IntPoint LocalMapBase::getPosition(int cellX, int cellY, float nX, float nY) const { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); return MyGUI::IntPoint( std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize) ); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const { osg::Vec2i cellIndex; // normalized cell coordinates float nX,nY; if (!mInterior) { cellIndex = MWWorld::positionToCellIndex(worldX, worldY); nX = (worldX - cellSize * cellIndex.x()) / cellSize; // Image space is -Y up, cells are Y up nY = 1 - (worldY - cellSize * cellIndex.y()) / cellSize; } else mLocalMapRender->worldToInteriorMapPosition({ worldX, worldY }, nX, nY, cellIndex.x(), cellIndex.y()); markerPos.cellX = cellIndex.x(); markerPos.cellY = cellIndex.y(); markerPos.nX = nX; markerPos.nY = nY; return getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); } MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const { int halfMarkerSize = markerSize / 2; auto position = getMarkerPosition(worldX, worldY, markerPos); return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); } MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, const MyGUI::VectorString& notes, float x, float y) const { MarkerUserData data(mLocalMapRender); data.notes = notes; data.caption = name; MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); markerWidget->setDepth(Local_MarkerLayer); markerWidget->setNeedMouseFocus(true); // Used by tooltips to not show the tooltip if marker is hidden by fog of war markerWidget->setUserString("ToolTipType", "MapMarker"); markerWidget->setUserData(data); return markerWidget; } void LocalMapBase::centerView() { MyGUI::IntPoint pos = mCompass->getPosition() + MyGUI::IntPoint{ 16, 16 }; MyGUI::IntSize viewsize = mLocalMap->getSize(); MyGUI::IntPoint viewOffset((viewsize.width / 2) - pos.left, (viewsize.height / 2) - pos.top); mLocalMap->setViewOffset(viewOffset); } MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const { MarkerUserData& markerPos(*widget->getUserData()); auto position = getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); return MyGUI::IntCoord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize); } std::vector& LocalMapBase::currentDoorMarkersWidgets() { return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() { for (MyGUI::Widget* widget : mCustomMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { for (int dY =-mCellDistance; dY <= mCellDistance; ++dY) { ESM::CellId cellId; cellId.mPaged = !mInterior; cellId.mWorldspace = (mInterior ? mPrefix : ESM::CellId::sDefaultWorldspace); cellId.mIndex.mX = mCurX+dX; cellId.mIndex.mY = mCurY+dY; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) { const ESM::CustomMarker& marker = it->second; MarkerUserData markerPos (mLocalMapRender); MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); markerWidget->setUserData(marker); markerWidget->setNeedMouseFocus(true); customMarkerCreated(markerWidget); mCustomMarkerWidgets.push_back(markerWidget); } } } redraw(); } void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell if (!interior && !(x == mCurX && y == mCurY)) { const MyGUI::IntRect intersection = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); mExteriorDoorMarkerWidgets.clear(); for (auto& [coord, doors] : mExteriorDoorsByCell) { if (!mHasALastActiveCell || !currentView.inside({ coord.first, coord.second }) || activeGrid.inside({ coord.first, coord.second })) { mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), doors.begin(), doors.end()); doors.clear(); } else mExteriorDoorMarkerWidgets.insert(mExteriorDoorMarkerWidgets.end(), doors.begin(), doors.end()); } for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); for (auto const& cell : mMaps) { if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); } } mCurX = x; mCurY = y; mInterior = interior; mChanged = false; for (int mx=0; mxsetRenderItemTexture(nullptr); entry.mFogWidget->setRenderItemTexture(nullptr); entry.mMapTexture.reset(); entry.mFogTexture.reset(); entry.mCellX = x + (mx - mCellDistance); entry.mCellY = y - (my - mCellDistance); } } // Delay the door markers update until scripts have been given a chance to run. // If we don't do this, door markers that should be disabled will still appear on the map. mNeedDoorMarkersUpdate = true; for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); if (!mInterior) mHasALastActiveCell = true; updateMagicMarkers(); updateCustomMarkers(); } void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) { mLocalMapRender->requestMap(cell); } void LocalMapBase::redraw() { // Redraw children in proper order mLocalMap->getParent()->_updateChilds(); } float LocalMapBase::getWidgetSize() const { return mLocalMapZoom * mMapWidgetSize; } void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { MyGUI::IntPoint pos = getPosition(cellX, cellY, nx, ny) - MyGUI::IntPoint{ 16, 16 }; if (pos != mCompass->getPosition()) { notifyPlayerUpdate (); mCompass->setPosition(pos); } osg::Vec2f curPos((cellX + nx) * cellSize, (cellY + 1 - ny) * cellSize); if ((curPos - mCurPos).length2() > 0.001) { mCurPos = curPos; centerView(); } } void LocalMapBase::setPlayerDir(const float x, const float y) { if (x == mLastDirectionX && y == mLastDirectionY) return; notifyPlayerUpdate (); MyGUI::ISubWidget* main = mCompass->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); float angle = std::atan2(x,y); rotatingSubskin->setAngle(angle); mLastDirectionX = x; mLastDirectionY = y; } void LocalMapBase::addDetectionMarkers(int type) { std::vector markers; MWBase::World* world = MWBase::Environment::get().getWorld(); world->listDetectedReferences( world->getPlayerPtr(), markers, MWBase::World::DetectionType(type)); if (markers.empty()) return; std::string markerTexture; if (type == MWBase::World::Detect_Creature) { markerTexture = "textures\\detect_animal_icon.dds"; } if (type == MWBase::World::Detect_Key) { markerTexture = "textures\\detect_key_icon.dds"; } if (type == MWBase::World::Detect_Enchantment) { markerTexture = "textures\\detect_enchantment_icon.dds"; } for (const MWWorld::Ptr& ptr : markers) { const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", getMarkerCoordinates(worldPos.pos[0], worldPos.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); markerWidget->setImageCoord(MyGUI::IntCoord(0,0,8,8)); markerWidget->setNeedMouseFocus(false); markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } } void LocalMapBase::onFrame(float dt) { if (mNeedDoorMarkersUpdate) { updateDoorMarkers(); mNeedDoorMarkersUpdate = false; } mMarkerUpdateTimer += dt; if (mMarkerUpdateTimer >= 0.25) { mMarkerUpdateTimer = 0; updateMagicMarkers(); } updateRequiredMaps(); } bool widgetCropped(MyGUI::Widget* widget, MyGUI::Widget* cropTo) { MyGUI::IntRect coord = widget->getAbsoluteRect(); MyGUI::IntRect croppedCoord = cropTo->getAbsoluteRect(); if (coord.left < croppedCoord.left && coord.right < croppedCoord.left) return true; if (coord.left > croppedCoord.right && coord.right > croppedCoord.right) return true; if (coord.top < croppedCoord.top && coord.bottom < croppedCoord.top) return true; if (coord.top > croppedCoord.bottom && coord.bottom > croppedCoord.bottom) return true; return false; } void LocalMapBase::updateRequiredMaps() { bool needRedraw = false; for (MapEntry& entry : mMaps) { if (widgetCropped(entry.mMapWidget, mLocalMap)) continue; if (!entry.mMapTexture) { if (!mInterior) requestMapRender(MWBase::Environment::get().getWorld()->getExterior (entry.mCellX, entry.mCellY)); osg::ref_ptr texture = mLocalMapRender->getMapTexture(entry.mCellX, entry.mCellY); if (texture) { entry.mMapTexture = std::make_unique(texture); entry.mMapWidget->setRenderItemTexture(entry.mMapTexture.get()); entry.mMapWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); needRedraw = true; } else entry.mMapTexture = std::make_unique(std::string(), nullptr); } if (!entry.mFogTexture && mFogOfWarToggled && mFogOfWarEnabled) { osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(entry.mCellX, entry.mCellY); if (tex) { entry.mFogTexture = std::make_unique(tex); entry.mFogWidget->setRenderItemTexture(entry.mFogTexture.get()); entry.mFogWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } else { entry.mFogWidget->setImageTexture("black"); entry.mFogTexture = std::make_unique(std::string(), nullptr); } needRedraw = true; } } if (needRedraw) redraw(); } void LocalMapBase::updateDoorMarkers() { std::vector doors; MWBase::World* world = MWBase::Environment::get().getWorld(); mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); mInteriorDoorMarkerWidgets.clear(); if (mInterior) { for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) widget->setVisible(false); MWWorld::CellStore* cell = world->getInterior (mPrefix); world->getDoorMarkers(cell, doors); } else { for (MapEntry& entry : mMaps) { if (!entry.mMapTexture && !widgetCropped(entry.mMapWidget, mLocalMap)) world->getDoorMarkers(world->getExterior(entry.mCellX, entry.mCellY), doors); } if (doors.empty()) return; } // Create a widget for each marker for (MWBase::World::DoorMarker& marker : doors) { std::vector destNotes; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; ++iter) destNotes.push_back(iter->second.mNote); MyGUI::Widget* markerWidget = nullptr; MarkerUserData* data; if (mDoorMarkersToRecycle.empty()) { markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); data = markerWidget->getUserData(); doorMarkerCreated(markerWidget); } else { markerWidget = (MarkerWidget*)mDoorMarkersToRecycle.back(); mDoorMarkersToRecycle.pop_back(); data = markerWidget->getUserData(); data->notes = destNotes; data->caption = marker.name; markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); markerWidget->setVisible(true); } currentDoorMarkersWidgets().push_back(markerWidget); if (!mInterior) mExteriorDoorsByCell[{data->cellX, data->cellY}].push_back(markerWidget); } for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); } void LocalMapBase::updateMagicMarkers() { // clear all previous markers for (MyGUI::Widget* widget : mMagicMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mMagicMarkerWidgets.clear(); addDetectionMarkers(MWBase::World::Detect_Creature); addDetectionMarkers(MWBase::World::Detect_Key); addDetectionMarkers(MWBase::World::Detect_Enchantment); // Add marker for the spot marked with Mark magic effect MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell && markedCell->isExterior() == !mInterior && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) { MarkerUserData markerPos (mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", getMarkerCoordinates(markedPosition.pos[0], markedPosition.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setNeedMouseFocus(false); markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } redraw(); } void LocalMapBase::updateLocalMap() { auto mapWidgetSize = getWidgetSize(); mLocalMap->setCanvasSize(mapWidgetSize * mNumCells, mapWidgetSize * mNumCells); const auto size = MyGUI::IntSize(std::ceil(mapWidgetSize), std::ceil(mapWidgetSize)); for (auto& entry : mMaps) { const auto position = getPosition(entry.mCellX, entry.mCellY, 0, 0); entry.mMapWidget->setCoord({ position, size }); entry.mFogWidget->setCoord({ position, size }); } MarkerUserData markerPos(mLocalMapRender); for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); for (MyGUI::Widget* widget : mCustomMarkerWidgets) { const auto& marker = *widget->getUserData(); widget->setCoord(getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16)); } for (MyGUI::Widget* widget : mMagicMarkerWidgets) widget->setCoord(getMarkerCoordinates(widget, 8)); } // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) #ifdef USE_OPENXR : WindowPinnableBase("openmw_map_window_vr.layout") #else : WindowPinnableBase("openmw_map_window.layout") #endif , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) , mGlobalMapImage(nullptr) , mGlobalMapOverlay(nullptr) , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() , mAllowZooming(Settings::Manager::getBool("allow zooming", "Map")) { static bool registered = false; if (!registered) { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); registered = true; } mEditNoteDialog.setVisible(false); mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); mEditNoteDialog.eventDeleteClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDelete); setCoord(500,0,320,300); getWidget(mLocalMap, "LocalMap"); getWidget(mGlobalMap, "GlobalMap"); getWidget(mGlobalMapImage, "GlobalMapImage"); getWidget(mGlobalMapOverlay, "GlobalMapOverlay"); getWidget(mPlayerArrowLocal, "CompassLocal"); getWidget(mPlayerArrowGlobal, "CompassGlobal"); mPlayerArrowGlobal->setDepth(Global_CompassLayer); mPlayerArrowGlobal->setNeedMouseFocus(false); mGlobalMapImage->setDepth(Global_MapLayer); mGlobalMapOverlay->setDepth(Global_ExploreOverlayLayer); mLastScrollWindowCoordinates = mLocalMap->getCoord(); mLocalMap->eventChangeCoord += MyGUI::newDelegate(this, &MapWindow::onChangeScrollWindowCoord); mGlobalMap->setVisible (false); getWidget(mButton, "WorldButton"); mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); if (mAllowZooming) mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); if (mAllowZooming) mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance()); mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); } void MapWindow::onNoteEditOk() { if (mEditNoteDialog.getDeleteButtonShown()) mCustomMarkers.updateMarker(mEditingMarker, mEditNoteDialog.getText()); else { mEditingMarker.mNote = mEditNoteDialog.getText(); mCustomMarkers.addMarker(mEditingMarker); } mEditNoteDialog.setVisible(false); } void MapWindow::onNoteEditDelete() { ConfirmationDialog* confirmation = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); confirmation->askForConfirmation("#{sDeleteNote}"); confirmation->eventCancelClicked.clear(); confirmation->eventOkClicked.clear(); confirmation->eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDeleteConfirm); } void MapWindow::onNoteEditDeleteConfirm() { mCustomMarkers.deleteMarker(mEditingMarker); mEditNoteDialog.setVisible(false); } void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender) { mEditingMarker = *sender->getUserData(); mEditNoteDialog.setText(mEditingMarker.mNote); mEditNoteDialog.showDeleteButton(true); mEditNoteDialog.setVisible(true); } void MapWindow::onMapDoubleClicked(MyGUI::Widget *sender) { MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); auto mapWidgetSize = getWidgetSize(); int x = int(widgetPos.left/float(mapWidgetSize))-mCellDistance; int y = (int(widgetPos.top/float(mapWidgetSize))-mCellDistance)*-1; float nX = widgetPos.left/float(mapWidgetSize) - int(widgetPos.left/float(mapWidgetSize)); float nY = widgetPos.top/float(mapWidgetSize) - int(widgetPos.top/float(mapWidgetSize)); x += mCurX; y += mCurY; osg::Vec2f worldPos; if (mInterior) { worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } else { worldPos.x() = (x + nX) * cellSize; worldPos.y() = (y + (1.0f-nY)) * cellSize; } mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); mEditingMarker.mCell.mPaged = !mInterior; if (mInterior) mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix; else { mEditingMarker.mCell.mWorldspace = ESM::CellId::sDefaultWorldspace; mEditingMarker.mCell.mIndex.mX = x; mEditingMarker.mCell.mIndex.mY = y; } mEditNoteDialog.setVisible(true); mEditNoteDialog.showDeleteButton(false); mEditNoteDialog.setText(""); } void MapWindow::onMapZoomed(MyGUI::Widget* sender, int rel) { const static int localWidgetSize = Settings::Manager::getInt("local map widget size", "Map"); const static int globalCellSize = Settings::Manager::getInt("global map cell size", "Map"); const bool zoomOut = rel < 0; const bool zoomIn = !zoomOut; const double speedDiff = zoomOut ? 1.0 / speed : speed; const float localMapSizeInUnits = localWidgetSize * mNumCells; const float currentMinLocalMapZoom = std::max({ (float(globalCellSize) * 4.f) / float(localWidgetSize), float(mLocalMap->getWidth()) / localMapSizeInUnits, float(mLocalMap->getHeight()) / localMapSizeInUnits }); if (mGlobal) { const float currentGlobalZoom = mGlobalMapZoom; const float currentMinGlobalMapZoom = std::min( float(mGlobalMap->getWidth()) / float(mGlobalMapRender->getWidth()), float(mGlobalMap->getHeight()) / float(mGlobalMapRender->getHeight()) ); mGlobalMapZoom *= speedDiff; if (zoomIn && mGlobalMapZoom > 4.f) { mGlobalMapZoom = currentGlobalZoom; mLocalMapZoom = currentMinLocalMapZoom; onWorldButtonClicked(nullptr); updateLocalMap(); return; //the zoom in is too big } if (zoomOut && mGlobalMapZoom < currentMinGlobalMapZoom) { mGlobalMapZoom = currentGlobalZoom; return; //the zoom out is too big, we have reach the borders of the widget } } else { auto const currentLocalZoom = mLocalMapZoom; mLocalMapZoom *= speedDiff; if (zoomIn && mLocalMapZoom > 4.0f) { mLocalMapZoom = currentLocalZoom; return; //the zoom in is too big } if (zoomOut && mLocalMapZoom < currentMinLocalMapZoom) { mLocalMapZoom = currentLocalZoom; float zoomRatio = 4.f/ mGlobalMapZoom; mGlobalMapZoom = 4.f; onWorldButtonClicked(nullptr); zoomOnCursor(zoomRatio); return; //the zoom out is too big, we switch to the global map } if (zoomOut) mNeedDoorMarkersUpdate = true; } zoomOnCursor(speedDiff); } void MapWindow::zoomOnCursor(float speedDiff) { auto map = mGlobal ? mGlobalMap : mLocalMap; auto cursor = MyGUI::InputManager::getInstance().getMousePosition() - map->getAbsolutePosition(); auto centerView = map->getViewOffset() - cursor; mGlobal? updateGlobalMap() : updateLocalMap(); map->setViewOffset(MyGUI::IntPoint( std::round(centerView.left * speedDiff) + cursor.left, std::round(centerView.top * speedDiff) + cursor.top )); } void MapWindow::updateGlobalMap() { resizeGlobalMap(); float x = mCurPos.x(), y = mCurPos.y(); if (mInterior) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); x = pos.x(); y = pos.y(); } setGlobalMapPlayerPosition(x, y); for (auto& [marker, col] : mGlobalMapMarkers) { marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), col.size())); marker.widget->setVisible(marker.widget->getHeight() >= 6); } } void MapWindow::onChangeScrollWindowCoord(MyGUI::Widget* sender) { MyGUI::IntCoord currentCoordinates = sender->getCoord(); MyGUI::IntPoint currentViewPortCenter = MyGUI::IntPoint(currentCoordinates.width / 2, currentCoordinates.height / 2); MyGUI::IntPoint lastViewPortCenter = MyGUI::IntPoint(mLastScrollWindowCoordinates.width / 2, mLastScrollWindowCoordinates.height / 2); MyGUI::IntPoint viewPortCenterDiff = currentViewPortCenter - lastViewPortCenter; mLocalMap->setViewOffset(mLocalMap->getViewOffset() + viewPortCenterDiff); mGlobalMap->setViewOffset(mGlobalMap->getViewOffset() + viewPortCenterDiff); mLastScrollWindowCoordinates = currentCoordinates; } void MapWindow::setVisible(bool visible) { WindowBase::setVisible(visible); mButton->setVisible(visible && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None); } void MapWindow::renderGlobalMap() { mGlobalMapRender->render(); resizeGlobalMap(); } MapWindow::~MapWindow() { } void MapWindow::setCellName(const std::string& cellName) { setTitle("#{sCell=" + cellName + "}"); } MyGUI::IntCoord MapWindow::createMarkerCoords(float x, float y, float agregatedWeight) const { float worldX, worldY; worldPosToGlobalMapImageSpace((x + 0.5f) * Constants::CellSizeInUnits, (y + 0.5f)* Constants::CellSizeInUnits, worldX, worldY); const float markerSize = getMarkerSize(agregatedWeight); const float halfMarkerSize = markerSize / 2.0f; return MyGUI::IntCoord( static_cast(worldX - halfMarkerSize), static_cast(worldY - halfMarkerSize), markerSize, markerSize); } MyGUI::Widget* MapWindow::createMarker(const std::string& name, float x, float y, float agregatedWeight) { MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", createMarkerCoords(x, y, agregatedWeight), MyGUI::Align::Default); markerWidget->setVisible(markerWidget->getHeight() >= 6.0); markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); setGlobalMapMarkerTooltip(markerWidget, x, y); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setNeedMouseFocus(true); markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); markerWidget->setDepth(Global_MarkerLayer); markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); if (mAllowZooming) markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); return markerWidget; } void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { CellId cell; cell.first = x; cell.second = y; if (mMarkers.insert(cell).second) { MapMarkerType mapMarkerWidget = { osg::Vec2f(x, y), createMarker(name, x, y, 0) }; mGlobalMapMarkers.emplace(mapMarkerWidget, std::vector()); std::string name_ = name.substr(0, name.find(',')); auto& entry = mGlobalMapMarkersByName[name_]; if (!entry.widget) { entry = { osg::Vec2f(x, y), entry.widget }; //update the coords entry.widget = createMarker(name_, entry.position.x(), entry.position.y(), 1); mGlobalMapMarkers.emplace(entry, std::vector{ entry }); } else { auto it = mGlobalMapMarkers.find(entry); auto& marker = const_cast(it->first); auto& elements = it->second; elements.emplace_back(mapMarkerWidget); //we compute the barycenter of the entry elements => it will be the place on the world map for the agregated widget marker.position = std::accumulate(elements.begin(), elements.end(), osg::Vec2f(0.f, 0.f), [](const auto& left, const auto& right) { return left + right.position; }) / float(elements.size()); marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), elements.size())); marker.widget->setVisible(marker.widget->getHeight() >= 6); } } } void MapWindow::cellExplored(int x, int y) { mGlobalMapRender->cleanupCameras(); mGlobalMapRender->exploreCell(x, y, mLocalMapRender->getMapTexture(x, y)); } void MapWindow::onFrame(float dt) { LocalMapBase::onFrame(dt); NoDrop::onFrame(dt); } void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y) { ESM::CellId cellId; cellId.mIndex.mX = x; cellId.mIndex.mY = y; cellId.mWorldspace = ESM::CellId::sDefaultWorldspace; cellId.mPaged = true; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); std::vector destNotes; for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) destNotes.push_back(it->second.mNote); if (!destNotes.empty()) { MarkerUserData data (nullptr); data.notes = destNotes; data.caption = markerWidget->getUserString("Caption_TextOneLine"); markerWidget->setUserData(data); markerWidget->setUserString("ToolTipType", "MapMarker"); } else { markerWidget->setUserString("ToolTipType", "Layout"); } } float MapWindow::getMarkerSize(size_t agregatedWeight) const { float markerSize = 12.f * mGlobalMapZoom; if (mGlobalMapZoom < 1) return markerSize * std::sqrt(agregatedWeight); //we want to see agregated object return agregatedWeight ? 0 : markerSize; //we want to see only original markers (i.e. non agregated) } void MapWindow::resizeGlobalMap() { mGlobalMap->setCanvasSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); mGlobalMapImage->setSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); } void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const { mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); imageX *= mGlobalMapZoom; imageY *= mGlobalMapZoom; } void MapWindow::updateCustomMarkers() { LocalMapBase::updateCustomMarkers(); for (auto& [widgetPair, ignore]: mGlobalMapMarkers) setGlobalMapMarkerTooltip(widgetPair.widget, widgetPair.position.x(), widgetPair.position.y()); } void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; mLastDragPos = MyGUI::IntPoint(_left, _top); } void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; if (!mGlobal) { mNeedDoorMarkersUpdate = true; mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); } else mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); mLastDragPos = MyGUI::IntPoint(_left, _top); } void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) { mGlobal = !mGlobal; mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); Settings::Manager::setBool("global", "Map", mGlobal); mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); } void MapWindow::onPinToggled() { Settings::Manager::setBool("map pin", "Windows", mPinned); MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } void MapWindow::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) MWBase::Environment::get().getWindowManager()->toggleMaximized(this); else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); } void MapWindow::onOpen() { ensureGlobalMapLoaded(); globalMapUpdatePlayer(); } void MapWindow::globalMapUpdatePlayer () { // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { osg::Vec3f pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData().getPosition().asVec3(); setGlobalMapPlayerPosition(pos.x(), pos.y()); } } void MapWindow::notifyPlayerUpdate () { globalMapUpdatePlayer (); setGlobalMapPlayerDir(mLastDirectionX, mLastDirectionY); } void MapWindow::centerView() { LocalMapBase::centerView(); // set the view offset so that player is in the center MyGUI::IntSize viewsize = mGlobalMap->getSize(); MyGUI::IntPoint pos = mPlayerArrowGlobal->getPosition() + MyGUI::IntPoint{ 16,16 }; MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - pos.left), static_cast(viewsize.height * 0.5f - pos.top)); mGlobalMap->setViewOffset(viewoffs); } void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; worldPosToGlobalMapImageSpace(worldX, worldY, x, y); mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(static_cast(x - 16), static_cast(y - 16))); } void MapWindow::setGlobalMapPlayerDir(const float x, const float y) { MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); float angle = std::atan2(x,y); rotatingSubskin->setAngle(angle); } void MapWindow::ensureGlobalMapLoaded() { if (!mGlobalMapTexture.get()) { mGlobalMapTexture = std::make_unique(mGlobalMapRender->getBaseTexture()); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); mGlobalMapOverlayTexture = std::make_unique(mGlobalMapRender->getOverlayTexture()); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); // Redraw children in proper order mGlobalMap->getParent()->_updateChilds(); } } void MapWindow::clear() { mMarkers.clear(); mGlobalMapRender->clear(); mChanged = true; for (auto& widgetPair : mGlobalMapMarkers) MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); mGlobalMapMarkers.clear(); mGlobalMapMarkersByName.clear(); } void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) { ESM::GlobalMap map; mGlobalMapRender->write(map); map.mMarkers = mMarkers; writer.startRecord(ESM::REC_GMAP); map.save(writer); writer.endRecord(ESM::REC_GMAP); } void MapWindow::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_GMAP) { ESM::GlobalMap map; map.load(reader); mGlobalMapRender->read(map); for (const ESM::GlobalMap::CellId& cellId : map.mMarkers) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(cellId.first, cellId.second); if (cell && !cell->mName.empty()) addVisitedLocation(cell->mName, cellId.first, cellId.second); } } } void MapWindow::setAlpha(float alpha) { NoDrop::setAlpha(alpha); // can't allow showing map with partial transparency, as the fog of war will also go transparent // and reveal parts of the map you shouldn't be able to see for (MapEntry& entry : mMaps) entry.mMapWidget->setVisible(alpha == 1); } void MapWindow::customMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked); if (mAllowZooming) marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); if (mAllowZooming) marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } void MapWindow::asyncPrepareSaveMap() { mGlobalMapRender->asyncWritePng(); } // ------------------------------------------------------------------- EditNoteDialog::EditNoteDialog() : WindowModal("openmw_edit_note.layout") { getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mDeleteButton, "DeleteButton"); getWidget(mTextEdit, "TextEdit"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onOkButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onDeleteButtonClicked); } void EditNoteDialog::showDeleteButton(bool show) { mDeleteButton->setVisible(show); } bool EditNoteDialog::getDeleteButtonShown() { return mDeleteButton->getVisible(); } void EditNoteDialog::setText(const std::string &text) { mTextEdit->setCaption(MyGUI::TextIterator::toTagsString(text)); } std::string EditNoteDialog::getText() { return MyGUI::TextIterator::getOnlyText(mTextEdit->getCaption()); } void EditNoteDialog::onOpen() { WindowModal::onOpen(); center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) { setVisible(false); } void EditNoteDialog::onOkButtonClicked(MyGUI::Widget *sender) { eventOkClicked(); } void EditNoteDialog::onDeleteButtonClicked(MyGUI::Widget *sender) { eventDeleteClicked(); } bool LocalMapBase::MarkerUserData::isPositionExplored() const { if (!mLocalMapRender) return true; return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY); } } openmw-openmw-0.48.0/apps/openmw/mwgui/mapwindow.hpp000066400000000000000000000247251445372753700225640ustar00rootroot00000000000000#ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H #include #include #include #include #include "windowpinnablebase.hpp" #include #include #include namespace MWRender { class GlobalMap; class LocalMap; } namespace ESM { class ESMReader; class ESMWriter; } namespace MWWorld { class CellStore; } namespace Loading { class Listener; } namespace SceneUtil { class WorkQueue; } namespace MWGui { class CustomMarkerCollection { public: void addMarker(const ESM::CustomMarker& marker, bool triggerEvent=true); void deleteMarker (const ESM::CustomMarker& marker); void updateMarker(const ESM::CustomMarker& marker, const std::string& newNote); void clear(); size_t size() const; typedef std::multimap ContainerType; typedef std::pair RangeType; ContainerType::const_iterator begin() const; ContainerType::const_iterator end() const; RangeType getMarkers(const ESM::CellId& cellId) const; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventMarkersChanged; private: ContainerType mMarkers; }; class LocalMapBase { public: LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); void onFrame(float dt); bool toggleFogOfWar(); struct MarkerUserData { MarkerUserData(MWRender::LocalMap* map) : mLocalMapRender(map) , cellX(0) , cellY(0) , nX(0.f) , nY(0.f) { } bool isPositionExplored() const; MWRender::LocalMap* mLocalMapRender; int cellX; int cellY; float nX; float nY; std::vector notes; std::string caption; }; protected: void updateLocalMap(); float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; int mCurX, mCurY; //the position of the active cell on the global map (in cell coords) bool mHasALastActiveCell = false; osg::Vec2f mCurPos; //the position of the player in the world (in cell coords) bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; std::string mPrefix; bool mChanged; bool mFogOfWarToggled; bool mFogOfWarEnabled; int mMapWidgetSize; int mNumCells; // for convenience, mCellDistance * 2 + 1 int mCellDistance; // Stores markers that were placed by a player. May be shared between multiple map views. CustomMarkerCollection& mCustomMarkers; struct MapEntry { MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget) : mMapWidget(mapWidget), mFogWidget(fogWidget), mCellX(0), mCellY(0) {} MyGUI::ImageBox* mMapWidget; MyGUI::ImageBox* mFogWidget; std::unique_ptr mMapTexture; std::unique_ptr mFogTexture; int mCellX; int mCellY; }; std::vector mMaps; // Keep track of created marker widgets, just to easily remove them later. std::vector mExteriorDoorMarkerWidgets; std::map, std::vector> mExteriorDoorsByCell; std::vector mInteriorDoorMarkerWidgets; std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; std::vector mDoorMarkersToRecycle; std::vector& currentDoorMarkersWidgets(); virtual void updateCustomMarkers(); void applyFogOfWar(); MyGUI::IntPoint getPosition(int cellX, int cellY, float nx, float ny) const; MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerUserData& markerPos) const; MyGUI::IntCoord getMarkerCoordinates(float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const; MyGUI::Widget* createDoorMarker(const std::string& name, const MyGUI::VectorString& notes, float x, float y) const; MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const; virtual void notifyPlayerUpdate() {} virtual void centerView(); virtual void notifyMapChanged() {} virtual void customMarkerCreated(MyGUI::Widget* marker) {} virtual void doorMarkerCreated(MyGUI::Widget* marker) {} void updateRequiredMaps(); void updateMagicMarkers(); void addDetectionMarkers(int type); void redraw(); float getWidgetSize() const; float mMarkerUpdateTimer; float mLastDirectionX; float mLastDirectionY; bool mNeedDoorMarkersUpdate; private: void updateDoorMarkers(); }; class EditNoteDialog : public MWGui::WindowModal { public: EditNoteDialog(); void onOpen() override; void showDeleteButton(bool show); bool getDeleteButtonShown(); void setText(const std::string& text); std::string getText(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventDeleteClicked; EventHandle_Void eventOkClicked; private: void onCancelButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); void onDeleteButtonClicked(MyGUI::Widget* sender); MyGUI::TextBox* mTextEdit; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; MyGUI::Button* mDeleteButton; }; class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue); virtual ~MapWindow(); void setCellName(const std::string& cellName); void setAlpha(float alpha) override; void setVisible(bool visible) override; void renderGlobalMap(); /// adds the marker to the global map /// @param name The ESM::Cell::mName void addVisitedLocation(const std::string& name, int x, int y); // reveals this cell's map on the global map void cellExplored(int x, int y); void setGlobalMapPlayerPosition (float worldX, float worldY); void setGlobalMapPlayerDir(const float x, const float y); void ensureGlobalMapLoaded(); void onOpen() override; void onFrame(float dt) override; void updateCustomMarkers() override; /// Clear all savegame-specific data void clear() override; void write (ESM::ESMWriter& writer, Loading::Listener& progress); void readRecord (ESM::ESMReader& reader, uint32_t type); void asyncPrepareSaveMap(); private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onWorldButtonClicked(MyGUI::Widget* _sender); void onMapDoubleClicked(MyGUI::Widget* sender); void onMapZoomed(MyGUI::Widget* sender, int rel); void zoomOnCursor(float speedDiff); void updateGlobalMap(); void onCustomMarkerDoubleClicked(MyGUI::Widget* sender); void onNoteEditOk(); void onNoteEditDelete(); void onNoteEditDeleteConfirm(); void onNoteDoubleClicked(MyGUI::Widget* sender); void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y); float getMarkerSize(size_t agregatedWeight) const; void resizeGlobalMap(); void worldPosToGlobalMapImageSpace(float x, float z, float& imageX, float& imageY) const; MyGUI::IntCoord createMarkerCoords(float x, float y, float agregatedWeight) const; MyGUI::Widget* createMarker(const std::string& name, float x, float y, float agregatedWeight); MyGUI::ScrollView* mGlobalMap; std::unique_ptr mGlobalMapTexture; std::unique_ptr mGlobalMapOverlayTexture; MyGUI::ImageBox* mGlobalMapImage; MyGUI::ImageBox* mGlobalMapOverlay; MyGUI::ImageBox* mPlayerArrowLocal; MyGUI::ImageBox* mPlayerArrowGlobal; MyGUI::Button* mButton; MyGUI::IntPoint mLastDragPos; bool mGlobal; MyGUI::IntCoord mLastScrollWindowCoordinates; // Markers on global map typedef std::pair CellId; std::set mMarkers; MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxLocal; float mGlobalMapZoom = 1.0f; std::unique_ptr mGlobalMapRender; struct MapMarkerType { osg::Vec2f position; MyGUI::Widget* widget = nullptr; bool operator<(const MapMarkerType& right) const { return widget < right.widget; } }; std::map mGlobalMapMarkersByName; std::map> mGlobalMapMarkers; EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; bool mAllowZooming; void onPinToggled() override; void onTitleDoubleClicked() override; void doorMarkerCreated(MyGUI::Widget* marker) override; void customMarkerCreated(MyGUI::Widget *marker) override; void notifyPlayerUpdate() override; void centerView() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/merchantrepair.cpp000066400000000000000000000127701445372753700235530ustar00rootroot00000000000000#include "merchantrepair.hpp" #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" namespace MWGui { MerchantRepair::MerchantRepair() : WindowBase("openmw_merchantrepair.layout") { getWidget(mList, "RepairView"); getWidget(mOkButton, "OkButton"); getWidget(mGoldLabel, "PlayerGold"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick); } void MerchantRepair::setPtr(const MWWorld::Ptr &actor) { mActor = actor; while (mList->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0)); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; int currentY = 0; MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); iter!=store.end(); ++iter) { if (iter->getClass().hasItemHealth(*iter)) { int maxDurability = iter->getClass().getItemMaxHealth(*iter); int durability = iter->getClass().getItemHealth(*iter); if (maxDurability == durability || maxDurability == 0) continue; int basePrice = iter->getClass().getValue(*iter); float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fRepairMult")->mValue.getFloat(); float p = static_cast(std::max(1, basePrice)); float r = static_cast(std::max(1, static_cast(maxDurability / p))); int x = static_cast((maxDurability - durability) / r); x = static_cast(fRepairMult * x); x = std::max(1, x); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true); std::string name = iter->getClass().getName(*iter) + " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getWorld()->getStore().get() .find("sgp")->mValue.getString(); MyGUI::Button* button = mList->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip 0, currentY, 0, lineHeight, MyGUI::Align::Default ); currentY += lineHeight; button->setUserString("Price", MyGUI::utility::toString(price)); button->setUserData(MWWorld::Ptr(*iter)); button->setCaptionWithReplacing(name); button->setSize(mList->getWidth(), lineHeight); button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); button->setUserString("ToolTipType", "ItemPtr"); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); } } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mList->setVisibleVScroll(false); mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); mList->setVisibleVScroll(true); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); } void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mList->getViewOffset().top + _rel*0.3f > 0) mList->setViewOffset(MyGUI::IntPoint(0, 0)); else mList->setViewOffset(MyGUI::IntPoint(0, static_cast(mList->getViewOffset().top + _rel*0.3f))); } void MerchantRepair::onOpen() { center(); // Reset scrollbars mList->setViewOffset(MyGUI::IntPoint(0, 0)); } void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { MWWorld::Ptr player = MWMechanics::getPlayer(); int price = MyGUI::utility::parseInt(sender->getUserString("Price")); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; // repair MWWorld::Ptr item = *sender->getUserData(); item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); player.getClass().getContainerStore(player).restack(item); MWBase::Environment::get().getWindowManager()->playSound("Repair"); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); actorStats.setGoldPool(actorStats.getGoldPool() + price); setPtr(mActor); } void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); } } openmw-openmw-0.48.0/apps/openmw/mwgui/merchantrepair.hpp000066400000000000000000000011561445372753700235540ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_MERCHANTREPAIR_H #define OPENMW_MWGUI_MERCHANTREPAIR_H #include "windowbase.hpp" #include "../mwworld/ptr.hpp" namespace MWGui { class MerchantRepair : public WindowBase { public: MerchantRepair(); void onOpen() override; void setPtr(const MWWorld::Ptr& actor) override; private: MyGUI::ScrollView* mList; MyGUI::Button* mOkButton; MyGUI::TextBox* mGoldLabel; MWWorld::Ptr mActor; protected: void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onRepairButtonClick(MyGUI::Widget* sender); void onOkButtonClick(MyGUI::Widget* sender); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/messagebox.cpp000066400000000000000000000326351445372753700227060ustar00rootroot00000000000000#include "messagebox.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" namespace MWGui { MessageBoxManager::MessageBoxManager (float timePerChar) { mInterMessageBoxe = nullptr; mStaticMessageBox = nullptr; mLastButtonPressed = -1; mMessageBoxSpeed = timePerChar; } MessageBoxManager::~MessageBoxManager () { MessageBoxManager::clear(); } int MessageBoxManager::getMessagesCount() { return mMessageBoxes.size(); } void MessageBoxManager::clear() { if (mInterMessageBoxe) { mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = nullptr; } for (MessageBox* messageBox : mMessageBoxes) { if (messageBox == mStaticMessageBox) mStaticMessageBox = nullptr; delete messageBox; } mMessageBoxes.clear(); mLastButtonPressed = -1; } void MessageBoxManager::onFrame (float frameDuration) { std::vector::iterator it; for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();) { (*it)->mCurrentTime += frameDuration; if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox) { delete *it; it = mMessageBoxes.erase(it); } else ++it; } float height = 0; it = mMessageBoxes.begin(); while(it != mMessageBoxes.end()) { (*it)->update(static_cast(height)); height += (*it)->getHeight(); ++it; } if(mInterMessageBoxe != nullptr && mInterMessageBoxe->mMarkedToDelete) { mLastButtonPressed = mInterMessageBoxe->readPressedButton(); mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = nullptr; MWBase::Environment::get().getInputManager()->changeInputMode( MWBase::Environment::get().getWindowManager()->isGuiMode()); } } void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { MessageBox *box = new MessageBox(*this, message); box->mCurrentTime = 0; std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message); box->mMaxTime = realMessage.length()*mMessageBoxSpeed; if(stat) mStaticMessageBox = box; box->setVisible(mVisible); mMessageBoxes.push_back(box); if(mMessageBoxes.size() > 3) { delete *mMessageBoxes.begin(); mMessageBoxes.erase(mMessageBoxes.begin()); } int height = 0; for (MessageBox* messageBox : mMessageBoxes) { messageBox->update(height); height += messageBox->getHeight(); } } void MessageBoxManager::removeStaticMessageBox () { removeMessageBox(mStaticMessageBox); mStaticMessageBox = nullptr; } bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector& buttons) { if (mInterMessageBoxe != nullptr) { Log(Debug::Warning) << "Warning: replacing an interactive message box that was not answered yet"; mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = nullptr; } mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons); mLastButtonPressed = -1; return true; } bool MessageBoxManager::isInteractiveMessageBox () { return mInterMessageBoxe != nullptr; } bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) { std::vector::iterator it; for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) { if((*it) == msgbox) { delete (*it); mMessageBoxes.erase(it); return true; } } return false; } const std::vector MessageBoxManager::getActiveMessageBoxes() { return mMessageBoxes; } int MessageBoxManager::readPressedButton (bool reset) { int pressed = mLastButtonPressed; if (reset) mLastButtonPressed = -1; return pressed; } void MessageBoxManager::setVisible(bool value) { mVisible = value; for (MessageBox* messageBox : mMessageBoxes) messageBox->setVisible(value); } MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message) : Layout("openmw_messagebox.layout") , mCurrentTime(0) , mMaxTime(0) , mMessageBoxManager(parMessageBoxManager) , mMessage(message) { // defines mBottomPadding = 48; mNextBoxPadding = 4; getWidget(mMessageWidget, "message"); mMessageWidget->setCaptionWithReplacing(mMessage); } void MessageBox::update (int height) { MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint pos; pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2; pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding); mMainWidget->setPosition(pos); } int MessageBox::getHeight () { return mMainWidget->getHeight()+mNextBoxPadding; } void MessageBox::setVisible(bool value) { mMainWidget->setVisible(value); } InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) { int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal int buttonTopPadding = 10; // ^-- if vertical int buttonLabelLeftPadding = 12; // padding between button label and button itself, from left int buttonLabelTopPadding = 4; // padding between button label and button itself, from top int buttonMainPadding = 10; // padding between buttons and bottom of the main widget mMarkedToDelete = false; getWidget(mMessageWidget, "message"); getWidget(mButtonsWidget, "buttons"); mMessageWidget->setSize(400, mMessageWidget->getHeight()); mMessageWidget->setCaptionWithReplacing(message); MyGUI::IntSize textSize = mMessageWidget->getTextSize(); MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); int biggestButtonWidth = 0; int buttonsWidth = 0; int buttonsHeight = 0; int buttonHeight = 0; MyGUI::IntCoord dummyCoord(0, 0, 0, 0); for(const std::string& buttonId : buttons) { MyGUI::Button* button = mButtonsWidget->createWidget( MyGUI::WidgetStyle::Child, std::string("MW_Button"), dummyCoord, MyGUI::Align::Default); button->setCaptionWithReplacing(buttonId); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InteractiveMessageBox::mousePressed); mButtons.push_back(button); if (buttonsWidth != 0) buttonsWidth += buttonLeftPadding; int buttonWidth = button->getTextSize().width + 2*buttonLabelLeftPadding; buttonsWidth += buttonWidth; buttonHeight = button->getTextSize().height + 2*buttonLabelTopPadding; if (buttonsHeight != 0) buttonsHeight += buttonTopPadding; buttonsHeight += buttonHeight; if(buttonWidth > biggestButtonWidth) { biggestButtonWidth = buttonWidth; } } MyGUI::IntSize mainWidgetSize; if(buttonsWidth < textSize.width) { // on one line mainWidgetSize.width = textSize.width + 3*textPadding; mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; MyGUI::IntSize realSize = mainWidgetSize + // To account for borders (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize()); MyGUI::IntPoint absPos; absPos.left = (gameWindowSize.width - realSize.width)/2; absPos.top = (gameWindowSize.height - realSize.height)/2; mMainWidget->setPosition(absPos); mMainWidget->setSize(realSize); MyGUI::IntCoord messageWidgetCoord; messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; messageWidgetCoord.top = textPadding; mMessageWidget->setCoord(messageWidgetCoord); mMessageWidget->setSize(textSize); MyGUI::IntCoord buttonCord; MyGUI::IntSize buttonSize(0, buttonHeight); int left = (mainWidgetSize.width - buttonsWidth)/2; for(MyGUI::Button* button : mButtons) { buttonCord.left = left; buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding; buttonSize.width = button->getTextSize().width + 2*buttonLabelLeftPadding; buttonSize.height = button->getTextSize().height + 2*buttonLabelTopPadding; button->setCoord(buttonCord); button->setSize(buttonSize); left += buttonSize.width + buttonLeftPadding; } } else { // among each other if(biggestButtonWidth > textSize.width) { mainWidgetSize.width = biggestButtonWidth + buttonTopPadding*2; } else { mainWidgetSize.width = textSize.width + 3*textPadding; } MyGUI::IntCoord buttonCord; MyGUI::IntSize buttonSize(0, buttonHeight); int top = textPadding + textSize.height + textButtonPadding; for(MyGUI::Button* button : mButtons) { buttonSize.width = button->getTextSize().width + buttonLabelLeftPadding*2; buttonSize.height = button->getTextSize().height + buttonLabelTopPadding*2; buttonCord.top = top; buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2; button->setCoord(buttonCord); button->setSize(buttonSize); top += buttonSize.height + buttonTopPadding; } mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonsHeight + buttonMainPadding; mMainWidget->setSize(mainWidgetSize + // To account for borders (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize())); MyGUI::IntPoint absPos; absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; mMainWidget->setPosition(absPos); MyGUI::IntCoord messageWidgetCoord; messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; messageWidgetCoord.top = textPadding; messageWidgetCoord.width = textSize.width; messageWidgetCoord.height = textSize.height; mMessageWidget->setCoord(messageWidgetCoord); } setVisible(true); } MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { std::vector keywords { "sOk", "sYes" }; for(MyGUI::Button* button : mButtons) { for (const std::string& keyword : keywords) { if (Misc::StringUtils::ciEqual( MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}").asUTF8(), button->getCaption().asUTF8())) { return button; } } } return nullptr; } void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) { buttonActivated (pressed); } void InteractiveMessageBox::buttonActivated (MyGUI::Widget* pressed) { mMarkedToDelete = true; int index = 0; for(const MyGUI::Button* button : mButtons) { if(button == pressed) { mButtonPressed = index; mMessageBoxManager.onButtonPressed(mButtonPressed); return; } index++; } } int InteractiveMessageBox::readPressedButton () { return mButtonPressed; } } openmw-openmw-0.48.0/apps/openmw/mwgui/messagebox.hpp000066400000000000000000000063351445372753700227110ustar00rootroot00000000000000#ifndef MWGUI_MESSAGE_BOX_H #define MWGUI_MESSAGE_BOX_H #include "windowbase.hpp" namespace MyGUI { class Widget; class Button; class EditBox; } namespace MWGui { class InteractiveMessageBox; class MessageBoxManager; class MessageBox; class MessageBoxManager { public: MessageBoxManager (float timePerChar); ~MessageBoxManager (); void onFrame (float frameDuration); void createMessageBox (const std::string& message, bool stat = false); void removeStaticMessageBox (); bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); int getMessagesCount(); const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } /// Remove all message boxes void clear(); bool removeMessageBox (MessageBox *msgbox); /// @param reset Reset the pressed button to -1 after reading it. int readPressedButton (bool reset=true); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; // Note: this delegate unassigns itself after it was fired, i.e. works once. EventHandle_Int eventButtonPressed; void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); } void setVisible(bool value); const std::vector getActiveMessageBoxes(); private: std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; float mMessageBoxSpeed; int mLastButtonPressed; bool mVisible = true; }; class MessageBox : public Layout { public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); void setMessage (const std::string& message); const std::string& getMessage() { return mMessage; }; int getHeight (); void update (int height); void setVisible(bool value); float mCurrentTime; float mMaxTime; protected: MessageBoxManager& mMessageBoxManager; std::string mMessage; MyGUI::EditBox* mMessageWidget; int mBottomPadding; int mNextBoxPadding; }; class InteractiveMessageBox : public WindowModal { public: InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons); void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); MyGUI::Widget* getDefaultKeyFocus() override; bool exit() override { return false; } bool mMarkedToDelete; private: void buttonActivated (MyGUI::Widget* _widget); MessageBoxManager& mMessageBoxManager; MyGUI::EditBox* mMessageWidget; MyGUI::Widget* mButtonsWidget; std::vector mButtons; int mButtonPressed; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/mode.hpp000066400000000000000000000023251445372753700214730ustar00rootroot00000000000000#ifndef MWGUI_MODE_H #define MWGUI_MODE_H namespace MWGui { enum GuiMode { GM_None, GM_Settings, // Settings window GM_Inventory, // Inventory mode GM_Container, GM_Companion, GM_MainMenu, // Main menu mode GM_Journal, // Journal mode GM_Scroll, // Read scroll GM_Book, // Read book GM_Alchemy, // Make potions GM_Repair, GM_Dialogue, // NPC interaction GM_Barter, GM_Rest, GM_SpellBuying, GM_Travel, GM_SpellCreation, GM_Enchanting, GM_Recharge, GM_Training, GM_MerchantRepair, GM_Levelup, // Startup character creation dialogs GM_Name, GM_Race, GM_Birth, GM_Class, GM_ClassGenerate, GM_ClassPick, GM_ClassCreate, GM_Review, GM_Loading, GM_LoadingWallpaper, GM_Jail, GM_QuickKeysMenu }; // Windows shown in inventory mode enum GuiWindow { GW_None = 0, GW_Map = 0x01, GW_Inventory = 0x02, GW_Magic = 0x04, GW_Stats = 0x08, GW_ALL = 0xFF }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/pickpocketitemmodel.cpp000066400000000000000000000112541445372753700245770ustar00rootroot00000000000000#include "pickpocketitemmodel.hpp" #include #include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/pickpocket.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" namespace MWGui { PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& actor, ItemModel *sourceModel, bool hideItems) : mActor(actor), mPickpocketDetected(false) { MWWorld::Ptr player = MWMechanics::getPlayer(); mSourceModel = sourceModel; float chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); // build list of items that player is unable to find when attempts to pickpocket. if (hideItems) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (size_t i = 0; igetItemCount(); ++i) { if (Misc::Rng::roll0to99(prng) > chance) mHiddenItems.push_back(mSourceModel->getItem(i)); } } } bool PickpocketItemModel::allowedToUseItems() const { return false; } ItemStack PickpocketItemModel::getItem (ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); if (mItems.size() <= static_cast(index)) throw std::runtime_error("Item index out of range"); return mItems[index]; } size_t PickpocketItemModel::getItemCount() { return mItems.size(); } void PickpocketItemModel::update() { mSourceModel->update(); mItems.clear(); for (size_t i = 0; igetItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); // Bound items may not be stolen if (item.mFlags & ItemStack::Flag_Bound) continue; if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() && item.mType != ItemStack::Type_Equipped) mItems.push_back(item); } } void PickpocketItemModel::removeItem (const ItemStack &item, size_t count) { ProxyItemModel::removeItem(item, count); } bool PickpocketItemModel::onDropItem(const MWWorld::Ptr &item, int count) { // don't allow "reverse pickpocket" (it will be handled by scripts after 1.0) return false; } void PickpocketItemModel::onClose() { // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened) if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Container) // If it was already detected while taking an item, no need to check now || mPickpocketDetected) return; MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::Pickpocket pickpocket(player, mActor); if (pickpocket.finish()) { MWBase::Environment::get().getMechanicsManager()->commitCrime( player, mActor, MWBase::MechanicsManager::OT_Pickpocket, std::string(), 0, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); mPickpocketDetected = true; } } bool PickpocketItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { if (mActor.getClass().getCreatureStats(mActor).getKnockedDown()) return mSourceModel->onTakeItem(item, count); bool success = stealItem(item, count); if (success) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count, false); } return success; } bool PickpocketItemModel::stealItem(const MWWorld::Ptr &item, int count) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::Pickpocket pickpocket(player, mActor); if (pickpocket.pick(item, count)) { MWBase::Environment::get().getMechanicsManager()->commitCrime( player, mActor, MWBase::MechanicsManager::OT_Pickpocket, std::string(), 0, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); mPickpocketDetected = true; return false; } else player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); return true; } } openmw-openmw-0.48.0/apps/openmw/mwgui/pickpocketitemmodel.hpp000066400000000000000000000021231445372753700245770ustar00rootroot00000000000000#ifndef MWGUI_PICKPOCKET_ITEM_MODEL_H #define MWGUI_PICKPOCKET_ITEM_MODEL_H #include "itemmodel.hpp" namespace MWGui { /// @brief The pickpocket item model randomly hides item stacks based on a specified chance. Equipped items are always hidden. class PickpocketItemModel : public ProxyItemModel { public: PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel, bool hideItems=true); bool allowedToUseItems() const override; ItemStack getItem (ModelIndex index) override; size_t getItemCount() override; void update() override; void removeItem (const ItemStack& item, size_t count) override; void onClose() override; bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; protected: MWWorld::Ptr mActor; bool mPickpocketDetected; bool stealItem(const MWWorld::Ptr &item, int count); private: std::vector mHiddenItems; std::vector mItems; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/postprocessorhud.cpp000066400000000000000000000436421445372753700241770ustar00rootroot00000000000000#include "postprocessorhud.hpp" #include #include #include #include #include #include #include #include #include #include "../mwrender/postprocessor.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" namespace MWGui { void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) { if (MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) return; MyGUI::ListBox::onKeyButtonPressed(key, ch); } PostProcessorHud::PostProcessorHud() : WindowBase("openmw_postprocessor_hud.layout") { getWidget(mActiveList, "ActiveList"); getWidget(mInactiveList, "InactiveList"); getWidget(mConfigLayout, "ConfigLayout"); getWidget(mFilter, "Filter"); getWidget(mButtonActivate, "ButtonActivate"); getWidget(mButtonDeactivate, "ButtonDeactivate"); getWidget(mButtonUp, "ButtonUp"); getWidget(mButtonDown, "ButtonDown"); mButtonActivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyActivatePressed); mButtonDeactivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyDeactivatePressed); mButtonUp->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderUpPressed); mButtonDown->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderDownPressed); mActiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); mInactiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); mActiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); mInactiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); mFilter->eventEditTextChange += MyGUI::newDelegate(this, &PostProcessorHud::notifyFilterChanged); mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize); mShaderInfo = mConfigLayout->createWidget("HeaderText", {}, MyGUI::Align::Default); mShaderInfo->setUserString("VStretch", "true"); mShaderInfo->setUserString("HStretch", "true"); mShaderInfo->setTextAlign(MyGUI::Align::Left | MyGUI::Align::Top); mShaderInfo->setEditReadOnly(true); mShaderInfo->setEditWordWrap(true); mShaderInfo->setEditMultiLine(true); mShaderInfo->setNeedMouseFocus(false); mConfigLayout->setVisibleVScroll(true); mConfigArea = mConfigLayout->createWidget("", {}, MyGUI::Align::Default); mConfigLayout->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); mConfigArea->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); } void PostProcessorHud::notifyFilterChanged(MyGUI::EditBox* sender) { updateTechniques(); } void PostProcessorHud::notifyWindowResize(MyGUI::Window* sender) { layout(); } void PostProcessorHud::notifyResetButtonClicked(MyGUI::Widget* sender) { for (size_t i = 1; i < mConfigArea->getChildCount(); ++i) { if (auto* child = dynamic_cast(mConfigArea->getChildAt(i))) child->toDefault(); } } void PostProcessorHud::notifyListChangePosition(MyGUI::ListBox* sender, size_t index) { if (sender == mActiveList) mInactiveList->clearIndexSelected(); else if (sender == mInactiveList) mActiveList->clearIndexSelected(); if (index >= sender->getItemCount()) return; updateConfigView(sender->getItemNameAt(index)); } void PostProcessorHud::toggleTechnique(bool enabled) { auto* list = enabled ? mInactiveList : mActiveList; size_t selected = list->getIndexSelected(); if (selected != MyGUI::ITEM_NONE) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); mOverrideHint = list->getItemNameAt(selected); auto technique = *list->getItemDataAt>(selected); if (technique->getDynamic()) return; if (enabled) processor->enableTechnique(technique); else processor->disableTechnique(technique); processor->saveChain(); } } void PostProcessorHud::notifyActivatePressed(MyGUI::Widget* sender) { toggleTechnique(true); } void PostProcessorHud::notifyDeactivatePressed(MyGUI::Widget* sender) { toggleTechnique(false); } void PostProcessorHud::moveShader(Direction direction) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); size_t selected = mActiveList->getIndexSelected(); if (selected == MyGUI::ITEM_NONE) return; int index = direction == Direction::Up ? static_cast(selected) - 1 : selected + 1; index = std::clamp(index, 0, mActiveList->getItemCount() - 1); if (static_cast(index) != selected) { auto technique = *mActiveList->getItemDataAt>(selected); if (technique->getDynamic()) return; if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error) processor->saveChain(); } } void PostProcessorHud::notifyShaderUpPressed(MyGUI::Widget* sender) { moveShader(Direction::Up); } void PostProcessorHud::notifyShaderDownPressed(MyGUI::Widget* sender) { moveShader(Direction::Down); } void PostProcessorHud::notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch) { MyGUI::ListBox* list = static_cast(sender); if (list->getIndexSelected() == MyGUI::ITEM_NONE) return; if (key == MyGUI::KeyCode::ArrowLeft && list == mActiveList) { if (MyGUI::InputManager::getInstance().isShiftPressed()) { toggleTechnique(false); } else { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mInactiveList); mActiveList->clearIndexSelected(); select(mInactiveList, 0); } } else if (key == MyGUI::KeyCode::ArrowRight && list == mInactiveList) { if (MyGUI::InputManager::getInstance().isShiftPressed()) { toggleTechnique(true); } else { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mActiveList); mInactiveList->clearIndexSelected(); select(mActiveList, 0); } } else if (list == mActiveList && MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) { moveShader(key == MyGUI::KeyCode::ArrowUp ? Direction::Up : Direction::Down); } } void PostProcessorHud::onOpen() { toggleMode(Settings::ShaderManager::Mode::Debug); updateTechniques(); } void PostProcessorHud::onClose() { toggleMode(Settings::ShaderManager::Mode::Normal); } void PostProcessorHud::layout() { constexpr int padding = 12; constexpr int padding2 = padding * 2; mShaderInfo->setCoord(padding, padding, mConfigLayout->getSize().width - padding2 - padding, mShaderInfo->getTextSize().height); int totalHeight = mShaderInfo->getTop() + mShaderInfo->getTextSize().height + padding; mConfigArea->setCoord({padding, totalHeight, mShaderInfo->getSize().width, mConfigLayout->getHeight()}); int childHeights = 0; MyGUI::EnumeratorWidgetPtr enumerator = mConfigArea->getEnumerator(); while (enumerator.next()) { enumerator.current()->setCoord(padding, childHeights + padding, mShaderInfo->getSize().width - padding2, enumerator.current()->getHeight()); childHeights += enumerator.current()->getHeight() + padding; } totalHeight += childHeights; mConfigArea->setSize(mConfigArea->getWidth(), childHeights); mConfigLayout->setCanvasSize(mConfigLayout->getWidth() - padding2, totalHeight); mConfigLayout->setSize(mConfigLayout->getWidth(), mConfigLayout->getParentSize().height - padding2); } void PostProcessorHud::notifyMouseWheel(MyGUI::Widget *sender, int rel) { int offset = mConfigLayout->getViewOffset().top + rel * 0.3; if (offset > 0) mConfigLayout->setViewOffset(MyGUI::IntPoint(0, 0)); else mConfigLayout->setViewOffset(MyGUI::IntPoint(0, static_cast(offset))); } void PostProcessorHud::select(ListWrapper* list, size_t index) { list->setIndexSelected(index); notifyListChangePosition(list, index); } void PostProcessorHud::toggleMode(Settings::ShaderManager::Mode mode) { Settings::ShaderManager::get().setMode(mode); MWBase::Environment::get().getWorld()->getPostProcessor()->toggleMode(); if (!isVisible()) return; if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); } void PostProcessorHud::updateConfigView(const std::string& name) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); auto technique = processor->loadTechnique(name); if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) return; while (mConfigArea->getChildCount() > 0) MyGUI::Gui::getInstance().destroyWidget(mConfigArea->getChildAt(0)); mShaderInfo->setCaption(""); std::ostringstream ss; const std::string NA = "#{Interface:NotAvailableShort}"; const std::string endl = "\n"; std::string author = technique->getAuthor().empty() ? NA : std::string(technique->getAuthor()); std::string version = technique->getVersion().empty() ? NA : std::string(technique->getVersion()); std::string description = technique->getDescription().empty() ? NA : std::string(technique->getDescription()); auto serializeBool = [](bool value) { return value ? "#{sYes}" : "#{sNo}"; }; const auto flags = technique->getFlags(); const auto flag_interior = serializeBool(!(flags & fx::Technique::Flag_Disable_Interiors)); const auto flag_exterior = serializeBool(!(flags & fx::Technique::Flag_Disable_Exteriors)); const auto flag_underwater = serializeBool(!(flags & fx::Technique::Flag_Disable_Underwater)); const auto flag_abovewater = serializeBool(!(flags & fx::Technique::Flag_Disable_Abovewater)); switch (technique->getStatus()) { case fx::Technique::Status::Success: case fx::Technique::Status::Uncompiled: { if (technique->getDynamic()) ss << "#{fontcolourhtml=header}#{PostProcessing:ShaderLocked}: #{fontcolourhtml=normal} #{PostProcessing:ShaderLockedDescription}" << endl << endl; ss << "#{fontcolourhtml=header}#{PostProcessing:Author}: #{fontcolourhtml=normal} " << author << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:Version}: #{fontcolourhtml=normal} " << version << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:Description}: #{fontcolourhtml=normal} " << description << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:InInteriors}: #{fontcolourhtml=normal} " << flag_interior << "#{fontcolourhtml=header} #{PostProcessing:InExteriors}: #{fontcolourhtml=normal} " << flag_exterior << "#{fontcolourhtml=header} #{PostProcessing:Underwater}: #{fontcolourhtml=normal} " << flag_underwater << "#{fontcolourhtml=header} #{PostProcessing:Abovewater}: #{fontcolourhtml=normal} " << flag_abovewater; break; } case fx::Technique::Status::Parse_Error: ss << "#{fontcolourhtml=negative}Shader Compile Error: #{fontcolourhtml=normal} <" << std::string(technique->getName()) << "> failed to compile." << endl << endl << technique->getLastError(); break; case fx::Technique::Status::File_Not_exists: break; } mShaderInfo->setCaptionWithReplacing(ss.str()); if (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug) { if (technique->getUniformMap().size() > 0) { MyGUI::Button* resetButton = mConfigArea->createWidget("MW_Button", {0,0,0,24}, MyGUI::Align::Default); resetButton->setCaptionWithReplacing("#{PostProcessing:ResetShader}"); resetButton->setTextAlign(MyGUI::Align::Center); resetButton->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); resetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyResetButtonClicked); } for (const auto& uniform : technique->getUniformMap()) { if (!uniform->mStatic || uniform->mSamplerType) continue; if (!uniform->mHeader.empty()) { Gui::AutoSizedTextBox* divider = mConfigArea->createWidget("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default); divider->setNeedMouseFocus(false); divider->setCaptionWithReplacing(uniform->mHeader); } fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default); uwidget->init(uniform); uwidget->getLabel()->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); } } layout(); } void PostProcessorHud::updateTechniques() { if (!isVisible()) return; std::string hint; ListWrapper* hintWidget = nullptr; if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) { hint = mInactiveList->getItemNameAt(mInactiveList->getIndexSelected()); hintWidget = mInactiveList; } else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) { hint = mActiveList->getItemNameAt(mActiveList->getIndexSelected()); hintWidget = mActiveList; } mInactiveList->removeAllItems(); mActiveList->removeAllItems(); auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); for (const auto& [name, _] : processor->getTechniqueMap()) { auto technique = processor->loadTechnique(name); if (!technique) continue; if (!technique->getHidden() && !processor->isTechniqueEnabled(technique)) { std::string lowerName = Utf8Stream::lowerCaseUtf8(name); std::string lowerCaption = mFilter->getCaption(); lowerCaption = Utf8Stream::lowerCaseUtf8(lowerCaption); if (lowerName.find(lowerCaption) != std::string::npos) mInactiveList->addItem(name, technique); } } for (auto technique : processor->getTechniques()) { if (!technique->getHidden()) mActiveList->addItem(technique->getName(), technique); } auto tryFocus = [this](ListWrapper* widget, const std::string& hint) { MyGUI::Widget* oldFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (oldFocus == mFilter) return; size_t index = widget->findItemIndexWith(hint); if (index != MyGUI::ITEM_NONE) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(widget); select(widget, index); } }; if (!mOverrideHint.empty()) { tryFocus(mActiveList, mOverrideHint); tryFocus(mInactiveList, mOverrideHint); mOverrideHint.clear(); } else if (hintWidget && !hint.empty()) tryFocus(hintWidget, hint); } void PostProcessorHud::registerMyGUIComponents() { MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/postprocessorhud.hpp000066400000000000000000000044241445372753700241770ustar00rootroot00000000000000#ifndef MYGUI_POSTPROCESSOR_HUD_H #define MYGUI_POSTPROCESSOR_HUD_H #include "windowbase.hpp" #include #include namespace MyGUI { class ScrollView; class EditBox; class TabItem; } namespace Gui { class AutoSizedButton; class AutoSizedEditBox; } namespace MWGui { class PostProcessorHud : public WindowBase { class ListWrapper final : public MyGUI::ListBox { MYGUI_RTTI_DERIVED(ListWrapper) protected: void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) override; }; public: PostProcessorHud(); void onOpen() override; void onClose() override; void updateTechniques(); void toggleMode(Settings::ShaderManager::Mode mode); static void registerMyGUIComponents(); private: void notifyWindowResize(MyGUI::Window* sender); void notifyFilterChanged(MyGUI::EditBox* sender); void updateConfigView(const std::string& name); void notifyResetButtonClicked(MyGUI::Widget* sender); void notifyListChangePosition(MyGUI::ListBox* sender, size_t index); void notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch); void notifyActivatePressed(MyGUI::Widget* sender); void notifyDeactivatePressed(MyGUI::Widget* sender); void notifyShaderUpPressed(MyGUI::Widget* sender); void notifyShaderDownPressed(MyGUI::Widget* sender); void notifyMouseWheel(MyGUI::Widget *sender, int rel); enum class Direction { Up, Down }; void moveShader(Direction direction); void toggleTechnique(bool enabled); void select(ListWrapper* list, size_t index); void layout(); ListWrapper* mActiveList; ListWrapper* mInactiveList; Gui::AutoSizedButton* mButtonActivate; Gui::AutoSizedButton* mButtonDeactivate; Gui::AutoSizedButton* mButtonDown; Gui::AutoSizedButton* mButtonUp; MyGUI::ScrollView* mConfigLayout; MyGUI::Widget* mConfigArea; MyGUI::EditBox* mFilter; Gui::AutoSizedEditBox* mShaderInfo; std::string mOverrideHint; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/quickkeysmenu.cpp000066400000000000000000000536701445372753700234500ustar00rootroot00000000000000#include "quickkeysmenu.hpp" #include #include #include #include #include #include #include #include #include #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "itemselection.hpp" #include "spellview.hpp" #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" namespace MWGui { QuickKeysMenu::QuickKeysMenu() : WindowBase("openmw_quickkeys_menu.layout") , mKey(std::vector(10)) , mSelected(nullptr) , mActivated(nullptr) , mAssignDialog(nullptr) , mItemSelectionDialog(nullptr) , mMagicSelectionDialog(nullptr) { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); mMainWidget->setSize(mMainWidget->getWidth(), mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked); center(); for (int i = 0; i < 10; ++i) { mKey[i].index = i+1; getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1)); mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); unassign(&mKey[i]); } } void QuickKeysMenu::clear() { mActivated = nullptr; for (int i=0; i<10; ++i) { unassign(&mKey[i]); } } QuickKeysMenu::~QuickKeysMenu() { delete mAssignDialog; delete mItemSelectionDialog; delete mMagicSelectionDialog; } inline void QuickKeysMenu::validate(int index) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); switch (mKey[index].type) { case Type_Unassigned: case Type_HandToHand: case Type_Magic: break; case Type_Item: case Type_MagicItem: { MWWorld::Ptr item = *mKey[index].button->getUserData(); // Make sure the item is available and is not broken if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement item = store.findReplacement(mKey[index].id); if (item) mKey[index].button->setUserData(MWWorld::Ptr(item)); break; } } } } void QuickKeysMenu::onOpen() { WindowBase::onOpen(); // Quick key index for (int index = 0; index < 10; ++index) { validate(index); } } void QuickKeysMenu::onClose() { WindowBase::onClose(); if (mAssignDialog) mAssignDialog->setVisible(false); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::unassign(keyData* key) { key->button->clearUserStrings(); key->button->setItem(MWWorld::Ptr()); while (key->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0)); if (key->index == 10) { key->type = Type_HandToHand; MyGUI::ImageBox* image = key->button->createWidget("ImageBox", MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default); image->setImageTexture("icons\\k\\stealth_handtohand.dds"); image->setNeedMouseFocus(false); } else { key->type = Type_Unassigned; key->id.clear(); key->name.clear(); MyGUI::TextBox* textBox = key->button->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); textBox->setTextAlign(MyGUI::Align::Center); textBox->setCaption(MyGUI::utility::toString(key->index)); textBox->setNeedMouseFocus(false); } } void QuickKeysMenu::onQuickKeyButtonClicked(MyGUI::Widget* sender) { int index = -1; for (int i = 0; i < 10; ++i) { if (sender == mKey[i].button || sender->getParent() == mKey[i].button) { index = i; break; } } assert(index != -1); if (index < 0) { mSelected = nullptr; return; } mSelected = &mKey[index]; // prevent reallocation of zero key from Type_HandToHand if(mSelected->index == 10) return; // open assign dialog if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this); mAssignDialog->setVisible(true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu); } void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender) { if (!mItemSelectionDialog) { mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible(false); } void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender) { if (!mMagicSelectionDialog) { mMagicSelectionDialog = new MagicSelectionDialog(this); } mMagicSelectionDialog->setVisible(true); mAssignDialog->setVisible(false); } void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender) { unassign(mSelected); mAssignDialog->setVisible(false); } void QuickKeysMenu::onCancelButtonClicked(MyGUI::Widget* sender) { mAssignDialog->setVisible(false); } void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { assert(mSelected); while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); mSelected->type = Type_Item; mSelected->id = item.getCellRef().getRefId(); mSelected->name = item.getClass().getName(item); mSelected->button->setItem(item, ItemWidget::Barter); mSelected->button->setUserString("ToolTipType", "ItemPtr"); mSelected->button->setUserData(item); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignItemCancel() { mItemSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagicItem(MWWorld::Ptr item) { assert(mSelected); while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); mSelected->type = Type_MagicItem; mSelected->id = item.getCellRef().getRefId(); mSelected->name = item.getClass().getName(item); float scale = 1.f; MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic_magic.dds"); if (texture) scale = texture->getHeight() / 64.f; mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(0, 0, 44*scale, 44*scale)); mSelected->button->setIcon(item); mSelected->button->setUserString("ToolTipType", "ItemPtr"); mSelected->button->setUserData(MWWorld::Ptr(item)); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagic(const std::string& spellId) { assert(mSelected); while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell* spell = esmStore.get().find(spellId); mSelected->type = Type_Magic; mSelected->id = spellId; mSelected->name = spell->mName; mSelected->button->setItem(MWWorld::Ptr()); mSelected->button->setUserString("ToolTipType", "Spell"); mSelected->button->setUserString("Spell", spellId); // use the icon of the first effect const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); std::string path = effect->mIcon; int slashPos = path.rfind('\\'); path.insert(slashPos+1, "b_"); path = Misc::ResourceHelpers::correctIconPath(path, MWBase::Environment::get().getResourceSystem()->getVFS()); float scale = 1.f; MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic.dds"); if (texture) scale = texture->getHeight() / 64.f; mSelected->button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(0, 0, 44*scale, 44*scale)); mSelected->button->setIcon(path); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagicCancel() { mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::updateActivatedQuickKey() { // there is no delayed action, nothing to do. if (!mActivated) return; activateQuickKey(mActivated->index); } void QuickKeysMenu::activateQuickKey(int index) { assert(index >= 1 && index <= 10); keyData *key = &mKey[index-1]; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); validate(index-1); // Delay action executing, // if player is busy for now (casting a spell, attacking someone, etc.) bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) || playerStats.getKnockedDown() || playerStats.getHitRecovery(); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); bool isReturnNeeded = (!godmode && playerStats.isParalyzed()) || playerStats.isDead(); if (isReturnNeeded) { return; } else if (isDelayNeeded) { mActivated = key; return; } else { mActivated = nullptr; } if (key->type == Type_Item || key->type == Type_MagicItem) { MWWorld::Ptr item = *key->button->getUserData(); MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) { if (*it == item) break; } if (it == store.end()) item = nullptr; // check the item is available and not broken if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { item = store.findReplacement(key->id); if (!item || item.getRefData().getCount() < 1) { MWBase::Environment::get().getWindowManager()->messageBox( "#{sQuickMenu5} " + key->name); return; } } if (key->type == Type_Item) { if (!store.isEquipped(item)) MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand if (rightHand != store.end() && item == *rightHand) { MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon); } } else if (key->type == Type_MagicItem) { // equip, if it can be equipped and isn't yet equipped if (!item.getClass().getEquipmentSlots(item).first.empty() && !store.isEquipped(item)) { MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) return; } store.setSelectedEnchantItem(it); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Spell); } } else if (key->type == Type_Magic) { std::string spellId = key->id; // Make sure the player still has this spell MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); if (!spells.hasSpell(spellId)) { MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); return; } store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager() ->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Spell); } else if (key->type == Type_HandToHand) { store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon); } } // --------------------------------------------------------------------------------------------------------- QuickKeysMenuAssign::QuickKeysMenuAssign (QuickKeysMenu* parent) : WindowModal("openmw_quickkeys_menu_assign.layout") , mParent(parent) { getWidget(mLabel, "Label"); getWidget(mItemButton, "ItemButton"); getWidget(mMagicButton, "MagicButton"); getWidget(mUnassignButton, "UnassignButton"); getWidget(mCancelButton, "CancelButton"); mItemButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onItemButtonClicked); mMagicButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onMagicButtonClicked); mUnassignButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onUnassignButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onCancelButtonClicked); int maxWidth = mLabel->getTextSize ().width + 24; maxWidth = std::max(maxWidth, mItemButton->getTextSize ().width + 24); maxWidth = std::max(maxWidth, mMagicButton->getTextSize ().width + 24); maxWidth = std::max(maxWidth, mUnassignButton->getTextSize ().width + 24); maxWidth = std::max(maxWidth, mCancelButton->getTextSize ().width + 24); mMainWidget->setSize(maxWidth + 24, mMainWidget->getHeight()); mLabel->setSize(maxWidth, mLabel->getHeight()); mItemButton->setCoord((maxWidth - mItemButton->getTextSize().width-24)/2 + 8, mItemButton->getTop(), mItemButton->getTextSize().width + 24, mItemButton->getHeight()); mMagicButton->setCoord((maxWidth - mMagicButton->getTextSize().width-24)/2 + 8, mMagicButton->getTop(), mMagicButton->getTextSize().width + 24, mMagicButton->getHeight()); mUnassignButton->setCoord((maxWidth - mUnassignButton->getTextSize().width-24)/2 + 8, mUnassignButton->getTop(), mUnassignButton->getTextSize().width + 24, mUnassignButton->getHeight()); mCancelButton->setCoord((maxWidth - mCancelButton->getTextSize().width-24)/2 + 8, mCancelButton->getTop(), mCancelButton->getTextSize().width + 24, mCancelButton->getHeight()); center(); } void QuickKeysMenu::write(ESM::ESMWriter &writer) { writer.startRecord(ESM::REC_KEYS); ESM::QuickKeys keys; // NB: The quick key with index 9 always has Hand-to-Hand type and must not be saved for (int i=0; i<9; ++i) { ItemWidget* button = mKey[i].button; int type = mKey[i].type; ESM::QuickKeys::QuickKey key; key.mType = type; switch (type) { case Type_Unassigned: case Type_HandToHand: break; case Type_Item: case Type_MagicItem: { MWWorld::Ptr item = *button->getUserData(); key.mId = item.getCellRef().getRefId(); break; } case Type_Magic: std::string spellId = button->getUserString("Spell"); key.mId = spellId; break; } keys.mKeys.push_back(key); } keys.save(writer); writer.endRecord(ESM::REC_KEYS); } void QuickKeysMenu::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type != ESM::REC_KEYS) return; ESM::QuickKeys keys; keys.load(reader); MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); int i=0; for (ESM::QuickKeys::QuickKey& quickKey : keys.mKeys) { // NB: The quick key with index 9 always has Hand-to-Hand type and must not be loaded if (i >= 9) return; mSelected = &mKey[i]; switch (quickKey.mType) { case Type_Magic: if (MWBase::Environment::get().getWorld()->getStore().get().search(quickKey.mId)) onAssignMagic(quickKey.mId); break; case Type_Item: case Type_MagicItem: { // Find the item by id MWWorld::Ptr item = store.findReplacement(quickKey.mId); if (item.isEmpty()) unassign(mSelected); else { if (quickKey.mType == Type_Item) onAssignItem(item); else // if (quickKey.mType == Type_MagicItem) onAssignMagicItem(item); } break; } case Type_Unassigned: case Type_HandToHand: unassign(mSelected); break; } ++i; } } // --------------------------------------------------------------------------------------------------------- MagicSelectionDialog::MagicSelectionDialog(QuickKeysMenu* parent) : WindowModal("openmw_magicselection_dialog.layout") , mParent(parent) { getWidget(mCancelButton, "CancelButton"); getWidget(mMagicList, "MagicList"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onCancelButtonClicked); mMagicList->setShowCostColumn(false); mMagicList->setHighlightSelected(false); mMagicList->eventSpellClicked += MyGUI::newDelegate(this, &MagicSelectionDialog::onModelIndexSelected); center(); } void MagicSelectionDialog::onCancelButtonClicked (MyGUI::Widget *sender) { exit(); } bool MagicSelectionDialog::exit() { mParent->onAssignMagicCancel(); return true; } void MagicSelectionDialog::onOpen () { WindowModal::onOpen(); mMagicList->setModel(new SpellModel(MWMechanics::getPlayer())); mMagicList->resetScrollbars(); } void MagicSelectionDialog::onModelIndexSelected(SpellModel::ModelIndex index) { const Spell& spell = mMagicList->getModel()->getItem(index); if (spell.mType == Spell::Type_EnchantedItem) mParent->onAssignMagicItem(spell.mItem); else mParent->onAssignMagic(spell.mId); } } openmw-openmw-0.48.0/apps/openmw/mwgui/quickkeysmenu.hpp000066400000000000000000000060651445372753700234510ustar00rootroot00000000000000#ifndef MWGUI_QUICKKEYS_H #define MWGUI_QUICKKEYS_H #include "windowbase.hpp" #include "spellmodel.hpp" namespace MWGui { class QuickKeysMenuAssign; class ItemSelectionDialog; class MagicSelectionDialog; class ItemWidget; class SpellView; class QuickKeysMenu : public WindowBase { public: QuickKeysMenu(); ~QuickKeysMenu(); void onResChange(int, int) override { center(); } void onItemButtonClicked(MyGUI::Widget* sender); void onMagicButtonClicked(MyGUI::Widget* sender); void onUnassignButtonClicked(MyGUI::Widget* sender); void onCancelButtonClicked(MyGUI::Widget* sender); void onAssignItem (MWWorld::Ptr item); void onAssignItemCancel (); void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); void onOpen() override; void onClose() override; void activateQuickKey(int index); void updateActivatedQuickKey(); /// @note This enum is serialized, so don't move the items around! enum QuickKeyType { Type_Item, Type_Magic, Type_MagicItem, Type_Unassigned, Type_HandToHand }; void write (ESM::ESMWriter& writer); void readRecord (ESM::ESMReader& reader, uint32_t type); void clear() override; private: struct keyData { int index; ItemWidget* button; QuickKeysMenu::QuickKeyType type; std::string id; std::string name; keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {} }; std::vector mKey; keyData* mSelected; keyData* mActivated; MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; QuickKeysMenuAssign* mAssignDialog; ItemSelectionDialog* mItemSelectionDialog; MagicSelectionDialog* mMagicSelectionDialog; void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); // Check if quick key is still valid inline void validate(int index); void unassign(keyData* key); }; class QuickKeysMenuAssign : public WindowModal { public: QuickKeysMenuAssign(QuickKeysMenu* parent); private: MyGUI::TextBox* mLabel; MyGUI::Button* mItemButton; MyGUI::Button* mMagicButton; MyGUI::Button* mUnassignButton; MyGUI::Button* mCancelButton; QuickKeysMenu* mParent; }; class MagicSelectionDialog : public WindowModal { public: MagicSelectionDialog(QuickKeysMenu* parent); void onOpen() override; bool exit() override; private: MyGUI::Button* mCancelButton; SpellView* mMagicList; QuickKeysMenu* mParent; void onCancelButtonClicked (MyGUI::Widget* sender); void onModelIndexSelected(SpellModel::ModelIndex index); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/race.cpp000066400000000000000000000371251445372753700214620ustar00rootroot00000000000000#include "race.hpp" #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwrender/characterpreview.hpp" #include "tooltips.hpp" namespace { int wrap(int index, int max) { if (index < 0) return max - 1; else if (index >= max) return 0; else return index; } bool sortRaces(const std::pair& left, const std::pair& right) { return left.second.compare(right.second) < 0; } } namespace MWGui { RaceDialog::RaceDialog(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : WindowModal("openmw_chargen_race.layout") , mParent(parent) , mResourceSystem(resourceSystem) , mGenderIndex(0) , mFaceIndex(0) , mHairIndex(0) , mCurrentAngle(0) , mPreviewDirty(true) { // Centre dialog center(); setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance")); getWidget(mPreviewImage, "PreviewImage"); mPreviewImage->eventMouseWheel += MyGUI::newDelegate(this, &RaceDialog::onPreviewScroll); getWidget(mHeadRotate, "HeadRotate"); mHeadRotate->setScrollRange(1000); mHeadRotate->setScrollPosition(500); mHeadRotate->setScrollViewPage(50); mHeadRotate->setScrollPage(50); mHeadRotate->setScrollWheelPage(50); mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons MyGUI::Button *prevButton, *nextButton; setText("GenderChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu2", "Change Sex")); getWidget(prevButton, "PrevGenderButton"); getWidget(nextButton, "NextGenderButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousGender); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextGender); setText("FaceChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu3", "Change Face")); getWidget(prevButton, "PrevFaceButton"); getWidget(nextButton, "NextFaceButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousFace); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextFace); setText("HairChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu4", "Change Hair")); getWidget(prevButton, "PrevHairButton"); getWidget(nextButton, "NextHairButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextHair); setText("RaceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu5", "Race")); getWidget(mRaceList, "RaceList"); mRaceList->setScrollVisible(true); mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept); mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus")); getWidget(mSkillList, "SkillList"); setText("SpellPowerT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials")); getWidget(mSpellPowerList, "SpellPowerList"); MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); updateRaces(); updateSkills(); updateSpellPowers(); } void RaceDialog::setNextButtonShow(bool shown) { MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); else okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } void RaceDialog::onOpen() { WindowModal::onOpen(); updateRaces(); updateSkills(); updateSpellPowers(); mPreviewImage->setRenderItemTexture(nullptr); mPreview.reset(nullptr); mPreviewTexture.reset(nullptr); mPreview = std::make_unique(mParent, mResourceSystem); mPreview->rebuild(); mPreview->setAngle (mCurrentAngle); mPreviewTexture = std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet()); mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); const ESM::NPC& proto = mPreview->getPrototype(); setRaceId(proto.mRace); setGender(proto.isMale() ? GM_Male : GM_Female); recountParts(); for (unsigned int i=0; igetScrollRange()/2+mHeadRotate->getScrollRange()/10; mHeadRotate->setScrollPosition(initialPos); onHeadRotate(mHeadRotate, initialPos); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mRaceList); } void RaceDialog::setRaceId(const std::string &raceId) { mCurrentRaceId = raceId; mRaceList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mRaceList->getItemCount(); for (size_t i = 0; i < count; ++i) { if (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt(i), raceId)) { mRaceList->setIndexSelected(i); break; } } updateSkills(); updateSpellPowers(); } void RaceDialog::onClose() { WindowModal::onClose(); mPreviewImage->setRenderItemTexture(nullptr); mPreviewTexture.reset(nullptr); mPreview.reset(nullptr); } // widget controls void RaceDialog::onOkClicked(MyGUI::Widget* _sender) { if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void RaceDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } void RaceDialog::onPreviewScroll(MyGUI::Widget*, int _delta) { size_t oldPos = mHeadRotate->getScrollPosition(); size_t maxPos = mHeadRotate->getScrollRange() - 1; size_t scrollPage = mHeadRotate->getScrollWheelPage(); if (_delta < 0) mHeadRotate->setScrollPosition(oldPos + std::min(maxPos - oldPos, scrollPage)); else mHeadRotate->setScrollPosition(oldPos - std::min(oldPos, scrollPage)); onHeadRotate(mHeadRotate, mHeadRotate->getScrollPosition()); } void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2; mPreview->setAngle (angle); mCurrentAngle = angle; } void RaceDialog::onSelectPreviousGender(MyGUI::Widget*) { mGenderIndex = wrap(mGenderIndex - 1, 2); recountParts(); updatePreview(); } void RaceDialog::onSelectNextGender(MyGUI::Widget*) { mGenderIndex = wrap(mGenderIndex + 1, 2); recountParts(); updatePreview(); } void RaceDialog::onSelectPreviousFace(MyGUI::Widget*) { mFaceIndex = wrap(mFaceIndex - 1, mAvailableHeads.size()); updatePreview(); } void RaceDialog::onSelectNextFace(MyGUI::Widget*) { mFaceIndex = wrap(mFaceIndex + 1, mAvailableHeads.size()); updatePreview(); } void RaceDialog::onSelectPreviousHair(MyGUI::Widget*) { mHairIndex = wrap(mHairIndex - 1, mAvailableHairs.size()); updatePreview(); } void RaceDialog::onSelectNextHair(MyGUI::Widget*) { mHairIndex = wrap(mHairIndex + 1, mAvailableHairs.size()); updatePreview(); } void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) return; const std::string *raceId = mRaceList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId)) return; mCurrentRaceId = *raceId; recountParts(); updatePreview(); updateSkills(); updateSpellPowers(); } void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) { onSelectRace(_sender, _index); if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void RaceDialog::getBodyParts (int part, std::vector& out) { out.clear(); const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); for (const ESM::BodyPart& bodypart : store) { if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; if (bodypart.mData.mPart != static_cast(part)) continue; if (mGenderIndex != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) continue; bool firstPerson = (bodypart.mId.size() >= 3) && bodypart.mId[bodypart.mId.size()-3] == '1' && bodypart.mId[bodypart.mId.size()-2] == 's' && bodypart.mId[bodypart.mId.size()-1] == 't'; if (firstPerson) continue; if (Misc::StringUtils::ciEqual(bodypart.mRace, mCurrentRaceId)) out.push_back(bodypart.mId); } } void RaceDialog::recountParts() { getBodyParts(ESM::BodyPart::MP_Hair, mAvailableHairs); getBodyParts(ESM::BodyPart::MP_Head, mAvailableHeads); mFaceIndex = 0; mHairIndex = 0; } // update widget content void RaceDialog::updatePreview() { ESM::NPC record = mPreview->getPrototype(); record.mRace = mCurrentRaceId; record.setIsMale(mGenderIndex == 0); if (mFaceIndex >= 0 && mFaceIndex < int(mAvailableHeads.size())) record.mHead = mAvailableHeads[mFaceIndex]; if (mHairIndex >= 0 && mHairIndex < int(mAvailableHairs.size())) record.mHair = mAvailableHairs[mHairIndex]; try { mPreview->setPrototype(record); } catch (std::exception& e) { Log(Debug::Error) << "Error creating preview: " << e.what(); } } void RaceDialog::updateRaces() { mRaceList->removeAllItems(); const MWWorld::Store &races = MWBase::Environment::get().getWorld()->getStore().get(); std::vector > items; // ID, name for (const ESM::Race& race : races) { bool playable = race.mData.mFlags & ESM::Race::Playable; if (!playable) // Only display playable races continue; items.emplace_back(race.mId, race.mName); } std::sort(items.begin(), items.end(), sortRaces); int index = 0; for (auto& item : items) { mRaceList->addItem(item.second, item.first); if (Misc::StringUtils::ciEqual(item.first, mCurrentRaceId)) mRaceList->setIndexSelected(index); ++index; } } void RaceDialog::updateSkills() { for (MyGUI::Widget* widget : mSkillItems) { MyGUI::Gui::getInstance().destroyWidget(widget); } mSkillItems.clear(); if (mCurrentRaceId.empty()) return; Widgets::MWSkillPtr skillWidget; const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mCurrentRaceId); int count = sizeof(race->mData.mBonus)/sizeof(race->mData.mBonus[0]); // TODO: Find a portable macro for this ARRAYSIZE? for (int i = 0; i < count; ++i) { int skillId = race->mData.mBonus[i].mSkill; if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes continue; skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, std::string("Skill") + MyGUI::utility::toString(i)); skillWidget->setSkillNumber(skillId); skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus), 0.f)); ToolTips::createSkillToolTip(skillWidget, skillId); mSkillItems.push_back(skillWidget); coord1.top += lineHeight; } } void RaceDialog::updateSpellPowers() { for (MyGUI::Widget* widget : mSpellPowerItems) { MyGUI::Gui::getInstance().destroyWidget(widget); } mSpellPowerItems.clear(); if (mCurrentRaceId.empty()) return; const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), lineHeight); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mCurrentRaceId); int i = 0; for (const std::string& spellpower : race->mPowers.mList) { Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); spellPowerWidget->setUserString("Spell", spellpower); mSpellPowerItems.push_back(spellPowerWidget); coord.top += lineHeight; ++i; } } const ESM::NPC& RaceDialog::getResult() const { return mPreview->getPrototype(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/race.hpp000066400000000000000000000060541445372753700214640ustar00rootroot00000000000000#ifndef MWGUI_RACE_H #define MWGUI_RACE_H #include #include "windowbase.hpp" namespace MWRender { class RaceSelectionPreview; } namespace ESM { struct NPC; } namespace osg { class Group; } namespace Resource { class ResourceSystem; } namespace MWGui { class RaceDialog : public WindowModal { public: RaceDialog(osg::Group* parent, Resource::ResourceSystem* resourceSystem); enum Gender { GM_Male, GM_Female }; const ESM::NPC &getResult() const; const std::string &getRaceId() const { return mCurrentRaceId; } Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; } void setRaceId(const std::string &raceId); void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; } void setNextButtonShow(bool shown); void onOpen() override; void onClose() override; bool exit() override { return false; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onPreviewScroll(MyGUI::Widget* _sender, int _delta); void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position); void onSelectPreviousGender(MyGUI::Widget* _sender); void onSelectNextGender(MyGUI::Widget* _sender); void onSelectPreviousFace(MyGUI::Widget* _sender); void onSelectNextFace(MyGUI::Widget* _sender); void onSelectPreviousHair(MyGUI::Widget* _sender); void onSelectNextHair(MyGUI::Widget* _sender); void onSelectRace(MyGUI::ListBox* _sender, size_t _index); void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); private: void updateRaces(); void updateSkills(); void updateSpellPowers(); void updatePreview(); void recountParts(); void getBodyParts (int part, std::vector& out); osg::Group* mParent; Resource::ResourceSystem* mResourceSystem; std::vector mAvailableHeads; std::vector mAvailableHairs; MyGUI::ImageBox* mPreviewImage; MyGUI::ListBox* mRaceList; MyGUI::ScrollBar* mHeadRotate; MyGUI::Widget* mSkillList; std::vector mSkillItems; MyGUI::Widget* mSpellPowerList; std::vector mSpellPowerItems; int mGenderIndex, mFaceIndex, mHairIndex; std::string mCurrentRaceId; float mCurrentAngle; std::unique_ptr mPreview; std::unique_ptr mPreviewTexture; bool mPreviewDirty; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/recharge.cpp000066400000000000000000000076161445372753700223320ustar00rootroot00000000000000#include "recharge.hpp" #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/recharge.hpp" #include "itemselection.hpp" #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" #include "inventoryitemmodel.hpp" namespace MWGui { Recharge::Recharge() : WindowBase("openmw_recharge_dialog.layout") , mItemSelectionDialog(nullptr) { getWidget(mBox, "Box"); getWidget(mGemBox, "GemBox"); getWidget(mGemIcon, "GemIcon"); getWidget(mChargeLabel, "ChargeLabel"); getWidget(mCancelButton, "CancelButton"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); mBox->eventItemClicked += MyGUI::newDelegate(this, &Recharge::onItemClicked); mBox->setDisplayMode(ItemChargeView::DisplayMode_EnchantmentCharge); mGemIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onSelectItem); } void Recharge::onOpen() { center(); SortFilterItemModel * model = new SortFilterItemModel(new InventoryItemModel(MWMechanics::getPlayer())); model->setFilter(SortFilterItemModel::Filter_OnlyRechargable); mBox->setModel(model); // Reset scrollbars mBox->resetScrollbars(); } void Recharge::setPtr (const MWWorld::Ptr &item) { mGemIcon->setItem(item); mGemIcon->setUserString("ToolTipType", "ItemPtr"); mGemIcon->setUserData(MWWorld::Ptr(item)); updateView(); } void Recharge::updateView() { MWWorld::Ptr gem = *mGemIcon->getUserData(); std::string soul = gem.getCellRef().getSoul(); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); bool toolBoxVisible = (gem.getRefData().getCount() != 0); mGemBox->setVisible(toolBoxVisible); mGemBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); if (!toolBoxVisible) { mGemIcon->setItem(MWWorld::Ptr()); mGemIcon->clearUserStrings(); } mBox->update(); Gui::Box* box = dynamic_cast(mMainWidget); if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); center(); } void Recharge::onCancel(MyGUI::Widget *sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); } void Recharge::onSelectItem(MyGUI::Widget *sender) { delete mItemSelectionDialog; mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Recharge::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Recharge::onItemCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); } void Recharge::onItemSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); mGemIcon->setItem(item); mGemIcon->setUserString ("ToolTipType", "ItemPtr"); mGemIcon->setUserData(item); MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); updateView(); } void Recharge::onItemCancel() { mItemSelectionDialog->setVisible(false); } void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) { MWWorld::Ptr gem = *mGemIcon->getUserData(); if (!MWMechanics::rechargeItem(item, gem)) return; updateView(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/recharge.hpp000066400000000000000000000016071445372753700223310ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_RECHARGE_H #define OPENMW_MWGUI_RECHARGE_H #include "windowbase.hpp" namespace MWWorld { class Ptr; } namespace MWGui { class ItemSelectionDialog; class ItemWidget; class ItemChargeView; class Recharge : public WindowBase { public: Recharge(); void onOpen() override; void setPtr (const MWWorld::Ptr& gem) override; protected: ItemChargeView* mBox; MyGUI::Widget* mGemBox; ItemWidget* mGemIcon; ItemSelectionDialog* mItemSelectionDialog; MyGUI::TextBox* mChargeLabel; MyGUI::Button* mCancelButton; void updateView(); void onSelectItem(MyGUI::Widget* sender); void onItemSelected(MWWorld::Ptr item); void onItemCancel(); void onItemClicked (MyGUI::Widget* sender, const MWWorld::Ptr& item); void onCancel (MyGUI::Widget* sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/referenceinterface.cpp000066400000000000000000000007061445372753700243620ustar00rootroot00000000000000#include "referenceinterface.hpp" namespace MWGui { ReferenceInterface::ReferenceInterface() { } ReferenceInterface::~ReferenceInterface() { } void ReferenceInterface::checkReferenceAvailable() { // check if count of the reference has become 0 if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) { mPtr = MWWorld::Ptr(); onReferenceUnavailable(); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/referenceinterface.hpp000066400000000000000000000017071445372753700243710ustar00rootroot00000000000000#ifndef MWGUI_REFERENCEINTERFACE_H #define MWGUI_REFERENCEINTERFACE_H #include "../mwworld/ptr.hpp" namespace MWGui { /// \brief this class is intended for GUI interfaces that access an MW-Reference /// for example dialogue window accesses an NPC, or Container window accesses a Container /// these classes have to be automatically closed if the reference becomes unavailable /// make sure that checkReferenceAvailable() is called every frame and that onReferenceUnavailable() has been overridden class ReferenceInterface { public: ReferenceInterface(); virtual ~ReferenceInterface(); void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable virtual void resetReference() { mPtr = MWWorld::Ptr(); } protected: virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable MWWorld::Ptr mPtr; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/repair.cpp000066400000000000000000000102221445372753700220170ustar00rootroot00000000000000#include "repair.hpp" #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "itemselection.hpp" #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" #include "inventoryitemmodel.hpp" namespace MWGui { Repair::Repair() : WindowBase("openmw_repair.layout") , mItemSelectionDialog(nullptr) { getWidget(mRepairBox, "RepairBox"); getWidget(mToolBox, "ToolBox"); getWidget(mToolIcon, "ToolIcon"); getWidget(mUsesLabel, "UsesLabel"); getWidget(mQualityLabel, "QualityLabel"); getWidget(mCancelButton, "CancelButton"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onCancel); mRepairBox->eventItemClicked += MyGUI::newDelegate(this, &Repair::onRepairItem); mRepairBox->setDisplayMode(ItemChargeView::DisplayMode_Health); mToolIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onSelectItem); } void Repair::onOpen() { center(); SortFilterItemModel * model = new SortFilterItemModel(new InventoryItemModel(MWMechanics::getPlayer())); model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); mRepairBox->setModel(model); // Reset scrollbars mRepairBox->resetScrollbars(); } void Repair::setPtr(const MWWorld::Ptr &item) { MWBase::Environment::get().getWindowManager()->playSound("Item Repair Up"); mRepair.setTool(item); mToolIcon->setItem(item); mToolIcon->setUserString("ToolTipType", "ItemPtr"); mToolIcon->setUserData(MWWorld::Ptr(item)); updateRepairView(); } void Repair::updateRepairView() { MWWorld::LiveCellRef *ref = mRepair.getTool().get(); int uses = mRepair.getTool().getClass().getItemHealth(mRepair.getTool()); float quality = ref->mBase->mData.mQuality; mToolIcon->setUserData(mRepair.getTool()); std::stringstream qualityStr; qualityStr << std::setprecision(3) << quality; mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); mToolBox->setVisible(toolBoxVisible); mToolBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); if (!toolBoxVisible) { mToolIcon->setItem(MWWorld::Ptr()); mToolIcon->clearUserStrings(); } mRepairBox->update(); Gui::Box* box = dynamic_cast(mMainWidget); if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); center(); } void Repair::onSelectItem(MyGUI::Widget *sender) { delete mItemSelectionDialog; mItemSelectionDialog = new ItemSelectionDialog("#{sRepair}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Repair::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Repair::onItemCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyRepairTools); } void Repair::onItemSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); mToolIcon->setItem(item); mToolIcon->setUserString ("ToolTipType", "ItemPtr"); mToolIcon->setUserData(item); mRepair.setTool(item); MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); updateRepairView(); } void Repair::onItemCancel() { mItemSelectionDialog->setVisible(false); } void Repair::onCancel(MyGUI::Widget* /*sender*/) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); } void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) { if (!mRepair.getTool().getRefData().getCount()) return; mRepair.repair(ptr); updateRepairView(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/repair.hpp000066400000000000000000000016241445372753700220320ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_REPAIR_H #define OPENMW_MWGUI_REPAIR_H #include "windowbase.hpp" #include "../mwmechanics/repair.hpp" namespace MWGui { class ItemSelectionDialog; class ItemWidget; class ItemChargeView; class Repair : public WindowBase { public: Repair(); void onOpen() override; void setPtr (const MWWorld::Ptr& item) override; protected: ItemChargeView* mRepairBox; MyGUI::Widget* mToolBox; ItemWidget* mToolIcon; ItemSelectionDialog* mItemSelectionDialog; MyGUI::TextBox* mUsesLabel; MyGUI::TextBox* mQualityLabel; MyGUI::Button* mCancelButton; MWMechanics::Repair mRepair; void updateRepairView(); void onSelectItem(MyGUI::Widget* sender); void onItemSelected(MWWorld::Ptr item); void onItemCancel(); void onRepairItem(MyGUI::Widget* sender, const MWWorld::Ptr& ptr); void onCancel(MyGUI::Widget* sender); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/resourceskin.cpp000066400000000000000000000057121445372753700232610ustar00rootroot00000000000000#include "resourceskin.hpp" #include #include namespace MWGui { void resizeSkin(MyGUI::xml::ElementPtr _node) { _node->setAttribute("type", "ResourceSkin"); const std::string size = _node->findAttribute("size"); if (!size.empty()) return; const std::string textureName = _node->findAttribute("texture"); if (textureName.empty()) return; MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(textureName); if (!texture) return; MyGUI::IntCoord coord(0, 0, texture->getWidth(), texture->getHeight()); MyGUI::xml::ElementEnumerator basis = _node->getElementEnumerator(); const std::string textureSize = std::to_string(coord.width) + " " + std::to_string(coord.height); _node->addAttribute("size", textureSize); while (basis.next()) { if (basis->getName() != "BasisSkin") continue; const std::string basisSkinType = basis->findAttribute("type"); if (Misc::StringUtils::ciEqual(basisSkinType, "SimpleText")) continue; const std::string offset = basis->findAttribute("offset"); if (!offset.empty()) continue; basis->addAttribute("offset", coord); MyGUI::xml::ElementEnumerator state = basis->getElementEnumerator(); while (state.next()) { if (state->getName() == "State") { const std::string stateOffset = state->findAttribute("offset"); if (!stateOffset.empty()) continue; state->addAttribute("offset", coord); if (Misc::StringUtils::ciEqual(basisSkinType, "TileRect")) { MyGUI::xml::ElementEnumerator property = state->getElementEnumerator(); bool hasTileSize = false; while (property.next("Property")) { const std::string key = property->findAttribute("key"); if (key != "TileSize") continue; hasTileSize = true; } if (!hasTileSize) { MyGUI::xml::ElementPtr tileSizeProperty = state->createChild("Property"); tileSizeProperty->addAttribute("key", "TileSize"); tileSizeProperty->addAttribute("value", textureSize); } } } } } } void AutoSizedResourceSkin::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) { resizeSkin(_node); Base::deserialization(_node, _version); } } openmw-openmw-0.48.0/apps/openmw/mwgui/resourceskin.hpp000066400000000000000000000005501445372753700232610ustar00rootroot00000000000000#ifndef MWGUI_RESOURCESKIN_H #define MWGUI_RESOURCESKIN_H #include namespace MWGui { class AutoSizedResourceSkin final : public MyGUI::ResourceSkin { MYGUI_RTTI_DERIVED( AutoSizedResourceSkin ) public: void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/review.cpp000066400000000000000000000452711445372753700220520ustar00rootroot00000000000000#include "review.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/autocalcspell.hpp" #include "tooltips.hpp" namespace { void adjustButtonSize(MyGUI::Button *button) { // adjust size of button to fit its text MyGUI::IntSize size = button->getTextSize(); button->setSize(size.width + 24, button->getSize().height); } } namespace MWGui { ReviewDialog::ReviewDialog() : WindowModal("openmw_chargen_review.layout"), mUpdateSkillArea(false) { // Centre dialog center(); // Setup static stats MyGUI::Button* button; getWidget(mNameWidget, "NameText"); getWidget(button, "NameButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked); getWidget(mRaceWidget, "RaceText"); getWidget(button, "RaceButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked); getWidget(mClassWidget, "ClassText"); getWidget(button, "ClassButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked); getWidget(mBirthSignWidget, "SignText"); getWidget(button, "SignButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked); // Setup dynamic stats getWidget(mHealth, "Health"); mHealth->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sHealth", "")); mHealth->setValue(45, 45); getWidget(mMagicka, "Magicka"); mMagicka->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sMagic", "")); mMagicka->setValue(50, 50); getWidget(mFatigue, "Fatigue"); mFatigue->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFatigue", "")); mFatigue->setValue(160, 160); // Setup attributes Widgets::MWAttributePtr attribute; for (int idx = 0; idx < ESM::Attribute::Length; ++idx) { getWidget(attribute, std::string("Attribute") + MyGUI::utility::toString(idx)); mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::sAttributeIds[idx]), attribute)); attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]); attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue()); } // Setup skills getWidget(mSkillView, "SkillView"); mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); for (int i = 0; i < ESM::Skill::Length; ++i) { mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::make_pair(i, static_cast (nullptr))); } MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked); } void ReviewDialog::onOpen() { WindowModal::onOpen(); mUpdateSkillArea = true; } void ReviewDialog::onFrame(float /*duration*/) { if (mUpdateSkillArea) { updateSkillArea(); mUpdateSkillArea = false; } } void ReviewDialog::setPlayerName(const std::string &name) { mNameWidget->setCaption(name); } void ReviewDialog::setRace(const std::string &raceId) { mRaceId = raceId; const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().search(mRaceId); if (race) { ToolTips::createRaceToolTip(mRaceWidget, race); mRaceWidget->setCaption(race->mName); } mUpdateSkillArea = true; } void ReviewDialog::setClass(const ESM::Class& class_) { mKlass = class_; mClassWidget->setCaption(mKlass.mName); ToolTips::createClassToolTip(mClassWidget, mKlass); } void ReviewDialog::setBirthSign(const std::string& signId) { mBirthSignId = signId; const ESM::BirthSign *sign = MWBase::Environment::get().getWorld()->getStore().get().search(mBirthSignId); if (sign) { mBirthSignWidget->setCaption(sign->mName); ToolTips::createBirthsignToolTip(mBirthSignWidget, mBirthSignId); } mUpdateSkillArea = true; } void ReviewDialog::setHealth(const MWMechanics::DynamicStat& value) { int current = std::max(0, static_cast(value.getCurrent())); int modified = static_cast(value.getModified()); mHealth->setValue(current, modified); std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } void ReviewDialog::setMagicka(const MWMechanics::DynamicStat& value) { int current = std::max(0, static_cast(value.getCurrent())); int modified = static_cast(value.getModified()); mMagicka->setValue(current, modified); std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mMagicka->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); mFatigue->setValue(current, modified); std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value) { std::map::iterator attr = mAttributeWidgets.find(static_cast(attributeId)); if (attr == mAttributeWidgets.end()) return; if (attr->second->getAttributeValue() != value) { attr->second->setAttributeValue(value); mUpdateSkillArea = true; } } void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value) { mSkillValues[skillId] = value; MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; if (widget) { float modified = static_cast(value.getModified()), base = static_cast(value.getBase()); std::string text = MyGUI::utility::toString(std::floor(modified)); std::string state = "normal"; if (modified > base) state = "increased"; else if (modified < base) state = "decreased"; widget->setCaption(text); widget->_setWidgetState(state); } mUpdateSkillArea = true; } void ReviewDialog::configureSkills(const std::vector& major, const std::vector& minor) { mMajorSkills = major; mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); mMiscSkills.clear(); for (const int skill : ESM::Skill::sSkillIds) { if (skillSet.find(skill) == skillSet.end()) mMiscSkills.push_back(skill); } mUpdateSkillArea = true; } void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::ImageBox* separator = mSkillView->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(separator); coord1.top += separator->getHeight(); coord2.top += separator->getHeight(); } void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); groupWidget->setCaption(label); mSkillWidgets.push_back(groupWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; } MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; MyGUI::TextBox* skillValueWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillValueWidget = mSkillView->createWidget("SandTextRight", coord2, MyGUI::Align::Default); skillValueWidget->setCaption(value); skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(skillNameWidget); mSkillWidgets.push_back(skillValueWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; return skillValueWidget; } void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(skillNameWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; } void ReviewDialog::addItem(const ESM::Spell* spell, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { Widgets::MWSpellPtr widget = mSkillView->createWidget("MW_StatName", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); widget->setSpellId(spell->mId); widget->setUserString("ToolTipType", "Spell"); widget->setUserString("Spell", spell->mId); widget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(widget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; } void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) { addSeparator(coord1, coord2); } addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); for (const int& skillId : skills) { if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; int base = stat.getBase(); int modified = stat.getModified(); std::string state = "normal"; if (modified > base) state = "increased"; else if (modified < base) state = "decreased"; MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); for (int i=0; i<2; ++i) { ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size()-1-i], skillId); } mSkillWidgetMap[skillId] = widget; } } void ReviewDialog::updateSkillArea() { for (MyGUI::Widget* skillWidget : mSkillWidgets) { MyGUI::Gui::getInstance().destroyWidget(skillWidget); } mSkillWidgets.clear(); const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); if (!mMajorSkills.empty()) addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); if (!mMinorSkills.empty()) addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); // starting spells std::vector spells; const ESM::Race* race = nullptr; if (!mRaceId.empty()) race = MWBase::Environment::get().getWorld()->getStore().get().find(mRaceId); int skills[ESM::Skill::Length]; for (int i=0; isecond.getBase(); int attributes[ESM::Attribute::Length]; for (int i=0; igetAttributeValue().getBase(); std::vector selectedSpells = MWMechanics::autoCalcPlayerSpells(skills, attributes, race); for (std::string& spellId : selectedSpells) { std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } if (race) { for (const std::string& spellId : race->mPowers.mList) { std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } } if (!mBirthSignId.empty()) { const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(mBirthSignId); for (const std::string& spellId : sign->mPowers.mList) { std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } } if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeAbility", "Abilities"), coord1, coord2); for (std::string& spellId : spells) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Ability) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypePower", "Powers"), coord1, coord2); for (std::string& spellId : spells) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Power) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeSpell", "Spells"), coord1, coord2); for (std::string& spellId : spells) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Spell) addItem(spell, coord1, coord2); } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); mSkillView->setVisibleVScroll(true); } // widget controls void ReviewDialog::onOkClicked(MyGUI::Widget* _sender) { eventDone(this); } void ReviewDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } void ReviewDialog::onNameClicked(MyGUI::Widget* _sender) { eventActivateDialog(NAME_DIALOG); } void ReviewDialog::onRaceClicked(MyGUI::Widget* _sender) { eventActivateDialog(RACE_DIALOG); } void ReviewDialog::onClassClicked(MyGUI::Widget* _sender) { eventActivateDialog(CLASS_DIALOG); } void ReviewDialog::onBirthSignClicked(MyGUI::Widget* _sender) { eventActivateDialog(BIRTHSIGN_DIALOG); } void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mSkillView->getViewOffset().top + _rel*0.3 > 0) mSkillView->setViewOffset(MyGUI::IntPoint(0, 0)); else mSkillView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel*0.3))); } } openmw-openmw-0.48.0/apps/openmw/mwgui/review.hpp000066400000000000000000000070141445372753700220500ustar00rootroot00000000000000#ifndef MWGUI_REVIEW_H #define MWGUI_REVIEW_H #include #include #include "windowbase.hpp" #include "widgets.hpp" namespace ESM { struct Spell; } namespace MWGui { class ReviewDialog : public WindowModal { public: enum Dialogs { NAME_DIALOG, RACE_DIALOG, CLASS_DIALOG, BIRTHSIGN_DIALOG }; typedef std::vector SkillList; ReviewDialog(); bool exit() override { return false; } void setPlayerName(const std::string &name); void setRace(const std::string &raceId); void setClass(const ESM::Class& class_); void setBirthSign (const std::string &signId); void setHealth(const MWMechanics::DynamicStat& value); void setMagicka(const MWMechanics::DynamicStat& value); void setFatigue(const MWMechanics::DynamicStat& value); void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value); void configureSkills(const SkillList& major, const SkillList& minor); void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); void onOpen() override; void onFrame(float duration) override; // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; /** Event : Back button clicked.\n signature : void method()\n */ EventHandle_Void eventBack; /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; EventHandle_Int eventActivateDialog; protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); void onNameClicked(MyGUI::Widget* _sender); void onRaceClicked(MyGUI::Widget* _sender); void onClassClicked(MyGUI::Widget* _sender); void onBirthSignClicked(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addItem(const ESM::Spell* spell, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void updateSkillArea(); MyGUI::TextBox *mNameWidget, *mRaceWidget, *mClassWidget, *mBirthSignWidget; MyGUI::ScrollView* mSkillView; Widgets::MWDynamicStatPtr mHealth, mMagicka, mFatigue; std::map mAttributeWidgets; SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::map mSkillValues; std::map mSkillWidgetMap; std::string mName, mRaceId, mBirthSignId; ESM::Class mKlass; std::vector mSkillWidgets; //< Skills and other information bool mUpdateSkillArea; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/savegamedialog.cpp000066400000000000000000000424271445372753700235210ustar00rootroot00000000000000#include "savegamedialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwstate/character.hpp" #include "confirmationdialog.hpp" namespace MWGui { SaveGameDialog::SaveGameDialog() : WindowModal("openmw_savegame_dialog.layout") , mSaving(true) , mCurrentCharacter(nullptr) , mCurrentSlot(nullptr) { getWidget(mScreenshot, "Screenshot"); getWidget(mCharacterSelection, "SelectCharacter"); getWidget(mInfoText, "InfoText"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mDeleteButton, "DeleteButton"); getWidget(mSaveList, "SaveList"); getWidget(mSaveNameEdit, "SaveNameEdit"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterAccept); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); mSaveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &SaveGameDialog::onKeyButtonPressed); mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); // To avoid accidental deletions mDeleteButton->setNeedKeyFocus(false); } void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos) { onSlotSelected(sender, pos); accept(); } void SaveGameDialog::onSlotMouseClick(MyGUI::ListBox* sender, size_t pos) { onSlotSelected(sender, pos); if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed()) confirmDeleteSave(); } void SaveGameDialog::confirmDeleteSave() { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sMessage3}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); dialog->eventCancelClicked.clear(); dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotCancel); } void SaveGameDialog::onDeleteSlotConfirmed() { MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot); mSaveList->removeItemAt(mSaveList->getIndexSelected()); onSlotSelected(mSaveList, mSaveList->getIndexSelected()); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); if (mSaveList->getItemCount() == 0) { size_t previousIndex = mCharacterSelection->getIndexSelected(); mCurrentCharacter = nullptr; mCharacterSelection->removeItemAt(previousIndex); if (mCharacterSelection->getItemCount()) { size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1); mCharacterSelection->setIndexSelected(nextCharacter); onCharacterSelected(mCharacterSelection, nextCharacter); } else fillSaveList(); } } void SaveGameDialog::onDeleteSlotCancel() { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); } void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) { // This might have previously been a save slot from the list. If so, that is no longer the case mSaveList->setIndexSelected(MyGUI::ITEM_NONE); onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender) { accept(); // To do not spam onEditSelectAccept() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void SaveGameDialog::onClose() { mSaveList->setIndexSelected(MyGUI::ITEM_NONE); WindowModal::onClose(); } void SaveGameDialog::onOpen() { WindowModal::onOpen(); mSaveNameEdit->setCaption (""); if (mSaving) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); else MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); center(); mCharacterSelection->setCaption(""); mCharacterSelection->removeAllItems(); mCurrentCharacter = nullptr; mCurrentSlot = nullptr; mSaveList->removeAllItems(); onSlotSelected(mSaveList, MyGUI::ITEM_NONE); MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); if (mgr->characterBegin() == mgr->characterEnd()) return; mCurrentCharacter = mgr->getCurrentCharacter(); std::string directory = Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); size_t selectedIndex = MyGUI::ITEM_NONE; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) { if (it->begin()!=it->end()) { std::stringstream title; title << it->getSignature().mPlayerName; // For a custom class, we will not find it in the store (unless we loaded the savegame first). // Fall back to name stored in savegame header in that case. std::string className; if (it->getSignature().mPlayerClassId.empty()) className = it->getSignature().mPlayerClassName; else { // Find the localised name for this class from the store const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().search( it->getSignature().mPlayerClassId); if (class_) className = class_->mName; else className = "?"; // From an older savegame format that did not support custom classes properly. } title << " (#{sLevel} " << it->getSignature().mPlayerLevel << " " << MyGUI::TextIterator::toTagsString(className) << ")"; mCharacterSelection->addItem (MyGUI::LanguageManager::getInstance().replaceTags(title.str())); if (mCurrentCharacter == &*it || (!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase ( it->begin()->mPath.parent_path().filename().string()))) { mCurrentCharacter = &*it; selectedIndex = mCharacterSelection->getItemCount()-1; } } } mCharacterSelection->setIndexSelected(selectedIndex); if (selectedIndex == MyGUI::ITEM_NONE) mCharacterSelection->setCaptionWithReplacing("#{SavegameMenu:SelectCharacter}"); fillSaveList(); } void SaveGameDialog::setLoadOrSave(bool load) { mSaving = !load; mSaveNameEdit->setVisible(!load); mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); mCharacterSelection->setVisible(load); mDeleteButton->setUserString("Hidden", load ? "false" : "true"); mDeleteButton->setVisible(load); if (!load) { mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(); } center(); } void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) { setVisible(false); } void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) { if (mCurrentSlot) confirmDeleteSave(); } void SaveGameDialog::onConfirmationGiven() { accept(true); } void SaveGameDialog::onConfirmationCancel() { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); } void SaveGameDialog::accept(bool reallySure) { if (mSaving) { // If overwriting an existing slot, ask for confirmation first if (mCurrentSlot != nullptr && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sMessage4}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationCancel); return; } if (mSaveNameEdit->getCaption().empty()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); return; } } else { MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); // If game is running, ask for confirmation first if (state == MWBase::StateManager::State_Running && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sMessage1}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); dialog->eventCancelClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationCancel); return; } } setVisible(false); MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); if (mSaving) { MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot); } else { assert (mCurrentCharacter && mCurrentSlot); MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot->mPath.string()); } } void SaveGameDialog::onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::Delete && mCurrentSlot) confirmDeleteSave(); } void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) { accept(); } void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos) { MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); unsigned int i=0; const MWState::Character* character = nullptr; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) { if (i == pos) character = &*it; } assert(character && "Can't find selected character"); mCurrentCharacter = character; mCurrentSlot = nullptr; fillSaveList(); } void SaveGameDialog::onCharacterAccept(MyGUI::ComboBox* sender, size_t pos) { // Give key focus to save list so we can confirm the selection with Enter MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); } void SaveGameDialog::fillSaveList() { mSaveList->removeAllItems(); if (!mCurrentCharacter) return; for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) { mSaveList->addItem(it->mProfile.mDescription); } // When loading, Auto-select the first save, if there is one if (mSaveList->getItemCount() && !mSaving) { mSaveList->setIndexSelected(0); onSlotSelected(mSaveList, 0); } else onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } std::string formatTimeplayed(const double timeInSeconds) { int timePlayed = (int)floor(timeInSeconds); int days = timePlayed / 60 / 60 / 24; int hours = (timePlayed / 60 / 60) % 24; int minutes = (timePlayed / 60) % 60; int seconds = timePlayed % 60; std::stringstream stream; stream << std::setfill('0') << std::setw(2) << days << ":"; stream << std::setfill('0') << std::setw(2) << hours << ":"; stream << std::setfill('0') << std::setw(2) << minutes << ":"; stream << std::setfill('0') << std::setw(2) << seconds; return stream.str(); } void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); if (pos == MyGUI::ITEM_NONE || !mCurrentCharacter) { mCurrentSlot = nullptr; mInfoText->setCaption(""); mScreenshot->setImageTexture(""); return; } if (mSaving) mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); mCurrentSlot = nullptr; unsigned int i=0; for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) { if (i == pos) mCurrentSlot = &*it; } if (!mCurrentSlot) throw std::runtime_error("Can't find selected slot"); std::stringstream text; time_t time = mCurrentSlot->mTimeStamp; struct tm* timeinfo; timeinfo = localtime(&time); text << std::put_time(timeinfo, "%Y.%m.%d %T") << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n"; int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour); bool pm = hour >= 12; if (hour >= 13) hour -= 12; if (hour == 0) hour = 12; text << mCurrentSlot->mProfile.mInGameTime.mDay << " " << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); if (Settings::Manager::getBool("timeplayed","Saves")) { text << "\n" << "#{SavegameMenu:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); } mInfoText->setCaptionWithReplacing(text.str()); // Decode screenshot const std::vector& data = mCurrentSlot->mProfile.mScreenshot; Files::IMemStream instream (&data[0], data.size()); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { Log(Debug::Error) << "Error: Can't open savegame screenshot, no jpg readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream); if (!result.success()) { Log(Debug::Error) << "Error: Failed to read savegame screenshot: " << result.message() << " code " << result.status(); return; } osg::ref_ptr texture (new osg::Texture2D); texture->setImage(result.getImage()); texture->setInternalFormat(GL_RGB); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setResizeNonPowerOfTwoHint(false); texture->setUnRefImageDataAfterApply(true); mScreenshotTexture = std::make_unique(texture); mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } } openmw-openmw-0.48.0/apps/openmw/mwgui/savegamedialog.hpp000066400000000000000000000040101445372753700235100ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_SAVEGAMEDIALOG_H #define OPENMW_MWGUI_SAVEGAMEDIALOG_H #include #include "windowbase.hpp" namespace MWState { class Character; struct Slot; } namespace MWGui { class SaveGameDialog : public MWGui::WindowModal { public: SaveGameDialog(); void onOpen() override; void onClose() override; void setLoadOrSave(bool load); private: void confirmDeleteSave(); void onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character); void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); void onDeleteButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); void onCharacterAccept(MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); // Slot activated (double click or enter key) void onSlotActivated (MyGUI::ListBox* sender, size_t pos); // Slot clicked with mouse void onSlotMouseClick(MyGUI::ListBox* sender, size_t pos); void onDeleteSlotConfirmed(); void onDeleteSlotCancel(); void onEditSelectAccept (MyGUI::EditBox* sender); void onSaveNameChanged (MyGUI::EditBox* sender); void onConfirmationGiven(); void onConfirmationCancel(); void accept(bool reallySure=false); void fillSaveList(); std::unique_ptr mScreenshotTexture; MyGUI::ImageBox* mScreenshot; bool mSaving; MyGUI::ComboBox* mCharacterSelection; MyGUI::EditBox* mInfoText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; MyGUI::Button* mDeleteButton; MyGUI::ListBox* mSaveList; MyGUI::EditBox* mSaveNameEdit; const MWState::Character* mCurrentCharacter; const MWState::Slot* mCurrentSlot; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/screenfader.cpp000066400000000000000000000121741445372753700230260ustar00rootroot00000000000000#include "screenfader.hpp" #include #include namespace MWGui { FadeOp::FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay) : mFader(fader), mRemainingTime(time+delay), mTargetTime(time), mTargetAlpha(targetAlpha), mStartAlpha(0.f), mDelay(delay), mRunning(false) { } bool FadeOp::isRunning() { return mRunning; } void FadeOp::start() { if (mRunning) return; mRemainingTime = mTargetTime + mDelay; mStartAlpha = mFader->getCurrentAlpha(); mRunning = true; } void FadeOp::update(float dt) { if (!mRunning) return; if (mStartAlpha == mTargetAlpha) { finish(); return; } if (mRemainingTime <= 0) { // Make sure the target alpha is applied mFader->notifyAlphaChanged(mTargetAlpha); finish(); return; } if (mRemainingTime > mTargetTime) { mRemainingTime -= dt; return; } float currentAlpha = mFader->getCurrentAlpha(); if (mStartAlpha > mTargetAlpha) { currentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); if (currentAlpha < mTargetAlpha) currentAlpha = mTargetAlpha; } else { currentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); if (currentAlpha > mTargetAlpha) currentAlpha = mTargetAlpha; } mFader->notifyAlphaChanged(currentAlpha); mRemainingTime -= dt; } void FadeOp::finish() { mRunning = false; mFader->notifyOperationFinished(); } ScreenFader::ScreenFader(const std::string & texturePath, const std::string& layout, const MyGUI::FloatCoord& texCoordOverride) : WindowBase(layout) , mCurrentAlpha(0.f) , mFactor(1.f) , mRepeat(false) { MyGUI::Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &ScreenFader::onFrameStart); MyGUI::ImageBox* imageBox = mMainWidget->castType(false); if (imageBox) { imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); imageBox->setImageCoord(MyGUI::IntCoord(texCoordOverride.left * imageSize.width, texCoordOverride.top * imageSize.height, texCoordOverride.width * imageSize.width, texCoordOverride.height * imageSize.height)); } } ScreenFader::~ScreenFader() { try { MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &ScreenFader::onFrameStart); } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } void ScreenFader::onFrameStart(float dt) { if (!mQueue.empty()) { if (!mQueue.front()->isRunning()) mQueue.front()->start(); mQueue.front()->update(dt); } } void ScreenFader::applyAlpha() { setVisible(true); mMainWidget->setAlpha(1.f-((1.f-mCurrentAlpha) * mFactor)); } void ScreenFader::fadeIn(float time, float delay) { queue(time, 1.f, delay); } void ScreenFader::fadeOut(const float time, float delay) { queue(time, 0.f, delay); } void ScreenFader::fadeTo(const int percent, const float time, float delay) { queue(time, percent/100.f, delay); } void ScreenFader::clear() { clearQueue(); notifyAlphaChanged(0.f); } void ScreenFader::setFactor(float factor) { mFactor = factor; applyAlpha(); } void ScreenFader::setRepeat(bool repeat) { mRepeat = repeat; } void ScreenFader::queue(float time, float targetAlpha, float delay) { if (time < 0.f) return; if (time == 0.f && delay == 0.f) { mCurrentAlpha = targetAlpha; applyAlpha(); return; } mQueue.push_back(FadeOp::Ptr(new FadeOp(this, time, targetAlpha, delay))); } bool ScreenFader::isEmpty() { return mQueue.empty(); } void ScreenFader::clearQueue() { mQueue.clear(); } void ScreenFader::notifyAlphaChanged(float alpha) { if (mCurrentAlpha == alpha) return; mCurrentAlpha = alpha; if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f) mMainWidget->setVisible(false); else applyAlpha(); } void ScreenFader::notifyOperationFinished() { FadeOp::Ptr op = mQueue.front(); mQueue.pop_front(); if (mRepeat) mQueue.push_back(op); } float ScreenFader::getCurrentAlpha() { return mCurrentAlpha; } } openmw-openmw-0.48.0/apps/openmw/mwgui/screenfader.hpp000066400000000000000000000032551445372753700230330ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_SCREENFADER_H #define OPENMW_MWGUI_SCREENFADER_H #include #include #include "windowbase.hpp" namespace MWGui { class ScreenFader; class FadeOp { public: typedef std::shared_ptr Ptr; FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay); bool isRunning(); void start(); void update(float dt); void finish(); private: ScreenFader * mFader; float mRemainingTime; float mTargetTime; float mTargetAlpha; float mStartAlpha; float mDelay; bool mRunning; }; class ScreenFader : public WindowBase { public: ScreenFader(const std::string & texturePath, const std::string& layout = "openmw_screen_fader.layout", const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0,0,1,1)); ~ScreenFader(); void onFrameStart(float dt); void fadeIn(const float time, float delay=0); void fadeOut(const float time, float delay=0); void fadeTo(const int percent, const float time, float delay=0); void clear() override; void setFactor (float factor); void setRepeat(bool repeat); void queue(float time, float targetAlpha, float delay); bool isEmpty(); void clearQueue(); void notifyAlphaChanged(float alpha); void notifyOperationFinished(); float getCurrentAlpha(); private: void applyAlpha(); float mCurrentAlpha; float mFactor; bool mRepeat; // repeat queued operations without removing them std::deque mQueue; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/scrollwindow.cpp000066400000000000000000000071641445372753700232760ustar00rootroot00000000000000#include "scrollwindow.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/actiontake.hpp" #include "../mwworld/class.hpp" #include "formatting.hpp" namespace MWGui { ScrollWindow::ScrollWindow () : BookWindowBase("openmw_scroll.layout") , mTakeButtonShow(true) , mTakeButtonAllowed(true) { getWidget(mTextView, "TextView"); getWidget(mCloseButton, "CloseButton"); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onCloseButtonClicked); getWidget(mTakeButton, "TakeButton"); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onTakeButtonClicked); adjustButton("CloseButton"); adjustButton("TakeButton"); mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed); mTakeButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed); center(); } void ScrollWindow::setPtr (const MWWorld::Ptr& scroll) { mScroll = scroll; MWWorld::Ptr player = MWMechanics::getPlayer(); bool showTakeButton = scroll.getContainerStore() != &player.getClass().getContainerStore(player); MWWorld::LiveCellRef *ref = mScroll.get(); Formatting::BookFormatter formatter; formatter.markupToWidget(mTextView, ref->mBase->mText, 390, mTextView->getHeight()); MyGUI::IntSize size = mTextView->getChildAt(0)->getSize(); // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(mTextView->getWidth(), size.height); else mTextView->setCanvasSize(mTextView->getWidth(), mTextView->getSize().height); mTextView->setVisibleVScroll(true); mTextView->setViewOffset(MyGUI::IntPoint(0,0)); setTakeButtonShow(showTakeButton); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); } void ScrollWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) { int scroll = 0; if (key == MyGUI::KeyCode::ArrowUp) scroll = 40; else if (key == MyGUI::KeyCode::ArrowDown) scroll = -40; if (scroll != 0) mTextView->setViewOffset(mTextView->getViewOffset() + MyGUI::IntPoint(0, scroll)); } void ScrollWindow::setTakeButtonShow(bool show) { mTakeButtonShow = show; mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } void ScrollWindow::setInventoryAllowed(bool allowed) { mTakeButtonAllowed = allowed; mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->playSound("Item Book Up"); MWWorld::ActionTake take(mScroll); take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll, true); } } openmw-openmw-0.48.0/apps/openmw/mwgui/scrollwindow.hpp000066400000000000000000000020001445372753700232630ustar00rootroot00000000000000#ifndef MWGUI_SCROLLWINDOW_H #define MWGUI_SCROLLWINDOW_H #include "windowbase.hpp" #include "../mwworld/ptr.hpp" namespace Gui { class ImageButton; } namespace MWGui { class ScrollWindow : public BookWindowBase { public: ScrollWindow (); void setPtr (const MWWorld::Ptr& scroll) override; void setInventoryAllowed(bool allowed); void onResChange(int, int) override { center(); } protected: void onCloseButtonClicked (MyGUI::Widget* _sender); void onTakeButtonClicked (MyGUI::Widget* _sender); void setTakeButtonShow(bool show); void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); private: Gui::ImageButton* mCloseButton; Gui::ImageButton* mTakeButton; MyGUI::ScrollView* mTextView; MWWorld::Ptr mScroll; bool mTakeButtonShow; bool mTakeButtonAllowed; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/settingswindow.cpp000066400000000000000000001200311445372753700236250ustar00rootroot00000000000000#include "settingswindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "confirmationdialog.hpp" namespace { std::string textureMipmappingToStr(const std::string& val) { if (val == "linear") return "#{SettingsMenu:TextureFilteringTrilinear}"; if (val == "nearest") return "#{SettingsMenu:TextureFilteringBilinear}"; if (val == "none") return "#{SettingsMenu:TextureFilteringDisabled}"; Log(Debug::Warning) << "Warning: Invalid texture mipmap option: "<< val; return "#{SettingsMenu:TextureFilteringOther}"; } std::string lightingMethodToStr(SceneUtil::LightingMethod method) { std::string result; switch (method) { case SceneUtil::LightingMethod::FFP: result = "#{SettingsMenu:LightingMethodLegacy}"; break; case SceneUtil::LightingMethod::PerObjectUniform: result = "#{SettingsMenu:LightingMethodShadersCompatibility}"; break; case SceneUtil::LightingMethod::SingleUBO: default: result = "#{SettingsMenu:LightingMethodShaders}"; break; } return MyGUI::LanguageManager::getInstance().replaceTags(result); } void parseResolution (int &x, int &y, const std::string& str) { std::vector split; Misc::StringUtils::split (str, split, "@(x"); assert (split.size() >= 2); Misc::StringUtils::trim(split[0]); Misc::StringUtils::trim(split[1]); x = MyGUI::utility::parseInt (split[0]); y = MyGUI::utility::parseInt (split[1]); } bool sortResolutions (std::pair left, std::pair right) { if (left.first == right.first) return left.second > right.second; return left.first > right.first; } std::string getAspect (int x, int y) { int gcd = std::gcd (x, y); if (gcd == 0) return std::string(); int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 if (xaspect == 8 && yaspect == 5) return "16 : 10"; return MyGUI::utility::toString(xaspect) + " : " + MyGUI::utility::toString(yaspect); } const char* checkButtonType = "CheckButton"; const char* sliderType = "Slider"; std::string getSettingType(MyGUI::Widget* widget) { return widget->getUserString("SettingType"); } std::string getSettingName(MyGUI::Widget* widget) { return widget->getUserString("SettingName"); } std::string getSettingCategory(MyGUI::Widget* widget) { return widget->getUserString("SettingCategory"); } std::string getSettingValueType(MyGUI::Widget* widget) { return widget->getUserString("SettingValueType"); } void getSettingMinMax(MyGUI::Widget* widget, float& min, float& max) { const char* settingMin = "SettingMin"; const char* settingMax = "SettingMax"; min = 0.f; max = 1.f; if (!widget->getUserString(settingMin).empty()) min = MyGUI::utility::parseFloat(widget->getUserString(settingMin)); if (!widget->getUserString(settingMax).empty()) max = MyGUI::utility::parseFloat(widget->getUserString(settingMax)); } void updateMaxLightsComboBox(MyGUI::ComboBox* box) { constexpr int min = 8; constexpr int max = 32; constexpr int increment = 8; int maxLights = Settings::Manager::getInt("max lights", "Shaders"); // show increments of 8 in dropdown if (maxLights >= min && maxLights <= max && !(maxLights % increment)) box->setIndexSelected((maxLights / increment)-1); else box->setIndexSelected(MyGUI::ITEM_NONE); } } namespace MWGui { void SettingsWindow::configureWidgets(MyGUI::Widget* widget, bool init) { MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator(); while (widgets.next()) { MyGUI::Widget* current = widgets.current(); std::string type = getSettingType(current); if (type == checkButtonType) { std::string initialValue = Settings::Manager::getBool(getSettingName(current), getSettingCategory(current)) ? "#{sOn}" : "#{sOff}"; current->castType()->setCaptionWithReplacing(initialValue); if (init) current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); } if (type == sliderType) { MyGUI::ScrollBar* scroll = current->castType(); std::string valueStr; std::string valueType = getSettingValueType(current); if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget float min,max; getSettingMinMax(scroll, min, max); float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); if (valueType == "Cell") { std::stringstream ss; ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } else if (valueType == "Float") { std::stringstream ss; ss << std::fixed << std::setprecision(2) << value; valueStr = ss.str(); } else valueStr = MyGUI::utility::toString(int(value)); value = std::clamp(value, min, max); value = (value-min)/(max-min); scroll->setScrollPosition(static_cast(value * (scroll->getScrollRange() - 1))); } else { int value = Settings::Manager::getInt(getSettingName(current), getSettingCategory(current)); valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } if (init) scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); if (scroll->getVisible()) updateSliderLabel(scroll, valueStr); } configureWidgets(current, init); } } void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar *scroller, const std::string& value) { std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); if (!labelWidgetName.empty()) { MyGUI::TextBox* textBox; getWidget(textBox, labelWidgetName); std::string labelCaption = scroller->getUserString("SettingLabelCaption"); labelCaption = Misc::StringUtils::format(labelCaption, value); textBox->setCaptionWithReplacing(labelCaption); } } SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; MyGUI::Widget* unusedSlider; getWidget(unusedSlider, widgetName); unusedSlider->setVisible(false); configureWidgets(mMainWidget, true); setTitle("#{sOptions}"); getWidget(mSettingsTab, "SettingsTab"); getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mWindowModeList, "WindowModeList"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); getWidget(mPrimaryLanguage, "PrimaryLanguage"); getWidget(mSecondaryLanguage, "SecondaryLanguage"); getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); getWidget(mScriptFilter, "ScriptFilter"); getWidget(mScriptList, "ScriptList"); getWidget(mScriptBox, "ScriptBox"); getWidget(mScriptView, "ScriptView"); getWidget(mScriptAdapter, "ScriptAdapter"); getWidget(mScriptDisabled, "ScriptDisabled"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux MyGUI::ScrollBar *gammaSlider; getWidget(gammaSlider, "GammaSlider"); gammaSlider->setVisible(false); MyGUI::TextBox *textBox; getWidget(textBox, "GammaText"); textBox->setVisible(false); getWidget(textBox, "GammaTextDark"); textBox->setVisible(false); getWidget(textBox, "GammaTextLight"); textBox->setVisible(false); #endif mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SettingsWindow::onWindowResize); mSettingsTab->eventTabChangeSelect += MyGUI::newDelegate(this, &SettingsWindow::onTabChanged); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); mWaterRainRippleDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged); mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged); mWindowModeList->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWindowModeChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); mPrimaryLanguage->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onPrimaryLanguageChanged); mSecondaryLanguage->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSecondaryLanguageChanged); computeMinimumWindowSize(); center(); mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list int screen = Settings::Manager::getInt("screen", "Video"); int numDisplayModes = SDL_GetNumDisplayModes(screen); std::vector < std::pair > resolutions; for (int i = 0; i < numDisplayModes; i++) { SDL_DisplayMode mode; SDL_GetDisplayMode(screen, i, &mode); resolutions.emplace_back(mode.w, mode.h); } std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); std::string aspect = getAspect(resolution.first, resolution.second); if (!aspect.empty()) str = str + " (" + aspect + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); } highlightCurrentResolution(); std::string tmip = Settings::Manager::getString("texture mipmap", "General"); mTextureFilteringButton->setCaptionWithReplacing(textureMipmappingToStr(tmip)); int waterTextureSize = Settings::Manager::getInt("rtt size", "Water"); if (waterTextureSize >= 512) mWaterTextureSize->setIndexSelected(0); if (waterTextureSize >= 1024) mWaterTextureSize->setIndexSelected(1); if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); updateMaxLightsComboBox(mMaxLights); Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); mWindowBorderButton->setEnabled(windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); std::vector availableLanguages; const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/")) { if (Misc::getFileExtension(path) == "yaml") { std::string localeName(Misc::stemFile(path)); if (std::find(availableLanguages.begin(), availableLanguages.end(), localeName) == availableLanguages.end()) availableLanguages.push_back(localeName); } } std::sort (availableLanguages.begin(), availableLanguages.end()); std::vector currentLocales = Settings::Manager::getStringArray("preferred locales", "General"); if (currentLocales.empty()) currentLocales.push_back("en"); icu::Locale primaryLocale(currentLocales[0].c_str()); mPrimaryLanguage->removeAllItems(); mPrimaryLanguage->setIndexSelected(MyGUI::ITEM_NONE); mSecondaryLanguage->removeAllItems(); mSecondaryLanguage->addItem(MyGUI::LanguageManager::getInstance().replaceTags("#{sNone}"), std::string()); mSecondaryLanguage->setIndexSelected(0); size_t i = 0; for (const auto& language : availableLanguages) { icu::Locale locale(language.c_str()); icu::UnicodeString str(language.c_str()); locale.getDisplayName(primaryLocale, str); std::string localeString; str.toUTF8String(localeString); mPrimaryLanguage->addItem(localeString, language); mSecondaryLanguage->addItem(localeString, language); if (language == currentLocales[0]) mPrimaryLanguage->setIndexSelected(i); if (currentLocales.size() > 1 && language == currentLocales[1]) mSecondaryLanguage->setIndexSelected(i + 1); i++; } } void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) { resetScrollbars(); } void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); } void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) { if (index == MyGUI::ITEM_NONE) return; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sNotifyMessage67}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); dialog->eventCancelClicked.clear(); dialog->eventCancelClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionCancel); } void SettingsWindow::onResolutionAccept() { std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; parseResolution (resX, resY, resStr); Settings::Manager::setInt("resolution x", "Video", resX); Settings::Manager::setInt("resolution y", "Video", resY); apply(); } void SettingsWindow::onResolutionCancel() { highlightCurrentResolution(); } void SettingsWindow::highlightCurrentResolution() { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); int currentX = Settings::Manager::getInt("resolution x", "Video"); int currentY = Settings::Manager::getInt("resolution y", "Video"); for (size_t i=0; igetItemCount(); ++i) { int resX, resY; parseResolution (resX, resY, mResolutionList->getItemNameAt(i)); if (resX == currentX && resY == currentY) { mResolutionList->setIndexSelected(i); break; } } } void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) { int size = 0; if (pos == 0) size = 512; else if (pos == 1) size = 1024; else if (pos == 2) size = 2048; Settings::Manager::setInt("rtt size", "Water", size); apply(); } void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { unsigned int level = static_cast(std::min(pos, 5)); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { unsigned int level = static_cast(std::min(pos, 2)); Settings::Manager::setInt("rain ripple detail", "Water", level); apply(); } void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{SettingsMenu:ChangeRequiresRestart}", {"#{sOK}"}, true); Settings::Manager::setString("lighting method", "Shaders", *_sender->getItemDataAt(pos)); apply(); } void SettingsWindow::onLanguageChanged(size_t langPriority, MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{SettingsMenu:ChangeRequiresRestart}", {"#{sOK}"}, true); std::vector currentLocales = Settings::Manager::getStringArray("preferred locales", "General"); if (currentLocales.size() <= langPriority) currentLocales.resize(langPriority + 1, "en"); const auto& languageCode = *_sender->getItemDataAt(pos); if (!languageCode.empty()) currentLocales[langPriority] = languageCode; else currentLocales.resize(1); Settings::Manager::setStringArray("preferred locales", "General", currentLocales); } void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; Settings::Manager::setInt("window mode", "Video", static_cast(_sender->getIndexSelected())); apply(); } void SettingsWindow::onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos) { int count = 8 * (pos + 1); Settings::Manager::setInt("max lights", "Shaders", count); apply(); configureWidgets(mMainWidget, false); } void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender) { std::vector buttons = {"#{sYes}", "#{sNo}"}; MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{SettingsMenu:LightingResetToDefaults}", buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return; constexpr std::array settings = { "light bounds multiplier", "maximum light distance", "light fade start", "minimum interior brightness", "max lights", "lighting method", }; for (const auto& setting : settings) Settings::Manager::setString(setting, "Shaders", Settings::Manager::mDefaultSettings[{"Shaders", setting}]); auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::mDefaultSettings[{"Shaders", "lighting method"}]); auto lightIndex = mLightingMethodButton->findItemIndexWith(lightingMethodToStr(lightingMethod)); mLightingMethodButton->setIndexSelected(lightIndex); updateMaxLightsComboBox(mMaxLights); apply(); configureWidgets(mMainWidget, false); } void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); std::string off = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "On"); bool newState; if (_sender->castType()->getCaption() == on) { _sender->castType()->setCaption(off); newState = false; } else { _sender->castType()->setCaption(on); newState = true; } if (getSettingType(_sender) == checkButtonType) { Settings::Manager::setBool(getSettingName(_sender), getSettingCategory(_sender), newState); apply(); return; } } void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { if(pos == 0) Settings::Manager::setString("texture mipmap", "General", "nearest"); else if(pos == 1) Settings::Manager::setString("texture mipmap", "General", "linear"); else Log(Debug::Warning) << "Unexpected option pos " << pos; apply(); } void SettingsWindow::onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos) { if (getSettingType(scroller) == "Slider") { std::string valueStr; std::string valueType = getSettingValueType(scroller); if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { float value = pos / float(scroller->getScrollRange()-1); float min,max; getSettingMinMax(scroller, min, max); value = min + (max-min) * value; if (valueType == "Float") Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); else Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value); if (valueType == "Cell") { std::stringstream ss; ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } else if (valueType == "Float") { std::stringstream ss; ss << std::fixed << std::setprecision(2) << value; valueStr = ss.str(); } else valueStr = MyGUI::utility::toString(int(value)); } else { Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); valueStr = MyGUI::utility::toString(pos); } updateSliderLabel(scroller, valueStr); apply(); } } void SettingsWindow::apply() { const Settings::CategorySettingVector changed = Settings::Manager::getPendingChanges(); MWBase::Environment::get().getWorld()->processChangedSettings(changed); MWBase::Environment::get().getSoundManager()->processChangedSettings(changed); MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed); Settings::Manager::resetPendingChanges(); } void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender) { if(mKeyboardMode) return; mKeyboardMode = true; mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); updateControlsBox(); resetScrollbars(); } void SettingsWindow::onControllerSwitchClicked(MyGUI::Widget* _sender) { if(!mKeyboardMode) return; mKeyboardMode = false; mKeyboardSwitch->setStateSelected(false); mControllerSwitch->setStateSelected(true); updateControlsBox(); resetScrollbars(); } void SettingsWindow::updateControlsBox() { while (mControlsBox->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mControlsBox->getChildAt(0)); MWBase::Environment::get().getWindowManager()->removeStaticMessageBox(); std::vector actions; if(mKeyboardMode) actions = MWBase::Environment::get().getInputManager()->getActionKeySorting(); else actions = MWBase::Environment::get().getInputManager()->getActionControllerSorting(); for (const int& action : actions) { std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (action); if (desc == "") continue; std::string binding; if(mKeyboardMode) binding = MWBase::Environment::get().getInputManager()->getActionKeyBindingName(action); else binding = MWBase::Environment::get().getInputManager()->getActionControllerBindingName(action); Gui::SharedStateButton* leftText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); leftText->setCaptionWithReplacing(desc); Gui::SharedStateButton* rightText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); rightText->setCaptionWithReplacing(binding); rightText->setTextAlign (MyGUI::Align::Right); rightText->setUserData(action); // save the action id for callbacks rightText->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onRebindAction); rightText->eventMouseWheel += MyGUI::newDelegate(this, &SettingsWindow::onInputTabMouseWheel); Gui::ButtonGroup group; group.push_back(leftText); group.push_back(rightText); Gui::SharedStateButton::createButtonGroup(group); } layoutControlsBox(); } void SettingsWindow::updateLightSettings() { auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod(); std::string lightingMethodStr = lightingMethodToStr(lightingMethod); mLightingMethodButton->removeAllItems(); std::array methods = { SceneUtil::LightingMethod::FFP, SceneUtil::LightingMethod::PerObjectUniform, SceneUtil::LightingMethod::SingleUBO, }; for (const auto& method : methods) { if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->isSupportedLightingMethod(method)) continue; mLightingMethodButton->addItem(lightingMethodToStr(method), SceneUtil::LightManager::getLightingMethodString(method)); } mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); } void SettingsWindow::updateWindowModeSettings() { size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); if (index > static_cast(Settings::WindowMode::Windowed)) index = MyGUI::ITEM_NONE; mWindowModeList->setIndexSelected(index); if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) { // check if this resolution is supported in fullscreen if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) { std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; parseResolution (resX, resY, resStr); Settings::Manager::setInt("resolution x", "Video", resX); Settings::Manager::setInt("resolution y", "Video", resY); } bool supported = false; int fallbackX = 0, fallbackY = 0; for (unsigned int i=0; igetItemCount(); ++i) { std::string resStr = mResolutionList->getItemNameAt(i); int resX, resY; parseResolution (resX, resY, resStr); if (i == 0) { fallbackX = resX; fallbackY = resY; } if (resX == Settings::Manager::getInt("resolution x", "Video") && resY == Settings::Manager::getInt("resolution y", "Video")) supported = true; } if (!supported && mResolutionList->getItemCount()) { if (fallbackX != 0 && fallbackY != 0) { Settings::Manager::setInt("resolution x", "Video", fallbackX); Settings::Manager::setInt("resolution y", "Video", fallbackY); } } mWindowBorderButton->setEnabled(false); } } void SettingsWindow::layoutControlsBox() { const int h = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; const int w = mControlsBox->getWidth() - 28; const int noWidgetsInRow = 2; const int totalH = mControlsBox->getChildCount() / noWidgetsInRow * h; for (size_t i = 0; i < mControlsBox->getChildCount(); i++) { MyGUI::Widget * widget = mControlsBox->getChildAt(i); widget->setCoord(0, i / noWidgetsInRow * h, w, h); } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mControlsBox->setVisibleVScroll(false); mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(totalH, mControlsBox->getHeight())); mControlsBox->setVisibleVScroll(true); } namespace { std::string escapeRegex(const std::string& str) { static const std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); return std::regex_replace(str, specialChars, R"(\$&)"); } std::regex wordSearch(const std::string& query) { static const std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex); auto wordsEnd = std::sregex_iterator(); std::string searchRegex("("); for (auto it = wordsBegin; it != wordsEnd; ++it) { if (it != wordsBegin) searchRegex += '|'; searchRegex += escapeRegex(query.substr(it->position(), it->length())); } searchRegex += ')'; // query had only whitespace characters if (searchRegex == "()") searchRegex = "^(.*)$"; return std::regex(searchRegex, std::regex_constants::extended | std::regex_constants::icase); } double weightedSearch(const std::regex& regex, const std::string& text) { std::smatch matches; std::regex_search(text, matches, regex); // need a signed value, so cast to double (not an integer type to guarantee no overflow) return static_cast(matches.size()); } } void SettingsWindow::renderScriptSettings() { mScriptAdapter->detach(); mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); struct WeightedPage { size_t mIndex; std::string mName; double mNameWeight; double mHintWeight; constexpr auto tie() const { return std::tie(mNameWeight, mHintWeight, mName); } constexpr bool operator<(const WeightedPage& rhs) const { return tie() < rhs.tie(); } }; std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); std::vector weightedPages; weightedPages.reserve(LuaUi::scriptSettingsPageCount()); for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) { LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); double nameWeight = weightedSearch(searchRegex, page.mName); double hintWeight = weightedSearch(searchRegex, page.mSearchHints); if ((nameWeight + hintWeight) > 0) weightedPages.push_back({ i, page.mName, -nameWeight, -hintWeight }); } std::sort(weightedPages.begin(), weightedPages.end()); for (const WeightedPage& weightedPage : weightedPages) mScriptList->addItem(weightedPage.mName, weightedPage.mIndex); // Hide script settings when the game world isn't loaded bool disabled = LuaUi::scriptSettingsPageCount() == 0; mScriptFilter->setVisible(!disabled); mScriptList->setVisible(!disabled); mScriptBox->setVisible(!disabled); mScriptDisabled->setVisible(disabled); LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); mScriptView->setCanvasSize(mScriptAdapter->getSize()); } void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*) { renderScriptSettings(); } void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index) { mScriptAdapter->detach(); mCurrentPage = -1; if (index < mScriptList->getItemCount()) { mCurrentPage = *mScriptList->getItemDataAt(index); LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); } mScriptView->setCanvasSize(mScriptAdapter->getSize()); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); _sender->castType()->setCaptionWithReplacing("#{sNone}"); MWBase::Environment::get().getWindowManager ()->staticMessageBox ("#{sControlsMenu3}"); MWBase::Environment::get().getWindowManager ()->disallowMouse(); MWBase::Environment::get().getInputManager ()->enableDetectingBindingMode (actionId, mKeyboardMode); } void SettingsWindow::onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mControlsBox->getViewOffset().top + _rel*0.3f > 0) mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0)); else mControlsBox->setViewOffset(MyGUI::IntPoint(0, static_cast(mControlsBox->getViewOffset().top + _rel*0.3f))); } void SettingsWindow::onResetDefaultBindings(MyGUI::Widget* _sender) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sNotifyMessage66}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindingsAccept); dialog->eventCancelClicked.clear(); } void SettingsWindow::onResetDefaultBindingsAccept() { if(mKeyboardMode) MWBase::Environment::get().getInputManager ()->resetToDefaultKeyBindings (); else MWBase::Environment::get().getInputManager()->resetToDefaultControllerBindings(); updateControlsBox (); } void SettingsWindow::onOpen() { highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); updateWindowModeSettings(); resetScrollbars(); renderScriptSettings(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) { layoutControlsBox(); } void SettingsWindow::computeMinimumWindowSize() { auto* window = mMainWidget->castType(); auto minSize = window->getMinSize(); // Window should be at minimum wide enough to show all tabs. int tabBarWidth = 0; for (uint32_t i = 0; i < mSettingsTab->getItemCount(); i++) { tabBarWidth += mSettingsTab->getButtonWidthAt(i); } // Need to include window margins int margins = mMainWidget->getWidth() - mSettingsTab->getWidth(); int minimumWindowWidth = tabBarWidth + margins; if (minimumWindowWidth > minSize.width) { minSize.width = minimumWindowWidth; window->setMinSize(minSize); // Make a dummy call to setSize so MyGUI can apply any resize resulting from the change in MinSize mMainWidget->setSize(mMainWidget->getSize()); } } void SettingsWindow::resetScrollbars() { mResolutionList->setScrollPosition(0); mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0)); } } openmw-openmw-0.48.0/apps/openmw/mwgui/settingswindow.hpp000066400000000000000000000101171445372753700236350ustar00rootroot00000000000000#ifndef MWGUI_SETTINGS_H #define MWGUI_SETTINGS_H #include #include "windowbase.hpp" namespace MWGui { class SettingsWindow : public WindowBase { public: SettingsWindow(); void onOpen() override; void updateControlsBox(); void updateLightSettings(); void updateWindowModeSettings(); void onResChange(int, int) override { center(); } protected: MyGUI::TabControl* mSettingsTab; MyGUI::Button* mOkButton; // graphics MyGUI::ListBox* mResolutionList; MyGUI::ComboBox* mWindowModeList; MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterRainRippleDetail; MyGUI::ComboBox* mMaxLights; MyGUI::ComboBox* mLightingMethodButton; MyGUI::Button* mLightsResetButton; MyGUI::ComboBox* mPrimaryLanguage; MyGUI::ComboBox* mSecondaryLanguage; // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; MyGUI::Button* mKeyboardSwitch; MyGUI::Button* mControllerSwitch; bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller MyGUI::EditBox* mScriptFilter; MyGUI::ListBox* mScriptList; MyGUI::Widget* mScriptBox; MyGUI::Widget* mScriptDisabled; MyGUI::ScrollView* mScriptView; LuaUi::LuaAdapter* mScriptAdapter; int mCurrentPage; void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onButtonToggled(MyGUI::Widget* _sender); void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); void onResolutionAccept(); void onResolutionCancel(); void highlightCurrentResolution(); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightsResetButtonClicked(MyGUI::Widget* _sender); void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos); void onPrimaryLanguageChanged(MyGUI::ComboBox* _sender, size_t pos) { onLanguageChanged(0, _sender, pos); } void onSecondaryLanguageChanged(MyGUI::ComboBox* _sender, size_t pos) { onLanguageChanged(1, _sender, pos); } void onLanguageChanged(size_t langPriority, MyGUI::ComboBox* _sender, size_t pos); void onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); void onResetDefaultBindingsAccept (); void onKeyboardSwitchClicked(MyGUI::Widget* _sender); void onControllerSwitchClicked(MyGUI::Widget* _sender); void onWindowResize(MyGUI::Window* _sender); void onScriptFilterChange(MyGUI::EditBox*); void onScriptListSelection(MyGUI::ListBox*, size_t index); void apply(); void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); void renderScriptSettings(); void computeMinimumWindowSize(); private: void resetScrollbars(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/sortfilteritemmodel.cpp000066400000000000000000000332341445372753700246420ustar00rootroot00000000000000#include "sortfilteritemmodel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/alchemy.hpp" namespace { unsigned int getTypeOrder(unsigned int type) { switch (type) { case ESM::Weapon::sRecordId: return 0; case ESM::Armor::sRecordId: return 1; case ESM::Clothing::sRecordId: return 2; case ESM::Potion::sRecordId: return 3; case ESM::Ingredient::sRecordId: return 4; case ESM::Apparatus::sRecordId: return 5; case ESM::Book::sRecordId: return 6; case ESM::Light::sRecordId: return 7; case ESM::Miscellaneous::sRecordId: return 8; case ESM::Lockpick::sRecordId: return 9; case ESM::Repair::sRecordId: return 10; case ESM::Probe::sRecordId: return 11; } assert(false && "Invalid type value"); return std::numeric_limits::max(); } bool compareType(unsigned int type1, unsigned int type2) { return getTypeOrder(type1) < getTypeOrder(type2); } struct Compare { bool mSortByType; Compare() : mSortByType(true) {} bool operator() (const MWGui::ItemStack& left, const MWGui::ItemStack& right) { if (mSortByType && left.mType != right.mType) return left.mType < right.mType; float result = 0; // compare items by type auto leftType = left.mBase.getType(); auto rightType = right.mBase.getType(); if (leftType != rightType) return compareType(leftType, rightType); // compare items by name std::string leftName = Utf8Stream::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); std::string rightName = Utf8Stream::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); result = leftName.compare(rightName); if (result != 0) return result < 0; // compare items by enchantment: // 1. enchanted items showed before non-enchanted // 2. item with lesser charge percent comes after items with more charge percent // 3. item with constant effect comes before items with non-constant effects int leftChargePercent = -1; int rightChargePercent = -1; leftName = left.mBase.getClass().getEnchantment(left.mBase); rightName = right.mBase.getClass().getEnchantment(right.mBase); if (!leftName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) leftChargePercent = 101; else leftChargePercent = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } if (!rightName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) rightChargePercent = 101; else rightChargePercent = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } result = leftChargePercent - rightChargePercent; if (result != 0) return result > 0; // compare items by condition if (left.mBase.getClass().hasItemHealth(left.mBase) && right.mBase.getClass().hasItemHealth(right.mBase)) { result = left.mBase.getClass().getItemHealth(left.mBase) - right.mBase.getClass().getItemHealth(right.mBase); if (result != 0) return result > 0; } // compare items by remaining usage time result = left.mBase.getClass().getRemainingUsageTime(left.mBase) - right.mBase.getClass().getRemainingUsageTime(right.mBase); if (result != 0) return result > 0; // compare items by value result = left.mBase.getClass().getValue(left.mBase) - right.mBase.getClass().getValue(right.mBase); if (result != 0) return result > 0; // compare items by weight result = left.mBase.getClass().getWeight(left.mBase) - right.mBase.getClass().getWeight(right.mBase); if (result != 0) return result > 0; // compare items by Id leftName = left.mBase.getCellRef().getRefId(); rightName = right.mBase.getCellRef().getRefId(); result = leftName.compare(rightName); return result < 0; } }; } namespace MWGui { SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) : mCategory(Category_All) , mFilter(0) , mSortByType(true) , mNameFilter("") , mEffectFilter("") { mSourceModel = sourceModel; } bool SortFilterItemModel::allowedToUseItems() const { return mSourceModel->allowedToUseItems(); } void SortFilterItemModel::addDragItem (const MWWorld::Ptr& dragItem, size_t count) { mDragItems.emplace_back(dragItem, count); } void SortFilterItemModel::clearDragItems() { mDragItems.clear(); } bool SortFilterItemModel::filterAccepts (const ItemStack& item) { MWWorld::Ptr base = item.mBase; int category = 0; switch (base.getType()) { case ESM::Armor::sRecordId: case ESM::Clothing::sRecordId: category = Category_Apparel; break; case ESM::Weapon::sRecordId: category = Category_Weapon; break; case ESM::Ingredient::sRecordId: case ESM::Potion::sRecordId: category = Category_Magic; break; case ESM::Miscellaneous::sRecordId: case ESM::Repair::sRecordId: case ESM::Lockpick::sRecordId: case ESM::Light::sRecordId: case ESM::Apparatus::sRecordId: case ESM::Book::sRecordId: case ESM::Probe::sRecordId: category = Category_Misc; break; } if (item.mFlags & ItemStack::Flag_Enchanted) category |= Category_Magic; if (!(category & mCategory)) return false; if (mFilter & Filter_OnlyIngredients) { if (base.getType() != ESM::Ingredient::sRecordId) return false; if (!mNameFilter.empty() && !mEffectFilter.empty()) throw std::logic_error("name and magic effect filter are mutually exclusive"); if (!mNameFilter.empty()) { const auto itemName = Utf8Stream::lowerCaseUtf8(base.getClass().getName(base)); return itemName.find(mNameFilter) != std::string::npos; } if (!mEffectFilter.empty()) { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); const auto alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); const auto effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill); for (const auto& effect : effects) { const auto ciEffect = Utf8Stream::lowerCaseUtf8(effect); if (ciEffect.find(mEffectFilter) != std::string::npos) return true; } return false; } return true; } if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getType() != ESM::Miscellaneous::sRecordId || base.getCellRef().getSoul() == "" || !MWBase::Environment::get().getWorld()->getStore().get().search(base.getCellRef().getSoul()))) return false; if ((mFilter & Filter_OnlyRepairTools) && (base.getType() != ESM::Repair::sRecordId)) return false; if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted || (base.getType() != ESM::Armor::sRecordId && base.getType() != ESM::Clothing::sRecordId && base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Book::sRecordId))) return false; if ((mFilter & Filter_OnlyEnchantable) && base.getType() == ESM::Book::sRecordId && !base.get()->mBase->mData.mIsScroll) return false; if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) { std::unique_ptr actionOnUse = base.getClass().use(base); if (!actionOnUse || actionOnUse->isNullAction()) return false; } if ((mFilter & Filter_OnlyRepairable) && ( !base.getClass().hasItemHealth(base) || (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) || (base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Armor::sRecordId))) return false; if (mFilter & Filter_OnlyRechargable) { if (!(item.mFlags & ItemStack::Flag_Enchanted)) return false; std::string enchId = base.getClass().getEnchantment(base); const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(enchId); if (!ench) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchId << "' on item " << base.getCellRef().getRefId(); return false; } if (base.getCellRef().getEnchantmentCharge() >= ench->mData.mCharge || base.getCellRef().getEnchantmentCharge() == -1) return false; } std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); if(compare.find(mNameFilter) == std::string::npos) return false; return true; } ItemStack SortFilterItemModel::getItem (ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); if (mItems.size() <= static_cast(index)) throw std::runtime_error("Item index out of range"); return mItems[index]; } size_t SortFilterItemModel::getItemCount() { return mItems.size(); } void SortFilterItemModel::setCategory (int category) { mCategory = category; } void SortFilterItemModel::setFilter (int filter) { mFilter = filter; } void SortFilterItemModel::setNameFilter (const std::string& filter) { mNameFilter = Utf8Stream::lowerCaseUtf8(filter); } void SortFilterItemModel::setEffectFilter (const std::string& filter) { mEffectFilter = Utf8Stream::lowerCaseUtf8(filter); } void SortFilterItemModel::update() { mSourceModel->update(); size_t count = mSourceModel->getItemCount(); mItems.clear(); for (size_t i=0; igetItem(i); for (std::vector >::iterator it = mDragItems.begin(); it != mDragItems.end(); ++it) { if (item.mBase == it->first) { if (item.mCount < it->second) throw std::runtime_error("Dragging more than present in the model"); item.mCount -= it->second; } } if (item.mCount > 0 && filterAccepts(item)) mItems.push_back(item); } Compare cmp; cmp.mSortByType = mSortByType; std::sort(mItems.begin(), mItems.end(), cmp); } void SortFilterItemModel::onClose() { mSourceModel->onClose(); } bool SortFilterItemModel::onDropItem(const MWWorld::Ptr &item, int count) { return mSourceModel->onDropItem(item, count); } bool SortFilterItemModel::onTakeItem(const MWWorld::Ptr &item, int count) { return mSourceModel->onTakeItem(item, count); } } openmw-openmw-0.48.0/apps/openmw/mwgui/sortfilteritemmodel.hpp000066400000000000000000000042721445372753700246470ustar00rootroot00000000000000#ifndef MWGUI_SORT_FILTER_ITEM_MODEL_H #define MWGUI_SORT_FILTER_ITEM_MODEL_H #include "itemmodel.hpp" namespace MWGui { class SortFilterItemModel : public ProxyItemModel { public: SortFilterItemModel (ItemModel* sourceModel); void update() override; bool filterAccepts (const ItemStack& item); bool allowedToUseItems() const override; ItemStack getItem (ModelIndex index) override; size_t getItemCount() override; /// Dragged items are not displayed. void addDragItem (const MWWorld::Ptr& dragItem, size_t count); void clearDragItems(); void setCategory (int category); void setFilter (int filter); void setNameFilter (const std::string& filter); void setEffectFilter (const std::string& filter); /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } void onClose() override; bool onDropItem(const MWWorld::Ptr &item, int count) override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; static constexpr int Category_Weapon = (1<<1); static constexpr int Category_Apparel = (1<<2); static constexpr int Category_Misc = (1<<3); static constexpr int Category_Magic = (1<<4); static constexpr int Category_All = 255; static constexpr int Filter_OnlyIngredients = (1<<0); static constexpr int Filter_OnlyEnchanted = (1<<1); static constexpr int Filter_OnlyEnchantable = (1<<2); static constexpr int Filter_OnlyChargedSoulstones = (1<<3); static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action static constexpr int Filter_OnlyRepairable = (1<<5); static constexpr int Filter_OnlyRechargable = (1<<6); static constexpr int Filter_OnlyRepairTools = (1<<7); private: std::vector mItems; std::vector > mDragItems; int mCategory; int mFilter; bool mSortByType; std::string mNameFilter; // filter by item name std::string mEffectFilter; // filter by magic effect }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/soulgemdialog.cpp000066400000000000000000000016241445372753700233760ustar00rootroot00000000000000#include "soulgemdialog.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "messagebox.hpp" namespace MWGui { void SoulgemDialog::show(const MWWorld::Ptr &soulgem) { mSoulgem = soulgem; std::vector buttons; buttons.emplace_back("#{sRechargeEnchantment}"); buttons.emplace_back("#{sMake Enchantment}"); mManager->createInteractiveMessageBox("#{sDoYouWantTo}", buttons); mManager->eventButtonPressed += MyGUI::newDelegate(this, &SoulgemDialog::onButtonPressed); } void SoulgemDialog::onButtonPressed(int button) { if (button == 0) { MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge, mSoulgem); } else { MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mSoulgem); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/soulgemdialog.hpp000066400000000000000000000007341445372753700234040ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_SOULGEMDIALOG_H #define OPENMW_MWGUI_SOULGEMDIALOG_H #include "../mwworld/ptr.hpp" namespace MWGui { class MessageBoxManager; class SoulgemDialog { public: SoulgemDialog (MessageBoxManager* manager) : mManager(manager) {} void show (const MWWorld::Ptr& soulgem); void onButtonPressed(int button); private: MessageBoxManager* mManager; MWWorld::Ptr mSoulgem; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellbuyingwindow.cpp000066400000000000000000000170131445372753700243270ustar00rootroot00000000000000#include "spellbuyingwindow.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" namespace MWGui { SpellBuyingWindow::SpellBuyingWindow() : WindowBase("openmw_spell_buying_window.layout") , mCurrentY(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mPlayerGold, "PlayerGold"); getWidget(mSpellsView, "SpellsView"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked); } bool SpellBuyingWindow::sortSpells (const ESM::Spell* left, const ESM::Spell* right) { std::string leftName = Misc::StringUtils::lowerCase(left->mName); std::string rightName = Misc::StringUtils::lowerCase(right->mName); return leftName.compare(rightName) < 0; } void SpellBuyingWindow::addSpell(const ESM::Spell& spell) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); int price = std::max(1, static_cast(spell.mData.mCost*store.get().find("fSpellValueMult")->mValue.getFloat())); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // TODO: refactor to use MyGUI::ListBox int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::Button* toAdd = mSpellsView->createWidget( price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default ); mCurrentY += lineHeight; toAdd->setUserData(price); toAdd->setCaptionWithReplacing(spell.mName+" - "+MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setSize(mSpellsView->getWidth(), lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel); toAdd->setUserString("ToolTipType", "Spell"); toAdd->setUserString("Spell", spell.mId); toAdd->setUserString("SpellCost", std::to_string(spell.mData.mCost)); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick); mSpellsWidgetMap.insert(std::make_pair (toAdd, spell.mId)); } void SpellBuyingWindow::clearSpells() { mSpellsView->setViewOffset(MyGUI::IntPoint(0,0)); mCurrentY = 0; while (mSpellsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mSpellsView->getChildAt(0)); mSpellsWidgetMap.clear(); } void SpellBuyingWindow::setPtr(const MWWorld::Ptr &actor) { setPtr(actor, 0); } void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor, int startOffset) { center(); mPtr = actor; clearSpells(); MWMechanics::Spells& merchantSpells = actor.getClass().getCreatureStats (actor).getSpells(); std::vector spellsToSort; for (const ESM::Spell* spell : merchantSpells) { if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers if (actor.getClass().isNpc()) { const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find( actor.get()->mBase->mRace); if (race->mPowers.exists(spell->mId)) continue; } if (playerHasSpell(spell->mId)) continue; spellsToSort.push_back(spell); } std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells); for (const ESM::Spell* spell : spellsToSort) { addSpell(*spell); } spellsToSort.clear(); updateLabels(); // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mSpellsView->setVisibleVScroll(false); mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); mSpellsView->setVisibleVScroll(true); mSpellsView->setViewOffset(MyGUI::IntPoint(0, startOffset)); } bool SpellBuyingWindow::playerHasSpell(const std::string &id) { MWWorld::Ptr player = MWMechanics::getPlayer(); return player.getClass().getCreatureStats(player).getSpells().hasSpell(id); } void SpellBuyingWindow::onSpellButtonClick(MyGUI::Widget* _sender) { int price = *_sender->getUserData(); MWWorld::Ptr player = MWMechanics::getPlayer(); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); setPtr(mPtr, mSpellsView->getViewOffset().top); MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); } void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellBuying); } void SpellBuyingWindow::updateLabels() { MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, mPlayerGold->getHeight()); } void SpellBuyingWindow::onReferenceUnavailable() { // remove both Spells and Dialogue (since you always trade with the NPC/creature that you have previously talked to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void SpellBuyingWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mSpellsView->getViewOffset().top + _rel*0.3 > 0) mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0)); else mSpellsView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSpellsView->getViewOffset().top + _rel*0.3f))); } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellbuyingwindow.hpp000066400000000000000000000027041445372753700243350ustar00rootroot00000000000000#ifndef MWGUI_SpellBuyingWINDOW_H #define MWGUI_SpellBuyingWINDOW_H #include "windowbase.hpp" #include "referenceinterface.hpp" namespace ESM { struct Spell; } namespace MyGUI { class Gui; class Widget; } namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase { public: SpellBuyingWindow(); void setPtr(const MWWorld::Ptr& actor) override; void setPtr(const MWWorld::Ptr& actor, int startOffset); void onFrame(float dt) override { checkReferenceAvailable(); } void clear() override { resetReference(); } void onResChange(int, int) override { center(); } protected: MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; MyGUI::ScrollView* mSpellsView; std::map mSpellsWidgetMap; void onCancelButtonClicked(MyGUI::Widget* _sender); void onSpellButtonClick(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void addSpell(const ESM::Spell& spell); void clearSpells(); int mCurrentY; void updateLabels(); void onReferenceUnavailable() override; bool playerHasSpell (const std::string& id); private: static bool sortSpells (const ESM::Spell* left, const ESM::Spell* right); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellcreationdialog.cpp000066400000000000000000000672231445372753700245760ustar00rootroot00000000000000#include "spellcreationdialog.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "tooltips.hpp" #include "class.hpp" #include "widgets.hpp" namespace { bool sortMagicEffects (short id1, short id2) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); return gmst.find(ESM::MagicEffect::effectIdToString (id1))->mValue.getString() < gmst.find(ESM::MagicEffect::effectIdToString (id2))->mValue.getString(); } void init(ESM::ENAMstruct& effect) { effect.mArea = 0; effect.mDuration = 0; effect.mEffectID = -1; effect.mMagnMax = 0; effect.mMagnMin = 0; effect.mRange = 0; effect.mSkill = -1; effect.mAttribute = -1; } } namespace MWGui { EditEffectDialog::EditEffectDialog() : WindowModal("openmw_edit_effect.layout") , mEditing(false) , mMagicEffect(nullptr) , mConstantEffect(false) { init(mEffect); init(mOldEffect); getWidget(mCancelButton, "CancelButton"); getWidget(mOkButton, "OkButton"); getWidget(mDeleteButton, "DeleteButton"); getWidget(mRangeButton, "RangeButton"); getWidget(mMagnitudeMinValue, "MagnitudeMinValue"); getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue"); getWidget(mDurationValue, "DurationValue"); getWidget(mAreaValue, "AreaValue"); getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider"); getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider"); getWidget(mDurationSlider, "DurationSlider"); getWidget(mAreaSlider, "AreaSlider"); getWidget(mEffectImage, "EffectImage"); getWidget(mEffectName, "EffectName"); getWidget(mAreaText, "AreaText"); getWidget(mDurationBox, "DurationBox"); getWidget(mAreaBox, "AreaBox"); getWidget(mMagnitudeBox, "MagnitudeBox"); mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); mMagnitudeMinSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); mMagnitudeMaxSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); } void EditEffectDialog::setConstantEffect(bool constant) { mConstantEffect = constant; } void EditEffectDialog::onOpen() { WindowModal::onOpen(); center(); } bool EditEffectDialog::exit() { if(mEditing) eventEffectModified(mOldEffect); else eventEffectRemoved(mEffect); return true; } void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) { bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; setMagicEffect(effect); mEditing = false; mDeleteButton->setVisible (false); mEffect.mRange = ESM::RT_Self; if (!allowSelf) mEffect.mRange = ESM::RT_Touch; if (!allowTouch) mEffect.mRange = ESM::RT_Target; mEffect.mMagnMin = 1; mEffect.mMagnMax = 1; mEffect.mDuration = 1; mEffect.mArea = 0; mEffect.mSkill = -1; mEffect.mAttribute = -1; eventEffectAdded(mEffect); onRangeButtonClicked(mRangeButton); mMagnitudeMinSlider->setScrollPosition (0); mMagnitudeMaxSlider->setScrollPosition (0); mAreaSlider->setScrollPosition (0); mDurationSlider->setScrollPosition (0); mDurationValue->setCaption("1"); mMagnitudeMinValue->setCaption("1"); const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); mMagnitudeMaxValue->setCaption(to + " 1"); mAreaValue->setCaption("0"); setVisible(true); } void EditEffectDialog::editEffect (ESM::ENAMstruct effect) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); setMagicEffect(magicEffect); mOldEffect = effect; mEffect = effect; mEditing = true; mDeleteButton->setVisible (true); mMagnitudeMinSlider->setScrollPosition (effect.mMagnMin-1); mMagnitudeMaxSlider->setScrollPosition (effect.mMagnMax-1); mAreaSlider->setScrollPosition (effect.mArea); mDurationSlider->setScrollPosition (effect.mDuration-1); if (mEffect.mRange == ESM::RT_Self) mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}"); else if (mEffect.mRange == ESM::RT_Touch) mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); onMagnitudeMinChanged (mMagnitudeMinSlider, effect.mMagnMin-1); onMagnitudeMaxChanged (mMagnitudeMinSlider, effect.mMagnMax-1); onAreaChanged (mAreaSlider, effect.mArea); onDurationChanged (mDurationSlider, effect.mDuration-1); eventEffectModified(mEffect); updateBoxes(); } void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect) { mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}"); mEffect.mEffectID = effect->mIndex; mMagicEffect = effect; updateBoxes(); } void EditEffectDialog::updateBoxes() { static int startY = mMagnitudeBox->getPosition().top; int curY = startY; mMagnitudeBox->setVisible (false); mDurationBox->setVisible (false); mAreaBox->setVisible (false); if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); mMagnitudeBox->setVisible (true); curY += mMagnitudeBox->getSize().height; } if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&mConstantEffect==false) { mDurationBox->setPosition(mDurationBox->getPosition().left, curY); mDurationBox->setVisible (true); curY += mDurationBox->getSize().height; } if (mEffect.mRange != ESM::RT_Self) { mAreaBox->setPosition(mAreaBox->getPosition().left, curY); mAreaBox->setVisible (true); //curY += mAreaBox->getSize().height; } } void EditEffectDialog::onRangeButtonClicked (MyGUI::Widget* sender) { mEffect.mRange = (mEffect.mRange+1)%3; // cycle through range types until we find something that's allowed // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect dialog) bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (mEffect.mRange == ESM::RT_Self && !allowSelf) mEffect.mRange = (mEffect.mRange+1)%3; if (mEffect.mRange == ESM::RT_Touch && !allowTouch) mEffect.mRange = (mEffect.mRange+1)%3; if (mEffect.mRange == ESM::RT_Target && !allowTarget) mEffect.mRange = (mEffect.mRange+1)%3; if(mEffect.mRange == ESM::RT_Self) { mAreaSlider->setScrollPosition(0); onAreaChanged(mAreaSlider,0); } if (mEffect.mRange == ESM::RT_Self) mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}"); else if (mEffect.mRange == ESM::RT_Touch) mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); updateBoxes(); eventEffectModified(mEffect); } void EditEffectDialog::onDeleteButtonClicked (MyGUI::Widget* sender) { setVisible(false); eventEffectRemoved(mEffect); } void EditEffectDialog::onOkButtonClicked (MyGUI::Widget* sender) { setVisible(false); } void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender) { setVisible(false); exit(); } void EditEffectDialog::setSkill (int skill) { mEffect.mSkill = skill; eventEffectModified(mEffect); } void EditEffectDialog::setAttribute (int attribute) { mEffect.mAttribute = attribute; eventEffectModified(mEffect); } void EditEffectDialog::onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos) { mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos+1)); mEffect.mMagnMin = pos+1; // trigger the check again (see below) onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition ()); eventEffectModified(mEffect); } void EditEffectDialog::onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos) { // make sure the max value is actually larger or equal than the min value size_t magnMin = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning if (pos+1 < magnMin) { pos = mEffect.mMagnMin-1; sender->setScrollPosition (pos); } mEffect.mMagnMax = pos+1; const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos+1)); eventEffectModified(mEffect); } void EditEffectDialog::onDurationChanged (MyGUI::ScrollBar* sender, size_t pos) { mDurationValue->setCaption(MyGUI::utility::toString(pos+1)); mEffect.mDuration = pos+1; eventEffectModified(mEffect); } void EditEffectDialog::onAreaChanged (MyGUI::ScrollBar* sender, size_t pos) { mAreaValue->setCaption(MyGUI::utility::toString(pos)); mEffect.mArea = pos; eventEffectModified(mEffect); } // ------------------------------------------------------------------------------------------------ SpellCreationDialog::SpellCreationDialog() : WindowBase("openmw_spellcreation_dialog.layout") , EffectEditorBase(EffectEditorBase::Spellmaking) { getWidget(mNameEdit, "NameEdit"); getWidget(mMagickaCost, "MagickaCost"); getWidget(mSuccessChance, "SuccessChance"); getWidget(mAvailableEffectsList, "AvailableEffects"); getWidget(mUsedEffectsView, "UsedEffects"); getWidget(mPriceLabel, "PriceLabel"); getWidget(mBuyButton, "BuyButton"); getWidget(mCancelButton, "CancelButton"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked); mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked); mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept); setWidgets(mAvailableEffectsList, mUsedEffectsView); } void SpellCreationDialog::setPtr (const MWWorld::Ptr& actor) { mPtr = actor; mNameEdit->setCaption(""); startEditing(); } void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); } void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender) { if (mEffects.size() <= 0) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}"); return; } if (mNameEdit->getCaption () == "") { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}"); return; } if (mMagickaCost->getCaption() == "0") { MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu8}"); return; } MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); if (price > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; } mSpell.mName = mNameEdit->getCaption(); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); MWBase::Environment::get().getWindowManager()->playSound ("Mysticism Hit"); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); } void SpellCreationDialog::onAccept(MyGUI::EditBox *sender) { onBuyButtonClicked(sender); // To do not spam onAccept() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void SpellCreationDialog::onOpen() { center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } void SpellCreationDialog::onReferenceUnavailable () { MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); } void SpellCreationDialog::notifyEffectsChanged () { if (mEffects.empty()) { mMagickaCost->setCaption("0"); mPriceLabel->setCaption("0"); mSuccessChance->setCaption("0"); return; } float y = 0; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (const ESM::ENAMstruct& effect : mEffects) { y += std::max(1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); if (effect.mRange == ESM::RT_Target) y *= 1.5; } ESM::EffectList effectList; effectList.mList = mEffects; mSpell.mEffects = effectList; mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); int price = std::max(1, static_cast(y * fSpellMakingValueMult)); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); int intChance = std::min(100, int(chance)); mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); } // ------------------------------------------------------------------------------------------------ EffectEditorBase::EffectEditorBase(Type type) : mAvailableEffectsList(nullptr) , mUsedEffectsView(nullptr) , mAddEffectDialog() , mSelectAttributeDialog(nullptr) , mSelectSkillDialog(nullptr) , mSelectedEffect(0) , mSelectedKnownEffectId(0) , mConstantEffect(false) , mType(type) { mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); mAddEffectDialog.setVisible (false); } EffectEditorBase::~EffectEditorBase() { } void EffectEditorBase::startEditing () { // get the list of magic effects that are known to the player MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); std::vector knownEffects; for (const ESM::Spell* spell : spells) { // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) continue; for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) { const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectInfo.mEffectID); // skip effects that do not allow spellmaking/enchanting int requiredFlags = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; if (!(effect->mData.mFlags & requiredFlags)) continue; if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end()) knownEffects.push_back(effectInfo.mEffectID); } } std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); mAvailableEffectsList->clear (); int i=0; for (const short effectId : knownEffects) { mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get().find( ESM::MagicEffect::effectIdToString(effectId))->mValue.getString()); mButtonMapping[i] = effectId; ++i; } mAvailableEffectsList->adjustSize (); mAvailableEffectsList->scrollToTop(); for (const short effectId : knownEffects) { std::string name = MWBase::Environment::get().getWorld ()->getStore ().get().find( ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); ToolTips::createMagicEffectToolTip (w, effectId); } mEffects.clear(); updateEffectsView (); } void EffectEditorBase::setWidgets (Gui::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView) { mAvailableEffectsList = availableEffectsList; mUsedEffectsView = usedEffectsView; mAvailableEffectsList->eventWidgetSelected += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); } void EffectEditorBase::onSelectAttribute () { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectAttributeDialog = nullptr; } void EffectEditorBase::onSelectSkill () { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); mAddEffectDialog.newEffect(effect); mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); mSelectSkillDialog = nullptr; } void EffectEditorBase::onAttributeOrSkillCancel () { if (mSelectSkillDialog) MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); if (mSelectAttributeDialog) MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectSkillDialog = nullptr; mSelectAttributeDialog = nullptr; } void EffectEditorBase::onAvailableEffectClicked (MyGUI::Widget* sender) { if (mEffects.size() >= 8) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}"); return; } int buttonId = *sender->getUserData(); mSelectedKnownEffectId = mButtonMapping[buttonId]; const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (!allowSelf && !allowTouch && !allowTarget) return; // TODO: Show an error message popup? if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) { delete mSelectSkillDialog; mSelectSkillDialog = new SelectSkillDialog(); mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); mSelectSkillDialog->setVisible (true); } else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) { delete mSelectAttributeDialog; mSelectAttributeDialog = new SelectAttributeDialog(); mSelectAttributeDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); mSelectAttributeDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); mSelectAttributeDialog->setVisible (true); } else { for (const ESM::ENAMstruct& effectInfo : mEffects) { if (effectInfo.mEffectID == mSelectedKnownEffectId) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); return; } } mAddEffectDialog.newEffect(effect); } } void EffectEditorBase::onEffectModified (ESM::ENAMstruct effect) { mEffects[mSelectedEffect] = effect; updateEffectsView(); } void EffectEditorBase::onEffectRemoved (ESM::ENAMstruct effect) { mEffects.erase(mEffects.begin() + mSelectedEffect); updateEffectsView(); } void EffectEditorBase::updateEffectsView () { MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (oldWidgets); MyGUI::IntSize size(0,0); int i = 0; for (const ESM::ENAMstruct& effectInfo : mEffects) { Widgets::SpellEffectParams params; params.mEffectID = effectInfo.mEffectID; params.mSkill = effectInfo.mSkill; params.mAttribute = effectInfo.mAttribute; params.mDuration = effectInfo.mDuration; params.mMagnMin = effectInfo.mMagnMin; params.mMagnMax = effectInfo.mMagnMax; params.mRange = effectInfo.mRange; params.mArea = effectInfo.mArea; params.mIsConstant = mConstantEffect; MyGUI::Button* button = mUsedEffectsView->createWidget("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); button->setUserData(i); button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); button->setNeedMouseFocus (true); Widgets::MWSpellEffectPtr effect = button->createWidget("MW_EffectImage", MyGUI::IntCoord(0,0,0,24), MyGUI::Align::Default); effect->setNeedMouseFocus (false); effect->setSpellEffect (params); effect->setSize(effect->getRequestedWidth (), 24); button->setSize(effect->getRequestedWidth (), 24); size.width = std::max(size.width, effect->getRequestedWidth ()); size.height += 24; ++i; } // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mUsedEffectsView->setVisibleHScroll(false); mUsedEffectsView->setCanvasSize(size); mUsedEffectsView->setVisibleHScroll(true); notifyEffectsChanged(); } void EffectEditorBase::onEffectAdded (ESM::ENAMstruct effect) { mEffects.push_back(effect); mSelectedEffect=mEffects.size()-1; updateEffectsView(); } void EffectEditorBase::onEditEffect (MyGUI::Widget *sender) { int id = *sender->getUserData(); mSelectedEffect = id; mAddEffectDialog.editEffect (mEffects[id]); mAddEffectDialog.setVisible (true); } void EffectEditorBase::setConstantEffect(bool constant) { mAddEffectDialog.setConstantEffect(constant); mConstantEffect = constant; if (!constant) return; for (auto it = mEffects.begin(); it != mEffects.end();) { if (it->mRange != ESM::RT_Self) { auto& store = MWBase::Environment::get().getWorld()->getStore(); auto magicEffect = store.get().find(it->mEffectID); if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0) { it = mEffects.erase(it); continue; } it->mRange = ESM::RT_Self; } ++it; } } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellcreationdialog.hpp000066400000000000000000000112451445372753700245740ustar00rootroot00000000000000#ifndef MWGUI_SPELLCREATION_H #define MWGUI_SPELLCREATION_H #include #include #include "windowbase.hpp" #include "referenceinterface.hpp" namespace Gui { class MWList; } namespace MWGui { class SelectSkillDialog; class SelectAttributeDialog; class EditEffectDialog : public WindowModal { public: EditEffectDialog(); void onOpen() override; bool exit() override; void setConstantEffect(bool constant); void setSkill(int skill); void setAttribute(int attribute); void newEffect (const ESM::MagicEffect* effect); void editEffect (ESM::ENAMstruct effect); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Effect; EventHandle_Effect eventEffectAdded; EventHandle_Effect eventEffectModified; EventHandle_Effect eventEffectRemoved; protected: MyGUI::Button* mCancelButton; MyGUI::Button* mOkButton; MyGUI::Button* mDeleteButton; MyGUI::Button* mRangeButton; MyGUI::Widget* mDurationBox; MyGUI::Widget* mMagnitudeBox; MyGUI::Widget* mAreaBox; MyGUI::TextBox* mMagnitudeMinValue; MyGUI::TextBox* mMagnitudeMaxValue; MyGUI::TextBox* mDurationValue; MyGUI::TextBox* mAreaValue; MyGUI::ScrollBar* mMagnitudeMinSlider; MyGUI::ScrollBar* mMagnitudeMaxSlider; MyGUI::ScrollBar* mDurationSlider; MyGUI::ScrollBar* mAreaSlider; MyGUI::TextBox* mAreaText; MyGUI::ImageBox* mEffectImage; MyGUI::TextBox* mEffectName; bool mEditing; protected: void onRangeButtonClicked (MyGUI::Widget* sender); void onDeleteButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); void onCancelButtonClicked (MyGUI::Widget* sender); void onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos); void onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos); void onDurationChanged (MyGUI::ScrollBar* sender, size_t pos); void onAreaChanged (MyGUI::ScrollBar* sender, size_t pos); void setMagicEffect(const ESM::MagicEffect* effect); void updateBoxes(); protected: ESM::ENAMstruct mEffect; ESM::ENAMstruct mOldEffect; const ESM::MagicEffect* mMagicEffect; bool mConstantEffect; }; class EffectEditorBase { public: enum Type { Spellmaking, Enchanting }; EffectEditorBase(Type type); virtual ~EffectEditorBase(); void setConstantEffect(bool constant); protected: std::map mButtonMapping; // maps button ID to effect ID Gui::MWList* mAvailableEffectsList; MyGUI::ScrollView* mUsedEffectsView; EditEffectDialog mAddEffectDialog; SelectAttributeDialog* mSelectAttributeDialog; SelectSkillDialog* mSelectSkillDialog; int mSelectedEffect; short mSelectedKnownEffectId; bool mConstantEffect; std::vector mEffects; void onEffectAdded(ESM::ENAMstruct effect); void onEffectModified(ESM::ENAMstruct effect); void onEffectRemoved(ESM::ENAMstruct effect); void onAvailableEffectClicked (MyGUI::Widget* sender); void onAttributeOrSkillCancel(); void onSelectAttribute(); void onSelectSkill(); void onEditEffect(MyGUI::Widget* sender); void updateEffectsView(); void startEditing(); void setWidgets (Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); virtual void notifyEffectsChanged () {} private: Type mType; }; class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase { public: SpellCreationDialog(); void onOpen() override; void clear() override { resetReference(); } void onFrame(float dt) override { checkReferenceAvailable(); } void setPtr(const MWWorld::Ptr& actor) override; protected: void onReferenceUnavailable() override; void onCancelButtonClicked (MyGUI::Widget* sender); void onBuyButtonClicked (MyGUI::Widget* sender); void onAccept(MyGUI::EditBox* sender); void notifyEffectsChanged() override; MyGUI::EditBox* mNameEdit; MyGUI::TextBox* mMagickaCost; MyGUI::TextBox* mSuccessChance; MyGUI::Button* mBuyButton; MyGUI::Button* mCancelButton; MyGUI::TextBox* mPriceLabel; ESM::Spell mSpell; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellicons.cpp000066400000000000000000000202171445372753700227150ustar00rootroot00000000000000#include "spellicons.hpp" #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" namespace MWGui { void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); std::map> effects; for(const auto& params : stats.getActiveSpells()) { for(const auto& effect : params.getEffects()) { if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) continue; MagicEffectInfo newEffectSource; newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); newEffectSource.mMagnitude = static_cast(effect.mMagnitude); newEffectSource.mPermanent = effect.mDuration == -1.f; newEffectSource.mRemainingTime = effect.mTimeLeft; newEffectSource.mSource = params.getDisplayName(); newEffectSource.mTotalTime = effect.mDuration; effects[effect.mEffectId].push_back(newEffectSource); } } int w=2; for (const auto& [effectId, effectInfos] : effects) { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld ()->getStore ().get().find(effectId); float remainingDuration = 0; float totalDuration = 0; std::string sourcesDescription; static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); bool addNewLine = false; for (const MagicEffectInfo& effectInfo : effectInfos) { if (addNewLine) sourcesDescription += "\n"; // if at least one of the effect sources is permanent, the effect will never wear off if (effectInfo.mPermanent) { remainingDuration = fadeTime; totalDuration = fadeTime; } else { remainingDuration = std::max(remainingDuration, effectInfo.mRemainingTime); totalDuration = std::max(totalDuration, effectInfo.mTotalTime); } sourcesDescription += effectInfo.mSource; if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) sourcesDescription += " (" + MWBase::Environment::get().getWindowManager()->getGameSettingString( ESM::Skill::sSkillNameIds[effectInfo.mKey.mArg], "") + ")"; if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) sourcesDescription += " (" + MWBase::Environment::get().getWindowManager()->getGameSettingString( ESM::Attribute::sGmstAttributeIds[effectInfo.mKey.mArg], "") + ")"; ESM::MagicEffect::MagnitudeDisplayType displayType = effect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) { std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); std::stringstream formatter; formatter << std::fixed << std::setprecision(1) << " " << (effectInfo.mMagnitude / 10.0f) << timesInt; sourcesDescription += formatter.str(); } else if ( displayType != ESM::MagicEffect::MDT_None ) { sourcesDescription += ": " + MyGUI::utility::toString(effectInfo.mMagnitude); if ( displayType == ESM::MagicEffect::MDT_Percentage ) sourcesDescription += MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); else if ( displayType == ESM::MagicEffect::MDT_Feet ) sourcesDescription += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", ""); else if ( displayType == ESM::MagicEffect::MDT_Level ) { sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", "") : MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", "") ); } else // ESM::MagicEffect::MDT_Points { sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", "") : MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); } } if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game")) sourcesDescription += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}"); addNewLine = true; } if (remainingDuration > 0.f) { MyGUI::ImageBox* image; if (mWidgetMap.find(effectId) == mWidgetMap.end()) { image = parent->createWidget ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); mWidgetMap[effectId] = image; image->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); std::string name = ESM::MagicEffect::effectIdToString (effectId); ToolTipInfo tooltipInfo; tooltipInfo.caption = "#{" + name + "}"; tooltipInfo.icon = effect->mIcon; tooltipInfo.imageSize = 16; tooltipInfo.wordWrap = false; image->setUserData(tooltipInfo); image->setUserString("ToolTipType", "ToolTipInfo"); } else image = mWidgetMap[effectId]; image->setPosition(w,2); image->setVisible(true); w += 16; ToolTipInfo* tooltipInfo = image->getUserData(); tooltipInfo->text = sourcesDescription; // Fade out if (totalDuration >= fadeTime && fadeTime > 0.f) image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); } else if (mWidgetMap.find(effectId) != mWidgetMap.end()) { MyGUI::ImageBox* image = mWidgetMap[effectId]; image->setVisible(false); image->setAlpha(1.f); } } if (adjustSize) { int s = w + 2; if (effects.empty()) s = 0; int diff = parent->getWidth() - s; parent->setSize(s, parent->getHeight()); parent->setPosition(parent->getLeft()+diff, parent->getTop()); } // hide inactive effects for (auto& widgetPair : mWidgetMap) { if (effects.find(widgetPair.first) == effects.end()) widgetPair.second->setVisible(false); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellicons.hpp000066400000000000000000000017701445372753700227250ustar00rootroot00000000000000#ifndef MWGUI_SPELLICONS_H #define MWGUI_SPELLICONS_H #include #include #include "../mwmechanics/magiceffects.hpp" namespace MyGUI { class Widget; class ImageBox; } namespace ESM { struct ENAMstruct; struct EffectList; } namespace MWGui { // information about a single magic effect source as required for display in the tooltip struct MagicEffectInfo { MagicEffectInfo() : mMagnitude(0) , mRemainingTime(0.f) , mTotalTime(0.f) , mPermanent(false) {} std::string mSource; // display name for effect source (e.g. potion name) MWMechanics::EffectKey mKey; int mMagnitude; float mRemainingTime; float mTotalTime; bool mPermanent; // the effect is permanent }; class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: std::map mWidgetMap; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellmodel.cpp000066400000000000000000000164061445372753700227070ustar00rootroot00000000000000#include "spellmodel.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" namespace { bool sortSpells(const MWGui::Spell& left, const MWGui::Spell& right) { if (left.mType != right.mType) return left.mType < right.mType; std::string leftName = Misc::StringUtils::lowerCase(left.mName); std::string rightName = Misc::StringUtils::lowerCase(right.mName); return leftName.compare(rightName) < 0; } } namespace MWGui { SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter) : mActor(actor), mFilter(filter) { } SpellModel::SpellModel(const MWWorld::Ptr &actor) : mActor(actor) { } bool SpellModel::matchingEffectExists(std::string filter, const ESM::EffectList &effects) { auto wm = MWBase::Environment::get().getWindowManager(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (const auto& effect : effects.mList) { short effectId = effect.mEffectID; if (effectId != -1) { const ESM::MagicEffect *magicEffect = store.get().find(effectId); std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1) { fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], ""); } if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1) { fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], ""); } std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); if (convert.find(filter) != std::string::npos) { return true; } } } return false; } void SpellModel::update() { mSpells.clear(); MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor); const MWMechanics::Spells& spells = stats.getSpells(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); std::string filter = Utf8Stream::lowerCaseUtf8(mFilter); for (const ESM::Spell* spell : spells) { if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; std::string name = Utf8Stream::lowerCaseUtf8(spell->mName); if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, spell->mEffects)) continue; Spell newSpell; newSpell.mName = spell->mName; if (spell->mData.mType == ESM::Spell::ST_Spell) { newSpell.mType = Spell::Type_Spell; std::string cost = std::to_string(MWMechanics::calcSpellCost(*spell)); std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); newSpell.mCostColumn = cost + "/" + chance; } else newSpell.mType = Spell::Type_Power; newSpell.mId = spell->mId; newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == spell->mId); newSpell.mActive = true; newSpell.mCount = 1; mSpells.push_back(newSpell); } MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { MWWorld::Ptr item = *it; const std::string enchantId = item.getClass().getEnchantment(item); if (enchantId.empty()) continue; const ESM::Enchantment* enchant = esmStore.get().search(enchantId); if (!enchant) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId(); continue; } if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) continue; std::string name = Utf8Stream::lowerCaseUtf8(item.getClass().getName(item)); if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, enchant->mEffects)) continue; Spell newSpell; newSpell.mItem = item; newSpell.mId = item.getCellRef().getRefId(); newSpell.mName = item.getClass().getName(item); newSpell.mCount = item.getRefData().getCount(); newSpell.mType = Spell::Type_EnchantedItem; newSpell.mSelected = invStore.getSelectedEnchantItem() == it; // FIXME: move to mwmechanics if (enchant->mData.mType == ESM::Enchantment::CastOnce) { newSpell.mCostColumn = "100/100"; newSpell.mActive = false; } else { if (!item.getClass().getEquipmentSlots(item).first.empty() && item.getClass().canBeEquipped(item, mActor).first == 0) continue; int castCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast(enchant->mData.mCost), mActor); std::string cost = std::to_string(castCost); int currentCharge = int(item.getCellRef().getEnchantmentCharge()); if (currentCharge == -1) currentCharge = enchant->mData.mCharge; std::string charge = std::to_string(currentCharge); newSpell.mCostColumn = cost + "/" + charge; newSpell.mActive = invStore.isEquipped(item); } mSpells.push_back(newSpell); } std::stable_sort(mSpells.begin(), mSpells.end(), sortSpells); } size_t SpellModel::getItemCount() const { return mSpells.size(); } SpellModel::ModelIndex SpellModel::getSelectedIndex() const { ModelIndex selected = -1; for (SpellModel::ModelIndex i = 0; i= int(mSpells.size())) throw std::runtime_error("invalid spell index supplied"); return mSpells[index]; } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellmodel.hpp000066400000000000000000000031431445372753700227060ustar00rootroot00000000000000#ifndef OPENMW_GUI_SPELLMODEL_H #define OPENMW_GUI_SPELLMODEL_H #include "../mwworld/ptr.hpp" #include namespace MWGui { struct Spell { enum Type { Type_Power, Type_Spell, Type_EnchantedItem }; Type mType; std::string mName; std::string mCostColumn; // Cost/chance or Cost/charge std::string mId; // Item ID or spell ID MWWorld::Ptr mItem; // Only for Type_EnchantedItem int mCount; // Only for Type_EnchantedItem bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time) bool mActive; // (Items only) is the item equipped? Spell() : mType(Type_Spell) , mCount(0) , mSelected(false) , mActive(false) { } }; ///@brief Model that lists all usable powers, spells and enchanted items for an actor. class SpellModel { public: SpellModel(const MWWorld::Ptr& actor, const std::string& filter); SpellModel(const MWWorld::Ptr& actor); typedef int ModelIndex; void update(); Spell getItem (ModelIndex index) const; ///< throws for invalid index size_t getItemCount() const; ModelIndex getSelectedIndex() const; ///< returns -1 if nothing is selected private: MWWorld::Ptr mActor; std::vector mSpells; std::string mFilter; bool matchingEffectExists(std::string filter, const ESM::EffectList &effects); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellview.cpp000066400000000000000000000256061445372753700225630ustar00rootroot00000000000000#include "spellview.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include #include #include "tooltips.hpp" namespace MWGui { const char* SpellView::sSpellModelIndex = "SpellModelIndex"; SpellView::LineInfo::LineInfo(MyGUI::Widget* leftWidget, MyGUI::Widget* rightWidget, SpellModel::ModelIndex spellIndex) : mLeftWidget(leftWidget) , mRightWidget(rightWidget) , mSpellIndex(spellIndex) { } SpellView::SpellView() : mScrollView(nullptr) , mShowCostColumn(true) , mHighlightSelected(true) { } void SpellView::initialiseOverride() { Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); if (mScrollView == nullptr) throw std::runtime_error("Item view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); } void SpellView::registerComponents() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } void SpellView::setModel(SpellModel *model) { mModel.reset(model); update(); } SpellModel* SpellView::getModel() { return mModel.get(); } void SpellView::setShowCostColumn(bool show) { if (show != mShowCostColumn) { mShowCostColumn = show; update(); } } void SpellView::setHighlightSelected(bool highlight) { if (highlight != mHighlightSelected) { mHighlightSelected = highlight; update(); } } void SpellView::update() { if (!mModel.get()) return; mModel->update(); int curType = -1; const int spellHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; mLines.clear(); while (mScrollView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); for (SpellModel::ModelIndex i = 0; igetItemCount()); ++i) { const Spell& spell = mModel->getItem(i); if (curType != spell.mType) { if (spell.mType == Spell::Type_Power) addGroup("#{sPowers}", ""); else if (spell.mType == Spell::Type_Spell) addGroup("#{sSpells}", mShowCostColumn ? "#{sCostChance}" : ""); else addGroup("#{sMagicItem}", mShowCostColumn ? "#{sCostCharge}" : ""); curType = spell.mType; } const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped"; const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount); Gui::SharedStateButton* t = mScrollView->createWidget(skin, MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setNeedKeyFocus(true); t->setCaption(spell.mName + captionSuffix); t->setTextAlign(MyGUI::Align::Left); adjustSpellWidget(spell, i, t); if (!spell.mCostColumn.empty() && mShowCostColumn) { Gui::SharedStateButton* costChance = mScrollView->createWidget(skin, MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); costChance->setCaption(spell.mCostColumn); costChance->setTextAlign(MyGUI::Align::Right); adjustSpellWidget(spell, i, costChance); Gui::ButtonGroup group; group.push_back(t); group.push_back(costChance); Gui::SharedStateButton::createButtonGroup(group); mLines.emplace_back(t, costChance, i); } else mLines.emplace_back(t, (MyGUI::Widget*)nullptr, i); t->setStateSelected(spell.mSelected); } layoutWidgets(); } void SpellView::incrementalUpdate() { if (!mModel.get()) { return; } mModel->update(); bool fullUpdateRequired = false; SpellModel::ModelIndex maxSpellIndexFound = -1; for (LineInfo& line : mLines) { // only update the lines that are "updateable" SpellModel::ModelIndex spellIndex(line.mSpellIndex); if (spellIndex != NoSpellIndex) { Gui::SharedStateButton* nameButton = reinterpret_cast(line.mLeftWidget); // match model against line // if don't match, then major change has happened, so do a full update if (mModel->getItemCount() <= static_cast(spellIndex)) { fullUpdateRequired = true; break; } // more checking for major change. const Spell& spell = mModel->getItem(spellIndex); const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount); if (nameButton->getCaption() != (spell.mName + captionSuffix)) { fullUpdateRequired = true; break; } else { maxSpellIndexFound = spellIndex; Gui::SharedStateButton* costButton = reinterpret_cast(line.mRightWidget); if ((costButton != nullptr) && (costButton->getCaption() != spell.mCostColumn)) { costButton->setCaption(spell.mCostColumn); } } } } // special case, look for spells added to model that are beyond last updatable item SpellModel::ModelIndex topSpellIndex = mModel->getItemCount() - 1; if (fullUpdateRequired || ((0 <= topSpellIndex) && (maxSpellIndexFound < topSpellIndex))) { update(); } } void SpellView::layoutWidgets() { int height = 0; for (LineInfo& line : mLines) { height += line.mLeftWidget->getHeight(); } bool scrollVisible = height > mScrollView->getHeight(); int width = mScrollView->getWidth() - (scrollVisible ? 18 : 0); height = 0; for (LineInfo& line : mLines) { int lineHeight = line.mLeftWidget->getHeight(); line.mLeftWidget->setCoord(4, height, width - 8, lineHeight); if (line.mRightWidget) { line.mRightWidget->setCoord(4, height, width - 8, lineHeight); MyGUI::TextBox* second = line.mRightWidget->castType(false); if (second) line.mLeftWidget->setSize(width - 8 - second->getTextSize().width, lineHeight); } height += lineHeight; } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), height)); mScrollView->setVisibleVScroll(true); } void SpellView::addGroup(const std::string &label, const std::string& label2) { if (mScrollView->getChildCount() > 0) { MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), MyGUI::Align::Left | MyGUI::Align::Top); separator->setNeedMouseFocus(false); mLines.emplace_back(separator, (MyGUI::Widget*)nullptr, NoSpellIndex); } MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget->setCaptionWithReplacing(label); groupWidget->setTextAlign(MyGUI::Align::Left); groupWidget->setNeedMouseFocus(false); if (label2 != "") { MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget2->setCaptionWithReplacing(label2); groupWidget2->setTextAlign(MyGUI::Align::Right); groupWidget2->setNeedMouseFocus(false); mLines.emplace_back(groupWidget, groupWidget2, NoSpellIndex); } else mLines.emplace_back(groupWidget, (MyGUI::Widget*)nullptr, NoSpellIndex); } void SpellView::setSize(const MyGUI::IntSize &_value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); if (changed) layoutWidgets(); } void SpellView::setCoord(const MyGUI::IntCoord &_value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); if (changed) layoutWidgets(); } void SpellView::adjustSpellWidget(const Spell &spell, SpellModel::ModelIndex index, MyGUI::Widget *widget) { if (spell.mType == Spell::Type_EnchantedItem) { widget->setUserData(MWWorld::Ptr(spell.mItem)); widget->setUserString("ToolTipType", "ItemPtr"); } else { widget->setUserString("ToolTipType", "Spell"); widget->setUserString("Spell", spell.mId); } widget->setUserString(sSpellModelIndex, MyGUI::utility::toString(index)); widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheelMoved); widget->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellView::onSpellSelected); } SpellModel::ModelIndex SpellView::getSpellModelIndex(MyGUI::Widget* widget) { return MyGUI::utility::parseInt(widget->getUserString(sSpellModelIndex)); } void SpellView::onSpellSelected(MyGUI::Widget* _sender) { eventSpellClicked(getSpellModelIndex(_sender)); } void SpellView::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) { if (mScrollView->getViewOffset().top + _rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + _rel*0.3f))); } void SpellView::resetScrollbars() { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellview.hpp000066400000000000000000000051121445372753700225560ustar00rootroot00000000000000#ifndef OPENMW_GUI_SPELLVIEW_H #define OPENMW_GUI_SPELLVIEW_H #include #include #include #include "spellmodel.hpp" namespace MyGUI { class ScrollView; } namespace MWGui { class SpellModel; ///@brief Displays a SpellModel in a list widget class SpellView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(SpellView) public: SpellView(); /// Register needed components with MyGUI's factory manager static void registerComponents (); /// Should the cost/chance column be shown? void setShowCostColumn(bool show); void setHighlightSelected(bool highlight); /// Takes ownership of \a model void setModel (SpellModel* model); SpellModel* getModel(); void update(); /// simplified update called each frame void incrementalUpdate(); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; /// Fired when a spell was clicked EventHandle_ModelIndex eventSpellClicked; void initialiseOverride() override; void setSize(const MyGUI::IntSize& _value) override; void setCoord(const MyGUI::IntCoord& _value) override; void resetScrollbars(); private: MyGUI::ScrollView* mScrollView; std::unique_ptr mModel; /// tracks a row in the spell view struct LineInfo { /// the widget on the left side of the row MyGUI::Widget* mLeftWidget; /// the widget on the left side of the row (if there is one) MyGUI::Widget* mRightWidget; /// index to item in mModel that row is showing information for SpellModel::ModelIndex mSpellIndex; LineInfo(MyGUI::Widget* leftWidget, MyGUI::Widget* rightWidget, SpellModel::ModelIndex spellIndex); }; /// magic number indicating LineInfo does not correspond to an item in mModel enum { NoSpellIndex = -1 }; std::vector< LineInfo > mLines; bool mShowCostColumn; bool mHighlightSelected; void layoutWidgets(); void addGroup(const std::string& label1, const std::string& label2); void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget); void onSpellSelected(MyGUI::Widget* _sender); void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); SpellModel::ModelIndex getSpellModelIndex(MyGUI::Widget* _sender); static const char* sSpellModelIndex; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/spellwindow.cpp000066400000000000000000000225631445372753700231170ustar00rootroot00000000000000#include "spellwindow.hpp" #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "spellicons.hpp" #include "confirmationdialog.hpp" #include "spellview.hpp" namespace MWGui { SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") , NoDrop(drag, mMainWidget) , mSpellView(nullptr) , mUpdateTimer(0.0f) { mSpellIcons = new SpellIcons(); MyGUI::Widget* deleteButton; getWidget(deleteButton, "DeleteSpellButton"); getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); getWidget(mFilterEdit, "FilterEdit"); mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged); deleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onDeleteClicked); setCoord(498, 300, 302, 300); // Adjust the spell filtering widget size because of MyGUI limitations. int filterWidth = mSpellView->getSize().width - deleteButton->getSize().width - 3; mFilterEdit->setSize(filterWidth, mFilterEdit->getSize().height); } SpellWindow::~SpellWindow() { delete mSpellIcons; } void SpellWindow::onPinToggled() { Settings::Manager::setBool("spells pin", "Windows", mPinned); MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned); } void SpellWindow::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) MWBase::Environment::get().getWindowManager()->toggleMaximized(this); else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); } void SpellWindow::onOpen() { // Reset the filter focus when opening the window MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (focus == mFilterEdit) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); updateSpells(); } void SpellWindow::onFrame(float dt) { NoDrop::onFrame(dt); mUpdateTimer += dt; if (0.5f < mUpdateTimer) { mUpdateTimer = 0; mSpellView->incrementalUpdate(); } // Update effects in-game too if the window is pinned if (mPinned && !MWBase::Environment::get().getWindowManager()->isGuiMode()) mSpellIcons->updateWidgets(mEffectBox, false); } void SpellWindow::updateSpells() { mSpellIcons->updateWidgets(mEffectBox, false); mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), mFilterEdit->getCaption())); } void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) { if (*it == item) { break; } } if (it == store.end()) throw std::runtime_error("can't find selected item"); // equip, if it can be equipped and is not already equipped if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) return; } store.setSelectedEnchantItem(it); // to reset WindowManager::mSelectedSpell immediately MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it); updateSpells(); } void SpellWindow::askDeleteSpell(const std::string &spellId) { // delete spell, if allowed const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); MWWorld::Ptr player = MWMechanics::getPlayer(); std::string raceId = player.get()->mBase->mRace; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceId); // can't delete racial spells, birthsign spells or powers bool isInherent = race->mPowers.exists(spell->mId) || spell->mData.mType == ESM::Spell::ST_Power; const std::string& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!isInherent && !signId.empty()) { const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(signId); isInherent = sign->mPowers.exists(spell->mId); } if (isInherent) { MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}"); } else { // ask for confirmation mSpellToDelete = spellId; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); question = Misc::StringUtils::format(question, spell->mName); dialog->askForConfirmation(question); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); dialog->eventCancelClicked.clear(); } } void SpellWindow::onModelIndexSelected(SpellModel::ModelIndex index) { const Spell& spell = mSpellView->getModel()->getItem(index); if (spell.mType == Spell::Type_EnchantedItem) { onEnchantedItemSelected(spell.mItem, spell.mActive); } else { if (MyGUI::InputManager::getInstance().isShiftPressed()) askDeleteSpell(spell.mId); else onSpellSelected(spell.mId); } } void SpellWindow::onFilterChanged(MyGUI::EditBox *sender) { mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption())); } void SpellWindow::onDeleteClicked(MyGUI::Widget *widget) { SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); if (selected < 0) return; const Spell& spell = mSpellView->getModel()->getItem(selected); if (spell.mType != Spell::Type_EnchantedItem) askDeleteSpell(spell.mId); } void SpellWindow::onSpellSelected(const std::string& spellId) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); updateSpells(); } void SpellWindow::onDeleteSpellAccept() { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); if (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == mSpellToDelete) MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); spells.remove(mSpellToDelete); updateSpells(); } void SpellWindow::cycle(bool next) { MWWorld::Ptr player = MWMechanics::getPlayer(); if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) return; bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) return; mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), "")); SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); if (selected < 0) selected = 0; selected += next ? 1 : -1; int itemcount = mSpellView->getModel()->getItemCount(); if (itemcount == 0) return; selected = (selected + itemcount) % itemcount; const Spell& spell = mSpellView->getModel()->getItem(selected); if (spell.mType == Spell::Type_EnchantedItem) onEnchantedItemSelected(spell.mItem, spell.mActive); else onSpellSelected(spell.mId); } } openmw-openmw-0.48.0/apps/openmw/mwgui/spellwindow.hpp000066400000000000000000000023421445372753700231150ustar00rootroot00000000000000#ifndef MWGUI_SPELLWINDOW_H #define MWGUI_SPELLWINDOW_H #include "windowpinnablebase.hpp" #include "spellmodel.hpp" namespace MWGui { class SpellIcons; class SpellView; class SpellWindow : public WindowPinnableBase, public NoDrop { public: SpellWindow(DragAndDrop* drag); virtual ~SpellWindow(); void updateSpells(); void onFrame(float dt) override; /// Cycle to next/previous spell void cycle(bool next); protected: MyGUI::Widget* mEffectBox; std::string mSpellToDelete; void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); void onSpellSelected(const std::string& spellId); void onModelIndexSelected(SpellModel::ModelIndex index); void onFilterChanged(MyGUI::EditBox *sender); void onDeleteClicked(MyGUI::Widget *widget); void onDeleteSpellAccept(); void askDeleteSpell(const std::string& spellId); void onPinToggled() override; void onTitleDoubleClicked() override; void onOpen() override; SpellView* mSpellView; SpellIcons* mSpellIcons; MyGUI::EditBox* mFilterEdit; private: float mUpdateTimer; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/statswatcher.cpp000066400000000000000000000144341445372753700232620ustar00rootroot00000000000000#include "statswatcher.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include namespace MWGui { // mWatchedTimeToStartDrowning = -1 for correct drowning state check, // if stats.getTimeToStartDrowning() == 0 already on game start StatsWatcher::StatsWatcher() : mWatchedLevel(-1), mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty(true) { } void StatsWatcher::watchActor(const MWWorld::Ptr& ptr) { mWatched = ptr; } void StatsWatcher::update() { if (mWatched.isEmpty()) return; MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched); for (int i = 0;i < ESM::Attribute::Length;++i) { if (stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty) { mWatchedAttributes[i] = stats.getAttribute(i); setValue("AttribVal" + std::to_string(i + 1), stats.getAttribute(i)); } } if (stats.getHealth() != mWatchedHealth || mWatchedStatsEmpty) { static const std::string hbar("HBar"); mWatchedHealth = stats.getHealth(); setValue(hbar, stats.getHealth()); } if (stats.getMagicka() != mWatchedMagicka || mWatchedStatsEmpty) { static const std::string mbar("MBar"); mWatchedMagicka = stats.getMagicka(); setValue(mbar, stats.getMagicka()); } if (stats.getFatigue() != mWatchedFatigue || mWatchedStatsEmpty) { static const std::string fbar("FBar"); mWatchedFatigue = stats.getFatigue(); setValue(fbar, stats.getFatigue()); } float timeToDrown = stats.getTimeToStartDrowning(); if (timeToDrown != mWatchedTimeToStartDrowning) { static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get() .find("fHoldBreathTime")->mValue.getFloat(); mWatchedTimeToStartDrowning = timeToDrown; if(timeToDrown >= fHoldBreathTime || timeToDrown == -1.0) // -1.0 is a special value during initialization winMgr->setDrowningBarVisibility(false); else { winMgr->setDrowningBarVisibility(true); winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning(), fHoldBreathTime); } } //Loop over ESM::Skill::SkillEnum for (int i = 0; i < ESM::Skill::Length; ++i) { if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty) { mWatchedSkills[i] = stats.getSkill(i); setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); } } if (stats.getLevel() != mWatchedLevel || mWatchedStatsEmpty) { mWatchedLevel = stats.getLevel(); setValue("level", mWatchedLevel); } if (mWatched.getClass().isNpc()) { const ESM::NPC *watchedRecord = mWatched.get()->mBase; if (watchedRecord->mName != mWatchedName || mWatchedStatsEmpty) { mWatchedName = watchedRecord->mName; setValue("name", watchedRecord->mName); } if (watchedRecord->mRace != mWatchedRace || mWatchedStatsEmpty) { mWatchedRace = watchedRecord->mRace; const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore() .get().find(watchedRecord->mRace); setValue("race", race->mName); } if (watchedRecord->mClass != mWatchedClass || mWatchedStatsEmpty) { mWatchedClass = watchedRecord->mClass; const ESM::Class *cls = MWBase::Environment::get().getWorld()->getStore() .get().find(watchedRecord->mClass); setValue("class", cls->mName); MWBase::WindowManager::SkillList majorSkills (5); MWBase::WindowManager::SkillList minorSkills (5); for (int i=0; i<5; ++i) { minorSkills[i] = cls->mData.mSkills[i][0]; majorSkills[i] = cls->mData.mSkills[i][1]; } configureSkills(majorSkills, minorSkills); } } mWatchedStatsEmpty = false; } void StatsWatcher::addListener(StatsListener* listener) { mListeners.insert(listener); } void StatsWatcher::removeListener(StatsListener* listener) { mListeners.erase(listener); } void StatsWatcher::setValue(const std::string& id, const MWMechanics::AttributeValue& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } void StatsWatcher::setValue(ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we /// allow custom skills. for (StatsListener* listener : mListeners) listener->setValue(parSkill, value); } void StatsWatcher::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } void StatsWatcher::setValue(const std::string& id, const std::string& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } void StatsWatcher::setValue(const std::string& id, int value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } void StatsWatcher::configureSkills(const std::vector& major, const std::vector& minor) { for (StatsListener* listener : mListeners) listener->configureSkills(major, minor); } } openmw-openmw-0.48.0/apps/openmw/mwgui/statswatcher.hpp000066400000000000000000000045161445372753700232670ustar00rootroot00000000000000#ifndef MWGUI_STATSWATCHER_H #define MWGUI_STATSWATCHER_H #include #include #include #include "../mwmechanics/stat.hpp" #include "../mwworld/ptr.hpp" namespace MWGui { class StatsListener { public: /// Set value for the given ID. virtual void setValue(const std::string& id, const MWMechanics::AttributeValue& value) {} virtual void setValue(const std::string& id, const MWMechanics::DynamicStat& value) {} virtual void setValue(const std::string& id, const std::string& value) {} virtual void setValue(const std::string& id, int value) {} virtual void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) {} virtual void configureSkills(const std::vector& major, const std::vector& minor) {} }; class StatsWatcher { MWWorld::Ptr mWatched; MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length]; MWMechanics::SkillValue mWatchedSkills[ESM::Skill::Length]; MWMechanics::DynamicStat mWatchedHealth; MWMechanics::DynamicStat mWatchedMagicka; MWMechanics::DynamicStat mWatchedFatigue; std::string mWatchedName; std::string mWatchedRace; std::string mWatchedClass; int mWatchedLevel; float mWatchedTimeToStartDrowning; bool mWatchedStatsEmpty; std::set mListeners; void setValue(const std::string& id, const MWMechanics::AttributeValue& value); void setValue(const std::string& id, const MWMechanics::DynamicStat& value); void setValue(const std::string& id, const std::string& value); void setValue(const std::string& id, int value); void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills(const std::vector& major, const std::vector& minor); public: StatsWatcher(); void update(); void addListener(StatsListener* listener); void removeListener(StatsListener* listener); void watchActor(const MWWorld::Ptr& ptr); MWWorld::Ptr getWatchedActor() const { return mWatched; } void forceUpdate() { mWatchedStatsEmpty = true; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/statswindow.cpp000066400000000000000000000722221445372753700231330ustar00rootroot00000000000000#include "statswindow.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" namespace MWGui { StatsWindow::StatsWindow (DragAndDrop* drag) : WindowPinnableBase("openmw_stats_window.layout") , NoDrop(drag, mMainWidget) , mSkillView(nullptr) , mMajorSkills() , mMinorSkills() , mMiscSkills() , mSkillValues() , mSkillWidgetMap() , mFactionWidgetMap() , mFactions() , mBirthSignId() , mReputation(0) , mBounty(0) , mSkillWidgets() , mChanged(true) , mMinFullWidth(mMainWidget->getSize().width) { const char *names[][2] = { { "Attrib1", "sAttributeStrength" }, { "Attrib2", "sAttributeIntelligence" }, { "Attrib3", "sAttributeWillpower" }, { "Attrib4", "sAttributeAgility" }, { "Attrib5", "sAttributeSpeed" }, { "Attrib6", "sAttributeEndurance" }, { "Attrib7", "sAttributePersonality" }, { "Attrib8", "sAttributeLuck" }, { 0, 0 } }; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (int i=0; names[i][0]; ++i) { setText (names[i][0], store.get().find (names[i][1])->mValue.getString()); } getWidget(mSkillView, "SkillView"); getWidget(mLeftPane, "LeftPane"); getWidget(mRightPane, "RightPane"); for (int i = 0; i < ESM::Skill::Length; ++i) { mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::make_pair(i, std::make_pair((MyGUI::TextBox*)nullptr, (MyGUI::TextBox*)nullptr))); } MyGUI::Window* t = mMainWidget->castType(); t->eventWindowChangeCoord += MyGUI::newDelegate(this, &StatsWindow::onWindowResize); onWindowResize(t); } void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mSkillView->getViewOffset().top + _rel*0.3 > 0) mSkillView->setViewOffset(MyGUI::IntPoint(0, 0)); else mSkillView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel*0.3))); } void StatsWindow::onWindowResize(MyGUI::Window* window) { int windowWidth = window->getSize().width; int windowHeight = window->getSize().height; //initial values defined in openmw_stats_window.layout, if custom options are not present in .layout, a default is loaded float leftPaneRatio = 0.44f; if (mLeftPane->isUserString("LeftPaneRatio")) leftPaneRatio = MyGUI::utility::parseFloat(mLeftPane->getUserString("LeftPaneRatio")); int leftOffsetWidth = 24; if (mLeftPane->isUserString("LeftOffsetWidth")) leftOffsetWidth = MyGUI::utility::parseInt(mLeftPane->getUserString("LeftOffsetWidth")); float rightPaneRatio = 1.f - leftPaneRatio; int minLeftWidth = static_cast(mMinFullWidth * leftPaneRatio); int minLeftOffsetWidth = minLeftWidth + leftOffsetWidth; //if there's no space for right pane mRightPane->setVisible(windowWidth >= minLeftOffsetWidth); if (!mRightPane->getVisible()) { mLeftPane->setCoord(MyGUI::IntCoord(0, 0, windowWidth - leftOffsetWidth, windowHeight)); } //if there's some space for right pane else if (windowWidth < mMinFullWidth) { mLeftPane->setCoord(MyGUI::IntCoord(0, 0, minLeftWidth, windowHeight)); mRightPane->setCoord(MyGUI::IntCoord(minLeftWidth, 0, windowWidth - minLeftWidth, windowHeight)); } //if there's enough space for both panes else { mLeftPane->setCoord(MyGUI::IntCoord(0, 0, static_cast(leftPaneRatio*windowWidth), windowHeight)); mRightPane->setCoord(MyGUI::IntCoord(static_cast(leftPaneRatio*windowWidth), 0, static_cast(rightPaneRatio*windowWidth), windowHeight)); } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height); mSkillView->setVisibleVScroll(true); } void StatsWindow::setBar(const std::string& name, const std::string& tname, int val, int max) { MyGUI::ProgressBar* pt; getWidget(pt, name); std::stringstream out; out << val << "/" << max; setText(tname, out.str()); pt->setProgressRange(std::max(0, max)); pt->setProgressPosition(std::max(0, val)); } void StatsWindow::setPlayerName(const std::string& playerName) { mMainWidget->castType()->setCaption(playerName); } void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { static const char *ids[] = { "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", "AttribVal6", "AttribVal7", "AttribVal8", 0 }; for (int i=0; ids[i]; ++i) if (ids[i]==id) { setText (id, std::to_string(static_cast(value.getModified()))); MyGUI::TextBox* box; getWidget(box, id); if (value.getModified()>value.getBase()) box->_setWidgetState("increased"); else if (value.getModified()_setWidgetState("decreased"); else box->_setWidgetState("normal"); break; } } void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified(false)); // Fatigue can be negative if (id != "FBar") current = std::max(0, current); setBar (id, id + "T", current, modified); // health, magicka, fatigue tooltip MyGUI::Widget* w; std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); if (id == "HBar") { getWidget(w, "Health"); w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } else if (id == "MBar") { getWidget(w, "Magicka"); w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { getWidget(w, "Fatigue"); w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } } void StatsWindow::setValue (const std::string& id, const std::string& value) { if (id=="name") setPlayerName (value); else if (id=="race") setText ("RaceText", value); else if (id=="class") setText ("ClassText", value); } void StatsWindow::setValue (const std::string& id, int value) { if (id=="level") { std::ostringstream text; text << value; setText("LevelText", text.str()); } } void setSkillProgress(MyGUI::Widget* w, float progress, int skillId) { MWWorld::Ptr player = MWMechanics::getPlayer(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement(skillId, *esmStore.get().find(player.get()->mBase->mClass)); // This is how vanilla MW displays the progress bar (I think). Note it's slightly inaccurate, // due to the int casting in the skill levelup logic. Also the progress label could in rare cases // reach 100% without the skill levelling up. // Leaving the original display logic for now, for consistency with ess-imported savegames. int progressPercent = int(float(progress) / float(progressRequirement) * 100.f + 0.5f); w->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent)+"/100"); w->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); } void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { mSkillValues[parSkill] = value; std::pair widgets = mSkillWidgetMap[(int)parSkill]; MyGUI::TextBox* valueWidget = widgets.second; MyGUI::TextBox* nameWidget = widgets.first; if (valueWidget && nameWidget) { int modified = value.getModified(), base = value.getBase(); std::string text = MyGUI::utility::toString(modified); std::string state = "normal"; if (modified > base) state = "increased"; else if (modified < base) state = "decreased"; int widthBefore = valueWidget->getTextSize().width; valueWidget->setCaption(text); valueWidget->_setWidgetState(state); int widthAfter = valueWidget->getTextSize().width; if (widthBefore != widthAfter) { valueWidget->setCoord(valueWidget->getLeft() - (widthAfter-widthBefore), valueWidget->getTop(), valueWidget->getWidth() + (widthAfter-widthBefore), valueWidget->getHeight()); nameWidget->setSize(nameWidget->getWidth() - (widthAfter-widthBefore), nameWidget->getHeight()); } if (value.getBase() < 100) { nameWidget->setUserString("Visible_SkillMaxed", "false"); nameWidget->setUserString("UserData^Hidden_SkillMaxed", "true"); nameWidget->setUserString("Visible_SkillProgressVBox", "true"); nameWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false"); valueWidget->setUserString("Visible_SkillMaxed", "false"); valueWidget->setUserString("UserData^Hidden_SkillMaxed", "true"); valueWidget->setUserString("Visible_SkillProgressVBox", "true"); valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false"); setSkillProgress(nameWidget, value.getProgress(), parSkill); setSkillProgress(valueWidget, value.getProgress(), parSkill); } else { nameWidget->setUserString("Visible_SkillMaxed", "true"); nameWidget->setUserString("UserData^Hidden_SkillMaxed", "false"); nameWidget->setUserString("Visible_SkillProgressVBox", "false"); nameWidget->setUserString("UserData^Hidden_SkillProgressVBox", "true"); valueWidget->setUserString("Visible_SkillMaxed", "true"); valueWidget->setUserString("UserData^Hidden_SkillMaxed", "false"); valueWidget->setUserString("Visible_SkillProgressVBox", "false"); valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "true"); } } } void StatsWindow::configureSkills (const std::vector& major, const std::vector& minor) { mMajorSkills = major; mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); mMiscSkills.clear(); for (const int skill : ESM::Skill::sSkillIds) { if (skillSet.find(skill) == skillSet.end()) mMiscSkills.push_back(skill); } updateSkillArea(); } void StatsWindow::onFrame (float dt) { NoDrop::onFrame(dt); MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); std::string detailText; std::stringstream detail; for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) { float mult = PCstats.getLevelupAttributeMultiplier(attribute); mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase()); if (mult > 1) detail << (detail.str().empty() ? "" : "\n") << "#{" << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute]) << "} x" << MyGUI::utility::toString(mult); } detailText = MyGUI::LanguageManager::getInstance().replaceTags(detail.str()); // level progress MyGUI::Widget* levelWidget; for (int i=0; i<2; ++i) { int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelDetailText", detailText); } setFactions(PCstats.getFactionRanks()); setExpelled(PCstats.getExpelled ()); const std::string &signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); setBirthSign(signId); setReputation (PCstats.getReputation ()); setBounty (PCstats.getBounty ()); if (mChanged) updateSkillArea(); } void StatsWindow::setFactions (const FactionList& factions) { if (mFactions != factions) { mFactions = factions; mChanged = true; } } void StatsWindow::setExpelled (const std::set& expelled) { if (mExpelled != expelled) { mExpelled = expelled; mChanged = true; } } void StatsWindow::setBirthSign (const std::string& signId) { if (signId != mBirthSignId) { mBirthSignId = signId; mChanged = true; } } void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::ImageBox* separator = mSkillView->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); separator->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); mSkillWidgets.push_back(separator); coord1.top += separator->getHeight(); coord2.top += separator->getHeight(); } void StatsWindow::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); groupWidget->setCaption(label); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); mSkillWidgets.push_back(groupWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; } std::pair StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox *skillNameWidget, *skillValueWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillValueWidget = mSkillView->createWidget("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); skillValueWidget->setCaption(value); skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); // resize dynamically according to text size int textWidthPlusMargin = skillValueWidget->getTextSize().width + 12; skillValueWidget->setCoord(coord2.left + coord2.width - textWidthPlusMargin, coord2.top, textWidthPlusMargin, coord2.height); skillNameWidget->setSize(skillNameWidget->getSize() + MyGUI::IntSize(coord2.width - textWidthPlusMargin, 0)); mSkillWidgets.push_back(skillNameWidget); mSkillWidgets.push_back(skillValueWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; return std::make_pair(skillNameWidget, skillValueWidget); } MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); int textWidth = skillNameWidget->getTextSize().width; skillNameWidget->setSize(textWidth, skillNameWidget->getHeight()); mSkillWidgets.push_back(skillNameWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; coord1.top += lineHeight; coord2.top += lineHeight; return skillNameWidget; } void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) { addSeparator(coord1, coord2); } addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); for (const int skillId : skills) { if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Skill* skill = esmStore.get().find(skillId); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; const ESM::Attribute* attr = esmStore.get().find(skill->mData.mAttribute); std::pair widgets = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), "", "normal", coord1, coord2); mSkillWidgetMap[skillId] = widgets; for (int i=0; i<2; ++i) { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->mDescription); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); } setValue(static_cast(skillId), mSkillValues.find(skillId)->second); } } void StatsWindow::updateSkillArea() { mChanged = false; for (MyGUI::Widget* widget : mSkillWidgets) { MyGUI::Gui::getInstance().destroyWidget(widget); } mSkillWidgets.clear(); const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); if (!mMajorSkills.empty()) addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); if (!mMinorSkills.empty()) addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); const ESM::NPC *player = world->getPlayerPtr().get()->mBase; // race tooltip const ESM::Race* playerRace = store.get().find(player->mRace); MyGUI::Widget* raceWidget; getWidget(raceWidget, "RaceText"); ToolTips::createRaceToolTip(raceWidget, playerRace); getWidget(raceWidget, "Race_str"); ToolTips::createRaceToolTip(raceWidget, playerRace); // class tooltip MyGUI::Widget* classWidget; const ESM::Class *playerClass = store.get().find(player->mClass); getWidget(classWidget, "ClassText"); ToolTips::createClassToolTip(classWidget, *playerClass); getWidget(classWidget, "Class_str"); ToolTips::createClassToolTip(classWidget, *playerClass); if (!mFactions.empty()) { MWWorld::Ptr playerPtr = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = playerPtr.getClass().getNpcStats(playerPtr); const std::set &expelled = PCstats.getExpelled(); bool firstFaction=true; for (auto& factionPair : mFactions) { const std::string& factionId = factionPair.first; const ESM::Faction *faction = store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; if (firstFaction) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), coord1, coord2); firstFaction = false; } MyGUI::Widget* w = addItem(faction->mName, coord1, coord2); std::string text; text += std::string("#{fontcolourhtml=header}") + faction->mName; if (expelled.find(factionId) != expelled.end()) text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { const int rank = std::clamp(factionPair.second, 0, 9); text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; if (rank < 9) { // player doesn't have max rank yet text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank+1]; ESM::RankData rankData = faction->mData.mRankData[rank+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); text += "\n#{fontcolourhtml=normal}#{" + attr1->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute1) + ", #{" + attr2->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute2); text += "\n\n#{fontcolourhtml=header}#{sFavoriteSkills}"; text += "\n#{fontcolourhtml=normal}"; bool firstSkill = true; for (int i=0; i<7; ++i) { if (faction->mData.mSkills[i] != -1) { if (!firstSkill) text += ", "; firstSkill = false; text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; } } text += "\n"; if (rankData.mPrimarySkill > 0) text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mPrimarySkill); if (rankData.mFavouredSkill > 0) text += " #{sand} #{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mFavouredSkill); } } w->setUserString("ToolTipType", "Layout"); w->setUserString("ToolTipLayout", "FactionToolTip"); w->setUserString("Caption_FactionText", text); } } if (!mBirthSignId.empty()) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBirthSign", "Sign"), coord1, coord2); const ESM::BirthSign *sign = store.get().find(mBirthSignId); MyGUI::Widget* w = addItem(sign->mName, coord1, coord2); ToolTips::createBirthsignToolTip(w, mBirthSignId); } // Add a line separator if there are items above if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sReputation", "Reputation"), MyGUI::utility::toString(static_cast(mReputation)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); } addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBounty", "Bounty"), MyGUI::utility::toString(static_cast(mBounty)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); mSkillView->setVisibleVScroll(true); } void StatsWindow::onPinToggled() { Settings::Manager::setBool("stats pin", "Windows", mPinned); MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned); } void StatsWindow::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) { MWBase::Environment::get().getWindowManager()->toggleMaximized(this); MyGUI::Window* t = mMainWidget->castType(); onWindowResize(t); } else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); } } openmw-openmw-0.48.0/apps/openmw/mwgui/statswindow.hpp000066400000000000000000000067721445372753700231470ustar00rootroot00000000000000#ifndef MWGUI_STATS_WINDOW_H #define MWGUI_STATS_WINDOW_H #include "statswatcher.hpp" #include "windowpinnablebase.hpp" namespace MWGui { class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener { public: typedef std::map FactionList; typedef std::vector SkillList; StatsWindow(DragAndDrop* drag); /// automatically updates all the data in the stats window, but only if it has changed. void onFrame(float dt) override; void setBar(const std::string& name, const std::string& tname, int val, int max); void setPlayerName(const std::string& playerName); /// Set value for the given ID. void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override; void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; void setValue (const std::string& id, const std::string& value) override; void setValue (const std::string& id, int value) override; void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; void configureSkills(const SkillList& major, const SkillList& minor) override; void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; } void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); void onOpen() override { onWindowResize(mMainWidget->castType()); } private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); std::pair addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void setFactions (const FactionList& factions); void setExpelled (const std::set& expelled); void setBirthSign (const std::string &signId); void onWindowResize(MyGUI::Window* window); void onMouseWheel(MyGUI::Widget* _sender, int _rel); MyGUI::Widget* mLeftPane; MyGUI::Widget* mRightPane; MyGUI::ScrollView* mSkillView; SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::map mSkillValues; std::map > mSkillWidgetMap; std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank std::string mBirthSignId; int mReputation, mBounty; std::vector mSkillWidgets; //< Skills and other information std::set mExpelled; bool mChanged; const int mMinFullWidth; protected: void onPinToggled() override; void onTitleDoubleClicked() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/textcolours.cpp000066400000000000000000000021511445372753700231320ustar00rootroot00000000000000#include "textcolours.hpp" #include #include namespace MWGui { MyGUI::Colour getTextColour(const std::string& type) { return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); } void TextColours::loadColours() { header = getTextColour("header"); normal = getTextColour("normal"); notify = getTextColour("notify"); link = getTextColour("link"); linkOver = getTextColour("link_over"); linkPressed = getTextColour("link_pressed"); answer = getTextColour("answer"); answerOver = getTextColour("answer_over"); answerPressed = getTextColour("answer_pressed"); journalLink = getTextColour("journal_link"); journalLinkOver = getTextColour("journal_link_over"); journalLinkPressed = getTextColour("journal_link_pressed"); journalTopic = getTextColour("journal_topic"); journalTopicOver = getTextColour("journal_topic_over"); journalTopicPressed = getTextColour("journal_topic_pressed"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/textcolours.hpp000066400000000000000000000013111445372753700231340ustar00rootroot00000000000000#ifndef MWGUI_TEXTCOLORS_H #define MWGUI_TEXTCOLORS_H #include namespace MWGui { struct TextColours { MyGUI::Colour header; MyGUI::Colour normal; MyGUI::Colour notify; MyGUI::Colour link; MyGUI::Colour linkOver; MyGUI::Colour linkPressed; MyGUI::Colour answer; MyGUI::Colour answerOver; MyGUI::Colour answerPressed; MyGUI::Colour journalLink; MyGUI::Colour journalLinkOver; MyGUI::Colour journalLinkPressed; MyGUI::Colour journalTopic; MyGUI::Colour journalTopicOver; MyGUI::Colour journalTopicPressed; public: void loadColours(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/textinput.cpp000066400000000000000000000044741445372753700226150ustar00rootroot00000000000000#include "textinput.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include #include namespace MWGui { TextInputDialog::TextInputDialog() : WindowModal("openmw_text_input.layout") { // Centre dialog center(); getWidget(mTextEdit, "TextEdit"); mTextEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } void TextInputDialog::setNextButtonShow(bool shown) { MyGUI::Button* okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); else okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); } void TextInputDialog::setTextLabel(const std::string &label) { setText("LabelT", label); } void TextInputDialog::onOpen() { WindowModal::onOpen(); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } // widget controls void TextInputDialog::onOkClicked(MyGUI::Widget* _sender) { if (mTextEdit->getCaption() == "") { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}"); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit); } else eventDone(this); } void TextInputDialog::onTextAccepted(MyGUI::Edit* _sender) { onOkClicked(_sender); // To do not spam onTextAccepted() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } std::string TextInputDialog::getTextInput() const { return mTextEdit->getCaption(); } void TextInputDialog::setTextInput(const std::string &text) { mTextEdit->setCaption(text); } } openmw-openmw-0.48.0/apps/openmw/mwgui/textinput.hpp000066400000000000000000000014371445372753700226160ustar00rootroot00000000000000#ifndef MWGUI_TEXT_INPUT_H #define MWGUI_TEXT_INPUT_H #include "windowbase.hpp" namespace MWGui { class TextInputDialog : public WindowModal { public: TextInputDialog(); std::string getTextInput() const; void setTextInput(const std::string &text); void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); void onOpen() override; bool exit() override { return false; } /** Event : Dialog finished, OK button clicked.\n signature : void method()\n */ EventHandle_WindowBase eventDone; protected: void onOkClicked(MyGUI::Widget* _sender); void onTextAccepted(MyGUI::Edit* _sender); private: MyGUI::EditBox* mTextEdit; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/timeadvancer.cpp000066400000000000000000000024131445372753700232020ustar00rootroot00000000000000#include "timeadvancer.hpp" namespace MWGui { TimeAdvancer::TimeAdvancer(float delay) : mRunning(false), mCurHour(0), mHours(1), mInterruptAt(-1), mDelay(delay), mRemainingTime(delay) { } void TimeAdvancer::run(int hours, int interruptAt) { mHours = hours; mCurHour = 0; mInterruptAt = interruptAt; mRemainingTime = mDelay; mRunning = true; } void TimeAdvancer::stop() { mRunning = false; } void TimeAdvancer::onFrame(float dt) { if (!mRunning) return; if (mCurHour == mInterruptAt) { stop(); eventInterrupted(); return; } mRemainingTime -= dt; while (mRemainingTime <= 0) { mRemainingTime += mDelay; ++mCurHour; if (mCurHour <= mHours) eventProgressChanged(mCurHour, mHours); else { stop(); eventFinished(); return; } } } int TimeAdvancer::getHours() const { return mHours; } bool TimeAdvancer::isRunning() const { return mRunning; } } openmw-openmw-0.48.0/apps/openmw/mwgui/timeadvancer.hpp000066400000000000000000000016111445372753700232060ustar00rootroot00000000000000#ifndef MWGUI_TIMEADVANCER_H #define MWGUI_TIMEADVANCER_H #include namespace MWGui { class TimeAdvancer { public: TimeAdvancer(float delay); void run(int hours, int interruptAt=-1); void stop(); void onFrame(float dt); int getHours() const; bool isRunning() const; // signals typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate2 EventHandle_IntInt; EventHandle_IntInt eventProgressChanged; EventHandle_Void eventInterrupted; EventHandle_Void eventFinished; private: bool mRunning; int mCurHour; int mHours; int mInterruptAt; float mDelay; float mRemainingTime; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/tooltips.cpp000066400000000000000000001140101445372753700224120ustar00rootroot00000000000000#include "tooltips.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" #include "mapwindow.hpp" #include "inventorywindow.hpp" #include "itemmodel.hpp" namespace MWGui { std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"}; ToolTips::ToolTips() : Layout("openmw_tooltips.layout") , mFocusToolTipX(0.0) , mFocusToolTipY(0.0) , mHorizontalScrollIndex(0) , mDelay(0.0) , mRemainingDelay(0.0) , mLastMouseX(0) , mLastMouseY(0) , mEnabled(true) , mFullHelp(false) , mShowOwned(0) , mFrameDuration(0.f) { getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); mDynamicToolTipBox->setVisible(false); // turn off mouse focus so that getMouseFocusWidget returns the correct widget, // even if the mouse is over the tooltip mDynamicToolTipBox->setNeedMouseFocus(false); mMainWidget->setNeedMouseFocus(false); mDelay = Settings::Manager::getFloat("tooltip delay", "GUI"); mRemainingDelay = mDelay; for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void ToolTips::setEnabled(bool enabled) { mEnabled = enabled; } void ToolTips::onFrame(float frameDuration) { mFrameDuration = frameDuration; } void ToolTips::update(float frameDuration) { while (mDynamicToolTipBox->getChildCount()) { MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); } // start by hiding everything for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } const MyGUI::IntSize &viewSize = MyGUI::RenderManager::getInstance().getViewSize(); if (!mEnabled) { return; } MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); bool guiMode = winMgr->isGuiMode(); if (guiMode) { if (!winMgr->getCursorVisible()) return; const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); if (winMgr->getWorldMouseOver() && (winMgr->isConsoleMode() || (winMgr->getMode() == GM_Container) || (winMgr->getMode() == GM_Inventory))) { if (mFocusObject.isEmpty ()) return; const MWWorld::Class& objectclass = mFocusObject.getClass(); MyGUI::IntSize tooltipSize; if (!objectclass.hasToolTip(mFocusObject) && winMgr->isConsoleMode()) { setCoord(0, 0, 300, 300); mDynamicToolTipBox->setVisible(true); ToolTipInfo info; info.caption = mFocusObject.getClass().getName(mFocusObject); if (info.caption.empty()) info.caption=mFocusObject.getCellRef().getRefId(); info.icon.clear(); tooltipSize = createToolTip(info, checkOwned()); } else tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); } else { if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) { mRemainingDelay -= frameDuration; } else { mHorizontalScrollIndex = 0; mRemainingDelay = mDelay; } mLastMouseX = mousePos.left; mLastMouseY = mousePos.top; if (mRemainingDelay > 0) return; MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); if (focus == nullptr) return; MyGUI::IntSize tooltipSize; // try to go 1 level up until there is a widget that has tooltip // this is necessary because some skin elements are actually separate widgets while (!focus->isUserString("ToolTipType")) { focus = focus->getParent(); if (!focus) return; } std::string type = focus->getUserString("ToolTipType"); if (type == "") { return; } // special handling for markers on the local map: the tooltip should only be visible // if the marker is not hidden due to the fog of war. if (type == "MapMarker") { LocalMapBase::MarkerUserData data = *focus->getUserData(); if (!data.isPositionExplored()) return; ToolTipInfo info; info.text = data.caption; info.notes = data.notes; tooltipSize = createToolTip(info); } else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); if (!mFocusObject) return; tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") { std::pair pair = *focus->getUserData >(); mFocusObject = pair.second->getItem(pair.first).mBase; bool isAllowedToUse = pair.second->allowedToUseItems(); tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false, !isAllowedToUse); } else if (type == "ToolTipInfo") { tooltipSize = createToolTip(*focus->getUserData()); } else if (type == "AvatarItemSelection") { MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); MWWorld::Ptr item = winMgr->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); mFocusObject = item; if (!mFocusObject.isEmpty ()) tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "Spell") { ToolTipInfo info; const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(focus->getUserString("Spell")); info.caption = spell->mName; Widgets::SpellEffectList effects; for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) { Widgets::SpellEffectParams params; params.mEffectID = spellEffect.mEffectID; params.mSkill = spellEffect.mSkill; params.mAttribute = spellEffect.mAttribute; params.mDuration = spellEffect.mDuration; params.mMagnMin = spellEffect.mMagnMin; params.mMagnMax = spellEffect.mMagnMax; params.mRange = spellEffect.mRange; params.mArea = spellEffect.mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); } if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress { MWWorld::Ptr player = MWMechanics::getPlayer(); int school = MWMechanics::getSpellSchool(spell, player); info.text = "#{sSchool}: " + sSchoolNames[school]; } std::string cost = focus->getUserString("SpellCost"); if (cost != "" && cost != "0") info.text += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); info.effects = effects; tooltipSize = createToolTip(info); } else if (type == "Layout") { // tooltip defined in the layout MyGUI::Widget* tooltip; getWidget(tooltip, focus->getUserString("ToolTipLayout")); tooltip->setVisible(true); std::map userStrings = focus->getUserStrings(); for (auto& userStringPair : userStrings) { size_t underscorePos = userStringPair.first.find('_'); if (underscorePos == std::string::npos) continue; std::string key = userStringPair.first.substr(0, underscorePos); std::string widgetName = userStringPair.first.substr(underscorePos+1, userStringPair.first.size()-(underscorePos+1)); type = "Property"; size_t caretPos = key.find('^'); if (caretPos != std::string::npos) { type = key.substr(0, caretPos); key.erase(key.begin(), key.begin() + caretPos + 1); } MyGUI::Widget* w; getWidget(w, widgetName); if (type == "Property") w->setProperty(key, userStringPair.second); else if (type == "UserData") w->setUserString(key, userStringPair.second); } tooltipSize = tooltip->getSize(); tooltip->setCoord(0, 0, tooltipSize.width, tooltipSize.height); } else throw std::runtime_error ("unknown tooltip type"); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); } } else { if (!mFocusObject.isEmpty()) { MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); setCoord(viewSize.width/2 - tooltipSize.width/2, std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), tooltipSize.width, tooltipSize.height); mDynamicToolTipBox->setVisible(true); } } } void ToolTips::position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize) { position += MyGUI::IntPoint(0, 32) - MyGUI::IntPoint(static_cast(MyGUI::InputManager::getInstance().getMousePosition().left / float(viewportSize.width) * size.width), 0); if ((position.left + size.width) > viewportSize.width) { position.left = viewportSize.width - size.width; } if ((position.top + size.height) > viewportSize.height) { position.top = MyGUI::InputManager::getInstance().getMousePosition().top - size.height - 8; } } void ToolTips::clear() { mFocusObject = MWWorld::Ptr(); while (mDynamicToolTipBox->getChildCount()) { MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); } for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } } void ToolTips::setFocusObject(const MWWorld::Ptr& focus) { mFocusObject = focus; update(mFrameDuration); } MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image, bool isOwned) { // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); MyGUI::IntSize tooltipSize; const MWWorld::Class& object = mFocusObject.getClass(); if (!object.hasToolTip(mFocusObject)) { mDynamicToolTipBox->setVisible(false); } else { mDynamicToolTipBox->setVisible(true); ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) info.icon.clear(); tooltipSize = createToolTip(info, isOwned); } return tooltipSize; } bool ToolTips::checkOwned() { if(mFocusObject.isEmpty()) return false; MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWWorld::Ptr victim; MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); return !mm->isAllowedToUse(ptr, mFocusObject, victim); } MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isOwned) { mDynamicToolTipBox->setVisible(true); if((mShowOwned == 1 || mShowOwned == 3) && isOwned) mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp_Owned" : "HUD_Box_Owned"); else mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); std::string caption = info.caption; std::string image = info.icon; int imageSize = (image != "") ? info.imageSize : 0; std::string text = info.text; // remove the first newline (easier this way) if (text.size() > 0 && text[0] == '\n') text.erase(0, 1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (info.enchant != "") { enchant = store.get().search(info.enchant); if (enchant) { if (enchant->mData.mType == ESM::Enchantment::CastOnce) text += "\n#{sItemCastOnce}"; else if (enchant->mData.mType == ESM::Enchantment::WhenStrikes) text += "\n#{sItemCastWhenStrikes}"; else if (enchant->mData.mType == ESM::Enchantment::WhenUsed) text += "\n#{sItemCastWhenUsed}"; else if (enchant->mData.mType == ESM::Enchantment::ConstantEffect) text += "\n#{sItemCastConstant}"; } } // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); const MyGUI::IntPoint padding(8, 8); const int imageCaptionHPadding = (caption != "" ? 8 : 0); const int imageCaptionVPadding = (caption != "" ? 4 : 0); const int maximumWidth = MyGUI::RenderManager::getInstance().getViewSize().width - imageCaptionHPadding * 2; const std::string realImage = Misc::ResourceHelpers::correctIconPath(image, MWBase::Environment::get().getResourceSystem()->getVFS()); Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setEditStatic(true); captionWidget->setNeedKeyFocus(false); captionWidget->setCaptionWithReplacing(caption); MyGUI::IntSize captionSize = captionWidget->getTextSize(); int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); textWidget->setEditStatic(true); textWidget->setEditMultiLine(true); textWidget->setEditWordWrap(info.wordWrap); textWidget->setCaptionWithReplacing(text); textWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); textWidget->setNeedKeyFocus(false); MyGUI::IntSize textSize = textWidget->getTextSize(); captionSize += MyGUI::IntSize(imageSize, 0); // adjust for image MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); for (const std::string& note : info.notes) { MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); Gui::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(padding.left+8+4, totalSize.height+padding.top, 300-padding.left-8-4, 300-totalSize.height), MyGUI::Align::Default); edit->setEditMultiLine(true); edit->setEditWordWrap(true); edit->setCaption(note); edit->setSize(edit->getWidth(), edit->getTextSize().height); icon->setPosition(icon->getLeft(),(edit->getTop()+edit->getBottom())/2-icon->getHeight()/2); totalSize.height += std::max(edit->getHeight(), icon->getHeight()); totalSize.width = std::max(totalSize.width, edit->getWidth()+8+4); } if (!info.effects.empty()) { MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget("", MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget ("MW_StatName", coord, MyGUI::Align::Default); effectsWidget->setEffectList(info.effects); std::vector effectItems; int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; flag |= info.isIngredient ? Widgets::MWEffectList::EF_Constant : 0; effectsWidget->createEffectWidgets(effectItems, effectArea, coord, info.isPotion || info.isIngredient, flag); totalSize.height += coord.top-6; totalSize.width = std::max(totalSize.width, coord.width); } if (enchant) { MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget("", MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget ("MW_StatName", coord, MyGUI::Align::Default); enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects)); std::vector enchantEffectItems; int flag = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, false, flag); totalSize.height += coord.top-6; totalSize.width = std::max(totalSize.width, coord.width); if (enchant->mData.mType == ESM::Enchantment::WhenStrikes || enchant->mData.mType == ESM::Enchantment::WhenUsed) { int maxCharge = enchant->mData.mCharge; int charge = (info.remainingEnchantCharge == -1) ? maxCharge : info.remainingEnchantCharge; const int chargeWidth = 204; MyGUI::TextBox* chargeText = enchantArea->createWidget("SandText", MyGUI::IntCoord(0, 0, 10, 18), MyGUI::Align::Default, "ToolTipEnchantChargeText"); chargeText->setCaptionWithReplacing("#{sCharges}"); const int chargeTextWidth = chargeText->getTextSize().width + 5; const int chargeAndTextWidth = chargeWidth + chargeTextWidth; totalSize.width = std::max(totalSize.width, chargeAndTextWidth); chargeText->setCoord((totalSize.width - chargeAndTextWidth)/2, coord.top+6, chargeTextWidth, 18); MyGUI::IntCoord chargeCoord; if (totalSize.width < chargeWidth) { totalSize.width = chargeWidth; chargeCoord = MyGUI::IntCoord(0, coord.top+6, chargeWidth, 18); } else { chargeCoord = MyGUI::IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18); } Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default); chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } } captionWidget->setCoord( (totalSize.width - captionSize.width)/2 + imageSize, (captionHeight-captionSize.height)/2, captionSize.width-imageSize, captionSize.height); //if its too long we do hscroll with the caption if (captionSize.width > maximumWidth) { mHorizontalScrollIndex = mHorizontalScrollIndex + 2; if (mHorizontalScrollIndex > captionSize.width){ mHorizontalScrollIndex = -totalSize.width; } int horizontal_scroll = mHorizontalScrollIndex; if (horizontal_scroll < 40){ horizontal_scroll = 40; }else{ horizontal_scroll = 80 - mHorizontalScrollIndex; } captionWidget->setPosition (MyGUI::IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top)); } else { captionWidget->setPosition (captionWidget->getPosition() + padding); } textWidget->setPosition (textWidget->getPosition() + MyGUI::IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter if (image != "") { MyGUI::ImageBox* imageWidget = mDynamicToolTipBox->createWidget("ImageBox", MyGUI::IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize), MyGUI::Align::Left | MyGUI::Align::Top); imageWidget->setImageTexture(realImage); imageWidget->setPosition (imageWidget->getPosition() + padding); } totalSize += MyGUI::IntSize(padding.left*2, padding.top*2); return totalSize; } std::string ToolTips::toString(const float value) { std::ostringstream stream; if (value != int(value)) stream << std::setprecision(3); stream << value; return stream.str(); } std::string ToolTips::toString(const int value) { return std::to_string(value); } std::string ToolTips::getWeightString(const float weight, const std::string& prefix) { if (weight == 0) return ""; else return "\n" + prefix + ": " + toString(weight); } std::string ToolTips::getPercentString(const float value, const std::string& prefix) { if (value == 0) return ""; else return "\n" + prefix + ": " + toString(value*100) +"%"; } std::string ToolTips::getValueString(const int value, const std::string& prefix) { if (value == 0) return ""; else return "\n" + prefix + ": " + toString(value); } std::string ToolTips::getMiscString(const std::string& text, const std::string& prefix) { if (text == "") return ""; else return "\n" + prefix + ": " + text; } std::string ToolTips::getCountString(const int value) { if (value == 1) return ""; else return " (" + MyGUI::utility::toString(value) + ")"; } std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref) { const std::string& soul = cellref.getSoul(); if (soul.empty()) return std::string(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Creature *creature = store.get().search(soul); if (!creature) return std::string(); if (creature->mName.empty()) return " (" + creature->mId + ")"; return " (" + creature->mName + ")"; } std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) { std::string ret; ret += getMiscString(cellref.getOwner(), "Owner"); const std::string& factionId = cellref.getFaction(); if (!factionId.empty()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Faction *fact = store.get().search(factionId); if (fact != nullptr) { ret += getMiscString(fact->mName.empty() ? factionId : fact->mName, "Owner Faction"); if (cellref.getFactionRank() >= 0) { int rank = cellref.getFactionRank(); const std::string rankName = fact->mRanks[rank]; if (rankName.empty()) ret += getValueString(cellref.getFactionRank(), "Rank"); else ret += getMiscString(rankName, "Rank"); } } } std::vector > itemOwners = MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); for (std::pair& owner : itemOwners) { if (owner.second == std::numeric_limits::max()) ret += std::string("\nStolen from ") + owner.first; // for legacy (ESS) savegames else ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from " + owner.first; } ret += getMiscString(cellref.getGlobalVariable(), "Global"); return ret; } std::string ToolTips::getDurationString(float duration, const std::string& prefix) { std::string ret; ret = prefix + ": "; if (duration < 1.f) { ret += "0 s"; return ret; } constexpr int secondsPerMinute = 60; // 60 seconds constexpr int secondsPerHour = secondsPerMinute * 60; // 60 minutes constexpr int secondsPerDay = secondsPerHour * 24; // 24 hours constexpr int secondsPerMonth = secondsPerDay * 30; // 30 days constexpr int secondsPerYear = secondsPerDay * 365; int fullDuration = static_cast(duration); int units = 0; int years = fullDuration / secondsPerYear; int months = fullDuration % secondsPerYear / secondsPerMonth; int days = fullDuration % secondsPerYear % secondsPerMonth / secondsPerDay; // Because a year is not exactly 12 "months" int hours = fullDuration % secondsPerDay / secondsPerHour; int minutes = fullDuration % secondsPerHour / secondsPerMinute; int seconds = fullDuration % secondsPerMinute; if (years) { units++; ret += toString(years) + " y "; } if (months) { units++; ret += toString(months) + " mo "; } if (units < 2 && days) { units++; ret += toString(days) + " d "; } if (units < 2 && hours) { units++; ret += toString(hours) + " h "; } if (units >= 2) return ret; if (minutes) ret += toString(minutes) + " min "; if (seconds) ret += toString(seconds) + " s "; return ret; } bool ToolTips::toggleFullHelp() { mFullHelp = !mFullHelp; return mFullHelp; } bool ToolTips::getFullHelp() const { return mFullHelp; } void ToolTips::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) { mFocusToolTipX = (min_x + max_x) / 2; mFocusToolTipY = min_y; } void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId) { if (skillId == -1) return; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; const ESM::Skill* skill = store.get().find(skillId); assert(skill); const ESM::Attribute* attr = store.get().find(skill->mData.mAttribute); assert(attr); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); widget->setUserString("Caption_SkillNoProgressName", "#{"+skillNameId+"}"); widget->setUserString("Caption_SkillNoProgressDescription", skill->mDescription); widget->setUserString("Caption_SkillNoProgressAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); widget->setUserString("ImageTexture_SkillNoProgressImage", icon); } void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId) { if (attributeId == -1) return; std::string icon = ESM::Attribute::sAttributeIcons[attributeId]; std::string name = ESM::Attribute::sGmstAttributeIds[attributeId]; std::string desc = ESM::Attribute::sGmstAttributeDescIds[attributeId]; widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "AttributeToolTip"); widget->setUserString("Caption_AttributeName", "#{"+name+"}"); widget->setUserString("Caption_AttributeDescription", "#{"+desc+"}"); widget->setUserString("ImageTexture_AttributeImage", icon); } void ToolTips::createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId) { widget->setUserString("Caption_Caption", name); std::string specText; // get all skills of this specialisation const MWWorld::Store &skills = MWBase::Environment::get().getWorld()->getStore().get(); bool isFirst = true; for (auto& skillPair : skills) { if (skillPair.second.mData.mSpecialization == specId) { if (isFirst) isFirst = false; else specText += "\n"; specText += std::string("#{") + ESM::Skill::sSkillNameIds[skillPair.first] + "}"; } } widget->setUserString("Caption_ColumnText", specText); widget->setUserString("ToolTipLayout", "SpecializationToolTip"); widget->setUserString("ToolTipType", "Layout"); } void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::BirthSign *sign = store.get().find(birthsignId); const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); widget->setUserString("ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture, vfs)); std::string text; text += sign->mName; text += "\n#{fontcolourhtml=normal}" + sign->mDescription; std::vector abilities, powers, spells; for (const std::string& spellId : sign->mPowers.mList) { const ESM::Spell *spell = store.get().search(spellId); if (!spell) continue; // Skip spells which cannot be found ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power) continue; // We only want spell, ability and powers. if (type == ESM::Spell::ST_Ability) abilities.push_back(spellId); else if (type == ESM::Spell::ST_Power) powers.push_back(spellId); else if (type == ESM::Spell::ST_Spell) spells.push_back(spellId); } struct { const std::vector &spells; std::string label; } categories[3] = { {abilities, "sBirthsignmenu1"}, {powers, "sPowers"}, {spells, "sBirthsignmenu2"} }; for (int category = 0; category < 3; ++category) { bool addHeader = true; for (const std::string& spellId : categories[category].spells) { if (addHeader) { text += std::string("\n\n#{fontcolourhtml=header}") + std::string("#{") + categories[category].label + "}"; addHeader = false; } const ESM::Spell *spell = store.get().find(spellId); text += "\n#{fontcolourhtml=normal}" + spell->mName; } } widget->setUserString("Caption_BirthSignText", text); } void ToolTips::createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace) { widget->setUserString("Caption_CenteredCaption", playerRace->mName); widget->setUserString("Caption_CenteredCaptionText", playerRace->mDescription); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "RaceToolTip"); } void ToolTips::createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass) { if (playerClass.mName == "") return; int spec = playerClass.mData.mSpecialization; std::string specStr; if (spec == 0) specStr = "#{sSpecializationCombat}"; else if (spec == 1) specStr = "#{sSpecializationMagic}"; else if (spec == 2) specStr = "#{sSpecializationStealth}"; widget->setUserString("Caption_ClassName", playerClass.mName); widget->setUserString("Caption_ClassDescription", playerClass.mDescription); widget->setUserString("Caption_ClassSpecialisation", "#{sSpecialization}: " + specStr); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "ClassToolTip"); } void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, short id) { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld ()->getStore ().get().find(id); const std::string &name = ESM::MagicEffect::effectIdToString (id); std::string icon = effect->mIcon; int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); widget->setUserString("Caption_MagicEffectName", "#{" + name + "}"); widget->setUserString("Caption_MagicEffectDescription", effect->mDescription); widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + sSchoolNames[effect->mData.mSchool]); widget->setUserString("ImageTexture_MagicEffectImage", icon); } void ToolTips::setDelay(float delay) { mDelay = delay; mRemainingDelay = mDelay; } } openmw-openmw-0.48.0/apps/openmw/mwgui/tooltips.hpp000066400000000000000000000115171445372753700224270ustar00rootroot00000000000000#ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H #include "layout.hpp" #include "../mwworld/ptr.hpp" #include "widgets.hpp" namespace ESM { struct Class; struct Race; } namespace MWGui { // Info about tooltip that is supplied by the MWWorld::Class object struct ToolTipInfo { public: ToolTipInfo() : imageSize(32) , remainingEnchantCharge(-1) , isPotion(false) , isIngredient(false) , wordWrap(true) {} std::string caption; std::string text; std::string icon; int imageSize; // enchantment (for cloth, armor, weapons) std::string enchant; int remainingEnchantCharge; // effects (for potions, ingredients) Widgets::SpellEffectList effects; // local map notes std::vector notes; bool isPotion; // potions do not show target in the tooltip bool isIngredient; // ingredients have no effect magnitude bool wordWrap; }; class ToolTips : public Layout { public: ToolTips(); void onFrame(float frameDuration); void update(float frameDuration); void setEnabled(bool enabled); bool toggleFullHelp(); ///< show extra info in item tooltips (owner, script) bool getFullHelp() const; void setDelay(float delay); void clear(); void setFocusObject(const MWWorld::Ptr& focus); void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); ///< set the screen-space position of the tooltip for focused object static std::string getWeightString(const float weight, const std::string& prefix); static std::string getPercentString(const float value, const std::string& prefix); static std::string getValueString(const int value, const std::string& prefix); ///< @return "prefix: value" or "" if value is 0 static std::string getMiscString(const std::string& text, const std::string& prefix); ///< @return "prefix: text" or "" if text is empty static std::string toString(const float value); static std::string toString(const int value); static std::string getCountString(const int value); ///< @return blank string if count is 1, or else " (value)" static std::string getSoulString(const MWWorld::CellRef& cellref); ///< Returns a string containing the name of the creature that the ID in the cellref's soul field belongs to. static std::string getCellRefString(const MWWorld::CellRef& cellref); ///< Returns a string containing debug tooltip information about the given cellref. static std::string getDurationString (float duration, const std::string& prefix); ///< Returns duration as two largest time units, rounded down. Note: not localized; no line break. // these do not create an actual tooltip, but they fill in the data that is required so the tooltip // system knows what to show in case this widget is hovered static void createSkillToolTip(MyGUI::Widget* widget, int skillId); static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId); static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId); static void createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId); static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); bool checkOwned(); /// Returns True if taking mFocusObject would be crime private: MyGUI::Widget* mDynamicToolTipBox; MWWorld::Ptr mFocusObject; MyGUI::IntSize getToolTipViaPtr (int count, bool image = true, bool isOwned = false); ///< @return requested tooltip size MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isOwned = false); ///< @return requested tooltip size /// @param isFocusObject Is the object this tooltips originates from mFocusObject? float mFocusToolTipX; float mFocusToolTipY; /// Adjust position for a tooltip so that it doesn't leave the screen and does not obscure the mouse cursor void position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize); static std::string sSchoolNames[6]; int mHorizontalScrollIndex; float mDelay; float mRemainingDelay; // remaining time until tooltip will show int mLastMouseX; int mLastMouseY; bool mEnabled; bool mFullHelp; int mShowOwned; float mFrameDuration; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/tradeitemmodel.cpp000066400000000000000000000150261445372753700235430ustar00rootroot00000000000000#include "tradeitemmodel.hpp" #include #include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" namespace MWGui { TradeItemModel::TradeItemModel(ItemModel *sourceModel, const MWWorld::Ptr& merchant) : mMerchant(merchant) { mSourceModel = sourceModel; } bool TradeItemModel::allowedToUseItems() const { return true; } ItemStack TradeItemModel::getItem (ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); if (mItems.size() <= static_cast(index)) throw std::runtime_error("Item index out of range"); return mItems[index]; } size_t TradeItemModel::getItemCount() { return mItems.size(); } void TradeItemModel::borrowImpl(const ItemStack &item, std::vector &out) { bool found = false; for (ItemStack& itemStack : out) { if (itemStack.mBase == item.mBase) { itemStack.mCount += item.mCount; found = true; break; } } if (!found) out.push_back(item); } void TradeItemModel::unborrowImpl(const ItemStack &item, size_t count, std::vector &out) { std::vector::iterator it = out.begin(); bool found = false; for (; it != out.end(); ++it) { if (it->mBase == item.mBase) { if (it->mCount < count) throw std::runtime_error("Not enough borrowed items to return"); it->mCount -= count; if (it->mCount == 0) out.erase(it); found = true; break; } } if (!found) throw std::runtime_error("Can't find borrowed item to return"); } void TradeItemModel::borrowItemFromUs (ModelIndex itemIndex, size_t count) { ItemStack item = getItem(itemIndex); item.mCount = count; borrowImpl(item, mBorrowedFromUs); } void TradeItemModel::borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count) { ItemStack item = source->getItem(itemIndex); item.mCount = count; borrowImpl(item, mBorrowedToUs); } void TradeItemModel::returnItemBorrowedToUs (ModelIndex itemIndex, size_t count) { ItemStack item = getItem(itemIndex); unborrowImpl(item, count, mBorrowedToUs); } void TradeItemModel::returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count) { ItemStack item = source->getItem(itemIndex); unborrowImpl(item, count, mBorrowedFromUs); } void TradeItemModel::adjustEncumbrance(float &encumbrance) { for (ItemStack& itemStack : mBorrowedToUs) { MWWorld::Ptr& item = itemStack.mBase; encumbrance += item.getClass().getWeight(item) * itemStack.mCount; } for (ItemStack& itemStack : mBorrowedFromUs) { MWWorld::Ptr& item = itemStack.mBase; encumbrance -= item.getClass().getWeight(item) * itemStack.mCount; } encumbrance = std::max(0.f, encumbrance); } void TradeItemModel::abort() { mBorrowedFromUs.clear(); mBorrowedToUs.clear(); } const std::vector TradeItemModel::getItemsBorrowedToUs() const { return mBorrowedToUs; } void TradeItemModel::transferItems() { for (ItemStack& itemStack : mBorrowedToUs) { // get index in the source model ItemModel* sourceModel = itemStack.mCreator; size_t i=0; for (; igetItemCount(); ++i) { if (itemStack.mBase == sourceModel->getItem(i).mBase) break; } if (i == sourceModel->getItemCount()) throw std::runtime_error("The borrowed item disappeared"); const ItemStack& item = sourceModel->getItem(i); static const bool prevent = Settings::Manager::getBool("prevent merchant equipping", "Game"); // copy the borrowed items to our model copyItem(item, itemStack.mCount, !prevent); // then remove them from the source model sourceModel->removeItem(item, itemStack.mCount); } mBorrowedToUs.clear(); mBorrowedFromUs.clear(); } void TradeItemModel::update() { mSourceModel->update(); int services = 0; if (!mMerchant.isEmpty()) services = mMerchant.getClass().getServices(mMerchant); mItems.clear(); // add regular items for (size_t i=0; igetItemCount(); ++i) { ItemStack item = mSourceModel->getItem(i); if(!mMerchant.isEmpty()) { MWWorld::Ptr base = item.mBase; if(Misc::StringUtils::ciEqual(base.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) continue; if (!base.getClass().showsInInventory(base)) return; if(!base.getClass().canSell(base, services)) continue; // Bound items may not be bought if (item.mFlags & ItemStack::Flag_Bound) continue; // don't show equipped items if(mMerchant.getClass().hasInventoryStore(mMerchant)) { MWWorld::InventoryStore& store = mMerchant.getClass().getInventoryStore(mMerchant); if (store.isEquipped(base)) continue; } } // don't show items that we borrowed to someone else for (ItemStack& itemStack : mBorrowedFromUs) { if (itemStack.mBase == item.mBase) { if (item.mCount < itemStack.mCount) throw std::runtime_error("Lent more items than present"); item.mCount -= itemStack.mCount; } } if (item.mCount > 0) mItems.push_back(item); } // add items borrowed to us for (ItemStack& itemStack : mBorrowedToUs) { itemStack.mType = ItemStack::Type_Barter; mItems.push_back(itemStack); } } } openmw-openmw-0.48.0/apps/openmw/mwgui/tradeitemmodel.hpp000066400000000000000000000035031445372753700235450ustar00rootroot00000000000000#ifndef MWGUI_TRADE_ITEM_MODEL_H #define MWGUI_TRADE_ITEM_MODEL_H #include "itemmodel.hpp" namespace MWGui { class ItemModel; /// @brief An item model that allows 'borrowing' items from another item model. Used for previewing barter offers. /// Also filters items that the merchant does not sell. class TradeItemModel : public ProxyItemModel { public: TradeItemModel (ItemModel* sourceModel, const MWWorld::Ptr& merchant); bool allowedToUseItems() const override; ItemStack getItem (ModelIndex index) override; size_t getItemCount() override; void update() override; void borrowItemFromUs (ModelIndex itemIndex, size_t count); void borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count); ///< @note itemIndex points to an item in \a source void returnItemBorrowedToUs (ModelIndex itemIndex, size_t count); void returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count); /// Permanently transfers items that were borrowed to us from another model to this model void transferItems (); /// Aborts trade void abort(); /// Adjusts the given encumbrance by adding weight for items that have been lent to us, /// and removing weight for items we've lent to someone else. void adjustEncumbrance (float& encumbrance); const std::vector getItemsBorrowedToUs() const; private: void borrowImpl(const ItemStack& item, std::vector& out); void unborrowImpl(const ItemStack& item, size_t count, std::vector& out); std::vector mItems; std::vector mBorrowedToUs; std::vector mBorrowedFromUs; MWWorld::Ptr mMerchant; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/tradewindow.cpp000066400000000000000000000521301445372753700230700ustar00rootroot00000000000000#include "tradewindow.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" #include "sortfilteritemmodel.hpp" #include "containeritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" #include "tooltips.hpp" namespace { int getEffectiveValue (MWWorld::Ptr item, int count) { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) { price *= item.getClass().getItemNormalizedHealth(item); } return static_cast(price * count); } } namespace MWGui { TradeWindow::TradeWindow() : WindowBase("openmw_trade_window.layout") , mSortModel(nullptr) , mTradeModel(nullptr) , mItemToSell(-1) , mCurrentBalance(0) , mCurrentMerchantOffer(0) { getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); getWidget(mFilterApparel, "ApparelButton"); getWidget(mFilterMagic, "MagicButton"); getWidget(mFilterMisc, "MiscButton"); getWidget(mMaxSaleButton, "MaxSaleButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mOfferButton, "OfferButton"); getWidget(mPlayerGold, "PlayerGold"); getWidget(mMerchantGold, "MerchantGold"); getWidget(mIncreaseButton, "IncreaseButton"); getWidget(mDecreaseButton, "DecreaseButton"); getWidget(mTotalBalance, "TotalBalance"); getWidget(mTotalBalanceLabel, "TotalBalanceLabel"); getWidget(mBottomPane, "BottomPane"); getWidget(mFilterEdit, "FilterEdit"); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &TradeWindow::onItemSelected); mFilterAll->setStateSelected(true); mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &TradeWindow::onNameFilterChanged); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onCancelButtonClicked); mOfferButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onOfferButtonClicked); mMaxSaleButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onMaxSaleButtonClicked); mIncreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onIncreaseButtonPressed); mIncreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onDecreaseButtonPressed); mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); mTotalBalance->eventValueChanged += MyGUI::newDelegate(this, &TradeWindow::onBalanceValueChanged); mTotalBalance->eventEditSelectAccept += MyGUI::newDelegate(this, &TradeWindow::onAccept); mTotalBalance->setMinValue(std::numeric_limits::min()+1); // disallow INT_MIN since abs(INT_MIN) is undefined setCoord(400, 0, 400, 300); } void TradeWindow::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; mCurrentBalance = 0; mCurrentMerchantOffer = 0; std::vector itemSources; // Important: actor goes first, so purchased items come out of the actor's pocket first itemSources.push_back(actor); MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); std::vector worldItems; MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources, worldItems), mPtr); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel (mSortModel); mItemView->resetScrollBars(); updateLabels(); setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); mFilterEdit->setCaption(""); } void TradeWindow::onFrame(float dt) { checkReferenceAvailable(); } void TradeWindow::onNameFilterChanged(MyGUI::EditBox* _sender) { mSortModel->setNameFilter(_sender->getCaption()); mItemView->update(); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) mSortModel->setCategory(SortFilterItemModel::Category_All); else if (_sender == mFilterWeapon) mSortModel->setCategory(SortFilterItemModel::Category_Weapon); else if (_sender == mFilterApparel) mSortModel->setCategory(SortFilterItemModel::Category_Apparel); else if (_sender == mFilterMagic) mSortModel->setCategory(SortFilterItemModel::Category_Magic); else if (_sender == mFilterMisc) mSortModel->setCategory(SortFilterItemModel::Category_Misc); mFilterAll->setStateSelected(false); mFilterWeapon->setStateSelected(false); mFilterApparel->setStateSelected(false); mFilterMagic->setStateSelected(false); mFilterMisc->setStateSelected(false); _sender->castType()->setStateSelected(true); mItemView->update(); } int TradeWindow::getMerchantServices() { return mPtr.getClass().getServices(mPtr); } bool TradeWindow::exit() { mTradeModel->abort(); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort(); return true; } void TradeWindow::onItemSelected (int index) { const ItemStack& item = mSortModel->getItem(index); MWWorld::Ptr object = item.mBase; int count = item.mCount; bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = "#{sQuanityMenuMessage02}"; std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, message, count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem); mItemToSell = mSortModel->mapToSource(index); } else { mItemToSell = mSortModel->mapToSource(index); sellItem (nullptr, count); } } void TradeWindow::sellItem(MyGUI::Widget* sender, int count) { const ItemStack& item = mTradeModel->getItem(mItemToSell); std::string sound = item.mBase.getClass().getUpSoundId(item.mBase); MWBase::Environment::get().getWindowManager()->playSound(sound); TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); if (item.mType == ItemStack::Type_Barter) { // this was an item borrowed to us by the player mTradeModel->returnItemBorrowedToUs(mItemToSell, count); playerTradeModel->returnItemBorrowedFromUs(mItemToSell, mTradeModel, count); buyFromNpc(item.mBase, count, true); } else { // borrow item to player playerTradeModel->borrowItemToUs(mItemToSell, mTradeModel, count); mTradeModel->borrowItemFromUs(mItemToSell, count); buyFromNpc(item.mBase, count, false); } MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); mItemView->update(); } void TradeWindow::borrowItem (int index, size_t count) { TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); mTradeModel->borrowItemToUs(index, playerTradeModel, count); mItemView->update(); sellToNpc(playerTradeModel->getItem(index).mBase, count, false); } void TradeWindow::returnItem (int index, size_t count) { TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); const ItemStack& item = playerTradeModel->getItem(index); mTradeModel->returnItemBorrowedFromUs(index, playerTradeModel, count); mItemView->update(); sellToNpc(item.mBase, count, true); } void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor) { MWWorld::ContainerStore& store = actor.getClass().getContainerStore(actor); if (amount > 0) { store.add(MWWorld::ContainerStore::sGoldId, amount, actor); } else { store.remove(MWWorld::ContainerStore::sGoldId, - amount, actor); } } void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) { TradeItemModel* playerItemModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; // were there any items traded at all? const std::vector& playerBought = playerItemModel->getItemsBorrowedToUs(); const std::vector& merchantBought = mTradeModel->getItemsBorrowedToUs(); if (playerBought.empty() && merchantBought.empty()) { // user notification MWBase::Environment::get().getWindowManager()-> messageBox("#{sBarterDialog11}"); return; } MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // check if the player can afford this if (mCurrentBalance < 0 && playerGold < std::abs(mCurrentBalance)) { // user notification MWBase::Environment::get().getWindowManager()-> messageBox("#{sBarterDialog1}"); return; } // check if the merchant can afford this if (mCurrentBalance > 0 && getMerchantGold() < mCurrentBalance) { // user notification MWBase::Environment::get().getWindowManager()-> messageBox("#{sBarterDialog2}"); return; } // check if the player is attempting to sell back an item stolen from this actor for (const ItemStack& itemStack : merchantBought) { if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) { std::string msg = gmst.find("sNotifyMessage49")->mValue.getString(); msg = Misc::StringUtils::format(msg, itemStack.mBase.getClass().getName(itemStack.mBase)); MWBase::Environment::get().getWindowManager()->messageBox(msg); MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, itemStack.mBase, mPtr, itemStack.mCount); onCancelButtonClicked(mCancelButton); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } } bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); // apply disposition change if merchant is NPC if ( mPtr.getClass().isNpc() ) { int dispositionDelta = offerAccepted ? gmst.find("iBarterSuccessDisposition")->mValue.getInteger() : gmst.find("iBarterFailDisposition")->mValue.getInteger(); MWBase::Environment::get().getDialogueManager()->applyBarterDispositionChange(dispositionDelta); } // display message on haggle failure if ( !offerAccepted ) { MWBase::Environment::get().getWindowManager()-> messageBox("#{sNotifyMessage9}"); return; } // make the item transfer mTradeModel->transferItems(); playerItemModel->transferItems(); // transfer the gold if (mCurrentBalance != 0) { addOrRemoveGold(mCurrentBalance, player); mPtr.getClass().getCreatureStats(mPtr).setGoldPool( mPtr.getClass().getCreatureStats(mPtr).getGoldPool() - mCurrentBalance ); } eventTradeDone(); MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } void TradeWindow::onAccept(MyGUI::EditBox *sender) { onOfferButtonClicked(sender); // To do not spam onAccept() again and again MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { exit(); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } void TradeWindow::onMaxSaleButtonClicked(MyGUI::Widget* _sender) { mCurrentBalance = getMerchantGold(); updateLabels(); } void TradeWindow::addRepeatController(MyGUI::Widget *widget) { MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); MyGUI::ControllerRepeatClick* controller = static_cast(item); controller->eventRepeatClick += newDelegate(this, &TradeWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); } void TradeWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { addRepeatController(_sender); onIncreaseButtonTriggered(); } void TradeWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { addRepeatController(_sender); onDecreaseButtonTriggered(); } void TradeWindow::onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller) { if (widget == mIncreaseButton) onIncreaseButtonTriggered(); else if (widget == mDecreaseButton) onDecreaseButtonTriggered(); } void TradeWindow::onBalanceButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) { MyGUI::ControllerManager::getInstance().removeItem(_sender); } void TradeWindow::onBalanceValueChanged(int value) { int previousBalance = mCurrentBalance; // Entering a "-" sign inverts the buying/selling state mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; updateLabels(); if (mCurrentBalance == 0) mCurrentBalance = previousBalance; if (value != std::abs(value)) mTotalBalance->setValue(std::abs(value)); } void TradeWindow::onIncreaseButtonTriggered() { // prevent overflows, and prevent entering INT_MIN since abs(INT_MIN) is undefined if (mCurrentBalance == std::numeric_limits::max() || mCurrentBalance == std::numeric_limits::min()+1) return; if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; if (mCurrentBalance < 0) mCurrentBalance -= 1; else mCurrentBalance += 1; updateLabels(); } void TradeWindow::onDecreaseButtonTriggered() { if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; if (mCurrentBalance < 0) mCurrentBalance += 1; else mCurrentBalance -= 1; updateLabels(); } void TradeWindow::updateLabels() { MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); if (playerBorrowed.empty() && merchantBorrowed.empty()) { mCurrentBalance = 0; } if (mCurrentBalance < 0) { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); } else { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalSold}"); } mTotalBalance->setValue(std::abs(mCurrentBalance)); mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + MyGUI::utility::toString(getMerchantGold())); } void TradeWindow::updateOffer() { TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); int merchantOffer = 0; // The offered price must be capped at 75% of the base price to avoid exploits // connected to buying and selling the same item. // This value has been determined by researching the limitations of the vanilla formula // and may not be sufficient if getBarterOffer behavior has been changed. const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : playerBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Minimum buying price -- 75% of the base const int buyingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, true); merchantOffer -= std::max(cap, buyingPrice); } const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : merchantBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Maximum selling price -- 75% of the base const int sellingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, false); merchantOffer += mPtr.getClass().isNpc() ? std::min(cap, sellingPrice) : sellingPrice; } int diff = merchantOffer - mCurrentMerchantOffer; mCurrentMerchantOffer = merchantOffer; mCurrentBalance += diff; updateLabels(); } void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { updateOffer(); } void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { updateOffer(); } void TradeWindow::onReferenceUnavailable() { // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } int TradeWindow::getMerchantGold() { int merchantGold = mPtr.getClass().getCreatureStats(mPtr).getGoldPool(); return merchantGold; } void TradeWindow::resetReference() { ReferenceInterface::resetReference(); mItemView->setModel(nullptr); mTradeModel = nullptr; mSortModel = nullptr; } void TradeWindow::onClose() { // Make sure the window was actually closed and not temporarily hidden. if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Barter)) return; resetReference(); } void TradeWindow::onDeleteCustomData(const MWWorld::Ptr& ptr) { if(mTradeModel && mTradeModel->usesContainer(ptr)) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } } openmw-openmw-0.48.0/apps/openmw/mwgui/tradewindow.hpp000066400000000000000000000073411445372753700231010ustar00rootroot00000000000000#ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H #include "../mwmechanics/trading.hpp" #include "referenceinterface.hpp" #include "windowbase.hpp" namespace Gui { class NumericEditBox; } namespace MyGUI { class ControllerItem; } namespace MWGui { class ItemView; class SortFilterItemModel; class TradeItemModel; class TradeWindow : public WindowBase, public ReferenceInterface { public: TradeWindow(); void setPtr(const MWWorld::Ptr& actor) override; void onClose() override; void onFrame(float dt) override; void clear() override { resetReference(); } void borrowItem (int index, size_t count); void returnItem (int index, size_t count); int getMerchantServices(); bool exit() override; void resetReference() override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_TradeDone; EventHandle_TradeDone eventTradeDone; private: ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; MWMechanics::Trading mTrading; static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds MyGUI::Button* mFilterAll; MyGUI::Button* mFilterWeapon; MyGUI::Button* mFilterApparel; MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; MyGUI::EditBox* mFilterEdit; MyGUI::Button* mIncreaseButton; MyGUI::Button* mDecreaseButton; MyGUI::TextBox* mTotalBalanceLabel; Gui::NumericEditBox* mTotalBalance; MyGUI::Widget* mBottomPane; MyGUI::Button* mMaxSaleButton; MyGUI::Button* mCancelButton; MyGUI::Button* mOfferButton; MyGUI::TextBox* mPlayerGold; MyGUI::TextBox* mMerchantGold; int mItemToSell; int mCurrentBalance; int mCurrentMerchantOffer; void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance void updateOffer(); void onItemSelected (int index); void sellItem (MyGUI::Widget* sender, int count); void onFilterChanged(MyGUI::Widget* _sender); void onNameFilterChanged(MyGUI::EditBox* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox* sender); void onCancelButtonClicked(MyGUI::Widget* _sender); void onMaxSaleButtonClicked(MyGUI::Widget* _sender); void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onBalanceValueChanged(int value); void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); void addRepeatController(MyGUI::Widget* widget); void onIncreaseButtonTriggered(); void onDecreaseButtonTriggered(); void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); void updateLabels(); void onReferenceUnavailable() override; int getMerchantGold(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/trainingwindow.cpp000066400000000000000000000175701445372753700236150ustar00rootroot00000000000000#include "trainingwindow.hpp" #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include #include "tooltips.hpp" namespace { // Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID. // pair bool sortSkills (const std::pair& left, const std::pair& right) { if (left == right) return false; if (left.second > right.second) return true; else if (left.second < right.second) return false; return left.first < right.first; } } namespace MWGui { TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") , mTimeAdvancer(0.05f) , mTrainingSkillBasedOnBaseSkill(Settings::Manager::getBool("trainers training skills based on base skill", "Game")) { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); getWidget(mPlayerGold, "PlayerGold"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onCancelButtonClicked); mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &TrainingWindow::onTrainingProgressChanged); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &TrainingWindow::onTrainingFinished); } void TrainingWindow::onOpen() { if (mTimeAdvancer.isRunning()) { mProgressBar.setVisible(true); setVisible(false); } else mProgressBar.setVisible(false); center(); } void TrainingWindow::setPtr (const MWWorld::Ptr& actor) { mPtr = actor; MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); // NPC can train you in his best 3 skills std::vector< std::pair > skills; MWMechanics::NpcStats const& actorStats(actor.getClass().getNpcStats(actor)); for (int i=0; igetEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; for (int i=0; i<3; ++i) { int price = static_cast(pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger()); price = std::max(1, price); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip MyGUI::IntCoord(5, 5+i*lineHeight, mTrainingOptions->getWidth()-10, lineHeight), MyGUI::Align::Default); button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + MyGUI::utility::toString(price)); button->setSize(button->getTextSize ().width+12, button->getSize().height); ToolTips::createSkillToolTip (button, skills[i].first); } center(); } void TrainingWindow::onReferenceUnavailable () { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); } void TrainingWindow::onCancelButtonClicked (MyGUI::Widget *sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); } void TrainingWindow::onTrainingSelected (MyGUI::Widget *sender) { int skillId = *sender->getUserData(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->mValue.getInteger(); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; if (getSkillForTraining(mPtr.getClass().getNpcStats(mPtr), skillId) <= pcStats.getSkill(skillId).getBase()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); return; } // You can not train a skill above its governing attribute const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillId); if (pcStats.getSkill(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage17}"); return; } // increase skill MWWorld::LiveCellRef *playerRef = player.get(); const ESM::Class *class_ = store.get().find(playerRef->mBase->mClass); pcStats.increaseSkill (skillId, *class_, true); // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); // advance time MWBase::Environment::get().getMechanicsManager()->rest(2, false); MWBase::Environment::get().getWorld ()->advanceTime (2); setVisible(false); mProgressBar.setVisible(true); mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25, false, 0.25); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) { mProgressBar.setProgress(cur, total); } void TrainingWindow::onTrainingFinished() { mProgressBar.setVisible(false); // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } float TrainingWindow::getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const { if (mTrainingSkillBasedOnBaseSkill) return stats.getSkill(skillId).getBase(); return stats.getSkill(skillId).getModified(); } void TrainingWindow::onFrame(float dt) { checkReferenceAvailable(); mTimeAdvancer.onFrame(dt); } bool TrainingWindow::exit() { return !mTimeAdvancer.isRunning(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/trainingwindow.hpp000066400000000000000000000027221445372753700236130ustar00rootroot00000000000000#ifndef MWGUI_TRAININGWINDOW_H #define MWGUI_TRAININGWINDOW_H #include "windowbase.hpp" #include "referenceinterface.hpp" #include "timeadvancer.hpp" #include "waitdialog.hpp" namespace MWMechanics { class NpcStats; } namespace MWGui { class TrainingWindow : public WindowBase, public ReferenceInterface { public: TrainingWindow(); void onOpen() override; bool exit() override; void setPtr(const MWWorld::Ptr& actor) override; void onFrame(float dt) override; WindowBase* getProgressBar() { return &mProgressBar; } void clear() override { resetReference(); } protected: void onReferenceUnavailable() override; void onCancelButtonClicked (MyGUI::Widget* sender); void onTrainingSelected(MyGUI::Widget* sender); void onTrainingProgressChanged(int cur, int total); void onTrainingFinished(); // Retrieve the base skill value if the setting 'training skills based on base skill' is set; // otherwise returns the modified skill float getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const; MyGUI::Widget* mTrainingOptions; MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; WaitDialogProgressBar mProgressBar; TimeAdvancer mTimeAdvancer; bool mTrainingSkillBasedOnBaseSkill; //corresponds to the setting 'training skills based on base skill' }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/travelwindow.cpp000066400000000000000000000227641445372753700233000ustar00rootroot00000000000000#include "travelwindow.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/cellutils.hpp" #include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" namespace MWGui { TravelWindow::TravelWindow() : WindowBase("openmw_travel_window.layout") , mCurrentY(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mPlayerGold, "PlayerGold"); getWidget(mSelect, "Select"); getWidget(mDestinations, "Travel"); getWidget(mDestinationsView, "DestinationsView"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onCancelButtonClicked); mDestinations->setCoord(450/2-mDestinations->getTextSize().width/2, mDestinations->getTop(), mDestinations->getTextSize().width, mDestinations->getHeight()); mSelect->setCoord(8, mSelect->getTop(), mSelect->getTextSize().width, mSelect->getHeight()); } void TravelWindow::addDestination(const std::string& name, const ESM::Position &pos, bool interior) { int price; const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (!mPtr.getCell()->isExterior()) { price = gmst.find("fMagesGuildTravel")->mValue.getInteger(); } else { ESM::Position PlayerPos = player.getRefData().getPosition(); float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + pow(pos.pos[2] - PlayerPos.pos[2], 2)); float fTravelMult = gmst.find("fTravelMult")->mValue.getFloat(); if (fTravelMult != 0) price = static_cast(d / fTravelMult); else price = static_cast(d); } price = std::max(1, price); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); // Add price for the travelling followers std::set followers; MWWorld::ActionTeleport::getFollowers(player, followers, !interior); // Apply followers cost, unlike vanilla the first follower doesn't travel for free price *= 1 + static_cast(followers.size()); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default); toAdd->setEnabled(price <= playerGold); mCurrentY += lineHeight; if(interior) toAdd->setUserString("interior","y"); else toAdd->setUserString("interior","n"); toAdd->setUserString("price", std::to_string(price)); toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setSize(mDestinationsView->getWidth(),lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", name); toAdd->setUserData(pos); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onTravelButtonClick); } void TravelWindow::clearDestinations() { mDestinationsView->setViewOffset(MyGUI::IntPoint(0,0)); mCurrentY = 0; while (mDestinationsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0)); } void TravelWindow::setPtr(const MWWorld::Ptr& actor) { center(); mPtr = actor; clearDestinations(); std::vector transport; if (mPtr.getClass().isNpc()) transport = mPtr.get()->mBase->getTransport(); else if (mPtr.getType() == ESM::Creature::sRecordId) transport = mPtr.get()->mBase->getTransport(); for(unsigned int i = 0;igetExterior(cellIndex.x(), cellIndex.y()); cellname = MWBase::Environment::get().getWorld()->getCellName(cell); interior = false; } addDestination(cellname,transport[i].mPos,interior); } updateLabels(); // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mDestinationsView->setVisibleVScroll(false); mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); mDestinationsView->setVisibleVScroll(true); } void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender) { std::istringstream iss(_sender->getUserString("price")); int price; iss >> price; MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (playerGoldsetPlayerTraveling(true); if (!mPtr.getCell()->isExterior()) // Interior cell -> mages guild transport MWBase::Environment::get().getWindowManager()->playSound("mysticism cast"); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); bool interior = _sender->getUserString("interior") == "y"; if (mPtr.getCell()->isExterior()) { ESM::Position playerPos = player.getRefData().getPosition(); float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->mValue.getFloat()); MWBase::Environment::get().getMechanicsManager ()->rest (hours, true); MWBase::Environment::get().getWorld()->advanceTime(hours); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); // Teleports any followers, too. MWWorld::ActionTeleport action(interior ? cellname : "", pos, true); action.execute(player); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); } void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); } void TravelWindow::updateLabels() { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, mPlayerGold->getHeight()); } void TravelWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void TravelWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (mDestinationsView->getViewOffset().top + _rel*0.3f > 0) mDestinationsView->setViewOffset(MyGUI::IntPoint(0, 0)); else mDestinationsView->setViewOffset(MyGUI::IntPoint(0, static_cast(mDestinationsView->getViewOffset().top + _rel*0.3f))); } } openmw-openmw-0.48.0/apps/openmw/mwgui/travelwindow.hpp000066400000000000000000000020521445372753700232710ustar00rootroot00000000000000#ifndef MWGUI_TravelWINDOW_H #define MWGUI_TravelWINDOW_H #include "windowbase.hpp" #include "referenceinterface.hpp" namespace MyGUI { class Gui; class Widget; } namespace MWGui { class TravelWindow : public ReferenceInterface, public WindowBase { public: TravelWindow(); void setPtr (const MWWorld::Ptr& actor) override; protected: MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; MyGUI::TextBox* mDestinations; MyGUI::TextBox* mSelect; MyGUI::ScrollView* mDestinationsView; void onCancelButtonClicked(MyGUI::Widget* _sender); void onTravelButtonClick(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); void addDestination(const std::string& name, const ESM::Position& pos, bool interior); void clearDestinations(); int mCurrentY; void updateLabels(); void onReferenceUnavailable() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/videowidget.cpp000066400000000000000000000050121445372753700230500ustar00rootroot00000000000000#include "videowidget.hpp" #include #include #include #include #include #include #include "../mwsound/movieaudiofactory.hpp" namespace MWGui { VideoWidget::VideoWidget() : mVFS(nullptr) { mPlayer = std::make_unique(); setNeedKeyFocus(true); } VideoWidget::~VideoWidget() = default; void VideoWidget::setVFS(const VFS::Manager *vfs) { mVFS = vfs; } void VideoWidget::playVideo(const std::string &video) { mPlayer->setAudioFactory(new MWSound::MovieAudioFactory()); Files::IStreamPtr videoStream; try { videoStream = mVFS->get(video); } catch (std::exception& e) { Log(Debug::Error) << "Failed to open video: " << e.what(); return; } mPlayer->playVideo(std::move(videoStream), video); osg::ref_ptr texture = mPlayer->getVideoTexture(); if (!texture) return; mTexture = std::make_unique(texture); setRenderItemTexture(mTexture.get()); getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } int VideoWidget::getVideoWidth() { return mPlayer->getVideoWidth(); } int VideoWidget::getVideoHeight() { return mPlayer->getVideoHeight(); } bool VideoWidget::update() { return mPlayer->update(); } void VideoWidget::stop() { mPlayer->close(); } void VideoWidget::pause() { mPlayer->pause(); } void VideoWidget::resume() { mPlayer->play(); } bool VideoWidget::isPaused() const { return mPlayer->isPaused(); } bool VideoWidget::hasAudioStream() { return mPlayer->hasAudioStream(); } void VideoWidget::autoResize(bool stretch) { MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); if (getParent()) screenSize = getParent()->getSize(); if (getVideoHeight() > 0 && !stretch) { double imageaspect = static_cast(getVideoWidth())/getVideoHeight(); int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * imageaspect) / 2); int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / imageaspect) / 2); setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2); } else setCoord(0,0,screenSize.width,screenSize.height); } } openmw-openmw-0.48.0/apps/openmw/mwgui/videowidget.hpp000066400000000000000000000027671445372753700230730ustar00rootroot00000000000000#ifndef OPENMW_MWGUI_VIDEOWIDGET_H #define OPENMW_MWGUI_VIDEOWIDGET_H #include #include namespace Video { class VideoPlayer; } namespace VFS { class Manager; } namespace MWGui { /** * Widget that plays a video. */ class VideoWidget : public MyGUI::Widget { public: MYGUI_RTTI_DERIVED(VideoWidget) VideoWidget(); ~VideoWidget(); /// Set the VFS (virtual file system) to find the videos on. void setVFS(const VFS::Manager* vfs); void playVideo (const std::string& video); int getVideoWidth(); int getVideoHeight(); /// @return Is the video still playing? bool update(); /// Return true if a video is currently playing and it has an audio stream. bool hasAudioStream(); /// Stop video and free resources (done automatically on destruction) void stop(); void pause(); void resume(); bool isPaused() const; /// Adjust the coordinates of this video widget relative to its parent, /// based on the dimensions of the playing video. /// @param stretch Stretch the video to fill the whole screen? If false, /// black bars may be added to fix the aspect ratio. void autoResize (bool stretch); private: const VFS::Manager* mVFS; std::unique_ptr mTexture; std::unique_ptr mPlayer; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/waitdialog.cpp000066400000000000000000000311601445372753700226650ustar00rootroot00000000000000#include "waitdialog.hpp" #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWGui { WaitDialogProgressBar::WaitDialogProgressBar() : WindowBase("openmw_wait_dialog_progressbar.layout") { getWidget(mProgressBar, "ProgressBar"); getWidget(mProgressText, "ProgressText"); } void WaitDialogProgressBar::onOpen() { center(); } void WaitDialogProgressBar::setProgress (int cur, int total) { mProgressBar->setProgressRange (total); mProgressBar->setProgressPosition (cur); mProgressText->setCaption(MyGUI::utility::toString(cur) + "/" + MyGUI::utility::toString(total)); } // --------------------------------------------------------------------------------------------------------- WaitDialog::WaitDialog() : WindowBase("openmw_wait_dialog.layout") , mTimeAdvancer(0.05f) , mSleeping(false) , mHours(1) , mManualHours(1) , mFadeTimeRemaining(0) , mInterruptAt(-1) , mProgressBar() { getWidget(mDateTimeText, "DateTimeText"); getWidget(mRestText, "RestText"); getWidget(mHourText, "HourText"); getWidget(mUntilHealedButton, "UntilHealedButton"); getWidget(mWaitButton, "WaitButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mHourSlider, "HourSlider"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onCancelButtonClicked); mUntilHealedButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onUntilHealedButtonClicked); mWaitButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onWaitButtonClicked); mHourSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &WaitDialog::onHourSliderChangedPosition); mCancelButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); mWaitButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); mUntilHealedButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &WaitDialog::onKeyButtonPressed); mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &WaitDialog::onWaitingProgressChanged); mTimeAdvancer.eventInterrupted += MyGUI::newDelegate(this, &WaitDialog::onWaitingInterrupted); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); } void WaitDialog::setPtr(const MWWorld::Ptr &ptr) { setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == MWBase::World::Rest_Allowed); if (ptr.isEmpty() && MWBase::Environment::get().getWorld ()->canRest() == MWBase::World::Rest_PlayerIsInAir) { // Resting in air is not allowed unless you're using a bed MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); } if (mUntilHealedButton->getVisible()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mUntilHealedButton); else MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); } bool WaitDialog::exit() { bool canExit = !mTimeAdvancer.isRunning(); // Only exit if not currently waiting if (canExit) { clear(); stopWaiting(); } return canExit; } void WaitDialog::clear() { mSleeping = false; mHours = 1; mManualHours = 1; mFadeTimeRemaining = 0; mInterruptAt = -1; mTimeAdvancer.stop(); } void WaitDialog::onOpen() { if (mTimeAdvancer.isRunning()) { mProgressBar.setVisible(true); setVisible(false); return; } else { mProgressBar.setVisible(false); } if (!MWBase::Environment::get().getWindowManager ()->getRestEnabled ()) { MWBase::Environment::get().getWindowManager()->popGuiMode (); } MWBase::World::RestPermitted canRest = MWBase::Environment::get().getWorld ()->canRest (); if (canRest == MWBase::World::Rest_EnemiesAreNearby) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); MWBase::Environment::get().getWindowManager()->popGuiMode (); } else if (canRest == MWBase::World::Rest_PlayerIsUnderwater) { // resting underwater not allowed MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); MWBase::Environment::get().getWindowManager()->popGuiMode (); } onHourSliderChangedPosition(mHourSlider, 0); mHourSlider->setScrollPosition (0); std::string month = MWBase::Environment::get().getWorld ()->getMonthName(); int hour = static_cast(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); bool pm = hour >= 12; if (hour >= 13) hour -= 12; if (hour == 0) hour = 12; ESM::EpochTimeStamp currentDate = MWBase::Environment::get().getWorld()->getEpochTimeStamp(); std::string daysPassed = Misc::StringUtils::format("(#{sDay} %i)", MWBase::Environment::get().getWorld()->getTimeStamp().getDay()); std::string formattedHour(pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); std::string dateTimeText = Misc::StringUtils::format("%i %s %s %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour); mDateTimeText->setCaptionWithReplacing (dateTimeText); } void WaitDialog::onUntilHealedButtonClicked(MyGUI::Widget* sender) { int autoHours = MWBase::Environment::get().getMechanicsManager()->getHoursToRest(); startWaiting(autoHours); } void WaitDialog::onWaitButtonClicked(MyGUI::Widget* sender) { startWaiting(mManualHours); } void WaitDialog::startWaiting(int hoursToWait) { if(Settings::Manager::getBool("autosave","Saves")) //autosaves when enabled MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2f); mFadeTimeRemaining = 0.4f; setVisible(false); mHours = hoursToWait; // FIXME: move this somewhere else? mInterruptAt = -1; MWWorld::Ptr player = world->getPlayerPtr(); if (mSleeping && player.getCell()->isExterior()) { std::string regionstr = player.getCell()->getCell()->mRegion; if (!regionstr.empty()) { const ESM::Region *region = world->getStore().get().find (regionstr); if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping int x = Misc::Rng::rollDice(hoursToWait, world->getPrng()); float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->mValue.getFloat(); if (x < fSleepRandMod * hoursToWait) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->mValue.getFloat(); int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); if (interruptAtHoursRemaining != 0) { mInterruptAt = hoursToWait - interruptAtHoursRemaining; mInterruptCreatureList = region->mSleepList; } } } } } mProgressBar.setProgress (0, hoursToWait); } void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); } void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) { mHourText->setCaptionWithReplacing (MyGUI::utility::toString(position+1) + " #{sRestMenu2}"); mManualHours = position+1; MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); } void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1)); else if (key == MyGUI::KeyCode::ArrowDown) mHourSlider->setScrollPosition(std::max(static_cast(mHourSlider->getScrollPosition())-1, 0)); else return; onHourSliderChangedPosition(mHourSlider, mHourSlider->getScrollPosition()); } void WaitDialog::onWaitingProgressChanged(int cur, int total) { mProgressBar.setProgress(cur, total); MWBase::Environment::get().getMechanicsManager()->rest(1, mSleeping); MWBase::Environment::get().getWorld()->advanceTime(1); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (player.getClass().getCreatureStats(player).isDead()) stopWaiting(); } void WaitDialog::onWaitingInterrupted() { MWBase::Environment::get().getWindowManager()->messageBox("#{sSleepInterrupt}"); MWBase::Environment::get().getWorld()->spawnRandomCreature(mInterruptCreatureList); stopWaiting(); } void WaitDialog::onWaitingFinished() { stopWaiting(); MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); // trigger levelup if possible const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } } void WaitDialog::setCanRest (bool canRest) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); mUntilHealedButton->setVisible(canRest && !full); mWaitButton->setCaptionWithReplacing (canRest ? "#{sRest}" : "#{sWait}"); mRestText->setCaptionWithReplacing (canRest ? "#{sRestMenu3}" : (werewolf ? "#{sWerewolfRestMessage}" : "#{sRestIllegal}")); mSleeping = canRest; Gui::Box* box = dynamic_cast(mMainWidget); if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); center(); } void WaitDialog::onFrame(float dt) { mTimeAdvancer.onFrame(dt); if (mFadeTimeRemaining <= 0) return; mFadeTimeRemaining -= dt; if (mFadeTimeRemaining <= 0) { mProgressBar.setVisible(true); mTimeAdvancer.run(mHours, mInterruptAt); } } void WaitDialog::stopWaiting () { MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f); mProgressBar.setVisible (false); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); mTimeAdvancer.stop(); } void WaitDialog::wakeUp () { mSleeping = false; if (mInterruptAt != -1) onWaitingInterrupted(); else stopWaiting(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/waitdialog.hpp000066400000000000000000000040631445372753700226740ustar00rootroot00000000000000#ifndef MWGUI_WAIT_DIALOG_H #define MWGUI_WAIT_DIALOG_H #include "timeadvancer.hpp" #include "windowbase.hpp" namespace MWGui { class WaitDialogProgressBar : public WindowBase { public: WaitDialogProgressBar(); void onOpen() override; void setProgress(int cur, int total); protected: MyGUI::ProgressBar* mProgressBar; MyGUI::TextBox* mProgressText; }; class WaitDialog : public WindowBase { public: WaitDialog(); void setPtr(const MWWorld::Ptr &ptr) override; void onOpen() override; bool exit() override; void clear() override; void onFrame(float dt) override; bool getSleeping() { return mTimeAdvancer.isRunning() && mSleeping; } void wakeUp(); void autosave(); WindowBase* getProgressBar() { return &mProgressBar; } protected: MyGUI::TextBox* mDateTimeText; MyGUI::TextBox* mRestText; MyGUI::TextBox* mHourText; MyGUI::Button* mUntilHealedButton; MyGUI::Button* mWaitButton; MyGUI::Button* mCancelButton; MyGUI::ScrollBar* mHourSlider; TimeAdvancer mTimeAdvancer; bool mSleeping; int mHours; int mManualHours; // stores the hours to rest selected via slider float mFadeTimeRemaining; int mInterruptAt; std::string mInterruptCreatureList; WaitDialogProgressBar mProgressBar; void onUntilHealedButtonClicked(MyGUI::Widget* sender); void onWaitButtonClicked(MyGUI::Widget* sender); void onCancelButtonClicked(MyGUI::Widget* sender); void onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position); void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); void onWaitingProgressChanged(int cur, int total); void onWaitingInterrupted(); void onWaitingFinished(); void setCanRest(bool canRest); void startWaiting(int hoursToWait); void stopWaiting(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/widgets.cpp000066400000000000000000000500461445372753700222130ustar00rootroot00000000000000#include "widgets.hpp" #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/esmstore.hpp" #include "controllers.hpp" namespace MWGui::Widgets { /* MWSkill */ MWSkill::MWSkill() : mSkillId(ESM::Skill::Length) , mSkillNameWidget(nullptr) , mSkillValueWidget(nullptr) { } void MWSkill::setSkillId(ESM::Skill::SkillEnum skill) { mSkillId = skill; updateWidgets(); } void MWSkill::setSkillNumber(int skill) { if (skill < 0) setSkillId(ESM::Skill::Length); else if (skill < ESM::Skill::Length) setSkillId(static_cast(skill)); else throw std::runtime_error("Skill number out of range"); } void MWSkill::setSkillValue(const SkillValue& value) { mValue = value; updateWidgets(); } void MWSkill::updateWidgets() { if (mSkillNameWidget) { if (mSkillId == ESM::Skill::Length) { mSkillNameWidget->setCaption(""); } else { const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mSkillId], ""); mSkillNameWidget->setCaption(name); } } if (mSkillValueWidget) { SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); mSkillValueWidget->setCaption(MyGUI::utility::toString(modified)); if (modified > base) mSkillValueWidget->_setWidgetState("increased"); else if (modified < base) mSkillValueWidget->_setWidgetState("decreased"); else mSkillValueWidget->_setWidgetState("normal"); } } void MWSkill::onClicked(MyGUI::Widget* _sender) { eventClicked(this); } MWSkill::~MWSkill() { } void MWSkill::initialiseOverride() { Base::initialiseOverride(); assignWidget(mSkillNameWidget, "StatName"); assignWidget(mSkillValueWidget, "StatValue"); MyGUI::Button* button; assignWidget(button, "StatNameButton"); if (button) { mSkillNameWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } button = nullptr; assignWidget(button, "StatValueButton"); if (button) { mSkillValueWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } } /* MWAttribute */ MWAttribute::MWAttribute() : mId(-1) , mAttributeNameWidget(nullptr) , mAttributeValueWidget(nullptr) { } void MWAttribute::setAttributeId(int attributeId) { mId = attributeId; updateWidgets(); } void MWAttribute::setAttributeValue(const AttributeValue& value) { mValue = value; updateWidgets(); } void MWAttribute::onClicked(MyGUI::Widget* _sender) { eventClicked(this); } void MWAttribute::updateWidgets() { if (mAttributeNameWidget) { if (mId < 0 || mId >= 8) { mAttributeNameWidget->setCaption(""); } else { static const char *attributes[8] = { "sAttributeStrength", "sAttributeIntelligence", "sAttributeWillpower", "sAttributeAgility", "sAttributeSpeed", "sAttributeEndurance", "sAttributePersonality", "sAttributeLuck" }; const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(attributes[mId], ""); mAttributeNameWidget->setCaption(name); } } if (mAttributeValueWidget) { int modified = mValue.getModified(), base = mValue.getBase(); mAttributeValueWidget->setCaption(MyGUI::utility::toString(modified)); if (modified > base) mAttributeValueWidget->_setWidgetState("increased"); else if (modified < base) mAttributeValueWidget->_setWidgetState("decreased"); else mAttributeValueWidget->_setWidgetState("normal"); } } MWAttribute::~MWAttribute() { } void MWAttribute::initialiseOverride() { Base::initialiseOverride(); assignWidget(mAttributeNameWidget, "StatName"); assignWidget(mAttributeValueWidget, "StatValue"); MyGUI::Button* button; assignWidget(button, "StatNameButton"); if (button) { mAttributeNameWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } button = nullptr; assignWidget(button, "StatValueButton"); if (button) { mAttributeValueWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } } /* MWSpell */ MWSpell::MWSpell() : mSpellNameWidget(nullptr) { } void MWSpell::setSpellId(const std::string &spellId) { mId = spellId; updateWidgets(); } void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) { MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; params.mEffectID = effectInfo.mEffectID; params.mSkill = effectInfo.mSkill; params.mAttribute = effectInfo.mAttribute; params.mDuration = effectInfo.mDuration; params.mMagnMin = effectInfo.mMagnMin; params.mMagnMax = effectInfo.mMagnMax; params.mRange = effectInfo.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); effect->setSpellEffect(params); effects.push_back(effect); coord.top += effect->getHeight(); coord.width = std::max(coord.width, effect->getRequestedWidth()); } } void MWSpell::updateWidgets() { if (mSpellNameWidget && MWBase::Environment::get().getWindowManager()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().search(mId); if (spell) mSpellNameWidget->setCaption(spell->mName); else mSpellNameWidget->setCaption(""); } } void MWSpell::initialiseOverride() { Base::initialiseOverride(); assignWidget(mSpellNameWidget, "StatName"); } MWSpell::~MWSpell() { } /* MWEffectList */ MWEffectList::MWEffectList() : mEffectList(0) { } void MWEffectList::setEffectList(const SpellEffectList& list) { mEffectList = list; updateWidgets(); } void MWEffectList::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags) { // We don't know the width of all the elements beforehand, so we do it in // 2 steps: first, create all widgets and check their width.... MWSpellEffectPtr effect = nullptr; int maxwidth = coord.width; for (auto& effectInfo : mEffectList) { effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); effectInfo.mIsConstant = (flags & EF_Constant) || effectInfo.mIsConstant; effectInfo.mNoTarget = (flags & EF_NoTarget) || effectInfo.mNoTarget; effectInfo.mNoMagnitude = (flags & EF_NoMagnitude) || effectInfo.mNoMagnitude; effect->setSpellEffect(effectInfo); effects.push_back(effect); if (effect->getRequestedWidth() > maxwidth) maxwidth = effect->getRequestedWidth(); coord.top += effect->getHeight(); } // ... then adjust the size for all widgets for (MyGUI::Widget* effectWidget : effects) { effect = effectWidget->castType(); bool needcenter = center && (maxwidth > effect->getRequestedWidth()); int diff = maxwidth - effect->getRequestedWidth(); if (needcenter) { effect->setCoord(diff/2, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); } else { effect->setCoord(0, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); } } // inform the parent about width coord.width = maxwidth; } void MWEffectList::updateWidgets() { } void MWEffectList::initialiseOverride() { Base::initialiseOverride(); } MWEffectList::~MWEffectList() { } SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) { SpellEffectList result; for (const ESM::ENAMstruct& effectInfo : effects->mList) { SpellEffectParams params; params.mEffectID = effectInfo.mEffectID; params.mSkill = effectInfo.mSkill; params.mAttribute = effectInfo.mAttribute; params.mDuration = effectInfo.mDuration; params.mMagnMin = effectInfo.mMagnMin; params.mMagnMax = effectInfo.mMagnMax; params.mRange = effectInfo.mRange; params.mArea = effectInfo.mArea; result.push_back(params); } return result; } /* MWSpellEffect */ MWSpellEffect::MWSpellEffect() : mImageWidget(nullptr) , mTextWidget(nullptr) , mRequestedWidth(0) { } void MWSpellEffect::setSpellEffect(const SpellEffectParams& params) { mEffectParams = params; updateWidgets(); } void MWSpellEffect::updateWidgets() { if (!mEffectParams.mKnown) { mTextWidget->setCaption ("?"); mTextWidget->setCoord(sIconOffset / 2, mTextWidget->getCoord().top, mTextWidget->getCoord().width, mTextWidget->getCoord().height); // Compensates for the missing image when effect is not known mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset; mImageWidget->setImageTexture (""); return; } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *magicEffect = store.get().search(mEffectParams.mEffectID); assert(magicEffect); std::string pt = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", ""); std::string pts = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", ""); std::string pct = MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); std::string ft = MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", ""); std::string lvl = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", ""); std::string lvls = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", ""); std::string to = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "") + " "; std::string sec = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("ssecond", ""); std::string secs = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sseconds", ""); std::string effectIDStr = ESM::MagicEffect::effectIdToString(mEffectParams.mEffectID); std::string spellLine = MWBase::Environment::get().getWindowManager()->getGameSettingString(effectIDStr, ""); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && mEffectParams.mSkill != -1) { spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mEffectParams.mSkill], ""); } if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && mEffectParams.mAttribute != -1) { spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Attribute::sGmstAttributeIds[mEffectParams.mAttribute], ""); } if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) { ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); if ( displayType == ESM::MagicEffect::MDT_TimesInt ) { std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); std::stringstream formatter; formatter << std::fixed << std::setprecision(1) << " " << (mEffectParams.mMagnMin / 10.0f); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) formatter << to << (mEffectParams.mMagnMax / 10.0f); formatter << timesInt; spellLine += formatter.str(); } else if ( displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) { spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) spellLine += to + MyGUI::utility::toString(mEffectParams.mMagnMax); if ( displayType == ESM::MagicEffect::MDT_Percentage ) spellLine += pct; else if ( displayType == ESM::MagicEffect::MDT_Feet ) spellLine += " " + ft; else if ( displayType == ESM::MagicEffect::MDT_Level ) spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? lvl : lvls ); else // ESM::MagicEffect::MDT_Points spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? pt : pts ); } } // constant effects have no duration and no target if (!mEffectParams.mIsConstant) { if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) mEffectParams.mDuration = std::max(1, mEffectParams.mDuration); if (mEffectParams.mDuration > 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) { spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + MyGUI::utility::toString(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); } if (mEffectParams.mArea > 0) { spellLine += " #{sin} " + MyGUI::utility::toString(mEffectParams.mArea) + " #{sfootarea}"; } // potions have no target if (!mEffectParams.mNoTarget) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sonword", ""); if (mEffectParams.mRange == ESM::RT_Self) spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeSelf", ""); else if (mEffectParams.mRange == ESM::RT_Touch) spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTouch", ""); else if (mEffectParams.mRange == ESM::RT_Target) spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTarget", ""); } } mTextWidget->setCaptionWithReplacing(spellLine); mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset; mImageWidget->setImageTexture(Misc::ResourceHelpers::correctIconPath(magicEffect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); } MWSpellEffect::~MWSpellEffect() { } void MWSpellEffect::initialiseOverride() { Base::initialiseOverride(); assignWidget(mTextWidget, "Text"); assignWidget(mImageWidget, "Image"); } /* MWDynamicStat */ MWDynamicStat::MWDynamicStat() : mValue(0) , mMax(1) , mTextWidget(nullptr) , mBarWidget(nullptr) , mBarTextWidget(nullptr) { } void MWDynamicStat::setValue(int cur, int max) { mValue = cur; mMax = max; if (mBarWidget) { mBarWidget->setProgressRange(std::max(0, mMax)); mBarWidget->setProgressPosition(std::max(0, mValue)); } if (mBarTextWidget) { std::stringstream out; out << mValue << "/" << mMax; mBarTextWidget->setCaption(out.str().c_str()); } } void MWDynamicStat::setTitle(const std::string& text) { if (mTextWidget) mTextWidget->setCaption(text); } MWDynamicStat::~MWDynamicStat() { } void MWDynamicStat::initialiseOverride() { Base::initialiseOverride(); assignWidget(mTextWidget, "Text"); assignWidget(mBarWidget, "Bar"); assignWidget(mBarTextWidget, "BarText"); } } openmw-openmw-0.48.0/apps/openmw/mwgui/widgets.hpp000066400000000000000000000224761445372753700222260ustar00rootroot00000000000000#ifndef MWGUI_WIDGETS_H #define MWGUI_WIDGETS_H #include "../mwmechanics/stat.hpp" #include #include #include #include #include namespace MyGUI { class ImageBox; class ControllerItem; } namespace MWBase { class WindowManager; } /* This file contains various custom widgets used in OpenMW. */ namespace MWGui { namespace Widgets { class MWEffectList; void fixTexturePath(std::string &path); struct SpellEffectParams { SpellEffectParams() : mNoTarget(false) , mIsConstant(false) , mNoMagnitude(false) , mKnown(true) , mEffectID(-1) , mSkill(-1) , mAttribute(-1) , mMagnMin(-1) , mMagnMax(-1) , mRange(-1) , mDuration(-1) , mArea(0) { } bool mNoTarget; // potion effects for example have no target (target is always the player) bool mIsConstant; // constant effect means that duration will not be displayed bool mNoMagnitude; // effect magnitude will not be displayed (e.g ingredients) bool mKnown; // is this effect known to the player? (If not, will display as a question mark instead) // value of -1 here means the effect is unknown to the player short mEffectID; // value of -1 here means there is no skill/attribute signed char mSkill, mAttribute; // value of -1 here means the value is unavailable int mMagnMin, mMagnMax, mRange, mDuration; // value of 0 -> no area effect int mArea; bool operator==(const SpellEffectParams& other) const { if (mEffectID != other.mEffectID) return false; bool involvesAttribute = (mEffectID == 74 // restore attribute || mEffectID == 85 // absorb attribute || mEffectID == 17 // drain attribute || mEffectID == 79 // fortify attribute || mEffectID == 22); // damage attribute bool involvesSkill = (mEffectID == 78 // restore skill || mEffectID == 89 // absorb skill || mEffectID == 21 // drain skill || mEffectID == 83 // fortify skill || mEffectID == 26); // damage skill return ((other.mSkill == mSkill) || !involvesSkill) && ((other.mAttribute == mAttribute) && !involvesAttribute) && (other.mArea == mArea); } }; typedef std::vector SpellEffectList; class MWSkill final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSkill ) public: MWSkill(); typedef MWMechanics::Stat SkillValue; void setSkillId(ESM::Skill::SkillEnum skillId); void setSkillNumber(int skillId); void setSkillValue(const SkillValue& value); ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } const SkillValue& getSkillValue() const { return mValue; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_SkillVoid; /** Event : Skill clicked.\n signature : void method(MWSkill* _sender)\n */ EventHandle_SkillVoid eventClicked; protected: virtual ~MWSkill(); void initialiseOverride() override; void onClicked(MyGUI::Widget* _sender); private: void updateWidgets(); ESM::Skill::SkillEnum mSkillId; SkillValue mValue; MyGUI::TextBox* mSkillNameWidget; MyGUI::TextBox* mSkillValueWidget; }; typedef MWSkill* MWSkillPtr; class MWAttribute final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWAttribute ) public: MWAttribute(); typedef MWMechanics::AttributeValue AttributeValue; void setAttributeId(int attributeId); void setAttributeValue(const AttributeValue& value); int getAttributeId() const { return mId; } const AttributeValue& getAttributeValue() const { return mValue; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_AttributeVoid; /** Event : Attribute clicked.\n signature : void method(MWAttribute* _sender)\n */ EventHandle_AttributeVoid eventClicked; protected: virtual ~MWAttribute(); void initialiseOverride() override; void onClicked(MyGUI::Widget* _sender); private: void updateWidgets(); int mId; AttributeValue mValue; MyGUI::TextBox* mAttributeNameWidget; MyGUI::TextBox* mAttributeValueWidget; }; typedef MWAttribute* MWAttributePtr; /** * @todo remove this class and use MWEffectList instead */ class MWSpellEffect; class MWSpell final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpell ) public: MWSpell(); void setSpellId(const std::string &id); /** * @param vector to store the created effect widgets * @param parent widget * @param coordinates to use, will be expanded if more space is needed * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. duration * @param various flags, see MWEffectList::EffectFlags */ void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags); const std::string &getSpellId() const { return mId; } protected: virtual ~MWSpell(); void initialiseOverride() override; private: void updateWidgets(); std::string mId; MyGUI::TextBox* mSpellNameWidget; }; typedef MWSpell* MWSpellPtr; class MWEffectList final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWEffectList ) public: MWEffectList(); enum EffectFlags { EF_NoTarget = 0x01, // potions have no target (target is always the player) EF_Constant = 0x02, // constant effect means that duration will not be displayed EF_NoMagnitude = 0x04 // ingredients have no magnitude }; void setEffectList(const SpellEffectList& list); static SpellEffectList effectListFromESM(const ESM::EffectList* effects); /** * @param vector to store the created effect widgets * @param parent widget * @param coordinates to use, will be expanded if more space is needed * @param center the effect widgets horizontally * @param various flags, see MWEffectList::EffectFlags */ void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags); protected: virtual ~MWEffectList(); void initialiseOverride() override; private: void updateWidgets(); SpellEffectList mEffectList; }; typedef MWEffectList* MWEffectListPtr; class MWSpellEffect final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpellEffect ) public: MWSpellEffect(); typedef ESM::ENAMstruct SpellEffectValue; void setSpellEffect(const SpellEffectParams& params); int getRequestedWidth() const { return mRequestedWidth; } protected: virtual ~MWSpellEffect(); void initialiseOverride() override; private: static constexpr int sIconOffset = 24; void updateWidgets(); SpellEffectParams mEffectParams; MyGUI::ImageBox* mImageWidget; MyGUI::TextBox* mTextWidget; int mRequestedWidth; }; typedef MWSpellEffect* MWSpellEffectPtr; class MWDynamicStat final : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWDynamicStat ) public: MWDynamicStat(); void setValue(int value, int max); void setTitle(const std::string& text); int getValue() const { return mValue; } int getMax() const { return mMax; } protected: virtual ~MWDynamicStat(); void initialiseOverride() override; private: int mValue, mMax; MyGUI::TextBox* mTextWidget; MyGUI::ProgressBar* mBarWidget; MyGUI::TextBox* mBarTextWidget; }; typedef MWDynamicStat* MWDynamicStatPtr; } } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/windowbase.cpp000066400000000000000000000103331445372753700227020ustar00rootroot00000000000000#include "windowbase.hpp" #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include #include "draganddrop.hpp" #include "exposedwindow.hpp" using namespace MWGui; WindowBase::WindowBase(std::string_view parLayout) : Layout(parLayout) { mMainWidget->setVisible(false); Window* window = mMainWidget->castType(false); if (!window) return; MyGUI::Button* button = nullptr; MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); for (MyGUI::Widget* widget : widgets) { if (widget->isUserString("SupportDoubleClick")) button = widget->castType(); } if (button) button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowBase::onDoubleClick); } void WindowBase::onTitleDoubleClicked() { if (MyGUI::InputManager::getInstance().isShiftPressed()) MWBase::Environment::get().getWindowManager()->toggleMaximized(this); } void WindowBase::onDoubleClick(MyGUI::Widget *_sender) { onTitleDoubleClicked(); } void WindowBase::setVisible(bool visible) { bool wasVisible = mMainWidget->getVisible(); mMainWidget->setVisible(visible); if (visible) onOpen(); else if (wasVisible) onClose(); } bool WindowBase::isVisible() { return mMainWidget->getVisible(); } void WindowBase::center() { // Centre dialog MyGUI::IntSize layerSize = MyGUI::RenderManager::getInstance().getViewSize(); if (mMainWidget->getLayer()) layerSize = mMainWidget->getLayer()->getSize(); MyGUI::IntCoord coord = mMainWidget->getCoord(); coord.left = (layerSize.width - coord.width)/2; coord.top = (layerSize.height - coord.height)/2; mMainWidget->setCoord(coord); } WindowModal::WindowModal(const std::string& parLayout) : WindowBase(parLayout) { } void WindowModal::onOpen() { MWBase::Environment::get().getWindowManager()->addCurrentModal(this); //Set so we can escape it if needed MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget); MyGUI::InputManager::getInstance().setKeyFocusWidget(focus); } void WindowModal::onClose() { MWBase::Environment::get().getWindowManager()->removeCurrentModal(this); MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); } NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) : mWidget(widget), mDrag(drag), mTransparent(false) { } void NoDrop::onFrame(float dt) { if (!mWidget) return; MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition(); if (mDrag->mIsOnDragAndDrop) { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); while (focus && focus != mWidget) focus = focus->getParent(); if (focus == mWidget) mTransparent = true; } if (!mWidget->getAbsoluteCoord().inside(mousePos)) mTransparent = false; if (mTransparent) { mWidget->setNeedMouseFocus(false); // Allow click-through setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); } else { mWidget->setNeedMouseFocus(true); setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); } } void NoDrop::setAlpha(float alpha) { if (mWidget) mWidget->setAlpha(alpha); } BookWindowBase::BookWindowBase(std::string_view parLayout) : WindowBase(parLayout) { } float BookWindowBase::adjustButton(std::string_view name) { Gui::ImageButton* button; WindowBase::getWidget (button, name); MyGUI::IntSize requested = button->getRequestedSize(); float scale = float(requested.height) / button->getSize().height; MyGUI::IntSize newSize = requested; newSize.width /= scale; newSize.height /= scale; button->setSize(newSize); if (button->getAlign().isRight()) { MyGUI::IntSize diff = (button->getSize() - requested); diff.width /= scale; diff.height /= scale; button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); } return scale; } openmw-openmw-0.48.0/apps/openmw/mwgui/windowbase.hpp000066400000000000000000000050011445372753700227030ustar00rootroot00000000000000#ifndef MWGUI_WINDOW_BASE_H #define MWGUI_WINDOW_BASE_H #include "layout.hpp" namespace MWWorld { class Ptr; } namespace MWGui { class DragAndDrop; class WindowBase: public Layout { public: WindowBase(std::string_view parLayout); virtual MyGUI::Widget* getDefaultKeyFocus() { return nullptr; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; /// Open this object in the GUI, for windows that support it virtual void setPtr(const MWWorld::Ptr& ptr) {} /// Called every frame if the window is in an active GUI mode virtual void onFrame(float duration) {} /// Notify that window has been made visible virtual void onOpen() {} /// Notify that window has been hidden virtual void onClose () {} /// Gracefully exits the window virtual bool exit() {return true;} /// Sets the visibility of the window void setVisible(bool visible) override; /// Returns the visibility state of the window bool isVisible(); void center(); /// Clear any state specific to the running game virtual void clear() {} /// Called when GUI viewport changes size virtual void onResChange(int width, int height) {} virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) {} protected: virtual void onTitleDoubleClicked(); private: void onDoubleClick(MyGUI::Widget* _sender); }; /* * "Modal" windows cause the rest of the interface to be inaccessible while they are visible */ class WindowModal : public WindowBase { public: WindowModal(const std::string& parLayout); void onOpen() override; void onClose() override; bool exit() override {return true;} }; /// A window that cannot be the target of a drag&drop action. /// When hovered with a drag item, the window will become transparent and allow click-through. class NoDrop { public: NoDrop(DragAndDrop* drag, MyGUI::Widget* widget); void onFrame(float dt); virtual void setAlpha(float alpha); virtual ~NoDrop() = default; private: MyGUI::Widget* mWidget; DragAndDrop* mDrag; bool mTransparent; }; class BookWindowBase : public WindowBase { public: BookWindowBase(std::string_view parLayout); protected: float adjustButton(std::string_view name); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/windowmanagerimp.cpp000066400000000000000000002351221445372753700241150ustar00rootroot00000000000000#include "windowmanagerimp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include // For BT_NO_PROFILE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwrender/vismask.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwrender/localmap.hpp" #include "../mwrender/postprocessor.hpp" #include "console.hpp" #include "journalwindow.hpp" #include "journalviewmodel.hpp" #include "charactercreation.hpp" #include "dialogue.hpp" #include "statswindow.hpp" #include "messagebox.hpp" #include "tooltips.hpp" #include "scrollwindow.hpp" #include "bookwindow.hpp" #include "hud.hpp" #include "mainmenu.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" #include "spellbuyingwindow.hpp" #include "travelwindow.hpp" #include "settingswindow.hpp" #include "confirmationdialog.hpp" #include "alchemywindow.hpp" #include "spellwindow.hpp" #include "quickkeysmenu.hpp" #include "loadingscreen.hpp" #include "levelupdialog.hpp" #include "waitdialog.hpp" #include "enchantingdialog.hpp" #include "trainingwindow.hpp" #include "recharge.hpp" #include "exposedwindow.hpp" #include "cursor.hpp" #include "merchantrepair.hpp" #include "repair.hpp" #include "soulgemdialog.hpp" #include "companionwindow.hpp" #include "inventorywindow.hpp" #include "bookpage.hpp" #include "itemview.hpp" #include "videowidget.hpp" #include "backgroundimage.hpp" #include "itemwidget.hpp" #include "screenfader.hpp" #include "debugwindow.hpp" #include "postprocessorhud.hpp" #include "spellview.hpp" #include "draganddrop.hpp" #include "container.hpp" #include "controllers.hpp" #include "jailscreen.hpp" #include "itemchargeview.hpp" #include "keyboardnavigation.hpp" #include "resourceskin.hpp" namespace MWGui { WindowManager::WindowManager( SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders) : mOldUpdateMask(0) , mOldCullMask(0) , mStore(nullptr) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mViewer(viewer) , mConsoleOnlyScripts(consoleOnlyScripts) , mCurrentModals() , mHud(nullptr) , mMap(nullptr) , mLocalMapRender(nullptr) , mToolTips(nullptr) , mStatsWindow(nullptr) , mMessageBoxManager(nullptr) , mConsole(nullptr) , mDialogueWindow(nullptr) , mDragAndDrop(nullptr) , mInventoryWindow(nullptr) , mScrollWindow(nullptr) , mBookWindow(nullptr) , mCountDialog(nullptr) , mTradeWindow(nullptr) , mSettingsWindow(nullptr) , mConfirmationDialog(nullptr) , mSpellWindow(nullptr) , mQuickKeysMenu(nullptr) , mLoadingScreen(nullptr) , mWaitDialog(nullptr) , mSoulgemDialog(nullptr) , mVideoBackground(nullptr) , mVideoWidget(nullptr) , mWerewolfFader(nullptr) , mBlindnessFader(nullptr) , mHitFader(nullptr) , mScreenFader(nullptr) , mDebugWindow(nullptr) , mPostProcessorHud(nullptr) , mJailScreen(nullptr) , mContainerWindow(nullptr) , mTranslationDataStorage (translationDataStorage) , mCharGen(nullptr) , mInputBlocker(nullptr) , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHitFaderEnabled(Settings::Manager::getBool ("hit fader", "GUI")) , mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI")) , mHudEnabled(true) , mCursorVisible(true) , mCursorActive(true) , mPlayerBounty(-1) , mGui(nullptr) , mGuiModes() , mCursorManager(nullptr) , mGarbageDialogs() , mShown(GW_ALL) , mForceHidden(GW_None) , mAllowed(GW_ALL) , mRestAllowed(true) , mShowOwned(0) , mEncoding(encoding) , mVersionDescription(versionDescription) , mWindowVisible(true) { mScalingFactor = std::clamp(Settings::Manager::getFloat("scaling factor", "GUI"), 0.5f, 8.f); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), resourceSystem->getVFS(), mScalingFactor, "mygui", (boost::filesystem::path(logpath) / "MyGUI.log").generic_string()); mGui = new MyGUI::Gui; mGui->initialise(""); createTextures(); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts mFontLoader = std::make_unique(encoding, resourceSystem->getVFS(), mScalingFactor); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); BookPage::registerMyGUIComponents(); PostProcessorHud::registerMyGUIComponents(); ItemView::registerComponents(); ItemChargeView::registerComponents(); ItemWidget::registerComponents(); SpellView::registerComponents(); Gui::registerAllWidgets(); LuaUi::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "AutoSizedResourceSkin"); MyGUI::ResourceManager::getInstance().load("core.xml"); bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI"); mKeyboardNavigation = std::make_unique(); mKeyboardNavigation->setEnabled(keyboardNav); Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); mLoadingScreen = new LoadingScreen(mResourceSystem, mViewer); mWindows.push_back(mLoadingScreen); //set up the hardware cursor manager mCursorManager = new SDLUtil::SDLCursorManager(); MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); // Create all cursors in advance createCursors(); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); mCursorManager->setEnabled(true); // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default, "Video"); mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); mVideoBackground->setNeedKeyFocus(true); mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); mVideoWidget->setNeedMouseFocus(true); mVideoWidget->setNeedKeyFocus(true); mVideoWidget->setVFS(resourceSystem->getVFS()); // Removes default MyGUI system clipboard implementation, which supports windows only MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); mShowOwned = Settings::Manager::getInt("show owned", "Game"); mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer); mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); if (useShaders) mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); mStatsWatcher = std::make_unique(); } void WindowManager::initUI() { // Get size info from the Gui object int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; mTextColours.loadColours(); mDragAndDrop = new DragAndDrop(); Recharge* recharge = new Recharge(); mGuiModeStates[GM_Recharge] = GuiModeState(recharge); mWindows.push_back(recharge); MainMenu* menu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); mGuiModeStates[GM_MainMenu] = GuiModeState(menu); mWindows.push_back(menu); mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup()); mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue); mWindows.push_back(mMap); mMap->renderGlobalMap(); trackWindow(mMap, "map"); mStatsWindow = new StatsWindow(mDragAndDrop); mWindows.push_back(mStatsWindow); trackWindow(mStatsWindow, "stats"); mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer->getSceneData()->asGroup(), mResourceSystem); mWindows.push_back(mInventoryWindow); mSpellWindow = new SpellWindow(mDragAndDrop); mWindows.push_back(mSpellWindow); trackWindow(mSpellWindow, "spells"); mGuiModeStates[GM_Inventory] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); mGuiModeStates[GM_None] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); mTradeWindow = new TradeWindow(); mWindows.push_back(mTradeWindow); trackWindow(mTradeWindow, "barter"); mGuiModeStates[GM_Barter] = GuiModeState({mInventoryWindow, mTradeWindow}); mConsole = new Console(w,h, mConsoleOnlyScripts); mWindows.push_back(mConsole); trackWindow(mConsole, "console"); bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding); mWindows.push_back(journal); mGuiModeStates[GM_Journal] = GuiModeState(journal); mGuiModeStates[GM_Journal].mCloseSound = "book close"; mGuiModeStates[GM_Journal].mOpenSound = "book open"; mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->mValue.getFloat()); SpellBuyingWindow* spellBuyingWindow = new SpellBuyingWindow(); mWindows.push_back(spellBuyingWindow); mGuiModeStates[GM_SpellBuying] = GuiModeState(spellBuyingWindow); TravelWindow* travelWindow = new TravelWindow(); mWindows.push_back(travelWindow); mGuiModeStates[GM_Travel] = GuiModeState(travelWindow); mDialogueWindow = new DialogueWindow(); mWindows.push_back(mDialogueWindow); trackWindow(mDialogueWindow, "dialogue"); mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); mContainerWindow = new ContainerWindow(mDragAndDrop); mWindows.push_back(mContainerWindow); trackWindow(mContainerWindow, "container"); mGuiModeStates[GM_Container] = GuiModeState({mContainerWindow, mInventoryWindow}); mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mWindows.push_back(mHud); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mWindows.push_back(mScrollWindow); mGuiModeStates[GM_Scroll] = GuiModeState(mScrollWindow); mGuiModeStates[GM_Scroll].mOpenSound = "scroll"; mGuiModeStates[GM_Scroll].mCloseSound = "scroll"; mBookWindow = new BookWindow(); mWindows.push_back(mBookWindow); mGuiModeStates[GM_Book] = GuiModeState(mBookWindow); mGuiModeStates[GM_Book].mOpenSound = "book open"; mGuiModeStates[GM_Book].mCloseSound = "book close"; mCountDialog = new CountDialog(); mWindows.push_back(mCountDialog); mSettingsWindow = new SettingsWindow(); mWindows.push_back(mSettingsWindow); trackWindow(mSettingsWindow, "settings"); mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); mConfirmationDialog = new ConfirmationDialog(); mWindows.push_back(mConfirmationDialog); AlchemyWindow* alchemyWindow = new AlchemyWindow(); mWindows.push_back(alchemyWindow); trackWindow(alchemyWindow, "alchemy"); mGuiModeStates[GM_Alchemy] = GuiModeState(alchemyWindow); mQuickKeysMenu = new QuickKeysMenu(); mWindows.push_back(mQuickKeysMenu); mGuiModeStates[GM_QuickKeysMenu] = GuiModeState(mQuickKeysMenu); LevelupDialog* levelupDialog = new LevelupDialog(); mWindows.push_back(levelupDialog); mGuiModeStates[GM_Levelup] = GuiModeState(levelupDialog); mWaitDialog = new WaitDialog(); mWindows.push_back(mWaitDialog); mGuiModeStates[GM_Rest] = GuiModeState({mWaitDialog->getProgressBar(), mWaitDialog}); SpellCreationDialog* spellCreationDialog = new SpellCreationDialog(); mWindows.push_back(spellCreationDialog); mGuiModeStates[GM_SpellCreation] = GuiModeState(spellCreationDialog); EnchantingDialog* enchantingDialog = new EnchantingDialog(); mWindows.push_back(enchantingDialog); mGuiModeStates[GM_Enchanting] = GuiModeState(enchantingDialog); TrainingWindow* trainingWindow = new TrainingWindow(); mWindows.push_back(trainingWindow); mGuiModeStates[GM_Training] = GuiModeState({trainingWindow->getProgressBar(), trainingWindow}); MerchantRepair* merchantRepair = new MerchantRepair(); mWindows.push_back(merchantRepair); mGuiModeStates[GM_MerchantRepair] = GuiModeState(merchantRepair); Repair* repair = new Repair(); mWindows.push_back(repair); mGuiModeStates[GM_Repair] = GuiModeState(repair); mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); CompanionWindow* companionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); mWindows.push_back(companionWindow); trackWindow(companionWindow, "companion"); mGuiModeStates[GM_Companion] = GuiModeState({mInventoryWindow, companionWindow}); mJailScreen = new JailScreen(); mWindows.push_back(mJailScreen); mGuiModeStates[GM_Jail] = GuiModeState(mJailScreen); std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) { mWerewolfFader = new ScreenFader(werewolfFaderTex); mWindows.push_back(mWerewolfFader); } mBlindnessFader = new ScreenFader("black"); mWindows.push_back(mBlindnessFader); // fall back to player_hit_01.dds if bm_player_hit_01.dds is not available std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; const std::string hitFaderLayout = "openmw_screen_fader_hit.layout"; MyGUI::FloatCoord hitFaderCoord (0,0,1,1); if(!mResourceSystem->getVFS()->exists(hitFaderTexture)) { hitFaderTexture = "textures\\player_hit_01.dds"; hitFaderCoord = MyGUI::FloatCoord(0.2, 0.25, 0.6, 0.5); } mHitFader = new ScreenFader(hitFaderTexture, hitFaderLayout, hitFaderCoord); mWindows.push_back(mHitFader); mScreenFader = new ScreenFader("black"); mWindows.push_back(mScreenFader); mDebugWindow = new DebugWindow(); mWindows.push_back(mDebugWindow); mPostProcessorHud = new PostProcessorHud(); mWindows.push_back(mPostProcessorHud); trackWindow(mPostProcessorHud, "postprocessor"); mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mHud->setVisible(true); mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); updatePinnedWindows(); // Set up visibility updateVisible(); mStatsWatcher->addListener(mHud); mStatsWatcher->addListener(mStatsWindow); mStatsWatcher->addListener(mCharGen); } int WindowManager::getFontHeight() const { return mFontLoader->getFontHeight(); } void WindowManager::setNewGame(bool newgame) { if (newgame) { disallowAll(); mStatsWatcher->removeListener(mCharGen); delete mCharGen; mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); mStatsWatcher->addListener(mCharGen); } else allow(GW_ALL); mStatsWatcher->forceUpdate(); } WindowManager::~WindowManager() { try { LuaUi::clearUserInterface(); mStatsWatcher.reset(); MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); for (WindowBase* window : mWindows) delete window; mWindows.clear(); delete mMessageBoxManager; delete mLocalMapRender; delete mCharGen; delete mDragAndDrop; delete mSoulgemDialog; delete mCursorManager; delete mToolTips; mKeyboardNavigation.reset(); cleanupGarbage(); mFontLoader.reset(); mGui->shutdown(); delete mGui; mGuiPlatform->shutdown(); delete mGuiPlatform; delete mVideoWrapper; } catch(const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } void WindowManager::setStore(const MWWorld::ESMStore &store) { mStore = &store; } void WindowManager::cleanupGarbage() { // Delete any dialogs which are no longer in use if (!mGarbageDialogs.empty()) { for (Layout* widget : mGarbageDialogs) { delete widget; } mGarbageDialogs.clear(); } } void WindowManager::enableScene(bool enable) { unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile; if (!enable && getCullMask() != disablemask) { mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); mOldCullMask = getCullMask(); mViewer->getUpdateVisitor()->setTraversalMask(disablemask); setCullMask(disablemask); } else if (enable && getCullMask() == disablemask) { mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); setCullMask(mOldCullMask); } } void WindowManager::updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) { mConsole->updateSelectedObjectPtr(currentPtr, newPtr); } void WindowManager::updateVisible() { bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper); bool mainmenucover = containsMode(GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; enableScene(!loading && !mainmenucover); if (!mMap) return; // UI not created yet mHud->setVisible(mHudEnabled && !loading); mToolTips->setVisible(mHudEnabled && !loading); bool gameMode = !isGuiMode(); MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); mInputBlocker->setVisible (gameMode); if (loading) setCursorVisible(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); else setCursorVisible(!gameMode); if (gameMode) setKeyFocusWidget (nullptr); // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); setSpellVisibility((mAllowed & GW_Magic) && (!mSpellWindow->pinned() || (mForceHidden & GW_Magic))); setHMSVisibility((mAllowed & GW_Stats) && (!mStatsWindow->pinned() || (mForceHidden & GW_Stats))); mInventoryWindow->setGuiMode(getMode()); // If in game mode (or interactive messagebox), show the pinned windows if (mGuiModes.empty()) { mMap->setVisible(mMap->pinned() && !isConsoleMode() && !(mForceHidden & GW_Map) && (mAllowed & GW_Map)); mStatsWindow->setVisible(mStatsWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); mInventoryWindow->setVisible(mInventoryWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); mSpellWindow->setVisible(mSpellWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); return; } else if (getMode() != GM_Inventory) { mMap->setVisible(false); mStatsWindow->setVisible(false); mSpellWindow->setVisible(false); mHud->setDrowningBarVisible(false); mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); } GuiMode mode = mGuiModes.back(); mInventoryWindow->setTrading(mode == GM_Barter); if (getMode() == GM_Inventory) { // For the inventory mode, compute the effective set of windows to show. // This is controlled both by what windows the // user has opened/closed (the 'shown' variable) and by what // windows we are allowed to show (the 'allowed' var.) int eff = mShown & mAllowed & ~mForceHidden; mMap->setVisible(eff & GW_Map); mInventoryWindow->setVisible(eff & GW_Inventory); mSpellWindow->setVisible(eff & GW_Magic); mStatsWindow->setVisible(eff & GW_Stats); } switch (mode) { // FIXME: refactor chargen windows to use modes properly (or not use them at all) case GM_Name: case GM_Race: case GM_Class: case GM_ClassPick: case GM_ClassCreate: case GM_Birth: case GM_ClassGenerate: case GM_Review: mCharGen->spawnDialog(mode); break; default: break; } } void WindowManager::setDrowningTimeLeft (float time, float maxTime) { mHud->setDrowningTimeLeft(time, maxTime); } void WindowManager::removeDialog(Layout*dialog) { if (!dialog) return; dialog->setVisible(false); mGarbageDialogs.push_back(dialog); } void WindowManager::exitCurrentGuiMode() { if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) { mDragAndDrop->finish(); return; } GuiModeState& state = mGuiModeStates[mGuiModes.back()]; for (WindowBase* window : state.mWindows) { if (!window->exit()) { // unable to exit window, but give access to main menu if (!MyGUI::InputManager::getInstance().isModalAny() && getMode() != GM_MainMenu) pushGuiMode (GM_MainMenu); return; } } popGuiMode(); } void WindowManager::interactiveMessageBox(const std::string &message, const std::vector &buttons, bool block) { mMessageBoxManager->createInteractiveMessageBox(message, buttons); updateVisible(); if (block) { Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mMessageBoxManager->readPressedButton(false) == -1 && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); mKeyboardNavigation->onFrame(); mMessageBoxManager->onFrame(dt); MWBase::Environment::get().getInputManager()->update(dt, true, false); if (!mWindowVisible) std::this_thread::sleep_for(std::chrono::milliseconds(5)); else { mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); frameRateLimiter.limit(); } } } void WindowManager::messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode) { if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { mMessageBoxManager->createMessageBox(message); } } void WindowManager::scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode) { mScheduledMessageBoxes.lock()->emplace_back(std::move(message), showInDialogueMode); } void WindowManager::staticMessageBox(const std::string& message) { mMessageBoxManager->createMessageBox(message, true); } void WindowManager::removeStaticMessageBox() { mMessageBoxManager->removeStaticMessageBox(); } const std::vector WindowManager::getActiveMessageBoxes() { return mMessageBoxManager->getActiveMessageBoxes(); } int WindowManager::readPressedButton () { return mMessageBoxManager->readPressedButton(); } std::string WindowManager::getGameSettingString(const std::string &id, const std::string &default_) { const ESM::GameSetting *setting = mStore->get().search(id); if (setting && setting->mValue.getType()==ESM::VT_String) return setting->mValue.getString(); return default_; } void WindowManager::updateMap() { if (!mLocalMapRender) return; MWWorld::ConstPtr player = MWMechanics::getPlayer(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); osg::Vec3f playerdirection; int x,y; float u,v; mLocalMapRender->updatePlayer(playerPosition, playerOrientation, u, v, x, y, playerdirection); if (!player.getCell()->isExterior()) { setActiveMap(x, y, true); } // else: need to know the current grid center, call setActiveMap from changeCell mMap->setPlayerDir(playerdirection.x(), playerdirection.y()); mMap->setPlayerPos(x, y, u, v); mHud->setPlayerDir(playerdirection.x(), playerdirection.y()); mHud->setPlayerPos(x, y, u, v); } void WindowManager::update (float frameDuration) { handleScheduledMessageBoxes(); bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame; if (gameRunning) updateMap(); if (!mGuiModes.empty()) { GuiModeState& state = mGuiModeStates[mGuiModes.back()]; for (WindowBase* window : state.mWindows) window->onFrame(frameDuration); } else { // update pinned windows if visible for (WindowBase* window : mGuiModeStates[GM_Inventory].mWindows) if (window->isVisible()) window->onFrame(frameDuration); } // Make sure message boxes are always in front // This is an awful workaround for a series of awfully interwoven issues that couldn't be worked around // in a better way because of an impressive number of even more awfully interwoven issues. if (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox() && mCurrentModals.back() != mMessageBoxManager->getInteractiveMessageBox()) { std::vector::iterator found = std::find(mCurrentModals.begin(), mCurrentModals.end(), mMessageBoxManager->getInteractiveMessageBox()); if (found != mCurrentModals.end()) { WindowModal* msgbox = *found; std::swap(*found, mCurrentModals.back()); MyGUI::InputManager::getInstance().addWidgetModal(msgbox->mMainWidget); mKeyboardNavigation->setModalWindow(msgbox->mMainWidget); mKeyboardNavigation->setDefaultFocus(msgbox->mMainWidget, msgbox->getDefaultKeyFocus()); } } if (!mCurrentModals.empty()) mCurrentModals.back()->onFrame(frameDuration); mKeyboardNavigation->onFrame(); if (mMessageBoxManager) mMessageBoxManager->onFrame(frameDuration); mToolTips->onFrame(frameDuration); if (mLocalMapRender) mLocalMapRender->cleanupCameras(); mDebugWindow->onFrame(frameDuration); if (!gameRunning) return; // We should display message about crime only once per frame, even if there are several crimes. // Otherwise we will get message spam when stealing several items via Take All button. const MWWorld::Ptr player = MWMechanics::getPlayer(); int currentBounty = player.getClass().getNpcStats(player).getBounty(); if (currentBounty != mPlayerBounty) { if (mPlayerBounty >= 0 && currentBounty > mPlayerBounty) messageBox("#{sCrimeMessage}"); mPlayerBounty = currentBounty; } mDragAndDrop->onFrame(); mHud->onFrame(frameDuration); mPostProcessorHud->onFrame(frameDuration); if (mCharGen) mCharGen->onFrame(frameDuration); updateActivatedQuickKey(); mStatsWatcher->update(); cleanupGarbage(); } void WindowManager::changeCell(const MWWorld::CellStore* cell) { mMap->requestMapRender(cell); std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); mMap->setCellName( name ); mHud->setCellName( name ); if (cell->getCell()->isExterior()) { if (!cell->getCell()->mName.empty()) mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); setActiveMap(cell->getCell()->getGridX(), cell->getCell()->getGridY(), false); } else { mMap->setCellPrefix (cell->getCell()->mName ); mHud->setCellPrefix (cell->getCell()->mName ); osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); setActiveMap(0, 0, true); } } void WindowManager::setActiveMap(int x, int y, bool interior) { mMap->setActiveCell(x,y, interior); mHud->setActiveCell(x,y, interior); } void WindowManager::setDrowningBarVisibility(bool visible) { mHud->setDrowningBarVisible(visible); } void WindowManager::setHMSVisibility(bool visible) { mHud->setHmsVisible (visible); } void WindowManager::setMinimapVisibility(bool visible) { mHud->setMinimapVisible (visible); } bool WindowManager::toggleFogOfWar() { mMap->toggleFogOfWar(); return mHud->toggleFogOfWar(); } void WindowManager::setFocusObject(const MWWorld::Ptr& focus) { mToolTips->setFocusObject(focus); if(mHud && (mShowOwned == 2 || mShowOwned == 3)) { bool owned = mToolTips->checkOwned(); mHud->setCrosshairOwned(owned); } } void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) { mToolTips->setFocusObjectScreenCoords(min_x, min_y, max_x, max_y); } bool WindowManager::toggleFullHelp() { return mToolTips->toggleFullHelp(); } bool WindowManager::getFullHelp() const { return mToolTips->getFullHelp(); } void WindowManager::setWeaponVisibility(bool visible) { mHud->setWeapVisible (visible); } void WindowManager::setSpellVisibility(bool visible) { mHud->setSpellVisible (visible); mHud->setEffectVisible (visible); } void WindowManager::setSneakVisibility(bool visible) { mHud->setSneakVisible(visible); } void WindowManager::setDragDrop(bool dragDrop) { mToolTips->setEnabled(!dragDrop); MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop); } void WindowManager::setCursorVisible(bool visible) { mCursorVisible = visible; } void WindowManager::setCursorActive(bool active) { mCursorActive = active; } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { std::string tag(_tag); std::string MyGuiPrefix = "setting="; size_t MyGuiPrefixLength = MyGuiPrefix.length(); std::string tokenToFind = "sCell="; size_t tokenLength = tokenToFind.length(); if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0) { tag = tag.substr(MyGuiPrefixLength, tag.length()); size_t comma_pos = tag.find(','); std::string settingSection = tag.substr(0, comma_pos); std::string settingTag = tag.substr(comma_pos+1, tag.length()); _result = Settings::Manager::getString(settingTag, settingSection); } else if (tag.compare(0, tokenLength, tokenToFind) == 0) { _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); _result = MyGUI::TextIterator::toTagsString(_result); } else if (Gui::replaceTag(tag, _result)) { return; } else { std::vector split; Misc::StringUtils::split(tag, split, ":"); // TODO: LocalizationManager should not be a part of lua const auto& luaManager = MWBase::Environment::get().getLuaManager(); // If a key has a "Context:KeyName" format, use YAML to translate data if (split.size() == 2 && luaManager != nullptr) { _result = luaManager->translate(split[0], split[1]); return; } // If not, treat is as GMST name from legacy localization if (!mStore) { Log(Debug::Error) << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'"; _result = tag; return; } const ESM::GameSetting *setting = mStore->get().search(tag); if (setting && setting->mValue.getType()==ESM::VT_String) _result = setting->mValue.getString(); else _result = tag; } } void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) { mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); bool changeRes = false; for (const auto& setting : changed) { if (setting.first == "HUD" && setting.second == "crosshair") mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); else if (setting.first == "GUI" && setting.second == "subtitles") mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI"); else if (setting.first == "GUI" && setting.second == "menu transparency") setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); else if (setting.first == "Video" && ( setting.second == "resolution x" || setting.second == "resolution y" || setting.second == "window mode" || setting.second == "window border")) changeRes = true; else if (setting.first == "Video" && setting.second == "vsync") mVideoWrapper->setSyncToVBlank(Settings::Manager::getBool("vsync", "Video")); else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast")) mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); } if (changeRes) { mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video"), static_cast(Settings::Manager::getInt("window mode", "Video")), Settings::Manager::getBool("window border", "Video")); } } void WindowManager::windowResized(int x, int y) { Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); // We only want to process changes to window-size related settings. Settings::CategorySettingVector filter = {{"Video", "resolution x"}, {"Video", "resolution y"}}; // If the HUD has not been initialised, the World singleton will not be available. if (mHud) { MWBase::Environment::get().getWorld()->processChangedSettings( Settings::Manager::getPendingChanges(filter)); } Settings::Manager::resetPendingChanges(filter); mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y); // scaled size const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); x = viewSize.width; y = viewSize.height; sizeVideo(x, y); if (!mHud) return; // UI not initialized yet for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) { std::string settingName = it->second; if (Settings::Manager::getBool(settingName + " maximized", "Windows")) settingName += " maximized"; MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * x), static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * y)); MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * x), static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * y)); it->first->setPosition(pos); it->first->setSize(size); } for (WindowBase* window : mWindows) window->onResChange(x, y); // TODO: check if any windows are now off-screen and move them back if so } bool WindowManager::isWindowVisible() { return mWindowVisible; } void WindowManager::windowVisibilityChange(bool visible) { mWindowVisible = visible; } void WindowManager::windowClosed() { MWBase::Environment::get().getStateManager()->requestQuit(); } void WindowManager::onCursorChange(const std::string &name) { mCursorManager->cursorChanged(name); } void WindowManager::pushGuiMode(GuiMode mode) { pushGuiMode(mode, MWWorld::Ptr()); } void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) { pushGuiMode(mode, arg, false); } void WindowManager::forceLootMode(const MWWorld::Ptr& ptr) { pushGuiMode(MWGui::GM_Container, ptr, true); } void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force) { if (mode==GM_Inventory && mAllowed==GW_None) return; if (mGuiModes.empty() || mGuiModes.back() != mode) { // If this mode already exists somewhere in the stack, just bring it to the front. if (std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end()) { mGuiModes.erase(std::find(mGuiModes.begin(), mGuiModes.end(), mode)); } if (!mGuiModes.empty()) { mKeyboardNavigation->saveFocus(mGuiModes.back()); mGuiModeStates[mGuiModes.back()].update(false); } mGuiModes.push_back(mode); mGuiModeStates[mode].update(true); playSound(mGuiModeStates[mode].mOpenSound); } if(force) mContainerWindow->treatNextOpenAsLoot(); for (WindowBase* window : mGuiModeStates[mode].mWindows) window->setPtr(arg); mKeyboardNavigation->restoreFocus(mode); updateVisible(); } void WindowManager::setCullMask(uint32_t mask) { mViewer->getCamera()->setCullMask(mask); // We could check whether stereo is enabled here, but these methods are // trivial and have no effect in mono or multiview so just call them regardless. mViewer->getCamera()->setCullMaskLeft(mask); mViewer->getCamera()->setCullMaskRight(mask); } uint32_t WindowManager::getCullMask() { return mViewer->getCamera()->getCullMask(); } void WindowManager::popGuiMode(bool noSound) { if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) { mDragAndDrop->finish(); } if (!mGuiModes.empty()) { const GuiMode mode = mGuiModes.back(); mKeyboardNavigation->saveFocus(mode); mGuiModes.pop_back(); mGuiModeStates[mode].update(false); if (!noSound) playSound(mGuiModeStates[mode].mCloseSound); } if (!mGuiModes.empty()) { const GuiMode mode = mGuiModes.back(); mGuiModeStates[mode].update(true); mKeyboardNavigation->restoreFocus(mode); } updateVisible(); // To make sure that console window get focus again if (mConsole && mConsole->isVisible()) mConsole->onOpen(); } void WindowManager::removeGuiMode(GuiMode mode, bool noSound) { if (!mGuiModes.empty() && mGuiModes.back() == mode) { popGuiMode(noSound); return; } std::vector::iterator it = mGuiModes.begin(); while (it != mGuiModes.end()) { if (*it == mode) it = mGuiModes.erase(it); else ++it; } updateVisible(); } void WindowManager::goToJail(int days) { pushGuiMode(MWGui::GM_Jail); mJailScreen->goToJail(days); } void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) { mSelectedSpell = spellId; mSelectedEnchantItem = MWWorld::Ptr(); mHud->setSelectedSpell(spellId, successChancePercent); const ESM::Spell* spell = mStore->get().find(spellId); mSpellWindow->setTitle(spell->mName); } void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { mSelectedEnchantItem = item; mSelectedSpell.clear(); const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } const MWWorld::Ptr &WindowManager::getSelectedEnchantItem() const { return mSelectedEnchantItem; } void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item) { mSelectedWeapon = item; int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item) * 100); } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } const MWWorld::Ptr &WindowManager::getSelectedWeapon() const { return mSelectedWeapon; } void WindowManager::unsetSelectedSpell() { mSelectedSpell.clear(); mSelectedEnchantItem = MWWorld::Ptr(); mHud->unsetSelectedSpell(); MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); if (player->getDrawState() == MWMechanics::DrawState::Spell) player->setDrawState(MWMechanics::DrawState::Nothing); mSpellWindow->setTitle("#{sNone}"); } void WindowManager::unsetSelectedWeapon() { mSelectedWeapon = MWWorld::Ptr(); mHud->unsetSelectedWeapon(); mInventoryWindow->setTitle("#{sSkillHandtohand}"); } void WindowManager::getMousePosition(int &x, int &y) { const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); x = pos.left; y = pos.top; } void WindowManager::getMousePosition(float &x, float &y) { const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); x = static_cast(pos.left); y = static_cast(pos.top); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); x /= viewSize.width; y /= viewSize.height; } bool WindowManager::getWorldMouseOver() { return mHud->getWorldMouseOver(); } float WindowManager::getScalingFactor() const { return mScalingFactor; } void WindowManager::executeInConsole (const std::string& path) { mConsole->executeFile (path); } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; } MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } MWGui::PostProcessorHud* WindowManager::getPostProcessorHud() { return mPostProcessorHud; } void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions) { if (mInventoryWindow) mInventoryWindow->useItem(item, bypassBeastRestrictions); } bool WindowManager::isAllowed (GuiWindow wnd) const { return (mAllowed & wnd) != 0; } void WindowManager::allow (GuiWindow wnd) { mAllowed = (GuiWindow)(mAllowed | wnd); if (wnd & GW_Inventory) { mBookWindow->setInventoryAllowed (true); mScrollWindow->setInventoryAllowed (true); } updateVisible(); } void WindowManager::disallowAll() { mAllowed = GW_None; mRestAllowed = false; mBookWindow->setInventoryAllowed (false); mScrollWindow->setInventoryAllowed (false); updateVisible(); } void WindowManager::toggleVisible (GuiWindow wnd) { if (getMode() != GM_Inventory) return; std::string settingName; switch (wnd) { case GW_Inventory: settingName = "inventory"; break; case GW_Map: settingName = "map"; break; case GW_Magic: settingName = "spells"; break; case GW_Stats: settingName = "stats"; break; default: break; } if (!settingName.empty()) { settingName += " hidden"; bool hidden = Settings::Manager::getBool(settingName, "Windows"); Settings::Manager::setBool(settingName, "Windows", !hidden); } mShown = (GuiWindow)(mShown ^ wnd); updateVisible(); } void WindowManager::forceHide(GuiWindow wnd) { mForceHidden = (GuiWindow)(mForceHidden | wnd); updateVisible(); } void WindowManager::unsetForceHide(GuiWindow wnd) { mForceHidden = (GuiWindow)(mForceHidden & ~wnd); updateVisible(); } bool WindowManager::isGuiMode() const { return !mGuiModes.empty() || isConsoleMode() || (mPostProcessorHud && mPostProcessorHud->isVisible()) || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } bool WindowManager::isConsoleMode() const { return mConsole && mConsole->isVisible(); } bool WindowManager::isPostProcessorHudVisible() const { return mPostProcessorHud->isVisible(); } MWGui::GuiMode WindowManager::getMode() const { if (mGuiModes.empty()) return GM_None; return mGuiModes.back(); } void WindowManager::disallowMouse() { mInputBlocker->setVisible (true); } void WindowManager::allowMouse() { mInputBlocker->setVisible (!isGuiMode ()); } void WindowManager::notifyInputActionBound () { mSettingsWindow->updateControlsBox (); allowMouse(); } bool WindowManager::containsMode(GuiMode mode) const { if(mGuiModes.empty()) return false; return std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end(); } void WindowManager::showCrosshair (bool show) { if (mHud) mHud->setCrosshairVisible (show && mCrosshairEnabled); } void WindowManager::updateActivatedQuickKey () { mQuickKeysMenu->updateActivatedQuickKey(); } void WindowManager::activateQuickKey (int index) { mQuickKeysMenu->activateQuickKey(index); } bool WindowManager::getSubtitlesEnabled () { return mSubtitlesEnabled; } bool WindowManager::toggleHud() { mHudEnabled = !mHudEnabled; updateVisible(); mMessageBoxManager->setVisible(mHudEnabled); return mHudEnabled; } bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) mRestAllowed=true; return mRestAllowed; } bool WindowManager::getPlayerSleeping () { return mWaitDialog->getSleeping(); } void WindowManager::wakeUpPlayer() { mWaitDialog->wakeUp(); } void WindowManager::addVisitedLocation(const std::string& name, int x, int y) { mMap->addVisitedLocation (name, x, y); } const Translation::Storage& WindowManager::getTranslationDataStorage() const { return mTranslationDataStorage; } void WindowManager::changePointer(const std::string &name) { MyGUI::PointerManager::getInstance().setPointer(name); onCursorChange(name); } void WindowManager::showSoulgemDialog(MWWorld::Ptr item) { mSoulgemDialog->show(item); updateVisible(); } void WindowManager::updatePlayer() { mInventoryWindow->updatePlayer(); const MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { setWerewolfOverlay(true); forceHide((GuiWindow)(MWGui::GW_Inventory | MWGui::GW_Magic)); } } // Remove this wrapper once onKeyFocusChanged call is rendered unnecessary void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget) { MyGUI::InputManager::getInstance().setKeyFocusWidget(widget); onKeyFocusChanged(widget); } void WindowManager::onKeyFocusChanged(MyGUI::Widget *widget) { if (widget && widget->castType(false)) SDL_StartTextInput(); else SDL_StopTextInput(); } void WindowManager::setEnemy(const MWWorld::Ptr &enemy) { mHud->setEnemy(enemy); } int WindowManager::getMessagesCount() const { int count = 0; if (mMessageBoxManager) count = mMessageBoxManager->getMessagesCount(); return count; } Loading::Listener* WindowManager::getLoadingScreen() { return mLoadingScreen; } bool WindowManager::getCursorVisible() { return mCursorVisible && mCursorActive; } void WindowManager::trackWindow(Layout *layout, const std::string &name) { std::string settingName = name; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); bool isMaximized = Settings::Manager::getBool(name + " maximized", "Windows"); if (isMaximized) settingName += " maximized"; MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * viewSize.height)); MyGUI::IntSize size (static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * viewSize.height)); layout->mMainWidget->setPosition(pos); layout->mMainWidget->setSize(size); MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); mTrackedWindows[window] = name; } void WindowManager::toggleMaximized(Layout *layout) { MyGUI::Window* window = layout->mMainWidget->castType(); std::string setting = mTrackedWindows[window]; if (setting.empty()) return; bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); if (maximized) setting += " maximized"; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); window->setCoord(x, y, w, h); Settings::Manager::setBool(mTrackedWindows[window] + " maximized", "Windows", maximized); } void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) { std::string setting = mTrackedWindows[_sender]; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float x = _sender->getPosition().left / float(viewSize.width); float y = _sender->getPosition().top / float(viewSize.height); float w = _sender->getSize().width / float(viewSize.width); float h = _sender->getSize().height / float(viewSize.height); Settings::Manager::setFloat(setting + " x", "Windows", x); Settings::Manager::setFloat(setting + " y", "Windows", y); Settings::Manager::setFloat(setting + " w", "Windows", w); Settings::Manager::setFloat(setting + " h", "Windows", h); bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); if (maximized) Settings::Manager::setBool(setting + " maximized", "Windows", false); } void WindowManager::clear() { mPlayerBounty = -1; for (WindowBase* window : mWindows) window->clear(); if (mLocalMapRender) mLocalMapRender->clear(); mMessageBoxManager->clear(); mToolTips->clear(); mSelectedSpell.clear(); mCustomMarkers.clear(); mForceHidden = GW_None; mRestAllowed = true; while (!mGuiModes.empty()) popGuiMode(); updateVisible(); } void WindowManager::write(ESM::ESMWriter &writer, Loading::Listener& progress) { mMap->write(writer, progress); mQuickKeysMenu->write(writer); if (!mSelectedSpell.empty()) { writer.startRecord(ESM::REC_ASPL); writer.writeHNString("ID__", mSelectedSpell); writer.endRecord(ESM::REC_ASPL); } for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) { writer.startRecord(ESM::REC_MARK); it->second.save(writer); writer.endRecord(ESM::REC_MARK); } } void WindowManager::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_GMAP) mMap->readRecord(reader, type); else if (type == ESM::REC_KEYS) mQuickKeysMenu->readRecord(reader, type); else if (type == ESM::REC_ASPL) { reader.getSubNameIs("ID__"); std::string spell = reader.getHString(); if (mStore->get().search(spell)) mSelectedSpell = spell; } else if (type == ESM::REC_MARK) { ESM::CustomMarker marker; marker.load(reader); mCustomMarkers.addMarker(marker, false); } } int WindowManager::countSavedGameRecords() const { return 1 // Global map + 1 // QuickKeysMenu + mCustomMarkers.size() + (!mSelectedSpell.empty() ? 1 : 0); } bool WindowManager::isSavingAllowed() const { return !MyGUI::InputManager::getInstance().isModalAny() && !isConsoleMode() // TODO: remove this, once we have properly serialized the state of open windows && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest))); } void WindowManager::playVideo(const std::string &name, bool allowSkipping, bool overrideSounds) { mVideoWidget->playVideo("video\\" + name); mVideoWidget->eventKeyButtonPressed.clear(); mVideoBackground->eventKeyButtonPressed.clear(); if (allowSkipping) { mVideoWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); } enableScene(false); MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); sizeVideo(screenSize.width, screenSize.height); MyGUI::Widget* oldKeyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); setKeyFocusWidget(mVideoWidget); mVideoBackground->setVisible(true); bool cursorWasVisible = mCursorVisible; setCursorVisible(false); if (overrideSounds && mVideoWidget->hasAudioStream()) MWBase::Environment::get().getSoundManager()->pauseSounds(MWSound::VideoPlayback, ~MWSound::Type::Movie & MWSound::Type::Mask ); Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); MWBase::Environment::get().getInputManager()->update(dt, true, false); if (!mWindowVisible) { mVideoWidget->pause(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } else { if (mVideoWidget->isPaused()) mVideoWidget->resume(); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); frameRateLimiter.limit(); } mVideoWidget->stop(); MWBase::Environment::get().getSoundManager()->resumeSounds(MWSound::VideoPlayback); setKeyFocusWidget(oldKeyFocus); setCursorVisible(cursorWasVisible); // Restore normal rendering updateVisible(); mVideoBackground->setVisible(false); } void WindowManager::sizeVideo(int screenWidth, int screenHeight) { // Use black bars to correct aspect ratio bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); mVideoBackground->setSize(screenWidth, screenHeight); mVideoWidget->autoResize(stretch); } void WindowManager::exitCurrentModal() { if (!mCurrentModals.empty()) { WindowModal* window = mCurrentModals.back(); if (!window->exit()) return; window->setVisible(false); } } void WindowManager::addCurrentModal(WindowModal *input) { if (mCurrentModals.empty()) mKeyboardNavigation->saveFocus(getMode()); mCurrentModals.push_back(input); mKeyboardNavigation->restoreFocus(-1); mKeyboardNavigation->setModalWindow(input->mMainWidget); mKeyboardNavigation->setDefaultFocus(input->mMainWidget, input->getDefaultKeyFocus()); } void WindowManager::removeCurrentModal(WindowModal* input) { if(!mCurrentModals.empty()) { if(input == mCurrentModals.back()) { mCurrentModals.pop_back(); mKeyboardNavigation->saveFocus(-1); } else { auto found = std::find(mCurrentModals.begin(), mCurrentModals.end(), input); if (found != mCurrentModals.end()) mCurrentModals.erase(found); else Log(Debug::Warning) << "Warning: can't find modal window " << input; } } if (mCurrentModals.empty()) { mKeyboardNavigation->setModalWindow(nullptr); mKeyboardNavigation->restoreFocus(getMode()); } else mKeyboardNavigation->setModalWindow(mCurrentModals.back()->mMainWidget); } void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) { if (_key == MyGUI::KeyCode::Escape) mVideoWidget->stop(); } void WindowManager::updatePinnedWindows() { mInventoryWindow->setPinned(Settings::Manager::getBool("inventory pin", "Windows")); if (Settings::Manager::getBool("inventory hidden", "Windows")) mShown = (GuiWindow)(mShown ^ GW_Inventory); mMap->setPinned(Settings::Manager::getBool("map pin", "Windows")); if (Settings::Manager::getBool("map hidden", "Windows")) mShown = (GuiWindow)(mShown ^ GW_Map); mSpellWindow->setPinned(Settings::Manager::getBool("spells pin", "Windows")); if (Settings::Manager::getBool("spells hidden", "Windows")) mShown = (GuiWindow)(mShown ^ GW_Magic); mStatsWindow->setPinned(Settings::Manager::getBool("stats pin", "Windows")); if (Settings::Manager::getBool("stats hidden", "Windows")) mShown = (GuiWindow)(mShown ^ GW_Stats); } void WindowManager::pinWindow(GuiWindow window) { switch (window) { case GW_Inventory: mInventoryWindow->setPinned(true); break; case GW_Map: mMap->setPinned(true); break; case GW_Magic: mSpellWindow->setPinned(true); break; case GW_Stats: mStatsWindow->setPinned(true); break; default: break; } updateVisible(); } void WindowManager::fadeScreenIn(const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); mScreenFader->fadeOut(time, delay); } void WindowManager::fadeScreenOut(const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); mScreenFader->fadeIn(time, delay); } void WindowManager::fadeScreenTo(const int percent, const float time, bool clearQueue, float delay) { if (clearQueue) mScreenFader->clearQueue(); mScreenFader->fadeTo(percent, time, delay); } void WindowManager::setBlindness(const int percent) { mBlindnessFader->notifyAlphaChanged(percent / 100.f); } void WindowManager::activateHitOverlay(bool interrupt) { if (!mHitFaderEnabled) return; if (!interrupt && !mHitFader->isEmpty()) return; mHitFader->clearQueue(); mHitFader->fadeTo(100, 0.0f); mHitFader->fadeTo(0, 0.5f); } void WindowManager::setWerewolfOverlay(bool set) { if (!mWerewolfOverlayEnabled) return; if (mWerewolfFader) mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); } void WindowManager::onClipboardChanged(const std::string &_type, const std::string &_data) { if (_type == "Text") SDL_SetClipboardText(MyGUI::TextIterator::getOnlyText(MyGUI::UString(_data)).asUTF8().c_str()); } void WindowManager::onClipboardRequested(const std::string &_type, std::string &_data) { if (_type != "Text") return; char* text=nullptr; text = SDL_GetClipboardText(); if (text) _data = MyGUI::TextIterator::toTagsString(text); SDL_free(text); } void WindowManager::toggleConsole() { bool visible = mConsole->isVisible(); if (!visible && !mGuiModes.empty()) mKeyboardNavigation->saveFocus(mGuiModes.back()); mConsole->setVisible(!visible); if (visible && !mGuiModes.empty()) mKeyboardNavigation->restoreFocus(mGuiModes.back()); updateVisible(); } void WindowManager::toggleDebugWindow() { mDebugWindow->setVisible(!mDebugWindow->isVisible()); } void WindowManager::togglePostProcessorHud() { if (!MWBase::Environment::get().getWorld()->getPostProcessor()->isEnabled()) { messageBox("Postprocessor is not enabled."); return; } bool visible = mPostProcessorHud->isVisible(); if (!visible && !mGuiModes.empty()) mKeyboardNavigation->saveFocus(mGuiModes.back()); mPostProcessorHud->setVisible(!visible); if (visible && !mGuiModes.empty()) mKeyboardNavigation->restoreFocus(mGuiModes.back()); updateVisible(); } void WindowManager::cycleSpell(bool next) { if (!isGuiMode()) mSpellWindow->cycle(next); } void WindowManager::cycleWeapon(bool next) { if (!isGuiMode()) mInventoryWindow->cycle(next); } void WindowManager::playSound(const std::string& soundId, float volume, float pitch) { if (soundId.empty()) return; MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnvNoScaling); } void WindowManager::updateSpellWindow() { if (mSpellWindow) mSpellWindow->updateSpells(); } void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr &object) { mConsole->setSelectedObject(object); } void WindowManager::printToConsole(const std::string& msg, std::string_view color) { mConsole->print(msg, color); } void WindowManager::setConsoleMode(const std::string& mode) { mConsole->setConsoleMode(mode); } void WindowManager::createCursors() { // FIXME: currently we do not scale cursor since it is not a MyGUI widget. // In theory, we can do it manually (rescale the cursor image via osg::Imag::scaleImage() and scale the hotspot position). // Unfortunately, this apploach can lead to driver crashes on some setups (e.g. on laptops with nvidia-prime on Linux). MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); while (enumerator.next()) { MyGUI::IResource* resource = enumerator.current().second; ResourceImageSetPointerFix* imgSetPointer = resource->castType(false); if (!imgSetPointer) continue; std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(tex_name); if(image.valid()) { //everything looks good, send it to the cursor manager Uint8 hotspot_x = imgSetPointer->getHotSpot().left; Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y); } } } void WindowManager::createTextures() { { MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("white"); tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); for (int x=0; x<8; ++x) for (int y=0; y<8; ++y) { *(data++) = 255; *(data++) = 255; *(data++) = 255; } tex->unlock(); } { MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("black"); tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); for (int x=0; x<8; ++x) for (int y=0; y<8; ++y) { *(data++) = 0; *(data++) = 0; *(data++) = 0; } tex->unlock(); } { MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("transparent"); tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } } void WindowManager::setMenuTransparency(float value) { MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("transparent"); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); for (int x=0; x<8; ++x) for (int y=0; y<8; ++y) { *(data++) = 255; *(data++) = 255; *(data++) = 255; *(data++) = static_cast(value*255); } tex->unlock(); } void WindowManager::addCell(MWWorld::CellStore* cell) { mLocalMapRender->addCell(cell); } void WindowManager::removeCell(MWWorld::CellStore *cell) { mLocalMapRender->removeCell(cell); } void WindowManager::writeFog(MWWorld::CellStore *cell) { mLocalMapRender->saveFogOfWar(cell); } const MWGui::TextColours& WindowManager::getTextColours() { return mTextColours; } bool WindowManager::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) { if (!mKeyboardNavigation->injectKeyPress(key, text, repeat)) { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool widgetActive = MyGUI::InputManager::getInstance().injectKeyPress(key, text); if (!widgetActive || !focus) return false; // FIXME: MyGUI doesn't allow widgets to state if a given key was actually used, so make a guess if (focus->getTypeName().find("Button") != std::string::npos) { switch (key.getValue()) { case MyGUI::KeyCode::ArrowDown: case MyGUI::KeyCode::ArrowUp: case MyGUI::KeyCode::ArrowLeft: case MyGUI::KeyCode::ArrowRight: case MyGUI::KeyCode::Return: case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::Space: return true; default: return false; } } return false; } else return true; } bool WindowManager::injectKeyRelease(MyGUI::KeyCode key) { return MyGUI::InputManager::getInstance().injectKeyRelease(key); } void WindowManager::GuiModeState::update(bool visible) { for (unsigned int i=0; isetVisible(visible); } void WindowManager::watchActor(const MWWorld::Ptr& ptr) { mStatsWatcher->watchActor(ptr); } MWWorld::Ptr WindowManager::getWatchedActor() const { return mStatsWatcher->getWatchedActor(); } const std::string& WindowManager::getVersionDescription() const { return mVersionDescription; } void WindowManager::handleScheduledMessageBoxes() { const auto scheduledMessageBoxes = mScheduledMessageBoxes.lock(); for (const ScheduledMessageBox& v : *scheduledMessageBoxes) messageBox(v.mMessage, v.mShowInDialogueMode); scheduledMessageBoxes->clear(); } void WindowManager::onDeleteCustomData(const MWWorld::Ptr& ptr) { for(auto* window : mWindows) window->onDeleteCustomData(ptr); } void WindowManager::asyncPrepareSaveMap() { mMap->asyncPrepareSaveMap(); } } openmw-openmw-0.48.0/apps/openmw/mwgui/windowmanagerimp.hpp000066400000000000000000000460351445372753700241250ustar00rootroot00000000000000#ifndef MWGUI_WINDOWMANAGERIMP_H #define MWGUI_WINDOWMANAGERIMP_H /** This class owns and controls all the MW specific windows in the GUI. It can enable/disable Gui mode, and is responsible for sending and retrieving information from the Gui. **/ #include #include #include #include "../mwbase/windowmanager.hpp" #include #include #include #include #include "mapwindow.hpp" #include "statswatcher.hpp" #include "textcolours.hpp" #include #include namespace MyGUI { class Gui; class Widget; class Window; class UString; class ImageBox; } namespace MWWorld { class ESMStore; } namespace Compiler { class Extensions; } namespace Translation { class Storage; } namespace osg { class Group; } namespace osgViewer { class Viewer; } namespace Resource { class ResourceSystem; } namespace SceneUtil { class WorkQueue; } namespace SDLUtil { class SDLCursorManager; class VideoWrapper; } namespace osgMyGUI { class Platform; } namespace Gui { class FontLoader; } namespace MWRender { class LocalMap; } namespace MWGui { class WindowBase; class HUD; class MapWindow; class MainMenu; class StatsWindow; class InventoryWindow; struct JournalWindow; class CharacterCreation; class DragAndDrop; class ToolTips; class TextInputDialog; class InfoBoxDialog; class MessageBoxManager; class SettingsWindow; class AlchemyWindow; class QuickKeysMenu; class LoadingScreen; class LevelupDialog; class WaitDialog; class SpellCreationDialog; class EnchantingDialog; class TrainingWindow; class SpellIcons; class MerchantRepair; class SoulgemDialog; class Recharge; class CompanionWindow; class VideoWidget; class WindowModal; class ScreenFader; class DebugWindow; class PostProcessorHud; class JailScreen; class KeyboardNavigation; class WindowManager : public MWBase::WindowManager { public: typedef std::pair Faction; typedef std::vector FactionList; WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders); virtual ~WindowManager(); /// Set the ESMStore to use for retrieving of GUI-related strings. void setStore (const MWWorld::ESMStore& store); void initUI(); Loading::Listener* getLoadingScreen() override; /// @note This method will block until the video finishes playing /// (and will continually update the window while doing so) void playVideo(const std::string& name, bool allowSkipping, bool overrideSounds = true) override; /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. void setKeyFocusWidget (MyGUI::Widget* widget) override; void setNewGame(bool newgame) override; void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) override; void pushGuiMode (GuiMode mode) override; void popGuiMode(bool noSound=false) override; void removeGuiMode(GuiMode mode, bool noSound=false) override; ///< can be anywhere in the stack void goToJail(int days) override; GuiMode getMode() const override; bool containsMode(GuiMode mode) const override; bool isGuiMode() const override; bool isConsoleMode() const override; bool isPostProcessorHudVisible() const override; void toggleVisible(GuiWindow wnd) override; void forceHide(MWGui::GuiWindow wnd) override; void unsetForceHide(MWGui::GuiWindow wnd) override; /// Disallow all inventory mode windows void disallowAll() override; /// Allow one or more windows void allow(GuiWindow wnd) override; bool isAllowed(GuiWindow wnd) const override; /// \todo investigate, if we really need to expose every single lousy UI element to the outside world MWGui::InventoryWindow* getInventoryWindow() override; MWGui::CountDialog* getCountDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; const std::vector getActiveMessageBoxes() override; MWGui::PostProcessorHud* getPostProcessorHud() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; void updateSpellWindow() override; void setConsoleSelectedObject(const MWWorld::Ptr& object) override; void printToConsole(const std::string& msg, std::string_view color) override; void setConsoleMode(const std::string& mode) override; /// Set time left for the player to start drowning (update the drowning bar) /// @param time time left to start drowning /// @param maxTime how long we can be underwater (in total) until drowning starts void setDrowningTimeLeft (float time, float maxTime) override; void changeCell(const MWWorld::CellStore* cell) override; ///< change the active cell void setFocusObject(const MWWorld::Ptr& focus) override; void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) override; void getMousePosition(int &x, int &y) override; void getMousePosition(float &x, float &y) override; void setDragDrop(bool dragDrop) override; bool getWorldMouseOver() override; float getScalingFactor() const override; bool toggleFogOfWar() override; bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) bool getFullHelp() const override; void setActiveMap(int x, int y, bool interior) override; ///< set the indices of the map texture that should be used /// sets the visibility of the drowning bar void setDrowningBarVisibility(bool visible) override; // sets the visibility of the hud health/magicka/stamina bars void setHMSVisibility(bool visible) override; // sets the visibility of the hud minimap void setMinimapVisibility(bool visible) override; void setWeaponVisibility(bool visible) override; void setSpellVisibility(bool visible) override; void setSneakVisibility(bool visible) override; /// activate selected quick key void activateQuickKey (int index) override; /// update activated quick key state (if action executing was delayed for some reason) void updateActivatedQuickKey () override; std::string getSelectedSpell() override { return mSelectedSpell; } void setSelectedSpell(const std::string& spellId, int successChancePercent) override; void setSelectedEnchantItem(const MWWorld::Ptr& item) override; const MWWorld::Ptr& getSelectedEnchantItem() const override; void setSelectedWeapon(const MWWorld::Ptr& item) override; const MWWorld::Ptr& getSelectedWeapon() const override; int getFontHeight() const override; void unsetSelectedSpell() override; void unsetSelectedWeapon() override; void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) override; void showCrosshair(bool show) override; bool getSubtitlesEnabled() override; /// Turn visibility of HUD on or off bool toggleHud() override; void disallowMouse() override; void allowMouse() override; void notifyInputActionBound() override; void addVisitedLocation(const std::string& name, int x, int y) override; ///Hides dialog and schedules dialog to be deleted. void removeDialog(Layout* dialog) override; ///Gracefully attempts to exit the topmost GUI mode void exitCurrentGuiMode() override; void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; void scheduleMessageBox (std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; void staticMessageBox(const std::string& message) override; void removeStaticMessageBox() override; void interactiveMessageBox (const std::string& message, const std::vector& buttons = std::vector(), bool block=false) override; int readPressedButton () override; ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) void update (float duration); /** * Fetches a GMST string from the store, if there is no setting with the given * ID or it is not a string the default string is returned. * * @param id Identifier for the GMST setting, e.g. "aName" * @param default Default value if the GMST setting cannot be used. */ std::string getGameSettingString(const std::string &id, const std::string &default_) override; void processChangedSettings(const Settings::CategorySettingVector& changed) override; void windowVisibilityChange(bool visible) override; void windowResized(int x, int y) override; void windowClosed() override; bool isWindowVisible() override; void watchActor(const MWWorld::Ptr& ptr) override; MWWorld::Ptr getWatchedActor() const override; void executeInConsole (const std::string& path) override; void enableRest() override { mRestAllowed = true; } bool getRestEnabled() override; bool getJournalAllowed() override { return (mAllowed & GW_Magic) != 0; } bool getPlayerSleeping() override; void wakeUpPlayer() override; void updatePlayer() override; void showSoulgemDialog (MWWorld::Ptr item) override; void changePointer (const std::string& name) override; void setEnemy (const MWWorld::Ptr& enemy) override; int getMessagesCount() const override; const Translation::Storage& getTranslationDataStorage() const override; void onSoulgemDialogButtonPressed (int button); bool getCursorVisible() override; /// Call when mouse cursor or buttons are used. void setCursorActive(bool active) override; /// Clear all savegame-specific data void clear() override; void write (ESM::ESMWriter& writer, Loading::Listener& progress) override; void readRecord (ESM::ESMReader& reader, uint32_t type) override; int countSavedGameRecords() const override; /// Does the current stack of GUI-windows permit saving? bool isSavingAllowed() const override; /// Send exit command to active Modal window **/ void exitCurrentModal() override; /// Sets the current Modal /** Used to send exit command to active Modal when Esc is pressed **/ void addCurrentModal(WindowModal* input) override; /// Removes the top Modal /** Used when one Modal adds another Modal \param input Pointer to the current modal, to ensure proper modal is removed **/ void removeCurrentModal(WindowModal* input) override; void pinWindow (MWGui::GuiWindow window) override; void toggleMaximized(Layout *layout) override; /// Fade the screen in, over \a time seconds void fadeScreenIn(const float time, bool clearQueue, float delay) override; /// Fade the screen out to black, over \a time seconds void fadeScreenOut(const float time, bool clearQueue, float delay) override; /// Fade the screen to a specified percentage of black, over \a time seconds void fadeScreenTo(const int percent, const float time, bool clearQueue, float delay) override; /// Darken the screen to a specified percentage void setBlindness(const int percent) override; void activateHitOverlay(bool interrupt) override; void setWerewolfOverlay(bool set) override; void toggleConsole() override; void toggleDebugWindow() override; void togglePostProcessorHud() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; /// Cycle to next or previous weapon void cycleWeapon(bool next) override; void playSound(const std::string& soundId, float volume = 1.f, float pitch = 1.f) override; void addCell(MWWorld::CellStore* cell) override; void removeCell(MWWorld::CellStore* cell) override; void writeFog(MWWorld::CellStore* cell) override; const MWGui::TextColours& getTextColours() override; bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat=false) override; bool injectKeyRelease(MyGUI::KeyCode key) override; const std::string& getVersionDescription() const override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void forceLootMode(const MWWorld::Ptr& ptr) override; void asyncPrepareSaveMap() override; private: unsigned int mOldUpdateMask; unsigned int mOldCullMask; const MWWorld::ESMStore* mStore; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; osgMyGUI::Platform* mGuiPlatform; osgViewer::Viewer* mViewer; std::unique_ptr mFontLoader; std::unique_ptr mStatsWatcher; bool mConsoleOnlyScripts; std::map mTrackedWindows; void trackWindow(Layout* layout, const std::string& name); void onWindowChangeCoord(MyGUI::Window* _sender); std::string mSelectedSpell; MWWorld::Ptr mSelectedEnchantItem; MWWorld::Ptr mSelectedWeapon; std::vector mCurrentModals; // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). CustomMarkerCollection mCustomMarkers; HUD *mHud; MapWindow *mMap; MWRender::LocalMap* mLocalMapRender; ToolTips *mToolTips; StatsWindow *mStatsWindow; MessageBoxManager *mMessageBoxManager; Console *mConsole; DialogueWindow *mDialogueWindow; DragAndDrop* mDragAndDrop; InventoryWindow *mInventoryWindow; ScrollWindow* mScrollWindow; BookWindow* mBookWindow; CountDialog* mCountDialog; TradeWindow* mTradeWindow; SettingsWindow* mSettingsWindow; ConfirmationDialog* mConfirmationDialog; SpellWindow* mSpellWindow; QuickKeysMenu* mQuickKeysMenu; LoadingScreen* mLoadingScreen; WaitDialog* mWaitDialog; SoulgemDialog* mSoulgemDialog; MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideoWidget; ScreenFader* mWerewolfFader; ScreenFader* mBlindnessFader; ScreenFader* mHitFader; ScreenFader* mScreenFader; DebugWindow* mDebugWindow; PostProcessorHud* mPostProcessorHud; JailScreen* mJailScreen; ContainerWindow* mContainerWindow; std::vector mWindows; Translation::Storage& mTranslationDataStorage; CharacterCreation* mCharGen; MyGUI::Widget* mInputBlocker; bool mCrosshairEnabled; bool mSubtitlesEnabled; bool mHitFaderEnabled; bool mWerewolfOverlayEnabled; bool mHudEnabled; bool mCursorVisible; bool mCursorActive; int mPlayerBounty; void setCursorVisible(bool visible) override; MyGUI::Gui *mGui; // Gui struct GuiModeState { GuiModeState(WindowBase* window) { mWindows.push_back(window); } GuiModeState(const std::vector& windows) : mWindows(windows) {} GuiModeState() {} void update(bool visible); std::vector mWindows; std::string mCloseSound; std::string mOpenSound; }; // Defines the windows that should be shown in a particular GUI mode. std::map mGuiModeStates; // The currently active stack of GUI modes (top mode is the one we are in). std::vector mGuiModes; SDLUtil::SDLCursorManager* mCursorManager; std::vector mGarbageDialogs; void cleanupGarbage(); GuiWindow mShown; // Currently shown windows in inventory mode GuiWindow mForceHidden; // Hidden windows (overrides mShown) /* Currently ALLOWED windows in inventory mode. This is used at the start of the game, when windows are enabled one by one through script commands. You can manipulate this through using allow() and disableAll(). */ GuiWindow mAllowed; // is the rest window allowed? bool mRestAllowed; void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings void updateMap(); int mShowOwned; ToUTF8::FromType mEncoding; std::string mVersionDescription; bool mWindowVisible; MWGui::TextColours mTextColours; std::unique_ptr mKeyboardNavigation; SDLUtil::VideoWrapper* mVideoWrapper; float mScalingFactor; struct ScheduledMessageBox { std::string mMessage; MWGui::ShowInDialogueMode mShowInDialogueMode; ScheduledMessageBox(std::string&& message, MWGui::ShowInDialogueMode showInDialogueMode) : mMessage(std::move(message)), mShowInDialogueMode(showInDialogueMode) {} }; Misc::ScopeGuarded> mScheduledMessageBoxes; /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: * #{GMSTName}: retrieves String value of the GMST called GMSTName * #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME from settings.cfg * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name) * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, * in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins. * #{fontcolourhtml=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, * in the format "#xxxxxx" where x are hexadecimal numbers. Useful in an EditBox's caption to change the color of following text. */ void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); void onCursorChange(const std::string& name); void onKeyFocusChanged(MyGUI::Widget* widget); // Key pressed while playing a video void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); void sizeVideo(int screenWidth, int screenHeight); void onClipboardChanged(const std::string& _type, const std::string& _data); void onClipboardRequested(const std::string& _type, std::string& _data); void createTextures(); void createCursors(); void setMenuTransparency(float value); void updatePinnedWindows(); void enableScene(bool enable); void handleScheduledMessageBoxes(); void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force); void setCullMask(uint32_t mask) override; uint32_t getCullMask() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwgui/windowpinnablebase.cpp000066400000000000000000000021371445372753700244160ustar00rootroot00000000000000#include "windowpinnablebase.hpp" #include "exposedwindow.hpp" namespace MWGui { WindowPinnableBase::WindowPinnableBase(const std::string& parLayout) : WindowBase(parLayout), mPinned(false) { Window* window = mMainWidget->castType(); mPinButton = window->getSkinWidget ("Button"); mPinButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonPressed); } void WindowPinnableBase::onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id) { if (id != MyGUI::MouseButton::Left) return; mPinned = !mPinned; if (mPinned) mPinButton->changeWidgetSkin ("PinDown"); else mPinButton->changeWidgetSkin ("PinUp"); onPinToggled(); } void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) onPinButtonPressed(mPinButton, 0, 0, MyGUI::MouseButton::Left); } void WindowPinnableBase::setPinButtonVisible(bool visible) { mPinButton->setVisible(visible); } } openmw-openmw-0.48.0/apps/openmw/mwgui/windowpinnablebase.hpp000066400000000000000000000011511445372753700244160ustar00rootroot00000000000000#ifndef MWGUI_WINDOW_PINNABLE_BASE_H #define MWGUI_WINDOW_PINNABLE_BASE_H #include "windowbase.hpp" namespace MWGui { class WindowPinnableBase: public WindowBase { public: WindowPinnableBase(const std::string& parLayout); bool pinned() { return mPinned; } void setPinned (bool pinned); void setPinButtonVisible(bool visible); private: void onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id); protected: virtual void onPinToggled() = 0; MyGUI::Widget* mPinButton; bool mPinned; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/000077500000000000000000000000001445372753700204075ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwinput/actionmanager.cpp000066400000000000000000000516141445372753700237320ustar00rootroot00000000000000#include "actionmanager.hpp" #include #include #include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwgui/messagebox.hpp" #include "actions.hpp" #include "bindingsmanager.hpp" namespace MWInput { ActionManager::ActionManager(BindingsManager* bindingsManager, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler) : mBindingsManager(bindingsManager) , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureOperation(screenCaptureOperation) , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mSneaking(false) , mAttemptJump(false) , mTimeIdle(0.f) { } void ActionManager::update(float dt, bool triedToMove) { // Disable movement in Gui mode if (MWBase::Environment::get().getWindowManager()->isGuiMode() || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) { mAttemptJump = false; return; } // Configure player movement according to keyboard input. Actual movement will // be done in the physics system. if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { bool alwaysRunAllowed = false; MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); if (mBindingsManager->actionIsActive(A_MoveLeft) != mBindingsManager->actionIsActive(A_MoveRight)) { alwaysRunAllowed = true; triedToMove = true; player.setLeftRight(mBindingsManager->actionIsActive(A_MoveRight) ? 1 : -1); } if (mBindingsManager->actionIsActive(A_MoveForward) != mBindingsManager->actionIsActive(A_MoveBackward)) { alwaysRunAllowed = true; triedToMove = true; player.setAutoMove (false); player.setForwardBackward(mBindingsManager->actionIsActive(A_MoveForward) ? 1 : -1); } if (player.getAutoMove()) { alwaysRunAllowed = true; triedToMove = true; player.setForwardBackward (1); } if (mAttemptJump && MWBase::Environment::get().getInputManager()->getControlSwitch("playerjumping")) { player.setUpDown(1); triedToMove = true; } // if player tried to start moving, but can't (due to being overencumbered), display a notification. if (triedToMove) { MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) { player.setAutoMove (false); std::vector msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes(); const std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [](MWGui::MessageBox*& msgbox) { return (msgbox->getMessage() == "#{sNotifyMessage59}"); }); // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new one. if (it != msgboxs.end()) (*it)->mCurrentTime = 0; else MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); } } if (triedToMove) MWBase::Environment::get().getInputManager()->resetIdleTime(); static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); if (!isToggleSneak) { if(!MWBase::Environment::get().getInputManager()->joystickLastUsed()) player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); } float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); bool isRunning = osg::Vec2f(xAxis * 2 - 1, yAxis * 2 - 1).length2() > 0.25f; if ((mAlwaysRunActive && alwaysRunAllowed) || isRunning) player.setRunState(!mBindingsManager->actionIsActive(A_Run)); else player.setRunState(mBindingsManager->actionIsActive(A_Run)); } if (mBindingsManager->actionIsActive(A_MoveForward) || mBindingsManager->actionIsActive(A_MoveBackward) || mBindingsManager->actionIsActive(A_MoveLeft) || mBindingsManager->actionIsActive(A_MoveRight) || mBindingsManager->actionIsActive(A_Jump) || mBindingsManager->actionIsActive(A_Sneak) || mBindingsManager->actionIsActive(A_TogglePOV) || mBindingsManager->actionIsActive(A_ZoomIn) || mBindingsManager->actionIsActive(A_ZoomOut)) { resetIdleTime(); } else mTimeIdle += dt; mAttemptJump = false; } void ActionManager::resetIdleTime() { mTimeIdle = 0.f; } void ActionManager::executeAction(int action) { MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); const auto inputManager = MWBase::Environment::get().getInputManager(); const auto windowManager = MWBase::Environment::get().getWindowManager(); // trigger action activated switch (action) { case A_GameMenu: toggleMainMenu (); break; case A_Screenshot: screenshot(); break; case A_Inventory: toggleInventory (); break; case A_Console: toggleConsole (); break; case A_Activate: inputManager->resetIdleTime(); activate(); break; case A_MoveLeft: case A_MoveRight: case A_MoveForward: case A_MoveBackward: handleGuiArrowKey(action); break; case A_Journal: toggleJournal(); break; case A_AutoMove: toggleAutoMove(); break; case A_AlwaysRun: toggleWalking(); break; case A_ToggleWeapon: toggleWeapon(); break; case A_Rest: rest(); break; case A_ToggleSpell: toggleSpell(); break; case A_QuickKey1: quickKey(1); break; case A_QuickKey2: quickKey(2); break; case A_QuickKey3: quickKey(3); break; case A_QuickKey4: quickKey(4); break; case A_QuickKey5: quickKey(5); break; case A_QuickKey6: quickKey(6); break; case A_QuickKey7: quickKey(7); break; case A_QuickKey8: quickKey(8); break; case A_QuickKey9: quickKey(9); break; case A_QuickKey10: quickKey(10); break; case A_QuickKeysMenu: showQuickKeysMenu(); break; case A_ToggleHUD: windowManager->toggleHud(); break; case A_ToggleDebug: windowManager->toggleDebugWindow(); break; case A_TogglePostProcessorHUD: windowManager->togglePostProcessorHud(); break; case A_QuickSave: quickSave(); break; case A_QuickLoad: quickLoad(); break; case A_CycleSpellLeft: if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) MWBase::Environment::get().getWindowManager()->cycleSpell(false); break; case A_CycleSpellRight: if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) MWBase::Environment::get().getWindowManager()->cycleSpell(true); break; case A_CycleWeaponLeft: if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) MWBase::Environment::get().getWindowManager()->cycleWeapon(false); break; case A_CycleWeaponRight: if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) MWBase::Environment::get().getWindowManager()->cycleWeapon(true); break; case A_Sneak: static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); if (isToggleSneak) { toggleSneaking(); } break; } } bool ActionManager::checkAllowedToUseItems() const { MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { // Cannot use items or spells while in werewolf form MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return false; } return true; } void ActionManager::screenshot() { const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); } else { osg::ref_ptr screenshot (new osg::Image); if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) { (*mScreenCaptureOperation) (*(screenshot.get()), 0); // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason } } } void ActionManager::toggleMainMenu() { if (MyGUI::InputManager::getInstance().isModalAny()) { MWBase::Environment::get().getWindowManager()->exitCurrentModal(); return; } if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) { MWBase::Environment::get().getWindowManager()->toggleConsole(); return; } if (MWBase::Environment::get().getWindowManager()->isPostProcessorHudVisible()) { MWBase::Environment::get().getWindowManager()->togglePostProcessorHud(); return; } if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } else //Close current GUI { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } } void ActionManager::toggleSpell() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the magic window is accessible if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; if (!checkAllowedToUseItems()) return; // Not allowed if no spell selected MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); MWWorld::InventoryStore& inventory = player.getPlayer().getClass().getInventoryStore(player.getPlayer()); if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() && inventory.getSelectedEnchantItem() == inventory.end()) return; if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) return; MWMechanics::DrawState state = player.getDrawState(); if (state == MWMechanics::DrawState::Weapon || state == MWMechanics::DrawState::Nothing) player.setDrawState(MWMechanics::DrawState::Spell); else player.setDrawState(MWMechanics::DrawState::Nothing); } void ActionManager::quickLoad() { if (!MyGUI::InputManager::getInstance().isModalAny()) MWBase::Environment::get().getStateManager()->quickLoad(); } void ActionManager::quickSave() { if (!MyGUI::InputManager::getInstance().isModalAny()) MWBase::Environment::get().getStateManager()->quickSave(); } void ActionManager::toggleWeapon() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the inventory window is accessible if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); // We want to interrupt animation only if attack is preparing, but still is not triggered // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player.getPlayer())) player.setAttackingOrSpell(false); else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) return; MWMechanics::DrawState state = player.getDrawState(); if (state == MWMechanics::DrawState::Spell || state == MWMechanics::DrawState::Nothing) player.setDrawState(MWMechanics::DrawState::Weapon); else player.setDrawState(MWMechanics::DrawState::Nothing); } void ActionManager::rest() { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; if (!MWBase::Environment::get().getWindowManager()->getRestEnabled() || MWBase::Environment::get().getWindowManager()->isGuiMode()) return; MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest); //Open rest GUI } void ActionManager::toggleInventory() { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; if (MyGUI::InputManager::getInstance().isModalAny()) return; if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) return; // Toggle between game mode and inventory mode if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory); else { MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); if(mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) MWBase::Environment::get().getWindowManager()->popGuiMode(); } // .. but don't touch any other mode, except container. } void ActionManager::toggleConsole() { if (MyGUI::InputManager::getInstance().isModalAny()) return; MWBase::Environment::get().getWindowManager()->toggleConsole(); } void ActionManager::toggleJournal() { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; if (MyGUI::InputManager::getInstance ().isModalAny()) return; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); if (windowManager->getMode() != MWGui::GM_Journal && windowManager->getMode() != MWGui::GM_MainMenu && windowManager->getMode() != MWGui::GM_Settings && windowManager->getJournalAllowed()) { windowManager->pushGuiMode(MWGui::GM_Journal); } else if (windowManager->containsMode(MWGui::GM_Journal)) { windowManager->removeGuiMode(MWGui::GM_Journal); } } void ActionManager::quickKey (int index) { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic")) return; if (!checkAllowedToUseItems()) return; if (MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")!=-1) return; if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->activateQuickKey (index); } void ActionManager::showQuickKeysMenu() { if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } if (MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate") != -1) return; if (!checkAllowedToUseItems()) return; MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); } void ActionManager::activate() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); if (!SDL_IsTextInputActive() && !mBindingsManager->isLeftOrRightButton(A_Activate, joystickUsed)) MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0, false); } else if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.activate(); } } void ActionManager::toggleAutoMove() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.setAutoMove (!player.getAutoMove()); } } void ActionManager::toggleWalking() { if (MWBase::Environment::get().getWindowManager()->isGuiMode() || SDL_IsTextInputActive()) return; mAlwaysRunActive = !mAlwaysRunActive; Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); } void ActionManager::toggleSneaking() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; mSneaking = !mSneaking; MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.setSneak(mSneaking); } void ActionManager::handleGuiArrowKey(int action) { bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); // This is currently keyboard-specific code // TODO: see if GUI controls can be refactored into a single function if (joystickUsed) return; if (SDL_IsTextInputActive()) return; if (mBindingsManager->isLeftOrRightButton(action, joystickUsed)) return; MyGUI::KeyCode key; switch (action) { case A_MoveLeft: key = MyGUI::KeyCode::ArrowLeft; break; case A_MoveRight: key = MyGUI::KeyCode::ArrowRight; break; case A_MoveForward: key = MyGUI::KeyCode::ArrowUp; break; case A_MoveBackward: default: key = MyGUI::KeyCode::ArrowDown; break; } MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); } } openmw-openmw-0.48.0/apps/openmw/mwinput/actionmanager.hpp000066400000000000000000000035511445372753700237340ustar00rootroot00000000000000#ifndef MWINPUT_ACTIONMANAGER_H #define MWINPUT_ACTIONMANAGER_H #include #include namespace osgViewer { class Viewer; class ScreenCaptureHandler; } namespace MWInput { class BindingsManager; class ActionManager { public: ActionManager(BindingsManager* bindingsManager, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler); void update(float dt, bool triedToMove); void executeAction(int action); bool checkAllowedToUseItems() const; void toggleMainMenu(); void toggleSpell(); void toggleWeapon(); void toggleInventory(); void toggleConsole(); void screenshot(); void toggleJournal(); void activate(); void toggleWalking(); void toggleSneaking(); void toggleAutoMove(); void rest(); void quickLoad(); void quickSave(); void quickKey (int index); void showQuickKeysMenu(); void resetIdleTime(); float getIdleTime() const { return mTimeIdle; } bool isAlwaysRunActive() const { return mAlwaysRunActive; }; bool isSneaking() const { return mSneaking; }; void setAttemptJump(bool enabled) { mAttemptJump = enabled; } private: void handleGuiArrowKey(int action); BindingsManager* mBindingsManager; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation; bool mAlwaysRunActive; bool mSneaking; bool mAttemptJump; float mTimeIdle; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/actions.hpp000066400000000000000000000037671445372753700225750ustar00rootroot00000000000000#ifndef MWINPUT_ACTIONS_H #define MWINPUT_ACTIONS_H namespace MWInput { enum Actions { // please add new actions at the bottom, in order to preserve the channel IDs in the key configuration files A_GameMenu, A_Unused, A_Screenshot, // Take a screenshot A_Inventory, // Toggle inventory screen A_Console, // Toggle console screen A_MoveLeft, // Move player left / right A_MoveRight, A_MoveForward, // Forward / Backward A_MoveBackward, A_Activate, A_Use, //Use weapon, spell, etc. A_Jump, A_AutoMove, //Toggle Auto-move forward A_Rest, //Rest A_Journal, //Journal A_Weapon, //Draw/Sheath weapon A_Spell, //Ready/Unready Casting A_Run, //Run when held A_CycleSpellLeft, //cycling through spells A_CycleSpellRight, A_CycleWeaponLeft, //Cycling through weapons A_CycleWeaponRight, A_ToggleSneak, //Toggles Sneak A_AlwaysRun, //Toggle Walking/Running A_Sneak, A_QuickSave, A_QuickLoad, A_QuickMenu, A_ToggleWeapon, A_ToggleSpell, A_TogglePOV, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_QuickKeysMenu, A_ToggleHUD, A_ToggleDebug, A_LookUpDown, //Joystick look A_LookLeftRight, A_MoveForwardBackward, A_MoveLeftRight, A_ZoomIn, A_ZoomOut, A_TogglePostProcessorHUD, A_Last // Marker for the last item }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/bindingsmanager.cpp000066400000000000000000000767751445372753700242710ustar00rootroot00000000000000#include "bindingsmanager.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" #include "actions.hpp" namespace MWInput { static const int sFakeDeviceId = 1; //As we only support one controller at a time, use a fake deviceID so we don't lose bindings when switching controllers void clearAllKeyBindings(ICS::InputControlSystem* inputBinder, ICS::Control* control) { // right now we don't really need multiple bindings for the same action, so remove all others first if (inputBinder->getKeyBinding(control, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) inputBinder->removeKeyBinding(inputBinder->getKeyBinding(control, ICS::Control::INCREASE)); if (inputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) inputBinder->removeMouseButtonBinding(inputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE)); if (inputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) != ICS::InputControlSystem::MouseWheelClick::UNASSIGNED) inputBinder->removeMouseWheelBinding(inputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE)); } void clearAllControllerBindings(ICS::InputControlSystem* inputBinder, ICS::Control* control) { // right now we don't really need multiple bindings for the same action, so remove all others first if (inputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) inputBinder->removeJoystickAxisBinding(sFakeDeviceId, inputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); if (inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) inputBinder->removeJoystickButtonBinding(sFakeDeviceId, inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); } class InputControlSystem : public ICS::InputControlSystem { public: InputControlSystem(const std::string& bindingsFile) : ICS::InputControlSystem(bindingsFile, true, nullptr, nullptr, A_Last) { } }; class BindingsListener : public ICS::ChannelListener, public ICS::DetectingBindingListener { public: BindingsListener(ICS::InputControlSystem* inputBinder, BindingsManager* bindingsManager) : mInputBinder(inputBinder) , mBindingsManager(bindingsManager) , mDetectingKeyboard(false) { } virtual ~BindingsListener() = default; void channelChanged(ICS::Channel* channel, float currentValue, float previousValue) override { int action = channel->getNumber(); mBindingsManager->actionValueChanged(action, currentValue, previousValue); } void keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , SDL_Scancode key, ICS::Control::ControlChangingDirection direction) override { //Disallow binding escape key if (key==SDL_SCANCODE_ESCAPE) { //Stop binding if esc pressed mInputBinder->cancelDetectingBindingState(); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); return; } // Disallow binding reserved keys if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10) return; #ifndef __APPLE__ // Disallow binding Windows/Meta keys if (key == SDL_SCANCODE_LGUI || key == SDL_SCANCODE_RGUI) return; #endif if (!mDetectingKeyboard) return; clearAllKeyBindings(mInputBinder, control); control->setInitialValue(0.0f); ICS::DetectingBindingListener::keyBindingDetected(ICS, control, key, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } void mouseAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction) override { // we don't want mouse movement bindings return; } void mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , unsigned int button, ICS::Control::ControlChangingDirection direction) override { if (!mDetectingKeyboard) return; clearAllKeyBindings(mInputBinder, control); control->setInitialValue(0.0f); ICS::DetectingBindingListener::mouseButtonBindingDetected(ICS, control, button, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } void mouseWheelBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control , ICS::InputControlSystem::MouseWheelClick click, ICS::Control::ControlChangingDirection direction) override { if (!mDetectingKeyboard) return; clearAllKeyBindings(mInputBinder, control); control->setInitialValue(0.0f); ICS::DetectingBindingListener::mouseWheelBindingDetected(ICS, control, click, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } void joystickAxisBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control , int axis, ICS::Control::ControlChangingDirection direction) override { //only allow binding to the trigers if (axis != SDL_CONTROLLER_AXIS_TRIGGERLEFT && axis != SDL_CONTROLLER_AXIS_TRIGGERRIGHT) return; if (mDetectingKeyboard) return; clearAllControllerBindings(mInputBinder, control); control->setValue(0.5f); //axis bindings must start at 0.5 control->setInitialValue(0.5f); ICS::DetectingBindingListener::joystickAxisBindingDetected(ICS, deviceID, control, axis, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } void joystickButtonBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control , unsigned int button, ICS::Control::ControlChangingDirection direction) override { if (mDetectingKeyboard) return; clearAllControllerBindings(mInputBinder,control); control->setInitialValue(0.0f); ICS::DetectingBindingListener::joystickButtonBindingDetected (ICS, deviceID, control, button, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } void setDetectingKeyboard(bool detecting) { mDetectingKeyboard = detecting; } private: ICS::InputControlSystem* mInputBinder; BindingsManager* mBindingsManager; bool mDetectingKeyboard; }; BindingsManager::BindingsManager(const std::string& userFile, bool userFileExists) : mUserFile(userFile) , mDragDrop(false) { std::string file = userFileExists ? userFile : ""; mInputBinder = std::make_unique(file); mListener = std::make_unique(mInputBinder.get(), this); mInputBinder->setDetectingBindingListener(mListener.get()); loadKeyDefaults(); loadControllerDefaults(); for (int i = 0; i < A_Last; ++i) { mInputBinder->getChannel(i)->addListener(mListener.get()); } } void BindingsManager::setDragDrop(bool dragDrop) { mDragDrop = dragDrop; } BindingsManager::~BindingsManager() { mInputBinder->save(mUserFile); } void BindingsManager::update(float dt) { // update values of channels (as a result of pressed keys) mInputBinder->update(dt); } bool BindingsManager::isLeftOrRightButton(int action, bool joystick) const { int mouseBinding = mInputBinder->getMouseButtonBinding(mInputBinder->getControl(action), ICS::Control::INCREASE); if (mouseBinding != ICS_MAX_DEVICE_BUTTONS) return true; int buttonBinding = mInputBinder->getJoystickButtonBinding(mInputBinder->getControl(action), sFakeDeviceId, ICS::Control::INCREASE); if (joystick && (buttonBinding == 0 || buttonBinding == 1)) return true; return false; } void BindingsManager::setPlayerControlsEnabled(bool enabled) { int playerChannels[] = {A_AutoMove, A_AlwaysRun, A_ToggleWeapon, A_ToggleSpell, A_Rest, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_Use, A_Journal}; for(int pc : playerChannels) { mInputBinder->getChannel(pc)->setEnabled(enabled); } } void BindingsManager::setJoystickDeadZone(float deadZone) { mInputBinder->setJoystickDeadZone(deadZone); } float BindingsManager::getActionValue (int id) const { return mInputBinder->getChannel(id)->getValue(); } bool BindingsManager::actionIsActive (int id) const { return getActionValue(id) == 1.0; } void BindingsManager::loadKeyDefaults (bool force) { // using hardcoded key defaults is inevitable, if we want the configuration files to stay valid // across different versions of OpenMW (in the case where another input action is added) std::map defaultKeyBindings; //Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format defaultKeyBindings[A_Activate] = SDL_SCANCODE_SPACE; defaultKeyBindings[A_MoveBackward] = SDL_SCANCODE_S; defaultKeyBindings[A_MoveForward] = SDL_SCANCODE_W; defaultKeyBindings[A_MoveLeft] = SDL_SCANCODE_A; defaultKeyBindings[A_MoveRight] = SDL_SCANCODE_D; defaultKeyBindings[A_ToggleWeapon] = SDL_SCANCODE_F; defaultKeyBindings[A_ToggleSpell] = SDL_SCANCODE_R; defaultKeyBindings[A_CycleSpellLeft] = SDL_SCANCODE_MINUS; defaultKeyBindings[A_CycleSpellRight] = SDL_SCANCODE_EQUALS; defaultKeyBindings[A_CycleWeaponLeft] = SDL_SCANCODE_LEFTBRACKET; defaultKeyBindings[A_CycleWeaponRight] = SDL_SCANCODE_RIGHTBRACKET; defaultKeyBindings[A_QuickKeysMenu] = SDL_SCANCODE_F1; defaultKeyBindings[A_Console] = SDL_SCANCODE_GRAVE; defaultKeyBindings[A_Run] = SDL_SCANCODE_LSHIFT; defaultKeyBindings[A_Sneak] = SDL_SCANCODE_LCTRL; defaultKeyBindings[A_AutoMove] = SDL_SCANCODE_Q; defaultKeyBindings[A_Jump] = SDL_SCANCODE_E; defaultKeyBindings[A_Journal] = SDL_SCANCODE_J; defaultKeyBindings[A_Rest] = SDL_SCANCODE_T; defaultKeyBindings[A_GameMenu] = SDL_SCANCODE_ESCAPE; defaultKeyBindings[A_TogglePOV] = SDL_SCANCODE_TAB; defaultKeyBindings[A_QuickKey1] = SDL_SCANCODE_1; defaultKeyBindings[A_QuickKey2] = SDL_SCANCODE_2; defaultKeyBindings[A_QuickKey3] = SDL_SCANCODE_3; defaultKeyBindings[A_QuickKey4] = SDL_SCANCODE_4; defaultKeyBindings[A_QuickKey5] = SDL_SCANCODE_5; defaultKeyBindings[A_QuickKey6] = SDL_SCANCODE_6; defaultKeyBindings[A_QuickKey7] = SDL_SCANCODE_7; defaultKeyBindings[A_QuickKey8] = SDL_SCANCODE_8; defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; defaultKeyBindings[A_ToggleDebug] = SDL_SCANCODE_F10; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2; std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; defaultMouseButtonBindings[A_Use] = SDL_BUTTON_LEFT; std::map defaultMouseWheelBindings; defaultMouseWheelBindings[A_ZoomIn] = ICS::InputControlSystem::MouseWheelClick::UP; defaultMouseWheelBindings[A_ZoomOut] = ICS::InputControlSystem::MouseWheelClick::DOWN; for (int i = 0; i < A_Last; ++i) { ICS::Control* control; bool controlExists = mInputBinder->getChannel(i)->getControlsCount() != 0; if (!controlExists) { control = new ICS::Control(std::to_string(i), false, true, 0, ICS::ICS_MAX, ICS::ICS_MAX); mInputBinder->addControl(control); control->attachChannel(mInputBinder->getChannel(i), ICS::Channel::DIRECT); } else { control = mInputBinder->getChannel(i)->getAttachedControls().front().control; } if (!controlExists || force || (mInputBinder->getKeyBinding(control, ICS::Control::INCREASE) == SDL_SCANCODE_UNKNOWN && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) { clearAllKeyBindings(mInputBinder.get(), control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addKeyBinding(control, defaultKeyBindings[i], ICS::Control::INCREASE); } else if (defaultMouseButtonBindings.find(i) != defaultMouseButtonBindings.end() && (force || !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addMouseButtonBinding(control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } else if (defaultMouseWheelBindings.find(i) != defaultMouseWheelBindings.end() && (force || !mInputBinder->isMouseWheelBound(defaultMouseWheelBindings[i]))) { control->setInitialValue(0.f); mInputBinder->addMouseWheelBinding(control, defaultMouseWheelBindings[i], ICS::Control::INCREASE); } if (i == A_LookLeftRight && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_4) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_6)) { mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_6, ICS::Control::INCREASE); mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_4, ICS::Control::DECREASE); } if (i == A_LookUpDown && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_8) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_2)) { mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_2, ICS::Control::INCREASE); mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_8, ICS::Control::DECREASE); } } } } void BindingsManager::loadControllerDefaults(bool force) { // using hardcoded key defaults is inevitable, if we want the configuration files to stay valid // across different versions of OpenMW (in the case where another input action is added) std::map defaultButtonBindings; defaultButtonBindings[A_Activate] = SDL_CONTROLLER_BUTTON_A; defaultButtonBindings[A_ToggleWeapon] = SDL_CONTROLLER_BUTTON_X; defaultButtonBindings[A_ToggleSpell] = SDL_CONTROLLER_BUTTON_Y; //defaultButtonBindings[A_QuickButtonsMenu] = SDL_GetButtonFromScancode(SDL_SCANCODE_F1); // Need to implement, should be ToggleSpell(5) AND Wait(9) defaultButtonBindings[A_Sneak] = SDL_CONTROLLER_BUTTON_LEFTSTICK; defaultButtonBindings[A_Journal] = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; defaultButtonBindings[A_Rest] = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; defaultButtonBindings[A_TogglePOV] = SDL_CONTROLLER_BUTTON_RIGHTSTICK; defaultButtonBindings[A_Inventory] = SDL_CONTROLLER_BUTTON_B; defaultButtonBindings[A_GameMenu] = SDL_CONTROLLER_BUTTON_START; defaultButtonBindings[A_QuickSave] = SDL_CONTROLLER_BUTTON_GUIDE; defaultButtonBindings[A_MoveForward] = SDL_CONTROLLER_BUTTON_DPAD_UP; defaultButtonBindings[A_MoveLeft] = SDL_CONTROLLER_BUTTON_DPAD_LEFT; defaultButtonBindings[A_MoveBackward] = SDL_CONTROLLER_BUTTON_DPAD_DOWN; defaultButtonBindings[A_MoveRight] = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; std::map defaultAxisBindings; defaultAxisBindings[A_MoveForwardBackward] = SDL_CONTROLLER_AXIS_LEFTY; defaultAxisBindings[A_MoveLeftRight] = SDL_CONTROLLER_AXIS_LEFTX; defaultAxisBindings[A_LookUpDown] = SDL_CONTROLLER_AXIS_RIGHTY; defaultAxisBindings[A_LookLeftRight] = SDL_CONTROLLER_AXIS_RIGHTX; defaultAxisBindings[A_Use] = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; defaultAxisBindings[A_Jump] = SDL_CONTROLLER_AXIS_TRIGGERLEFT; for (int i = 0; i < A_Last; i++) { ICS::Control* control; bool controlExists = mInputBinder->getChannel(i)->getControlsCount() != 0; if (!controlExists) { float initial; if (defaultAxisBindings.find(i) == defaultAxisBindings.end()) initial = 0.0f; else initial = 0.5f; control = new ICS::Control(std::to_string(i), false, true, initial, ICS::ICS_MAX, ICS::ICS_MAX); mInputBinder->addControl(control); control->attachChannel(mInputBinder->getChannel(i), ICS::Channel::DIRECT); } else { control = mInputBinder->getChannel(i)->getAttachedControls().front().control; } if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED && mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS)) { clearAllControllerBindings(mInputBinder.get(), control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addJoystickButtonBinding(control, sFakeDeviceId, defaultButtonBindings[i], ICS::Control::INCREASE); } else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && (force || !mInputBinder->isJoystickAxisBound(sFakeDeviceId, defaultAxisBindings[i]))) { control->setValue(0.5f); control->setInitialValue(0.5f); mInputBinder->addJoystickAxisBinding(control, sFakeDeviceId, defaultAxisBindings[i], ICS::Control::INCREASE); } } } } std::string BindingsManager::getActionDescription(int action) { switch (action) { case A_Screenshot: return "#{SettingsMenu:Screenshot}"; case A_ZoomIn: return "#{SettingsMenu:CameraZoomIn}"; case A_ZoomOut: return "#{SettingsMenu:CameraZoomOut}"; case A_ToggleHUD: return "#{SettingsMenu:ToggleHUD}"; case A_Use: return "#{sUse}"; case A_Activate: return "#{sActivate}"; case A_MoveBackward: return "#{sBack}"; case A_MoveForward: return "#{sForward}"; case A_MoveLeft: return "#{sLeft}"; case A_MoveRight: return "#{sRight}"; case A_ToggleWeapon: return "#{sReady_Weapon}"; case A_ToggleSpell: return "#{sReady_Magic}"; case A_CycleSpellLeft: return "#{sPrevSpell}"; case A_CycleSpellRight: return "#{sNextSpell}"; case A_CycleWeaponLeft: return "#{sPrevWeapon}"; case A_CycleWeaponRight: return "#{sNextWeapon}"; case A_Console: return "#{sConsoleTitle}"; case A_Run: return "#{sRun}"; case A_Sneak: return "#{sCrouch_Sneak}"; case A_AutoMove: return "#{sAuto_Run}"; case A_Jump: return "#{sJump}"; case A_Journal: return "#{sJournal}"; case A_Rest: return "#{sRestKey}"; case A_Inventory: return "#{sInventory}"; case A_TogglePOV: return "#{sTogglePOVCmd}"; case A_QuickKeysMenu: return "#{sQuickMenu}"; case A_QuickKey1: return "#{sQuick1Cmd}"; case A_QuickKey2: return "#{sQuick2Cmd}"; case A_QuickKey3: return "#{sQuick3Cmd}"; case A_QuickKey4: return "#{sQuick4Cmd}"; case A_QuickKey5: return "#{sQuick5Cmd}"; case A_QuickKey6: return "#{sQuick6Cmd}"; case A_QuickKey7: return "#{sQuick7Cmd}"; case A_QuickKey8: return "#{sQuick8Cmd}"; case A_QuickKey9: return "#{sQuick9Cmd}"; case A_QuickKey10: return "#{sQuick10Cmd}"; case A_AlwaysRun: return "#{sAlways_Run}"; case A_QuickSave: return "#{sQuickSaveCmd}"; case A_QuickLoad: return "#{sQuickLoadCmd}"; case A_TogglePostProcessorHUD: return "#{SettingsMenu:TogglePostProcessorHUD}"; default: return std::string(); // not configurable } } std::string BindingsManager::getActionKeyBindingName(int action) { if (mInputBinder->getChannel(action)->getControlsCount() == 0) return "#{sNone}"; ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; SDL_Scancode key = mInputBinder->getKeyBinding(c, ICS::Control::INCREASE); unsigned int mouse = mInputBinder->getMouseButtonBinding(c, ICS::Control::INCREASE); ICS::InputControlSystem::MouseWheelClick wheel = mInputBinder->getMouseWheelBinding(c, ICS::Control::INCREASE); if (key != SDL_SCANCODE_UNKNOWN) return MyGUI::TextIterator::toTagsString(mInputBinder->scancodeToString(key)); else if (mouse != ICS_MAX_DEVICE_BUTTONS) return "#{sMouse} " + std::to_string(mouse); else if (wheel != ICS::InputControlSystem::MouseWheelClick::UNASSIGNED) switch (wheel) { case ICS::InputControlSystem::MouseWheelClick::UP: return "Mouse Wheel Up"; case ICS::InputControlSystem::MouseWheelClick::DOWN: return "Mouse Wheel Down"; case ICS::InputControlSystem::MouseWheelClick::RIGHT: return "Mouse Wheel Right"; case ICS::InputControlSystem::MouseWheelClick::LEFT: return "Mouse Wheel Left"; default: return "#{sNone}"; } else return "#{sNone}"; } std::string BindingsManager::getActionControllerBindingName(int action) { if (mInputBinder->getChannel(action)->getControlsCount() == 0) return "#{sNone}"; ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS::InputControlSystem::UNASSIGNED) return SDLUtil::sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) return SDLUtil::sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else return "#{sNone}"; } std::vector BindingsManager::getActionKeySorting() { static const std::vector actions { A_MoveForward, A_MoveBackward, A_MoveLeft, A_MoveRight, A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Run, A_AlwaysRun, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, A_ToggleSpell, A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_TogglePostProcessorHUD }; return actions; } std::vector BindingsManager::getActionControllerSorting() { static const std::vector actions { A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, A_ToggleSpell, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight }; return actions; } void BindingsManager::enableDetectingBindingMode(int action, bool keyboard) { mListener->setDetectingKeyboard(keyboard); ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; mInputBinder->enableDetectingBindingState(c, ICS::Control::INCREASE); } bool BindingsManager::isDetectingBindingState() const { return mInputBinder->detectingBindingState(); } void BindingsManager::mousePressed(const SDL_MouseButtonEvent &arg, int deviceID) { mInputBinder->mousePressed(arg, deviceID); } void BindingsManager::mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID) { mInputBinder->mouseReleased(arg, deviceID); } void BindingsManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg) { mInputBinder->mouseMoved(arg); } void BindingsManager::mouseWheelMoved(const SDL_MouseWheelEvent &arg) { mInputBinder->mouseWheelMoved(arg); } void BindingsManager::keyPressed(const SDL_KeyboardEvent &arg) { mInputBinder->keyPressed(arg); } void BindingsManager::keyReleased(const SDL_KeyboardEvent &arg) { mInputBinder->keyReleased(arg); } void BindingsManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) { mInputBinder->controllerAdded(deviceID, arg); } void BindingsManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) { mInputBinder->controllerRemoved(arg); } void BindingsManager::controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) { mInputBinder->buttonPressed(deviceID, arg); } void BindingsManager::controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) { mInputBinder->buttonReleased(deviceID, arg); } void BindingsManager::controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) { mInputBinder->axisMoved(deviceID, arg); } SDL_Scancode BindingsManager::getKeyBinding(int actionId) { return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE); } SDL_GameController* BindingsManager::getControllerOrNull() const { const auto& controllers = mInputBinder->getJoystickInstanceMap(); if (controllers.empty()) return nullptr; else return controllers.begin()->second; } void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) { MWBase::Environment::get().getInputManager()->resetIdleTime(); if (mDragDrop && action != A_GameMenu && action != A_Inventory) return; if ((previousValue == 1 || previousValue == 0) && (currentValue==1 || currentValue==0)) { //Is a normal button press, so don't change it at all } //Otherwise only trigger button presses as they go through specific points else if (previousValue >= 0.8 && currentValue < 0.8) { currentValue = 0.0; previousValue = 1.0; } else if (previousValue <= 0.6 && currentValue > 0.6) { currentValue = 1.0; previousValue = 0.0; } else { //If it's not switching between those values, ignore the channel change. return; } if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); if (action == A_Use) { if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) action = A_CycleWeaponRight; else if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) action = A_CycleSpellRight; else { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); MWMechanics::DrawState state = player.getDrawState(); player.setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState::Nothing); } } else if (action == A_Jump) { if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) action = A_CycleWeaponLeft; else if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) action = A_CycleSpellLeft; else MWBase::Environment::get().getInputManager()->setAttemptJump(currentValue == 1.0 && previousValue == 0.0); } } if (currentValue == 1) MWBase::Environment::get().getInputManager()->executeAction(action); } } openmw-openmw-0.48.0/apps/openmw/mwinput/bindingsmanager.hpp000066400000000000000000000047241445372753700242570ustar00rootroot00000000000000#ifndef MWINPUT_MWBINDINGSMANAGER_H #define MWINPUT_MWBINDINGSMANAGER_H #include #include #include #include namespace MWInput { class BindingsListener; class InputControlSystem; class BindingsManager { public: BindingsManager(const std::string& userFile, bool userFileExists); virtual ~BindingsManager(); std::string getActionDescription (int action); std::string getActionKeyBindingName (int action); std::string getActionControllerBindingName (int action); std::vector getActionKeySorting(); std::vector getActionControllerSorting(); void enableDetectingBindingMode (int action, bool keyboard); bool isDetectingBindingState() const; void loadKeyDefaults(bool force = false); void loadControllerDefaults(bool force = false); void setDragDrop(bool dragDrop); void update(float dt); void setPlayerControlsEnabled(bool enabled); void setJoystickDeadZone(float deadZone); bool isLeftOrRightButton(int action, bool joystick) const; bool actionIsActive(int id) const; float getActionValue(int id) const; // returns value in range [0, 1] SDL_GameController* getControllerOrNull() const; void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID); void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID); void mouseMoved(const SDLUtil::MouseMotionEvent &arg); void mouseWheelMoved(const SDL_MouseWheelEvent &arg); void keyPressed(const SDL_KeyboardEvent &arg); void keyReleased(const SDL_KeyboardEvent &arg); void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg); void controllerRemoved(const SDL_ControllerDeviceEvent &arg); void controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent &arg); void controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent &arg); void controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent &arg); SDL_Scancode getKeyBinding(int actionId); void actionValueChanged(int action, float currentValue, float previousValue); private: void setupSDLKeyMappings(); std::unique_ptr mInputBinder; std::unique_ptr mListener; std::string mUserFile; bool mDragDrop; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/controllermanager.cpp000066400000000000000000000425431445372753700246410ustar00rootroot00000000000000#include "controllermanager.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" #include "actions.hpp" #include "actionmanager.hpp" #include "bindingsmanager.hpp" #include "mousemanager.hpp" namespace MWInput { ControllerManager::ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, MouseManager* mouseManager, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile) : mBindingsManager(bindingsManager) , mActionManager(actionManager) , mMouseManager(mouseManager) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mGyroAvailable(false) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) , mGamepadGuiCursorEnabled(true) , mGuiCursorEnabled(true) , mJoystickLastUsed(false) , mSneakGamepadShortcut(false) { if (!controllerBindingsFile.empty()) { SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str()); } if (!userControllerBindingsFile.empty()) { SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str()); } // Open all presently connected sticks int numSticks = SDL_NumJoysticks(); for (int i = 0; i < numSticks; i++) { if (SDL_IsGameController(i)) { SDL_ControllerDeviceEvent evt; evt.which = i; static const int fakeDeviceID = 1; ControllerManager::controllerAdded(fakeDeviceID, evt); Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); } else { Log(Debug::Info) << "Detected unusable controller: " << SDL_JoystickNameForIndex(i); } } float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); deadZoneRadius = std::clamp(deadZoneRadius, 0.f, 0.5f); mBindingsManager->setJoystickDeadZone(deadZoneRadius); } void ControllerManager::processChangedSettings(const Settings::CategorySettingVector& changed) { for (const auto& setting : changed) { if (setting.first == "Input" && setting.second == "enable controller") mJoystickEnabled = Settings::Manager::getBool("enable controller", "Input"); } } bool ControllerManager::update(float dt) { if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward) * 2.0f - 1.0f; float zAxis = mBindingsManager->getActionValue(A_LookUpDown) * 2.0f - 1.0f; xAxis *= (1.5f - mBindingsManager->getActionValue(A_Use)); yAxis *= (1.5f - mBindingsManager->getActionValue(A_Use)); // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float mouseWheelMove = -zAxis * dt * 1500.0f; if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) { mMouseManager->injectMouseMove(xMove, yMove, mouseWheelMove); mMouseManager->warpMouse(); MWBase::Environment::get().getWindowManager()->setCursorActive(true); } } // Disable movement in Gui mode if (MWBase::Environment::get().getWindowManager()->isGuiMode() || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) { return false; } MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); bool triedToMove = false; // Configure player movement according to controller input. Actual movement will // be done in the physics system. if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); if (xAxis != 0.5) { triedToMove = true; player.setLeftRight((xAxis - 0.5f) * 2); } if (yAxis != 0.5) { triedToMove = true; player.setAutoMove (false); player.setForwardBackward((0.5f - yAxis) * 2); } if (triedToMove) { mJoystickLastUsed = true; MWBase::Environment::get().getInputManager()->resetIdleTime(); } static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); if (!isToggleSneak) { if (mJoystickLastUsed) { if (mBindingsManager->actionIsActive(A_Sneak)) { if (mSneakToggleShortcutTimer) // New Sneak Button Press { if (mSneakToggleShortcutTimer <= 0.3f) { mSneakGamepadShortcut = true; mActionManager->toggleSneaking(); } else mSneakGamepadShortcut = false; } if (!mActionManager->isSneaking()) mActionManager->toggleSneaking(); mSneakToggleShortcutTimer = 0.f; } else { if (!mSneakGamepadShortcut && mActionManager->isSneaking()) mActionManager->toggleSneaking(); if (mSneakToggleShortcutTimer <= 0.3f) mSneakToggleShortcutTimer += dt; } } else player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); } } return triedToMove; } void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) { if (!mJoystickEnabled || mBindingsManager->isDetectingBindingState()) return; MWBase::Environment::get().getLuaManager()->inputEvent( {MWBase::LuaManager::InputEvent::ControllerPressed, arg.button}); mJoystickLastUsed = true; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { if (gamepadToGuiControl(arg)) return; if (mGamepadGuiCursorEnabled) { // Temporary mouse binding until keyboard controls are available: if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. { bool mousePressSuccess = mMouseManager->injectMouseButtonPress(SDL_BUTTON_LEFT); if (MyGUI::InputManager::getInstance().getMouseFocusWidget()) { MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); if (b && b->getEnabled()) MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); } mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); } } } else mBindingsManager->setPlayerControlsEnabled(true); //esc, to leave initial movie screen auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) mBindingsManager->controllerButtonPressed(deviceID, arg); } void ControllerManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) { if (mBindingsManager->isDetectingBindingState()) { mBindingsManager->controllerButtonReleased(deviceID, arg); return; } if (mJoystickEnabled) { MWBase::Environment::get().getLuaManager()->inputEvent( {MWBase::LuaManager::InputEvent::ControllerReleased, arg.button}); } if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; mJoystickLastUsed = true; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { if (mGamepadGuiCursorEnabled) { // Temporary mouse binding until keyboard controls are available: if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. { bool mousePressSuccess = mMouseManager->injectMouseButtonRelease(SDL_BUTTON_LEFT); if (mBindingsManager->isDetectingBindingState()) // If the player just triggered binding, don't let button release bind. return; mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); } } } else mBindingsManager->setPlayerControlsEnabled(true); //esc, to leave initial movie screen auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->controllerButtonReleased(deviceID, arg); } void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) { if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; mJoystickLastUsed = true; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { gamepadToGuiControl(arg); } else if (mBindingsManager->actionIsActive(A_TogglePOV) && (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)) { // Preview Mode Gamepad Zooming; do not propagate to mBindingsManager return; } mBindingsManager->controllerAxisMoved(deviceID, arg); } void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) { mBindingsManager->controllerAdded(deviceID, arg); enableGyroSensor(); } void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) { mBindingsManager->controllerRemoved(arg); } bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg) { // Presumption of GUI mode will be removed in the future. // MyGUI KeyCodes *may* change. MyGUI::KeyCode key = MyGUI::KeyCode::None; switch (arg.button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: key = MyGUI::KeyCode::ArrowUp; break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = MyGUI::KeyCode::ArrowRight; break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = MyGUI::KeyCode::ArrowDown; break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = MyGUI::KeyCode::ArrowLeft; break; case SDL_CONTROLLER_BUTTON_A: // If we are using the joystick as a GUI mouse, A must be handled via mouse. if (mGamepadGuiCursorEnabled) return false; key = MyGUI::KeyCode::Space; break; case SDL_CONTROLLER_BUTTON_B: if (MyGUI::InputManager::getInstance().isModalAny()) MWBase::Environment::get().getWindowManager()->exitCurrentModal(); else MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return true; case SDL_CONTROLLER_BUTTON_X: key = MyGUI::KeyCode::Semicolon; break; case SDL_CONTROLLER_BUTTON_Y: key = MyGUI::KeyCode::Apostrophe; break; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = MyGUI::KeyCode::Period; break; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = MyGUI::KeyCode::Slash; break; case SDL_CONTROLLER_BUTTON_LEFTSTICK: mGamepadGuiCursorEnabled = !mGamepadGuiCursorEnabled; MWBase::Environment::get().getWindowManager()->setCursorActive(mGamepadGuiCursorEnabled); return true; default: return false; } // Some keys will work even when Text Input windows/modals are in focus. if (SDL_IsTextInputActive()) return false; MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); return true; } bool ControllerManager::gamepadToGuiControl(const SDL_ControllerAxisEvent &arg) { switch (arg.axis) { case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: if (arg.value == 32767) // Treat like a button. MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Minus, 0, false); break; case SDL_CONTROLLER_AXIS_TRIGGERLEFT: if (arg.value == 32767) // Treat like a button. MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Equals, 0, false); break; case SDL_CONTROLLER_AXIS_LEFTX: case SDL_CONTROLLER_AXIS_LEFTY: case SDL_CONTROLLER_AXIS_RIGHTX: case SDL_CONTROLLER_AXIS_RIGHTY: // If we are using the joystick as a GUI mouse, process mouse movement elsewhere. if (mGamepadGuiCursorEnabled) return false; break; default: return false; } return true; } float ControllerManager::getAxisValue(SDL_GameControllerAxis axis) const { SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; if (cntrl) return SDL_GameControllerGetAxis(cntrl, axis) / static_cast(AXIS_MAX_ABSOLUTE_VALUE); else return 0; } bool ControllerManager::isButtonPressed(SDL_GameControllerButton button) const { SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); if (cntrl) return SDL_GameControllerGetButton(cntrl, button) > 0; else return false; } void ControllerManager::enableGyroSensor() { mGyroAvailable = false; #if SDL_VERSION_ATLEAST(2, 0, 14) SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); if (!cntrl) return; if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) return; if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) return; mGyroAvailable = true; #endif } bool ControllerManager::isGyroAvailable() const { return mGyroAvailable; } std::array ControllerManager::getGyroValues() const { float gyro[3] = { 0.f }; #if SDL_VERSION_ATLEAST(2, 0, 14) SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); if (cntrl && mGyroAvailable) SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); #endif return std::array({gyro[0], gyro[1], gyro[2]}); } void ControllerManager::touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) { MWBase::Environment::get().getLuaManager()->inputEvent( { MWBase::LuaManager::InputEvent::TouchMoved, arg }); } void ControllerManager::touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) { MWBase::Environment::get().getLuaManager()->inputEvent( { MWBase::LuaManager::InputEvent::TouchPressed, arg }); } void ControllerManager::touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) { MWBase::Environment::get().getLuaManager()->inputEvent( { MWBase::LuaManager::InputEvent::TouchReleased, arg }); } } openmw-openmw-0.48.0/apps/openmw/mwinput/controllermanager.hpp000066400000000000000000000053441445372753700246440ustar00rootroot00000000000000#ifndef MWINPUT_MWCONTROLLERMANAGER_H #define MWINPUT_MWCONTROLLERMANAGER_H #include #include #include namespace MWInput { class ActionManager; class BindingsManager; class MouseManager; class ControllerManager : public SDLUtil::ControllerListener { public: ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, MouseManager* mouseManager, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile); virtual ~ControllerManager() = default; bool update(float dt); void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) override; void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) override; void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) override; void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override; void controllerRemoved(const SDL_ControllerDeviceEvent &arg) override; void touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) override; void touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) override; void touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) override; void processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } bool joystickLastUsed() const { return mJoystickLastUsed; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; } bool gamepadGuiCursorEnabled() const { return mGamepadGuiCursorEnabled; } float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] bool isButtonPressed(SDL_GameControllerButton button) const; bool isGyroAvailable() const; std::array getGyroValues() const; private: // Return true if GUI consumes input. bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg); bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); void enableGyroSensor(); BindingsManager* mBindingsManager; ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; bool mGyroAvailable; float mGamepadCursorSpeed; float mSneakToggleShortcutTimer; bool mGamepadGuiCursorEnabled; bool mGuiCursorEnabled; bool mJoystickLastUsed; bool mSneakGamepadShortcut; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/controlswitch.cpp000066400000000000000000000064721445372753700240260ustar00rootroot00000000000000#include "controlswitch.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" namespace MWInput { ControlSwitch::ControlSwitch() { clear(); } void ControlSwitch::clear() { mSwitches["playercontrols"] = true; mSwitches["playerfighting"] = true; mSwitches["playerjumping"] = true; mSwitches["playerlooking"] = true; mSwitches["playermagic"] = true; mSwitches["playerviewswitch"] = true; mSwitches["vanitymode"] = true; } bool ControlSwitch::get(std::string_view key) { auto it = mSwitches.find(key); if (it == mSwitches.end()) throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); return it->second; } void ControlSwitch::set(std::string_view key, bool value) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); /// \note 7 switches at all, if-else is relevant if (key == "playercontrols" && !value) { player.setLeftRight(0); player.setForwardBackward(0); player.setAutoMove(false); player.setUpDown(0); } else if (key == "playerjumping" && !value) { /// \fixme maybe crouching at this time player.setUpDown(0); } else if (key == "playerlooking" && !value) { MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); } auto it = mSwitches.find(key); if (it == mSwitches.end()) throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); it->second = value; } void ControlSwitch::write(ESM::ESMWriter& writer, Loading::Listener& /*progress*/) { ESM::ControlsState controls; controls.mViewSwitchDisabled = !mSwitches["playerviewswitch"]; controls.mControlsDisabled = !mSwitches["playercontrols"]; controls.mJumpingDisabled = !mSwitches["playerjumping"]; controls.mLookingDisabled = !mSwitches["playerlooking"]; controls.mVanityModeDisabled = !mSwitches["vanitymode"]; controls.mWeaponDrawingDisabled = !mSwitches["playerfighting"]; controls.mSpellDrawingDisabled = !mSwitches["playermagic"]; writer.startRecord (ESM::REC_INPU); controls.save(writer); writer.endRecord (ESM::REC_INPU); } void ControlSwitch::readRecord(ESM::ESMReader& reader, uint32_t type) { ESM::ControlsState controls; controls.load(reader); set("playerviewswitch", !controls.mViewSwitchDisabled); set("playercontrols", !controls.mControlsDisabled); set("playerjumping", !controls.mJumpingDisabled); set("playerlooking", !controls.mLookingDisabled); set("vanitymode", !controls.mVanityModeDisabled); set("playerfighting", !controls.mWeaponDrawingDisabled); set("playermagic", !controls.mSpellDrawingDisabled); } int ControlSwitch::countSavedGameRecords() const { return 1; } } openmw-openmw-0.48.0/apps/openmw/mwinput/controlswitch.hpp000066400000000000000000000013501445372753700240210ustar00rootroot00000000000000#ifndef MWINPUT_CONTROLSWITCH_H #define MWINPUT_CONTROLSWITCH_H #include #include #include #include namespace ESM { struct ControlsState; class ESMReader; class ESMWriter; } namespace Loading { class Listener; } namespace MWInput { class ControlSwitch { public: ControlSwitch(); bool get(std::string_view key); void set(std::string_view key, bool value); void clear(); void write(ESM::ESMWriter& writer, Loading::Listener& progress); void readRecord(ESM::ESMReader& reader, uint32_t type); int countSavedGameRecords() const; private: std::map> mSwitches; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/gyromanager.cpp000066400000000000000000000100051445372753700234220ustar00rootroot00000000000000#include "gyromanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" namespace MWInput { GyroManager::GyroscopeAxis GyroManager::gyroscopeAxisFromString(std::string_view s) { if (s == "x") return GyroscopeAxis::X; else if (s == "y") return GyroscopeAxis::Y; else if (s == "z") return GyroscopeAxis::Z; else if (s == "-x") return GyroscopeAxis::Minus_X; else if (s == "-y") return GyroscopeAxis::Minus_Y; else if (s == "-z") return GyroscopeAxis::Minus_Z; return GyroscopeAxis::Unknown; } GyroManager::GyroManager() : mEnabled(Settings::Manager::getBool("enable gyroscope", "Input")) , mGuiCursorEnabled(true) , mSensitivityH(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) , mSensitivityV(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) {} void GyroManager::update(float dt, std::array values) const { if (!mGuiCursorEnabled) { float gyroH = getAxisValue(mAxisH, values); float gyroV = getAxisValue(mAxisV, values); if (gyroH == 0.f && gyroV == 0.f) return; float rot[3]; rot[0] = -gyroV * dt * mSensitivityV; rot[1] = 0.0f; rot[2] = -gyroH * dt * mSensitivityH; // Only actually turn player when we're not in vanity mode bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.yaw(-rot[2]); player.pitch(-rot[0]); } else if (!playerLooking) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); } } void GyroManager::processChangedSettings(const Settings::CategorySettingVector& changed) { for (const auto& setting : changed) { if (setting.first != "Input") continue; if (setting.second == "enable gyroscope") mEnabled = Settings::Manager::getBool("enable gyroscope", "Input"); else if (setting.second == "gyro horizontal sensitivity") mSensitivityH = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); else if (setting.second == "gyro vertical sensitivity") mSensitivityV = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); else if (setting.second == "gyro input threshold") mInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); else if (setting.second == "gyro horizontal axis") mAxisH = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); else if (setting.second == "gyro vertical axis") mAxisV = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); } } float GyroManager::getAxisValue(GyroscopeAxis axis, std::array values) const { if (axis == GyroscopeAxis::Unknown) return 0; float value = values[std::abs(axis) - 1]; if (axis < 0) value *= -1; if (std::abs(value) <= mInputThreshold) value = 0; return value; } } openmw-openmw-0.48.0/apps/openmw/mwinput/gyromanager.hpp000066400000000000000000000022621445372753700234350ustar00rootroot00000000000000#ifndef MWINPUT_GYROMANAGER #define MWINPUT_GYROMANAGER #include namespace MWInput { class GyroManager { public: GyroManager(); bool isEnabled() const { return mEnabled; } void update(float dt, std::array values) const; void processChangedSettings(const Settings::CategorySettingVector& changed); void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } private: enum GyroscopeAxis { Unknown = 0, X = 1, Y = 2, Z = 3, Minus_X = -1, Minus_Y = -2, Minus_Z = -3 }; static GyroscopeAxis gyroscopeAxisFromString(std::string_view s); bool mEnabled; bool mGuiCursorEnabled; float mSensitivityH; float mSensitivityV; float mInputThreshold; GyroscopeAxis mAxisH; GyroscopeAxis mAxisV; float getAxisValue(GyroscopeAxis axis, std::array values) const; }; } #endif // !MWINPUT_GYROMANAGER openmw-openmw-0.48.0/apps/openmw/mwinput/inputmanagerimp.cpp000066400000000000000000000176711445372753700243270ustar00rootroot00000000000000#include "inputmanagerimp.hpp" #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "actionmanager.hpp" #include "bindingsmanager.hpp" #include "controllermanager.hpp" #include "controlswitch.hpp" #include "keyboardmanager.hpp" #include "mousemanager.hpp" #include "sensormanager.hpp" #include "gyromanager.hpp" namespace MWInput { InputManager::InputManager( SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab) : mControlsDisabled(false) , mInputWrapper(std::make_unique(window, viewer, grab)) , mBindingsManager(std::make_unique(userFile, userFileExists)) , mControlSwitch(std::make_unique()) , mActionManager(std::make_unique(mBindingsManager.get(), screenCaptureOperation, viewer, screenCaptureHandler)) , mKeyboardManager(std::make_unique(mBindingsManager.get())) , mMouseManager(std::make_unique(mBindingsManager.get(), mInputWrapper.get(), window)) , mControllerManager(std::make_unique(mBindingsManager.get(), mActionManager.get(), mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile)) , mSensorManager(std::make_unique()) , mGyroManager(std::make_unique()) { mInputWrapper->setWindowEventCallback(MWBase::Environment::get().getWindowManager()); mInputWrapper->setKeyboardEventCallback(mKeyboardManager.get()); mInputWrapper->setMouseEventCallback(mMouseManager.get()); mInputWrapper->setControllerEventCallback(mControllerManager.get()); mInputWrapper->setSensorEventCallback(mSensorManager.get()); } void InputManager::clear() { // Enable all controls mControlSwitch->clear(); } InputManager::~InputManager() {} void InputManager::setAttemptJump(bool jumping) { mActionManager->setAttemptJump(jumping); } void InputManager::update(float dt, bool disableControls, bool disableEvents) { mControlsDisabled = disableControls; mInputWrapper->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); mInputWrapper->capture(disableEvents); if (disableControls) { mMouseManager->updateCursorMode(); return; } mBindingsManager->update(dt); mMouseManager->updateCursorMode(); bool controllerMove = mControllerManager->update(dt); mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); if (mGyroManager->isEnabled()) { bool controllerAvailable = mControllerManager->isGyroAvailable(); bool sensorAvailable = mSensorManager->isGyroAvailable(); if (controllerAvailable || sensorAvailable) { mGyroManager->update(dt, controllerAvailable ? mControllerManager->getGyroValues() : mSensorManager->getGyroValues()); } } } void InputManager::setDragDrop(bool dragDrop) { mBindingsManager->setDragDrop(dragDrop); } void InputManager::setGamepadGuiCursorEnabled(bool enabled) { mControllerManager->setGamepadGuiCursorEnabled(enabled); } void InputManager::changeInputMode(bool guiMode) { mControllerManager->setGuiCursorEnabled(guiMode); mMouseManager->setGuiCursorEnabled(guiMode); mGyroManager->setGuiCursorEnabled(guiMode); mMouseManager->setMouseLookEnabled(!guiMode); if (guiMode) MWBase::Environment::get().getWindowManager()->showCrosshair(false); bool isCursorVisible = guiMode && (!mControllerManager->joystickLastUsed() || mControllerManager->gamepadGuiCursorEnabled()); MWBase::Environment::get().getWindowManager()->setCursorVisible(isCursorVisible); // if not in gui mode, the camera decides whether to show crosshair or not. } void InputManager::processChangedSettings(const Settings::CategorySettingVector& changed) { mMouseManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed); mGyroManager->processChangedSettings(changed); } bool InputManager::getControlSwitch(std::string_view sw) { return mControlSwitch->get(sw); } void InputManager::toggleControlSwitch(std::string_view sw, bool value) { mControlSwitch->set(sw, value); } void InputManager::resetIdleTime() { mActionManager->resetIdleTime(); } bool InputManager::isIdle() const { return mActionManager->getIdleTime() > 0.5; } std::string InputManager::getActionDescription(int action) const { return mBindingsManager->getActionDescription(action); } std::string InputManager::getActionKeyBindingName(int action) const { return mBindingsManager->getActionKeyBindingName(action); } std::string InputManager::getActionControllerBindingName(int action) const { return mBindingsManager->getActionControllerBindingName(action); } bool InputManager::actionIsActive(int action) const { return mBindingsManager->actionIsActive(action); } float InputManager::getActionValue(int action) const { return mBindingsManager->getActionValue(action); } bool InputManager::isControllerButtonPressed(SDL_GameControllerButton button) const { return mControllerManager->isButtonPressed(button); } float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const { return mControllerManager->getAxisValue(axis); } int InputManager::getMouseMoveX() const { return mMouseManager->getMouseMoveX(); } int InputManager::getMouseMoveY() const { return mMouseManager->getMouseMoveY(); } std::vector InputManager::getActionKeySorting() { return mBindingsManager->getActionKeySorting(); } std::vector InputManager::getActionControllerSorting() { return mBindingsManager->getActionControllerSorting(); } void InputManager::enableDetectingBindingMode(int action, bool keyboard) { mBindingsManager->enableDetectingBindingMode(action, keyboard); } int InputManager::countSavedGameRecords() const { return mControlSwitch->countSavedGameRecords(); } void InputManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) { mControlSwitch->write(writer, progress); } void InputManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_INPU) { mControlSwitch->readRecord(reader, type); } } void InputManager::resetToDefaultKeyBindings() { mBindingsManager->loadKeyDefaults(true); } void InputManager::resetToDefaultControllerBindings() { mBindingsManager->loadControllerDefaults(true); } void InputManager::setJoystickLastUsed(bool enabled) { mControllerManager->setJoystickLastUsed(enabled); } bool InputManager::joystickLastUsed() { return mControllerManager->joystickLastUsed(); } void InputManager::executeAction(int action) { mActionManager->executeAction(action); } } openmw-openmw-0.48.0/apps/openmw/mwinput/inputmanagerimp.hpp000066400000000000000000000103241445372753700243200ustar00rootroot00000000000000#ifndef MWINPUT_MWINPUTMANAGERIMP_H #define MWINPUT_MWINPUTMANAGERIMP_H #include #include #include #include #include #include "../mwbase/inputmanager.hpp" #include "../mwgui/mode.hpp" #include "actions.hpp" namespace MWWorld { class Player; } namespace MWBase { class WindowManager; } namespace SDLUtil { class InputWrapper; } struct SDL_Window; namespace MWInput { class ControlSwitch; class ActionManager; class BindingsManager; class ControllerManager; class KeyboardManager; class MouseManager; class SensorManager; class GyroManager; /** * @brief Class that provides a high-level API for game input */ class InputManager final : public MWBase::InputManager { public: InputManager( SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab); ~InputManager() final; /// Clear all savegame-specific data void clear() override; void update(float dt, bool disableControls, bool disableEvents=false) override; void changeInputMode(bool guiMode) override; void processChangedSettings(const Settings::CategorySettingVector& changed) override; void setDragDrop(bool dragDrop) override; void setGamepadGuiCursorEnabled(bool enabled) override; void setAttemptJump(bool jumping) override; void toggleControlSwitch(std::string_view sw, bool value) override; bool getControlSwitch(std::string_view sw) override; std::string getActionDescription (int action) const override; std::string getActionKeyBindingName (int action) const override; std::string getActionControllerBindingName (int action) const override; bool actionIsActive(int action) const override; float getActionValue(int action) const override; bool isControllerButtonPressed(SDL_GameControllerButton button) const override; float getControllerAxisValue(SDL_GameControllerAxis axis) const override; int getMouseMoveX() const override; int getMouseMoveY() const override; int getNumActions() override { return A_Last; } std::vector getActionKeySorting() override; std::vector getActionControllerSorting() override; void enableDetectingBindingMode (int action, bool keyboard) override; void resetToDefaultKeyBindings() override; void resetToDefaultControllerBindings() override; void setJoystickLastUsed(bool enabled) override; bool joystickLastUsed() override; int countSavedGameRecords() const override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; void readRecord(ESM::ESMReader& reader, uint32_t type) override; void resetIdleTime() override; bool isIdle() const override; void executeAction(int action) override; bool controlsDisabled() override { return mControlsDisabled; } private: void convertMousePosForMyGUI(int& x, int& y); void handleGuiArrowKey(int action); void quickKey(int index); void showQuickKeysMenu(); void loadKeyDefaults(bool force = false); void loadControllerDefaults(bool force = false); bool mControlsDisabled; std::unique_ptr mInputWrapper; std::unique_ptr mBindingsManager; std::unique_ptr mControlSwitch; std::unique_ptr mActionManager; std::unique_ptr mKeyboardManager; std::unique_ptr mMouseManager; std::unique_ptr mControllerManager; std::unique_ptr mSensorManager; std::unique_ptr mGyroManager; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/keyboardmanager.cpp000066400000000000000000000063441445372753700242550ustar00rootroot00000000000000#include "keyboardmanager.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/player.hpp" #include "actions.hpp" #include "bindingsmanager.hpp" namespace MWInput { KeyboardManager::KeyboardManager(BindingsManager* bindingsManager) : mBindingsManager(bindingsManager) { } void KeyboardManager::textInput(const SDL_TextInputEvent &arg) { MyGUI::UString ustring(&arg.text[0]); MyGUI::UString::utf32string utf32string = ustring.asUTF32(); for (MyGUI::UString::utf32string::const_iterator it = utf32string.begin(); it != utf32string.end(); ++it) MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); } void KeyboardManager::keyPressed(const SDL_KeyboardEvent &arg) { // HACK: to make default keybinding for the console work without printing an extra "^" upon closing // This assumes that SDL_TextInput events always come *after* the key event // (which is somewhat reasonable, and hopefully true for all SDL platforms) auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (mBindingsManager->getKeyBinding(A_Console) == arg.keysym.scancode && MWBase::Environment::get().getWindowManager()->isConsoleMode()) SDL_StopTextInput(); bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && // Don't trust isprint for symbols outside the extended ASCII range ((kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff) || (arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym)))); if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) { if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat)) consumed = true; mBindingsManager->setPlayerControlsEnabled(!consumed); } if (arg.repeat) return; MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (!input->controlsDisabled() && !consumed) mBindingsManager->keyPressed(arg); if (!consumed) { MWBase::Environment::get().getLuaManager()->inputEvent( {MWBase::LuaManager::InputEvent::KeyPressed, arg.keysym}); } input->setJoystickLastUsed(false); } void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (!mBindingsManager->isDetectingBindingState()) mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->keyReleased(arg); MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::KeyReleased, arg.keysym}); } } openmw-openmw-0.48.0/apps/openmw/mwinput/keyboardmanager.hpp000066400000000000000000000011421445372753700242510ustar00rootroot00000000000000#ifndef MWINPUT_MWKEYBOARDMANAGER_H #define MWINPUT_MWKEYBOARDMANAGER_H #include namespace MWInput { class BindingsManager; class KeyboardManager : public SDLUtil::KeyListener { public: KeyboardManager(BindingsManager* bindingsManager); virtual ~KeyboardManager() = default; void textInput(const SDL_TextInputEvent &arg) override; void keyPressed(const SDL_KeyboardEvent &arg) override; void keyReleased(const SDL_KeyboardEvent &arg) override; private: BindingsManager* mBindingsManager; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/mousemanager.cpp000066400000000000000000000257151445372753700236100ustar00rootroot00000000000000#include "mousemanager.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" #include "actions.hpp" #include "bindingsmanager.hpp" namespace MWInput { MouseManager::MouseManager(BindingsManager* bindingsManager, SDLUtil::InputWrapper* inputWrapper, SDL_Window* window) : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) , mGrabCursor(Settings::Manager::getBool("grab cursor", "Input")) , mCameraSensitivity(Settings::Manager::getFloat("camera sensitivity", "Input")) , mCameraYMultiplier(Settings::Manager::getFloat("camera y multiplier", "Input")) , mBindingsManager(bindingsManager) , mInputWrapper(inputWrapper) , mGuiCursorX(0) , mGuiCursorY(0) , mMouseWheel(0) , mMouseLookEnabled(false) , mGuiCursorEnabled(true) , mMouseMoveX(0) , mMouseMoveY(0) { int w,h; SDL_GetWindowSize(window, &w, &h); float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); mGuiCursorX = w / (2.f * uiScale); mGuiCursorY = h / (2.f * uiScale); } void MouseManager::processChangedSettings(const Settings::CategorySettingVector& changed) { for (const auto& setting : changed) { if (setting.first == "Input" && setting.second == "invert x axis") mInvertX = Settings::Manager::getBool("invert x axis", "Input"); if (setting.first == "Input" && setting.second == "invert y axis") mInvertY = Settings::Manager::getBool("invert y axis", "Input"); if (setting.first == "Input" && setting.second == "camera sensitivity") mCameraSensitivity = Settings::Manager::getFloat("camera sensitivity", "Input"); if (setting.first == "Input" && setting.second == "grab cursor") mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); } } void MouseManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg) { mBindingsManager->mouseMoved(arg); MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); input->setJoystickLastUsed(false); input->resetIdleTime(); if (mGuiCursorEnabled) { input->setGamepadGuiCursorEnabled(true); // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); mGuiCursorX = static_cast(arg.x) / uiScale; mGuiCursorY = static_cast(arg.y) / uiScale; mMouseWheel = static_cast(arg.z); MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); // FIXME: inject twice to force updating focused widget states (tooltips) resulting from changing the viewport by scroll wheel MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); MWBase::Environment::get().getWindowManager()->setCursorActive(true); } if (mMouseLookEnabled && !input->controlsDisabled()) { MWBase::World* world = MWBase::Environment::get().getWorld(); float x = arg.xrel * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f; float y = arg.yrel * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f; float rot[3]; rot[0] = -y; rot[1] = 0.0f; rot[2] = -x; // Only actually turn player when we're not in vanity mode if (!world->vanityRotateCamera(rot) && input->getControlSwitch("playerlooking")) { MWWorld::Player& player = world->getPlayer(); player.yaw(x); player.pitch(y); } else if (!input->getControlSwitch("playerlooking")) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); } } void MouseManager::mouseReleased(const SDL_MouseButtonEvent &arg, Uint8 id) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); if (mBindingsManager->isDetectingBindingState()) { mBindingsManager->mouseReleased(arg, id); } else { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); guiMode = MyGUI::InputManager::getInstance().injectMouseRelease( static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(id) ) && guiMode; if (mBindingsManager->isDetectingBindingState()) return; // don't allow same mouseup to bind as initiated bind mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); } } void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent &arg) { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) mBindingsManager->mouseWheelMoved(arg); input->setJoystickLastUsed(false); } void MouseManager::mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); input->setJoystickLastUsed(false); bool guiMode = false; if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events { guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); guiMode = MyGUI::InputManager::getInstance().injectMousePress( static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(id) ) && guiMode; if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr) { MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); if (b && b->getEnabled() && id == SDL_BUTTON_LEFT) { MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); } } MWBase::Environment::get().getWindowManager()->setCursorActive(true); } mBindingsManager->setPlayerControlsEnabled(!guiMode); // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } void MouseManager::updateCursorMode() { bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); bool wasRelative = mInputWrapper->getMouseRelative(); bool isRelative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); // don't keep the pointer away from the window edge in gui mode // stop using raw mouse motions and switch to system cursor movements mInputWrapper->setMouseRelative(isRelative); //we let the mouse escape in the main menu mInputWrapper->setGrabPointer(grab && (mGrabCursor || isRelative)); //we switched to non-relative mode, move our cursor to where the in-game //cursor is if (!isRelative && wasRelative != isRelative) { warpMouse(); } } void MouseManager::update(float dt) { SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); if (!mMouseLookEnabled) return; float xAxis = mBindingsManager->getActionValue(A_LookLeftRight) * 2.0f - 1.0f; float yAxis = mBindingsManager->getActionValue(A_LookUpDown) * 2.0f - 1.0f; if (xAxis == 0 && yAxis == 0) return; float rot[3]; rot[0] = -yAxis * dt * 1000.0f * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f; rot[1] = 0.0f; rot[2] = -xAxis * dt * 1000.0f * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f; // Only actually turn player when we're not in vanity mode bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"); if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.yaw(-rot[2]); player.pitch(-rot[0]); } else if (!controls) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); } bool MouseManager::injectMouseButtonPress(Uint8 button) { return MyGUI::InputManager::getInstance().injectMousePress( static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(button)); } bool MouseManager::injectMouseButtonRelease(Uint8 button) { return MyGUI::InputManager::getInstance().injectMouseRelease( static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(button)); } void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove) { mGuiCursorX += xMove; mGuiCursorY += yMove; mMouseWheel += mouseWheelMove; const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); mGuiCursorX = std::clamp(mGuiCursorX, 0.f, viewSize.width - 1); mGuiCursorY = std::clamp(mGuiCursorY, 0.f, viewSize.height - 1); MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), static_cast(mMouseWheel)); } void MouseManager::warpMouse() { float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); mInputWrapper->warpMouse(static_cast(mGuiCursorX*uiScale), static_cast(mGuiCursorY*uiScale)); } } openmw-openmw-0.48.0/apps/openmw/mwinput/mousemanager.hpp000066400000000000000000000035221445372753700236050ustar00rootroot00000000000000#ifndef MWINPUT_MWMOUSEMANAGER_H #define MWINPUT_MWMOUSEMANAGER_H #include #include namespace SDLUtil { class InputWrapper; } namespace MWInput { class BindingsManager; class MouseManager : public SDLUtil::MouseListener { public: MouseManager(BindingsManager* bindingsManager, SDLUtil::InputWrapper* inputWrapper, SDL_Window* window); virtual ~MouseManager() = default; void updateCursorMode(); void update(float dt); void mouseMoved(const SDLUtil::MouseMotionEvent &arg) override; void mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) override; void mouseReleased(const SDL_MouseButtonEvent &arg, Uint8 id) override; void mouseWheelMoved(const SDL_MouseWheelEvent &arg) override; void processChangedSettings(const Settings::CategorySettingVector& changed); bool injectMouseButtonPress(Uint8 button); bool injectMouseButtonRelease(Uint8 button); void injectMouseMove(float xMove, float yMove, float mouseWheelMove); void warpMouse(); void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } int getMouseMoveX() const { return mMouseMoveX; } int getMouseMoveY() const { return mMouseMoveY; } private: bool mInvertX; bool mInvertY; bool mGrabCursor; float mCameraSensitivity; float mCameraYMultiplier; BindingsManager* mBindingsManager; SDLUtil::InputWrapper* mInputWrapper; float mGuiCursorX; float mGuiCursorY; int mMouseWheel; bool mMouseLookEnabled; bool mGuiCursorEnabled; int mMouseMoveX; int mMouseMoveY; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwinput/sensormanager.cpp000066400000000000000000000122771445372753700237700ustar00rootroot00000000000000#include "sensormanager.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" namespace MWInput { SensorManager::SensorManager() : mRotation() , mGyroValues() , mGyroUpdateTimer(0.f) , mGyroscope(nullptr) { init(); } void SensorManager::init() { correctGyroscopeAxes(); updateSensors(); } SensorManager::~SensorManager() { if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; } } void SensorManager::correctGyroscopeAxes() { if (!Settings::Manager::getBool("enable gyroscope", "Input")) return; // Treat setting from config as axes for landscape mode. // If the device does not support orientation change, do nothing. // Note: in is unclear how to correct axes for devices with non-standart Z axis direction. mRotation = osg::Matrixf::identity(); float angle = 0; SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: break; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { angle = osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT: { angle = -0.5 * osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { angle = 0.5 * osg::PIf; break; } } mRotation.makeRotate(angle, osg::Vec3f(0, 0, 1)); } void SensorManager::updateSensors() { if (Settings::Manager::getBool("enable gyroscope", "Input")) { int numSensors = SDL_NumSensors(); for (int i = 0; i < numSensors; ++i) { if (SDL_SensorGetDeviceType(i) == SDL_SENSOR_GYRO) { // It is unclear how to handle several enabled gyroscopes, so use the first one. // Note: Android registers some gyroscope as two separate sensors, for non-wake-up mode and for wake-up mode. if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; mGyroUpdateTimer = 0.f; } // FIXME: SDL2 does not provide a way to configure a sensor update frequency so far. SDL_Sensor *sensor = SDL_SensorOpen(i); if (sensor == nullptr) Log(Debug::Error) << "Couldn't open sensor " << SDL_SensorGetDeviceName(i) << ": " << SDL_GetError(); else { mGyroscope = sensor; break; } } } } else { if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; mGyroUpdateTimer = 0.f; } } } void SensorManager::processChangedSettings(const Settings::CategorySettingVector& changed) { for (const auto& setting : changed) { if (setting.first == "Input" && setting.second == "enable gyroscope") init(); } } void SensorManager::displayOrientationChanged() { correctGyroscopeAxes(); } void SensorManager::sensorUpdated(const SDL_SensorEvent &arg) { if (!Settings::Manager::getBool("enable gyroscope", "Input")) return; SDL_Sensor *sensor = SDL_SensorFromInstanceID(arg.which); if (!sensor) { Log(Debug::Info) << "Couldn't get sensor for sensor event"; return; } switch (SDL_SensorGetType(sensor)) { case SDL_SENSOR_ACCEL: break; case SDL_SENSOR_GYRO: { osg::Vec3f gyro(arg.data[0], arg.data[1], arg.data[2]); mGyroValues = mRotation * gyro; mGyroUpdateTimer = 0.f; break; } default: break; } } void SensorManager::update(float dt) { mGyroUpdateTimer += dt; if (mGyroUpdateTimer > 0.5f) { // More than half of second passed since the last gyroscope update. // A device more likely was disconnected or switched to the sleep mode. // Reset current rotation speed and wait for update. mGyroValues = osg::Vec3f(); mGyroUpdateTimer = 0.f; } } bool SensorManager::isGyroAvailable() const { return mGyroscope != nullptr; } std::array SensorManager::getGyroValues() const { return { mGyroValues.x(), mGyroValues.y(), mGyroValues.z() }; } } openmw-openmw-0.48.0/apps/openmw/mwinput/sensormanager.hpp000066400000000000000000000020231445372753700237610ustar00rootroot00000000000000#ifndef MWINPUT_MWSENSORMANAGER_H #define MWINPUT_MWSENSORMANAGER_H #include #include #include #include #include namespace SDLUtil { class InputWrapper; } namespace MWWorld { class Player; } namespace MWInput { class SensorManager : public SDLUtil::SensorListener { public: SensorManager(); virtual ~SensorManager(); void init(); void update(float dt); void sensorUpdated(const SDL_SensorEvent &arg) override; void displayOrientationChanged() override; void processChangedSettings(const Settings::CategorySettingVector& changed); bool isGyroAvailable() const; std::array getGyroValues() const; private: void updateSensors(); void correctGyroscopeAxes(); osg::Matrixf mRotation; osg::Vec3f mGyroValues; float mGyroUpdateTimer; SDL_Sensor* mGyroscope; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwlua/000077500000000000000000000000001445372753700200315ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwlua/camerabindings.cpp000066400000000000000000000140071445372753700235050ustar00rootroot00000000000000#include "luabindings.hpp" #include #include #include "../mwrender/camera.hpp" #include "../mwrender/renderingmanager.hpp" namespace MWLua { using CameraMode = MWRender::Camera::Mode; sol::table initCameraPackage(const Context& context) { MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); sol::table api(context.mLua->sol(), sol::create); api["MODE"] = LuaUtil::makeStrictReadOnly(context.mLua->sol().create_table_with( "Static", CameraMode::Static, "FirstPerson", CameraMode::FirstPerson, "ThirdPerson", CameraMode::ThirdPerson, "Vanity", CameraMode::Vanity, "Preview", CameraMode::Preview )); api["getMode"] = [camera]() -> int { return static_cast(camera->getMode()); }; api["getQueuedMode"] = [camera]() -> sol::optional { std::optional mode = camera->getQueuedMode(); if (mode) return static_cast(*mode); else return sol::nullopt; }; api["setMode"] = [camera](int mode, sol::optional force) { camera->setMode(static_cast(mode), force ? *force : false); }; api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); }; api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); }; api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); }; api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); }; // All angles are negated in order to make camera rotation consistent with objects rotation. // TODO: Fix the inconsistency of rotation direction in camera.cpp. api["getPitch"] = [camera]() { return -camera->getPitch(); }; api["getYaw"] = [camera]() { return -camera->getYaw(); }; api["getRoll"] = [camera]() { return -camera->getRoll(); }; api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); }; api["setPitch"] = [camera](float v) { camera->setPitch(-v, true); if (camera->getMode() == CameraMode::ThirdPerson) camera->calculateDeferredRotation(); }; api["setYaw"] = [camera](float v) { camera->setYaw(-v, true); if (camera->getMode() == CameraMode::ThirdPerson) camera->calculateDeferredRotation(); }; api["setRoll"] = [camera](float v) { camera->setRoll(-v); }; api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); }; api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); }; api["setExtraRoll"] = [camera](float v) { camera->setExtraRoll(-v); }; api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); }; api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); }; api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); }; api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); }; api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); }; api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); }; api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); }; api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); }; api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); }; api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); }; api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; api["instantTransition"] = [camera]() { camera->instantTransition(); }; api["getCollisionType"] = [camera]() { return camera->getCollisionType(); }; api["setCollisionType"] = [camera](int collisionType) { camera->setCollisionType(collisionType); }; api["getBaseFieldOfView"] = []() { return osg::DegreesToRadians(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)); }; api["getFieldOfView"] = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; api["setFieldOfView"] = [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; api["getBaseViewDistance"] = []() { return std::max(0.f, Settings::Manager::getFloat("viewing distance", "Camera")); }; api["getViewDistance"] = [renderingManager]() { return renderingManager->getViewDistance(); }; api["setViewDistance"] = [renderingManager](float d) { renderingManager->setViewDistance(d, true); }; api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{camera->getViewMatrix()}; }; api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { double width = Settings::Manager::getInt("resolution x", "Video"); double height = Settings::Manager::getInt("resolution y", "Video"); double aspect = (height == 0.0) ? 1.0 : width / height; double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); osg::Matrixf invertedViewMatrix; invertedViewMatrix.invert(camera->getViewMatrix()); float x = (pos.x() * 2 - 1) * aspect * fovTan; float y = (1 - pos.y() * 2) * fovTan; return invertedViewMatrix.preMult(osg::Vec3f(x, y, -1)) - camera->getPosition(); }; return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/cellbindings.cpp000066400000000000000000000142051445372753700231740ustar00rootroot00000000000000#include "luabindings.hpp" #include #include "../mwworld/cellstore.hpp" #include "types/types.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; } namespace MWLua { template static void initCellBindings(const std::string& prefix, const Context& context) { sol::usertype cellT = context.mLua->sol().new_usertype(prefix + "Cell"); cellT[sol::meta_function::equal_to] = [](const CellT& a, const CellT& b) { return a.mStore == b.mStore; }; cellT[sol::meta_function::to_string] = [](const CellT& c) { const ESM::Cell* cell = c.mStore->getCell(); std::stringstream res; if (cell->isExterior()) res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ")"; else res << "interior(" << cell->mName << ")"; return res.str(); }; cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mName; }); cellT["region"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mRegion; }); cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); }); cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); }); cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); }); cellT["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); }); cellT["isQuasiExterior"] = sol::readonly_property([](const CellT& c) { return (c.mStore->getCell()->mData.mFlags & ESM::Cell::QuasiEx) != 0; }); cellT["isInSameSpace"] = [](const CellT& c, const ObjectT& obj) { const MWWorld::Ptr& ptr = obj.ptr(); if (!ptr.isInCell()) return false; MWWorld::CellStore* cell = ptr.getCell(); return cell == c.mStore || (cell->isExterior() && c.mStore->isExterior()); }; if constexpr (std::is_same_v) { // only for global scripts cellT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())]( const CellT& cell, sol::optional type) { ObjectIdList res = std::make_shared>(); auto visitor = [&](const MWWorld::Ptr& ptr) { worldView->getObjectRegistry()->registerPtr(ptr); if (getLiveCellRefType(ptr.mRef) == ptr.getType()) res->push_back(getId(ptr)); return true; }; bool ok = false; sol::optional typeId = sol::nullopt; if (type.has_value()) typeId = ids[*type]; else { ok = true; cell.mStore->forEach(std::move(visitor)); } if (typeId.has_value()) { ok = true; switch (*typeId) { case ESM::REC_INTERNAL_PLAYER: { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (player.getCell() == cell.mStore) res->push_back(getId(player)); } break; case ESM::REC_CREA: cell.mStore->template forEachType(visitor); break; case ESM::REC_NPC_: cell.mStore->template forEachType(visitor); break; case ESM::REC_ACTI: cell.mStore->template forEachType(visitor); break; case ESM::REC_DOOR: cell.mStore->template forEachType(visitor); break; case ESM::REC_CONT: cell.mStore->template forEachType(visitor); break; case ESM::REC_ALCH: cell.mStore->template forEachType(visitor); break; case ESM::REC_ARMO: cell.mStore->template forEachType(visitor); break; case ESM::REC_BOOK: cell.mStore->template forEachType(visitor); break; case ESM::REC_CLOT: cell.mStore->template forEachType(visitor); break; case ESM::REC_INGR: cell.mStore->template forEachType(visitor); break; case ESM::REC_LIGH: cell.mStore->template forEachType(visitor); break; case ESM::REC_MISC: cell.mStore->template forEachType(visitor); break; case ESM::REC_WEAP: cell.mStore->template forEachType(visitor); break; case ESM::REC_APPA: cell.mStore->template forEachType(visitor); break; case ESM::REC_LOCK: cell.mStore->template forEachType(visitor); break; case ESM::REC_PROB: cell.mStore->template forEachType(visitor); break; case ESM::REC_REPA: cell.mStore->template forEachType(visitor); break; default: ok = false; } } if (!ok) throw std::runtime_error(std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type))); return GObjectList{res}; }; } } void initCellBindingsForLocalScripts(const Context& context) { initCellBindings("L", context); } void initCellBindingsForGlobalScripts(const Context& context) { initCellBindings("G", context); } } openmw-openmw-0.48.0/apps/openmw/mwlua/context.hpp000066400000000000000000000011201445372753700222200ustar00rootroot00000000000000#ifndef MWLUA_CONTEXT_H #define MWLUA_CONTEXT_H #include "eventqueue.hpp" namespace LuaUtil { class LuaState; class UserdataSerializer; class L10nManager; } namespace MWLua { class LuaManager; class WorldView; struct Context { bool mIsGlobal; LuaManager* mLuaManager; LuaUtil::LuaState* mLua; LuaUtil::UserdataSerializer* mSerializer; LuaUtil::L10nManager* mL10n; WorldView* mWorldView; LocalEventQueue* mLocalEventQueue; GlobalEventQueue* mGlobalEventQueue; }; } #endif // MWLUA_CONTEXT_H openmw-openmw-0.48.0/apps/openmw/mwlua/debugbindings.cpp000066400000000000000000000055141445372753700233460ustar00rootroot00000000000000#include "debugbindings.hpp" #include "context.hpp" #include "luamanagerimp.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/postprocessor.hpp" #include #include #include #include namespace MWLua { sol::table initDebugPackage(const Context& context) { sol::table api = context.mLua->newTable(); api["RENDER_MODE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"CollisionDebug", MWRender::Render_CollisionDebug}, {"Wireframe", MWRender::Render_Wireframe}, {"Pathgrid", MWRender::Render_Pathgrid}, {"Water", MWRender::Render_Water}, {"Scene", MWRender::Render_Scene}, {"NavMesh", MWRender::Render_NavMesh}, {"ActorsPaths", MWRender::Render_ActorsPaths}, {"RecastMesh", MWRender::Render_RecastMesh}, })); api["toggleRenderMode"] = [context] (MWRender::RenderMode value) { context.mLuaManager->addAction([value] { MWBase::Environment::get().getWorld()->toggleRenderMode(value); }); }; api["NAV_MESH_RENDER_MODE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"AreaType", MWRender::NavMeshMode::AreaType}, {"UpdateFrequency", MWRender::NavMeshMode::UpdateFrequency}, })); api["setNavMeshRenderMode"] = [context] (MWRender::NavMeshMode value) { context.mLuaManager->addAction([value] { MWBase::Environment::get().getWorld()->getRenderingManager()->setNavMeshMode(value); }); }; api["triggerShaderReload"] = [context]() { context.mLuaManager->addAction([] { auto world = MWBase::Environment::get().getWorld(); world->getRenderingManager()->getResourceSystem()->getSceneManager()->getShaderManager().triggerShaderReload(); world->getPostProcessor()->triggerShaderReload(); }); }; api["setShaderHotReloadEnabled"] = [context](bool value) { context.mLuaManager->addAction([value] { auto world = MWBase::Environment::get().getWorld(); world->getRenderingManager()->getResourceSystem()->getSceneManager()->getShaderManager().setHotReloadEnabled(value); world->getPostProcessor()->mEnableLiveReload = value; }); }; return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/debugbindings.hpp000066400000000000000000000003551445372753700233510ustar00rootroot00000000000000#ifndef OPENMW_MWLUA_DEBUGBINDINGS_H #define OPENMW_MWLUA_DEBUGBINDINGS_H #include namespace MWLua { struct Context; sol::table initDebugPackage(const Context& context); } #endif // OPENMW_MWLUA_DEBUGBINDINGS_H openmw-openmw-0.48.0/apps/openmw/mwlua/eventqueue.cpp000066400000000000000000000041221445372753700227220ustar00rootroot00000000000000#include "eventqueue.hpp" #include #include #include #include #include namespace MWLua { template void saveEvent(ESM::ESMWriter& esm, const ObjectId& dest, const Event& event) { esm.writeHNString("LUAE", event.mEventName); dest.save(esm, true); if (!event.mEventData.empty()) saveLuaBinaryData(esm, event.mEventData); } void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents, const std::map& contentFileMapping, const LuaUtil::UserdataSerializer* serializer) { while (esm.isNextSub("LUAE")) { std::string name = esm.getHString(); ObjectId dest; dest.load(esm, true); std::string data = loadLuaBinaryData(esm); try { data = LuaUtil::serialize(LuaUtil::deserialize(lua, data, serializer), serializer); } catch (std::exception& e) { Log(Debug::Error) << "loadEvent: invalid event data: " << e.what(); } if (dest.isSet()) { auto it = contentFileMapping.find(dest.mContentFile); if (it != contentFileMapping.end()) dest.mContentFile = it->second; localEvents.push_back({dest, std::move(name), std::move(data)}); } else globalEvents.push_back({std::move(name), std::move(data)}); } } void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue& globalEvents, const LocalEventQueue& localEvents) { ObjectId globalId; globalId.unset(); // Used as a marker of a global event. for (const GlobalEvent& e : globalEvents) saveEvent(esm, globalId, e); for (const LocalEvent& e : localEvents) saveEvent(esm, e.mDest, e); } } openmw-openmw-0.48.0/apps/openmw/mwlua/eventqueue.hpp000066400000000000000000000016161445372753700227340ustar00rootroot00000000000000#ifndef MWLUA_EVENTQUEUE_H #define MWLUA_EVENTQUEUE_H #include "object.hpp" namespace ESM { class ESMReader; class ESMWriter; } namespace LuaUtil { class UserdataSerializer; } namespace sol { class state; } namespace MWLua { struct GlobalEvent { std::string mEventName; std::string mEventData; }; struct LocalEvent { ObjectId mDest; std::string mEventName; std::string mEventData; }; using GlobalEventQueue = std::vector; using LocalEventQueue = std::vector; void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&, const std::map& contentFileMapping, const LuaUtil::UserdataSerializer* serializer); void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&); } #endif // MWLUA_EVENTQUEUE_H openmw-openmw-0.48.0/apps/openmw/mwlua/globalscripts.hpp000066400000000000000000000027361445372753700234220ustar00rootroot00000000000000#ifndef MWLUA_GLOBALSCRIPTS_H #define MWLUA_GLOBALSCRIPTS_H #include #include #include #include #include #include "object.hpp" namespace MWLua { class GlobalScripts : public LuaUtil::ScriptsContainer { public: GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global") { registerEngineHandlers({ &mObjectActiveHandlers, &mActorActiveHandlers, &mItemActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers }); } void newGameStarted() { callEngineHandlers(mNewGameHandlers); } void objectActive(const GObject& obj) { callEngineHandlers(mObjectActiveHandlers, obj); } void actorActive(const GObject& obj) { callEngineHandlers(mActorActiveHandlers, obj); } void itemActive(const GObject& obj) { callEngineHandlers(mItemActiveHandlers, obj); } void playerAdded(const GObject& obj) { callEngineHandlers(mPlayerAddedHandlers, obj); } private: EngineHandlerList mObjectActiveHandlers{"onObjectActive"}; EngineHandlerList mActorActiveHandlers{"onActorActive"}; EngineHandlerList mItemActiveHandlers{"onItemActive"}; EngineHandlerList mNewGameHandlers{"onNewGame"}; EngineHandlerList mPlayerAddedHandlers{"onPlayerAdded"}; }; } #endif // MWLUA_GLOBALSCRIPTS_H openmw-openmw-0.48.0/apps/openmw/mwlua/inputbindings.cpp000066400000000000000000000316171445372753700234220ustar00rootroot00000000000000#include "luabindings.hpp" #include #include #include #include #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { sol::table initInputPackage(const Context& context) { sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { if (e.sym > 0 && e.sym <= 255) return std::string(1, static_cast(e.sym)); else return std::string(); }); keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; }); keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); auto touchpadEvent = context.mLua->sol().new_usertype("TouchpadEvent"); touchpadEvent["device"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; }); touchpadEvent["finger"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); touchpadEvent["position"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> osg::Vec2f { return {e.mX, e.mY};}); touchpadEvent["pressure"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); api["isIdle"] = [input]() { return input->isIdle(); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; api["isKeyPressed"] = [](SDL_Scancode code) -> bool { int maxCode; const auto* state = SDL_GetKeyboardState(&maxCode); if (code >= 0 && code < maxCode) return state[code] != 0; else return false; }; api["isShiftPressed"] = []() -> bool { return SDL_GetModState() & KMOD_SHIFT; }; api["isCtrlPressed"] = []() -> bool { return SDL_GetModState() & KMOD_CTRL; }; api["isAltPressed"] = []() -> bool { return SDL_GetModState() & KMOD_ALT; }; api["isSuperPressed"] = []() -> bool { return SDL_GetModState() & KMOD_GUI; }; api["isControllerButtonPressed"] = [input](int button) { return input->isControllerButtonPressed(static_cast(button)); }; api["isMouseButtonPressed"] = [](int button) -> bool { return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getAxisValue"] = [input](int axis) { if (axis < SDL_CONTROLLER_AXIS_MAX) return input->getControllerAxisValue(static_cast(axis)); else return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; }; api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"GameMenu", MWInput::A_GameMenu}, {"Screenshot", MWInput::A_Screenshot}, {"Inventory", MWInput::A_Inventory}, {"Console", MWInput::A_Console}, {"MoveLeft", MWInput::A_MoveLeft}, {"MoveRight", MWInput::A_MoveRight}, {"MoveForward", MWInput::A_MoveForward}, {"MoveBackward", MWInput::A_MoveBackward}, {"Activate", MWInput::A_Activate}, {"Use", MWInput::A_Use}, {"Jump", MWInput::A_Jump}, {"AutoMove", MWInput::A_AutoMove}, {"Rest", MWInput::A_Rest}, {"Journal", MWInput::A_Journal}, {"Weapon", MWInput::A_Weapon}, {"Spell", MWInput::A_Spell}, {"Run", MWInput::A_Run}, {"CycleSpellLeft", MWInput::A_CycleSpellLeft}, {"CycleSpellRight", MWInput::A_CycleSpellRight}, {"CycleWeaponLeft", MWInput::A_CycleWeaponLeft}, {"CycleWeaponRight", MWInput::A_CycleWeaponRight}, {"ToggleSneak", MWInput::A_ToggleSneak}, {"AlwaysRun", MWInput::A_AlwaysRun}, {"Sneak", MWInput::A_Sneak}, {"QuickSave", MWInput::A_QuickSave}, {"QuickLoad", MWInput::A_QuickLoad}, {"QuickMenu", MWInput::A_QuickMenu}, {"ToggleWeapon", MWInput::A_ToggleWeapon}, {"ToggleSpell", MWInput::A_ToggleSpell}, {"TogglePOV", MWInput::A_TogglePOV}, {"QuickKey1", MWInput::A_QuickKey1}, {"QuickKey2", MWInput::A_QuickKey2}, {"QuickKey3", MWInput::A_QuickKey3}, {"QuickKey4", MWInput::A_QuickKey4}, {"QuickKey5", MWInput::A_QuickKey5}, {"QuickKey6", MWInput::A_QuickKey6}, {"QuickKey7", MWInput::A_QuickKey7}, {"QuickKey8", MWInput::A_QuickKey8}, {"QuickKey9", MWInput::A_QuickKey9}, {"QuickKey10", MWInput::A_QuickKey10}, {"QuickKeysMenu", MWInput::A_QuickKeysMenu}, {"ToggleHUD", MWInput::A_ToggleHUD}, {"ToggleDebug", MWInput::A_ToggleDebug}, {"TogglePostProcessorHUD", MWInput::A_TogglePostProcessorHUD}, {"ZoomIn", MWInput::A_ZoomIn}, {"ZoomOut", MWInput::A_ZoomOut} })); api["CONTROL_SWITCH"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Controls", "playercontrols"}, {"Fighting", "playerfighting"}, {"Jumping", "playerjumping"}, {"Looking", "playerlooking"}, {"Magic", "playermagic"}, {"ViewMode", "playerviewswitch"}, {"VanityMode", "vanitymode"} })); api["CONTROLLER_BUTTON"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"A", SDL_CONTROLLER_BUTTON_A}, {"B", SDL_CONTROLLER_BUTTON_B}, {"X", SDL_CONTROLLER_BUTTON_X}, {"Y", SDL_CONTROLLER_BUTTON_Y}, {"Back", SDL_CONTROLLER_BUTTON_BACK}, {"Guide", SDL_CONTROLLER_BUTTON_GUIDE}, {"Start", SDL_CONTROLLER_BUTTON_START}, {"LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK}, {"RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK}, {"LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, {"RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, {"DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP}, {"DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN}, {"DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT}, {"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT} })); api["CONTROLLER_AXIS"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"LeftX", SDL_CONTROLLER_AXIS_LEFTX}, {"LeftY", SDL_CONTROLLER_AXIS_LEFTY}, {"RightX", SDL_CONTROLLER_AXIS_RIGHTX}, {"RightY", SDL_CONTROLLER_AXIS_RIGHTY}, {"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT}, {"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, {"LookUpDown", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookUpDown)}, {"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookLeftRight)}, {"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveForwardBackward)}, {"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveLeftRight)} })); api["KEY"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"_0", SDL_SCANCODE_0}, {"_1", SDL_SCANCODE_1}, {"_2", SDL_SCANCODE_2}, {"_3", SDL_SCANCODE_3}, {"_4", SDL_SCANCODE_4}, {"_5", SDL_SCANCODE_5}, {"_6", SDL_SCANCODE_6}, {"_7", SDL_SCANCODE_7}, {"_8", SDL_SCANCODE_8}, {"_9", SDL_SCANCODE_9}, {"NP_0", SDL_SCANCODE_KP_0}, {"NP_1", SDL_SCANCODE_KP_1}, {"NP_2", SDL_SCANCODE_KP_2}, {"NP_3", SDL_SCANCODE_KP_3}, {"NP_4", SDL_SCANCODE_KP_4}, {"NP_5", SDL_SCANCODE_KP_5}, {"NP_6", SDL_SCANCODE_KP_6}, {"NP_7", SDL_SCANCODE_KP_7}, {"NP_8", SDL_SCANCODE_KP_8}, {"NP_9", SDL_SCANCODE_KP_9}, {"NP_Divide", SDL_SCANCODE_KP_DIVIDE}, {"NP_Enter", SDL_SCANCODE_KP_ENTER}, {"NP_Minus", SDL_SCANCODE_KP_MINUS}, {"NP_Multiply", SDL_SCANCODE_KP_MULTIPLY}, {"NP_Delete", SDL_SCANCODE_KP_PERIOD}, {"NP_Plus", SDL_SCANCODE_KP_PLUS}, {"F1", SDL_SCANCODE_F1}, {"F2", SDL_SCANCODE_F2}, {"F3", SDL_SCANCODE_F3}, {"F4", SDL_SCANCODE_F4}, {"F5", SDL_SCANCODE_F5}, {"F6", SDL_SCANCODE_F6}, {"F7", SDL_SCANCODE_F7}, {"F8", SDL_SCANCODE_F8}, {"F9", SDL_SCANCODE_F9}, {"F10", SDL_SCANCODE_F10}, {"F11", SDL_SCANCODE_F11}, {"F12", SDL_SCANCODE_F12}, {"A", SDL_SCANCODE_A}, {"B", SDL_SCANCODE_B}, {"C", SDL_SCANCODE_C}, {"D", SDL_SCANCODE_D}, {"E", SDL_SCANCODE_E}, {"F", SDL_SCANCODE_F}, {"G", SDL_SCANCODE_G}, {"H", SDL_SCANCODE_H}, {"I", SDL_SCANCODE_I}, {"J", SDL_SCANCODE_J}, {"K", SDL_SCANCODE_K}, {"L", SDL_SCANCODE_L}, {"M", SDL_SCANCODE_M}, {"N", SDL_SCANCODE_N}, {"O", SDL_SCANCODE_O}, {"P", SDL_SCANCODE_P}, {"Q", SDL_SCANCODE_Q}, {"R", SDL_SCANCODE_R}, {"S", SDL_SCANCODE_S}, {"T", SDL_SCANCODE_T}, {"U", SDL_SCANCODE_U}, {"V", SDL_SCANCODE_V}, {"W", SDL_SCANCODE_W}, {"X", SDL_SCANCODE_X}, {"Y", SDL_SCANCODE_Y}, {"Z", SDL_SCANCODE_Z}, {"LeftArrow", SDL_SCANCODE_LEFT}, {"RightArrow", SDL_SCANCODE_RIGHT}, {"UpArrow", SDL_SCANCODE_UP}, {"DownArrow", SDL_SCANCODE_DOWN}, {"LeftAlt", SDL_SCANCODE_LALT}, {"LeftCtrl", SDL_SCANCODE_LCTRL}, {"LeftBracket", SDL_SCANCODE_LEFTBRACKET}, {"LeftSuper", SDL_SCANCODE_LGUI}, {"LeftShift", SDL_SCANCODE_LSHIFT}, {"RightAlt", SDL_SCANCODE_RALT}, {"RightCtrl", SDL_SCANCODE_RCTRL}, {"RightSuper", SDL_SCANCODE_RGUI}, {"RightBracket", SDL_SCANCODE_RIGHTBRACKET}, {"RightShift", SDL_SCANCODE_RSHIFT}, {"Apostrophe", SDL_SCANCODE_APOSTROPHE}, {"BackSlash", SDL_SCANCODE_BACKSLASH}, {"Backspace", SDL_SCANCODE_BACKSPACE}, {"CapsLock", SDL_SCANCODE_CAPSLOCK}, {"Comma", SDL_SCANCODE_COMMA}, {"Delete", SDL_SCANCODE_DELETE}, {"End", SDL_SCANCODE_END}, {"Enter", SDL_SCANCODE_RETURN}, {"Equals", SDL_SCANCODE_EQUALS}, {"Escape", SDL_SCANCODE_ESCAPE}, {"Home", SDL_SCANCODE_HOME}, {"Insert", SDL_SCANCODE_INSERT}, {"Minus", SDL_SCANCODE_MINUS}, {"NumLock", SDL_SCANCODE_NUMLOCKCLEAR}, {"PageDown", SDL_SCANCODE_PAGEDOWN}, {"PageUp", SDL_SCANCODE_PAGEUP}, {"Period", SDL_SCANCODE_PERIOD}, {"Pause", SDL_SCANCODE_PAUSE}, {"PrintScreen", SDL_SCANCODE_PRINTSCREEN}, {"ScrollLock", SDL_SCANCODE_SCROLLLOCK}, {"Semicolon", SDL_SCANCODE_SEMICOLON}, {"Slash", SDL_SCANCODE_SLASH}, {"Space", SDL_SCANCODE_SPACE}, {"Tab", SDL_SCANCODE_TAB} })); return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/localscripts.cpp000066400000000000000000000217161445372753700232460ustar00rootroot00000000000000#include "localscripts.hpp" #include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/aisequence.hpp" #include "../mwmechanics/aicombat.hpp" #include "../mwmechanics/aiescort.hpp" #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aipursue.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" #include "../mwmechanics/aipackage.hpp" #include "../mwmechanics/creaturestats.hpp" #include "luamanagerimp.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; } namespace MWLua { void LocalScripts::initializeSelfPackage(const Context& context) { using ActorControls = MWBase::LuaManager::ActorControls; sol::usertype controls = context.mLua->sol().new_usertype("ActorControls"); #define CONTROL(TYPE, FIELD) sol::property([](const ActorControls& c) { return c.FIELD; },\ [](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; }) controls["movement"] = CONTROL(float, mMovement); controls["sideMovement"] = CONTROL(float, mSideMovement); controls["pitchChange"] = CONTROL(float, mPitchChange); controls["yawChange"] = CONTROL(float, mYawChange); controls["run"] = CONTROL(bool, mRun); controls["jump"] = CONTROL(bool, mJump); controls["use"] = CONTROL(int, mUse); #undef CONTROL sol::usertype selfAPI = context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); selfAPI[sol::meta_function::to_string] = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); aiPackage["type"] = sol::readonly_property([](const AiPackage& p) -> std::string_view { switch (p.getTypeId()) { case MWMechanics::AiPackageTypeId::Wander: return "Wander"; case MWMechanics::AiPackageTypeId::Travel: return "Travel"; case MWMechanics::AiPackageTypeId::Escort: return "Escort"; case MWMechanics::AiPackageTypeId::Follow: return "Follow"; case MWMechanics::AiPackageTypeId::Activate: return "Activate"; case MWMechanics::AiPackageTypeId::Combat: return "Combat"; case MWMechanics::AiPackageTypeId::Pursue: return "Pursue"; case MWMechanics::AiPackageTypeId::AvoidDoor: return "AvoidDoor"; case MWMechanics::AiPackageTypeId::Face: return "Face"; case MWMechanics::AiPackageTypeId::Breathe: return "Breathe"; case MWMechanics::AiPackageTypeId::Cast: return "Cast"; default: return "Unknown"; } }); aiPackage["target"] = sol::readonly_property([worldView=context.mWorldView](const AiPackage& p) -> sol::optional { MWWorld::Ptr target = p.getTarget(); if (target.isEmpty()) return sol::nullopt; else return LObject(getId(target), worldView->getObjectRegistry()); }); aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); }); aiPackage["destPosition"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); }); selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional> { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if (ai.isEmpty()) return sol::nullopt; else return *ai.begin(); }; selfAPI["_iterateAndFilterAiSequence"] = [](SelfObject& self, sol::function callback) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.erasePackagesIf([&](auto& entry) { bool keep = LuaUtil::call(callback, entry).template get(); return !keep; }); }; selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiCombat(target.ptr()), ptr); }; selfAPI["_startAiPursue"] = [](SelfObject& self, const LObject& target) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiPursue(target.ptr()), ptr); }; selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiFollow(target.ptr()), ptr); }; selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration, const osg::Vec3f& dest) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. const std::string& refId = target.ptr().getCellRef().getRefId(); int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); const ESM::Cell* esmCell = cell.mStore->getCell(); if (esmCell->isExterior()) ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr); else ai.stack(MWMechanics::AiEscort(refId, esmCell->mName, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr); }; selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr); }; selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr); }; } LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj) { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers}); } void LocalScripts::receiveEngineEvent(const EngineEvent& event) { std::visit([this](auto&& arg) { using EventT = std::decay_t; if constexpr (std::is_same_v) { mData.mIsActive = true; callEngineHandlers(mOnActiveHandlers); } else if constexpr (std::is_same_v) { mData.mIsActive = false; callEngineHandlers(mOnInactiveHandlers); } else if constexpr (std::is_same_v) { callEngineHandlers(mOnActivatedHandlers, arg.mActivatingActor); } else { static_assert(std::is_same_v); callEngineHandlers(mOnConsumeHandlers, arg.mConsumable); } }, event); } void LocalScripts::applyStatsCache() { const auto& ptr = mData.ptr(); for (auto& [stat, value] : mData.mStatsCache) stat(ptr, value); mData.mStatsCache.clear(); } } openmw-openmw-0.48.0/apps/openmw/mwlua/localscripts.hpp000066400000000000000000000050421445372753700232450ustar00rootroot00000000000000#ifndef MWLUA_LOCALSCRIPTS_H #define MWLUA_LOCALSCRIPTS_H #include #include #include #include #include #include "../mwbase/luamanager.hpp" #include "object.hpp" #include "luabindings.hpp" namespace MWLua { class LocalScripts : public LuaUtil::ScriptsContainer { public: static void initializeSelfPackage(const Context&); LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } struct SelfObject : public LObject { class CachedStat { public: using Setter = void(*)(int, std::string_view, const MWWorld::Ptr&, const sol::object&); private: Setter mSetter; // Function that updates a stat's property int mIndex; // Optional index to disambiguate the stat std::string_view mProp; // Name of the stat's property public: CachedStat(Setter setter, int index, std::string_view prop) : mSetter(setter), mIndex(index), mProp(std::move(prop)) {} void operator()(const MWWorld::Ptr& ptr, const sol::object& object) const { mSetter(mIndex, mProp, ptr, object); } bool operator<(const CachedStat& other) const { return std::tie(mSetter, mIndex, mProp) < std::tie(other.mSetter, other.mIndex, other.mProp); } }; SelfObject(const LObject& obj) : LObject(obj), mIsActive(false) {} MWBase::LuaManager::ActorControls mControls; std::map mStatsCache; bool mIsActive; }; struct OnActive {}; struct OnInactive {}; struct OnActivated { LObject mActivatingActor; }; struct OnConsume { LObject mConsumable; }; using EngineEvent = std::variant; void receiveEngineEvent(const EngineEvent&); void applyStatsCache(); protected: SelfObject mData; private: EngineHandlerList mOnActiveHandlers{"onActive"}; EngineHandlerList mOnInactiveHandlers{"onInactive"}; EngineHandlerList mOnConsumeHandlers{"onConsume"}; EngineHandlerList mOnActivatedHandlers{"onActivated"}; }; } #endif // MWLUA_LOCALSCRIPTS_H openmw-openmw-0.48.0/apps/openmw/mwlua/luabindings.cpp000066400000000000000000000134631445372753700230430ustar00rootroot00000000000000#include "luabindings.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/store.hpp" #include "eventqueue.hpp" #include "worldview.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" namespace MWLua { static void addTimeBindings(sol::table& api, const Context& context, bool global) { MWBase::World* world = MWBase::Environment::get().getWorld(); api["getSimulationTime"] = [world=context.mWorldView]() { return world->getSimulationTime(); }; api["getSimulationTimeScale"] = [world]() { return world->getSimulationTimeScale(); }; api["getGameTime"] = [world=context.mWorldView]() { return world->getGameTime(); }; api["getGameTimeScale"] = [world=context.mWorldView]() { return world->getGameTimeScale(); }; api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); }; api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; if (!global) return; api["setGameTimeScale"] = [world=context.mWorldView](double scale) { world->setGameTimeScale(scale); }; api["setSimulationTimeScale"] = [context, world](float scale) { context.mLuaManager->addAction([scale, world] { world->setSimulationTimeScale(scale); }); }; // TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding) // api["pause"] = []() {}; // api["resume"] = []() {}; } sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); api["API_REVISION"] = 29; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); }; api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; addTimeBindings(api, context, false); api["l10n"] = [l10n=context.mL10n](const std::string& context, const sol::object &fallbackLocale) { if (fallbackLocale == sol::nil) return l10n->getContext(context); else return l10n->getContext(context, fallbackLocale.as()); }; const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object { const ESM::Variant& value = gmst->find(setting)->mValue; if (value.getType() == ESM::VT_String) return sol::make_object(lua->sol(), value.getString()); else if (value.getType() == ESM::VT_Int) return sol::make_object(lua->sol(), value.getInteger()); else return sol::make_object(lua->sol(), value.getFloat()); }; return LuaUtil::makeReadOnly(api); } sol::table initWorldPackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; addTimeBindings(api, context, true); api["getCellByName"] = [worldView=context.mWorldView](const std::string& name) -> sol::optional { MWWorld::CellStore* cell = worldView->findNamedCell(name); if (cell) return GCell{cell}; else return sol::nullopt; }; api["getExteriorCell"] = [worldView=context.mWorldView](int x, int y) -> sol::optional { MWWorld::CellStore* cell = worldView->findExteriorCell(x, y); if (cell) return GCell{cell}; else return sol::nullopt; }; api["activeActors"] = GObjectList{worldView->getActorsInScene()}; // TODO: add world.placeNewObject(recordId, cell, pos, [rot]) return LuaUtil::makeReadOnly(api); } sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage) { sol::table res(context.mLua->sol(), sol::create); res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); }; res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } sol::table initLocalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage) { sol::table res(context.mLua->sol(), sol::create); res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; return LuaUtil::makeReadOnly(res); } sol::table initPlayerStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage) { sol::table res(context.mLua->sol(), sol::create); res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section); }; res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } } openmw-openmw-0.48.0/apps/openmw/mwlua/luabindings.hpp000066400000000000000000000031511445372753700230410ustar00rootroot00000000000000#ifndef MWLUA_LUABINDINGS_H #define MWLUA_LUABINDINGS_H #include #include #include #include #include "context.hpp" #include "eventqueue.hpp" #include "object.hpp" #include "worldview.hpp" namespace MWWorld { class CellStore; } namespace MWLua { sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); sol::table initPostprocessingPackage(const Context&); sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initPlayerStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage); // Implemented in nearbybindings.cpp sol::table initNearbyPackage(const Context&); // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); // Implemented in cellbindings.cpp void initCellBindingsForLocalScripts(const Context&); void initCellBindingsForGlobalScripts(const Context&); // Implemented in camerabindings.cpp sol::table initCameraPackage(const Context&); // Implemented in uibindings.cpp sol::table initUserInterfacePackage(const Context&); // Implemented in inputbindings.cpp sol::table initInputPackage(const Context&); // openmw.self package is implemented in localscripts.cpp } #endif // MWLUA_LUABINDINGS_H openmw-openmw-0.48.0/apps/openmw/mwlua/luamanagerimp.cpp000066400000000000000000000570501445372753700233660ustar00rootroot00000000000000#include "luamanagerimp.hpp" #include #include "sol/state_view.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/ptr.hpp" #include "luabindings.hpp" #include "userdataserializer.hpp" #include "types/types.hpp" #include "debugbindings.hpp" namespace MWLua { LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) : mLua(vfs, &mConfiguration) , mUiResourceManager(vfs) , mL10n(vfs, &mLua) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); mLua.addInternalLibSearchPath(libsDir); mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry()); mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry()); mGlobalLoader = createUserdataSerializer(false, mWorldView.getObjectRegistry(), &mContentFileMapping); mLocalLoader = createUserdataSerializer(true, mWorldView.getObjectRegistry(), &mContentFileMapping); mGlobalScripts.setSerializer(mGlobalSerializer.get()); } void LuaManager::initConfiguration() { mConfiguration.init(MWBase::Environment::get().getWorld()->getStore().getLuaScriptsCfg()); Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; for (size_t i = 0; i < mConfiguration.size(); ++i) Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf()); } void LuaManager::initL10n() { mL10n.init(); mL10n.setPreferredLocales(Settings::Manager::getStringArray("preferred locales", "General")); } void LuaManager::init() { Context context; context.mIsGlobal = true; context.mLuaManager = this; context.mLua = &mLua; context.mL10n = &mL10n; context.mWorldView = &mWorldView; context.mLocalEventQueue = &mLocalEvents; context.mGlobalEventQueue = &mGlobalEvents; context.mSerializer = mGlobalSerializer.get(); Context localContext = context; localContext.mIsGlobal = false; localContext.mSerializer = mLocalSerializer.get(); initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); initObjectBindingsForLocalScripts(localContext); initCellBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); mLua.addCommonPackage("openmw.async", LuaUtil::getAsyncPackageInitializer( mLua.sol(), [this] { return mWorldView.getSimulationTime(); }, [this] { return mWorldView.getGameTime(); })); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); mLua.addCommonPackage("openmw.types", initTypesPackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage)); mCameraPackage = initCameraPackage(localContext); mUserInterfacePackage = initUserInterfacePackage(localContext); mInputPackage = initInputPackage(localContext); mNearbyPackage = initNearbyPackage(localContext); mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage); mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage); mPostprocessingPackage = initPostprocessingPackage(localContext); mDebugPackage = initDebugPackage(localContext); initConfiguration(); mInitialized = true; } std::string LuaManager::translate(const std::string& contextName, const std::string& key) { return mL10n.translate(contextName, key); } void LuaManager::loadPermanentStorage(const std::string& userConfigPath) { auto globalPath = boost::filesystem::path(userConfigPath) / "global_storage.bin"; auto playerPath = boost::filesystem::path(userConfigPath) / "player_storage.bin"; if (boost::filesystem::exists(globalPath)) mGlobalStorage.load(globalPath); if (boost::filesystem::exists(playerPath)) mPlayerStorage.load(playerPath); } void LuaManager::savePermanentStorage(const std::string& userConfigPath) { boost::filesystem::path confDir(userConfigPath); mGlobalStorage.save((confDir / "global_storage.bin")); mPlayerStorage.save((confDir / "player_storage.bin")); } void LuaManager::update() { static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); static const int gcStepCount = Settings::Manager::getInt("gc steps per frame", "Lua"); if (gcStepCount > 0) lua_gc(mLua.sol(), LUA_GCSTEP, gcStepCount); if (mPlayer.isEmpty()) return; // The game is not started yet. float frameDuration = MWBase::Environment::get().getFrameDuration(); ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (!(getId(mPlayer) == getId(newPlayerPtr))) throw std::logic_error("Player Refnum was changed unexpectedly"); if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) { mPlayer = newPlayerPtr; // player was moved to another cell, update ptr in registry objectRegistry->registerPtr(mPlayer); } mWorldView.update(); std::vector globalEvents = std::move(mGlobalEvents); std::vector localEvents = std::move(mLocalEvents); mGlobalEvents = std::vector(); mLocalEvents = std::vector(); if (!mWorldView.isPaused()) { // Update time and process timers double simulationTime = mWorldView.getSimulationTime() + frameDuration; mWorldView.setSimulationTime(simulationTime); double gameTime = mWorldView.getGameTime(); mGlobalScripts.processTimers(simulationTime, gameTime); for (LocalScripts* scripts : mActiveLocalScripts) scripts->processTimers(simulationTime, gameTime); } // Receive events for (GlobalEvent& e : globalEvents) mGlobalScripts.receiveEvent(e.mEventName, e.mEventData); for (LocalEvent& e : localEvents) { LObject obj(e.mDest, objectRegistry); LocalScripts* scripts = obj.isValid() ? obj.ptr().getRefData().getLuaScripts() : nullptr; if (scripts) scripts->receiveEvent(e.mEventName, e.mEventData); else Log(Debug::Debug) << "Ignored event " << e.mEventName << " to L" << idToString(e.mDest) << ". Object not found or has no attached scripts"; } // Run queued callbacks for (CallbackWithData& c : mQueuedCallbacks) c.mCallback.tryCall(c.mArg); mQueuedCallbacks.clear(); // Engine handlers in local scripts for (const LocalEngineEvent& e : mLocalEngineEvents) { LObject obj(e.mDest, objectRegistry); if (!obj.isValid()) { if (luaDebug) Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found"; continue; } LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); if (scripts) scripts->receiveEngineEvent(e.mEvent); } mLocalEngineEvents.clear(); if (!mWorldView.isPaused()) { for (LocalScripts* scripts : mActiveLocalScripts) scripts->update(frameDuration); } // Engine handlers in global scripts if (mPlayerChanged) { mPlayerChanged = false; mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry)); } if (mNewGameStarted) { mNewGameStarted = false; mGlobalScripts.newGameStarted(); } for (ObjectId id : mObjectAddedEvents) { GObject obj(id, objectRegistry); if (obj.isValid()) { mGlobalScripts.objectActive(obj); const MWWorld::Class& objClass = obj.ptr().getClass(); if (objClass.isActor()) mGlobalScripts.actorActive(obj); if (mWorldView.isItem(obj.ptr())) mGlobalScripts.itemActive(obj); } else if (luaDebug) Log(Debug::Verbose) << "Could not resolve a Lua object added event: object" << idToString(id) << " is already removed"; } mObjectAddedEvents.clear(); if (!mWorldView.isPaused()) mGlobalScripts.update(frameDuration); } void LuaManager::synchronizedUpdate() { if (mPlayer.isEmpty()) return; // The game is not started yet. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. mProcessingInputEvents = true; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { for (const auto& event : mInputEvents) playerScripts->processInputEvent(event); } mInputEvents.clear(); if (playerScripts) playerScripts->onFrame(mWorldView.isPaused() ? 0.0 : MWBase::Environment::get().getFrameDuration()); mProcessingInputEvents = false; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) windowManager->messageBox(message); mUIMessages.clear(); for (auto& [msg, color] : mInGameConsoleMessages) windowManager->printToConsole(msg, "#" + color.toHex()); mInGameConsoleMessages.clear(); for (std::unique_ptr& action : mActionQueue) action->safeApply(mWorldView); mActionQueue.clear(); if (mTeleportPlayerAction) mTeleportPlayerAction->safeApply(mWorldView); mTeleportPlayerAction.reset(); } void LuaManager::clear() { LuaUi::clearUserInterface(); mUiResourceManager.clear(); MWBase::Environment::get().getWindowManager()->setConsoleMode(""); MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); mInputEvents.clear(); mObjectAddedEvents.clear(); mLocalEngineEvents.clear(); mNewGameStarted = false; mPlayerChanged = false; mWorldView.clear(); mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; if (!mPlayer.isEmpty()) { mPlayer.getCellRef().unsetRefNum(); mPlayer.getRefData().setLuaScripts(nullptr); mPlayer = MWWorld::Ptr(); } mGlobalStorage.clearTemporaryAndRemoveCallbacks(); mPlayerStorage.clearTemporaryAndRemoveCallbacks(); } void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) { if (!mInitialized) return; if (!mPlayer.isEmpty()) throw std::logic_error("Player is initialized twice"); mWorldView.objectAddedToScene(ptr); mPlayer = ptr; LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) { localScripts = createLocalScripts(ptr); localScripts->addAutoStartedScripts(); } mActiveLocalScripts.insert(localScripts); mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); mPlayerChanged = true; } void LuaManager::newGameStarted() { mNewGameStarted = true; mInputEvents.clear(); mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; } void LuaManager::gameLoaded() { if (!mGlobalScriptsStarted) mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) { LuaUtil::ScriptIdsWithInitializationData autoStartConf = mConfiguration.getLocalConf(getLiveCellRefType(ptr.mRef), ptr.getCellRef().getRefId(), getId(ptr)); if (!autoStartConf.empty()) { localScripts = createLocalScripts(ptr, std::move(autoStartConf)); localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()` } } if (localScripts) { mActiveLocalScripts.insert(localScripts); mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); } if (ptr != mPlayer) mObjectAddedEvents.push_back(getId(ptr)); } void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) { mWorldView.objectRemovedFromScene(ptr); LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (localScripts) { mActiveLocalScripts.erase(localScripts); if (!mWorldView.getObjectRegistry()->getPtr(getId(ptr), true).isEmpty()) mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnInactive{}}); } } void LuaManager::registerObject(const MWWorld::Ptr& ptr) { mWorldView.getObjectRegistry()->registerPtr(ptr); } void LuaManager::deregisterObject(const MWWorld::Ptr& ptr) { mWorldView.getObjectRegistry()->deregisterPtr(ptr); } void LuaManager::itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) { mWorldView.getObjectRegistry()->registerPtr(consumable); mLocalEngineEvents.push_back({getId(actor), LocalScripts::OnConsume{LObject(getId(consumable), mWorldView.getObjectRegistry())}}); } void LuaManager::objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) { mLocalEngineEvents.push_back({getId(object), LocalScripts::OnActivated{LObject(getId(actor), mWorldView.getObjectRegistry())}}); } MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) return nullptr; return localScripts->getActorControls(); } void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId) { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) { localScripts = createLocalScripts(ptr); localScripts->addAutoStartedScripts(); if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell())) mActiveLocalScripts.insert(localScripts); } localScripts->addCustomScript(scriptId); } LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr, std::optional autoStartConf) { assert(mInitialized); std::shared_ptr scripts; const uint32_t type = getLiveCellRefType(ptr.mRef); if (type == ESM::REC_STAT) throw std::runtime_error("Lua scripts on static objects are not allowed"); else if (type == ESM::REC_INTERNAL_PLAYER) { scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->setAutoStartConf(mConfiguration.getPlayerConf()); scripts->addPackage("openmw.ui", mUserInterfacePackage); scripts->addPackage("openmw.camera", mCameraPackage); scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.storage", mPlayerStoragePackage); scripts->addPackage("openmw.postprocessing", mPostprocessingPackage); scripts->addPackage("openmw.debug", mDebugPackage); } else { scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); if (!autoStartConf.has_value()) autoStartConf = mConfiguration.getLocalConf(type, ptr.getCellRef().getRefId(), getId(ptr)); scripts->setAutoStartConf(std::move(*autoStartConf)); scripts->addPackage("openmw.storage", mLocalStoragePackage); } scripts->addPackage("openmw.nearby", mNearbyPackage); scripts->setSerializer(mLocalSerializer.get()); MWWorld::RefData& refData = ptr.getRefData(); refData.setLuaScripts(std::move(scripts)); return refData.getLuaScripts(); } void LuaManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) { writer.startRecord(ESM::REC_LUAM); mWorldView.save(writer); ESM::LuaScripts globalScripts; mGlobalScripts.save(globalScripts); globalScripts.save(writer); saveEvents(writer, mGlobalEvents, mLocalEvents); writer.endRecord(ESM::REC_LUAM); } void LuaManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type != ESM::REC_LUAM) throw std::runtime_error("ESM::REC_LUAM is expected"); mWorldView.load(reader); ESM::LuaScripts globalScripts; globalScripts.load(reader); loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get()); mGlobalScripts.setSavedDataDeserializer(mGlobalLoader.get()); mGlobalScripts.load(globalScripts); mGlobalScriptsStarted = true; } void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) { if (ptr.getRefData().getLuaScripts()) ptr.getRefData().getLuaScripts()->save(data); else data.mScripts.clear(); } void LuaManager::loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) { if (data.mScripts.empty()) { if (ptr.getRefData().getLuaScripts()) ptr.getRefData().setLuaScripts(nullptr); return; } mWorldView.getObjectRegistry()->registerPtr(ptr); LocalScripts* scripts = createLocalScripts(ptr); scripts->setSerializer(mLocalSerializer.get()); scripts->setSavedDataDeserializer(mLocalLoader.get()); scripts->load(data); // LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered. mWorldView.getObjectRegistry()->deregisterPtr(ptr); } void LuaManager::reloadAllScripts() { Log(Debug::Info) << "Reload Lua"; LuaUi::clearUserInterface(); MWBase::Environment::get().getWindowManager()->setConsoleMode(""); mUiResourceManager.clear(); mLua.dropScriptCache(); mL10n.clear(); initConfiguration(); { // Reload global scripts mGlobalScripts.setSavedDataDeserializer(mGlobalSerializer.get()); ESM::LuaScripts data; mGlobalScripts.save(data); mGlobalScripts.load(data); } for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping) { // Reload local scripts LocalScripts* scripts = ptr.getRefData().getLuaScripts(); if (scripts == nullptr) continue; scripts->setSavedDataDeserializer(mLocalSerializer.get()); ESM::LuaScripts data; scripts->save(data); scripts->load(data); } for (LocalScripts* scripts : mActiveLocalScripts) scripts->receiveEngineEvent(LocalScripts::OnActive()); } void LuaManager::handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) { PlayerScripts* playerScripts = nullptr; if (!mPlayer.isEmpty()) playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (!playerScripts) { MWBase::Environment::get().getWindowManager()->printToConsole("You must enter a game session to run Lua commands\n", MWBase::WindowManager::sConsoleColor_Error); return; } sol::object selected = sol::nil; if (!selectedPtr.isEmpty()) selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr), mWorldView.getObjectRegistry())); if (!playerScripts->consoleCommand(consoleMode, command, selected)) MWBase::Environment::get().getWindowManager()->printToConsole("No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error); } LuaManager::Action::Action(LuaUtil::LuaState* state) { static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); if (luaDebug) mCallerTraceback = state->debugTraceback(); } void LuaManager::Action::safeApply(WorldView& w) const { try { apply(w); } catch (const std::exception& e) { Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); if (mCallerTraceback.empty()) Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; else Log(Debug::Error) << "Caller " << mCallerTraceback; } } namespace { class FunctionAction final : public LuaManager::Action { public: FunctionAction(LuaUtil::LuaState* state, std::function fn, std::string_view name) : Action(state), mFn(std::move(fn)), mName(name) {} void apply(WorldView&) const override { mFn(); } std::string toString() const override { return "FunctionAction " + mName; } private: std::function mFn; std::string mName; }; } void LuaManager::addAction(std::function action, std::string_view name) { mActionQueue.push_back(std::make_unique(&mLua, std::move(action), name)); } void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats) { const sol::state_view state(mLua.sol()); stats.setAttribute(frameNumber, "Lua UsedMemory", state.memory_used()); } } openmw-openmw-0.48.0/apps/openmw/mwlua/luamanagerimp.hpp000066400000000000000000000174641445372753700234000ustar00rootroot00000000000000#ifndef MWLUA_LUAMANAGERIMP_H #define MWLUA_LUAMANAGERIMP_H #include #include #include #include #include #include #include #include "../mwbase/luamanager.hpp" #include "object.hpp" #include "eventqueue.hpp" #include "globalscripts.hpp" #include "localscripts.hpp" #include "playerscripts.hpp" #include "worldview.hpp" namespace MWLua { class LuaManager : public MWBase::LuaManager { public: LuaManager(const VFS::Manager* vfs, const std::string& libsDir); // Called by engine.cpp before UI setup. void initL10n(); // Called by engine.cpp when the environment is fully initialized. void init(); void loadPermanentStorage(const std::string& userConfigPath); void savePermanentStorage(const std::string& userConfigPath); // Called by engine.cpp every frame. For performance reasons it works in a separate // thread (in parallel with osg Cull). Can not use scene graph. void update(); std::string translate(const std::string& contextName, const std::string& key) override; // Called by engine.cpp from the main thread. Can use scene graph. void synchronizedUpdate(); // Available everywhere through the MWBase::LuaManager interface. // LuaManager queues these events and propagates to scripts on the next `update` call. void newGameStarted() override; void gameLoaded() override; void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void registerObject(const MWWorld::Ptr& ptr) override; void deregisterObject(const MWWorld::Ptr& ptr) override; void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); } void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override; void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; void clear() override; // should be called before loading game or starting a new game to reset internal state. void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". // Used only in Lua bindings void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } void addInGameConsoleMessage(const std::string& msg, const Misc::Color& color) { mInGameConsoleMessages.push_back({msg, color}); } // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited // from MWLua::Action. class Action { public: Action(LuaUtil::LuaState* state); virtual ~Action() {} void safeApply(WorldView&) const; virtual void apply(WorldView&) const = 0; virtual std::string toString() const = 0; private: std::string mCallerTraceback; }; void addAction(std::function action, std::string_view name = ""); void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) override; // Loading from a save void readRecord(ESM::ESMReader& reader, uint32_t type) override; void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) override; void setContentFileMapping(const std::map& mapping) override { mContentFileMapping = mapping; } // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. void reloadAllScripts() override; void handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) override; // Used to call Lua callbacks from C++ void queueCallback(LuaUtil::Callback callback, sol::main_object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } // Wraps Lua callback into an std::function. // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or // any other Lua-related function is running. template std::function wrapLuaCallback(const LuaUtil::Callback& c) { return [this, c](Arg arg) { this->queueCallback(c, sol::main_object(this->mLua.sol(), sol::in_place, arg)); }; } LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } bool isProcessingInputEvents() const { return mProcessingInputEvents; } void reportStats(unsigned int frameNumber, osg::Stats& stats); private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, std::optional autoStartConf = std::nullopt); bool mInitialized = false; bool mGlobalScriptsStarted = false; bool mProcessingInputEvents = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; LuaUtil::L10nManager mL10n; sol::table mNearbyPackage; sol::table mUserInterfacePackage; sol::table mCameraPackage; sol::table mInputPackage; sol::table mLocalStoragePackage; sol::table mPlayerStoragePackage; sol::table mPostprocessingPackage; sol::table mDebugPackage; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; WorldView mWorldView; bool mPlayerChanged = false; bool mNewGameStarted = false; MWWorld::Ptr mPlayer; GlobalEventQueue mGlobalEvents; LocalEventQueue mLocalEvents; std::unique_ptr mGlobalSerializer; std::unique_ptr mLocalSerializer; std::map mContentFileMapping; std::unique_ptr mGlobalLoader; std::unique_ptr mLocalLoader; std::vector mInputEvents; std::vector mObjectAddedEvents; struct CallbackWithData { LuaUtil::Callback mCallback; sol::main_object mArg; }; std::vector mQueuedCallbacks; struct LocalEngineEvent { ObjectId mDest; LocalScripts::EngineEvent mEvent; }; std::vector mLocalEngineEvents; // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; std::vector> mInGameConsoleMessages; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; LuaUtil::LuaStorage mPlayerStorage{mLua.sol()}; }; } #endif // MWLUA_LUAMANAGERIMP_H openmw-openmw-0.48.0/apps/openmw/mwlua/nearbybindings.cpp000066400000000000000000000324101445372753700235330ustar00rootroot00000000000000#include "luabindings.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" #include "luamanagerimp.hpp" #include "worldview.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { sol::table initNearbyPackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; sol::usertype rayResult = context.mLua->sol().new_usertype("RayCastingResult"); rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; }); rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional { if (r.mHit) return r.mHitPos; else return sol::nullopt; }); rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional { if (r.mHit) return r.mHitNormal; else return sol::nullopt; }); rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional { if (r.mHitObject.isEmpty()) return sol::nullopt; else return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); }); api["COLLISION_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"World", MWPhysics::CollisionType_World}, {"Door", MWPhysics::CollisionType_Door}, {"Actor", MWPhysics::CollisionType_Actor}, {"HeightMap", MWPhysics::CollisionType_HeightMap}, {"Projectile", MWPhysics::CollisionType_Projectile}, {"Water", MWPhysics::CollisionType_Water}, {"Default", MWPhysics::CollisionType_Default}, {"AnyPhysical", MWPhysics::CollisionType_AnyPhysical}, {"Camera", MWPhysics::CollisionType_CameraOnly}, {"VisualOnly", MWPhysics::CollisionType_VisualOnly}, })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { MWWorld::Ptr ignore; int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { sol::optional ignoreObj = options->get>("ignore"); if (ignoreObj) ignore = ignoreObj->ptr(); collisionType = options->get>("collisionType").value_or(collisionType); radius = options->get>("radius").value_or(0); } const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); if (radius <= 0) return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); else { if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); return rayCasting->castSphere(from, to, radius, collisionType); } }; // TODO: async raycasting /*api["asyncCastRay"] = [luaManager = context.mLuaManager]( const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { std::function callback = luaManager->wrapLuaCallback(luaCallback); MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); // Handle options the same way as in `castRay`. // NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ api["castRenderingRay"] = [manager=context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) { if (!manager->isProcessingInputEvents()) { throw std::logic_error("castRenderingRay can be used only in player scripts during processing of input events; " "use asyncCastRenderingRay instead."); } MWPhysics::RayCastingResult res; MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); return res; }; api["asyncCastRenderingRay"] = [context]( const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) { context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] { MWPhysics::RayCastingResult res; MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); }); }; api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; api["containers"] = LObjectList{worldView->getContainersInScene()}; api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; api["NAVIGATOR_FLAGS"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Walk", DetourNavigator::Flag_walk}, {"Swim", DetourNavigator::Flag_swim}, {"OpenDoor", DetourNavigator::Flag_openDoor}, {"UsePathgrid", DetourNavigator::Flag_usePathgrid}, })); api["COLLISION_SHAPE_TYPE"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Aabb", DetourNavigator::CollisionShapeType::Aabb}, {"RotatingBox", DetourNavigator::CollisionShapeType::RotatingBox}, {"Cylinder", DetourNavigator::CollisionShapeType::Cylinder}, })); api["FIND_PATH_STATUS"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Success", DetourNavigator::Status::Success}, {"PartialPath", DetourNavigator::Status::PartialPath}, {"NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound}, {"StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound}, {"EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound}, {"MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed}, {"FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed}, {"GetPolyHeightFailed", DetourNavigator::Status::GetPolyHeightFailed}, {"InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed}, })); static const DetourNavigator::AgentBounds defaultAgentBounds { DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game")), Settings::Manager::getVector3("default actor pathfind half extents", "Game"), }; static const float defaultStepSize = 2 * std::max(defaultAgentBounds.mHalfExtents.x(), defaultAgentBounds.mHalfExtents.y()); static constexpr DetourNavigator::Flags defaultIncludeFlags = DetourNavigator::Flag_walk | DetourNavigator::Flag_swim | DetourNavigator::Flag_openDoor | DetourNavigator::Flag_usePathgrid; api["findPath"] = [] (const osg::Vec3f& source, const osg::Vec3f& destination, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; float stepSize = defaultStepSize; DetourNavigator::Flags includeFlags = defaultIncludeFlags; DetourNavigator::AreaCosts areaCosts {}; float destinationTolerance = 1; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) { agentBounds.mHalfExtents = *v; stepSize = 2 * std::max(v->x(), v->y()); } } if (const auto& v = options->get>("stepSize")) stepSize = *v; if (const auto& v = options->get>("includeFlags")) includeFlags = *v; if (const auto& t = options->get>("areaCosts")) { if (const auto& v = t->get>("water")) areaCosts.mWater = *v; if (const auto& v = t->get>("door")) areaCosts.mDoor = *v; if (const auto& v = t->get>("pathgrid")) areaCosts.mPathgrid = *v; if (const auto& v = t->get>("ground")) areaCosts.mGround = *v; } if (const auto& v = options->get>("destinationTolerance")) destinationTolerance = *v; } std::vector result; const DetourNavigator::Status status = DetourNavigator::findPath( *MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, stepSize, source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(result)); return std::make_tuple(status, std::move(result)); }; api["findRandomPointAroundCircle"] = [] (const osg::Vec3f& position, float maxRadius, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; DetourNavigator::Flags includeFlags = defaultIncludeFlags; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) agentBounds.mHalfExtents = *v; } if (const auto& v = options->get>("includeFlags")) includeFlags = *v; } constexpr auto getRandom = [] { return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng()); }; return DetourNavigator::findRandomPointAroundCircle(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, position, maxRadius, includeFlags, getRandom); }; api["castNavigationRay"] = [] (const osg::Vec3f& from, const osg::Vec3f& to, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; DetourNavigator::Flags includeFlags = defaultIncludeFlags; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) agentBounds.mHalfExtents = *v; } if (const auto& v = options->get>("includeFlags")) includeFlags = *v; } return DetourNavigator::raycast(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, from, to, includeFlags); }; return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/object.cpp000066400000000000000000000052351445372753700220100ustar00rootroot00000000000000#include "object.hpp" #include "types/types.hpp" #include namespace MWLua { std::string idToString(const ObjectId& id) { return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } bool isMarker(const MWWorld::Ptr& ptr) { return Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId()); } std::string ptrToString(const MWWorld::Ptr& ptr) { std::string res = "object"; res.append(idToString(getId(ptr))); res.append(" ("); res.append(getLuaObjectTypeName(ptr)); res.append(", "); res.append(ptr.getCellRef().getRefId()); res.append(")"); return res; } std::string Object::toString() const { if (isValid()) return ptrToString(ptr()); else return "object" + idToString(mId) + " (not found)"; } bool Object::isValid() const { if (mLastUpdate < mObjectRegistry->mUpdateCounter) { updatePtr(); mLastUpdate = mObjectRegistry->mUpdateCounter; } return !mPtr.isEmpty(); } const MWWorld::Ptr& Object::ptr() const { if (!isValid()) throw std::runtime_error("Object is not available: " + idToString(mId)); return mPtr; } void ObjectRegistry::update() { if (mChanged) { mUpdateCounter++; mChanged = false; } } void ObjectRegistry::clear() { mObjectMapping.clear(); mChanged = false; mUpdateCounter = 0; mLastAssignedId.unset(); } MWWorld::Ptr ObjectRegistry::getPtr(ObjectId id, bool local) { MWWorld::Ptr ptr; auto it = mObjectMapping.find(id); if (it != mObjectMapping.end()) ptr = it->second; if (local) { // TODO: Return ptr only if it is active or was active in the previous frame, otherwise return empty. // Needed because in multiplayer inactive objects will not be synchronized, so an be out of date. } else { // TODO: If Ptr is empty then try to load the object from esp/esm3. } return ptr; } ObjectId ObjectRegistry::registerPtr(const MWWorld::Ptr& ptr) { ObjectId id = ptr.getCellRef().getOrAssignRefNum(mLastAssignedId); mChanged = true; mObjectMapping[id] = ptr; return id; } ObjectId ObjectRegistry::deregisterPtr(const MWWorld::Ptr& ptr) { ObjectId id = getId(ptr); mChanged = true; mObjectMapping.erase(id); return id; } } openmw-openmw-0.48.0/apps/openmw/mwlua/object.hpp000066400000000000000000000102441445372753700220110ustar00rootroot00000000000000#ifndef MWLUA_OBJECT_H #define MWLUA_OBJECT_H #include #include #include #include #include "../mwworld/ptr.hpp" namespace MWLua { // ObjectId is a unique identifier of a game object. // It can change only if the order of content files was change. using ObjectId = ESM::RefNum; inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); bool isMarker(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry { public: ObjectRegistry() { mLastAssignedId.unset(); } void update(); // Should be called every frame. void clear(); // Should be called before starting or loading a new game. ObjectId registerPtr(const MWWorld::Ptr& ptr); ObjectId deregisterPtr(const MWWorld::Ptr& ptr); // Returns Ptr by id. If object is not found, returns empty Ptr. // If local = true, returns non-empty ptr only if it can be used in local scripts // (i.e. is active or was active in the previous frame). MWWorld::Ptr getPtr(ObjectId id, bool local); // Needed only for saving/loading. const ObjectId& getLastAssignedId() const { return mLastAssignedId; } void setLastAssignedId(ObjectId id) { mLastAssignedId = id; } private: friend class Object; friend class LuaManager; bool mChanged = false; int64_t mUpdateCounter = 0; std::map mObjectMapping; ObjectId mLastAssignedId; }; // Lua scripts can't use MWWorld::Ptr directly, because lifetime of a script can be longer than lifetime of Ptr. // `GObject` and `LObject` are intended to be passed to Lua as a userdata. // It automatically updates the underlying Ptr when needed. class Object { public: Object(ObjectId id, ObjectRegistry* reg) : mId(id), mObjectRegistry(reg) {} virtual ~Object() {} ObjectId id() const { return mId; } std::string toString() const; // Updates and returns the underlying Ptr. Throws an exception if object is not available. const MWWorld::Ptr& ptr() const; // Returns `true` if calling `ptr()` is safe. bool isValid() const; virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GOBject virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell protected: virtual void updatePtr() const = 0; const ObjectId mId; ObjectRegistry* mObjectRegistry; mutable MWWorld::Ptr mPtr; mutable int64_t mLastUpdate = -1; }; // Used only in local scripts struct LCell { MWWorld::CellStore* mStore; }; class LObject : public Object { using Object::Object; void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, true); } sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id, mObjectRegistry); } sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, LCell{store}); } }; // Used only in global scripts struct GCell { MWWorld::CellStore* mStore; }; class GObject : public Object { using Object::Object; void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, false); } sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id, mObjectRegistry); } sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, GCell{store}); } }; using ObjectIdList = std::shared_ptr>; template struct ObjectList { ObjectIdList mIds; }; using GObjectList = ObjectList; using LObjectList = ObjectList; template struct Inventory { Obj mObj; }; } #endif // MWLUA_OBJECT_H openmw-openmw-0.48.0/apps/openmw/mwlua/objectbindings.cpp000066400000000000000000000405161445372753700235270ustar00rootroot00000000000000#include "luabindings.hpp" #include #include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/creaturestats.hpp" #include "eventqueue.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical> : std::false_type {}; template <> struct is_automagical> : std::false_type {}; } namespace MWLua { namespace { class TeleportAction final : public LuaManager::Action { public: TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} void apply(WorldView& worldView) const override { MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); if (!cell) throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); const MWWorld::Class& cls = obj.getClass(); bool isPlayer = obj == world->getPlayerPtr(); if (cls.isActor()) cls.getCreatureStats(obj).land(isPlayer); if (isPlayer) { ESM::Position esmPos; static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); world->getPlayer().setTeleported(true); if (cell->isExterior()) world->changeToExteriorCell(esmPos, true); else world->changeToInteriorCell(mCell, esmPos, true); } else { MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); world->rotateObject(newObj, mRot); } } std::string toString() const override { return "TeleportAction"; } private: ObjectId mObject; std::string mCell; osg::Vec3f mPos; osg::Vec3f mRot; }; class ActivateAction final : public LuaManager::Action { public: ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) : Action(state), mObject(object), mActor(actor) {} void apply(WorldView& worldView) const override { MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); if (object.isEmpty()) throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); if (actor.isEmpty()) throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); if (object.getRefData().activate()) { MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); std::unique_ptr action = object.getClass().activate(object, actor); action->execute(actor); } } std::string toString() const override { return std::string("ActivateAction object=") + idToString(mObject) + std::string(" actor=") + idToString(mActor); } private: ObjectId mObject; ObjectId mActor; }; template using Cell = std::conditional_t, LCell, GCell>; template void registerObjectList(const std::string& prefix, const Context& context) { using ListT = ObjectList; sol::state& lua = context.mLua->sol(); ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); sol::usertype listT = lua.new_usertype(prefix + "ObjectList"); listT[sol::meta_function::to_string] = [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; listT[sol::meta_function::index] = [registry](const ListT& list, size_t index) { if (index > 0 && index <= list.mIds->size()) return ObjectT((*list.mIds)[index - 1], registry); else throw std::runtime_error("Index out of range"); }; listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); } template void addBasicBindings(sol::usertype& objectT, const Context& context) { objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); }; objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId(); }); objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { const MWWorld::Ptr& ptr = o.ptr(); if (ptr.isInCell()) return Cell{ptr.getCell()}; else return sol::nullopt; }); objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asRotationVec3(); }); objectT["type"] = sol::readonly_property([types=getTypeToPackageTable(context.mLua->sol())](const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) { uint32_t esmRecordType = actor.ptr().getType(); if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " + ptrToString(actor.ptr())); context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); }; if constexpr (std::is_same_v) { // Only for global scripts objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path) { const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); std::optional scriptId = cfg.findId(path); if (!scriptId) throw std::runtime_error("Unknown script: " + std::string(path)); if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path)); if (object.ptr().getType() == ESM::REC_STAT) throw std::runtime_error("Attaching scripts to Static is not allowed: " + std::string(path)); luaManager->addCustomLocalScript(object.ptr(), *scriptId); }; objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path) { const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); std::optional scriptId = cfg.findId(path); if (!scriptId) return false; MWWorld::Ptr ptr = object.ptr(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (localScripts) return localScripts->hasScript(*scriptId); else return false; }; objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path) { const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); std::optional scriptId = cfg.findId(path); if (!scriptId) throw std::runtime_error("Unknown script: " + std::string(path)); MWWorld::Ptr ptr = object.ptr(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts || !localScripts->hasScript(*scriptId)) throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr)); if (localScripts->getAutoStartConf().count(*scriptId) > 0) throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); localScripts->removeScript(*scriptId); }; objectT["teleport"] = [context](const GObject& object, std::string_view cell, const osg::Vec3f& pos, const sol::optional& optRot) { MWWorld::Ptr ptr = object.ptr(); osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); auto action = std::make_unique(context.mLua, object.id(), std::string(cell), pos, rot); if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) context.mLuaManager->addTeleportPlayerAction(std::move(action)); else context.mLuaManager->addAction(std::move(action)); }; } } template void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) { using InventoryT = Inventory; sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); inventoryT[sol::meta_function::to_string] = [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; inventoryT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())]( const InventoryT& inventory, sol::optional type) { int mask = -1; sol::optional typeId = sol::nullopt; if (type.has_value()) typeId = ids[*type]; else mask = MWWorld::ContainerStore::Type_All; if (typeId.has_value()) { switch (*typeId) { case ESM::REC_ALCH: mask = MWWorld::ContainerStore::Type_Potion; break; case ESM::REC_ARMO: mask = MWWorld::ContainerStore::Type_Armor; break; case ESM::REC_BOOK: mask = MWWorld::ContainerStore::Type_Book; break; case ESM::REC_CLOT: mask = MWWorld::ContainerStore::Type_Clothing; break; case ESM::REC_INGR: mask = MWWorld::ContainerStore::Type_Ingredient; break; case ESM::REC_LIGH: mask = MWWorld::ContainerStore::Type_Light; break; case ESM::REC_MISC: mask = MWWorld::ContainerStore::Type_Miscellaneous; break; case ESM::REC_WEAP: mask = MWWorld::ContainerStore::Type_Weapon; break; case ESM::REC_APPA: mask = MWWorld::ContainerStore::Type_Apparatus; break; case ESM::REC_LOCK: mask = MWWorld::ContainerStore::Type_Lockpick; break; case ESM::REC_PROB: mask = MWWorld::ContainerStore::Type_Probe; break; case ESM::REC_REPA: mask = MWWorld::ContainerStore::Type_Repair; break; default:; } } if (mask == -1) throw std::runtime_error(std::string("Incorrect type argument in inventory:getAll: " + LuaUtil::toString(*type))); const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); ObjectIdList list = std::make_shared>(); auto it = store.begin(mask); while (it.getType() != -1) { const MWWorld::Ptr& item = *(it++); worldView->getObjectRegistry()->registerPtr(item); list->push_back(getId(item)); } return ObjectList{list}; }; inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); return store.count(recordId); }; if constexpr (std::is_same_v) { // Only for global scripts // TODO // obj.inventory:drop(obj2, [count]) // obj.inventory:drop(recordId, [count]) // obj.inventory:addNew(recordId, [count]) // obj.inventory:remove(obj/recordId, [count]) /*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; inventoryT["drop"] = [](const InventoryT& inventory) {}; inventoryT["addNew"] = [](const InventoryT& inventory) {}; inventoryT["remove"] = [](const InventoryT& inventory) {};*/ } } template void initObjectBindings(const std::string& prefix, const Context& context) { sol::usertype objectT = context.mLua->sol().new_usertype( prefix + "Object", sol::base_classes, sol::bases()); addBasicBindings(objectT, context); addInventoryBindings(objectT, prefix, context); registerObjectList(prefix, context); } } // namespace void initObjectBindingsForLocalScripts(const Context& context) { initObjectBindings("L", context); } void initObjectBindingsForGlobalScripts(const Context& context) { initObjectBindings("G", context); } } openmw-openmw-0.48.0/apps/openmw/mwlua/playerscripts.hpp000066400000000000000000000064161445372753700234550ustar00rootroot00000000000000#ifndef MWLUA_PLAYERSCRIPTS_H #define MWLUA_PLAYERSCRIPTS_H #include #include #include "../mwbase/luamanager.hpp" #include "localscripts.hpp" namespace MWLua { class PlayerScripts : public LocalScripts { public: PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj) { registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers, &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) { using InputEvent = MWBase::LuaManager::InputEvent; switch (event.mType) { case InputEvent::KeyPressed: callEngineHandlers(mKeyPressHandlers, std::get(event.mValue)); break; case InputEvent::KeyReleased: callEngineHandlers(mKeyReleaseHandlers, std::get(event.mValue)); break; case InputEvent::ControllerPressed: callEngineHandlers(mControllerButtonPressHandlers, std::get(event.mValue)); break; case InputEvent::ControllerReleased: callEngineHandlers(mControllerButtonReleaseHandlers, std::get(event.mValue)); break; case InputEvent::Action: callEngineHandlers(mActionHandlers, std::get(event.mValue)); break; case InputEvent::TouchPressed: callEngineHandlers(mTouchpadPressed, std::get(event.mValue)); break; case InputEvent::TouchReleased: callEngineHandlers(mTouchpadReleased, std::get(event.mValue)); break; case InputEvent::TouchMoved: callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); break; } } void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } bool consoleCommand(const std::string& consoleMode, const std::string& command, const sol::object& selectedObject) { callEngineHandlers(mConsoleCommandHandlers, consoleMode, command, selectedObject); return !mConsoleCommandHandlers.mList.empty(); } private: EngineHandlerList mConsoleCommandHandlers{"onConsoleCommand"}; EngineHandlerList mKeyPressHandlers{"onKeyPress"}; EngineHandlerList mKeyReleaseHandlers{"onKeyRelease"}; EngineHandlerList mControllerButtonPressHandlers{"onControllerButtonPress"}; EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mActionHandlers{"onInputAction"}; EngineHandlerList mOnFrameHandlers{"onFrame"}; EngineHandlerList mTouchpadPressed{ "onTouchPress" }; EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; EngineHandlerList mTouchpadMoved{ "onTouchMove" }; }; } #endif // MWLUA_PLAYERSCRIPTS_H openmw-openmw-0.48.0/apps/openmw/mwlua/postprocessingbindings.cpp000066400000000000000000000146401445372753700253420ustar00rootroot00000000000000#include "luabindings.hpp" #include "../mwbase/environment.hpp" #include "../mwrender/postprocessor.hpp" #include "luamanagerimp.hpp" namespace { template class SetUniformShaderAction final : public MWLua::LuaManager::Action { public: SetUniformShaderAction(LuaUtil::LuaState* state, std::shared_ptr shader, const std::string& name, const T& value) : MWLua::LuaManager::Action(state), mShader(std::move(shader)), mName(name), mValue(value) {} void apply(MWLua::WorldView&) const override { MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(mShader, mName, mValue); } std::string toString() const override { return std::string("SetUniformShaderAction shader=") + (mShader ? mShader->getName() : "nil") + std::string("uniform=") + (mShader ? mName : "nil"); } private: std::shared_ptr mShader; std::string mName; T mValue; }; } namespace MWLua { struct Shader; } namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { struct Shader { std::shared_ptr mShader; Shader(std::shared_ptr shader) : mShader(std::move(shader)) {} std::string toString() const { if (!mShader) return "Shader(nil)"; return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName()); } enum { Action_None, Action_Enable, Action_Disable } mQueuedAction = Action_None; }; template auto getSetter(const Context& context) { return [context](const Shader& shader, const std::string& name, const T& value) { context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); }; } template auto getArraySetter(const Context& context) { return [context](const Shader& shader, const std::string& name, const sol::table& table) { auto targetSize = MWBase::Environment::get().getWorld()->getPostProcessor()->getUniformSize(shader.mShader, name); if (!targetSize.has_value()) throw std::runtime_error(Misc::StringUtils::format("Failed setting uniform array '%s'", name)); if (*targetSize != table.size()) throw std::runtime_error(Misc::StringUtils::format("Mismatching uniform array size, got %zu expected %zu", table.size(), *targetSize)); std::vector values; values.reserve(*targetSize); for (size_t i = 0; i < *targetSize; ++i) { sol::object obj = table[i+1]; if (!obj.is()) throw std::runtime_error("Invalid type for uniform array"); values.push_back(obj.as()); } context.mLuaManager->addAction(std::make_unique>>(context.mLua, shader.mShader, name, values)); }; } sol::table initPostprocessingPackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); sol::usertype shader = context.mLua->sol().new_usertype("Shader"); shader[sol::meta_function::to_string] = [](const Shader& shader) { return shader.toString(); }; shader["enable"] = [context](Shader& shader, sol::optional optPos) { std::optional pos = std::nullopt; if (optPos) pos = optPos.value(); if (shader.mShader && shader.mShader->isValid()) shader.mQueuedAction = Shader::Action_Enable; context.mLuaManager->addAction( [=, &shader] { shader.mQueuedAction = Shader::Action_None; if (MWBase::Environment::get().getWorld()->getPostProcessor()->enableTechnique(shader.mShader, pos) == MWRender::PostProcessor::Status_Error) throw std::runtime_error("Failed enabling shader '" + shader.mShader->getName() + "'"); } ); }; shader["disable"] = [context](Shader& shader) { shader.mQueuedAction = Shader::Action_Disable; context.mLuaManager->addAction( [&] { shader.mQueuedAction = Shader::Action_None; if (MWBase::Environment::get().getWorld()->getPostProcessor()->disableTechnique(shader.mShader) == MWRender::PostProcessor::Status_Error) throw std::runtime_error("Failed disabling shader '" + shader.mShader->getName() + "'"); } ); }; shader["isEnabled"] = [](const Shader& shader) { if (shader.mQueuedAction == Shader::Action_Enable) return true; else if (shader.mQueuedAction == Shader::Action_Disable) return false; return MWBase::Environment::get().getWorld()->getPostProcessor()->isTechniqueEnabled(shader.mShader); }; shader["setBool"] = getSetter(context); shader["setFloat"] = getSetter(context); shader["setInt"] = getSetter(context); shader["setVector2"] = getSetter(context); shader["setVector3"] = getSetter(context); shader["setVector4"] = getSetter(context); shader["setFloatArray"] = getArraySetter(context); shader["setIntArray"] = getArraySetter(context); shader["setVector2Array"] = getArraySetter(context); shader["setVector3Array"] = getArraySetter(context); shader["setVector4Array"] = getArraySetter(context); api["load"] = [](const std::string& name) { Shader shader{MWBase::Environment::get().getWorld()->getPostProcessor()->loadTechnique(name, false)}; if (!shader.mShader || !shader.mShader->isValid()) throw std::runtime_error(Misc::StringUtils::format("Failed loading shader '%s'", name)); if (!shader.mShader->getDynamic()) throw std::runtime_error(Misc::StringUtils::format("Shader '%s' is not marked as dynamic", name)); return shader; }; return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/stats.cpp000066400000000000000000000415101445372753700216740ustar00rootroot00000000000000#include "stats.hpp" #include #include #include #include #include #include #include #include "localscripts.hpp" #include "luamanagerimp.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" namespace { template auto addIndexedAccessor(int index) { return sol::overload( [index](MWLua::LocalScripts::SelfObject& o) { return T::create(&o, index); }, [index](const MWLua::LObject& o) { return T::create(o, index); }, [index](const MWLua::GObject& o) { return T::create(o, index); } ); } template void addProp(const MWLua::Context& context, sol::usertype& type, std::string_view prop, G getter) { type[prop] = sol::property( [=](const T& stat) { return stat.get(context, prop, getter); }, [=](const T& stat, const sol::object& value) { stat.cache(context, prop, value); }); } using SelfObject = MWLua::LocalScripts::SelfObject; using StatObject = std::variant; const MWLua::Object* getObject(const StatObject& obj) { return std::visit([] (auto&& variant) -> const MWLua::Object* { using T = std::decay_t; if constexpr(std::is_same_v) return variant; else if constexpr(std::is_same_v) return &variant; else if constexpr(std::is_same_v) return &variant; }, obj); } template sol::object getValue(const MWLua::Context& context, const StatObject& obj, SelfObject::CachedStat::Setter setter, int index, std::string_view prop, G getter) { return std::visit([&] (auto&& variant) { using T = std::decay_t; if constexpr(std::is_same_v) { auto it = variant->mStatsCache.find({ setter, index, prop }); if(it != variant->mStatsCache.end()) return it->second; return sol::make_object(context.mLua->sol(), getter(variant)); } else if constexpr(std::is_same_v) return sol::make_object(context.mLua->sol(), getter(&variant)); else if constexpr(std::is_same_v) return sol::make_object(context.mLua->sol(), getter(&variant)); }, obj); } } namespace MWLua { namespace { class StatUpdateAction final : public LuaManager::Action { ObjectId mId; public: StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} void apply(WorldView& worldView) const override { LObject obj(mId, worldView.getObjectRegistry()); LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); if (scripts) scripts->applyStatsCache(); } std::string toString() const override { return "StatUpdateAction"; } }; } class LevelStat { StatObject mObject; LevelStat(StatObject object) : mObject(std::move(object)) {} public: sol::object getCurrent(const Context& context) const { return getValue(context, mObject, &LevelStat::setValue, 0, "current", [](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } void setCurrent(const Context& context, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&LevelStat::setValue, 0, "current"}] = value; } sol::object getProgress(const Context& context) const { const auto& ptr = getObject(mObject)->ptr(); if(!ptr.getClass().isNpc()) return sol::nil; return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return LevelStat{std::move(object)}; } static void setValue(int, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); if(prop == "current") stats.setLevel(LuaUtil::cast(value)); } }; class DynamicStat { StatObject mObject; int mIndex; DynamicStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &DynamicStat::setValue, mIndex, prop, [this, getter](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); }); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return DynamicStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&DynamicStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); auto stat = stats.getDynamic(index); float floatValue = LuaUtil::cast(value); if(prop == "base") stat.setBase(floatValue); else if(prop == "current") stat.setCurrent(floatValue, true, true); else if(prop == "modifier") stat.setModifier(floatValue); stats.setDynamic(index, stat); } }; class AttributeStat { StatObject mObject; int mIndex; AttributeStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &AttributeStat::setValue, mIndex, prop, [this, getter](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).*getter)(); }); } float getModified(const Context& context) const { auto base = LuaUtil::cast(get(context, "base", &MWMechanics::AttributeValue::getBase)); auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::AttributeValue::getDamage)); auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::AttributeValue::getModifier)); return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isActor()) return {}; return AttributeStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&AttributeStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getCreatureStats(ptr); auto stat = stats.getAttribute(index); float floatValue = LuaUtil::cast(value); if(prop == "base") stat.setBase(floatValue); else if(prop == "damage") { stat.restore(stat.getDamage()); stat.damage(floatValue); } else if(prop == "modifier") stat.setModifier(floatValue); stats.setAttribute(index, stat); } }; class SkillStat { StatObject mObject; int mIndex; SkillStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} static float getProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) { float progress = stat.getProgress(); if(progress != 0.f) progress /= getMaxProgress(ptr, index, stat); return progress; } static float getMaxProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) { const auto& store = MWBase::Environment::get().getWorld()->getStore(); const auto cl = store.get().find(ptr.get()->mBase->mClass); return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(index, *cl); } public: template sol::object get(const Context& context, std::string_view prop, G getter) const { return getValue(context, mObject, &SkillStat::setValue, mIndex, prop, [this, getter](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return (ptr.getClass().getNpcStats(ptr).getSkill(mIndex).*getter)(); }); } float getModified(const Context& context) const { auto base = LuaUtil::cast(get(context, "base", &MWMechanics::SkillValue::getBase)); auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::SkillValue::getDamage)); auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::SkillValue::getModifier)); return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified } sol::object getProgress(const Context& context) const { return getValue(context, mObject, &SkillStat::setValue, mIndex, "progress", [this](const MWLua::Object* obj) { const auto& ptr = obj->ptr(); return getProgress(ptr, mIndex, ptr.getClass().getNpcStats(ptr).getSkill(mIndex)); }); } static std::optional create(StatObject object, int index) { if(!getObject(object)->ptr().getClass().isNpc()) return {}; return SkillStat{std::move(object), index}; } void cache(const Context& context, std::string_view prop, const sol::object& value) const { SelfObject* obj = std::get(mObject); if(obj->mStatsCache.empty()) context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); obj->mStatsCache[SelfObject::CachedStat{&SkillStat::setValue, mIndex, prop}] = value; } static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { auto& stats = ptr.getClass().getNpcStats(ptr); auto stat = stats.getSkill(index); float floatValue = LuaUtil::cast(value); if(prop == "base") stat.setBase(floatValue); else if(prop == "damage") { stat.restore(stat.getDamage()); stat.damage(floatValue); } else if(prop == "modifier") stat.setModifier(floatValue); else if(prop == "progress") stat.setProgress(floatValue * getMaxProgress(ptr, index, stat)); stats.setSkill(index, stat); } }; } namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addActorStatsBindings(sol::table& actor, const Context& context) { sol::table stats(context.mLua->sol(), sol::create); actor["stats"] = LuaUtil::makeReadOnly(stats); auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); levelStatT["current"] = sol::property( [context](const LevelStat& stat) { return stat.getCurrent(context); }, [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); stats["level"] = addIndexedAccessor(0); auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); addProp(context, dynamicStatT, "base", &MWMechanics::DynamicStat::getBase); addProp(context, dynamicStatT, "current", &MWMechanics::DynamicStat::getCurrent); addProp(context, dynamicStatT, "modifier", &MWMechanics::DynamicStat::getModifier); sol::table dynamic(context.mLua->sol(), sol::create); stats["dynamic"] = LuaUtil::makeReadOnly(dynamic); dynamic["health"] = addIndexedAccessor(0); dynamic["magicka"] = addIndexedAccessor(1); dynamic["fatigue"] = addIndexedAccessor(2); auto attributeStatT = context.mLua->sol().new_usertype("AttributeStat"); addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); attributeStatT["modified"] = sol::property([=](const AttributeStat& stat) { return stat.getModified(context); }); addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); sol::table attributes(context.mLua->sol(), sol::create); stats["attributes"] = LuaUtil::makeReadOnly(attributes); for(int id = ESM::Attribute::Strength; id < ESM::Attribute::Length; ++id) attributes[Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id])] = addIndexedAccessor(id); } void addNpcStatsBindings(sol::table& npc, const Context& context) { sol::table npcStats(context.mLua->sol(), sol::create); sol::table baseMeta(context.mLua->sol(), sol::create); baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(npc["baseType"]["stats"]); npcStats[sol::metatable_key] = baseMeta; npc["stats"] = LuaUtil::makeReadOnly(npcStats); auto skillStatT = context.mLua->sol().new_usertype("SkillStat"); addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); }); addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); skillStatT["progress"] = sol::property( [context](const SkillStat& stat) { return stat.getProgress(context); }, [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); sol::table skills(context.mLua->sol(), sol::create); npcStats["skills"] = LuaUtil::makeReadOnly(skills); for(int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id])] = addIndexedAccessor(id); } } openmw-openmw-0.48.0/apps/openmw/mwlua/stats.hpp000066400000000000000000000003631445372753700217020ustar00rootroot00000000000000#ifndef MWLUA_STATS_H #define MWLUA_STATS_H #include "context.hpp" namespace MWLua { void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); } #endif openmw-openmw-0.48.0/apps/openmw/mwlua/types/000077500000000000000000000000001445372753700211755ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwlua/types/activator.cpp000066400000000000000000000032471445372753700237030ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addActivatorBindings(sol::table activator, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); activator["record"] = sol::overload( [](const Object& obj) -> const ESM::Activator* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Activator* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Activator"); record[sol::meta_function::to_string] = [](const ESM::Activator& rec) { return "ESM3_Activator[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Activator& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mScript; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/actor.cpp000066400000000000000000000271401445372753700230150ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include #include #include "../luabindings.hpp" #include "../localscripts.hpp" #include "../luamanagerimp.hpp" #include "../stats.hpp" namespace MWLua { namespace { class SetEquipmentAction final : public LuaManager::Action { public: using Item = std::variant; // recordId or ObjectId using Equipment = std::map; // slot to item SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} void apply(WorldView& worldView) const override { MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); std::array usedSlots; std::fill(usedSlots.begin(), usedSlots.end(), false); static constexpr int anySlot = -1; auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool { auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); MWWorld::Ptr itemPtr; if (std::holds_alternative(item)) { itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); if (old_it != store.end() && *old_it == itemPtr) return true; // already equipped if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || itemPtr.getContainerStore() != static_cast(&store)) { Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; return false; } } else { const std::string& recordId = std::get(item); if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) return true; // already equipped itemPtr = store.search(recordId); if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) { Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; return false; } } auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); if (!requestedSlotIsAllowed) { auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); if (firstAllowed == allowedSlots.end()) { Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); return false; } slot = *firstAllowed; } // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); if (it == store.end()) // should never happen throw std::logic_error("Item not found in container"); store.equip(slot, it, actor); return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed }; for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { auto old_it = store.getSlot(slot); auto new_it = mEquipment.find(slot); if (new_it == mEquipment.end()) { if (old_it != store.end()) store.unequipSlot(slot, actor); continue; } if (tryEquipToSlot(slot, new_it->second)) usedSlots[slot] = true; } for (const auto& [slot, item] : mEquipment) if (slot >= MWWorld::InventoryStore::Slots) tryEquipToSlot(anySlot, item); } std::string toString() const override { return "SetEquipmentAction"; } private: ObjectId mActor; Equipment mEquipment; }; } using SelfObject = LocalScripts::SelfObject; void addActorBindings(sol::table actor, const Context& context) { actor["STANCE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Nothing", MWMechanics::DrawState::Nothing}, {"Weapon", MWMechanics::DrawState::Weapon}, {"Spell", MWMechanics::DrawState::Spell}, })); actor["EQUIPMENT_SLOT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, {"Cuirass", MWWorld::InventoryStore::Slot_Cuirass}, {"Greaves", MWWorld::InventoryStore::Slot_Greaves}, {"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron}, {"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron}, {"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet}, {"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet}, {"Boots", MWWorld::InventoryStore::Slot_Boots}, {"Shirt", MWWorld::InventoryStore::Slot_Shirt}, {"Pants", MWWorld::InventoryStore::Slot_Pants}, {"Skirt", MWWorld::InventoryStore::Slot_Skirt}, {"Robe", MWWorld::InventoryStore::Slot_Robe}, {"LeftRing", MWWorld::InventoryStore::Slot_LeftRing}, {"RightRing", MWWorld::InventoryStore::Slot_RightRing}, {"Amulet", MWWorld::InventoryStore::Slot_Amulet}, {"Belt", MWWorld::InventoryStore::Slot_Belt}, {"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight}, {"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft}, {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} })); actor["stance"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); if (cls.isActor()) return cls.getCreatureStats(o.ptr()).getDrawState(); else throw std::runtime_error("Actor expected"); }; actor["canMove"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); return cls.getMaxSpeed(o.ptr()) > 0; }; actor["runSpeed"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); return cls.getRunSpeed(o.ptr()); }; actor["walkSpeed"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); return cls.getWalkSpeed(o.ptr()); }; actor["currentSpeed"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); return cls.getCurrentSpeed(o.ptr()); }; actor["isOnGround"] = [](const LObject& o) { return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); }; actor["isSwimming"] = [](const LObject& o) { return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); }; actor["inventory"] = sol::overload( [](const LObject& o) { return Inventory{o}; }, [](const GObject& o) { return Inventory{o}; } ); auto getAllEquipment = [context](const Object& o) { const MWWorld::Ptr& ptr = o.ptr(); sol::table equipment(context.mLua->sol(), sol::create); if (!ptr.getClass().hasInventoryStore(ptr)) return equipment; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { auto it = store.getSlot(slot); if (it == store.end()) continue; context.mWorldView->getObjectRegistry()->registerPtr(*it); equipment[slot] = o.getObject(context.mLua->sol(), getId(*it)); } return equipment; }; auto getEquipmentFromSlot = [context](const Object& o, int slot) -> sol::object { const MWWorld::Ptr& ptr = o.ptr(); sol::table equipment(context.mLua->sol(), sol::create); if (!ptr.getClass().hasInventoryStore(ptr)) return sol::nil; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); auto it = store.getSlot(slot); if (it == store.end()) return sol::nil; context.mWorldView->getObjectRegistry()->registerPtr(*it); return o.getObject(context.mLua->sol(), getId(*it)); }; actor["equipment"] = sol::overload(getAllEquipment, getEquipmentFromSlot); actor["hasEquipped"] = [](const Object& o, const Object& item) { const MWWorld::Ptr& ptr = o.ptr(); if (!ptr.getClass().hasInventoryStore(ptr)) return false; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); return store.isEquipped(item.ptr()); }; actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment) { if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) { if (!equipment.empty()) throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); return; } SetEquipmentAction::Equipment eqp; for (auto& [key, value] : equipment) { int slot = LuaUtil::cast(key); if (value.is()) eqp[slot] = LuaUtil::cast(value).id(); else eqp[slot] = LuaUtil::cast(value); } context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); }; actor["getPathfindingAgentBounds"] = [context](const LObject& o) { const DetourNavigator::AgentBounds agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(o.ptr()); sol::table result = context.mLua->newTable(); result["shapeType"] = agentBounds.mShapeType; result["halfExtents"] = agentBounds.mHalfExtents; return result; }; addActorStatsBindings(actor, context); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/apparatus.cpp000066400000000000000000000052321445372753700237030ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addApparatusBindings(sol::table apparatus, const Context& context) { apparatus["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"MortarPestle", ESM::Apparatus::MortarPestle}, {"Alembic", ESM::Apparatus::Alembic}, {"Calcinator", ESM::Apparatus::Calcinator}, {"Retort", ESM::Apparatus::Retort}, })); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); apparatus["record"] = sol::overload( [](const Object& obj) -> const ESM::Apparatus* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Apparatus* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Apparatus"); record[sol::meta_function::to_string] = [](const ESM::Apparatus& rec) { return "ESM3_Apparatus[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mScript; }); record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["type"] = sol::readonly_property([](const ESM::Apparatus& rec) -> int { return rec.mData.mType; }); record["value"] = sol::readonly_property([](const ESM::Apparatus& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Apparatus& rec) -> float { return rec.mData.mWeight; }); record["quality"] = sol::readonly_property([](const ESM::Apparatus& rec) -> float { return rec.mData.mQuality; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/book.cpp000066400000000000000000000061601445372753700226360ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addBookBindings(sol::table book, const Context& context) { sol::table skill(context.mLua->sol(), sol::create); book["SKILL"] = LuaUtil::makeStrictReadOnly(skill); for (int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) { std::string skillName = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]); skill[skillName] = skillName; } auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); book["record"] = sol::overload( [](const Object& obj) -> const ESM::Book* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Book* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Book"); record[sol::meta_function::to_string] = [](const ESM::Book& rec) { return "ESM3_Book[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript; }); record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); record["enchant"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant; }); record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); record["enchantCapacity"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mEnchant * 0.1f; }); record["skill"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional { if (rec.mData.mSkillId >= 0) return Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[rec.mData.mSkillId]); else return sol::nullopt; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/container.cpp000066400000000000000000000050331445372753700236640ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { static const MWWorld::Ptr& containerPtr(const Object& o) { return verifyType(ESM::REC_CONT, o.ptr()); } void addContainerBindings(sol::table container, const Context& context) { container["content"] = sol::overload( [](const LObject& o) { containerPtr(o); return Inventory{o}; }, [](const GObject& o) { containerPtr(o); return Inventory{o}; } ); container["encumbrance"] = [](const Object& obj) -> float { const MWWorld::Ptr& ptr = containerPtr(obj); return ptr.getClass().getEncumbrance(ptr); }; container["capacity"] = [](const Object& obj) -> float { const MWWorld::Ptr& ptr = containerPtr(obj); return ptr.getClass().getCapacity(ptr); }; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); container["record"] = sol::overload( [](const Object& obj) -> const ESM::Container* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Container* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Container"); record[sol::meta_function::to_string] = [](const ESM::Container& rec) -> std::string { return "ESM3_Container[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Container& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mScript; }); record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/creature.cpp000066400000000000000000000032751445372753700235220ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../stats.hpp" #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addCreatureBindings(sol::table creature, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); creature["record"] = sol::overload( [](const Object& obj) -> const ESM::Creature* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Creature* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Creature"); record[sol::meta_function::to_string] = [](const ESM::Creature& rec) { return "ESM3_Creature[" + rec.mId + "]"; }; record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mScript; }); record["baseCreature"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mOriginal; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/door.cpp000066400000000000000000000055271445372753700226550ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { static const MWWorld::Ptr& doorPtr(const Object& o) { return verifyType(ESM::REC_DOOR, o.ptr()); } void addDoorBindings(sol::table door, const Context& context) { door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); }; door["destPosition"] = [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asVec3(); }; door["destRotation"] = [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asRotationVec3(); }; door["destCell"] = [worldView=context.mWorldView](sol::this_state lua, const Object& o) -> sol::object { const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef(); if (!cellRef.getTeleport()) return sol::nil; MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3()); if (cell) return o.getCell(lua, cell); else return sol::nil; }; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); door["record"] = sol::overload( [](const Object& obj) -> const ESM::Door* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Door* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); record[sol::meta_function::to_string] = [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript; }); record["openSound"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mOpenSound; }); record["closeSound"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mCloseSound; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/ingredient.cpp000066400000000000000000000041231445372753700240310ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addIngredientBindings(sol::table ingredient, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); ingredient["record"] = sol::overload( [](const Object& obj)-> const ESM::Ingredient* { return obj.ptr().get()->mBase; }, [store](const std::string& recordID)-> const ESM::Ingredient* {return store->find(recordID); }); sol::usertype record = context.mLua->sol().new_usertype(("ESM3_Ingredient")); record[sol::meta_function::to_string] = [](const ESM::Ingredient& rec) {return "ESM3_Ingredient[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mScript; }); record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["weight"] = sol::readonly_property([](const ESM::Ingredient& rec) -> float {return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Ingredient& rec) -> int{return rec.mData.mValue; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/lockpick.cpp000066400000000000000000000044531445372753700235060ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addLockpickBindings(sol::table lockpick, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); lockpick["record"] = sol::overload( [](const Object& obj) -> const ESM::Lockpick* { return obj.ptr().get()->mBase;}, [store](const std::string& recordId) -> const ESM::Lockpick* { return store->find(recordId);}); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Lockpick"); record[sol::meta_function::to_string] = [](const ESM::Lockpick& rec) { return "ESM3_Lockpick[" + rec.mId + "]";}; record["id"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId;}); record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName;}); record["model"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mScript;}); record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["maxCondition"] = sol::readonly_property([](const ESM::Lockpick& rec) -> int { return rec.mData.mUses;}); record["value"] = sol::readonly_property([](const ESM::Lockpick& rec) -> int { return rec.mData.mValue;}); record["weight"] = sol::readonly_property([](const ESM::Lockpick& rec) -> float { return rec.mData.mWeight;}); record["quality"] = sol::readonly_property([](const ESM::Lockpick& rec) -> float { return rec.mData.mQuality;}); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/misc.cpp000066400000000000000000000044441445372753700226420ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addMiscellaneousBindings(sol::table miscellaneous, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); miscellaneous["record"] = sol::overload( [](const Object& obj) -> const ESM::Miscellaneous* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Miscellaneous* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Miscellaneous"); record[sol::meta_function::to_string] = [](const ESM::Miscellaneous& rec) { return "ESM3_Miscellaneous[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript; }); record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["isKey"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> bool { return rec.mData.mIsKey; }); record["value"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> float { return rec.mData.mWeight; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/npc.cpp000066400000000000000000000031451445372753700224640ustar00rootroot00000000000000#include "types.hpp" #include #include #include "../stats.hpp" #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addNpcBindings(sol::table npc, const Context& context) { addNpcStatsBindings(npc, context); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); npc["record"] = sol::overload( [](const Object& obj) -> const ESM::NPC* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::NPC* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_NPC"); record[sol::meta_function::to_string] = [](const ESM::NPC& rec) { return "ESM3_NPC[" + rec.mId + "]"; }; record["name"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mName; }); record["race"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace; }); record["class"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass; }); record["mwscript"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript; }); record["hair"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair; }); record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/potion.cpp000066400000000000000000000040271445372753700232140ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addPotionBindings(sol::table potion, const Context& context) { const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); potion["record"] = sol::overload( [](const Object& obj) -> const ESM::Potion* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Potion* { return store->find(recordId); }); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Potion"); record[sol::meta_function::to_string] = [](const ESM::Potion& rec) { return "ESM3_Potion[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mScript; }); record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/probe.cpp000066400000000000000000000043461445372753700230170ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addProbeBindings(sol::table probe, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); probe["record"] = sol::overload( [](const Object& obj) -> const ESM::Probe* { return obj.ptr().get()->mBase;}, [store](const std::string& recordId) -> const ESM::Probe* { return store->find(recordId);}); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Probe"); record[sol::meta_function::to_string] = [](const ESM::Probe& rec) { return "ESM3_Probe[" + rec.mId + "]";}; record["id"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId;}); record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName;}); record["model"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript;}); record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["maxCondition"] = sol::readonly_property([](const ESM::Probe& rec) -> int { return rec.mData.mUses;}); record["value"] = sol::readonly_property([](const ESM::Probe& rec) -> int { return rec.mData.mValue;}); record["weight"] = sol::readonly_property([](const ESM::Probe& rec) -> float { return rec.mData.mWeight;}); record["quality"] = sol::readonly_property([](const ESM::Probe& rec) -> float { return rec.mData.mQuality;}); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/repair.cpp000066400000000000000000000044071445372753700231700ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } namespace MWLua { void addRepairBindings(sol::table repair, const Context& context) { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); repair["record"] = sol::overload( [](const Object& obj) -> const ESM::Repair* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Repair* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Repair"); record[sol::meta_function::to_string] = [](const ESM::Repair& rec) { return "ESM3_Repair[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript; }); record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["maxCondition"] = sol::readonly_property([](const ESM::Repair& rec) -> int { return rec.mData.mUses; }); record["value"] = sol::readonly_property([](const ESM::Repair& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Repair& rec) -> float { return rec.mData.mWeight; }); record["quality"] = sol::readonly_property([](const ESM::Repair& rec) -> float { return rec.mData.mQuality; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/types.cpp000066400000000000000000000210171445372753700230460ustar00rootroot00000000000000#include "types.hpp" #include #include namespace MWLua { namespace ObjectTypeName { // Names of object types in Lua. // These names are part of OpenMW Lua API. constexpr std::string_view Actor = "Actor"; // base type for NPC, Creature, Player constexpr std::string_view Item = "Item"; // base type for all items constexpr std::string_view Activator = "Activator"; constexpr std::string_view Armor = "Armor"; constexpr std::string_view Book = "Book"; constexpr std::string_view Clothing = "Clothing"; constexpr std::string_view Container = "Container"; constexpr std::string_view Creature = "Creature"; constexpr std::string_view Door = "Door"; constexpr std::string_view Ingredient = "Ingredient"; constexpr std::string_view Light = "Light"; constexpr std::string_view MiscItem = "Miscellaneous"; constexpr std::string_view NPC = "NPC"; constexpr std::string_view Player = "Player"; constexpr std::string_view Potion = "Potion"; constexpr std::string_view Static = "Static"; constexpr std::string_view Weapon = "Weapon"; constexpr std::string_view Apparatus = "Apparatus"; constexpr std::string_view Lockpick = "Lockpick"; constexpr std::string_view Probe = "Probe"; constexpr std::string_view Repair = "Repair"; constexpr std::string_view Marker = "Marker"; } namespace { const static std::unordered_map luaObjectTypeInfo = { {ESM::REC_INTERNAL_PLAYER, ObjectTypeName::Player}, {ESM::REC_INTERNAL_MARKER, ObjectTypeName::Marker}, {ESM::REC_ACTI, ObjectTypeName::Activator}, {ESM::REC_ARMO, ObjectTypeName::Armor}, {ESM::REC_BOOK, ObjectTypeName::Book}, {ESM::REC_CLOT, ObjectTypeName::Clothing}, {ESM::REC_CONT, ObjectTypeName::Container}, {ESM::REC_CREA, ObjectTypeName::Creature}, {ESM::REC_DOOR, ObjectTypeName::Door}, {ESM::REC_INGR, ObjectTypeName::Ingredient}, {ESM::REC_LIGH, ObjectTypeName::Light}, {ESM::REC_MISC, ObjectTypeName::MiscItem}, {ESM::REC_NPC_, ObjectTypeName::NPC}, {ESM::REC_ALCH, ObjectTypeName::Potion}, {ESM::REC_STAT, ObjectTypeName::Static}, {ESM::REC_WEAP, ObjectTypeName::Weapon}, {ESM::REC_APPA, ObjectTypeName::Apparatus}, {ESM::REC_LOCK, ObjectTypeName::Lockpick}, {ESM::REC_PROB, ObjectTypeName::Probe}, {ESM::REC_REPA, ObjectTypeName::Repair}, }; } unsigned int getLiveCellRefType(const MWWorld::LiveCellRefBase* ref) { if (ref == nullptr) throw std::runtime_error("Can't get type name from an empty object."); const std::string_view id = ref->mRef.getRefId(); if (id == "player") return ESM::REC_INTERNAL_PLAYER; if (Misc::ResourceHelpers::isHiddenMarker(id)) return ESM::REC_INTERNAL_MARKER; return ref->getType(); } std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) { auto it = luaObjectTypeInfo.find(type); if (it != luaObjectTypeInfo.end()) return it->second; else return fallback; } std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr) { return getLuaObjectTypeName(static_cast(getLiveCellRefType(ptr.mRef)), /*fallback=*/ptr.getTypeDescription()); } const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) { if (ptr.getType() != recordType) { std::string msg = "Requires type '"; msg.append(getLuaObjectTypeName(recordType)); msg.append("', but applied to "); msg.append(ptrToString(ptr)); throw std::runtime_error(msg); } return ptr; } sol::table getTypeToPackageTable(lua_State* L) { constexpr std::string_view key = "typeToPackage"; sol::state_view lua(L); if (lua[key] == sol::nil) lua[key] = sol::table(lua, sol::create); return lua[key]; } sol::table getPackageToTypeTable(lua_State* L) { constexpr std::string_view key = "packageToType"; sol::state_view lua(L); if (lua[key] == sol::nil) lua[key] = sol::table(lua, sol::create); return lua[key]; } sol::table initTypesPackage(const Context& context) { auto* lua = context.mLua; sol::table types(lua->sol(), sol::create); auto addType = [&](std::string_view name, std::vector recTypes, std::optional base = std::nullopt) -> sol::table { sol::table t(lua->sol(), sol::create); sol::table ro = LuaUtil::makeReadOnly(t); sol::table meta = ro[sol::metatable_key]; meta[sol::meta_function::to_string] = [name]() { return name; }; if (base) { t["baseType"] = types[*base]; sol::table baseMeta(lua->sol(), sol::create); baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(types[*base]); t[sol::metatable_key] = baseMeta; } t["objectIsInstance"] = [types=recTypes](const Object& o) { unsigned int type = getLiveCellRefType(o.ptr().mRef); for (ESM::RecNameInts t : types) if (t == type) return true; return false; }; types[name] = ro; return t; }; addActorBindings(addType(ObjectTypeName::Actor, {ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_}), context); addType(ObjectTypeName::Item, {ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA}); addCreatureBindings(addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor), context); addNpcBindings(addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor), context); addType(ObjectTypeName::Player, {ESM::REC_INTERNAL_PLAYER}, ObjectTypeName::NPC); addType(ObjectTypeName::Armor, {ESM::REC_ARMO}, ObjectTypeName::Item); addType(ObjectTypeName::Clothing, {ESM::REC_CLOT}, ObjectTypeName::Item); addIngredientBindings(addType(ObjectTypeName::Ingredient, { ESM::REC_INGR }, ObjectTypeName::Item), context); addType(ObjectTypeName::Light, {ESM::REC_LIGH}, ObjectTypeName::Item); addMiscellaneousBindings(addType(ObjectTypeName::MiscItem, {ESM::REC_MISC}, ObjectTypeName::Item), context); addPotionBindings(addType(ObjectTypeName::Potion, {ESM::REC_ALCH}, ObjectTypeName::Item), context); addWeaponBindings(addType(ObjectTypeName::Weapon, {ESM::REC_WEAP}, ObjectTypeName::Item), context); addBookBindings(addType(ObjectTypeName::Book, {ESM::REC_BOOK}, ObjectTypeName::Item), context); addLockpickBindings(addType(ObjectTypeName::Lockpick, {ESM::REC_LOCK}, ObjectTypeName::Item), context); addProbeBindings(addType(ObjectTypeName::Probe, {ESM::REC_PROB}, ObjectTypeName::Item), context); addApparatusBindings(addType(ObjectTypeName::Apparatus, {ESM::REC_APPA}, ObjectTypeName::Item), context); addRepairBindings(addType(ObjectTypeName::Repair, {ESM::REC_REPA}, ObjectTypeName::Item), context); addActivatorBindings(addType(ObjectTypeName::Activator, {ESM::REC_ACTI}), context); addContainerBindings(addType(ObjectTypeName::Container, {ESM::REC_CONT}), context); addDoorBindings(addType(ObjectTypeName::Door, {ESM::REC_DOOR}), context); addType(ObjectTypeName::Static, {ESM::REC_STAT}); sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol()); sol::table packageToType = getPackageToTypeTable(context.mLua->sol()); for (const auto& [type, name] : luaObjectTypeInfo) { sol::object t = types[name]; if (t == sol::nil) continue; typeToPackage[type] = t; packageToType[t] = type; } return LuaUtil::makeReadOnly(types); } } openmw-openmw-0.48.0/apps/openmw/mwlua/types/types.hpp000066400000000000000000000044301445372753700230530ustar00rootroot00000000000000#ifndef MWLUA_TYPES_H #define MWLUA_TYPES_H #include #include #include #include "../context.hpp" namespace MWLua { // `getLiveCellRefType()` is not exactly what we usually mean by "type" because some refids have special meaning. // This function handles these special refids (and by this adds some performance overhead). // We use this "fixed" type in Lua because we don't want to expose the weirdness of Morrowind internals to our API. // TODO: Implement https://gitlab.com/OpenMW/openmw/-/issues/6617 and make `MWWorld::PtrBase::getType` work the // same as `getLiveCellRefType`. unsigned int getLiveCellRefType(const MWWorld::LiveCellRefBase* ref); std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback = "Unknown"); std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); const MWWorld::Ptr& verifyType(ESM::RecNameInts type, const MWWorld::Ptr& ptr); sol::table getTypeToPackageTable(lua_State* L); sol::table getPackageToTypeTable(lua_State* L); sol::table initTypesPackage(const Context& context); // used in initTypesPackage void addActivatorBindings(sol::table activator, const Context& context); void addBookBindings(sol::table book, const Context& context); void addContainerBindings(sol::table container, const Context& context); void addDoorBindings(sol::table door, const Context& context); void addActorBindings(sol::table actor, const Context& context); void addWeaponBindings(sol::table weapon, const Context& context); void addNpcBindings(sol::table npc, const Context& context); void addCreatureBindings(sol::table creature, const Context& context); void addLockpickBindings(sol::table lockpick, const Context& context); void addProbeBindings(sol::table probe, const Context& context); void addApparatusBindings(sol::table apparatus, const Context& context); void addRepairBindings(sol::table repair, const Context& context); void addMiscellaneousBindings(sol::table miscellaneous, const Context& context); void addPotionBindings(sol::table potion, const Context& context); void addIngredientBindings(sol::table Ingredient, const Context& context); } #endif // MWLUA_TYPES_H openmw-openmw-0.48.0/apps/openmw/mwlua/types/weapon.cpp000066400000000000000000000113421445372753700231730ustar00rootroot00000000000000#include "types.hpp" #include #include #include #include #include "../luabindings.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; } #include namespace MWLua { void addWeaponBindings(sol::table weapon, const Context& context) { weapon["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"ShortBladeOneHand", ESM::Weapon::ShortBladeOneHand}, {"LongBladeOneHand", ESM::Weapon::LongBladeOneHand}, {"LongBladeTwoHand", ESM::Weapon::LongBladeTwoHand}, {"BluntOneHand", ESM::Weapon::BluntOneHand}, {"BluntTwoClose", ESM::Weapon::BluntTwoClose}, {"BluntTwoWide", ESM::Weapon::BluntTwoWide}, {"SpearTwoWide", ESM::Weapon::SpearTwoWide}, {"AxeOneHand", ESM::Weapon::AxeOneHand}, {"AxeTwoHand", ESM::Weapon::AxeTwoHand}, {"MarksmanBow", ESM::Weapon::MarksmanBow}, {"MarksmanCrossbow", ESM::Weapon::MarksmanCrossbow}, {"MarksmanThrown", ESM::Weapon::MarksmanThrown}, {"Arrow", ESM::Weapon::Arrow}, {"Bolt", ESM::Weapon::Bolt}, })); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); weapon["record"] = sol::overload( [](const Object& obj) -> const ESM::Weapon* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Weapon* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Weapon"); record[sol::meta_function::to_string] = [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mEnchant; }); record["mwscript"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript; }); record["isMagical"] = sol::readonly_property( [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); record["isSilver"] = sol::readonly_property( [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Silver; }); record["weight"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mValue; }); record["type"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mType; }); record["health"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mHealth; }); record["speed"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mSpeed; }); record["reach"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mReach; }); record["enchantCapacity"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mEnchant * 0.1f; }); record["chopMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[0]; }); record["chopMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[1]; }); record["slashMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[0]; }); record["slashMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[1]; }); record["thrustMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[0]; }); record["thrustMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[1]; }); } } openmw-openmw-0.48.0/apps/openmw/mwlua/uibindings.cpp000066400000000000000000000256251445372753700227020ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "context.hpp" #include "luamanagerimp.hpp" #include "../mwbase/windowmanager.hpp" namespace MWLua { namespace { class UiAction final : public LuaManager::Action { public: enum Type { CREATE = 0, UPDATE, DESTROY, }; UiAction(Type type, std::shared_ptr element, LuaUtil::LuaState* state) : Action(state) , mType{ type } , mElement{ std::move(element) } {} void apply(WorldView&) const override { try { switch (mType) { case CREATE: mElement->create(); break; case UPDATE: mElement->update(); break; case DESTROY: mElement->destroy(); break; } } catch (std::exception&) { // prevent any actions on a potentially corrupted widget mElement->mRoot = nullptr; throw; } } std::string toString() const override { std::string result; switch (mType) { case CREATE: result += "Create"; break; case UPDATE: result += "Update"; break; case DESTROY: result += "Destroy"; break; } result += " UI"; return result; } private: Type mType; std::shared_ptr mElement; }; // Lua arrays index from 1 inline size_t fromLuaIndex(size_t i) { return i - 1; } inline size_t toLuaIndex(size_t i) { return i + 1; } } sol::table initUserInterfacePackage(const Context& context) { auto element = context.mLua->sol().new_usertype("Element"); element["layout"] = sol::property( [](LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; } ); element["update"] = [context](const std::shared_ptr& element) { if (element->mDestroy || element->mUpdate) return; element->mUpdate = true; context.mLuaManager->addAction(std::make_unique(UiAction::UPDATE, element, context.mLua)); }; element["destroy"] = [context](const std::shared_ptr& element) { if (element->mDestroy) return; element->mDestroy = true; context.mLuaManager->addAction(std::make_unique(UiAction::DESTROY, element, context.mLua)); }; sol::table api = context.mLua->newTable(); api["showMessage"] = [luaManager=context.mLuaManager](std::string_view message) { luaManager->addUIMessage(message); }; api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1))}, {"Error", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Error.substr(1))}, {"Success", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Success.substr(1))}, {"Info", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Info.substr(1))}, })); api["printToConsole"] = [luaManager=context.mLuaManager](const std::string& message, const Misc::Color& color) { luaManager->addInGameConsoleMessage(message + "\n", color); }; api["setConsoleMode"] = [luaManager=context.mLuaManager](std::string_view mode) { luaManager->addAction( [mode = std::string(mode)]{ MWBase::Environment::get().getWindowManager()->setConsoleMode(mode); }); }; api["setConsoleSelectedObject"] = [luaManager=context.mLuaManager](const sol::object& obj) { const auto wm = MWBase::Environment::get().getWindowManager(); if (obj == sol::nil) luaManager->addAction([wm]{ wm->setConsoleSelectedObject(MWWorld::Ptr()); }); else { if (!obj.is()) throw std::runtime_error("Game object expected"); luaManager->addAction([wm, obj=obj.as()]{ wm->setConsoleSelectedObject(obj.ptr()); }); } }; api["content"] = LuaUi::loadContentConstructor(context.mLua); api["create"] = [context](const sol::table& layout) { auto element = LuaUi::Element::make(layout); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); return element; }; api["updateAll"] = [context]() { LuaUi::Element::forEach([](LuaUi::Element* e) { e->mUpdate = true; }); context.mLuaManager->addAction([]() { LuaUi::Element::forEach([](LuaUi::Element* e) { e->update(); }); }, "Update all UI elements"); }; api["_getMenuTransparency"] = []() { return Settings::Manager::getFloat("menu transparency", "GUI"); }; auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; sol::table layers = context.mLua->newTable(); layers[sol::meta_function::length] = []() { return LuaUi::Layer::count(); }; layers[sol::meta_function::index] = [](size_t index) { index = fromLuaIndex(index); return LuaUi::Layer(index); }; layers["indexOf"] = [](std::string_view name) -> sol::optional { size_t index = LuaUi::Layer::indexOf(name); if (index == LuaUi::Layer::count()) return sol::nullopt; else return toLuaIndex(index); }; layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) { LuaUi::Layer::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); size_t index = LuaUi::Layer::indexOf(afterName); if (index == LuaUi::Layer::count()) throw std::logic_error(std::string("Layer not found")); index++; context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; layers["insertBefore"] = [context](std::string_view beforename, std::string_view name, const sol::object& opt) { LuaUi::Layer::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); size_t index = LuaUi::Layer::indexOf(beforename); if (index == LuaUi::Layer::count()) throw std::logic_error(std::string("Layer not found")); context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; { auto pairs = [layers](const sol::object&) { auto next = [](const sol::table& l, size_t i) -> sol::optional> { if (i < LuaUi::Layer::count()) return std::make_tuple(i + 1, LuaUi::Layer(i)); else return sol::nullopt; }; return std::make_tuple(next, layers, 0); }; layers[sol::meta_function::pairs] = pairs; layers[sol::meta_function::ipairs] = pairs; } api["layers"] = LuaUtil::makeReadOnly(layers); sol::table typeTable = context.mLua->newTable(); for (const auto& it : LuaUi::widgetTypeToName()) typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeStrictReadOnly(typeTable); api["ALIGNMENT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "Start", LuaUi::Alignment::Start }, { "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } })); api["registerSettingsPage"] = &LuaUi::registerSettingsPage; api["texture"] = [luaManager=context.mLuaManager](const sol::table& options) { LuaUi::TextureData data; sol::object path = LuaUtil::getFieldOrNil(options, "path"); if (path.is()) data.mPath = path.as(); if (data.mPath.empty()) throw std::logic_error("Invalid texture path"); sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); if (offset.is()) data.mOffset = offset.as(); sol::object size = LuaUtil::getFieldOrNil(options, "size"); if (size.is()) data.mSize = size.as(); return luaManager->uiResourceManager()->registerTexture(data); }; api["screenSize"] = []() { return osg::Vec2f( Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video") ); }; return LuaUtil::makeReadOnly(api); } } openmw-openmw-0.48.0/apps/openmw/mwlua/userdataserializer.cpp000066400000000000000000000045151445372753700244440ustar00rootroot00000000000000#include "userdataserializer.hpp" #include #include #include "object.hpp" namespace MWLua { class Serializer final : public LuaUtil::UserdataSerializer { public: explicit Serializer(bool localSerializer, ObjectRegistry* registry, std::map* contentFileMapping) : mLocalSerializer(localSerializer), mObjectRegistry(registry), mContentFileMapping(contentFileMapping) {} private: // Appends serialized sol::userdata to the end of BinaryData. // Returns false if this type of userdata is not supported by this serializer. bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override { if (data.is() || data.is()) { appendRefNum(out, data.as().id()); return true; } return false; } // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. // Returns false if this type is not supported by this serializer. bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override { if (typeName == sRefNumTypeName) { ObjectId id = loadRefNum(binaryData); if (id.hasContentFile() && mContentFileMapping) { auto iter = mContentFileMapping->find(id.mContentFile); if (iter != mContentFileMapping->end()) id.mContentFile = iter->second; } if (mLocalSerializer) sol::stack::push(lua, LObject(id, mObjectRegistry)); else sol::stack::push(lua, GObject(id, mObjectRegistry)); return true; } return false; } bool mLocalSerializer; ObjectRegistry* mObjectRegistry; std::map* mContentFileMapping; }; std::unique_ptr createUserdataSerializer( bool local, ObjectRegistry* registry, std::map* contentFileMapping) { return std::make_unique(local, registry, contentFileMapping); } } openmw-openmw-0.48.0/apps/openmw/mwlua/userdataserializer.hpp000066400000000000000000000013371445372753700244500ustar00rootroot00000000000000#ifndef MWLUA_USERDATASERIALIZER_H #define MWLUA_USERDATASERIALIZER_H #include "object.hpp" namespace LuaUtil { class UserdataSerializer; } namespace MWLua { // UserdataSerializer is an extension for components/lua/serialization.hpp // Needed to serialize references to objects. // If local=true, then during deserialization creates LObject, otherwise creates GObject. // contentFileMapping is used only for deserialization. Needed to fix references if the order // of content files was changed. std::unique_ptr createUserdataSerializer( bool local, ObjectRegistry* registry, std::map* contentFileMapping = nullptr); } #endif // MWLUA_USERDATASERIALIZER_H openmw-openmw-0.48.0/apps/openmw/mwlua/worldview.cpp000066400000000000000000000110671445372753700225640ustar00rootroot00000000000000#include "worldview.hpp" #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwclass/container.hpp" #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" #include "../mwworld/cellutils.hpp" namespace MWLua { void WorldView::update() { mObjectRegistry.update(); mActivatorsInScene.updateList(); mActorsInScene.updateList(); mContainersInScene.updateList(); mDoorsInScene.updateList(); mItemsInScene.updateList(); mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode(); } void WorldView::clear() { mObjectRegistry.clear(); mActivatorsInScene.clear(); mActorsInScene.clear(); mContainersInScene.clear(); mDoorsInScene.clear(); mItemsInScene.clear(); } WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) { // It is important to check `isMarker` first. // For example "prisonmarker" has class "Door" despite that it is only an invisible marker. if (isMarker(ptr)) return nullptr; const MWWorld::Class& cls = ptr.getClass(); if (cls.isActivator()) return &mActivatorsInScene; if (cls.isActor()) return &mActorsInScene; if (cls.isDoor()) return &mDoorsInScene; if (typeid(cls) == typeid(MWClass::Container)) return &mContainersInScene; if (cls.hasToolTip(ptr)) return &mItemsInScene; return nullptr; } void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectRegistry.registerPtr(ptr); ObjectGroup* group = chooseGroup(ptr); if (group) addToGroup(*group, ptr); } void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) { ObjectGroup* group = chooseGroup(ptr); if (group) removeFromGroup(*group, ptr); } double WorldView::getGameTime() const { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::TimeStamp timeStamp = world->getTimeStamp(); return (static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour()) * 3600.0; } void WorldView::load(ESM::ESMReader& esm) { esm.getHNT(mSimulationTime, "LUAW"); ObjectId lastAssignedId; lastAssignedId.load(esm, true); mObjectRegistry.setLastAssignedId(lastAssignedId); } void WorldView::save(ESM::ESMWriter& esm) const { esm.writeHNT("LUAW", mSimulationTime); mObjectRegistry.getLastAssignedId().save(esm, true); } void WorldView::ObjectGroup::updateList() { if (mChanged) { mList->clear(); for (const ObjectId& id : mSet) mList->push_back(id); mChanged = false; } } void WorldView::ObjectGroup::clear() { mChanged = false; mList->clear(); mSet.clear(); } void WorldView::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) { group.mSet.insert(getId(ptr)); group.mChanged = true; } void WorldView::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) { group.mSet.erase(getId(ptr)); group.mChanged = true; } // TODO: If Lua scripts will use several threads at the same time, then `find*Cell` functions should have critical sections. MWWorld::CellStore* WorldView::findCell(const std::string& name, osg::Vec3f position) { MWBase::World* world = MWBase::Environment::get().getWorld(); bool exterior = name.empty() || world->getExterior(name); if (exterior) { const osg::Vec2i cellIndex = MWWorld::positionToCellIndex(position.x(), position.y()); return world->getExterior(cellIndex.x(), cellIndex.y()); } else return world->getInterior(name); } MWWorld::CellStore* WorldView::findNamedCell(const std::string& name) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Cell* esmCell = world->getExterior(name); if (esmCell) return world->getExterior(esmCell->getGridX(), esmCell->getGridY()); else return world->getInterior(name); } MWWorld::CellStore* WorldView::findExteriorCell(int x, int y) { MWBase::World* world = MWBase::Environment::get().getWorld(); return world->getExterior(x, y); } } openmw-openmw-0.48.0/apps/openmw/mwlua/worldview.hpp000066400000000000000000000065761445372753700226020ustar00rootroot00000000000000#ifndef MWLUA_WORLDVIEW_H #define MWLUA_WORLDVIEW_H #include "object.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include namespace ESM { class ESMWriter; class ESMReader; } namespace MWLua { // Tracks all used game objects. class WorldView { public: void update(); // Should be called every frame. void clear(); // Should be called every time before starting or loading a new game. // Whether the world is paused (i.e. game time is not changing and actors don't move). bool isPaused() const { return mPaused; } // The number of seconds passed from the beginning of the game. double getSimulationTime() const { return mSimulationTime; } void setSimulationTime(double t) { mSimulationTime = t; } // The game time (in game seconds) passed from the beginning of the game. // Note that game time generally goes faster than the simulation time. double getGameTime() const; double getGameTimeScale() const { return MWBase::Environment::get().getWorld()->getTimeScaleFactor(); } void setGameTimeScale(double s) { MWBase::Environment::get().getWorld()->setGlobalFloat("timescale", s); } ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } ObjectIdList getDoorsInScene() const { return mDoorsInScene.mList; } ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } ObjectRegistry* getObjectRegistry() { return &mObjectRegistry; } void objectUnloaded(const MWWorld::Ptr& ptr) { mObjectRegistry.deregisterPtr(ptr); } void objectAddedToScene(const MWWorld::Ptr& ptr); void objectRemovedFromScene(const MWWorld::Ptr& ptr); // Returns list of objects that meets the `query` criteria. // If onlyActive = true, then search only among the objects that are currently in the scene. // TODO: ObjectIdList selectObjects(const Queries::Query& query, bool onlyActive); MWWorld::CellStore* findCell(const std::string& name, osg::Vec3f position); MWWorld::CellStore* findNamedCell(const std::string& name); MWWorld::CellStore* findExteriorCell(int x, int y); void load(ESM::ESMReader& esm); void save(ESM::ESMWriter& esm) const; // TODO: move this functionality to MWClass bool isItem(const MWWorld::Ptr& ptr) { return chooseGroup(ptr) == &mItemsInScene; } private: struct ObjectGroup { void updateList(); void clear(); bool mChanged = false; ObjectIdList mList = std::make_shared>(); std::set mSet; }; ObjectGroup* chooseGroup(const MWWorld::Ptr& ptr); void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); ObjectRegistry mObjectRegistry; ObjectGroup mActivatorsInScene; ObjectGroup mActorsInScene; ObjectGroup mContainersInScene; ObjectGroup mDoorsInScene; ObjectGroup mItemsInScene; double mSimulationTime = 0; bool mPaused = false; }; } #endif // MWLUA_WORLDVIEW_H openmw-openmw-0.48.0/apps/openmw/mwmechanics/000077500000000000000000000000001445372753700212025ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwmechanics/activespells.cpp000066400000000000000000000514721445372753700244150ustar00rootroot00000000000000#include "activespells.hpp" #include #include #include #include #include #include #include #include "creaturestats.hpp" #include "spellcasting.hpp" #include "spelleffects.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" namespace { bool merge(std::vector& present, const std::vector& queued) { // Can't merge if we already have an effect with the same effect index auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect) { return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end(); }); if(problem != queued.end()) return false; present.insert(present.end(), queued.begin(), queued.end()); return true; } void addEffects(std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { int currentEffectIndex = 0; for(const auto& enam : list.mList) { ESM::ActiveEffect effect; effect.mEffectId = enam.mEffectID; effect.mArg = MWMechanics::EffectKey(enam).mArg; effect.mMagnitude = 0.f; effect.mMinMagnitude = enam.mMagnMin; effect.mMaxMagnitude = enam.mMagnMax; effect.mEffectIndex = currentEffectIndex++; effect.mFlags = ESM::ActiveEffect::Flag_None; if(ignoreResistances) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; effect.mDuration = -1; effect.mTimeLeft = -1; effects.emplace_back(effect); } } } namespace MWMechanics { ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells) { mActiveSpells.mIterating = true; } ActiveSpells::IterationGuard::~IterationGuard() { mActiveSpells.mIterating = false; } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) : mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1) { if(!caster.isEmpty() && caster.getClass().isActor()) mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId(); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) : mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0) , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1) { assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); addEffects(mEffects, spell->mEffects, ignoreResistances); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor) : mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1) { assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); addEffects(mEffects, enchantment->mEffects); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) : mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId) , mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0) , mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening}) {} ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) : mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mSlot(params.mSlot), mType(params.mType), mWorsenings(-1) {} ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const { ESM::ActiveSpells::ActiveSpellParams params; params.mId = mId; params.mEffects = mEffects; params.mDisplayName = mDisplayName; params.mCasterActorId = mCasterActorId; params.mItem.unset(); if(mSlot) { // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing // mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148) params.mItem = { static_cast(mSlot), 0 }; } params.mType = mType; params.mWorsenings = mWorsenings; params.mNextWorsening = mNextWorsening.toEsm(); return params; } void ActiveSpells::ActiveSpellParams::worsen() { ++mWorsenings; if(!mWorsenings) mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp(); mNextWorsening += CorprusStats::sWorseningPeriod; } bool ActiveSpells::ActiveSpellParams::shouldWorsen() const { return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening; } void ActiveSpells::ActiveSpellParams::resetWorsenings() { mWorsenings = -1; } void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) return; auto& creatureStats = ptr.getClass().getCreatureStats(ptr); assert(&creatureStats.getActiveSpells() == this); IterationGuard guard{*this}; // Erase no longer active spells and effects for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable) { ++spellIt; continue; } bool removedSpell = false; for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) { if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f) { auto effect = *effectIt; effectIt = spellIt->mEffects.erase(effectIt); onMagicEffectRemoved(ptr, *spellIt, effect); removedSpell = applyPurges(ptr, &spellIt, &effectIt); if(removedSpell) break; } else { ++effectIt; } } if(removedSpell) continue; if(spellIt->mEffects.empty()) spellIt = mSpells.erase(spellIt); else ++spellIt; } for(const auto& spell : mQueue) addToSpells(ptr, spell); mQueue.clear(); // Vanilla only does this on cell change I think const auto& spells = creatureStats.getSpells(); for(const ESM::Spell* spell : spells) { if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) mSpells.emplace_back(ActiveSpellParams{spell, ptr}); } if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { auto& store = ptr.getClass().getInventoryStore(ptr); if(store.getInvListener() != nullptr) { bool playNonLooping = !store.isFirstEquip(); const auto world = MWBase::Environment::get().getWorld(); for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) { auto slot = store.getSlot(slotIndex); if(slot == store.end()) continue; const auto& enchantmentId = slot->getClass().getEnchantment(*slot); if(enchantmentId.empty()) continue; const ESM::Enchantment* enchantment = world->getStore().get().find(enchantmentId); if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect) continue; if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params) { return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId(); }) != mSpells.end()) continue; // world->breakInvisibility leads to a stack overflow as it calls this method so just break invisibility manually purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); const ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{*slot, enchantment, slotIndex, ptr}); for(const auto& effect : params.mEffects) MWMechanics::playEffects(ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); } } } // Update effects for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid? bool removedSpell = false; std::optional reflected; for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration); if(result == MagicApplicationResult::REFLECTED) { if(!reflected) { static const bool keepOriginalCaster = Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"); if(keepOriginalCaster) reflected = {*spellIt, caster}; else reflected = {*spellIt, ptr}; } auto& reflectedEffect = reflected->mEffects.emplace_back(*it); reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; it = spellIt->mEffects.erase(it); } else if(result == MagicApplicationResult::REMOVED) it = spellIt->mEffects.erase(it); else ++it; removedSpell = applyPurges(ptr, &spellIt, &it); if(removedSpell) break; } if(reflected) { const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find("VFX_Reflect"); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if(animation && !reflectStatic->mModel.empty()) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); animation->addEffect( Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel, vfs), ESM::MagicEffect::Reflect, false, std::string()); } caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); } if(removedSpell) continue; bool remove = false; if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent) { try { remove = !spells.hasSpell(spellIt->mId); } catch(const std::runtime_error& e) { remove = true; Log(Debug::Error) << "Removing active effect: " << e.what(); } } else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment) { const auto& store = ptr.getClass().getInventoryStore(ptr); auto slot = store.getSlot(spellIt->mSlot); remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId; } if(remove) { auto params = *spellIt; spellIt = mSpells.erase(spellIt); for(const auto& effect : params.mEffects) onMagicEffectRemoved(ptr, params, effect); applyPurges(ptr, &spellIt); continue; } ++spellIt; } static const bool keepCalm = Settings::Manager::getBool("classic calm spells behavior", "Game"); if (keepCalm) { ESM::MagicEffect::Effects effect = ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature; if (creatureStats.getMagicEffects().get(effect).getMagnitude() > 0.f) creatureStats.getAiSequence().stopCombat(); } } void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { if(spell.mType != ESM::ActiveSpells::Type_Consumable) { auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing) { return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot; }); if(found != mSpells.end()) { if(merge(found->mEffects, spell.mEffects)) return; auto params = *found; mSpells.erase(found); for(const auto& effect : params.mEffects) onMagicEffectRemoved(ptr, params, effect); } } mSpells.emplace_back(spell); } ActiveSpells::ActiveSpells() : mIterating(false) {} ActiveSpells::TIterator ActiveSpells::begin() const { return mSpells.begin(); } ActiveSpells::TIterator ActiveSpells::end() const { return mSpells.end(); } bool ActiveSpells::isSpellActive(std::string_view id) const { return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell) { return Misc::StringUtils::ciEqual(spell.mId, id); }) != mSpells.end(); } void ActiveSpells::addSpell(const ActiveSpellParams& params) { mQueue.emplace_back(params); } void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor) { mQueue.emplace_back(ActiveSpellParams{spell, actor, true}); } void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr) { assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); mPurges.emplace(predicate); if(!mIterating) { IterationGuard guard{*this}; applyPurges(ptr); } } void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr) { assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); mPurges.emplace(predicate); if(!mIterating) { IterationGuard guard{*this}; applyPurges(ptr); } } bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell, std::vector::iterator* currentEffect) { bool removedCurrentSpell = false; while(!mPurges.empty()) { auto predicate = mPurges.front(); mPurges.pop(); for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { bool isCurrentSpell = currentSpell && *currentSpell == spellIt; std::visit([&] (auto&& variant) { using T = std::decay_t; if constexpr (std::is_same_v) { if(variant(*spellIt)) { auto params = *spellIt; spellIt = mSpells.erase(spellIt); if(isCurrentSpell) { *currentSpell = spellIt; removedCurrentSpell = true; } for(const auto& effect : params.mEffects) onMagicEffectRemoved(ptr, params, effect); } else ++spellIt; } else { static_assert(std::is_same_v, "Non-exhaustive visitor"); for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) { if(variant(*spellIt, *effectIt)) { auto effect = *effectIt; if(isCurrentSpell && currentEffect) { auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect); if(effectIt <= *currentEffect) distance--; effectIt = spellIt->mEffects.erase(effectIt); *currentEffect = spellIt->mEffects.begin() + distance; } else effectIt = spellIt->mEffects.erase(effectIt); onMagicEffectRemoved(ptr, *spellIt, effect); } else ++effectIt; } ++spellIt; } }, predicate); } } return removedCurrentSpell; } void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, std::string_view id) { purge([=] (const ActiveSpellParams& params) { return params.mId == id; }, ptr); } void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId) { purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect) { return effect.mEffectId == effectId; }, ptr); } void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId) { purge([=] (const ActiveSpellParams& params) { return params.mCasterActorId == casterActorId; }, ptr); } void ActiveSpells::clear(const MWWorld::Ptr& ptr) { mQueue.clear(); purge([] (const ActiveSpellParams& params) { return true; }, ptr); } void ActiveSpells::skipWorsenings(double hours) { for(auto& spell : mSpells) { if(spell.mWorsenings >= 0) spell.mNextWorsening += hours; } } void ActiveSpells::writeState(ESM::ActiveSpells &state) const { for(const auto& spell : mSpells) state.mSpells.emplace_back(spell.toEsm()); for(const auto& spell : mQueue) state.mQueue.emplace_back(spell.toEsm()); } void ActiveSpells::readState(const ESM::ActiveSpells &state) { for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) mSpells.emplace_back(ActiveSpellParams{spell}); for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) mQueue.emplace_back(ActiveSpellParams{spell}); } void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) { purge([] (const auto& spell) { return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary; }, ptr); mQueue.clear(); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/activespells.hpp000066400000000000000000000120251445372753700244110ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H #define GAME_MWMECHANICS_ACTIVESPELLS_H #include #include #include #include #include #include #include #include "../mwworld/timestamp.hpp" #include "../mwworld/ptr.hpp" #include "magiceffects.hpp" #include "spellcasting.hpp" namespace ESM { struct Enchantment; struct Spell; } namespace MWMechanics { /// \brief Lasting spell effects /// /// \note The name of this class is slightly misleading, since it also handles lasting potion /// effects. class ActiveSpells { public: using ActiveEffect = ESM::ActiveEffect; class ActiveSpellParams { std::string mId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; int mSlot; ESM::ActiveSpells::EffectType mType; int mWorsenings; MWWorld::TimeStamp mNextWorsening; ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params); ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false); ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor); ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor); ESM::ActiveSpells::ActiveSpellParams toEsm() const; friend class ActiveSpells; public: ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); const std::string& getId() const { return mId; } const std::vector& getEffects() const { return mEffects; } std::vector& getEffects() { return mEffects; } ESM::ActiveSpells::EffectType getType() const { return mType; } int getCasterActorId() const { return mCasterActorId; } int getWorsenings() const { return mWorsenings; } const std::string& getDisplayName() const { return mDisplayName; } // Increments worsenings count and sets the next timestamp void worsen(); bool shouldWorsen() const; void resetWorsenings(); }; typedef std::list::const_iterator TIterator; void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; TIterator begin() const; TIterator end() const; void update(const MWWorld::Ptr& ptr, float duration); private: using ParamsPredicate = std::function; using EffectPredicate = std::function; using Predicate = std::variant; struct IterationGuard { ActiveSpells& mActiveSpells; IterationGuard(ActiveSpells& spells); ~IterationGuard(); }; std::list mSpells; std::vector mQueue; std::queue mPurges; bool mIterating; void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell); bool applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell = nullptr, std::vector::iterator* currentEffect = nullptr); public: ActiveSpells(); /// Add lasting effects /// /// \brief addSpell /// \param id ID for stacking purposes. /// void addSpell (const ActiveSpellParams& params); /// Bypasses resistances void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id void removeEffects (const MWWorld::Ptr& ptr, std::string_view id); /// Remove all active effects with this effect id void purgeEffect (const MWWorld::Ptr& ptr, short effectId); void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr); void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr); /// Remove all effects that were cast by \a casterActorId void purge (const MWWorld::Ptr& ptr, int casterActorId); /// Remove all spells void clear(const MWWorld::Ptr& ptr); bool isSpellActive (std::string_view id) const; ///< case insensitive void skipWorsenings(double hours); void unloadActor(const MWWorld::Ptr& ptr); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/actor.hpp000066400000000000000000000046541445372753700230340ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_ACTOR_H #define OPENMW_MECHANICS_ACTOR_H #include #include "character.hpp" #include "greetingstate.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include namespace MWRender { class Animation; } namespace MWWorld { class Ptr; } namespace MWMechanics { /// @brief Holds temporary state for an actor that will be discarded when the actor leaves the scene. class Actor { public: Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation) : mCharacterController(ptr, animation) , mPositionAdjusted(false) {} const MWWorld::Ptr& getPtr() const { return mCharacterController.getPtr(); } /// Notify this actor of its new base object Ptr, use when the object changed cells void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController.updatePtr(newPtr); } CharacterController& getCharacterController() { return mCharacterController; } const CharacterController& getCharacterController() const { return mCharacterController; } int getGreetingTimer() const { return mGreetingTimer; } void setGreetingTimer(int timer) { mGreetingTimer = timer; } float getAngleToPlayer() const { return mTargetAngleRadians; } void setAngleToPlayer(float angle) { mTargetAngleRadians = angle; } GreetingState getGreetingState() const { return mGreetingState; } void setGreetingState(GreetingState state) { mGreetingState = state; } bool isTurningToPlayer() const { return mIsTurningToPlayer; } void setTurningToPlayer(bool turning) { mIsTurningToPlayer = turning; } Misc::TimerStatus updateEngageCombatTimer(float duration) { return mEngageCombat.update(duration, MWBase::Environment::get().getWorld()->getPrng()); } void setPositionAdjusted(bool adjusted) { mPositionAdjusted = adjusted; } bool getPositionAdjusted() const { return mPositionAdjusted; } private: CharacterController mCharacterController; int mGreetingTimer{0}; float mTargetAngleRadians{0.f}; GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng())}; bool mPositionAdjusted; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/actors.cpp000066400000000000000000003032761445372753700232140ustar00rootroot00000000000000#include "actors.hpp" #include #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/player.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwmechanics/aibreathe.hpp" #include "../mwrender/vismask.hpp" #include "spellcasting.hpp" #include "steering.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "character.hpp" #include "aicombat.hpp" #include "aicombataction.hpp" #include "aifollow.hpp" #include "aipursue.hpp" #include "aiwander.hpp" #include "actor.hpp" #include "summoning.hpp" #include "actorutil.hpp" namespace { bool isConscious(const MWWorld::Ptr& ptr) { const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); return !stats.isDead() && !stats.getKnockedDown(); } bool isCommanded(const MWWorld::Ptr& actor) { const auto& actorClass = actor.getClass(); const auto& stats = actorClass.getCreatureStats(actor); const bool isActorNpc = actorClass.isNpc(); const auto level = stats.getLevel(); for (const auto& params : stats.getActiveSpells()) { for (const auto& effect : params.getEffects()) { if (((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && isActorNpc) || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !isActorNpc)) && effect.mMagnitude >= level) return true; } } return false; } // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { if (isCommanded(actor)) return; MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); stats.getAiSequence().erasePackageIf([](auto& entry) { if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(entry.get())->isCommanded()) { return true; } return false; }); } std::pair getRestorationPerHourOfSleep(const MWWorld::Ptr& ptr) { const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); const float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified(); const float health = 0.1f * endurance; static const float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat(); const float magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); return {health, magicka}; } template void forEachFollowingPackage(const std::list& actors, const MWWorld::Ptr& actorPtr, const MWWorld::Ptr& player, T&& func) { for (const MWMechanics::Actor& actor : actors) { const MWWorld::Ptr &iteratedActor = actor.getPtr(); if (iteratedActor == player || iteratedActor == actorPtr) continue; const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; // An actor counts as following if AiFollow is the current AiPackage, // or there are only Combat and Wander packages before the AiFollow package for (const auto& package : stats.getAiSequence()) { if (!func(actor, package)) break; } } } float getStuntedMagickaDuration(const MWWorld::Ptr& actor) { float remainingTime = 0.f; for(const auto& params : actor.getClass().getCreatureStats(actor).getActiveSpells()) { for(const auto& effect : params.getEffects()) { if(effect.mEffectId == ESM::MagicEffect::StuntedMagicka) { if(effect.mDuration == -1.f) return -1.f; remainingTime = std::max(remainingTime, effect.mTimeLeft); } } } return remainingTime; } void soulTrap(const MWWorld::Ptr& creature) { const auto& stats = creature.getClass().getCreatureStats(creature); if(!stats.getMagicEffects().get(ESM::MagicEffect::Soultrap).getMagnitude()) return; const int creatureSoulValue = creature.get()->mBase->mData.mSoul; if (creatureSoulValue == 0) return; MWBase::World* const world = MWBase::Environment::get().getWorld(); static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); for(const auto& params : stats.getActiveSpells()) { for(const auto& effect : params.getEffects()) { if(effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f) continue; MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId()); if (caster.isEmpty() || !caster.getClass().isActor()) continue; // Use the smallest soulgem that is large enough to hold the soul MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); MWWorld::ContainerStoreIterator gem = container.end(); float gemCapacity = std::numeric_limits::max(); std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/ for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != container.end(); ++it) { const std::string& id = it->getCellRef().getRefId(); if (id.size() >= soulgemFilter.size() && id.substr(0,soulgemFilter.size()) == soulgemFilter) { float thisGemCapacity = it->get()->mBase->mData.mValue * fSoulgemMult; if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity && it->getCellRef().getSoul().empty()) { gem = it; gemCapacity = thisGemCapacity; } } } if (gem == container.end()) continue; // Set the soul on just one of the gems, not the whole stack gem->getContainerStore()->unstack(*gem, caster); gem->getCellRef().setSoul(creature.getCellRef().getRefId()); // Restack the gem with other gems with the same soul gem->getContainerStore()->restack(*gem); if (caster == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); const ESM::Static* const fx = world->getStore().get().search("VFX_Soul_Trap"); if (fx != nullptr) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); world->spawnEffect( Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", creature.getRefData().getPosition().asVec3()); } MWBase::Environment::get().getSoundManager()->playSound3D(creature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f); return; //remove to get vanilla behaviour } } } void removeTemporaryEffects(const MWWorld::Ptr& ptr) { ptr.getClass().getCreatureStats(ptr).getActiveSpells().unloadActor(ptr); } } namespace MWMechanics { static constexpr int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player static constexpr int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player static constexpr int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement static constexpr float DECELERATE_DISTANCE = 512.f; namespace { float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) { const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; } void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, bool inCombatOrPursue) { const auto& actorRefData = actor.getRefData(); if (!actorRefData.getBaseNode()) return; if (targetActor.getClass().getCreatureStats(targetActor).isDead()) return; if (isTargetMagicallyHidden(targetActor)) return; static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore() .get().find("fMaxHeadTrackDistance")->mValue.getFloat(); static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore() .get().find("fInteriorHeadTrackMult")->mValue.getFloat(); float maxDistance = fMaxHeadTrackDistance; const ESM::Cell* currentCell = actor.getCell()->getCell(); if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) maxDistance *= fInteriorHeadTrackMult; const osg::Vec3f actor1Pos(actorRefData.getPosition().asVec3()); const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); const float sqrDist = (actor1Pos - actor2Pos).length2(); if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance) && !inCombatOrPursue) return; // stop tracking when target is behind the actor osg::Vec3f actorDirection = actorRefData.getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); osg::Vec3f targetDirection(actor2Pos - actor1Pos); actorDirection.z() = 0; targetDirection.z() = 0; if ((actorDirection * targetDirection > 0 || inCombatOrPursue) // check LOS and awareness last as it's the most expensive function && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) { sqrHeadTrackDistance = sqrDist; headTrackTarget = targetActor; } } void updateHeadTracking(const MWWorld::Ptr& ptr, const std::list& actors, bool isPlayer, CharacterController& ctrl) { float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); // 1. Unconsious actor can not track target // 2. Actors in combat and pursue mode do not bother to headtrack anyone except their target // 3. Player character does not use headtracking in the 1st-person view if (!stats.getKnockedDown() && !firstPersonPlayer) { bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().isInPursuit(); if (inCombatOrPursue) { auto activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); if (!activePackageTarget.isEmpty()) { // Track the specified target of package. updateHeadTracking(ptr, activePackageTarget, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); } } else { // Find something nearby. for (const Actor& otherActor : actors) { if (otherActor.getPtr() == ptr) continue; updateHeadTracking(ptr, otherActor.getPtr(), headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); } } } ctrl.setHeadTrackTarget(headTrackTarget); } void updateLuaControls(const MWWorld::Ptr& ptr, bool isPlayer, MWBase::LuaManager::ActorControls& controls) { Movement& mov = ptr.getClass().getMovementSettings(ptr); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor; const osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor; const float rotationX = mov.mRotation[0]; const float rotationZ = mov.mRotation[2]; const bool jump = mov.mPosition[2] == 1; const bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); const bool attackingOrSpell = stats.getAttackingOrSpell(); if (controls.mChanged) { mov.mPosition[0] = controls.mSideMovement; mov.mPosition[1] = controls.mMovement; mov.mPosition[2] = controls.mJump ? 1 : 0; mov.mRotation[0] = controls.mPitchChange; mov.mRotation[1] = 0; mov.mRotation[2] = controls.mYawChange; mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setAttackingOrSpell((controls.mUse & 1) == 1); controls.mChanged = false; } controls.mSideMovement = movement.x(); controls.mMovement = movement.y(); controls.mPitchChange = rotationX; controls.mYawChange = rotationZ; controls.mJump = jump; controls.mRun = runFlag; controls.mUse = attackingOrSpell ? controls.mUse | 1 : controls.mUse & ~1; } } void Actors::updateActor(const MWWorld::Ptr& ptr, float duration) const { // magic effects adjustMagicEffects (ptr, duration); // fatigue restoration calculateRestoration(ptr, duration); } void Actors::playIdleDialogue(const MWWorld::Ptr& actor) const { if (!actor.getClass().isActor() || actor == getPlayer() || MWBase::Environment::get().getSoundManager()->sayActive(actor)) return; const CreatureStats &stats = actor.getClass().getCreatureStats(actor); if (stats.getAiSetting(AiSetting::Hello).getModified() == 0) return; const MWMechanics::AiSequence& seq = stats.getAiSequence(); if (seq.isInCombat() || seq.hasPackage(AiPackageTypeId::Follow) || seq.hasPackage(AiPackageTypeId::Escort)) return; const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3()); const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); MWBase::World* const world = MWBase::Environment::get().getWorld(); if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000) return; // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); if (Misc::Rng::rollProbability(world->getPrng()) * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) const { if (mSmoothMovement) return; const auto& actorClass = actor.getClass(); const CreatureStats &stats = actorClass.getCreatureStats(actor); const MWMechanics::AiSequence& seq = stats.getAiSequence(); if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed()) { const osg::Vec3f targetPos = seq.getActivePackage().getDestination(); const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); const float distance = (targetPos - actorPos).length(); if (distance < DECELERATE_DISTANCE) { const float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE); auto& movement = actorClass.getMovementSettings(actor); movement.mPosition[0] *= speedCoef; movement.mPosition[1] *= speedCoef; } } } void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) { const auto& actorClass = actor.getClass(); if (!actorClass.isActor() || actor == getPlayer()) return; const CreatureStats& actorStats = actorClass.getCreatureStats(actor); const MWMechanics::AiSequence& seq = actorStats.getAiSequence(); const auto packageId = seq.getTypeId(); if (seq.isInCombat() || MWBase::Environment::get().getWorld()->isSwimming(actor) || (packageId != AiPackageTypeId::Wander && packageId != AiPackageTypeId::Travel && packageId != AiPackageTypeId::None)) { actorState.setTurningToPlayer(false); actorState.setGreetingTimer(0); actorState.setGreetingState(Greet_None); return; } const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f dir = playerPos - actorPos; if (actorState.isTurningToPlayer()) { // Reduce the turning animation glitch by using a *HUGE* value of // epsilon... TODO: a proper fix might be in either the physics or the // animation subsystem if (zTurn(actor, actorState.getAngleToPlayer(), osg::DegreesToRadians(5.f))) { actorState.setTurningToPlayer(false); // An original engine launches an endless idle2 when an actor greets player. playAnimationGroup(actor, "idle2", 0, std::numeric_limits::max(), false); } } if (turnOnly) return; // Play a random voice greeting if the player gets too close static const int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); const float helloDistance = static_cast(actorStats.getAiSetting(AiSetting::Hello).getModified() * iGreetDistanceMultiplier); const auto& playerStats = player.getClass().getCreatureStats(player); int greetingTimer = actorState.getGreetingTimer(); GreetingState greetingState = actorState.getGreetingState(); if (greetingState == Greet_None) { if ((playerPos - actorPos).length2() <= helloDistance * helloDistance && !playerStats.isDead() && !actorStats.isParalyzed() && !isTargetMagicallyHidden(player) && MWBase::Environment::get().getWorld()->getLOS(player, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) greetingTimer++; if (greetingTimer >= GREETING_SHOULD_START) { greetingState = Greet_InProgress; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); greetingTimer = 0; } } if (greetingState == Greet_InProgress) { greetingTimer++; if (!actorStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !actorStats.getMovementFlag(CreatureStats::Flag_ForceSneak) && (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))) turnActorToFacePlayer(actor, actorState, dir); if (greetingTimer >= GREETING_COOLDOWN) { greetingState = Greet_Done; greetingTimer = 0; } } if (greetingState == Greet_Done) { float resetDist = 2 * helloDistance; if ((playerPos - actorPos).length2() >= resetDist * resetDist) greetingState = Greet_None; } actorState.setGreetingTimer(greetingTimer); actorState.setGreetingState(greetingState); } void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) const { auto& movementSettings = actor.getClass().getMovementSettings(actor); movementSettings.mPosition[1] = 0; movementSettings.mPosition[0] = 0; if (!actorState.isTurningToPlayer()) { float from = dir.x(); float to = dir.y(); float angle = std::atan2(from, to); actorState.setAngleToPlayer(angle); float deltaAngle = Misc::normalizeAngle(angle - actor.getRefData().getPosition().rot[2]); if (!mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f)) actorState.setTurningToPlayer(true); } } void Actors::stopCombat(const MWWorld::Ptr& ptr) const { auto& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); std::vector targets; if(ai.getCombatTargets(targets)) { std::set allySet; getActorsSidingWith(ptr, allySet); allySet.insert(ptr); std::vector allies(allySet.begin(), allySet.end()); for(const auto& ally : allies) ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(targets); for(const auto& target : targets) target.getClass().getCreatureStats(target).getAiSequence().stopCombat(allies); } } void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map>& cachedAllies, bool againstPlayer) const { // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) return; CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1); if (creatureStats1.isDead() || creatureStats1.getAiSequence().isInCombat(actor2)) return; const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); if (creatureStats2.isDead()) return; const osg::Vec3f actor1Pos(actor1.getRefData().getPosition().asVec3()); const osg::Vec3f actor2Pos(actor2.getRefData().getPosition().asVec3()); const float sqrDist = (actor1Pos - actor2Pos).length2(); if (sqrDist > mActorsProcessingRange * mActorsProcessingRange) return; // If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true bool aggressive = false; // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting those actors, (recursive) // and any actor currently being followed or escorted by actor1 std::set allies1; getActorsSidingWith(actor1, allies1, cachedAllies); const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2 for (const MWWorld::Ptr& ally : allies1) { if (creatureStats1.getAiSequence().isInCombat(ally)) continue; if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { mechanicsManager->startCombat(actor1, actor2); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); return; } // If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2 if (ally.getClass().getCreatureStats(ally).getAiSequence().isInCombat(actor2)) aggressive = true; } std::set playerAllies; MWWorld::Ptr player = MWMechanics::getPlayer(); getActorsSidingWith(player, playerAllies, cachedAllies); bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); // If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them // Doesn't apply for player followers/escorters if (!aggressive && !isPlayerFollowerOrEscorter) { // Check that actor2 is in combat with actor1 if (creatureStats2.getAiSequence().isInCombat(actor1)) { std::set allies2; getActorsSidingWith(actor2, allies2, cachedAllies); // Check that an ally of actor2 is also in combat with actor1 for (const MWWorld::Ptr& ally2 : allies2) { if (ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { mechanicsManager->startCombat(actor1, actor2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) if (ally1 != player) mechanicsManager->startCombat(ally1, actor2); return; } } } } if (creatureStats2.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) return; // Stop here if target is unreachable if (!canFight(actor1, actor2)) return; // If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player static const bool followersAttackOnSight = Settings::Manager::getBool("followers attack on sight", "Game"); if (!aggressive && isPlayerFollowerOrEscorter && followersAttackOnSight) { if (creatureStats2.getAiSequence().isInCombat(actor1)) aggressive = true; else { for (const MWWorld::Ptr& ally : allies1) { if (creatureStats2.getAiSequence().isInCombat(ally)) { aggressive = true; break; } } } } // Do aggression check if actor2 is the player or a player follower or escorter if (!aggressive) { if (againstPlayer || playerAllies.find(actor2) != playerAllies.end()) { // Player followers and escorters with high fight should not initiate combat with the player or with // other player followers or escorters if (!isPlayerFollowerOrEscorter) aggressive = mechanicsManager->isAggressive(actor1, actor2); } } // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter const auto world = MWBase::Environment::get().getWorld(); if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far static const float fAlarmRadius = world->getStore().get().find("fAlarmRadius")->mValue.getFloat(); if (sqrDist > fAlarmRadius * fAlarmRadius) return; bool followerOrEscorter = false; for (const auto& package : creatureStats2.getAiSequence()) { // The follow package must be first or have nothing but combat before it if (package->sideWithTarget()) { followerOrEscorter = true; break; } else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) break; } if (!followerOrEscorter) aggressive = true; } // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2. if (aggressive) { bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) mechanicsManager->startCombat(actor1, actor2); } } void Actors::adjustMagicEffects(const MWWorld::Ptr& creature, float duration) const { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); const bool wasDead = creatureStats.isDead(); creatureStats.getActiveSpells().update(creature, duration); if (!wasDead && creatureStats.isDead()) { // The actor was killed by a magic effect. Figure out if the player was responsible for it. const ActiveSpells& spells = creatureStats.getActiveSpells(); const MWWorld::Ptr player = getPlayer(); std::set playerFollowers; getActorsSidingWith(player, playerFollowers); for (const ActiveSpells::ActiveSpellParams& spell : spells) { bool actorKilled = false; MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId()); if (caster.isEmpty()) continue; for (const auto& effect : spell.getEffects()) { static const std::array damageEffects{ ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth }; const bool isDamageEffect = std::find(damageEffects.begin(), damageEffects.end(), effect.mEffectId) != damageEffects.end(); if (isDamageEffect) { if (caster.getClass().isNpc() && caster.getClass().getNpcStats(caster).isWerewolf()) caster.getClass().getNpcStats(caster).addWerewolfKill(); if (caster == player || playerFollowers.find(caster) != playerFollowers.end()) { MWBase::Environment::get().getMechanicsManager()->actorKilled(creature, player); actorKilled = true; break; } } } if (actorKilled) break; } } // updateSummons assumes the actor belongs to a cell. // This assumption isn't always valid for the player character. if (!creature.isInCell()) return; if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty()) updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } void Actors::restoreDynamicStats(const MWWorld::Ptr& ptr, double hours, bool sleep) const { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); if (stats.isDead()) return; const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); if (sleep) { const auto [health, magicka] = getRestorationPerHourOfSleep(ptr); DynamicStat stat = stats.getHealth(); stat.setCurrent(stat.getCurrent() + health * hours); stats.setHealth(stat); double restoreHours = hours; const bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; if (stunted) { // Stunted Magicka effect should be taken into account. float remainingTime = getStuntedMagickaDuration(ptr); // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. if (remainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if(timeScale == 0.0) timeScale = 1; restoreHours = std::max(0.0, hours - remainingTime * timeScale / 3600.f); } else if (remainingTime == -1) restoreHours = 0; } if (restoreHours > 0) { stat = stats.getMagicka(); stat.setCurrent(stat.getCurrent() + magicka * restoreHours); stats.setMagicka(stat); } } // Current fatigue can be above base value due to a fortify effect. // In that case stop here and don't try to restore. DynamicStat fatigue = stats.getFatigue(); if (fatigue.getCurrent() >= fatigue.getBase()) return; // Restore fatigue static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); static const float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); const float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); if (normalizedEncumbrance > 1) normalizedEncumbrance = 1; const float x = (fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance)) * (fEndFatigueMult * endurance); fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours); stats.setFatigue (fatigue); } void Actors::calculateRestoration(const MWWorld::Ptr& ptr, float duration) const { if (ptr.getClass().getCreatureStats(ptr).isDead()) return; MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); // Current fatigue can be above base value due to a fortify effect. // In that case stop here and don't try to restore. DynamicStat fatigue = stats.getFatigue(); if (fatigue.getCurrent() >= fatigue.getBase()) return; // Restore fatigue const float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); const float x = fFatigueReturnBase + fFatigueReturnMult * endurance; fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); } bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isAttackPreparing(); } bool Actors::isRunning(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isRunning(); } bool Actors::isSneaking(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isSneaking(); } static void updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) { const auto& actorClass = ptr.getClass(); NpcStats& stats = actorClass.getNpcStats(ptr); // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); if (stats.getTimeToStartDrowning() == -1.f) stats.setTimeToStartDrowning(fHoldBreathTime); if (!isPlayer && stats.getTimeToStartDrowning() < fHoldBreathTime / 2) { AiSequence& seq = actorClass.getCreatureStats(ptr).getAiSequence(); if (seq.getTypeId() != AiPackageTypeId::Breathe) //Only add it once seq.stack(AiBreathe(), ptr); } const MWBase::World* const world = MWBase::Environment::get().getWorld(); const bool knockedOutUnderwater = (isKnockedOut && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); if ((world->isSubmerged(ptr) || knockedOutUnderwater) && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; if (knockedOutUnderwater) stats.setTimeToStartDrowning(0); else { timeLeft = stats.getTimeToStartDrowning() - duration; if (timeLeft < 0.0f) timeLeft = 0.0f; stats.setTimeToStartDrowning(timeLeft); } const bool godmode = isPlayer && world->getGodModeState(); if (timeLeft == 0.0f && !godmode) { // If drowning, apply 3 points of damage per second static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->mValue.getFloat(); DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent() - fSuffocationDamage * duration); stats.setHealth(health); // Play a drowning sound MWBase::SoundManager* sndmgr = MWBase::Environment::get().getSoundManager(); if (!sndmgr->getSoundPlaying(ptr, "drown")) sndmgr->playSound3D(ptr, "drown", 1.0f, 1.0f); if (isPlayer) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); } } else stats.setTimeToStartDrowning(fHoldBreathTime); } static void updateEquippedLight(const MWWorld::Ptr& ptr, float duration, bool mayEquip) { const bool isPlayer = (ptr == getPlayer()); const auto& actorClass = ptr.getClass(); auto& inventoryStore = actorClass.getInventoryStore(ptr); auto heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); /** * Automatically equip NPCs torches at night and unequip them at day */ if (!isPlayer) { auto torchIter = std::find_if(std::begin(inventoryStore), std::end(inventoryStore), [&](auto entry) { return entry.getType() == ESM::Light::sRecordId && entry.getClass().canBeEquipped(entry, ptr).first; }); if (mayEquip) { if (torchIter != inventoryStore.end()) { if (!actorClass.getCreatureStats(ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId) inventoryStore.unequipItem(*heldIter, ptr); } else if (heldIter == inventoryStore.end() || heldIter->getType() == ESM::Light::sRecordId) { // For hostile NPCs, see if they have anything better to equip first auto shield = inventoryStore.getPreferredShield(ptr); if (shield != inventoryStore.end()) inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); // If we have a torch and can equip it, then equip it now. if (heldIter == inventoryStore.end()) { inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torchIter, ptr); } } } else { if (heldIter != inventoryStore.end() && heldIter->getType() == ESM::Light::sRecordId) { // At day, unequip lights and auto equip shields or other suitable items // (Note: autoEquip will ignore lights) inventoryStore.autoEquip(ptr); } } } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); //If holding a light... const auto world = MWBase::Environment::get().getWorld(); MWRender::Animation *anim = world->getAnimation(ptr); if (heldIter.getType() == MWWorld::ContainerStore::Type_Light && anim && anim->getCarriedLeftShown()) { // Use time from the player's light if(isPlayer) { float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); // -1 is infinite light source. Other negative values are treated as 0. if (timeRemaining != -1.0f) { timeRemaining -= duration; if (timeRemaining <= 0.f) { inventoryStore.remove(*heldIter, 1, ptr); // remove it return; } heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining); } } // Both NPC and player lights extinguish in water. if(world->isSwimming(ptr)) { inventoryStore.remove(*heldIter, 1, ptr); // remove it // ...But, only the player makes a sound. if(isPlayer) MWBase::Environment::get().getSoundManager()->playSound("torch out", 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } } } void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const { const MWWorld::Ptr player = getPlayer(); if (ptr == player) return; const auto& actorClass = ptr.getClass(); if (!actorClass.isNpc()) return; // get stats of witness CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); const auto& playerClass = player.getClass(); const auto& playerStats = playerClass.getNpcStats(player); if (playerStats.isWerewolf()) return; const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); const auto world = MWBase::Environment::get().getWorld(); if (actorClass.isClass(ptr, "Guard") && !creatureStats.getAiSequence().isInPursuit() && !creatureStats.getAiSequence().isInCombat() && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = world->getStore(); static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); // Force dialogue on sight if bounty is greater than the cutoff // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) if (playerStats.getBounty() >= cutoff // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so? && world->getLOS(ptr, player) && mechanicsManager->awarenessCheck(player, ptr)) { static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { mechanicsManager->startCombat(ptr, player); creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable } else creatureStats.getAiSequence().stack(AiPursue(player), ptr); creatureStats.setAlarmed(true); npcStats.setCrimeId(world->getPlayer().getNewCrimeId()); } } // if I was a witness to a crime if (npcStats.getCrimeId() != -1) { // if you've paid for your crimes and I havent noticed if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPursuit(); stopCombat(ptr); // Reset factors to attack creatureStats.setAttacked(false); creatureStats.setAlarmed(false); creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr)); // Update witness crime id npcStats.setCrimeId(-1); } } } Actors::Actors() : mSmoothMovement(Settings::Manager::getBool("smooth movement", "Game")) { mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning updateProcessingRange(); } float Actors::getProcessingRange() const { return mActorsProcessingRange; } void Actors::updateProcessingRange() { // We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876) static constexpr float maxRange = 7168.f; static constexpr float minRange = maxRange / 2.f; mActorsProcessingRange = std::clamp(Settings::Manager::getFloat("actors processing range", "Game"), minRange, maxRange); } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { removeActor(ptr, true); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; const auto it = mActors.emplace(mActors.end(), ptr, anim); mIndex.emplace(ptr.mRef, it); if (updateImmediately) it->getCharacterController().update(0); // We should initially hide actors outside of processing range. // Note: since we update player after other actors, distance will be incorrect during teleportation. // Do not update visibility if player was teleported, so actors will be visible during teleportation frame. if (MWBase::Environment::get().getWorld()->getPlayer().wasTeleported()) return; updateVisibility(ptr, it->getCharacterController()); } void Actors::updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const { MWWorld::Ptr player = MWMechanics::getPlayer(); if (ptr == player) return; const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); if (dist > mActorsProcessingRange) { ptr.getRefData().getBaseNode()->setNodeMask(0); return; } else ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); // Fade away actors on large distance (>90% of actor's processing distance) float visibilityRatio = 1.0; const float fadeStartDistance = mActorsProcessingRange*0.9f; const float fadeEndDistance = mActorsProcessingRange; const float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); if (fadeRatio > 0) visibilityRatio -= std::max(0.f, fadeRatio); visibilityRatio = std::min(1.f, visibilityRatio); ctrl.setVisibility(visibilityRatio); } void Actors::removeActor (const MWWorld::Ptr& ptr, bool keepActive) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { if(!keepActive) removeTemporaryEffects(iter->second->getPtr()); mActors.erase(iter->second); mIndex.erase(iter); } } void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string& spellId, bool manualSpell) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) iter->second->getCharacterController().castSpell(spellId, manualSpell); } bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const { if (!actor.getClass().isActor()) return false; // If an observer is NPC, check if he detected an actor if (!observer.isEmpty() && observer.getClass().isNpc()) { return MWBase::Environment::get().getWorld()->getLOS(observer, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); } // Otherwise check if any actor in AI processing range sees the target actor std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); for (const MWWorld::Ptr &neighbor : neighbors) { if (neighbor == actor) continue; const bool result = MWBase::Environment::get().getWorld()->getLOS(neighbor, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, neighbor); if (result) return true; } return false; } void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) const { const auto iter = mIndex.find(old.mRef); if (iter != mIndex.end()) iter->second->updatePtr(ptr); } void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) { for (auto iter = mActors.begin(); iter != mActors.end();) { if ((iter->getPtr().isInCell() && iter->getPtr().getCell() == cellStore) && iter->getPtr() != ignore) { removeTemporaryEffects(iter->getPtr()); mIndex.erase(iter->getPtr().mRef); iter = mActors.erase(iter); } else ++iter; } } void Actors::updateCombatMusic () { const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); bool hasHostiles = false; // need to know this to play Battle music const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); if (aiActive) { for (const Actor& actor : mActors) { if (actor.getPtr() == player) continue; bool inProcessingRange = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange; if (inProcessingRange) { MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); if (!stats.isDead() && stats.getAiSequence().isInCombat()) { hasHostiles = true; break; } } } } // check if we still have any player enemies to switch music if (mCurrentMusic != MusicType::Explore && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); mCurrentMusic = MusicType::Explore; } else if (mCurrentMusic != MusicType::Battle && hasHostiles) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); mCurrentMusic = MusicType::Battle; } } void Actors::predictAndAvoidCollisions(float duration) const { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; const float minGap = 10.f; const float maxDistForPartialAvoiding = 200.f; const float maxDistForStrictAvoiding = 100.f; const float maxTimeToCheck = 2.0f; static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game"); const MWWorld::Ptr player = getPlayer(); const MWBase::World* const world = MWBase::Environment::get().getWorld(); for (const Actor& actor : mActors) { const MWWorld::Ptr& ptr = actor.getPtr(); if (ptr == player) continue; // Don't interfere with player controls. const float maxSpeed = ptr.getClass().getMaxSpeed(ptr); if (maxSpeed == 0.0) continue; // Can't move, so there is no sense to predict collisions. Movement& movement = ptr.getClass().getMovementSettings(ptr); const osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); const bool isMoving = origMovement.length2() > 0.01; if (movement.mPosition[1] < 0) continue; // Actors can not see others when move backward. // Moving NPCs always should avoid collisions. // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // follow player or have a AIWander package with non-empty wander area. bool shouldAvoidCollision = isMoving; bool shouldGiveWay = false; bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if (!aiSequence.isEmpty()) { const auto& package = aiSequence.getActivePackage(); if (package.getTypeId() == AiPackageTypeId::Follow) { shouldAvoidCollision = true; } else if (package.getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package).isStationary()) shouldGiveWay = true; } else if (package.getTypeId() == AiPackageTypeId::Combat || package.getTypeId() == AiPackageTypeId::Pursue) { currentTarget = package.getTarget(); shouldAvoidCollision = isMoving; shouldTurnToApproachingActor = false; } } if (!shouldAvoidCollision && !shouldGiveWay) continue; const osg::Vec2f baseSpeed = origMovement * maxSpeed; const osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); const float baseRotZ = ptr.getRefData().getPosition().rot[2]; const osg::Vec3f halfExtents = world->getHalfExtents(ptr); const float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; float timeToCheck = maxTimeToCheck; if (!shouldGiveWay && !aiSequence.isEmpty()) timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); float timeToCollision = timeToCheck; osg::Vec2f movementCorrection(0, 0); float angleToApproachingActor = 0; // Iterate through all other actors and predict collisions. for (const Actor& otherActor : mActors) { const MWWorld::Ptr& otherPtr = otherActor.getPtr(); if (otherPtr == ptr || otherPtr == currentTarget) continue; const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); const osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; const osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); const float dist = deltaPos.length(); // Ignore actors which are not close enough or come from behind. if (dist > maxDistToCheck || relPos.y() < 0) continue; // Don't check for a collision if vertical distance is greater then the actor's height. if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2) continue; const osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() * otherPtr.getClass().getMaxSpeed(otherPtr); const float rotZ = otherPtr.getRefData().getPosition().rot[2]; const osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; float collisionDist = minGap + halfExtents.x() + otherHalfExtents.x(); collisionDist = std::min(collisionDist, relPos.length()); // Find the earliest `t` when |relPos + relSpeed * t| == collisionDist. const float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y(); const float v2 = relSpeed.length2(); const float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist); if (Dh <= 0 || v2 == 0) continue; // No solution; distance is always >= collisionDist. const float t = (-vr - std::sqrt(Dh)) / v2; if (t < 0 || t > timeToCollision) continue; // Check visibility and awareness last as it's expensive. if (!MWBase::Environment::get().getWorld()->getLOS(otherPtr, ptr)) continue; if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(otherPtr, ptr)) continue; timeToCollision = t; angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y()); const osg::Vec2f posAtT = relPos + relSpeed * t; const float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed) * std::clamp((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f); movementCorrection = posAtT * coef; if (otherPtr.getClass().getCreatureStats(otherPtr).isDead()) // In case of dead body still try to go around (it looks natural), but reduce the correction twice. movementCorrection.y() *= 0.5f; } if (timeToCollision < timeToCheck) { // Try to evade the nearest collision. osg::Vec2f newMovement = origMovement + movementCorrection; // Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location. newMovement.y() = std::max(newMovement.y(), 0.f); newMovement.normalize(); if (isMoving) newMovement *= origMovement.length(); // Keep the original speed. movement.mPosition[0] = newMovement.x(); movement.mPosition[1] = newMovement.y(); if (shouldTurnToApproachingActor) zTurn(ptr, angleToApproachingActor); } } } void Actors::update (float duration, bool paused) { if(!paused) { const float updateEquippedLightInterval = 1.0f; if (mTimerUpdateHeadTrack >= 0.3f) mTimerUpdateHeadTrack = 0; if (mTimerUpdateHello >= 0.25f) mTimerUpdateHello = 0; if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (mTimerUpdateEquippedLight >= updateEquippedLightInterval) mTimerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations MWBase::World* const world = MWBase::Environment::get().getWorld(); const bool showTorches = world->useTorches(); const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); /// \todo move update logic to Actor class where appropriate std::map > cachedAllies; // will be filled as engageCombat iterates const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); if (attackedByPlayerId != -1) { const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId); if (!playerHitAttemptActor.isInCell()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } const bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); // AI and magic effects update for (Actor& actor : mActors) { const bool isPlayer = actor.getPtr() == player; CharacterController& ctrl = actor.getCharacterController(); MWBase::LuaManager::ActorControls* luaControls = MWBase::Environment::get().getLuaManager()->getActorControls(actor.getPtr()); const float distSqr = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2(); // AI processing is only done within given distance to the player. const bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (!isPlayer && (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead() || !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence().isInCombat() || !inProcessingRange)) { actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActorId(-1); if (player.getClass().getCreatureStats(player).getHitAttemptActorId() == actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActorId()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } const Misc::TimerStatus engageCombatTimerStatus = actor.updateEngageCombatTimer(duration); // For dead actors we need to update looping spell particles if (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead()) { // They can be added during the death animation if (!actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDeathAnimationFinished()) adjustMagicEffects(actor.getPtr(), duration); ctrl.updateContinuousVfx(); } else { const bool cellChanged = world->hasCellChanged(); const MWWorld::Ptr actorPtr = actor.getPtr(); // make a copy of the map key to avoid it being invalidated when the player teleports updateActor(actorPtr, duration); // Looping magic VFX update // Note: we need to do this before any of the animations are updated. // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // so updating VFX immediately after that would just remove the particle effects instantly. // There needs to be a magic effect update in between. ctrl.updateContinuousVfx(); if (!cellChanged && world->hasCellChanged()) { return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame } if (aiActive && inProcessingRange) { if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) { if (!isPlayer) adjustCommandedActor(actor.getPtr()); for (const Actor& otherActor : mActors) { if (otherActor.getPtr() == actor.getPtr() || isPlayer) // player is not AI-controlled continue; engageCombat(actor.getPtr(), otherActor.getPtr(), cachedAllies, otherActor.getPtr() == player); } } if (mTimerUpdateHeadTrack == 0) updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl); if (actor.getPtr().getClass().isNpc() && !isPlayer) updateCrimePursuit(actor.getPtr(), duration); if (!isPlayer) { CreatureStats &stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); if (isConscious(actor.getPtr()) && !(luaControls && luaControls->mDisableAI)) { stats.getAiSequence().execute(actor.getPtr(), ctrl, duration); updateGreetingState(actor.getPtr(), actor, mTimerUpdateHello > 0); playIdleDialogue(actor.getPtr()); updateMovementSpeed(actor.getPtr()); } } } else if (aiActive && !isPlayer && isConscious(actor.getPtr()) && !(luaControls && luaControls->mDisableAI)) { CreatureStats &stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); stats.getAiSequence().execute(actor.getPtr(), ctrl, duration, /*outOfRange*/true); } if (inProcessingRange && actor.getPtr().getClass().isNpc()) { // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe updateDrowning(actor.getPtr(), duration, ctrl.isKnockedOut(), isPlayer); } if (mTimerUpdateEquippedLight == 0 && actor.getPtr().getClass().hasInventoryStore(actor.getPtr())) updateEquippedLight(actor.getPtr(), updateEquippedLightInterval, showTorches); if (luaControls != nullptr && isConscious(actor.getPtr())) updateLuaControls(actor.getPtr(), isPlayer, *luaControls); } } static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game"); if (avoidCollisions) predictAndAvoidCollisions(duration); mTimerUpdateHeadTrack += duration; mTimerUpdateEquippedLight += duration; mTimerUpdateHello += duration; mTimerDisposeSummonsCorpses += duration; // Animation/movement update CharacterController* playerCharacter = nullptr; for (Actor& actor : mActors) { const float dist = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length(); const bool isPlayer = actor.getPtr() == player; CreatureStats &stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); // Actors with active AI should be able to move. bool alwaysActive = false; if (!isPlayer && isConscious(actor.getPtr()) && !stats.isParalyzed()) { MWMechanics::AiSequence& seq = stats.getAiSequence(); alwaysActive = !seq.isEmpty() && seq.getActivePackage().alwaysActive(); } const bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive; const int activeFlag = isPlayer ? 2 : 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) const int active = inRange ? activeFlag : 0; CharacterController& ctrl = actor.getCharacterController(); ctrl.setActive(active); if (!inRange) { actor.getPtr().getRefData().getBaseNode()->setNodeMask(0); world->setActorActive(actor.getPtr(), false); continue; } world->setActorActive(actor.getPtr(), true); const bool isDead = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead(); if (!isDead && (!godmode || !isPlayer) && actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isParalyzed()) ctrl.skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) if (isPlayer) { playerCharacter = &ctrl; continue; } actor.getPtr().getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); world->setActorCollisionMode(actor.getPtr(), true, !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDeathAnimationFinished()); if (!actor.getPositionAdjusted()) { actor.getPtr().getClass().adjustPosition(actor.getPtr(), false); actor.setPositionAdjusted(true); } ctrl.update(duration); updateVisibility(actor.getPtr(), ctrl); } if (playerCharacter) { MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(duration); playerCharacter->update(duration); playerCharacter->setVisibility(1.f); } for (const Actor& actor : mActors) { const MWWorld::Class &cls = actor.getPtr().getClass(); CreatureStats &stats = cls.getCreatureStats(actor.getPtr()); //KnockedOutOneFrameLogic //Used for "OnKnockedOut" command //Put here to ensure that it's run for PRECISELY one frame. if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Start it for one frame if nessesary stats.setKnockedDownOneFrame(true); } else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Turn off KnockedOutOneframe stats.setKnockedDownOneFrame(false); stats.setKnockedDownOverOneFrame(true); } } killDeadActors(); updateSneaking(playerCharacter, duration); } updateCombatMusic(); } void Actors::notifyDied(const MWWorld::Ptr &actor) { actor.getClass().getCreatureStats(actor).notifyDied(); ++mDeathCount[Misc::StringUtils::lowerCase(actor.getCellRef().getRefId())]; } void Actors::resurrect(const MWWorld::Ptr &ptr) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { if (iter->second->getCharacterController().isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. MWBase::Environment::get().getWorld()->enableActorCollision(iter->second->getPtr(), true); iter->second->getCharacterController().resurrect(); } } } void Actors::killDeadActors() { for (Actor& actor : mActors) { const MWWorld::Class &cls = actor.getPtr().getClass(); CreatureStats &stats = cls.getCreatureStats(actor.getPtr()); if(!stats.isDead()) continue; MWBase::Environment::get().getWorld()->removeActorPath(actor.getPtr()); CharacterController::KillResult killResult = actor.getCharacterController().kill(); if (killResult == CharacterController::Result_DeathAnimStarted) { // Play dying words // Note: It's not known whether the soundgen tags scream, roar, and moan are reliable // for NPCs since some of the npc death animation files are missing them. MWBase::Environment::get().getDialogueManager()->say(actor.getPtr(), "hit"); // Apply soultrap if (actor.getPtr().getType() == ESM::Creature::sRecordId) soulTrap(actor.getPtr()); if (cls.isEssential(actor.getPtr())) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } else if (killResult == CharacterController::Result_DeathAnimJustFinished) { const bool isPlayer = actor.getPtr() == getPlayer(); notifyDied(actor.getPtr()); // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death const float vampirism = stats.getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude(); stats.getActiveSpells().clear(actor.getPtr()); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); // Play Death Music if it was the player dying MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } else { // NPC death animation is over, disable actor collision MWBase::Environment::get().getWorld()->enableActorCollision(actor.getPtr(), false); } } } } void Actors::cleanupSummonedCreature(MWMechanics::CreatureStats& casterStats, int creatureActorId) const { const MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); if (!ptr.isEmpty()) { MWBase::Environment::get().getWorld()->deleteObject(ptr); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_End"); if (fx) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); MWBase::Environment::get().getWorld()->spawnEffect( Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", ptr.getRefData().getPosition().asVec3()); } // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); auto& creatureMap = stats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) cleanupSummonedCreature(stats, creature.second); creatureMap.clear(); } else if (creatureActorId != -1) { // We didn't find the creature. It's probably in an inactive cell. // Add to graveyard so we can delete it when the cell becomes active. std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); graveyard.push_back(creatureActorId); } purgeSpellEffects(creatureActorId); } void Actors::purgeSpellEffects(int casterActorId) const { for (const Actor& actor : mActors) { MWMechanics::ActiveSpells& spells = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActiveSpells(); spells.purge(actor.getPtr(), casterActorId); } } void Actors::rest(double hours, bool sleep) const { float duration = hours * 3600.f; const float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if (timeScale != 0.f) duration /= timeScale; const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); for (const Actor& actor : mActors) { if (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead()) { adjustMagicEffects(actor.getPtr(), duration); continue; } if (!sleep || actor.getPtr() == player) restoreDynamicStats(actor.getPtr(), hours, sleep); if ((!actor.getPtr().getRefData().getBaseNode()) || (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; adjustMagicEffects (actor.getPtr(), duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(actor.getPtr()); if (animation) { animation->removeEffects(); MWBase::Environment::get().getWorld()->applyLoopingParticles(actor.getPtr()); } } fastForwardAi(); } void Actors::updateSneaking(CharacterController* ctrl, float duration) { if (!ctrl) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; } const MWWorld::Ptr player = getPlayer(); if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player)) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; } MWBase::World* const world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); if (mSneakTimer >= fSneakUseDelay) mSneakTimer = 0.f; if (mSneakTimer == 0.f) { // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. bool avoidedNotice = false; bool detected = false; std::vector observers; const osg::Vec3f position(player.getRefData().getPosition().asVec3()); const float radius = std::min(fSneakUseDist, mActorsProcessingRange); getObjectsInRange(position, radius, observers); std::set sidingActors; getActorsSidingWith(player, sidingActors); for (const MWWorld::Ptr &observer : observers) { if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) continue; if (sidingActors.find(observer) != sidingActors.cend()) continue; if (world->getLOS(player, observer)) { if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) { detected = true; avoidedNotice = false; MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); break; } else { avoidedNotice = true; } } } if (mSneakSkillTimer >= fSneakUseDelay) mSneakSkillTimer = 0.f; if (avoidedNotice && mSneakSkillTimer == 0.f) player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); } mSneakTimer += duration; mSneakSkillTimer += duration; } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const { const auto [healthPerHour, magickaPerHour] = getRestorationPerHourOfSleep(ptr); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; const float healthHours = healthPerHour > 0 ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour : 1.0f; const float magickaHours = magickaPerHour > 0 && !stunted ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour : 1.0f; return static_cast(std::ceil(std::max(1.f, std::max(healthHours, magickaHours)))); } int Actors::countDeaths (const std::string& id) const { const auto iter = mDeathCount.find(id); if(iter != mDeathCount.end()) return iter->second; return 0; } void Actors::forceStateUpdate(const MWWorld::Ptr& ptr) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) iter->second->getCharacterController().forceStateUpdate(); } bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) const { const auto iter = mIndex.find(ptr.mRef); if(iter != mIndex.end()) { return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); } else { Log(Debug::Warning) << "Warning: Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); return false; } } void Actors::skipAnimation(const MWWorld::Ptr& ptr) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) iter->second->getCharacterController().skipAnim(); } bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const { const auto iter = mIndex.find(ptr.mRef); if(iter != mIndex.end()) return iter->second->getCharacterController().isAnimPlaying(groupName); return false; } void Actors::persistAnimationStates() const { for (const Actor& actor : mActors) actor.getCharacterController().persistAnimationState(); } void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const { for (const Actor& actor : mActors) { if ((actor.getPtr().getRefData().getPosition().asVec3() - position).length2() <= radius*radius) out.push_back(actor.getPtr()); } } bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) const { for (const Actor& actor : mActors) { if ((actor.getPtr().getRefData().getPosition().asVec3() - position).length2() <= radius*radius) return true; } return false; } std::vector Actors::getActorsSidingWith(const MWWorld::Ptr& actorPtr, bool excludeInfighting) const { std::vector list; for (const Actor& actor : mActors) { const MWWorld::Ptr& iteratedActor = actor.getPtr(); if (iteratedActor == getPlayer()) continue; const bool sameActor = (iteratedActor == actorPtr); const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them for (const auto& package : stats.getAiSequence()) { if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat && package->getTarget() == actorPtr) break; if (package->sideWithTarget() && !package->getTarget().isEmpty()) { if (sameActor) { if(excludeInfighting) { MWWorld::Ptr ally = package->getTarget(); std::vector enemies; if(ally.getClass().getCreatureStats(ally).getAiSequence().getCombatTargets(enemies) && std::find(enemies.begin(), enemies.end(), actorPtr) != enemies.end()) break; } list.push_back(package->getTarget()); } else if (package->getTarget() == actorPtr) { list.push_back(iteratedActor); } break; } else if (package->getTypeId() > AiPackageTypeId::Wander && package->getTypeId() <= AiPackageTypeId::Activate) // Don't count "fake" package types break; } } return list; } std::vector Actors::getActorsFollowing(const MWWorld::Ptr& actorPtr) const { std::vector list; forEachFollowingPackage(mActors, actorPtr, getPlayer(), [&] (const Actor& actor, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actorPtr) list.push_back(actor.getPtr()); else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) return false; return true; }); return list; } void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set& out) const { auto followers = getActorsFollowing(actor); for(const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsFollowing(follower, out); } void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, bool excludeInfighting) const { auto followers = getActorsSidingWith(actor, excludeInfighting); for(const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsSidingWith(follower, out, excludeInfighting); } void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map>& cachedAllies) const { // If we have already found actor's allies, use the cache std::map >::const_iterator search = cachedAllies.find(actor); if (search != cachedAllies.end()) out.insert(search->second.begin(), search->second.end()); else { for (const MWWorld::Ptr &follower : getActorsSidingWith(actor, true)) if (out.insert(follower).second) getActorsSidingWith(follower, out, cachedAllies); // Cache ptrs and their sets of allies cachedAllies.insert(std::make_pair(actor, out)); for (const MWWorld::Ptr &iter : out) { search = cachedAllies.find(iter); if (search == cachedAllies.end()) cachedAllies.insert(std::make_pair(iter, out)); } } } std::vector Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) const { std::vector list; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (const Actor&, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { list.push_back(static_cast(package.get())->getFollowIndex()); return false; } else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) return false; return true; }); return list; } std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor) const { std::map map; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (const Actor& otherActor, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { const int index = static_cast(package.get())->getFollowIndex(); map[index] = otherActor.getPtr(); return false; } else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) return false; return true; }); return map; } std::vector Actors::getActorsFighting(const MWWorld::Ptr& actor) const { std::vector list; std::vector neighbors; const osg::Vec3f position(actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); for(const MWWorld::Ptr& neighbor : neighbors) { if (neighbor == actor) continue; const CreatureStats &stats = neighbor.getClass().getCreatureStats(neighbor); if (stats.isDead()) continue; if (stats.getAiSequence().isInCombat(actor)) list.push_back(neighbor); } return list; } std::vector Actors::getEnemiesNearby(const MWWorld::Ptr& actor) const { std::vector list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); for (const MWWorld::Ptr& neighbor : neighbors) { const CreatureStats &stats = neighbor.getClass().getCreatureStats(neighbor); if (stats.isDead() || neighbor == actor || neighbor.getClass().isPureWaterCreature(neighbor)) continue; const bool isFollower = followers.find(neighbor) != followers.end(); if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(neighbor, actor) && !isFollower)) list.push_back(neighbor); } return list; } void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const { writer.startRecord(ESM::REC_DCOU); for (const auto& [id, count] : mDeathCount) { writer.writeHNString("ID__", id); writer.writeHNT ("COUN", count); } writer.endRecord(ESM::REC_DCOU); } void Actors::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_DCOU) { while (reader.isNextSub("ID__")) { std::string id = reader.getHString(); int count; reader.getHNT(count, "COUN"); if (MWBase::Environment::get().getWorld()->getStore().find(id)) mDeathCount[id] = count; } } } void Actors::clear() { mIndex.clear(); mActors.clear(); mDeathCount.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) const { adjustMagicEffects(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isReadyToBlock(); } bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isCastingSpell(); } bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->getCharacterController().isAttackingOrSpell(); } int Actors::getGreetingTimer(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return 0; return it->second->getGreetingTimer(); } float Actors::getAngleToPlayer(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return 0.f; return it->second->getAngleToPlayer(); } GreetingState Actors::getGreetingState(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return Greet_None; return it->second->getGreetingState(); } bool Actors::isTurningToPlayer(const MWWorld::Ptr& ptr) const { const auto it = mIndex.find(ptr.mRef); if (it == mIndex.end()) return false; return it->second->isTurningToPlayer(); } void Actors::fastForwardAi() const { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; for (auto it = mActors.begin(); it != mActors.end();) { const MWWorld::Ptr ptr = it->getPtr(); ++it; if (ptr == getPlayer() || !isConscious(ptr) || ptr.getClass().getCreatureStats(ptr).isParalyzed()) continue; MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); seq.fastForward(ptr); } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/actors.hpp000066400000000000000000000222521445372753700232110ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTORS_H #define GAME_MWMECHANICS_ACTORS_H #include #include #include #include #include #include "actor.hpp" namespace ESM { class ESMReader; class ESMWriter; } namespace osg { class Vec3f; } namespace Loading { class Listener; } namespace MWWorld { class Ptr; class CellStore; } namespace MWMechanics { class Actor; class CharacterController; class CreatureStats; class Actors { public: Actors(); std::list::const_iterator begin() const { return mActors.begin(); } std::list::const_iterator end() const { return mActors.end(); } std::size_t size() const { return mActors.size(); } void notifyDied(const MWWorld::Ptr &actor); /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects(const MWWorld::Ptr& ptr) const; void updateProcessingRange(); float getProcessingRange() const; void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management /// /// \note Dead actors are ignored. void removeActor (const MWWorld::Ptr& ptr, bool keepActive); ///< Deregister an actor for stats management /// /// \note Ignored, if \a ptr is not a registered actor. void resurrect(const MWWorld::Ptr& ptr) const; void castSpell(const MWWorld::Ptr& ptr, const std::string& spellId, bool manualSpell = false) const; void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr) const; ///< Updates an actor with a new Ptr void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); ///< Deregister all actors (except for \a ignore) in the given cell. void updateCombatMusic(); ///< Update combat music state void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement void updateActor(const MWWorld::Ptr& ptr, float duration) const; ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. /// Removes an actor from combat and makes all of their allies stop fighting the actor's targets void stopCombat(const MWWorld::Ptr& ptr) const; void playIdleDialogue(const MWWorld::Ptr& actor) const; void updateMovementSpeed(const MWWorld::Ptr& actor) const; void updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly); void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) const; void rest(double hours, bool sleep) const; ///< Update actors while the player is waiting or sleeping. void updateSneaking(CharacterController* ctrl, float duration); ///< Update the sneaking indicator state according to the given player character controller. void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) const; int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed void fastForwardAi() const; ///< Simulate the passing of time int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. bool isAttackPreparing(const MWWorld::Ptr& ptr) const; bool isRunning(const MWWorld::Ptr& ptr) const; bool isSneaking(const MWWorld::Ptr& ptr) const; void forceStateUpdate(const MWWorld::Ptr &ptr) const; bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; void persistAnimationStates() const; void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; bool isAnyObjectInRange(const osg::Vec3f& position, float radius) const; void cleanupSummonedCreature(CreatureStats& casterStats, int creatureActorId) const; ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ std::vector getActorsSidingWith(const MWWorld::Ptr& actor, bool excludeInfighting = false) const; std::vector getActorsFollowing(const MWWorld::Ptr& actor) const; /// Recursive version of getActorsFollowing void getActorsFollowing(const MWWorld::Ptr &actor, std::set& out) const; /// Recursive version of getActorsSidingWith void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, bool excludeInfighting = false) const; /// Get the list of AiFollow::mFollowIndex for all actors following this target std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) const; std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) const; ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ std::vector getActorsFighting(const MWWorld::Ptr& actor) const; /// Unlike getActorsFighting, also returns actors that *would* fight the given actor if they saw him. std::vector getEnemiesNearby(const MWWorld::Ptr& actor) const; void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; void readRecord (ESM::ESMReader& reader, uint32_t type); void clear(); // Clear death counter bool isCastingSpell(const MWWorld::Ptr& ptr) const; bool isReadyToBlock(const MWWorld::Ptr& ptr) const; bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; int getGreetingTimer(const MWWorld::Ptr& ptr) const; float getAngleToPlayer(const MWWorld::Ptr& ptr) const; GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; private: enum class MusicType { Title, Explore, Battle }; std::map mDeathCount; std::list mActors; std::map::iterator> mIndex; float mTimerDisposeSummonsCorpses; float mTimerUpdateHeadTrack = 0; float mTimerUpdateEquippedLight = 0; float mTimerUpdateHello = 0; float mSneakTimer = 0; // Times update of sneak icon float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice" float mActorsProcessingRange; bool mSmoothMovement; MusicType mCurrentMusic = MusicType::Title; void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const; void adjustMagicEffects(const MWWorld::Ptr& creature, float duration) const; void calculateRestoration(const MWWorld::Ptr& ptr, float duration) const; void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const; void killDeadActors (); void purgeSpellEffects(int casterActorId) const; void predictAndAvoidCollisions(float duration) const; /** Start combat between two actors @Notes: If againstPlayer = true then actor2 should be the Player. If one of the combatants is creature it should be actor1. */ void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map>& cachedAllies, bool againstPlayer) const; /// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of /// actors mapped to their allies. Excludes infighting void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map>& cachedAllies) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/actorutil.cpp000066400000000000000000000025571445372753700237250ustar00rootroot00000000000000#include "actorutil.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/creaturestats.hpp" #include namespace MWMechanics { MWWorld::Ptr getPlayer() { return MWBase::Environment::get().getWorld()->getPlayerPtr(); } bool isPlayerInCombat() { return MWBase::Environment::get().getWorld()->getPlayer().isInCombat(); } bool canActorMoveByZAxis(const MWWorld::Ptr& actor) { MWBase::World* world = MWBase::Environment::get().getWorld(); return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); } bool hasWaterWalking(const MWWorld::Ptr& actor) { const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; } bool isTargetMagicallyHidden(const MWWorld::Ptr& actor) { const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/actorutil.hpp000066400000000000000000000005751445372753700237300ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H namespace MWWorld { class Ptr; } namespace MWMechanics { MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); bool hasWaterWalking(const MWWorld::Ptr& actor); bool isTargetMagicallyHidden(const MWWorld::Ptr& actor); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiactivate.cpp000066400000000000000000000046721445372753700240310ustar00rootroot00000000000000#include "aiactivate.hpp" #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" namespace MWMechanics { AiActivate::AiActivate(std::string_view objectId, bool repeat) : TypedAiPackage(repeat), mObjectId(objectId) { } bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) return true; // Turn to target and move to it directly, without pathfinding. const osg::Vec3f targetDir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3(); zTurn(actor, std::atan2(targetDir.x(), targetDir.y()), 0.f); actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[0] = 0; if (MWBase::Environment::get().getWorld()->getMaxActivationDistance() >= targetDir.length()) { // Note: we intentionally do not cancel package after activation here for backward compatibility with original engine. MWBase::Environment::get().getWorld()->activate(target, actor); } return false; } void AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const { auto activate = std::make_unique(); activate->mTargetId = mObjectId; activate->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Activate; package.mPackage = std::move(activate); sequence.mPackages.push_back(std::move(package)); } AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) : AiActivate(activate->mTargetId, activate->mRepeat) { } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiactivate.hpp000066400000000000000000000022471445372753700240320ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H #include "typedaipackage.hpp" #include #include #include "pathfinding.hpp" namespace ESM { namespace AiSequence { struct AiActivate; } } namespace MWMechanics { /// \brief Causes actor to walk to activatable object and activate it /** Will activate when close to object **/ class AiActivate final : public TypedAiPackage { public: /// Constructor /** \param objectId Reference to object to activate **/ explicit AiActivate(std::string_view objectId, bool repeat); explicit AiActivate(const ESM::AiSequence::AiActivate* activate); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Activate; } void writeState(ESM::AiSequence::AiSequence& sequence) const override; private: const std::string mObjectId; }; } #endif // GAME_MWMECHANICS_AIACTIVATE_H openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiavoiddoor.cpp000066400000000000000000000054671445372753700242220ustar00rootroot00000000000000#include "aiavoiddoor.hpp" #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "actorutil.hpp" #include "steering.hpp" static const int MAX_DIRECTIONS = 4; MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) : mDuration(1), mDoorPtr(doorPtr), mDirection(0) { } bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); if(mDuration == 1) //If it just started, get the actor position as the stuck detection thing mLastPos = pos.asVec3(); mDuration -= duration; //Update timer if (mDuration < 0) { if (isStuck(pos.asVec3())) { adjustDirection(); mDuration = 1; //reset timer } else return true; // We have tried backing up for more than one second, we've probably cleared it } if (mDoorPtr.getClass().getDoorState(mDoorPtr) == MWWorld::DoorState::Idle) return true; //Door is no longer opening ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door float x = pos.pos[1] - tPos.pos[1]; float y = pos.pos[0] - tPos.pos[0]; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // Make all nearby actors also avoid the door std::vector actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(),100,actors); for(auto& neighbor : actors) { if (neighbor == getPlayer()) continue; MWMechanics::AiSequence& seq = neighbor.getClass().getCreatureStats(neighbor).getAiSequence(); if (seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr), neighbor); } return false; } bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const { return (actorPos - mLastPos).length2() < 10 * 10; } void MWMechanics::AiAvoidDoor::adjustDirection() { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS, prng); } float MWMechanics::AiAvoidDoor::getAdjustedAngle() const { return 2 * osg::PI / MAX_DIRECTIONS * mDirection; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiavoiddoor.hpp000066400000000000000000000027101445372753700242130ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIAVOIDDOOR_H #define GAME_MWMECHANICS_AIAVOIDDOOR_H #include "typedaipackage.hpp" #include "../mwworld/class.hpp" #include "pathfinding.hpp" namespace MWMechanics { /// \brief AiPackage to have an actor avoid an opening door /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it **/ class AiAvoidDoor final : public TypedAiPackage { public: /// Avoid door until the door is fully open explicit AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::AvoidDoor; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mPriority = 2; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } private: float mDuration; const MWWorld::ConstPtr mDoorPtr; osg::Vec3f mLastPos; int mDirection; bool isStuck(const osg::Vec3f& actorPos) const; void adjustDirection(); float getAdjustedAngle() const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aibreathe.cpp000066400000000000000000000017761445372753700236450ustar00rootroot00000000000000#include "aibreathe.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "npcstats.hpp" #include "movement.hpp" #include "steering.hpp" bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); const MWWorld::Class& actorClass = actor.getClass(); if (actorClass.isNpc()) { if (actorClass.getNpcStats(actor).getTimeToStartDrowning() < fHoldBreathTime / 2) { actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); actorClass.getMovementSettings(actor).mPosition[1] = 1; smoothTurn(actor, static_cast(-osg::PI_2), 0); return false; } } return true; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aibreathe.hpp000066400000000000000000000015641445372753700236450ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIBREATHE_H #define GAME_MWMECHANICS_AIBREATHE_H #include "typedaipackage.hpp" namespace MWMechanics { /// \brief AiPackage to have an actor resurface to breathe // The AI will go up if lesser than half breath left class AiBreathe final : public TypedAiPackage { public: bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Breathe; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mPriority = 2; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicast.cpp000066400000000000000000000057041445372753700231600ustar00rootroot00000000000000#include "aicast.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "aicombataction.hpp" #include "creaturestats.hpp" #include "steering.hpp" namespace MWMechanics { namespace { float getInitialDistance(const std::string& spellId) { ActionSpell action = ActionSpell(spellId); bool isRanged; return action.getCombatRange(isRanged); } } } MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell) : mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(getInitialDistance(spellId)) { } bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration) { MWWorld::Ptr target; if (actor.getCellRef().getRefId() == mTargetId) { // If the target has the same ID as caster, consider that actor casts spell with Self range. target = actor; } else { target = getTarget(); if (!target) return true; if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance)) { return false; } } osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); // If the target of an on-target spell is an actor that is not the caster // the target position must be adjusted so that it's not casted at the actor's feet. if (target != actor && target.getClass().isActor()) { osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; } osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight; osg::Vec3f dir = targetPos - actorPos; bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); if (!turned) return false; // Check if the actor is already casting another spell bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor); if (isCasting && !mCasting) return false; if (!mCasting) { MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual); mCasting = true; return false; } // Finish package, if actor finished spellcasting return !isCasting; } MWWorld::Ptr MWMechanics::AiCast::getTarget() const { MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetId, false); return target; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicast.hpp000066400000000000000000000022511445372753700231570ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AICAST_H #define GAME_MWMECHANICS_AICAST_H #include "typedaipackage.hpp" namespace MWWorld { class Ptr; } namespace MWMechanics { /// AiPackage which makes an actor to cast given spell. class AiCast final : public TypedAiPackage { public: AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Cast; } MWWorld::Ptr getTarget() const override; static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mPriority = 3; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } private: const std::string mTargetId; const std::string mSpellId; bool mCasting; const bool mManual; const float mDistance; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicombat.cpp000066400000000000000000001014741445372753700234740ustar00rootroot00000000000000#include "aicombat.hpp" #include #include #include #include #include #include #include "../mwphysics/collisiontype.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" #include "character.hpp" #include "aicombataction.hpp" #include "actorutil.hpp" namespace { //chooses an attack depending on probability to avoid uniformity std::string_view chooseBestAttack(const ESM::Weapon* weapon); osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); } namespace MWMechanics { AiCombat::AiCombat(const MWWorld::Ptr& actor) { mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) { mTargetActorId = combat->mTargetActorId; } void AiCombat::init() { } /* * Current AiCombat movement states (as of 0.29.0), ignoring the details of the * attack states such as CombatMove, Strike and ReadyToAttack: * * +----(within strike range)----->attack--(beyond strike range)-->follow * | | ^ | | * | | | | | * pursue<---(beyond follow range)-----+ +----(within strike range)---+ | * ^ | * | | * +-------------------------(beyond follow range)--------------------+ * * * Below diagram is high level only, the code detail is a little different * (but including those detail will just complicate the diagram w/o adding much) * * +----------(same)-------------->attack---------(same)---------->follow * | |^^ ||| * | ||| ||| * | +--(same)-----------------+|+----------(same)------------+|| * | | | || * | | | (in range) || * | <---+ (too far) | || * pursue<-------------------------[door open]<-----+ || * ^^^ | || * ||| | || * ||+----------evade-----+ | || * || | [closed door] | || * |+----> maybe stuck, check --------------> back up, check door || * | ^ | ^ | ^ || * | | | | | | || * | | +---+ +---+ || * | +-------------------------------------------------------+| * | | * +---------------------------(same)---------------------------------+ * * FIXME: * * The new scheme is way too complicated, should really be implemented as a * proper state machine. * * TODO: * * Use the observer pattern to coordinate attacks, provide intelligence on * whether the target was hit, etc. */ bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage AiCombatStorage& storage = state.get(); //General description if (actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); if (target.isEmpty()) return true; if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager || target.getClass().getCreatureStats(target).isDead()) return true; if (actor == target) // This should never happen. return true; if (!storage.isFleeing()) { if (storage.mCurrentAction.get()) // need to wait to init action with its attack range { //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. updateLOS(actor, target, duration, storage); const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination ? storage.mAttackRange : 0.0f; const osg::Vec3f destination = storage.mUseCustomDestination ? storage.mCustomDestination : target.getRefData().getPosition().asVec3(); const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance); if (is_target_reached) storage.mReadyToAttack = true; } storage.updateCombatMove(duration); storage.mRotateMove = false; if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); if (storage.mRotateMove) return false; storage.updateAttack(actor, characterController); } else { updateFleeing(actor, target, duration, storage); } storage.mActionCooldown -= duration; if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) return false; return attack(actor, target, storage, characterController); } bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) { currentCell = actor.getCell(); } bool forceFlee = false; if (!canFight(actor, target)) { storage.stopAttack(); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted const auto& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); bool targetSidesWithPlayer = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end()); if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer) && ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId()) || (target.getClass().getCreatureStats(target).getHitAttemptActorId() == actor.getClass().getCreatureStats(actor).getActorId()))) forceFlee = true; else // Otherwise end combat return true; } const MWWorld::Class& actorClass = actor.getClass(); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; std::unique_ptr& currentAction = storage.mCurrentAction; if (!forceFlee) { if (actionCooldown > 0) return false; if (characterController.readyToPrepareAttack()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } } else { currentAction = std::make_unique(); actionCooldown = currentAction->getActionCooldown(); } if (!currentAction) return false; if (storage.isFleeing() != currentAction->isFleeing()) { if (currentAction->isFleeing()) { storage.startFleeing(); MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); return false; } else storage.stopFleeing(); } bool isRangedCombat = false; float &rangeAttack = storage.mAttackRange; rangeAttack = currentAction->getCombatRange(isRangedCombat); // Get weapon characteristics const ESM::Weapon* weapon = currentAction->getWeapon(); ESM::Position pos = actor.getRefData().getPosition(); const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); } else { osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } storage.mLastTargetPos = vTargetPos; if (storage.mReadyToAttack) { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); } // If actor uses custom destination it has to try to rebuild path because environment can change // (door is opened between actor and target) or target position has changed and current custom destination // is not good enough to attack target. if (storage.mCurrentAction->isAttackingOrSpell() && ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed()) || (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack))) { const MWBase::World* world = MWBase::Environment::get().getWorld(); // Try to build path to the target. const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); const auto pathGridGraph = getPathGridGraph(actor.getCell()); mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds, navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (!mPathFinder.isPathConstructed()) { // If there is no path, try to find a point on a line from the actor position to target projected // on navmesh to attack the target from there. const auto navigator = world->getNavigator(); const auto hit = DetourNavigator::raycast(*navigator, agentBounds, vActorPos, vTargetPos, navigatorFlags); if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) { // If the point is close enough, try to find a path to that point. mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds, navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (mPathFinder.isPathConstructed()) { // If path to that point is found use it as custom destination. storage.mCustomDestination = *hit; storage.mUseCustomDestination = true; } } if (!mPathFinder.isPathConstructed()) { storage.mUseCustomDestination = false; storage.stopAttack(); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); currentAction = std::make_unique(); actionCooldown = currentAction->getActionCooldown(); storage.startFleeing(); MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); } } else { storage.mUseCustomDestination = false; } } return false; } void MWMechanics::AiCombat::updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) { static const float LOS_UPDATE_DURATION = 0.5f; if (storage.mUpdateLOSTimer <= 0.f) { storage.mLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); storage.mUpdateLOSTimer = LOS_UPDATE_DURATION; } else storage.mUpdateLOSTimer -= duration; } void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) { static const float BLIND_RUN_DURATION = 1.0f; updateLOS(actor, target, duration, storage); AiCombatStorage::FleeState& state = storage.mFleeState; switch (state) { case AiCombatStorage::FleeState_None: return; case AiCombatStorage::FleeState_Idle: { float triggerDist = getMaxAttackDistance(target); if (storage.mLOS && (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) { const ESM::Pathgrid* pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*storage.mCell->getCell()); bool runFallback = true; if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; Misc::CoordinateConverter coords(storage.mCell->getCell()); osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); coords.toLocal(localPos); int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) { if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) { points.push_back(pathgrid->mPoints[static_cast(i)]); } } if (!points.empty()) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size(), prng)]; coords.toWorld(dest); state = AiCombatStorage::FleeState_RunToDestination; storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ); runFallback = false; } } if (runFallback) { state = AiCombatStorage::FleeState_RunBlindly; storage.mFleeBlindRunTimer = 0.0f; } } } break; case AiCombatStorage::FleeState_RunBlindly: { // timer to prevent twitchy movement that can be observed in vanilla MW if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION) { storage.mFleeBlindRunTimer += duration; storage.mMovement.mRotation[0] = -actor.getRefData().getPosition().rot[0]; storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3()); storage.mMovement.mPosition[1] = 1; updateActorsMovement(actor, duration, storage); } else state = AiCombatStorage::FleeState_Idle; } break; case AiCombatStorage::FleeState_RunToDestination: { static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fFleeDistance")->mValue.getFloat(); float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); if ((dist > fFleeDistance && !storage.mLOS) || pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration)) { state = AiCombatStorage::FleeState_Idle; } } break; }; } void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) { // apply combat movement float deltaAngle = storage.mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]; osg::Vec2f movement = Misc::rotateVec2f( osg::Vec2f(storage.mMovement.mPosition[0], storage.mMovement.mPosition[1]), -deltaAngle); MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); actorMovementSettings.mPosition[0] = movement.x(); actorMovementSettings.mPosition[1] = movement.y(); actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; rotateActorOnAxis(actor, 2, actorMovementSettings, storage); rotateActorOnAxis(actor, 0, actorMovementSettings, storage); } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage) { actorMovementSettings.mRotation[axis] = 0; bool isRangedCombat = false; storage.mCurrentAction->getCombatRange(isRangedCombat); float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f); float targetAngleRadians = storage.mMovement.mRotation[axis]; storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps); } MWWorld::Ptr AiCombat::getTarget() const { if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) { mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } return mCachedTarget; } void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const { auto combat = std::make_unique(); combat->mTargetActorId = mTargetActorId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Combat; package.mPackage = std::move(combat); sequence.mPackages.push_back(std::move(package)); } AiCombatStorage::AiCombatStorage() : mAttackCooldown(0.0f), mReaction(MWBase::Environment::get().getWorld()->getPrng()), mTimerCombatMove(0.0f), mReadyToAttack(false), mAttack(false), mAttackRange(0.0f), mCombatMove(false), mRotateMove(false), mLastTargetPos(0, 0, 0), mCell(nullptr), mCurrentAction(), mActionCooldown(0.0f), mStrength(), mForceNoShortcut(false), mShortcutFailPos(), mMovement(), mFleeState(FleeState_None), mLOS(false), mUpdateLOSTimer(0.0f), mFleeBlindRunTimer(0.0f), mUseCustomDestination(false), mCustomDestination() { } void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); // get the range of the target's weapon MWWorld::Ptr targetWeapon = MWWorld::Ptr(); const MWWorld::Class& targetClass = target.getClass(); if (targetClass.hasInventoryStore(target)) { int weapType = ESM::Weapon::None; MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType); if (weapType > ESM::Weapon::None) targetWeapon = *weaponSlot; } bool targetUsesRanged = false; float rangeAttackOfTarget = ActionWeapon(targetWeapon).getCombatRange(targetUsesRanged); if (mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); mCombatMove = true; } else if (isDistantCombat) { // Backing up behaviour // Actor backs up slightly further away than opponent's weapon range // (in vanilla - only as far as oponent's weapon range), // or not at all if opponent is using a ranged weapon if (targetUsesRanged || distToTarget > rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon return; // actor should not back up into water if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f)) return; int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; // Actor can not back up if there is no free space behind // Currently we take the 35% of actor's height from the ground as vector height. // This approach allows us to detect small obstacles (e.g. crates) and curved walls. osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); osg::Vec3f source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()); osg::Vec3f fallbackDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,-1,0); osg::Vec3f destination = source + fallbackDirection * (halfExtents.y() + 16); bool isObstacleDetected = MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask); if (isObstacleDetected) return; // Check if there is nothing behind - probably actor is near cliff. // A current approach: cast ray 1.5-yard ray down in 1.5 yard behind actor from 35% of actor's height. // If we did not hit anything, there is a cliff behind actor. source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()) + fallbackDirection * (halfExtents.y() + 96); destination = source - osg::Vec3f(0, 0, 0.75f * halfExtents.z() + 96); bool isCliffDetected = !MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask); if (isCliffDetected) return; mMovement.mPosition[1] = -1; } // dodge movements (for NPCs and bipedal creatures) // Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff else if (actor.getClass().isBipedal(actor)) { float moveDuration = 0; float angleToTarget = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]); // Apply a big side step if enemy tries to get around and come from behind. // Otherwise apply a random side step (kind of dodging) with some probability // if actor is within range of target's weapon. if (std::abs(angleToTarget) > osg::PI / 4) moveDuration = 0.2f; else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability(prng) < 0.25) moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); if (moveDuration > 0) { mMovement.mPosition[0] = Misc::Rng::rollProbability(prng) < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = moveDuration; mCombatMove = true; } } } void AiCombatStorage::updateCombatMove(float duration) { if (mCombatMove) { mTimerCombatMove -= duration; if (mTimerCombatMove <= 0) { stopCombatMove(); } } } void AiCombatStorage::stopCombatMove() { mTimerCombatMove = 0; mMovement.mPosition[1] = mMovement.mPosition[0] = 0; mCombatMove = false; } void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, const ESM::Weapon* weapon, bool distantCombat) { if (mReadyToAttack && characterController.readyToStartAttack()) { if (mAttackCooldown <= 0) { mAttack = true; // attack starts just now actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true); if (!distantCombat) characterController.setAIAttackType(chooseBestAttack(weapon)); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); mStrength = Misc::Rng::rollClosedProbability(prng); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); float baseDelay = store.get().find("fCombatDelayCreature")->mValue.getFloat(); if (actor.getClass().isNpc()) { baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); } // Say a provoking combat phrase const int iVoiceAttackOdds = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } else mAttackCooldown -= AI_REACTION_TIME; } } void AiCombatStorage::updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController) { if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) { mAttack = false; } actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); } void AiCombatStorage::stopAttack() { mMovement.mPosition[0] = 0; mMovement.mPosition[1] = 0; mMovement.mPosition[2] = 0; mReadyToAttack = false; mAttack = false; } void AiCombatStorage::startFleeing() { stopFleeing(); mFleeState = FleeState_Idle; } void AiCombatStorage::stopFleeing() { mMovement.mPosition[0] = 0; mMovement.mPosition[1] = 0; mMovement.mPosition[2] = 0; mFleeState = FleeState_None; mFleeDest = ESM::Pathgrid::Point(0, 0, 0); } bool AiCombatStorage::isFleeing() { return mFleeState != FleeState_None; } } namespace { std::string_view chooseBestAttack(const ESM::Weapon* weapon) { if (weapon != nullptr) { //the more damage attackType deals the more probability it has int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); float roll = Misc::Rng::rollClosedProbability(prng) * (slash + chop + thrust); if(roll <= slash) return "slash"; else if(roll <= (slash + thrust)) return "thrust"; else return "chop"; } return MWMechanics::CharacterController::getRandomAttackType(); } osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength) { float projSpeed; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); // get projectile speed (depending on weapon type) if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown) { static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); projSpeed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; } else if (weapType != 0) { static float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); static float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); projSpeed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; } else // weapType is 0 ==> it's a target spell projectile { projSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); } // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true); float distToTarget = vDirToTarget.length(); osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now osg::Vec3f vPerpToDir = vDirToTarget ^ osg::Vec3f(0,0,1); // cross product vPerpToDir.normalize(); osg::Vec3f vDirToTargetNormalized = vDirToTarget; vDirToTargetNormalized.normalize(); // dot product float velPerp = vTargetMoveDir * vPerpToDir; float velDir = vTargetMoveDir * vDirToTargetNormalized; // time to collision between target and projectile float t_collision; float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; if (projVelDirSquared > 0) { osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; vTargetMoveDirNormalized.normalize(); float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); } else t_collision = 0; // speed of projectile is not enough to reach moving target return vDirToTarget + vTargetMoveDir * t_collision; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicombat.hpp000066400000000000000000000077241445372753700235040ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AICOMBAT_H #define GAME_MWMECHANICS_AICOMBAT_H #include "typedaipackage.hpp" #include "aitemporarybase.hpp" #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" #include "pathfinding.hpp" #include "movement.hpp" #include "aitimer.hpp" namespace ESM { namespace AiSequence { struct AiCombat; } } namespace MWMechanics { class Action; /// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive. struct AiCombatStorage : AiTemporaryBase { float mAttackCooldown; AiReactionTimer mReaction; float mTimerCombatMove; bool mReadyToAttack; bool mAttack; float mAttackRange; bool mCombatMove; bool mRotateMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; std::unique_ptr mCurrentAction; float mActionCooldown; float mStrength; bool mForceNoShortcut; ESM::Position mShortcutFailPos; MWMechanics::Movement mMovement; enum FleeState { FleeState_None, FleeState_Idle, FleeState_RunBlindly, FleeState_RunToDestination }; FleeState mFleeState; bool mLOS; float mUpdateLOSTimer; float mFleeBlindRunTimer; ESM::Pathgrid::Point mFleeDest; bool mUseCustomDestination; osg::Vec3f mCustomDestination; AiCombatStorage(); void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, const ESM::Weapon* weapon, bool distantCombat); void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); void startFleeing(); void stopFleeing(); bool isFleeing(); }; /// \brief Causes the actor to fight another actor class AiCombat final : public TypedAiPackage { public: ///Constructor /** \param actor Actor to fight **/ explicit AiCombat(const MWWorld::Ptr& actor); explicit AiCombat (const ESM::AiSequence::AiCombat* combat); void init(); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Combat; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mPriority = 1; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } ///Returns target ID MWWorld::Ptr getTarget() const override; void writeState(ESM::AiSequence::AiSequence &sequence) const override; private: /// Returns true if combat should end bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); /// Transfer desired movement (from AiCombatStorage) to Actor void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicombataction.cpp000066400000000000000000000475411445372753700246760ustar00rootroot00000000000000#include "aicombataction.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/cellstore.hpp" #include "actorutil.hpp" #include "npcstats.hpp" #include "combat.hpp" #include "weaponpriority.hpp" #include "spellpriority.hpp" #include "weapontype.hpp" namespace MWMechanics { float suggestCombatRange(int rangeTypes) { static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); // This distance is a possible distance of melee attack static float distance = fCombatDistance * std::max(2.f, fHandToHandReach); if (rangeTypes & RangeTypes::Touch) { return fCombatDistance; } return distance * 4; } void ActionSpell::prepare(const MWWorld::Ptr &actor) { actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(mSpellId); actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Spell); if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); inv.setSelectedEnchantItem(inv.end()); } const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); MWBase::Environment::get().getWorld()->preloadEffects(&spell->mEffects); } float ActionSpell::getCombatRange (bool& isRanged) const { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); isRanged = (types & RangeTypes::Target) | (types & RangeTypes::Self); return suggestCombatRange(types); } void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) { actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(std::string()); actor.getClass().getInventoryStore(actor).setSelectedEnchantItem(mItem); actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Spell); } float ActionEnchantedItem::getCombatRange(bool& isRanged) const { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); isRanged = (types & RangeTypes::Target) | (types & RangeTypes::Self); return suggestCombatRange(types); } float ActionPotion::getCombatRange(bool& isRanged) const { // Distance doesn't matter since this action has no animation // If we want to back away slightly to avoid enemy hits, we should set isRanged to "true" return 600.f; } void ActionPotion::prepare(const MWWorld::Ptr &actor) { actor.getClass().consume(mPotion, actor); } void ActionWeapon::prepare(const MWWorld::Ptr &actor) { if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); else { MWWorld::ActionEquip equip(mWeapon); equip.execute(actor); } if (!mAmmunition.isEmpty()) { MWWorld::ActionEquip equip(mAmmunition); equip.execute(actor); } } actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Weapon); } float ActionWeapon::getCombatRange(bool& isRanged) const { isRanged = false; static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->mValue.getFloat(); if (mWeapon.isEmpty()) { static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); return fHandToHandReach * fCombatDistance; } const ESM::Weapon* weapon = mWeapon.get()->mBase; if (MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { isRanged = true; return fProjectileMaxSpeed; } else return weapon->mData.mReach * fCombatDistance; } const ESM::Weapon* ActionWeapon::getWeapon() const { if (mWeapon.isEmpty()) return nullptr; return mWeapon.get()->mBase; } std::unique_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); float bestActionRating = 0.f; float antiFleeRating = 0.f; // Default to hand-to-hand combat std::unique_ptr bestAction = std::make_unique(MWWorld::Ptr()); if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { bestAction->prepare(actor); return bestAction; } if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = ratePotion(*it, actor); if (rating > bestActionRating) { bestActionRating = rating; bestAction = std::make_unique(*it); antiFleeRating = std::numeric_limits::max(); } } for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = rateMagicItem(*it, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; bestAction = std::make_unique(it); antiFleeRating = std::numeric_limits::max(); } } MWWorld::Ptr bestArrow; float bestArrowRating = rateAmmo(actor, enemy, bestArrow, ESM::Weapon::Arrow); MWWorld::Ptr bestBolt; float bestBoltRating = rateAmmo(actor, enemy, bestBolt, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { const ESM::Weapon* weapon = it->get()->mBase; int ammotype = getWeaponType(weapon->mData.mType)->mAmmoType; MWWorld::Ptr ammo; if (ammotype == ESM::Weapon::Arrow) ammo = bestArrow; else if (ammotype == ESM::Weapon::Bolt) ammo = bestBolt; bestActionRating = rating; bestAction = std::make_unique(*it, ammo); antiFleeRating = vanillaRateWeaponAndAmmo(*it, ammo, actor, enemy); } } } for (const ESM::Spell* spell : spells) { float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; bestAction = std::make_unique(spell->mId); antiFleeRating = vanillaRateSpell(spell, actor, enemy); } } if (makeFleeDecision(actor, enemy, antiFleeRating)) bestAction = std::make_unique(); if (bestAction.get()) bestAction->prepare(actor); return bestAction; } float getBestActionRating(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); float bestActionRating = 0.f; // Default to hand-to-hand combat if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { return bestActionRating; } if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = rateMagicItem(*it, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; } } float bestArrowRating = rateAmmo(actor, enemy, ESM::Weapon::Arrow); float bestBoltRating = rateAmmo(actor, enemy, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { bestActionRating = rating; } } } for (const ESM::Spell* spell : spells) { float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; } } return bestActionRating; } float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool minusZDist) { osg::Vec3f actor1Pos = actor1.getRefData().getPosition().asVec3(); osg::Vec3f actor2Pos = actor2.getRefData().getPosition().asVec3(); float dist = (actor1Pos - actor2Pos).length(); if (minusZDist) dist -= std::abs(actor1Pos.z() - actor2Pos.z()); return (dist - MWBase::Environment::get().getWorld()->getHalfExtents(actor1).y() - MWBase::Environment::get().getWorld()->getHalfExtents(actor2).y()); } float getMaxAttackDistance(const MWWorld::Ptr& actor) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); std::string selectedSpellId = stats.getSpells().getSelectedSpell(); MWWorld::Ptr selectedEnchItem; MWWorld::Ptr activeWeapon, activeAmmo; if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); MWWorld::ContainerStoreIterator item = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon) activeWeapon = *item; item = invStore.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon) activeAmmo = *item; if (invStore.getSelectedEnchantItem() != invStore.end()) selectedEnchItem = *invStore.getSelectedEnchantItem(); } float dist = 1.0f; if (activeWeapon.isEmpty() && !selectedSpellId.empty() && !selectedEnchItem.isEmpty()) { static const float fHandToHandReach = gmst.find("fHandToHandReach")->mValue.getFloat(); dist = fHandToHandReach; } else if (stats.getDrawState() == MWMechanics::DrawState::Spell) { dist = 1.0f; if (!selectedSpellId.empty()) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(selectedSpellId); for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); dist = effect->mData.mSpeed; break; } } } else if (!selectedEnchItem.isEmpty()) { std::string enchId = selectedEnchItem.getClass().getEnchantment(selectedEnchItem); if (!enchId.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().find(enchId); for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); dist = effect->mData.mSpeed; break; } } } } static const float fTargetSpellMaxSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); dist *= std::max(1000.0f, fTargetSpellMaxSpeed); } else if (!activeWeapon.isEmpty()) { const ESM::Weapon* esmWeap = activeWeapon.get()->mBase; if (MWMechanics::getWeaponType(esmWeap->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); dist = fTargetSpellMaxSpeed; if (!activeAmmo.isEmpty()) { const ESM::Weapon* esmAmmo = activeAmmo.get()->mBase; dist *= esmAmmo->mData.mSpeed; } } else if (esmWeap->mData.mReach > 1) { dist = esmWeap->mData.mReach; } } dist = (dist > 0.f) ? dist : 1.0f; static const float fCombatDistance = gmst.find("fCombatDistance")->mValue.getFloat(); static const float fCombatDistanceWerewolfMod = gmst.find("fCombatDistanceWerewolfMod")->mValue.getFloat(); float combatDistance = fCombatDistance; if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) combatDistance *= (fCombatDistanceWerewolfMod + 1.0f); if (dist < combatDistance) dist *= combatDistance; return dist; } bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { ESM::Position actorPos = actor.getRefData().getPosition(); ESM::Position enemyPos = enemy.getRefData().getPosition(); if (isTargetMagicallyHidden(enemy) && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(enemy, actor)) { return false; } if (actor.getClass().isPureWaterCreature(actor)) { if (!MWBase::Environment::get().getWorld()->isWading(enemy)) return false; } float atDist = getMaxAttackDistance(actor); if (atDist > getDistanceMinusHalfExtents(actor, enemy) && atDist > std::abs(actorPos.pos[2] - enemyPos.pos[2])) { if (MWBase::Environment::get().getWorld()->getLOS(actor, enemy)) return true; } if (actor.getClass().isPureLandCreature(actor) && MWBase::Environment::get().getWorld()->isWalkingOnWater(enemy)) { return false; } if (actor.getClass().isPureFlyingCreature(actor) || actor.getClass().isPureLandCreature(actor)) { if (MWBase::Environment::get().getWorld()->isSwimming(enemy)) return false; } if (actor.getClass().isBipedal(actor) || !actor.getClass().canFly(actor)) { if (enemy.getClass().getCreatureStats(enemy).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) { float attackDistance = getMaxAttackDistance(actor); if ((attackDistance + actorPos.pos[2]) < enemyPos.pos[2]) { if (enemy.getCell()->isExterior()) { if (attackDistance < (enemyPos.pos[2] - MWBase::Environment::get().getWorld()->getTerrainHeightAt(enemyPos.asVec3()))) return false; } } } } if (!actor.getClass().canWalk(actor) && !actor.getClass().isBipedal(actor)) return true; if (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) return true; if (MWBase::Environment::get().getWorld()->isSwimming(actor)) return true; if (getDistanceMinusHalfExtents(actor, enemy, true) <= 0.0f) return false; return true; } float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); const int flee = stats.getAiSetting(AiSetting::Flee).getModified(); if (flee >= 100) return flee; static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat(); static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat(); float healthPercentage = stats.getHealth().getRatio(false); float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger(); if (actor.getClass().isNpc() && enemy.getClass().isNpc()) { if (enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack) { static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->mValue.getInteger(); rating = iWereWolfFleeMod; } } if (rating != 0.0f) rating += getFightDistanceBias(actor, enemy); return rating; } bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating) { float fleeRating = vanillaRateFlee(actor, enemy); if (fleeRating < 100.0f) fleeRating = 0.0f; if (fleeRating > antiFleeRating) return true; // Run away after summoning a creature if we have nothing to use but fists. if (antiFleeRating == 0.0f && !actor.getClass().getCreatureStats(actor).getSummonedCreatureMap().empty()) return true; return false; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aicombataction.hpp000066400000000000000000000071531445372753700246760ustar00rootroot00000000000000#ifndef OPENMW_AICOMBAT_ACTION_H #define OPENMW_AICOMBAT_ACTION_H #include #include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" namespace MWMechanics { class Action { public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; virtual float getCombatRange (bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return nullptr; } virtual bool isAttackingOrSpell() const { return true; } virtual bool isFleeing() const { return false; } }; class ActionFlee : public Action { public: ActionFlee() {} void prepare(const MWWorld::Ptr& actor) override {} float getCombatRange (bool& isRanged) const override { return 0.0f; } float getActionCooldown() override { return 3.0f; } bool isAttackingOrSpell() const override { return false; } bool isFleeing() const override { return true; } }; class ActionSpell : public Action { public: ActionSpell(const std::string& spellId) : mSpellId(spellId) {} std::string mSpellId; /// Sets the given spell as selected on the actor's spell list. void prepare(const MWWorld::Ptr& actor) override; float getCombatRange (bool& isRanged) const override; }; class ActionEnchantedItem : public Action { public: ActionEnchantedItem(const MWWorld::ContainerStoreIterator& item) : mItem(item) {} MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. void prepare(const MWWorld::Ptr& actor) override; float getCombatRange (bool& isRanged) const override; /// Since this action has no animation, apply a small cool down for using it float getActionCooldown() override { return 0.75f; } }; class ActionPotion : public Action { public: ActionPotion(const MWWorld::Ptr& potion) : mPotion(potion) {} MWWorld::Ptr mPotion; /// Drinks the given potion. void prepare(const MWWorld::Ptr& actor) override; float getCombatRange (bool& isRanged) const override; bool isAttackingOrSpell() const override { return false; } /// Since this action has no animation, apply a small cool down for using it float getActionCooldown() override { return 0.75f; } }; class ActionWeapon : public Action { private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; public: /// \a weapon may be empty for hand-to-hand combat ActionWeapon(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo = MWWorld::Ptr()) : mAmmunition(ammo), mWeapon(weapon) {} /// Equips the given weapon. void prepare(const MWWorld::Ptr& actor) override; float getCombatRange (bool& isRanged) const override; const ESM::Weapon* getWeapon() const override; }; std::unique_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float getBestActionRating(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy); float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist=false); float getMaxAttackDistance(const MWWorld::Ptr& actor); bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiescort.cpp000066400000000000000000000120141445372753700235150ustar00rootroot00000000000000#include "aiescort.hpp" #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "movement.hpp" /* TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. TODO: Take account for actors being in different cells. */ namespace MWMechanics { AiEscort::AiEscort(std::string_view actorId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = std::string(actorId); } AiEscort::AiEscort(std::string_view actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = std::string(actorId); } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) : TypedAiPackage(escort->mRepeat), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) , mDuration(escort->mData.mDuration) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = escort->mTargetId; mTargetActorId = escort->mTargetActorId; } bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. if (mDuration > 0) { mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); if (mRemainingDuration <= 0) { mRemainingDuration = mDuration; return true; } } if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) { const osg::Vec3f dest(mX, mY, mZ); if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete { mRemainingDuration = mDuration; return true; } mMaxDist = maxHalfExtent + 450.0f; } else { // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = maxHalfExtent + 250.0f; } return false; } void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { auto escort = std::make_unique(); escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; escort->mData.mDuration = mDuration; escort->mTargetId = mTargetActorRefId; escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; escort->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Escort; package.mPackage = std::move(escort); sequence.mPackages.push_back(std::move(package)); } void AiEscort::fastForward(const MWWorld::Ptr& actor, AiState &state) { // Update duration counter if this package has a duration if (mDuration > 0) mRemainingDuration--; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiescort.hpp000066400000000000000000000043111445372753700235230ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIESCORT_H #define GAME_MWMECHANICS_AIESCORT_H #include "typedaipackage.hpp" #include #include namespace ESM { namespace AiSequence { struct AiEscort; } } namespace MWMechanics { /// \brief AI Package to have an NPC lead the player to a specific point class AiEscort final : public TypedAiPackage { public: /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ AiEscort(std::string_view actorId, int duration, float x, float y, float z, bool repeat); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ AiEscort(std::string_view actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat); AiEscort(const ESM::AiSequence::AiEscort* escort); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Escort; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mUseVariableSpeed = true; options.mSideWithTarget = true; return options; } void writeState(ESM::AiSequence::AiSequence &sequence) const override; void fastForward(const MWWorld::Ptr& actor, AiState& state) override; osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } private: const std::string mCellId; const float mX; const float mY; const float mZ; float mMaxDist = 450; const float mDuration; // In hours float mRemainingDuration; // In hours const int mCellX; const int mCellY; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiface.cpp000066400000000000000000000010411445372753700231120ustar00rootroot00000000000000#include "aiface.hpp" #include "../mwworld/ptr.hpp" #include "steering.hpp" MWMechanics::AiFace::AiFace(float targetX, float targetY) : mTargetX(targetX), mTargetY(targetY) { } bool MWMechanics::AiFace::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& /*characterController*/, MWMechanics::AiState& /*state*/, float /*duration*/) { osg::Vec3f dir = osg::Vec3f(mTargetX, mTargetY, 0) - actor.getRefData().getPosition().asVec3(); return zTurn(actor, std::atan2(dir.x(), dir.y()), osg::DegreesToRadians(3.f)); } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiface.hpp000066400000000000000000000016641445372753700231320ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIFACE_H #define GAME_MWMECHANICS_AIFACE_H #include "typedaipackage.hpp" namespace MWMechanics { /// AiPackage which makes an actor face a certain direction. class AiFace final : public TypedAiPackage { public: AiFace(float targetX, float targetY); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Face; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mPriority = 2; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } private: const float mTargetX; const float mTargetY; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aifollow.cpp000066400000000000000000000200321445372753700235170ustar00rootroot00000000000000#include "aifollow.hpp" #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" namespace { osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) { if(actor.getClass().isNpc()) return 64; return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); } } namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; AiFollow::AiFollow(std::string_view actorId, float duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = std::string(actorId); } AiFollow::AiFollow(std::string_view actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = std::string(actorId); } AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) , mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actor.getCellRef().getRefId(); mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) , mAlwaysFollow(follow->mAlwaysFollow) , mDuration(follow->mData.mDuration) , mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = follow->mTargetId; mTargetActorId = follow->mTargetActorId; } bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { const MWWorld::Ptr target = getTarget(); // Target is not here right now, wait for it to return // Really we should be checking whether the target is currently registered with the MechanicsManager if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) return false; actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); AiFollowStorage& storage = state.get(); bool& rotate = storage.mTurnActorToTarget; if (rotate) { if (zTurn(actor, storage.mTargetAngleRadians)) rotate = false; return false; } const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f targetDir = targetPos - actorPos; // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) { storage.mTimer -= duration; if (storage.mTimer < 0) { if (targetDir.length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) mActive = true; storage.mTimer = 0.5f; } } if (!mActive) return false; // In the original engine the first follower stays closer to the player than any subsequent followers. // Followers beyond the first usually attempt to stand inside each other. osg::Vec3f::value_type floatingDistance = 0; auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) { for(auto& follower : followers) { auto halfExtent = getHalfExtents(follower.second); if(halfExtent > floatingDistance) floatingDistance = halfExtent; } floatingDistance += 128; } floatingDistance += getHalfExtents(target) + 64; floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time if (mDuration > 0) { mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); if (mRemainingDuration <= 0) { mRemainingDuration = mDuration; return true; } } osg::Vec3f finalPos(mX, mY, mZ); if ((actorPos-finalPos).length2() < followDistance*followDistance) //Close-ish to final position { if (actor.getCell()->isExterior()) //Outside? { if (mCellId == "") //No cell to travel to { mRemainingDuration = mDuration; return true; } } else if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to { mRemainingDuration = mDuration; return true; } } } short baseFollowDistance = followDistance; short threshold = 30; // to avoid constant switching between moving/stopping if (storage.mMoving) followDistance -= threshold; else followDistance += threshold; if (targetDir.length2() <= followDistance * followDistance) { float faceAngleRadians = std::atan2(targetDir.x(), targetDir.y()); if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f))) { storage.mTargetAngleRadians = faceAngleRadians; storage.mTurnActorToTarget = true; } return false; } storage.mMoving = !pathTo(actor, targetPos, duration, baseFollowDistance); // Go to the destination if (storage.mMoving) { //Check if you're far away if (targetDir.length2() > 450 * 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run else if (targetDir.length2() < 325 * 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk } return false; } std::string AiFollow::getFollowedActor() { return mTargetActorRefId; } bool AiFollow::isCommanded() const { return !mOptions.mShouldCancelPreviousAi; } void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { auto follow = std::make_unique(); follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; follow->mData.mDuration = mDuration; follow->mTargetId = mTargetActorRefId; follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = isCommanded(); follow->mActive = mActive; follow->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; package.mPackage = std::move(follow); sequence.mPackages.push_back(std::move(package)); } int AiFollow::getFollowIndex() const { return mFollowIndex; } void AiFollow::fastForward(const MWWorld::Ptr& actor, AiState &state) { // Update duration counter if this package has a duration if (mDuration > 0) mRemainingDuration--; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aifollow.hpp000066400000000000000000000063521445372753700235350ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIFOLLOW_H #define GAME_MWMECHANICS_AIFOLLOW_H #include "typedaipackage.hpp" #include "aitemporarybase.hpp" #include #include #include #include "../mwworld/ptr.hpp" namespace ESM::AiSequence { struct AiFollow; } namespace MWMechanics { struct AiFollowStorage : AiTemporaryBase { float mTimer; bool mMoving; float mTargetAngleRadians; bool mTurnActorToTarget; AiFollowStorage() : mTimer(0.f), mMoving(false), mTargetAngleRadians(0.f), mTurnActorToTarget(false) {} }; /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely **/ class AiFollow final : public TypedAiPackage { public: /// Follow Actor for duration or until you arrive at a world position AiFollow(std::string_view actorId, float duration, float x, float y, float z, bool repeat); /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(std::string_view actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat); /// Follow Actor indefinitively AiFollow(const MWWorld::Ptr& actor, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Follow; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mUseVariableSpeed = true; options.mSideWithTarget = true; options.mFollowTargetThroughDoors = true; return options; } /// Returns the actor being followed std::string getFollowedActor(); void writeState (ESM::AiSequence::AiSequence& sequence) const override; bool isCommanded() const; int getFollowIndex() const; void fastForward(const MWWorld::Ptr& actor, AiState& state) override; osg::Vec3f getDestination() const override { MWWorld::Ptr target = getTarget(); if (target.isEmpty()) return osg::Vec3f(0, 0, 0); return target.getRefData().getPosition().asVec3(); } private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ const bool mAlwaysFollow; const float mDuration; // Hours float mRemainingDuration; // Hours const float mX; const float mY; const float mZ; const std::string mCellId; bool mActive; // have we spotted the target? const int mFollowIndex; static int mFollowIndexCounter; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aipackage.cpp000066400000000000000000000453751445372753700236310ustar00rootroot00000000000000#include "aipackage.hpp" #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/inventorystore.hpp" #include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "steering.hpp" #include "actorutil.hpp" #include namespace { float divOrMax(float dividend, float divisor) { return divisor == 0 ? std::numeric_limits::max() * std::numeric_limits::epsilon() : dividend / divisor; } float getPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) { const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance); } bool canOpenDoors(const MWWorld::Ptr& ptr) { return ptr.getClass().isBipedal(ptr) || ptr.getClass().hasInventoryStore(ptr); } } MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), mOptions(options), mReaction(MWBase::Environment::get().getWorld()->getPrng()), mTargetActorRefId(""), mTargetActorId(-1), mCachedTarget(), mRotateOnTheRunChecks(0), mIsShortcutting(false), mShortcutProhibited(false), mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { if (!mCachedTarget.isEmpty()) { if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; } if (mTargetActorId == -2) return MWWorld::Ptr(); if (mTargetActorId == -1) { if (mTargetActorRefId.empty()) { mTargetActorId = -2; return MWWorld::Ptr(); } mCachedTarget = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); if (mCachedTarget.isEmpty()) { mTargetActorId = -2; return mCachedTarget; } else mTargetActorId = mCachedTarget.getClass().getCreatureStats(mCachedTarget).getActorId(); } if (mTargetActorId != -1) mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); else return MWWorld::Ptr(); return mCachedTarget; } void MWMechanics::AiPackage::reset() { // reset all members mReaction.reset(); mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); mCachedTarget = MWWorld::Ptr(); mPathFinder.clearPath(); mObstacleCheck.clear(); } bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance, float endTolerance, PathType pathType) { const Misc::TimerStatus timerStatus = mReaction.update(duration); const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor MWBase::World* world = MWBase::Environment::get().getWorld(); const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor); /// Stops the actor when it gets too close to a unloaded cell //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. if (isNearInactiveCell(position)) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; world->updateActorPath(actor, mPathFinder.getPath(), agentBounds, position, dest); return false; } mLastDestinationTolerance = destTolerance; const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); const bool actorCanMoveByZ = canActorMoveByZAxis(actor); if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { if (canOpenDoors(actor)) openDoors(actor); const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. mIsShortcutting = actorCanMoveByZ && shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path { mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, pathType); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity if (destInLOS && mPathFinder.getPath().size() > 1) { // get point just before dest auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1; // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target if (distance(position, dest) <= distance(dest, *pPointBeforeDest)) { mPathFinder.clearPath(); mPathFinder.addPointToPath(dest); } } } if (!mPathFinder.getPath().empty()) //Path has points in it { const osg::Vec3f& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path if(distance(dest, lastPos) > 100) //End of the path is far from the destination mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } } } const float pointTolerance = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, world->getHalfExtents(actor)); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ, agentBounds, getNavigatorFlags(actor)); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { // turn to destination point zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); return true; } else if (mPathFinder.getPath().empty()) return false; world->updateActorPath(actor, mPathFinder.getPath(), agentBounds, position, dest); if (mRotateOnTheRunChecks == 0 || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point { actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; } // turn to next path point by X,Z axes float zAngleToNext = mPathFinder.getZAngleToNext(position.x(), position.y()); zTurn(actor, zAngleToNext); smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0); const auto destination = getNextPathPoint(dest); mObstacleCheck.update(actor, destination, duration); if (smoothMovement) { const float smoothTurnReservedDist = 150; auto& movement = actor.getClass().getMovementSettings(actor); float distToNextSqr = osg::Vec2f(destination.x() - position.x(), destination.y() - position.y()).length2(); float diffAngle = zAngleToNext - actor.getRefData().getPosition().rot[2]; if (std::cos(diffAngle) < -0.1) movement.mPosition[0] = movement.mPosition[1] = 0; else if (distToNextSqr > smoothTurnReservedDist * smoothTurnReservedDist) { // Go forward (and slowly turn towards the next path point) movement.mPosition[0] = 0; movement.mPosition[1] = 1; } else { // Next path point is near, so use diagonal movement to follow the path precisely. movement.mPosition[0] = std::sin(diffAngle); movement.mPosition[1] = std::max(std::cos(diffAngle), 0.f); } } // handle obstacles on the way evadeObstacles(actor); return false; } void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) { // check if stuck due to obstacles if (!mObstacleCheck.isEvading()) return; // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); if (!door.isEmpty() && canOpenDoors(actor)) { openDoors(actor); } else { mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(actor)); } } namespace { bool isDoorOnTheWay(const MWWorld::Ptr& actor, const MWWorld::Ptr& door, const osg::Vec3f& nextPathPoint) { const auto world = MWBase::Environment::get().getWorld(); const auto halfExtents = world->getHalfExtents(actor); const auto position = actor.getRefData().getPosition().asVec3() + osg::Vec3f(0, 0, halfExtents.z()); const auto destination = nextPathPoint + osg::Vec3f(0, 0, halfExtents.z()); return world->hasCollisionWithDoor(door, position, destination); } } void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) { // note: AiWander currently does not open doors if (getTypeId() == AiPackageTypeId::Wander) return; if (mPathFinder.getPathSize() == 0) return; MWBase::World* world = MWBase::Environment::get().getWorld(); static float distance = world->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); if (door == MWWorld::Ptr()) return; if (!door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == MWWorld::DoorState::Idle) { if (!isDoorOnTheWay(actor, door, mPathFinder.getPath().front())) return; if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) { world->activate(door, actor); return; } const std::string keyId = door.getCellRef().getKey(); if (keyId.empty()) return; MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); MWWorld::Ptr keyPtr = invStore.search(keyId); if (!keyPtr.isEmpty()) world->activate(door, actor); } } const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) { const ESM::CellId& id = cell->getCell()->getCellId(); // static cache is OK for now, pathgrids can never change during runtime typedef std::map > CacheMap; static CacheMap cache; CacheMap::iterator found = cache.find(id); if (found == cache.end()) { cache.insert(std::make_pair(id, std::make_unique(MWMechanics::PathgridGraph(cell)))); } return *cache[id].get(); } bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) { if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( startPoint.x(), startPoint.y(), startPoint.z(), endPoint.x(), endPoint.y(), endPoint.z()); if (destInLOS != nullptr) *destInLOS = isPathClear; if (!isPathClear) return false; // check if an actor can move along the shortcut path isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); } if (isPathClear) // can shortcut the path { mPathFinder.clearPath(); mPathFinder.addPointToPath(endPoint); return true; } return false; } bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor) { if (canActorMoveByZAxis(actor)) return true; const float actorSpeed = actor.getClass().getMaxSpeed(actor); const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; // update shortcut prohibit state if (checkWayIsClear(startPoint, endPoint, offsetXY)) { if (mShortcutProhibited) { mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); } return true; } else { if (mShortcutFailPos == osg::Vec3f()) { mShortcutProhibited = true; mShortcutFailPos = startPoint; } } return false; } bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const { return mPathFinder.getPath().empty() || getPathDistance(actor, mPathFinder.getPath().back(), newDest) > 10 || mPathFinder.getPathCell() != actor.getCell(); } bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) { const ESM::Cell* playerCell(getPlayer().getCell()->getCell()); if (playerCell->isExterior()) { // get actor's distance from origin of center cell Misc::CoordinateConverter(playerCell).toLocal(position); // currently assumes 3 x 3 grid for exterior cells, with player at center cell. // AI shuts down actors before they reach edges of 3 x 3 grid. const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; return (position.x() < minThreshold) || (maxThreshold < position.x()) || (position.y() < minThreshold) || (maxThreshold < position.y()); } else { return false; } } bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest) { // get actor's shortest radius for moving in circle float speed = actor.getClass().getMaxSpeed(actor); speed += speed * 0.1f; // 10% real speed inaccuracy float radius = speed / getAngularVelocity(speed); // get radius direction to the center const float* rot = actor.getRefData().getPosition().rot; osg::Quat quatRot(rot[0], -osg::X_AXIS, rot[1], -osg::Y_AXIS, rot[2], -osg::Z_AXIS); osg::Vec3f dir = quatRot * osg::Y_AXIS; // actor's orientation direction is a tangent to circle osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent radiusDir.normalize(); radiusDir *= radius; // pick up the nearest center candidate osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); osg::Vec3f center1 = pos - radiusDir; osg::Vec3f center2 = pos + radiusDir; osg::Vec3f center = (center1 - dest).length2() < (center2 - dest).length2() ? center1 : center2; float distToDest = (center - dest).length(); // if pathpoint is reachable for the actor rotating on the run: // no points of actor's circle should be farther from the center than destination point return (radius <= distToDest); } DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const { static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game"); const MWWorld::Class& actorClass = actor.getClass(); DetourNavigator::Flags result = DetourNavigator::Flag_none; if ((actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) || actorClass.canSwim(actor) || hasWaterWalking(actor))) ) && actorClass.getSwimSpeed(actor) > 0) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) { result |= DetourNavigator::Flag_walk; if (getTypeId() == AiPackageTypeId::Travel) result |= DetourNavigator::Flag_usePathgrid; } if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; return result; } DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::Ptr& actor) const { DetourNavigator::AreaCosts costs; const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 ? 0.0f : actorClass.getSwimSpeed(actor); const float walkSpeed = [&] { if ((flags & DetourNavigator::Flag_walk) == 0) return 0.0f; if (getTypeId() == AiPackageTypeId::Wander) return actorClass.getWalkSpeed(actor); return actorClass.getRunSpeed(actor); } (); const float maxSpeed = std::max(swimSpeed, walkSpeed); if (maxSpeed == 0) return costs; const float swimFactor = swimSpeed / maxSpeed; const float walkFactor = walkSpeed / maxSpeed; costs.mWater = divOrMax(costs.mWater, swimFactor); costs.mDoor = divOrMax(costs.mDoor, walkFactor); costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor); costs.mGround = divOrMax(costs.mGround, walkFactor); return costs; } osg::Vec3f MWMechanics::AiPackage::getNextPathPoint(const osg::Vec3f& destination) const { return mPathFinder.getPath().empty() ? destination : mPathFinder.getPath().front(); } float MWMechanics::AiPackage::getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const { if (mPathFinder.getPathSize() <= 1) return std::max(DEFAULT_TOLERANCE, mLastDestinationTolerance); return getPointTolerance(speed, duration, halfExtents); } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aipackage.hpp000066400000000000000000000160041445372753700236210ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H #include #include #include "pathfinding.hpp" #include "obstacle.hpp" #include "aipackagetypeid.hpp" #include "aitimer.hpp" #include "aistatefwd.hpp" #include "../mwworld/ptr.hpp" namespace ESM { struct Cell; namespace AiSequence { struct AiSequence; } } namespace MWMechanics { class CharacterController; class PathgridGraph; /// \brief Base class for AI packages class AiPackage { public: struct Options { unsigned int mPriority = 0; bool mUseVariableSpeed = false; bool mSideWithTarget = false; bool mFollowTargetThroughDoors = false; bool mCanCancel = true; bool mShouldCancelPreviousAi = true; bool mRepeat = false; bool mAlwaysActive = false; constexpr Options withRepeat(bool value) { mRepeat = value; return *this; } constexpr Options withShouldCancelPreviousAi(bool value) { mShouldCancelPreviousAi = value; return *this; } }; AiPackage(AiPackageTypeId typeId, const Options& options); virtual ~AiPackage() = default; static constexpr Options makeDefaultOptions() { return Options{}; } ///Clones the package virtual std::unique_ptr clone() const = 0; /// Updates and runs the package (Should run every frame) /// \return Package completed? virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) = 0; /// Returns the TypeID of the AiPackage /// \see enum TypeId AiPackageTypeId getTypeId() const { return mTypeId; } /// Higher number is higher priority (0 being the lowest) unsigned int getPriority() const { return mOptions.mPriority; } /// Check if package use movement with variable speed bool useVariableSpeed() const { return mOptions.mUseVariableSpeed; } virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} /// Simulates the passing of time virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) virtual MWWorld::Ptr getTarget() const; /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); }; /// Return true if having this AiPackage makes the actor side with the target in fights (default false) bool sideWithTarget() const { return mOptions.mSideWithTarget; } /// Return true if the actor should follow the target through teleport doors (default false) bool followTargetThroughDoors() const { return mOptions.mFollowTargetThroughDoors; } /// Can this Ai package be canceled? (default true) bool canCancel() const { return mOptions.mCanCancel; } /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } /// Return true if this package should repeat. bool getRepeat() const { return mOptions.mRepeat; } virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } /// Return true if any loaded actor with this AI package must be active. bool alwaysActive() const { return mOptions.mAlwaysActive; } /// Reset pathfinding state void reset(); /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const; float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const; protected: /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f, float endTolerance = 0.0f, PathType pathType = PathType::Full); /// Check if there aren't any obstacles along the path to make shortcut possible /// If a shortcut is possible then path will be cleared and filled with the destination point. /// \param destInLOS If not nullptr function will return ray cast check result /// \return If can shortcut the path bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor); bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const; void evadeObstacles(const MWWorld::Ptr& actor); void openDoors(const MWWorld::Ptr& actor); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; const AiPackageTypeId mTypeId; const Options mOptions; // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; AiReactionTimer mReaction; std::string mTargetActorRefId; mutable int mTargetActorId; mutable MWWorld::Ptr mCachedTarget; short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt osg::Vec3f mShortcutFailPos; // position of last shortcut fail float mLastDestinationTolerance = 0; private: bool isNearInactiveCell(osg::Vec3f position); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aipackagetypeid.hpp000066400000000000000000000012161445372753700250370ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIPACKAGETYPEID_H #define GAME_MWMECHANICS_AIPACKAGETYPEID_H namespace MWMechanics { ///Enumerates the various AITypes available enum class AiPackageTypeId { None = -1, Wander = 0, Travel = 1, Escort = 2, Follow = 3, Activate = 4, // These 5 are not really handled as Ai Packages in the MW engine // For compatibility do *not* return these in the getCurrentAiPackage script function.. Combat = 5, Pursue = 6, AvoidDoor = 7, Face = 8, Breathe = 9, InternalTravel = 10, Cast = 11 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aipursue.cpp000066400000000000000000000062271445372753700235520ustar00rootroot00000000000000#include "aipursue.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "movement.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" namespace MWMechanics { AiPursue::AiPursue(const MWWorld::Ptr& actor) { mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) { mTargetActorId = pursue->mTargetActorId; } bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { if(actor.getClass().getCreatureStats(actor).isDead()) return true; const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) return true; if (isTargetMagicallyHidden(target) && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor)) return false; if (target.getClass().getCreatureStats(target).isDead()) return true; actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); //Set the target destination const osg::Vec3f dest = target.getRefData().getPosition().asVec3(); const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); const float pathTolerance = 100.f; // check the true distance in case the target is far away in Z-direction bool reached = pathTo(actor, dest, duration, pathTolerance, (actorPos - dest).length(), PathType::Partial) && std::abs(dest.z() - actorPos.z()) < pathTolerance; if (reached) { if (!MWBase::Environment::get().getWorld()->getLOS(target, actor)) return false; MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, actor); //Arrest player when reached return true; } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run return false; } MWWorld::Ptr AiPursue::getTarget() const { if (!mCachedTarget.isEmpty()) { if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; } mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); return mCachedTarget; } void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const { auto pursue = std::make_unique(); pursue->mTargetActorId = mTargetActorId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Pursue; package.mPackage = std::move(pursue); sequence.mPackages.push_back(std::move(package)); } } // namespace MWMechanics openmw-openmw-0.48.0/apps/openmw/mwmechanics/aipursue.hpp000066400000000000000000000026421445372753700235540ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIPURSUE_H #define GAME_MWMECHANICS_AIPURSUE_H #include "typedaipackage.hpp" namespace ESM { namespace AiSequence { struct AiPursue; } } namespace MWMechanics { /// \brief Makes the actor very closely follow the actor /** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the path is completed). **/ class AiPursue final : public TypedAiPackage { public: ///Constructor /** \param actor Actor to pursue **/ AiPursue(const MWWorld::Ptr& actor); AiPursue(const ESM::AiSequence::AiPursue* pursue); bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Pursue; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mCanCancel = false; options.mShouldCancelPreviousAi = false; return options; } MWWorld::Ptr getTarget() const override; void writeState (ESM::AiSequence::AiSequence& sequence) const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aisequence.cpp000066400000000000000000000400731445372753700240340ustar00rootroot00000000000000#include "aisequence.hpp" #include #include #include #include #include "aipackage.hpp" #include "aistate.hpp" #include "aiwander.hpp" #include "aiescort.hpp" #include "aitravel.hpp" #include "aifollow.hpp" #include "aiactivate.hpp" #include "aicombat.hpp" #include "aicombataction.hpp" #include "aipursue.hpp" #include "actorutil.hpp" #include "../mwworld/class.hpp" namespace MWMechanics { void AiSequence::copy (const AiSequence& sequence) { for (const auto& package : sequence.mPackages) mPackages.push_back(package->clone()); // We need to keep an AiWander storage, if present - it has a state machine. // Not sure about another temporary storages sequence.mAiState.copy(mAiState); mNumCombatPackages = sequence.mNumCombatPackages; mNumPursuitPackages = sequence.mNumPursuitPackages; } AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; } AiSequence& AiSequence::operator= (const AiSequence& sequence) { if (this!=&sequence) { clear(); copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; } return *this; } AiSequence::~AiSequence() { clear(); } void AiSequence::onPackageAdded(const AiPackage& package) { if (package.getTypeId() == AiPackageTypeId::Combat) mNumCombatPackages++; else if (package.getTypeId() == AiPackageTypeId::Pursue) mNumPursuitPackages++; assert(mNumCombatPackages >= 0); assert(mNumPursuitPackages >= 0); } void AiSequence::onPackageRemoved(const AiPackage& package) { if (package.getTypeId() == AiPackageTypeId::Combat) mNumCombatPackages--; else if (package.getTypeId() == AiPackageTypeId::Pursue) mNumPursuitPackages--; assert(mNumCombatPackages >= 0); assert(mNumPursuitPackages >= 0); } AiPackageTypeId AiSequence::getTypeId() const { if (mPackages.empty()) return AiPackageTypeId::None; return mPackages.front()->getTypeId(); } bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { if (getTypeId() != AiPackageTypeId::Combat) return false; targetActor = mPackages.front()->getTarget(); return !targetActor.isEmpty(); } bool AiSequence::getCombatTargets(std::vector &targetActors) const { for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Combat) targetActors.push_back((*it)->getTarget()); } return !targetActors.empty(); } AiPackages::iterator AiSequence::erase(AiPackages::iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? auto& ptr = *package; onPackageRemoved(*ptr); return mPackages.erase(package); } bool AiSequence::isInCombat() const { return mNumCombatPackages > 0; } bool AiSequence::isInPursuit() const { return mNumPursuitPackages > 0; } bool AiSequence::isEngagedWithActor() const { if (!isInCombat()) return false; for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) { MWWorld::Ptr target2 = (*it)->getTarget(); if (!target2.isEmpty() && target2.getClass().isNpc()) return true; } } return false; } bool AiSequence::hasPackage(AiPackageTypeId typeId) const { auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package) { return package->getTypeId() == typeId; }); return it != mPackages.end(); } bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { if (!isInCombat()) return false; for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) { if ((*it)->getTarget() == actor) return true; } } return false; } void AiSequence::removePackagesById(AiPackageTypeId id) { for (auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == id) { it = erase(it); } else ++it; } } void AiSequence::stopCombat() { removePackagesById(AiPackageTypeId::Combat); } void AiSequence::stopCombat(const std::vector& targets) { for(auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) { it = erase(it); } else ++it; } } void AiSequence::stopPursuit() { removePackagesById(AiPackageTypeId::Pursue); } bool AiSequence::isPackageDone() const { return mDone; } namespace { bool isActualAiPackage(AiPackageTypeId packageTypeId) { return (packageTypeId >= AiPackageTypeId::Wander && packageTypeId <= AiPackageTypeId::Activate); } } void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) { if (actor == getPlayer()) { // Players don't use this. return; } if (mPackages.empty()) { mLastAiPackage = AiPackageTypeId::None; return; } auto* package = mPackages.front().get(); if (!package->alwaysActive() && outOfRange) return; auto packageTypeId = package->getTypeId(); // workaround ai packages not being handled as in the vanilla engine if (isActualAiPackage(packageTypeId)) mLastAiPackage = packageTypeId; // if active package is combat one, choose nearest target if (packageTypeId == AiPackageTypeId::Combat) { auto itActualCombat = mPackages.end(); float nearestDist = std::numeric_limits::max(); osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); float bestRating = 0.f; for (auto it = mPackages.begin(); it != mPackages.end();) { if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; MWWorld::Ptr target = (*it)->getTarget(); // target disappeared (e.g. summoned creatures) if (target.isEmpty()) { it = erase(it); } else { float rating = MWMechanics::getBestActionRating(actor, target); const ESM::Position &targetPos = target.getRefData().getPosition(); float distTo = (targetPos.asVec3() - vActorPos).length2(); // Small threshold for changing target if (it == mPackages.begin()) distTo = std::max(0.f, distTo - 2500.f); // if a target has higher priority than current target or has same priority but closer if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) { nearestDist = distTo; itActualCombat = it; bestRating = rating; } ++it; } } if (mPackages.empty()) return; if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) { assert(itActualCombat != mPackages.end()); // move combat package with nearest target to the front std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } package = mPackages.front().get(); packageTypeId = package->getTypeId(); } try { if (package->execute(actor, characterController, mAiState, duration)) { // Put repeating non-combat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); } // The active package is typically the first entry, this is however not always the case // e.g. AiPursue executing a dialogue script that uses startCombat adds a combat package to the front // due to the priority. auto activePackageIt = std::find_if(mPackages.begin(), mPackages.end(), [&](auto& entry) { return entry.get() == package; }); erase(activePackageIt); if (isActualAiPackage(packageTypeId)) mDone = true; } else { mDone = false; } } catch (std::exception& e) { Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); } } void AiSequence::clear() { mPackages.clear(); mNumCombatPackages = 0; mNumPursuitPackages = 0; } void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) { if (actor == getPlayer()) throw std::runtime_error("Can't add AI packages to player"); // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) stopCombat(); // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. // Also there is no point to stack return packages. const auto currentTypeId = getTypeId(); const auto newTypeId = package.getTypeId(); if (currentTypeId <= MWMechanics::AiPackageTypeId::Wander && !hasPackage(MWMechanics::AiPackageTypeId::InternalTravel) && (newTypeId <= MWMechanics::AiPackageTypeId::Combat || newTypeId == MWMechanics::AiPackageTypeId::Pursue || newTypeId == MWMechanics::AiPackageTypeId::Cast)) { osg::Vec3f dest; if (currentTypeId == MWMechanics::AiPackageTypeId::Wander) { dest = getActivePackage().getDestination(actor); } else { dest = actor.getRefData().getPosition().asVec3(); } MWMechanics::AiInternalTravel travelPackage(dest.x(), dest.y(), dest.z()); stack(travelPackage, actor, false); } // remove previous packages if required if (cancelOther && package.shouldCancelPreviousAi()) { for (auto it = mPackages.begin(); it != mPackages.end();) { if((*it)->canCancel()) { it = erase(it); } else ++it; } } // insert new package in correct place depending on priority for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { // We should override current AiCast package, if we try to add a new one. if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast && package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) { *it = package.clone(); return; } if((*it)->getPriority() <= package.getPriority()) { onPackageAdded(package); mPackages.insert(it, package.clone()); return; } } onPackageAdded(package); mPackages.push_back(package.clone()); // Make sure that temporary storage is empty if (cancelOther) { mAiState.moveIn(std::make_unique()); mAiState.moveIn(std::make_unique()); mAiState.moveIn(std::make_unique()); } } bool MWMechanics::AiSequence::isEmpty() const { return mPackages.empty(); } const AiPackage& MWMechanics::AiSequence::getActivePackage() const { if(mPackages.empty()) throw std::runtime_error(std::string("No AI Package!")); return *mPackages.front(); } void AiSequence::fill(const ESM::AIPackageList &list) { for (const auto& esmPackage : list.mList) { std::unique_ptr package; if (esmPackage.mType == ESM::AI_Wander) { ESM::AIWander data = esmPackage.mWander; std::vector idles; idles.reserve(8); for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); package = std::make_unique(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Escort) { ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Travel) { ESM::AITravel data = esmPackage.mTravel; package = std::make_unique(data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Activate) { ESM::AIActivate data = esmPackage.mActivate; package = std::make_unique(data.mName.toStringView(), data.mShouldRepeat != 0); } else //if (esmPackage.mType == ESM::AI_Follow) { ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } onPackageAdded(*package); mPackages.push_back(std::move(package)); } } void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const { for (const auto& package : mPackages) package->writeState(sequence); sequence.mLastAiPackage = static_cast(mLastAiPackage); } void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) { if (!sequence.mPackages.empty()) clear(); // Load packages for (auto& container : sequence.mPackages) { std::unique_ptr package; switch (container.mType) { case ESM::AiSequence::Ai_Wander: { package = std::make_unique(&static_cast(*container.mPackage)); break; } case ESM::AiSequence::Ai_Travel: { const ESM::AiSequence::AiTravel& source = static_cast(*container.mPackage); if (source.mHidden) package = std::make_unique(&source); else package = std::make_unique(&source); break; } case ESM::AiSequence::Ai_Escort: { package = std::make_unique(&static_cast(*container.mPackage)); break; } case ESM::AiSequence::Ai_Follow: { package = std::make_unique(&static_cast(*container.mPackage)); break; } case ESM::AiSequence::Ai_Activate: { package = std::make_unique(&static_cast(*container.mPackage)); break; } case ESM::AiSequence::Ai_Combat: { package = std::make_unique(&static_cast(*container.mPackage)); break; } case ESM::AiSequence::Ai_Pursue: { package = std::make_unique(&static_cast(*container.mPackage)); break; } default: break; } if (!package.get()) continue; onPackageAdded(*package); mPackages.push_back(std::move(package)); } mLastAiPackage = static_cast(sequence.mLastAiPackage); } void AiSequence::fastForward(const MWWorld::Ptr& actor) { if (!mPackages.empty()) { mPackages.front()->fastForward(actor, mAiState); } } } // namespace MWMechanics openmw-openmw-0.48.0/apps/openmw/mwmechanics/aisequence.hpp000066400000000000000000000141711445372753700240410ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AISEQUENCE_H #define GAME_MWMECHANICS_AISEQUENCE_H #include #include #include #include "aistate.hpp" #include "aipackagetypeid.hpp" #include namespace MWWorld { class Ptr; } namespace ESM { namespace AiSequence { struct AiSequence; } } namespace MWMechanics { class AiPackage; class CharacterController; using AiPackages = std::vector>; /// \brief Sequence of AI-packages for a single actor /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ class AiSequence { ///AiPackages to run though AiPackages mPackages; ///Finished with top AIPackage, set for one frame bool mDone{}; int mNumCombatPackages{}; int mNumPursuitPackages{}; ///Copy AiSequence void copy (const AiSequence& sequence); /// The type of AI package that ran last AiPackageTypeId mLastAiPackage; AiState mAiState; void onPackageAdded(const AiPackage& package); void onPackageRemoved(const AiPackage& package); AiPackages::iterator erase(AiPackages::iterator package); public: ///Default constructor AiSequence(); /// Copy Constructor AiSequence (const AiSequence& sequence); /// Assignment operator AiSequence& operator= (const AiSequence& sequence); virtual ~AiSequence(); /// Iterator may be invalidated by any function calls other than begin() or end(). AiPackages::const_iterator begin() const { return mPackages.begin(); } AiPackages::const_iterator end() const { return mPackages.end(); } /// Removes all packages controlled by the predicate. template void erasePackagesIf(const F&& pred) { mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry) { const bool doRemove = pred(entry); if (doRemove) onPackageRemoved(*entry); return doRemove; }), mPackages.end()); } /// Removes a single package controlled by the predicate. template void erasePackageIf(const F&& pred) { auto it = std::find_if(mPackages.begin(), mPackages.end(), pred); if (it == mPackages.end()) return; erase(it); } /// Returns currently executing AiPackage type /** \see enum class AiPackageTypeId **/ AiPackageTypeId getTypeId() const; /// Get the typeid of the Ai package that ran last /** NOT the currently "active" Ai package that will be run in the next frame. This difference is important when an Ai package has just finished and been removed. \see enum class AiPackageTypeId **/ AiPackageTypeId getLastRunTypeId() const { return mLastAiPackage; } /// Return true and assign target if combat package is currently active, return false otherwise bool getCombatTarget (MWWorld::Ptr &targetActor) const; /// Return true and assign targets for all combat packages, or return false if there are no combat packages bool getCombatTargets(std::vector &targetActors) const; /// Is there any combat package? bool isInCombat () const; /// Is there any pursuit package. bool isInPursuit() const; /// Removes all packages using the specified id. void removePackagesById(AiPackageTypeId id); /// Are we in combat with any other actor, who's also engaging us? bool isEngagedWithActor () const; /// Does this AI sequence have the given package type? bool hasPackage(AiPackageTypeId typeId) const; /// Are we in combat with this particular actor? bool isInCombat (const MWWorld::Ptr& actor) const; bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; ///< Function assumes that actor can have only 1 target apart player /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); /// Removes all combat packages with the given targets void stopCombat(const std::vector& targets); /// Has a package been completed during the last update? bool isPackageDone() const; /// Removes all pursue packages until first non-pursue or stack empty. void stopPursuit(); /// Execute current package, switching if needed. void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange=false); /// Simulate the passing of time using the currently active AI package void fastForward(const MWWorld::Ptr &actor); /// Remove all packages. void clear(); ///< Add \a package to the front of the sequence /** Suspends current package @param actor The actor that owns this AiSequence **/ void stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther=true); /// Return the current active package. /** If there is no active package, it will throw an exception **/ const AiPackage& getActivePackage() const; /// Fills the AiSequence with packages /** Typically used for loading from the ESM \see ESM::AIPackageList **/ void fill (const ESM::AIPackageList& list); bool isEmpty() const; void writeState (ESM::AiSequence::AiSequence& sequence) const; void readState (const ESM::AiSequence::AiSequence& sequence); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aisetting.hpp000066400000000000000000000003411445372753700237000ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_AISETTING_H #define OPENMW_MWMECHANICS_AISETTING_H namespace MWMechanics { enum class AiSetting { Hello = 0, Fight = 1, Flee = 2, Alarm = 3 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aistate.hpp000066400000000000000000000033151445372753700233470ustar00rootroot00000000000000#ifndef AISTATE_H #define AISTATE_H #include "aistatefwd.hpp" #include "aitemporarybase.hpp" #include namespace MWMechanics { /** \brief stores one object of any class derived from Base. * Requesting a certain derived class via get() either returns * the stored object if it has the correct type or otherwise replaces * it with an object of the requested type. */ template< class Base > class DerivedClassStorage { private: std::unique_ptr mStorage; public: /// \brief returns reference to stored object or deletes it and creates a fitting template< class Derived > Derived& get() { Derived* result = dynamic_cast(mStorage.get()); if (result == nullptr) { auto storage = std::make_unique(); result = storage.get(); mStorage = std::move(storage); } //return a reference to the (new allocated) object return *result; } template< class Derived > void copy(DerivedClassStorage& destination) const { Derived* result = dynamic_cast(mStorage.get()); if (result != nullptr) destination.store(*result); } template< class Derived > void store( const Derived& payload ) { mStorage = std::make_unique(payload); } /// \brief takes ownership of the passed object template void moveIn(std::unique_ptr&& storage) { mStorage = std::move(storage); } }; } #endif // AISTATE_H openmw-openmw-0.48.0/apps/openmw/mwmechanics/aistatefwd.hpp000066400000000000000000000004641445372753700240520ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_AISTATEFWD_H #define OPENMW_MWMECHANICS_AISTATEFWD_H namespace MWMechanics { template class DerivedClassStorage; struct AiTemporaryBase; /// \brief Container for AI package status. using AiState = DerivedClassStorage; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aitemporarybase.hpp000066400000000000000000000010771445372753700251070ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_AISTATE_H #define OPENMW_MWMECHANICS_AISTATE_H namespace MWMechanics { /// \brief base class for the temporary storage of AiPackages. /** * Each AI package with temporary values needs a AiPackageStorage class * which is derived from AiTemporaryBase. The Actor holds a container * AiState where one of these storages can be stored at a time. * The execute(...) member function takes this container as an argument. * */ struct AiTemporaryBase { virtual ~AiTemporaryBase() = default; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aitimer.hpp000066400000000000000000000015051445372753700233460ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_AITIMER_H #define OPENMW_MECHANICS_AITIMER_H #include #include namespace MWMechanics { constexpr float AI_REACTION_TIME = 0.25f; class AiReactionTimer { public: static constexpr float sDeviation = 0.1f; AiReactionTimer(Misc::Rng::Generator& prng) : mPrng{ prng } , mImpl{ AI_REACTION_TIME, sDeviation, Misc::Rng::deviate(0, sDeviation, prng) } { } Misc::TimerStatus update(float duration) { return mImpl.update(duration, mPrng); } void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation, mPrng)); } private: Misc::Rng::Generator& mPrng; Misc::DeviatingPeriodicTimer mImpl; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aitravel.cpp000066400000000000000000000127661445372753700235310ustar00rootroot00000000000000#include "aitravel.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "movement.hpp" #include "creaturestats.hpp" namespace { constexpr float TRAVEL_FINISH_TIME = 2.f; bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) { // Maximum travel distance for vanilla compatibility. // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. return (pos1 - pos2).length2() <= 7168*7168; } } namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*) : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mHidden(false), mDestinationTimer(TRAVEL_FINISH_TIME) { } AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived) : TypedAiPackage(derived), mX(x), mY(y), mZ(z), mHidden(true), mDestinationTimer(TRAVEL_FINISH_TIME) { } AiTravel::AiTravel(float x, float y, float z, bool repeat) : AiTravel(x, y, z, repeat, this) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : TypedAiPackage(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) , mDestinationTimer(TRAVEL_FINISH_TIME) { // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type assert(!travel->mHidden); } bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); auto& stats = actor.getClass().getCreatureStats(actor); if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) && (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress)) return false; const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(mX, mY, mZ); stats.setMovementFlag(CreatureStats::Flag_Run, false); stats.setDrawState(DrawState::Nothing); // Note: we should cancel internal "return after combat" package, if original location is too far away if (!isWithinMaxRange(targetPos, actorPos)) return mHidden; if (pathTo(actor, targetPos, duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } // If we've been close enough to the destination for some time give up like Morrowind. // The end condition should be pretty much accurate. // FIXME: But the timing isn't. Right now we're being very generous, // but Morrowind might stop the actor prematurely under unclear conditions. // Note Morrowind uses the halved eye level, but this is close enough. float dist = distanceIgnoreZ(actorPos, targetPos) - MWBase::Environment::get().getWorld()->getHalfExtents(actor).z(); const float endTolerance = std::max(64.f, actor.getClass().getCurrentSpeed(actor) * duration); // Even if we have entered the threshold, we might have been pushed away. Reset the timer if we're currently too far. if (dist > endTolerance) { mDestinationTimer = TRAVEL_FINISH_TIME; return false; } mDestinationTimer -= duration; if (mDestinationTimer > 0) return false; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { osg::Vec3f pos(mX, mY, mZ); if (!isWithinMaxRange(pos, actor.getRefData().getPosition().asVec3())) return; // does not do any validation on the travel target (whether it's in air, inside collision geometry, etc), // that is the user's responsibility MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); reset(); } void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const { auto travel = std::make_unique(); travel->mData.mX = mX; travel->mData.mY = mY; travel->mData.mZ = mZ; travel->mHidden = mHidden; travel->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; package.mPackage = std::move(travel); sequence.mPackages.push_back(std::move(package)); } AiInternalTravel::AiInternalTravel(float x, float y, float z) : AiTravel(x, y, z, this) { } AiInternalTravel::AiInternalTravel(const ESM::AiSequence::AiTravel* travel) : AiTravel(travel->mData.mX, travel->mData.mY, travel->mData.mZ, this) { } std::unique_ptr AiInternalTravel::clone() const { return std::make_unique(*this); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aitravel.hpp000066400000000000000000000040201445372753700235160ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AITRAVEL_H #define GAME_MWMECHANICS_AITRAVEL_H #include "typedaipackage.hpp" namespace ESM { namespace AiSequence { struct AiTravel; } } namespace MWMechanics { struct AiInternalTravel; /// \brief Causes the AI to travel to the specified point class AiTravel : public TypedAiPackage { public: AiTravel(float x, float y, float z, bool repeat, AiTravel* derived); AiTravel(float x, float y, float z, AiInternalTravel* derived); AiTravel(float x, float y, float z, bool repeat); explicit AiTravel(const ESM::AiSequence::AiTravel* travel); /// Simulates the passing of time void fastForward(const MWWorld::Ptr& actor, AiState& state) override; void writeState(ESM::AiSequence::AiSequence &sequence) const override; bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Travel; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mUseVariableSpeed = true; options.mAlwaysActive = true; return options; } osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } private: const float mX; const float mY; const float mZ; const bool mHidden; float mDestinationTimer; }; struct AiInternalTravel final : public AiTravel { AiInternalTravel(float x, float y, float z); explicit AiInternalTravel(const ESM::AiSequence::AiTravel* travel); static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::InternalTravel; } std::unique_ptr clone() const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiwander.cpp000066400000000000000000001172241445372753700235070ustar00rootroot00000000000000#include "aiwander.hpp" #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/collisiontype.hpp" #include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "actorutil.hpp" namespace MWMechanics { static const int COUNT_BEFORE_RESET = 10; static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f; // to prevent overcrowding static const int DESTINATION_TOLERANCE = 64; // distance must be long enough that NPC will need to move to get there. static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2; static const std::size_t MAX_IDLE_SIZE = 8; const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), std::string("idle3"), std::string("idle4"), std::string("idle5"), std::string("idle6"), std::string("idle7"), std::string("idle8"), std::string("idle9"), }; namespace { inline int getCountBeforeReset(const MWWorld::ConstPtr& actor) { if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor)) return 1; return COUNT_BEFORE_RESET; } osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const float randomDirection = Misc::Rng::rollClosedProbability(prng) * 2.0f * osg::PI; osg::Matrixf rotation; rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0)); return position + osg::Vec3f(distance, 0.0, 0.0) * rotation; } bool isDestinationHidden(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) { const auto position = actor.getRefData().getPosition().asVec3(); const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor).mHalfExtents; osg::Vec3f direction = destination - position; direction.normalize(); const auto visibleDestination = ( isWaterCreature || isFlyingCreature ? destination : destination + osg::Vec3f(0, 0, halfExtents.z()) ) + direction * std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door | MWPhysics::CollisionType_Actor; return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); } void stopMovement(const MWWorld::Ptr& actor) { auto& movementSettings = actor.getClass().getMovementSettings(actor); movementSettings.mPosition[0] = 0; movementSettings.mPosition[1] = 0; } std::vector getInitialIdle(const std::vector& idle) { std::vector result(MAX_IDLE_SIZE, 0); std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin()); return result; } std::vector getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE]) { return std::vector(std::begin(idle), std::end(idle)); } } AiWanderStorage::AiWanderStorage() : mReaction(MWBase::Environment::get().getWorld()->getPrng()), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), mIdleAnimation(0), mBadIdles(), mPopulateAvailableNodes(true), mAllowedNodes(), mTrimCurrentNode(false), mCheckIdlePositionTimer(0), mStuckCount(0) { } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): TypedAiPackage(repeat), mDistance(std::max(0, distance)), mDuration(std::max(0, duration)), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(getInitialIdle(idle)), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false) { } /* * AiWander high level states (0.29.0). Not entirely accurate in some cases * e.g. non-NPC actors do not greet and some creatures may be moving even in * the IdleNow state. * * [select node, * build path] * +---------->MoveNow----------->Walking * | | * [allowed | | * nodes] | [hello if near] | * start--->ChooseAction----->IdleNow | * ^ ^ | | * | | | | * | +-----------+ | * | | * +----------------------------------+ * * * New high level states. Not exactly as per vanilla (e.g. door stuff) * but the differences are required because our physics does not work like * vanilla and therefore have to compensate/work around. * * [select node, [if stuck evade * build path] or remove nodes if near door] * +---------->MoveNow<---------->Walking * | ^ | | * | |(near door) | | * [allowed | | | | * nodes] | [hello if near] | | * start--->ChooseAction----->IdleNow | | * ^ ^ | ^ | | * | | | | (stuck near | | * | +-----------+ +---------------+ | * | player) | * +----------------------------------+ * * NOTE: non-time critical operations are run once every 250ms or so. * * TODO: It would be great if door opening/closing can be detected and pathgrid * links dynamically updated. Currently (0.29.0) AiWander allows choosing a * destination beyond closed doors which sometimes makes the actors stuck at the * door and impossible for the player to open the door. * * For now detect being stuck at the door and simply delete the nodes from the * allowed set. The issue is when the door opens the allowed set is not * re-calculated. However this would not be an issue in most cases since hostile * actors will enter combat (i.e. no longer wandering) and different pathfinding * will kick in. */ bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& /*characterController*/, AiState& state, float duration) { MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0) return true; // Don't bother with dead actors // get or create temporary storage AiWanderStorage& storage = state.get(); mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); cStats.setDrawState(DrawState::Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); ESM::Position pos = actor.getRefData().getPosition(); // If there is already a destination due to the package having been interrupted by a combat or pursue package, // rebuild a path to it if (!mPathFinder.isPathConstructed() && mHasDestination) { if (mUsePathgrid) { mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(actor.getCell())); } else { const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); constexpr float endTolerance = 0; mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(actor.getCell()), agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, PathType::Full); } if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); } if(!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak)) { GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); if (greetingState == Greet_InProgress) { if (storage.mState == AiWanderStorage::Wander_Walking) { stopMovement(actor); mObstacleCheck.clear(); storage.setState(AiWanderStorage::Wander_IdleNow); } } } doPerFrameActionsForState(actor, duration, storage); if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting) return false; return reactionTimeActions(actor, storage, pos); } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) { if (mDistance <= 0) storage.mCanWanderAlongPathGrid = false; if (isPackageCompleted()) { stopWalking(actor); // Reset package so it can be used again mRemainingDuration=mDuration; return true; } if (!mStoredInitialActorPosition) { mInitialActorPosition = actor.getRefData().getPosition().asVec3(); mStoredInitialActorPosition = true; } // Initialization to discover & store allowed node points for this actor. if (storage.mPopulateAvailableNodes) { getAllowedNodes(actor, actor.getCell()->getCell(), storage); } auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (canActorMoveByZAxis(actor) && mDistance > 0) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100, prng) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) { wanderNearStart(actor, storage, mDistance); } storage.mCanWanderAlongPathGrid = false; } // If the package has a wander distance but no pathgrid is available, // randomly idle or wander near spawn point else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100, prng) >= 96) { wanderNearStart(actor, storage, mDistance); } else { storage.setState(AiWanderStorage::Wander_IdleNow); } } else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) { storage.mCanWanderAlongPathGrid = false; } // If Wandering manually and hit an obstacle, stop if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) { completeManualWalking(actor, storage); } if (storage.mState == AiWanderStorage::Wander_MoveNow && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one if(!mPathFinder.isPathConstructed()) { if (!storage.mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } } } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted()) { completeManualWalking(actor, storage); } if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking && (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back()) || isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back()))) completeManualWalking(actor, storage); return false; // AiWander package not yet completed } osg::Vec3f AiWander::getDestination(const MWWorld::Ptr& actor) const { if (mHasDestination) return mDestination; return actor.getRefData().getPosition().asVec3(); } bool AiWander::isPackageCompleted() const { // End package if duration is complete return mDuration && mRemainingDuration <= 0; } /* * Commands actor to walk to a random location near original spawn location. */ void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { const auto currentPosition = actor.getRefData().getPosition().asVec3(); std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); const auto world = MWBase::Environment::get().getWorld(); const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigator = world->getNavigator(); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); do { // Determine a random location within radius of original position const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability(prng) * 0.8f) * wanderDistance; if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance const auto getRandom = []() { return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng()); }; auto destination = DetourNavigator::findRandomPointAroundCircle(*navigator, agentBounds, mInitialActorPosition, wanderRadius, navigatorFlags, getRandom); if (destination.has_value()) { osg::Vec3f direction = *destination - mInitialActorPosition; if (direction.length() > wanderDistance) { direction.normalize(); const osg::Vec3f adjustedDestination = mInitialActorPosition + direction * wanderRadius; destination = DetourNavigator::raycast(*navigator, agentBounds, currentPosition, adjustedDestination, navigatorFlags); if (destination.has_value() && (*destination - mInitialActorPosition).length() > wanderDistance) continue; } } mDestination = destination.has_value() ? *destination : getRandomPointAround(mInitialActorPosition, wanderRadius); } else mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius); // Check if land creature will walk onto water or if water creature will swim onto land if (!isWaterCreature && destinationIsAtWater(actor, mDestination)) continue; if (isDestinationHidden(actor, mDestination)) continue; if (isAreaOccupiedByOtherActor(actor, mDestination)) continue; constexpr float endTolerance = 0; if (isWaterCreature || isFlyingCreature) mPathFinder.buildStraightPath(mDestination); else mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full); if (mPathFinder.isPathConstructed()) { storage.setState(AiWanderStorage::Wander_Walking, true); mHasDestination = true; mUsePathgrid = false; } break; } while (--attempts); } /* * Returns true if the position provided is above water. */ bool AiWander::destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination) { float heightToGroundOrWater = MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); osg::Vec3f positionBelowSurface = destination; positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - 1.0f; return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); } void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor); mObstacleCheck.clear(); storage.setState(AiWanderStorage::Wander_IdleNow); } void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { switch (storage.mState) { case AiWanderStorage::Wander_IdleNow: onIdleStatePerFrameActions(actor, duration, storage); break; case AiWanderStorage::Wander_Walking: onWalkingStatePerFrameActions(actor, duration, storage); break; case AiWanderStorage::Wander_ChooseAction: onChooseActionStatePerFrameActions(actor, storage); break; case AiWanderStorage::Wander_MoveNow: break; // nothing to do default: // should never get here assert(false); break; } } void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking. storage.mCheckIdlePositionTimer += duration; if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary()) { storage.mCheckIdlePositionTimer = 0; // restart timer static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f; if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance)) { storage.setState(AiWanderStorage::Wander_MoveNow); storage.mTrimCurrentNode = false; // just in case return; } } // Check if idle animation finished GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); else storage.setState(AiWanderStorage::Wander_ChooseAction); } } bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const { const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); auto cell = actor.getCell()->getCell(); for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes) { osg::Vec3f point(node.mX, node.mY, node.mZ); Misc::CoordinateConverter(cell).toWorld(point); if ((actorPos - point).length2() < distance * distance) return true; } return false; } void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Is there no destination or are we there yet? if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) { stopWalking(actor); storage.setState(AiWanderStorage::Wander_ChooseAction); } else { // have not yet reached the destination evadeObstacles(actor, storage); } } void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage) { // Wait while fully stop before starting idle animation (important if "smooth movement" is enabled). if (actor.getClass().getCurrentSpeed(actor) > 0) return; unsigned short idleAnimation = getRandomIdle(); storage.mIdleAnimation = idleAnimation; if (!idleAnimation && mDistance) { storage.setState(AiWanderStorage::Wander_MoveNow); return; } if(idleAnimation) { if(std::find(storage.mBadIdles.begin(), storage.mBadIdles.end(), idleAnimation)==storage.mBadIdles.end()) { if(!playIdle(actor, idleAnimation)) { storage.mBadIdles.push_back(idleAnimation); storage.setState(AiWanderStorage::Wander_ChooseAction); return; } } } storage.setState(AiWanderStorage::Wander_IdleNow); } void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) { if (mUsePathgrid) { const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); mPathFinder.buildPathByNavMeshToNextPoint(actor, agentBounds, getNavigatorFlags(actor), getAreaCosts(actor)); } if (mObstacleCheck.isEvading()) { // first check if we're walking into a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); if (proximityToDoor(actor, distance)) { // remove allowed points then select another random destination storage.mTrimCurrentNode = true; trimAllowedNodes(storage.mAllowedNodes, mPathFinder); mObstacleCheck.clear(); stopWalking(actor); storage.setState(AiWanderStorage::Wander_MoveNow); } storage.mStuckCount++; // TODO: maybe no longer needed } // if stuck for sufficiently long, act like current location was the destination if (storage.mStuckCount >= getCountBeforeReset(actor)) // something has gone wrong, reset { mObstacleCheck.clear(); stopWalking(actor); storage.setState(AiWanderStorage::Wander_ChooseAction); storage.mStuckCount = 0; } } void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ToWorldCoordinates(dest, actor.getCell()->getCell()); // actor position is already in world coordinates const osg::Vec3f start = actorPos.asVec3(); // don't take shortcuts for wandering const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest); mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { mDestination = destVec3f; mHasDestination = true; mUsePathgrid = true; // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); // check if mCurrentNode was taken out of mAllowedNodes if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1) storage.mTrimCurrentNode = false; else storage.mAllowedNodes.push_back(storage.mCurrentNode); storage.mCurrentNode = temp; storage.setState(AiWanderStorage::Wander_Walking); } // Choose a different node and delete this one from possible nodes because it is uncreachable: else storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); } void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) { Misc::CoordinateConverter(cell).toWorld(point); } void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { // TODO: how to add these back in once the door opens? // Idea: keep a list of detected closed doors (see aicombat.cpp) // Every now and then check whether one of the doors is opened. (maybe // at the end of playing idle?) If the door is opened then re-calculate // allowed nodes starting from the spawn point. auto paths = pathfinder.getPath(); while(paths.size() >= 2) { const auto pt = paths.back(); for(unsigned int j = 0; j < nodes.size(); j++) { // FIXME: doesn't handle a door with the same X/Y // coordinates but with a different Z if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5) { nodes.erase(nodes.begin() + j); break; } } paths.pop_back(); } } void AiWander::stopWalking(const MWWorld::Ptr& actor) { mPathFinder.clearPath(); mHasDestination = false; stopMovement(actor); } bool AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); } else { Log(Debug::Verbose) << "Attempted to play out of range idle animation \"" << idleSelect << "\" for " << actor.getCellRef().getRefId(); return false; } } bool AiWander::checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName); } else { return false; } } int AiWander::getRandomIdle() const { MWBase::World* world = MWBase::Environment::get().getWorld(); static const float fIdleChanceMultiplier = world->getStore().get().find("fIdleChanceMultiplier")->mValue.getFloat(); if (Misc::Rng::rollClosedProbability(world->getPrng()) > fIdleChanceMultiplier) return 0; int newIdle = 0; float maxRoll = 0.f; for (size_t i = 0; i < mIdle.size(); i++) { float roll = Misc::Rng::rollClosedProbability(world->getPrng()) * 100.f; if (roll <= mIdle[i] && roll > maxRoll) { newIdle = GroupIndex_MinIdle + i; maxRoll = roll; } } return newIdle; } void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) { // Update duration counter mRemainingDuration--; if (mDistance == 0) return; AiWanderStorage& storage = state.get(); if (storage.mPopulateAvailableNodes) getAllowedNodes(actor, actor.getCell()->getCell(), storage); if (storage.mAllowedNodes.empty()) return; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; ESM::Pathgrid::Point worldDest = dest; ToWorldCoordinates(worldDest, actor.getCell()->getCell()); bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); // add offset only if the selected pathgrid is occupied by another actor if (isPathGridOccupied) { ESM::Pathgrid::PointList points; getNeighbouringNodes(dest, actor.getCell(), points); // there are no neighbouring nodes, nowhere to move if (points.empty()) return; int initialSize = points.size(); bool isOccupied = false; // AI will try to move the NPC towards every neighboring node until suitable place will be found for (int i = 0; i < initialSize; i++) { int randomIndex = Misc::Rng::rollDice(points.size(), prng); ESM::Pathgrid::Point connDest = points[randomIndex]; // add an offset towards random neighboring node osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest); float length = dir.length(); dir.normalize(); for (int j = 1; j <= 3; j++) { // move for 5-15% towards random neighboring node dest = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); worldDest = dest; ToWorldCoordinates(worldDest, actor.getCell()->getCell()); isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); if (!isOccupied) break; } if (!isOccupied) break; // Will try an another neighboring node points.erase(points.begin()+randomIndex); } // there is no free space, nowhere to move if (isOccupied) return; } // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. // Adding 20 in adjustPosition() is not enough. dest.mZ += 60; ToWorldCoordinates(dest, actor.getCell()->getCell()); state.moveIn(std::make_unique()); osg::Vec3f pos(static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); } void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) { const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); if (pathgrid == nullptr || pathgrid->mPoints.empty()) return; int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); getPathGridGraph(currentCell).getNeighbouringPoints(index, points); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) { // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); const MWWorld::CellStore* cellStore = actor.getCell(); storage.mAllowedNodes.clear(); // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 // Note: In order to wander, need at least two points. if(!pathgrid || (pathgrid->mPoints.size() < 2)) storage.mCanWanderAlongPathGrid = false; // A distance value passed into the constructor indicates how far the // actor can wander from the spawn position. AiWander assumes that // pathgrid points are available, and uses them to randomly select wander // destinations within the allowed set of pathgrid points (nodes). // ... pathgrids don't usually include water, so swimmers ignore them if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor)) { // get NPC's position in local (i.e. cell) coordinates osg::Vec3f npcPos(mInitialActorPosition); Misc::CoordinateConverter(cell).toLocal(npcPos); // Find closest pathgrid point int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // and if the point is connected to the closest current point // NOTE: mPoints and mAllowedNodes are in local coordinates int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance && getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) { storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; } } if (storage.mAllowedNodes.size() == 1) { AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex, storage); } if(!storage.mAllowedNodes.empty()) { SetCurrentNodeToClosestAllowedNode(npcPos, storage); } } storage.mPopulateAvailableNodes = false; } // When only one path grid point in wander distance, // additional points for NPC to wander to are: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) { storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos)); for (auto& edge : pathGrid->mEdges) { if (edge.mV0 == pointIndex) { AddPointBetweenPathGridPoints(pathGrid->mPoints[edge.mV0], pathGrid->mPoints[edge.mV1], storage); } } } void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) { osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start); osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart; float length = delta.length(); delta.normalize(); int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); delta *= distance; storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta)); } void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage) { float distanceToClosestNode = std::numeric_limits::max(); unsigned int index = 0; for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++) { osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree])); float tempDist = (npcPos - nodePos).length2(); if (tempDist < distanceToClosestNode) { index = counterThree; distanceToClosestNode = tempDist; } } storage.mCurrentNode = storage.mAllowedNodes[index]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index); } void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const { float remainingDuration; if (mRemainingDuration > 0 && mRemainingDuration < 24) remainingDuration = mRemainingDuration; else remainingDuration = mDuration; auto wander = std::make_unique(); wander->mData.mDistance = mDistance; wander->mData.mDuration = mDuration; wander->mData.mTimeOfDay = mTimeOfDay; wander->mDurationData.mRemainingDuration = remainingDuration; assert (mIdle.size() == 8); for (int i=0; i<8; ++i) wander->mData.mIdle[i] = mIdle[i]; wander->mData.mShouldRepeat = mOptions.mRepeat; wander->mStoredInitialActorPosition = mStoredInitialActorPosition; if (mStoredInitialActorPosition) wander->mInitialActorPosition = mInitialActorPosition; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Wander; package.mPackage = std::move(wander); sequence.mPackages.push_back(std::move(package)); } AiWander::AiWander (const ESM::AiSequence::AiWander* wander) : TypedAiPackage(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0)) , mDistance(std::max(static_cast(0), wander->mData.mDistance)) , mDuration(std::max(static_cast(0), wander->mData.mDuration)) , mRemainingDuration(wander->mDurationData.mRemainingDuration) , mTimeOfDay(wander->mData.mTimeOfDay) , mIdle(getInitialIdle(wander->mData.mIdle)) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) , mHasDestination(false) , mDestination(osg::Vec3f(0, 0, 0)) , mUsePathgrid(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; if (mRemainingDuration <= 0 || mRemainingDuration >= 24) mRemainingDuration = mDuration; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/aiwander.hpp000066400000000000000000000153071445372753700235130ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_AIWANDER_H #define GAME_MWMECHANICS_AIWANDER_H #include "typedaipackage.hpp" #include #include "pathfinding.hpp" #include "obstacle.hpp" #include "aitemporarybase.hpp" #include "aitimer.hpp" namespace ESM { struct Cell; namespace AiSequence { struct AiWander; } } namespace MWMechanics { /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { AiReactionTimer mReaction; // AiWander states enum WanderState { Wander_ChooseAction, Wander_IdleNow, Wander_MoveNow, Wander_Walking }; WanderState mState; bool mIsWanderingManually; bool mCanWanderAlongPathGrid; unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors // do we need to calculate allowed nodes based on mDistance bool mPopulateAvailableNodes; // allowed pathgrid nodes based on mDistance from the spawn point // in local coordinates of mCell std::vector mAllowedNodes; ESM::Pathgrid::Point mCurrentNode; bool mTrimCurrentNode; float mCheckIdlePositionTimer; int mStuckCount; AiWanderStorage(); void setState(const WanderState wanderState, const bool isManualWander = false) { mState = wanderState; mIsWanderingManually = isManualWander; } }; /// \brief Causes the Actor to wander within a specified range class AiWander final : public TypedAiPackage { public: /// Constructor /** \param distance Max distance the ACtor will wander \param duration Time, in hours, that this package will be preformed \param timeOfDay Currently unimplemented. Not functional in the original engine. \param idle Chances of each idle to play (9 in total) \param repeat Repeat wander or not **/ AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); explicit AiWander (const ESM::AiSequence::AiWander* wander); bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Wander; } static constexpr Options makeDefaultOptions() { AiPackage::Options options; options.mUseVariableSpeed = true; return options; } void writeState(ESM::AiSequence::AiSequence &sequence) const override; void fastForward(const MWWorld::Ptr& actor, AiState& state) override; osg::Vec3f getDestination(const MWWorld::Ptr& actor) const override; osg::Vec3f getDestination() const override { if (!mHasDestination) return osg::Vec3f(0, 0, 0); return mDestination; } bool isStationary() const { return mDistance == 0; } private: void stopWalking(const MWWorld::Ptr& actor); /// Have the given actor play an idle animation /// @return Success or error bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); int getRandomIdle() const; void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); inline bool isPackageCompleted() const; void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); bool isNearAllowedNode(const MWWorld::Ptr &actor, const AiWanderStorage& storage, float distance) const; const int mDistance; // how far the actor can wander from the spawn point const int mDuration; float mRemainingDuration; const int mTimeOfDay; const std::vector mIdle; bool mStoredInitialActorPosition; osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell bool mHasDestination; osg::Vec3f mDestination; bool mUsePathgrid; void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); // constants for converting idleSelect values into groupNames enum GroupIndex { GroupIndex_MinIdle = 2, GroupIndex_MaxIdle = 9 }; /// convert point from local (i.e. cell) to world coordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); void SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage); void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage); void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/alchemy.cpp000066400000000000000000000431351445372753700233360ustar00rootroot00000000000000#include "alchemy.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "magiceffects.hpp" #include "creaturestats.hpp" MWMechanics::Alchemy::Alchemy() : mValue(0) , mPotionName("") { } std::set MWMechanics::Alchemy::listEffects() const { std::map effects; for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) { if (!iter->isEmpty()) { const MWWorld::LiveCellRef *ingredient = iter->get(); std::set seenEffects; for (int i=0; i<4; ++i) if (ingredient->mBase->mData.mEffectID[i]!=-1) { EffectKey key ( ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ? ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]); if (seenEffects.insert(key).second) ++effects[key]; } } } std::set effects2; for (std::map::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) if (iter->second>1) effects2.insert (iter->first); return effects2; } void MWMechanics::Alchemy::applyTools (int flags, float& value) const { bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude); bool duration = !(flags & ESM::MagicEffect::NoDuration); bool negative = (flags & ESM::MagicEffect::Harmful) != 0; int tool = negative ? ESM::Apparatus::Alembic : ESM::Apparatus::Retort; int setup = 0; if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty()) setup = 1; else if (!mTools[tool].isEmpty()) setup = 2; else if (!mTools[ESM::Apparatus::Calcinator].isEmpty()) setup = 3; else return; float toolQuality = setup==1 || setup==2 ? mTools[tool].get()->mBase->mData.mQuality : 0; float calcinatorQuality = setup==1 || setup==3 ? mTools[ESM::Apparatus::Calcinator].get()->mBase->mData.mQuality : 0; float quality = 1; switch (setup) { case 1: quality = negative ? 2 * toolQuality + 3 * calcinatorQuality : (magnitude && duration ? 2 * toolQuality + calcinatorQuality : 2/3.0f * (toolQuality + calcinatorQuality) + 0.5f); break; case 2: quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5f); break; case 3: quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5f; break; } if (setup==3 || !negative) { value += quality; } else { if (quality==0) throw std::runtime_error ("invalid derived alchemy apparatus quality"); value /= quality; } } void MWMechanics::Alchemy::updateEffects() { mEffects.clear(); mValue = 0; if (countIngredients()<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty()) return; // find effects std::set effects (listEffects()); // general alchemy factor float x = getAlchemyFactor(); x *= mTools[ESM::Apparatus::MortarPestle].get()->mBase->mData.mQuality; x *= MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionStrengthMult")->mValue.getFloat(); // value mValue = static_cast ( x * MWBase::Environment::get().getWorld()->getStore().get().find ("iAlchemyMod")->mValue.getFloat()); // build quantified effect list for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); if (magicEffect->mData.mBaseCost<=0) { const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId); throw std::runtime_error (os); } float fPotionT1MagMul = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->mValue.getFloat(); if (fPotionT1MagMul<=0) throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); float fPotionT1DurMult = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->mValue.getFloat(); if (fPotionT1DurMult<=0) throw std::runtime_error ("invalid gmst: fPotionT1DurMult"); float magnitude = (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) ? 1.0f : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost; float duration = (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) ? 1.0f : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) applyTools (magicEffect->mData.mFlags, magnitude); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) applyTools (magicEffect->mData.mFlags, duration); duration = roundf(duration); magnitude = roundf(magnitude); if (magnitude>0 && duration>0) { ESM::ENAMstruct effect; effect.mEffectID = iter->mId; effect.mAttribute = -1; effect.mSkill = -1; if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) effect.mSkill = iter->mArg; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) effect.mAttribute = iter->mArg; effect.mRange = 0; effect.mArea = 0; effect.mDuration = static_cast(duration); effect.mMagnMin = effect.mMagnMax = static_cast(magnitude); mEffects.push_back (effect); } } } const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) const { const MWWorld::Store &potions = MWBase::Environment::get().getWorld()->getStore().get(); MWWorld::Store::iterator iter = potions.begin(); for (; iter != potions.end(); ++iter) { if (iter->mEffects.mList.size() != mEffects.size()) continue; if (iter->mName != toFind.mName || iter->mScript != toFind.mScript || iter->mData.mWeight != toFind.mData.mWeight || iter->mData.mValue != toFind.mData.mValue || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) continue; // Don't choose an ID that came from the content files, would have unintended side effects // where alchemy can be used to produce quest-relevant items if (!potions.isDynamic(iter->mId)) continue; bool mismatch = false; for (int i=0; i (iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; if (first.mEffectID!=second.mEffectID || first.mArea!=second.mArea || first.mRange!=second.mRange || first.mSkill!=second.mSkill || first.mAttribute!=second.mAttribute || first.mMagnMin!=second.mMagnMin || first.mMagnMax!=second.mMagnMax || first.mDuration!=second.mDuration) { mismatch = true; break; } } if (!mismatch) return &(*iter); } return nullptr; } void MWMechanics::Alchemy::removeIngredients() { for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { iter->getContainerStore()->remove(*iter, 1, mAlchemist); if (iter->getRefData().getCount()<1) *iter = MWWorld::Ptr(); } updateEffects(); } void MWMechanics::Alchemy::addPotion (const std::string& name) { ESM::Potion newRecord; newRecord.mData.mWeight = 0; for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) if (!iter->isEmpty()) newRecord.mData.mWeight += iter->get()->mBase->mData.mWeight; if (countIngredients() > 0) newRecord.mData.mWeight /= countIngredients(); newRecord.mData.mValue = mValue; newRecord.mData.mAutoCalc = 0; newRecord.mName = name; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int index = Misc::Rng::rollDice(6, prng); assert (index>=0 && index<6); static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; newRecord.mModel = "m\\misc_potion_" + std::string (meshes[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string (meshes[index]) + "_01.dds"; newRecord.mEffects.mList = mEffects; const ESM::Potion* record = getRecord(newRecord); if (!record) record = MWBase::Environment::get().getWorld()->createRecord (newRecord); mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist); } void MWMechanics::Alchemy::increaseSkill() { mAlchemist.getClass().skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0); } float MWMechanics::Alchemy::getAlchemyFactor() const { const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist); return (mAlchemist.getClass().getSkill(mAlchemist, ESM::Skill::Alchemy) + 0.1f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()); } int MWMechanics::Alchemy::countIngredients() const { int ingredients = 0; for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) if (!iter->isEmpty()) ++ingredients; return ingredients; } int MWMechanics::Alchemy::countPotionsToBrew() const { Result readyStatus = getReadyStatus(); if (readyStatus != Result_Success) return 0; int toBrew = -1; for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) if (!iter->isEmpty()) { int count = iter->getRefData().getCount(); if ((count > 0 && count < toBrew) || toBrew < 0) toBrew = count; } return toBrew; } void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) { mAlchemist = npc; mIngredients.resize (4); std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); mTools.resize (4); std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr()); mEffects.clear(); MWWorld::ContainerStore& store = npc.getClass().getContainerStore (npc); for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); iter!=store.end(); ++iter) { MWWorld::LiveCellRef* ref = iter->get(); int type = ref->mBase->mData.mType; if (type<0 || type>=static_cast (mTools.size())) throw std::runtime_error ("invalid apparatus type"); if (!mTools[type].isEmpty()) if (ref->mBase->mData.mQuality<=mTools[type].get()->mBase->mData.mQuality) continue; mTools[type] = *iter; } } MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::beginTools() const { return mTools.begin(); } MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::endTools() const { return mTools.end(); } MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::beginIngredients() const { return mIngredients.begin(); } MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients() const { return mIngredients.end(); } void MWMechanics::Alchemy::clear() { mAlchemist = MWWorld::Ptr(); mTools.clear(); mIngredients.clear(); mEffects.clear(); setPotionName(""); } void MWMechanics::Alchemy::setPotionName(const std::string& name) { mPotionName = name; } int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) { // find a free slot int slot = -1; for (int i=0; i (mIngredients.size()); ++i) if (mIngredients[i].isEmpty()) { slot = i; break; } if (slot==-1) return -1; for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getCellRef().getRefId(), iter->getCellRef().getRefId())) return -1; mIngredients[slot] = ingredient; updateEffects(); return slot; } void MWMechanics::Alchemy::removeIngredient (int index) { if (index>=0 && index (mIngredients.size())) { mIngredients[index] = MWWorld::Ptr(); updateEffects(); } } MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const { return mEffects.begin(); } MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const { return mEffects.end(); } bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) { float alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2) || (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue*3) || (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue*4); } MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) return Result_NoMortarAndPestle; if (countIngredients()<2) return Result_LessThanTwoIngredients; if (mPotionName.empty()) return Result_NoName; if (listEffects().empty()) return Result_NoEffects; return Result_Success; } MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name, int& count) { setPotionName(name); Result readyStatus = getReadyStatus(); if (readyStatus == Result_NoEffects) removeIngredients(); if (readyStatus != Result_Success) return readyStatus; Result result = Result_RandomFailure; int brewedCount = 0; for (int i = 0; i < count; ++i) { if (createSingle() == Result_Success) { result = Result_Success; brewedCount++; } } count = brewedCount; return result; } MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle () { if (beginEffects() == endEffects()) { // all effects were nullified due to insufficient skill removeIngredients(); return Result_RandomFailure; } auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (getAlchemyFactor() < Misc::Rng::roll0to99(prng)) { removeIngredients(); return Result_RandomFailure; } addPotion(mPotionName); removeIngredients(); increaseSkill(); return Result_Success; } std::string MWMechanics::Alchemy::suggestPotionName() { std::set effects = listEffects(); if (effects.empty()) return ""; int effectId = effects.begin()->mId; return MWBase::Environment::get().getWorld()->getStore().get().find( ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); } std::vector MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill) { std::vector effects; const auto& item = ptr.get()->mBase; const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get(); const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat(); const auto& data = item->mData; for (auto i = 0; i < 4; ++i) { const auto effectID = data.mEffectID[i]; const auto skillID = data.mSkills[i]; const auto attributeID = data.mAttributes[i]; if (alchemySkill < fWortChanceValue * (i + 1)) break; if (effectID != -1) { std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString(); if (skillID != -1) effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString(); else if (attributeID != -1) effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString(); effects.push_back(effect); } } return effects; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/alchemy.hpp000066400000000000000000000113521445372753700233370ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ALCHEMY_H #define GAME_MWMECHANICS_ALCHEMY_H #include #include #include #include "../mwworld/ptr.hpp" namespace ESM { struct Potion; } namespace MWMechanics { struct EffectKey; /// \brief Potion creation via alchemy skill class Alchemy { public: Alchemy(); typedef std::vector TToolsContainer; typedef TToolsContainer::const_iterator TToolsIterator; typedef std::vector TIngredientsContainer; typedef TIngredientsContainer::const_iterator TIngredientsIterator; typedef std::vector TEffectsContainer; typedef TEffectsContainer::const_iterator TEffectsIterator; enum Result { Result_Success, Result_NoMortarAndPestle, Result_LessThanTwoIngredients, Result_NoName, Result_NoEffects, Result_RandomFailure }; private: MWWorld::Ptr mAlchemist; TToolsContainer mTools; TIngredientsContainer mIngredients; TEffectsContainer mEffects; int mValue; std::string mPotionName; void applyTools (int flags, float& value) const; void updateEffects(); Result getReadyStatus() const; const ESM::Potion *getRecord(const ESM::Potion& toFind) const; ///< Try to find a potion record similar to \a toFind in the record store, or return 0 if not found /// \note Does not account for record ID, model or icon void removeIngredients(); ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and /// update effect list accordingly. void addPotion (const std::string& name); ///< Add a potion to the alchemist's inventory. void increaseSkill(); ///< Increase alchemist's skill. Result createSingle (); ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and /// adjust the skills of the alchemist accordingly. float getAlchemyFactor() const; int countIngredients() const; TEffectsIterator beginEffects() const; TEffectsIterator endEffects() const; public: int countPotionsToBrew() const; ///< calculates maximum amount of potions, which you can make from selected ingredients static bool knownEffect (unsigned int potionEffectIndex, const MWWorld::Ptr& npc); ///< Does npc have sufficient alchemy skill to know about this potion effect? void setAlchemist (const MWWorld::Ptr& npc); ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that /// there is no alchemist (alchemy session has ended). TToolsIterator beginTools() const; ///< \attention Iterates over tool slots, not over tools. Some of the slots may be empty. TToolsIterator endTools() const; TIngredientsIterator beginIngredients() const; ///< \attention Iterates over ingredient slots, not over ingredients. Some of the slots may be empty. TIngredientsIterator endIngredients() const; void clear(); ///< Remove alchemist, tools and ingredients. void setPotionName(const std::string& name); ///< Set name of potion to create std::set listEffects() const; ///< List all effects shared by at least two ingredients. int addIngredient (const MWWorld::Ptr& ingredient); ///< Add ingredient into the next free slot. /// /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being /// listed already. void removeIngredient (int index); ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). std::string suggestPotionName (); ///< Suggest a name for the potion, based on the current effects Result create (const std::string& name, int& count); ///< Try to create potions from the ingredients, place them in the inventory of the alchemist and /// adjust the skills of the alchemist accordingly. /// \param name must not be an empty string, or Result_NoName is returned static std::vector effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySKill); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/autocalcspell.cpp000066400000000000000000000305231445372753700245440ustar00rootroot00000000000000#include "autocalcspell.hpp" #include #include "../mwworld/esmstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "spellutil.hpp" namespace MWMechanics { struct SchoolCaps { int mCount; int mLimit; bool mReachedLimit; int mMinCost; std::string mWeakestSpell; }; std::vector autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->mValue.getFloat(); float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; static int iAutoSpellSchoolMax[6]; static bool init = false; if (!init) { for (int i=0; i<6; ++i) { const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; iAutoSpellSchoolMax[i] = gmst.find(gmstName)->mValue.getInteger(); } init = true; } std::map schoolCaps; for (int i=0; i<6; ++i) { SchoolCaps caps; caps.mCount = 0; caps.mLimit = iAutoSpellSchoolMax[i]; caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; caps.mMinCost = std::numeric_limits::max(); caps.mWeakestSpell.clear(); schoolCaps[i] = caps; } std::vector selectedSpells; const MWWorld::Store &spells = MWBase::Environment::get().getWorld()->getStore().get(); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. for (const ESM::Spell& spell : spells) { if (spell.mData.mType != ESM::Spell::ST_Spell) continue; if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) continue; static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger(); int spellCost = MWMechanics::calcSpellCost(spell); if (baseMagicka < iAutoSpellTimesCanCast * spellCost) continue; if (race && race->mPowers.exists(spell.mId)) continue; if (!attrSkillCheck(&spell, actorSkills, actorAttributes)) continue; int school; float skillTerm; calcWeakestSchool(&spell, actorSkills, school, skillTerm); assert(school >= 0 && school < 6); SchoolCaps& cap = schoolCaps[school]; if (cap.mReachedLimit && spellCost <= cap.mMinCost) continue; static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat(); if (calcAutoCastChance(&spell, actorSkills, actorAttributes, school) < fAutoSpellChance) continue; selectedSpells.push_back(spell.mId); if (cap.mReachedLimit) { std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); if (found != selectedSpells.end()) selectedSpells.erase(found); cap.mMinCost = std::numeric_limits::max(); for (const std::string& testSpellName : selectedSpells) { const ESM::Spell* testSpell = spells.find(testSpellName); int testSpellCost = MWMechanics::calcSpellCost(*testSpell); //int testSchool; //float dummySkillTerm; //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); // Note: if there are multiple spells with the same cost, we pick the first one we found. // So the algorithm depends on the iteration order of the outer loop. if ( // There is a huge bug here. It is not checked that weakestSpell is of the correct school. // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school // already erased it, and so the number of spells would often exceed the sum of limits. // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. //testSchool == school && testSpellCost < cap.mMinCost) { cap.mMinCost = testSpellCost; cap.mWeakestSpell = testSpell->mId; } } } else { cap.mCount += 1; if (cap.mCount == cap.mLimit) cap.mReachedLimit = true; if (spellCost < cap.mMinCost) { cap.mWeakestSpell = spell.mId; cap.mMinCost = spellCost; } } } return selectedSpells; } std::vector autoCalcPlayerSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->mValue.getFloat(); float baseMagicka = fPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; bool reachedLimit = false; const ESM::Spell* weakestSpell = nullptr; int minCost = std::numeric_limits::max(); std::vector selectedSpells; const MWWorld::Store &spells = esmStore.get(); for (const ESM::Spell& spell : spells) { if (spell.mData.mType != ESM::Spell::ST_Spell) continue; if (!(spell.mData.mFlags & ESM::Spell::F_PCStart)) continue; int spellCost = MWMechanics::calcSpellCost(spell); if (reachedLimit && spellCost <= minCost) continue; if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end()) continue; if (baseMagicka < spellCost) continue; static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->mValue.getFloat(); if (calcAutoCastChance(&spell, actorSkills, actorAttributes, -1) < fAutoPCSpellChance) continue; if (!attrSkillCheck(&spell, actorSkills, actorAttributes)) continue; selectedSpells.push_back(spell.mId); if (reachedLimit) { std::vector::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); if (it != selectedSpells.end()) selectedSpells.erase(it); minCost = std::numeric_limits::max(); for (const std::string& testSpellName : selectedSpells) { const ESM::Spell* testSpell = esmStore.get().find(testSpellName); int testSpellCost = MWMechanics::calcSpellCost(*testSpell); if (testSpellCost < minCost) { minCost = testSpellCost; weakestSpell = testSpell; } } } else { if (spellCost < minCost) { weakestSpell = &spell; minCost = MWMechanics::calcSpellCost(*weakestSpell); } static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->mValue.getInteger(); if (selectedSpells.size() == iAutoPCSpellMax) reachedLimit = true; } } return selectedSpells; } bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes) { for (const auto& spellEffect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(spellEffect.mEffectID); static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->mValue.getInteger(); if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { assert (spellEffect.mSkill >= 0 && spellEffect.mSkill < ESM::Skill::Length); if (actorSkills[spellEffect.mSkill] < iAutoSpellAttSkillMin) return false; } if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) { assert (spellEffect.mAttribute >= 0 && spellEffect.mAttribute < ESM::Attribute::Length); if (actorAttributes[spellEffect.mAttribute] < iAutoSpellAttSkillMin) return false; } } return true; } void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float minChance = std::numeric_limits::max(); for (const ESM::ENAMstruct& effect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { minMagn = effect.mMagnMin; maxMagn = effect.mMagnMax; } int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) duration = effect.mDuration; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) duration = std::max(1, duration); static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() .get().find("fEffectCostMult")->mValue.getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= 1 + duration; x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; x *= fEffectCostMult; if (effect.mRange == ESM::RT_Target) x *= 1.5f; float s = 2.f * actorSkills[spellSchoolToSkill(magicEffect->mData.mSchool)]; if (s - x < minChance) { minChance = s - x; effectiveSchool = magicEffect->mData.mSchool; skillTerm = s; } } } float calcAutoCastChance(const ESM::Spell *spell, const int *actorSkills, const int *actorAttributes, int effectiveSchool) { if (spell->mData.mType != ESM::Spell::ST_Spell) return 100.f; if (spell->mData.mFlags & ESM::Spell::F_Always) return 100.f; float skillTerm = 0; if (effectiveSchool != -1) skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)]; else calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this float castChance = skillTerm - MWMechanics::calcSpellCost(*spell) + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; return castChance; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/autocalcspell.hpp000066400000000000000000000017041445372753700245500ustar00rootroot00000000000000#ifndef OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H #include #include namespace ESM { struct Spell; struct Race; } namespace MWMechanics { /// Contains algorithm for calculating an NPC's spells based on stats /// @note We might want to move this code to a component later, so the editor can use it for preview purposes std::vector autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); std::vector autoCalcPlayerSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); // Helpers bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/character.cpp000066400000000000000000003357611445372753700236610ustar00rootroot00000000000000/* * OpenMW - The completely unofficial reimplementation of Morrowind * * This file (character.cpp) is part of the OpenMW package. * * OpenMW is distributed as free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * version 3, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * version 3 along with this program. If not, see * https://www.gnu.org/licenses/ . */ #include "character.hpp" #include #include #include #include #include #include #include #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "../mwworld/spellcaststate.hpp" #include "aicombataction.hpp" #include "movement.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" #include "security.hpp" #include "actorutil.hpp" #include "spellcasting.hpp" namespace { std::string getBestAttack (const ESM::Weapon* weapon) { int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1]; int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1]; int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1]; if (slash == chop && slash == thrust) return "slash"; else if (thrust >= chop && thrust >= slash) return "thrust"; else if (slash >= chop && slash >= thrust) return "slash"; else return "chop"; } // Converts a movement Run state to its equivalent Walk state, if there is one. MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_RunForward: return CharState_WalkForward; case CharState_RunBack: return CharState_WalkBack; case CharState_RunLeft: return CharState_WalkLeft; case CharState_RunRight: return CharState_WalkRight; case CharState_SwimRunForward: return CharState_SwimWalkForward; case CharState_SwimRunBack: return CharState_SwimWalkBack; case CharState_SwimRunLeft: return CharState_SwimWalkLeft; case CharState_SwimRunRight: return CharState_SwimWalkRight; default: return state; } } // Converts a Hit state to its equivalent Death state. MWMechanics::CharacterState hitStateToDeathState (MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_SwimKnockDown: return CharState_SwimDeathKnockDown; case CharState_SwimKnockOut: return CharState_SwimDeathKnockOut; case CharState_KnockDown: return CharState_DeathKnockDown; case CharState_KnockOut: return CharState_DeathKnockOut; default: return CharState_None; } } // Converts a movement state to its equivalent base animation group as long as it is a movement state. std::string movementStateToAnimGroup(MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_WalkForward: return "walkforward"; case CharState_WalkBack: return "walkback"; case CharState_WalkLeft: return "walkleft"; case CharState_WalkRight: return "walkright"; case CharState_SwimWalkForward: return "swimwalkforward"; case CharState_SwimWalkBack: return "swimwalkback"; case CharState_SwimWalkLeft: return "swimwalkleft"; case CharState_SwimWalkRight: return "swimwalkright"; case CharState_RunForward: return "runforward"; case CharState_RunBack: return "runback"; case CharState_RunLeft: return "runleft"; case CharState_RunRight: return "runright"; case CharState_SwimRunForward: return "swimrunforward"; case CharState_SwimRunBack: return "swimrunback"; case CharState_SwimRunLeft: return "swimrunleft"; case CharState_SwimRunRight: return "swimrunright"; case CharState_SneakForward: return "sneakforward"; case CharState_SneakBack: return "sneakback"; case CharState_SneakLeft: return "sneakleft"; case CharState_SneakRight: return "sneakright"; case CharState_TurnLeft: return "turnleft"; case CharState_TurnRight: return "turnright"; case CharState_SwimTurnLeft: return "swimturnleft"; case CharState_SwimTurnRight: return "swimturnright"; default: return {}; } } // Converts a death state to its equivalent animation group as long as it is a death state. std::string deathStateToAnimGroup(MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_SwimDeath: return "swimdeath"; case CharState_SwimDeathKnockDown: return "swimdeathknockdown"; case CharState_SwimDeathKnockOut: return "swimdeathknockout"; case CharState_DeathKnockDown: return "deathknockdown"; case CharState_DeathKnockOut: return "deathknockout"; case CharState_Death1: return "death1"; case CharState_Death2: return "death2"; case CharState_Death3: return "death3"; case CharState_Death4: return "death4"; case CharState_Death5: return "death5"; default: return {}; } } // Converts a hit state to its equivalent animation group as long as it is a hit state. std::string hitStateToAnimGroup(MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_SwimHit: return "swimhit"; case CharState_SwimKnockDown: return "swimknockdown"; case CharState_SwimKnockOut: return "swimknockout"; case CharState_Hit: return "hit"; case CharState_KnockDown: return "knockdown"; case CharState_KnockOut: return "knockout"; case CharState_Block: return "shield"; default: return {}; } } // Converts an idle state to its equivalent animation group. std::string idleStateToAnimGroup(MWMechanics::CharacterState state) { using namespace MWMechanics; switch (state) { case CharState_IdleSwim: return "idleswim"; case CharState_IdleSneak: return "idlesneak"; case CharState_Idle: case CharState_SpecialIdle: return "idle"; default: return {}; } } MWRender::Animation::AnimPriority getIdlePriority(MWMechanics::CharacterState state) { using namespace MWMechanics; MWRender::Animation::AnimPriority priority(Priority_Default); switch (state) { case CharState_IdleSwim: return Priority_SwimIdle; case CharState_IdleSneak: priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; [[fallthrough]]; default: return priority; } } float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat(); if (fallHeight >= fallDistanceMin) { const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat(); const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat(); const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat(); const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat(); float x = fallHeight - fallDistanceMin; x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; x = std::max(0.0f, x); float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); x = fallDistanceBase + fallDistanceMult * x; x *= a; return x; } return 0.f; } bool isRealWeapon(int weaponType) { return weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None; } } namespace MWMechanics { std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int numAnims=0; while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1))) ++numAnims; int roll = Misc::Rng::rollDice(numAnims, prng) + 1; // [1, numAnims] if (num) *num = roll; return prefix + std::to_string(roll); } void CharacterController::clearStateAnimation(std::string &anim) const { if (anim.empty()) return; if (mAnimation) mAnimation->disable(anim); anim.clear(); } void CharacterController::resetCurrentJumpState() { clearStateAnimation(mCurrentJump); mJumpState = JumpState_None; } void CharacterController::resetCurrentMovementState() { clearStateAnimation(mCurrentMovement); mMovementState = CharState_None; } void CharacterController::resetCurrentIdleState() { clearStateAnimation(mCurrentIdle); mIdleState = CharState_None; } void CharacterController::resetCurrentHitState() { clearStateAnimation(mCurrentHit); mHitState = CharState_None; } void CharacterController::resetCurrentWeaponState() { clearStateAnimation(mCurrentWeapon); mUpperBodyState = UpperCharState_Nothing; } void CharacterController::resetCurrentDeathState() { clearStateAnimation(mCurrentDeath); mDeathState = CharState_None; } void CharacterController::refreshHitRecoilAnims() { auto& charClass = mPtr.getClass(); if (!charClass.isActor()) return; const auto world = MWBase::Environment::get().getWorld(); auto& stats = charClass.getCreatureStats(mPtr); bool knockout = stats.getFatigue().getCurrent() < 0 || stats.getFatigue().getBase() == 0; bool recovery = stats.getHitRecovery(); bool knockdown = stats.getKnockedDown(); bool block = stats.getBlock(); bool isSwimming = world->isSwimming(mPtr); if (mHitState != CharState_None) { if (!mAnimation->isPlaying(mCurrentHit)) { if (isKnockedOut() && mCurrentHit.empty() && knockout) return; mHitState = CharState_None; mCurrentHit.clear(); stats.setKnockedDown(false); stats.setHitRecovery(false); stats.setBlock(false); resetCurrentIdleState(); } else if (isKnockedOut()) mAnimation->setLoopingEnabled(mCurrentHit, knockout); return; } if (!knockout && !knockdown && !recovery && !block) return; MWRender::Animation::AnimPriority priority(Priority_Knockdown); std::string startKey = "start"; std::string stopKey = "stop"; if (knockout) { mHitState = isSwimming ? CharState_SwimKnockOut : CharState_KnockOut; stats.setKnockedDown(true); } else if (knockdown) { mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; } else if (recovery) { mHitState = isSwimming ? CharState_SwimHit : CharState_Hit; priority = Priority_Hit; } else if (block) { mHitState = CharState_Block; priority = Priority_Hit; priority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; startKey = "block start"; stopKey = "block stop"; } mCurrentHit = hitStateToAnimGroup(mHitState); if (isRecovery()) { mCurrentHit = chooseRandomGroup(mCurrentHit); if (mHitState == CharState_SwimHit && !mAnimation->hasAnimation(mCurrentHit)) mCurrentHit = chooseRandomGroup(hitStateToAnimGroup(CharState_Hit)); } // Cancel upper body animations if (isKnockedOut() || isKnockedDown()) { if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); if (mUpperBodyState > UpperCharState_WeapEquiped) { mUpperBodyState = UpperCharState_WeapEquiped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } else if (mUpperBodyState < UpperCharState_WeapEquiped) { mUpperBodyState = UpperCharState_Nothing; } } if (!mAnimation->hasAnimation(mCurrentHit)) { mCurrentHit.clear(); return; } mAnimation->play(mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); } void CharacterController::refreshJumpAnims(JumpingState jump, bool force) { if (!force && jump == mJumpState) return; if (jump == JumpState_None) { if (!mCurrentJump.empty()) resetCurrentIdleState(); resetCurrentJumpState(); return; } std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); std::string jumpAnimName = "jump"; jumpAnimName += weapShortGroup; MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName)) jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); if (!mAnimation->hasAnimation(jumpAnimName)) { if (!mCurrentJump.empty()) resetCurrentIdleState(); resetCurrentJumpState(); return; } bool startAtLoop = (jump == mJumpState); mJumpState = jump; clearStateAnimation(mCurrentJump); mCurrentJump = jumpAnimName; if(mJumpState == JumpState_InAir) mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); else if (mJumpState == JumpState_Landing) mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } bool CharacterController::onOpen() const { if (mPtr.getType() == ESM::Container::sRecordId) { if (!mAnimation->hasAnimation("containeropen")) return true; if (mAnimation->isPlaying("containeropen")) return false; if (mAnimation->isPlaying("containerclose")) return false; mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; } return true; } void CharacterController::onClose() const { if (mPtr.getType() == ESM::Container::sRecordId) { if (!mAnimation->hasAnimation("containerclose")) return; float complete, startPoint = 0.f; bool animPlaying = mAnimation->getInfo("containeropen", &complete); if (animPlaying) startPoint = 1.f - complete; mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); } } std::string_view CharacterController::getWeaponAnimation(int weaponType) const { std::string_view weaponGroup = getWeaponType(weaponType)->mLongGroup; if (isRealWeapon(weaponType) && !mAnimation->hasAnimation(weaponGroup)) { static const std::string_view oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; static const std::string_view twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; const ESM::WeaponType* weapInfo = getWeaponType(weaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) weaponGroup = twoHandFallback; else weaponGroup = oneHandFallback; } else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) return "attack1"; return weaponGroup; } std::string_view CharacterController::getWeaponShortGroup(int weaponType) const { if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) return {}; return getWeaponType(weaponType)->mShortGroup; } std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) const { if (!isRealWeapon(mWeaponType)) { if (blendMask != nullptr) *blendMask = MWRender::Animation::BlendMask_LowerBody; return baseGroupName; } static const std::string_view oneHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeOneHand); static const std::string_view twoHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeTwoHand); std::string groupName = baseGroupName; const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) groupName += twoHandFallback; else groupName += oneHandFallback; // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) *blendMask = MWRender::Animation::BlendMask_LowerBody; if (!mAnimation->hasAnimation(groupName)) { groupName = baseGroupName; if (blendMask != nullptr) *blendMask = MWRender::Animation::BlendMask_LowerBody; } return groupName; } void CharacterController::refreshMovementAnims(CharacterState movement, bool force) { if (movement == mMovementState && !force) return; std::string movementAnimName = movementStateToAnimGroup(movement); if (movementAnimName.empty()) { if (!mCurrentMovement.empty()) resetCurrentIdleState(); resetCurrentMovementState(); return; } mMovementState = movement; std::string::size_type swimpos = movementAnimName.find("swim"); if (!mAnimation->hasAnimation(movementAnimName)) { if (swimpos != std::string::npos) { movementAnimName.erase(swimpos, 4); swimpos = std::string::npos; } } MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); // Non-biped creatures don't use spellcasting-specific movement animations. if(!isRealWeapon(mWeaponType) && !mPtr.getClass().isBipedal(mPtr)) weapShortGroup = {}; if (swimpos == std::string::npos && !weapShortGroup.empty()) { std::string weapMovementAnimName; // Spellcasting stance turning is a special case if (mWeaponType == ESM::Weapon::Spell && isTurning()) { weapMovementAnimName = weapShortGroup; weapMovementAnimName += movementAnimName; } else { weapMovementAnimName = movementAnimName; weapMovementAnimName += weapShortGroup; } if (!mAnimation->hasAnimation(weapMovementAnimName)) weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); movementAnimName = weapMovementAnimName; } if (!mAnimation->hasAnimation(movementAnimName)) { std::string::size_type runpos = movementAnimName.find("run"); if (runpos != std::string::npos) movementAnimName.replace(runpos, 3, "walk"); if (!mAnimation->hasAnimation(movementAnimName)) { if (!mCurrentMovement.empty()) resetCurrentIdleState(); resetCurrentMovementState(); return; } } // If we're playing the same animation, start it from the point it ended float startpoint = 0.f; if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement) mAnimation->getInfo(mCurrentMovement, &startpoint); mMovementAnimationControlled = true; clearStateAnimation(mCurrentMovement); mCurrentMovement = movementAnimName; // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. mAdjustMovementAnimSpeed = true; if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { CharacterState walkState = runStateToWalkState(mMovementState); std::string anim = movementStateToAnimGroup(walkState); mMovementAnimSpeed = mAnimation->getVelocity(anim); if (mMovementAnimSpeed <= 1.0f) { // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist // we will play without any scaling. // Makes the speed attribute of most water creatures totally useless. // And again, this can not be fixed without patching game data. mAdjustMovementAnimSpeed = false; mMovementAnimSpeed = 1.f; } } else { mMovementAnimSpeed = mAnimation->getVelocity(mCurrentMovement); if (mMovementAnimSpeed <= 1.0f) { // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); mMovementAnimationControlled = false; } } mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); } void CharacterController::refreshIdleAnims(CharacterState idle, bool force) { // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) || mMovementState != CharState_None || !mCurrentHit.empty()) && !mPtr.getClass().isBipedal(mPtr)) { resetCurrentIdleState(); return; } if (!force && idle == mIdleState && (mAnimation->isPlaying(mCurrentIdle) || !mAnimQueue.empty())) return; mIdleState = idle; std::string idleGroup = idleStateToAnimGroup(mIdleState); if (idleGroup.empty()) { resetCurrentIdleState(); return; } MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); size_t numLoops = std::numeric_limits::max(); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". bool fallback = mIdleState != CharState_Idle && !mAnimation->hasAnimation(idleGroup); if (fallback) { priority = getIdlePriority(CharState_Idle); idleGroup = idleStateToAnimGroup(CharState_Idle); } if (fallback || mIdleState == CharState_Idle || mIdleState == CharState_SpecialIdle) { std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); if (!weapShortGroup.empty()) { std::string weapIdleGroup = idleGroup; weapIdleGroup += weapShortGroup; if (!mAnimation->hasAnimation(weapIdleGroup)) weapIdleGroup = fallbackShortWeaponGroup(idleGroup); idleGroup = weapIdleGroup; // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation auto& prng = MWBase::Environment::get().getWorld()->getPrng(); numLoops = 1 + Misc::Rng::rollDice(4, prng); } } if (!mAnimation->hasAnimation(idleGroup)) { resetCurrentIdleState(); return; } float startPoint = 0.f; // There is no need to restart anim if the new and old anims are the same. // Just update the number of loops. if (mCurrentIdle == idleGroup) mAnimation->getInfo(mCurrentIdle, &startPoint); clearStateAnimation(mCurrentIdle); mCurrentIdle = idleGroup; mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); } void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // If the current animation is persistent, do not touch it if (isPersistentAnimPlaying()) return; refreshHitRecoilAnims(); refreshJumpAnims(jump, force); refreshMovementAnims(movement, force); // idle handled last as it can depend on the other states refreshIdleAnims(idle, force); } void CharacterController::playDeath(float startpoint, CharacterState death) { mDeathState = death; mCurrentDeath = deathStateToAnimGroup(mDeathState); // Make sure the character was swimming upon death for forward-compatibility if (!MWBase::Environment::get().getWorld()->isSwimming(mPtr)) { if (mDeathState == CharState_SwimDeathKnockDown) mCurrentDeath = "deathknockdown"; else if (mDeathState == CharState_SwimDeathKnockOut) mCurrentDeath = "deathknockout"; } mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). // However, they could still trigger text keys, such as Hit events, or sounds. resetCurrentMovementState(); resetCurrentWeaponState(); resetCurrentHitState(); resetCurrentIdleState(); resetCurrentJumpState(); mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0); } CharacterState CharacterController::chooseRandomDeathState() const { int selected=0; chooseRandomGroup("death", &selected); return static_cast(CharState_Death1 + (selected-1)); } void CharacterController::playRandomDeath(float startpoint) { if (mPtr == getPlayer()) { // The first-person animations do not include death, so we need to // force-switch to third person before playing the death animation. MWBase::Environment::get().getWorld()->useDeathCamera(); } mDeathState = hitStateToDeathState(mHitState); if (mDeathState == CharState_None && MWBase::Environment::get().getWorld()->isSwimming(mPtr)) mDeathState = CharState_SwimDeath; if (mDeathState == CharState_None || !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState))) mDeathState = chooseRandomDeathState(); // Do not interrupt scripted animation by death if (isPersistentAnimPlaying()) return; playDeath(startpoint, mDeathState); } std::string CharacterController::chooseRandomAttackAnimation() const { std::string result; bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); if (isSwimming) result = chooseRandomGroup("swimattack"); if (!isSwimming || !mAnimation->hasAnimation(result)) result = chooseRandomGroup("attack"); return result; } CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) , mAnimation(anim) { if(!mAnimation) return; mAnimation->setTextKeyListener(this); const MWWorld::Class &cls = mPtr.getClass(); if(cls.isActor()) { /* Accumulate along X/Y only for now, until we can figure out how we should * handle knockout and death which moves the character down. */ mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(mPtr, &mWeaponType); if (mWeaponType != ESM::Weapon::None) { mUpperBodyState = UpperCharState_WeapEquiped; mCurrentWeapon = getWeaponAnimation(mWeaponType); } if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand) { mAnimation->showWeapons(true); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass; bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); } mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); } if(!cls.getCreatureStats(mPtr).isDead()) { mIdleState = CharState_Idle; if (cls.getCreatureStats(mPtr).getFallHeight() > 0) mJumpState = JumpState_InAir; } else { const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); if (cStats.isDeathAnimationFinished()) { // Set the death state, but don't play it yet // We will play it in the first frame, but only if no script set the skipAnim flag signed char deathanim = cStats.getDeathAnimation(); if (deathanim == -1) mDeathState = chooseRandomDeathState(); else mDeathState = static_cast(CharState_Death1 + deathanim); mFloatToSurface = false; } // else: nothing to do, will detect death in the next frame and start playing death animation } } else { /* Don't accumulate with non-actors. */ mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); mIdleState = CharState_Idle; } // Do not update animation status for dead actors if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead())) refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); unpersistAnimationState(); } CharacterController::~CharacterController() { if (mAnimation) { persistAnimationState(); mAnimation->setTextKeyListener(nullptr); } } void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { std::string_view evt = key->second; if (evt.substr(0, 7) == "sound: ") { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); return; } auto& charClass = mPtr.getClass(); if (evt.substr(0, 10) == "soundgen: ") { std::string soundgen = std::string(evt.substr(10)); // The event can optionally contain volume and pitch modifiers float volume=1.f, pitch=1.f; if (soundgen.find(' ') != std::string::npos) { std::vector tokens; Misc::StringUtils::split(soundgen, tokens); soundgen = tokens[0]; if (tokens.size() >= 2) { std::stringstream stream; stream << tokens[1]; stream >> volume; } if (tokens.size() >= 3) { std::stringstream stream; stream << tokens[2]; stream >> pitch; } } std::string sound = charClass.getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if (soundgen == "left" || soundgen == "right") { sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } else { sndMgr->playSound3D(mPtr, sound, volume, pitch); } } return; } if (evt.substr(0, groupname.size()) != groupname || evt.substr(groupname.size(), 2) != ": ") { // Not ours, skip it return; } std::string_view action = evt.substr(groupname.size() + 2); if (action == "equip attach") { if (groupname == "shield") mAnimation->showCarriedLeft(true); else mAnimation->showWeapons(true); } else if (action == "unequip detach") { if (groupname == "shield") mAnimation->showCarriedLeft(false); else mAnimation->showWeapons(false); } else if (action == "chop hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (action == "slash hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (action == "thrust hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else if (action == "hit") { if (groupname == "attack1" || groupname == "swimattack1") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else charClass.hit(mPtr, mAttackStrength); } else if (isRandomAttackAnimation(groupname) && action == "start") { std::multimap::const_iterator hitKey = key; std::string hitKeyName = std::string(groupname) + ": hit"; std::string stopKeyName = std::string(groupname) + ": stop"; // Not all animations have a hit key defined. If there is none, the hit happens with the start key. bool hasHitKey = false; while (hitKey != map.end()) { if (hitKey->second == hitKeyName) { hasHitKey = true; break; } if (hitKey->second == stopKeyName) break; ++hitKey; } if (!hasHitKey) { if (groupname == "attack1" || groupname == "swimattack1") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); } } else if (action == "shoot attach") mAnimation->attachArrow(); else if (action == "shoot release") mAnimation->releaseArrow(mAttackStrength); else if (action == "shoot follow attach") mAnimation->attachArrow(); // Make sure this key is actually for the RangeType we are casting. The flame atronach has // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. else if (groupname == "spellcast" && action == mAttackType + " release") { if (mCanCast) MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); mCastingManualSpell = false; mCanCast = false; } else if (groupname == "shield" && action == "block hit") charClass.block(mPtr); else if (groupname == "containeropen" && action == "loot") MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr); } void CharacterController::updatePtr(const MWWorld::Ptr &ptr) { mPtr = ptr; } void CharacterController::updateIdleStormState(bool inwater) const { if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater) { mAnimation->disable("idlestorm"); return; } const auto world = MWBase::Environment::get().getWorld(); if (world->isInStorm()) { osg::Vec3f stormDirection = world->getStormDirection(); osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); stormDirection.normalize(); characterDirection.normalize(); if (stormDirection * characterDirection < -0.5f) { if (!mAnimation->isPlaying("idlestorm")) { int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul); } else { mAnimation->setLoopingEnabled("idlestorm", true); } return; } } if (mAnimation->isPlaying("idlestorm")) { mAnimation->setLoopingEnabled("idlestorm", false); } } bool CharacterController::updateCarriedLeftVisible(const int weaptype) const { // Shields/torches shouldn't be visible during any operation involving two hands // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", // but they are also present in weapon drawing animation. return mAnimation->updateCarriedLeftVisible(weaptype); } bool CharacterController::updateWeaponState(CharacterState idle) { const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); int weaptype = ESM::Weapon::None; if(stats.getDrawState() == DrawState::Weapon) weaptype = ESM::Weapon::HandToHand; else if (stats.getDrawState() == DrawState::Spell) weaptype = ESM::Weapon::Spell; const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); std::string upSoundId; std::string downSoundId; bool weaponChanged = false; if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); if(stats.getDrawState() == DrawState::Spell) weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None) upSoundId = weapon->getClass().getUpSoundId(*weapon); if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None) downSoundId = weapon->getClass().getDownSoundId(*weapon); // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell) downSoundId = mWeapon.getClass().getDownSoundId(mWeapon); MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); if (mWeapon != newWeapon) { mWeapon = newWeapon; weaponChanged = true; } } // For biped actors, blend weapon animations with lower body animations with higher priority MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); if (cls.isBipedal(mPtr)) priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped) { forcestateupdate = true; if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; setAttackingOrSpell(false); mAnimation->showWeapons(true); stats.setAttackingOrSpell(false); } if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) { std::string weapgroup; if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType && mUpperBodyState != UpperCharState_UnEquipingWeap && !isStillWeapon) { // We can not play un-equip animation if weapon changed since last update if (!weaponChanged) { // Note: we do not disable unequipping animation automatically to avoid body desync weapgroup = getWeaponAnimation(mWeaponType); int unequipMask = MWRender::Animation::BlendMask_All; bool useShieldAnims = mAnimation->useShieldAnimations(); if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) { unequipMask = unequipMask |~MWRender::Animation::BlendMask_LeftArm; mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, 1.0f, "unequip start", "unequip stop", 0.0f, 0); } else if (mWeaponType == ESM::Weapon::HandToHand) mAnimation->showCarriedLeft(false); mAnimation->play(weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; mAnimation->detachArrow(); // If we do not have the "unequip detach" key, hide weapon manually. if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0) mAnimation->showWeapons(false); } if(!downSoundId.empty()) { sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f); } } float complete; bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if (!animPlaying || complete >= 1.0f) { // Weapon is changed, no current animation (e.g. unequipping or attack). // Start equipping animation now. if (weaptype != mWeaponType) { forcestateupdate = true; bool useShieldAnims = mAnimation->useShieldAnimations(); if (!useShieldAnims) mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); weapgroup = getWeaponAnimation(weaptype); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass; bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); if (!isStillWeapon) { clearStateAnimation(mCurrentWeapon); if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); int equipMask = MWRender::Animation::BlendMask_All; if (useShieldAnims && weaptype != ESM::Weapon::Spell) { equipMask = equipMask |~MWRender::Animation::BlendMask_LeftArm; mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, 1.0f, "equip start", "equip stop", 0.0f, 0); } mAnimation->play(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; // If we do not have the "equip attach" key, show weapon manually. if (weaptype != ESM::Weapon::Spell) { if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) mAnimation->showWeapons(true); } } } if(isWerewolf) { const MWWorld::ESMStore &store = world->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfEquip", prng); if(sound) { sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } } mWeaponType = weaptype; mCurrentWeapon = weapgroup; if(!upSoundId.empty() && !isStillWeapon) { sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); } } // Make sure that we disabled unequipping animation if (mUpperBodyState == UpperCharState_UnEquipingWeap) { resetCurrentWeaponState(); mWeaponType = ESM::Weapon::None; mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } if(isWerewolf) { if(stats.getStance(MWMechanics::CreatureStats::Stance_Run) && mHasMovedInXY && !world->isSwimming(mPtr) && mWeaponType == ESM::Weapon::None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); } else sndMgr->stopSound3D(mPtr, "WolfRun"); } // Cancel attack if we no longer have ammunition bool ammunition = true; bool isWeapon = false; float weapSpeed = 1.f; if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId); if (isWeapon) { weapSpeed = weapon->get()->mBase->mData.mSpeed; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) ammunition = false; } if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) { if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } } // Combat for actors with persistent animations obviously will be buggy if (isPersistentAnimPlaying()) return forcestateupdate; float complete = 0.f; bool animPlaying = false; ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(getAttackingOrSpell()) { bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { mAttackStrength = 0; // Randomize attacks for non-bipedal creatures if (cls.getType() == ESM::Creature::sRecordId && !cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); } if(mWeaponType == ESM::Weapon::Spell) { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation setAttackingOrSpell(false); if (mPtr == getPlayer()) { // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); } std::string spellid = stats.getSpells().getSelectedSpell(); bool isMagicItem = false; // Play hand VFX and allow castSpell use (assuming an animation is going to be played) if spellcasting is successful. // Manual spellcasting bypasses restrictions. MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success; if (!mCastingManualSpell) spellCastResult = world->startSpellCast(mPtr); mCanCast = spellCastResult == MWWorld::SpellCastState::Success; if (spellid.empty()) { if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); if (inv.getSelectedEnchantItem() != inv.end()) { const MWWorld::Ptr& enchantItem = *inv.getSelectedEnchantItem(); spellid = enchantItem.getClass().getEnchantment(enchantItem); isMagicItem = true; } } } static const bool useCastingAnimations = Settings::Manager::getBool("use magic item animations", "Game"); if (isMagicItem && !useCastingAnimations) { world->breakInvisibility(mPtr); // Enchanted items by default do not use casting animations world->castSpell(mPtr); resetIdle = false; // Spellcasting animation needs to "play" for at least one frame to reset the aiming factor animPlaying = true; mUpperBodyState = UpperCharState_CastingSpell; } // Play the spellcasting animation/VFX if the spellcasting was successful or failed due to insufficient magicka. // Used up powers are exempt from this from some reason. else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed) { world->breakInvisibility(mPtr); MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid, isMagicItem); std::vector effects; const MWWorld::ESMStore &store = world->getStore(); if (isMagicItem) { const ESM::Enchantment *enchantment = store.get().find(spellid); effects = enchantment->mEffects.mList; } else { const ESM::Spell *spell = store.get().find(spellid); effects = spell->mEffects.mList; } if (mCanCast) { const ESM::MagicEffect *effect = store.get().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = world->getStore().get().find ("VFX_Hands"); const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect { if (mAnimation->getNode("Bip01 L Hand")) mAnimation->addEffect( Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, "Bip01 L Hand", effect->mParticle); if (mAnimation->getNode("Bip01 R Hand")) mAnimation->addEffect( Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, "Bip01 R Hand", effect->mParticle); } } const ESM::ENAMstruct &firstEffect = effects.at(0); // first effect used for casting animation std::string startKey; std::string stopKey; if (isRandomAttackAnimation(mCurrentWeapon)) { startKey = "start"; stopKey = "stop"; if (mCanCast) world->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately mCastingManualSpell = false; mCanCast = false; } else { switch(firstEffect.mRange) { case 0: mAttackType = "self"; break; case 1: mAttackType = "touch"; break; case 2: mAttackType = "target"; break; } startKey = mAttackType+" start"; stopKey = mAttackType+" stop"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } else { resetIdle = false; } } else if(mWeaponType == ESM::Weapon::PickProbe) { world->breakInvisibility(mPtr); MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. MWWorld::Ptr target = world->getFacedObject(); std::string resultMessage, resultSound; if(!target.isEmpty()) { if(item.getType() == ESM::Lockpick::sRecordId) Security(mPtr).pickLock(target, item, resultMessage, resultSound); else if(item.getType() == ESM::Probe::sRecordId) Security(mPtr).probeTrap(target, item, resultMessage, resultSound); } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0, 0); mUpperBodyState = UpperCharState_FollowStartToFollowStop; if(!resultMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); if(!resultSound.empty()) sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); } else if (ammunition) { std::string startKey; std::string stopKey; if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { mAttackType = "shoot"; startKey = mAttackType+" start"; stopKey = mAttackType+" min attack"; } else if (isRandomAttackAnimation(mCurrentWeapon)) { startKey = "start"; stopKey = "stop"; } else { if(mPtr == getPlayer()) { if (Settings::Manager::getBool("best attack", "Game")) { if (isWeapon) { MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); } else { // There is no "best attack" for Hand-to-Hand mAttackType = getRandomAttackType(); } } else { mAttackType = getMovementBasedAttackType(); } } // else if (mPtr != getPlayer()) use mAttackType set by AiCombat startKey = mAttackType+" start"; stopKey = mAttackType+" min attack"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, startKey, stopKey, 0.0f, 0); if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { mUpperBodyState = UpperCharState_StartToMinAttack; if (isRandomAttackAnimation(mCurrentWeapon)) { world->breakInvisibility(mPtr); mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); playSwishSound(mAttackStrength); } } } } // We should not break swim and sneak animations if (resetIdle && idle != CharState_IdleSneak && idle != CharState_IdleSwim && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { resetCurrentIdleState(); } if (!animPlaying) animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) mAttackStrength = complete; } else { animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) { world->breakInvisibility(mPtr); float attackStrength = complete; float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); if (minAttackTime == maxAttackTime) { // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); } if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { if(isWerewolf) { const MWWorld::ESMStore &store = world->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); if(sound) sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } else { playSwishSound(attackStrength); } } mAttackStrength = attackStrength; mAnimation->disable(mCurrentWeapon); mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); complete = 0.f; mUpperBodyState = UpperCharState_MaxAttackToMinHit; } else if (isKnockedDown()) { if (mUpperBodyState > UpperCharState_WeapEquiped) { mUpperBodyState = UpperCharState_WeapEquiped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); } } mAnimation->setPitchFactor(0.f); if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { switch (mUpperBodyState) { case UpperCharState_StartToMinAttack: mAnimation->setPitchFactor(complete); break; case UpperCharState_MinAttackToMaxAttack: case UpperCharState_MaxAttackToMinHit: case UpperCharState_MinHitToHit: mAnimation->setPitchFactor(1.f); break; case UpperCharState_FollowStartToFollowStop: if (animPlaying) { // technically we do not need a pitch for crossbow reload animation, // but we should avoid abrupt repositioning if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f)); else mAnimation->setPitchFactor(1.f-complete); } break; default: break; } } if (!animPlaying || complete >= 1.f) { if(mUpperBodyState == UpperCharState_EquipingWeap || mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); // Cancel stagger animation at the end of an attack to avoid abrupt transitions // in favor of a different abrupt transition, like Morrowind if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery()) mAnimation->disable(mCurrentHit); if (animPlaying) mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } else if(mUpperBodyState == UpperCharState_UnEquipingWeap) { if (animPlaying) mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_Nothing; } } if (complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) { std::string start, stop; switch(mUpperBodyState) { case UpperCharState_MinAttackToMaxAttack: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; case UpperCharState_StartToMinAttack: case UpperCharState_MaxAttackToMinHit: { if (mUpperBodyState == UpperCharState_StartToMinAttack) { // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. // Happens if the player did not hold the attack button. // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random. float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); if (getAttackingOrSpell() || minAttackTime == maxAttackTime) { start = mAttackType+" min attack"; stop = mAttackType+" max attack"; mUpperBodyState = UpperCharState_MinAttackToMaxAttack; break; } world->breakInvisibility(mPtr); if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) playSwishSound(0.0f); } if(mAttackType == "shoot") { start = mAttackType+" min hit"; stop = mAttackType+" release"; } else { start = mAttackType+" min hit"; stop = mAttackType+" hit"; } mUpperBodyState = UpperCharState_MinHitToHit; break; } case UpperCharState_MinHitToHit: if(mAttackType == "shoot") { start = mAttackType+" follow start"; stop = mAttackType+" follow stop"; } else { float str = mAttackStrength; start = mAttackType+((str < 0.5f) ? " small follow start" : (str < 1.0f) ? " medium follow start" : " large follow start"); stop = mAttackType+((str < 0.5f) ? " small follow stop" : (str < 1.0f) ? " medium follow stop" : " large follow stop"); } mUpperBodyState = UpperCharState_FollowStartToFollowStop; break; default: break; } // Note: apply crossbow reload animation only for upper body // since blending with movement animations can give weird result. if(!start.empty()) { int mask = MWRender::Animation::BlendMask_All; if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mask = MWRender::Animation::BlendMask_UpperBody; mAnimation->disable(mCurrentWeapon); mAnimation->play(mCurrentWeapon, priorityWeapon, mask, false, weapSpeed, start, stop, 0.0f, 0); } } else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) { clearStateAnimation(mCurrentWeapon); if (isRecovery()) mAnimation->disable(mCurrentHit); mUpperBodyState = UpperCharState_WeapEquiped; } if (cls.hasInventoryStore(mPtr)) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType)) { if (mAnimation->isPlaying("shield")) mAnimation->disable("shield"); mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); } else if (mAnimation->isPlaying("torch")) { mAnimation->disable("torch"); } } mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); return forcestateupdate; } void CharacterController::updateAnimQueue() { if(mAnimQueue.size() > 1) { if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } } if(!mAnimQueue.empty()) mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); const MWWorld::Class &cls = mPtr.getClass(); osg::Vec3f movement(0.f, 0.f, 0.f); float speed = 0.f; updateMagicEffects(); bool isPlayer = mPtr == MWMechanics::getPlayer(); bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); float scale = mPtr.getCellRef().getScale(); static const bool normalizeSpeed = Settings::Manager::getBool("normalise race speed", "Game"); if (!normalizeSpeed && mPtr.getClass().isNpc()) { const ESM::NPC* npc = mPtr.get()->mBase; const ESM::Race* race = world->getStore().get().find(npc->mRace); float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; scale *= weight; } if(!cls.isActor()) updateAnimQueue(); else if(!cls.getCreatureStats(mPtr).isDead()) { bool onground = world->isOnGround(mPtr); bool incapacitated = ((!godmode && cls.getCreatureStats(mPtr).isParalyzed()) || cls.getCreatureStats(mPtr).getKnockedDown()); bool inwater = world->isSwimming(mPtr); bool flying = world->isFlying(mPtr); bool solid = world->isActorCollisionEnabled(mPtr); // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed) bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying && !inwater; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; CreatureStats &stats = cls.getCreatureStats(mPtr); Movement& movementSettings = cls.getMovementSettings(mPtr); //Force Jump Logic bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5); if(!inwater && !flying && solid) { //Force Jump if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) movementSettings.mPosition[2] = onground ? 1 : 0; //Force Move Jump, only jump if they're otherwise moving if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) movementSettings.mPosition[2] = onground ? 1 : 0; } osg::Vec3f rot = cls.getRotationVector(mPtr); osg::Vec3f vec(movementSettings.asVec3()); movementSettings.mSpeedFactor = std::min(vec.length(), 1.f); vec.normalize(); // TODO: Move this check to mwinput. // Joystick analogue movement. // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f) movementSettings.mSpeedFactor *= 2.f; static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) { static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game")); float angle = mPtr.getRefData().getPosition().rot[2]; osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor; osg::Vec2f delta = targetSpeed - mSmoothedSpeed; float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length(); float deltaLen = delta.length(); float maxDelta; if (isFirstPersonPlayer) maxDelta = 1; else if (std::abs(speedDelta) < deltaLen / 2) // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point). maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f); else if (isPlayer && speedDelta < -deltaLen / 2) // As soon as controls are released, mwinput switches player from running to walking. // So stopping should be instant for player, otherwise it causes a small twitch. maxDelta = 1; else // In all other cases speeding up and stopping are smooth. maxDelta = duration * 3.f; if (deltaLen > maxDelta) delta *= maxDelta / deltaLen; mSmoothedSpeed += delta; osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle); movementSettings.mSpeedFactor = newSpeed.normalize(); vec.x() = newSpeed.x(); vec.y() = newSpeed.y(); const float eps = 0.001f; if (movementSettings.mSpeedFactor < eps) { movementSettings.mSpeedFactor = 0; vec.x() = 0; vec.y() = 1; } else if ((vec.y() < 0) != mIsMovingBackward) { if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward) vec.y() = mIsMovingBackward ? -eps : eps; } vec.normalize(); } float effectiveRotation = rot.z(); bool canMove = cls.getMaxSpeed(mPtr) > 0; static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); if (!turnToMovementDirection || isFirstPersonPlayer) { movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; stats.setSideMovementAngle(0); } else if (canMove) { float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState::Nothing || inwater) && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f); if (movementSettings.mIsStrafing) targetMovementAngle = 0; float delta = targetMovementAngle - stats.getSideMovementAngle(); float cosDelta = cosf(delta); if ((vec.y() < 0) == mIsMovingBackward) movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn if (std::abs(delta) < osg::DegreesToRadians(20.0f)) mIsMovingBackward = vec.y() < 0; float maxDelta = osg::PI * duration * (2.5f - cosDelta); delta = std::clamp(delta, -maxDelta, maxDelta); stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); effectiveRotation += delta; } mAnimation->setLegsYawRadians(stats.getSideMovementAngle()); if (stats.getDrawState() == MWMechanics::DrawState::Nothing || inwater) mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2); else mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); if (smoothMovement && !isPlayer && !inwater) mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2); speed = cls.getCurrentSpeed(mPtr); vec.x() *= speed; vec.y() *= speed; if(mHitState != CharState_None && mHitState != CharState_Block && mJumpState == JumpState_None) vec = osg::Vec3f(); CharacterState movestate = CharState_None; CharacterState idlestate = CharState_None; JumpingState jumpstate = JumpState_None; mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; isrunning = isrunning && mHasMovedInXY; // advance athletics if(mHasMovedInXY && isPlayer) { if(inwater) { mSecondsOfSwimming += duration; while(mSecondsOfSwimming > 1) { cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); mSecondsOfSwimming -= 1; } } else if(isrunning && !sneak) { mSecondsOfRunning += duration; while(mSecondsOfRunning > 1) { cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); mSecondsOfRunning -= 1; } } } // reduce fatigue const MWWorld::Store &gmst = world->getStore().get(); float fatigueLoss = 0; static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->mValue.getFloat(); static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->mValue.getFloat(); static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->mValue.getFloat(); static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->mValue.getFloat(); static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->mValue.getFloat(); static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->mValue.getFloat(); static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->mValue.getFloat(); static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->mValue.getFloat(); if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) { const float encumbrance = cls.getNormalizedEncumbrance(mPtr); if (sneak) fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; else { if (inwater) { if (!isrunning) fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; else fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; } else if (isrunning) fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; } } fatigueLoss *= duration; fatigueLoss *= movementSettings.mSpeedFactor; DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); if (!godmode) { fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); cls.getCreatureStats(mPtr).setFatigue(fatigue); } float z = cls.getJump(mPtr); if(sneak || inwater || flying || incapacitated || !solid || z <= 0) vec.z() = 0.0f; bool inJump = true; bool playLandingSound = false; if(!onground && !flying && !inwater && solid) { // In the air (either getting up —ascending part of jump— or falling). jumpstate = JumpState_InAir; static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->mValue.getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->mValue.getFloat(); float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; factor = std::min(1.f, factor); vec.x() *= factor; vec.y() *= factor; vec.z() = 0.0f; } else if(vec.z() > 0.0f && mJumpState != JumpState_InAir) { // Started a jump. if (z > 0) { if(vec.x() == 0 && vec.y() == 0) vec = osg::Vec3f(0.0f, 0.0f, z); else { osg::Vec3f lat (vec.x(), vec.y(), 0.0f); lat.normalize(); vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; } } } else { if (mJumpState == JumpState_InAir && !flying && solid) { float height = cls.getCreatureStats(mPtr).land(isPlayer); float healthLost = 0.f; if (!inwater) healthLost = getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); // inflict fall damages if (!godmode) { DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); float realHealthLost = healthLost * (1.0f - 0.25f * fatigueTerm); health.setCurrent(health.getCurrent() - realHealthLost); cls.getCreatureStats(mPtr).setHealth(health); sndMgr->playSound3D(mPtr, "Health Damage", 1.0f, 1.0f); if (isPlayer) MWBase::Environment::get().getWindowManager()->activateHitOverlay(); } const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); if (healthLost > (acrobaticsSkill * fatigueTerm)) { if (!godmode) cls.getCreatureStats(mPtr).setKnockedDown(true); } else { // report acrobatics progression if (isPlayer) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } if (mPtr.getClass().isNpc()) playLandingSound = true; } if (mAnimation->isPlaying(mCurrentJump)) jumpstate = JumpState_Landing; vec.x() *= scale; vec.y() *= scale; vec.z() = 0.0f; inJump = false; if (movementSettings.mIsStrafing) { if(vec.x() > 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) : (sneak ? CharState_SneakRight : (isrunning ? CharState_RunRight : CharState_WalkRight))); else if(vec.x() < 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); } else if (vec.length2() > 0.0f) { if (vec.y() >= 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward))); else movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } else { // Do not play turning animation for player if rotation speed is very slow. // Actual threshold should take framerate in account. float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration; // It seems only bipedal actors use turning animations. // Also do not use turning animations in the first-person view and when sneaking. if (!sneak && !isFirstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) { if(effectiveRotation > rotationThreshold) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; else if(effectiveRotation < -rotationThreshold) movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; } } } if (playLandingSound) { std::string sound; osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3()); if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr)) sound = "DefaultLandWater"; else if (onground) sound = "DefaultLand"; if (!sound.empty()) sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } if (turnToMovementDirection && !isFirstPersonPlayer && (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward || movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack)) { float swimmingPitch = mAnimation->getBodyPitchRadians(); float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; float maxSwimPitchDelta = 3.0f * duration; swimmingPitch += std::clamp(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); mAnimation->setBodyPitchRadians(swimmingPitch); } else mAnimation->setBodyPitchRadians(0); static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game"); if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection) { static const float swimUpwardCoef = Settings::Manager::getFloat("swim upward coef", "Game"); static const float swimForwardCoef = sqrtf(1.0f - swimUpwardCoef * swimUpwardCoef); vec.z() = std::abs(vec.y()) * swimUpwardCoef; vec.y() *= swimForwardCoef; } // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering if (isPlayer) { float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; float complete; bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); if (movestate == CharState_None && jumpstate == JumpState_None && isTurning()) { if (animPlaying && complete < threshold) movestate = mMovementState; } } else { if (mPtr.getClass().isBipedal(mPtr)) { if (mTurnAnimationThreshold > 0) mTurnAnimationThreshold -= duration; if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft || movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) { mTurnAnimationThreshold = 0.05f; } else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0) { movestate = mMovementState; } } } if (movestate != CharState_None) { clearAnimQueue(); jumpstate = JumpState_None; } if(mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) { if (inwater) idlestate = CharState_IdleSwim; else if (sneak && !inJump) idlestate = CharState_IdleSneak; else idlestate = CharState_Idle; } else updateAnimQueue(); if (!mSkipAnim) { refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState(idlestate)); updateIdleStormState(inwater); } if (inJump) mMovementAnimationControlled = false; if (isTurning()) { // Adjust animation speed from 1.0 to 1.5 multiplier if (duration > 0) { float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI)); mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f)); } } else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) { // Vanilla caps the played animation speed. const float maxSpeedMult = 10.f; const float speedMult = speed / mMovementAnimSpeed; mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); // Make sure the actual speed is the "expected" speed even though the animation is slower scale *= std::max(1.f, speedMult / maxSpeedMult); } if (!mSkipAnim) { if(!isKnockedDown() && !isKnockedOut()) { if (rot != osg::Vec3f()) world->rotateObject(mPtr, rot, true); } else //avoid z-rotating for knockdown { if (rot.x() != 0 && rot.y() != 0) { rot.z() = 0.0f; world->rotateObject(mPtr, rot, true); } } if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; if (movement.z() == 0.f) movementSettings.mPosition[2] = 0; // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. if (!mSkipAnim) updateHeadTracking(duration); } else if(cls.getCreatureStats(mPtr).isDead()) { // initial start of death animation for actors that started the game as dead // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) { // Fast-forward death animation to end for persisting corpses or corpses after end of death animation if (cls.isPersistent(mPtr) || cls.getCreatureStats(mPtr).isDeathAnimationFinished()) playDeath(1.f, mDeathState); } } bool isPersist = isPersistentAnimPlaying(); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if(duration > 0.0f) moved /= duration; else moved = osg::Vec3f(0.f, 0.f, 0.f); moved.x() *= scale; moved.y() *= scale; // Ensure we're moving in generally the right direction... if (speed > 0.f && moved != osg::Vec3f()) { float l = moved.length(); if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 || std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 || std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) { moved = movement; // For some creatures getSpeed doesn't work, so we adjust speed to the animation. // TODO: Fix Creature::getSpeed. float newLength = moved.length(); if (newLength > 0 && !cls.isNpc()) moved *= (l / newLength); } } if (mFloatToSurface && cls.isActor()) { if (cls.getCreatureStats(mPtr).isDead() || (!godmode && cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0)) { moved.z() = 1.0; } } // Update movement if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); } void CharacterController::persistAnimationState() const { ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { if (!iter->mPersist) continue; ESM::AnimationState::ScriptedAnimation anim; anim.mGroup = iter->mGroup; if (iter == mAnimQueue.begin()) { anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); float complete; mAnimation->getInfo(anim.mGroup, &complete, nullptr); anim.mTime = complete; } else { anim.mLoopCount = iter->mLoopCount; anim.mTime = 0.f; } state.mScriptedAnims.push_back(anim); } } void CharacterController::unpersistAnimationState() { const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); if (!state.mScriptedAnims.empty()) { clearAnimQueue(); for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) { AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; entry.mPersist = true; mAnimQueue.push_back(entry); } const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); float complete = anim.mTime; if (anim.mAbsolute) { float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); float time = std::clamp(anim.mTime, start, stop); complete = (time - start) / (stop - start); } clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", complete, anim.mLoopCount, loopfallback); } } bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) { if(!mAnimation || !mAnimation->hasAnimation(groupname)) return false; // We should not interrupt persistent animations by non-persistent ones if (isPersistentAnimPlaying() && !persist) return true; // If this animation is a looped animation (has a "loop start" key) that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and remove any other animations that were queued. // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && mAnimation->isPlaying(groupname)) { float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) { mAnimQueue.resize(1); return true; } } count = std::max(count, 1); AnimationQueueEntry entry; entry.mGroup = groupname; entry.mLoopCount = count-1; entry.mPersist = persist; if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { clearAnimQueue(persist); clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); } else { mAnimQueue.resize(1); } // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing if (groupname == "idle") entry.mPersist = false; mAnimQueue.push_back(entry); return true; } void CharacterController::skipAnim() { mSkipAnim = true; } bool CharacterController::isPersistentAnimPlaying() const { if (!mAnimQueue.empty()) { const AnimationQueueEntry& first = mAnimQueue.front(); return first.mPersist && isAnimPlaying(first.mGroup); } return false; } bool CharacterController::isAnimPlaying(const std::string &groupName) const { if(mAnimation == nullptr) return false; return mAnimation->isPlaying(groupName); } void CharacterController::clearAnimQueue(bool clearPersistAnims) { // Do not interrupt scripted animations, if we want to keep them if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); if (clearPersistAnims) { mAnimQueue.clear(); return; } for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { if (!it->mPersist) it = mAnimQueue.erase(it); else ++it; } } void CharacterController::forceStateUpdate() { if(!mAnimation) return; clearAnimQueue(); // Make sure we canceled the current attack or spellcasting, // because we disabled attack animations anyway. mCanCast = false; mCastingManualSpell = false; setAttackingOrSpell(false); if (mUpperBodyState != UpperCharState_Nothing) mUpperBodyState = UpperCharState_WeapEquiped; refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); if(mDeathState != CharState_None) { playRandomDeath(); } mAnimation->runAnimation(0.f); } CharacterController::KillResult CharacterController::kill() { if (mDeathState == CharState_None) { playRandomDeath(); resetCurrentIdleState(); return Result_DeathAnimStarted; } MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); if (isAnimPlaying(mCurrentDeath)) return Result_DeathAnimPlaying; if (!cStats.isDeathAnimationFinished()) { cStats.setDeathAnimationFinished(true); return Result_DeathAnimJustFinished; } return Result_DeathAnimFinished; } void CharacterController::resurrect() { if(mDeathState == CharState_None) return; resetCurrentDeathState(); mWeaponType = ESM::Weapon::None; } void CharacterController::updateContinuousVfx() const { // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. // Stop any effects that are no longer active std::vector effects; mAnimation->getLoopingEffects(effects); for (int effectId : effects) { if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(effectId)).getMagnitude() <= 0) mAnimation->removeEffect(effectId); } } void CharacterController::updateMagicEffects() const { if (!mPtr.getClass().isActor()) return; float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); mAnimation->setLightEffect(light); // If you're dead you don't care about whether you've started/stopped being a vampire or not if (mPtr.getClass().getCreatureStats(mPtr).isDead()) return; bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; mAnimation->setVampire(vampire); } void CharacterController::setVisibility(float visibility) const { // We should take actor's invisibility in account if (mPtr.getClass().isActor()) { float alpha = 1.f; if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). { if (mPtr == getPlayer()) alpha = 0.25f; else alpha = 0.05f; } float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); if (chameleon) { alpha *= std::clamp(1.f - chameleon / 100.f, 0.25f, 0.75f); } visibility = std::min(visibility, alpha); } // TODO: implement a dithering shader rather than just change object transparency. mAnimation->setAlpha(visibility); } std::string_view CharacterController::getMovementBasedAttackType() const { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward return "thrust"; if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway return "slash"; return "chop"; } bool CharacterController::isRandomAttackAnimation(std::string_view group) { return (group == "attack1" || group == "swimattack1" || group == "attack2" || group == "swimattack2" || group == "attack3" || group == "swimattack3"); } bool CharacterController::isAttackPreparing() const { return mUpperBodyState == UpperCharState_StartToMinAttack || mUpperBodyState == UpperCharState_MinAttackToMaxAttack; } bool CharacterController::isCastingSpell() const { return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell; } bool CharacterController::isReadyToBlock() const { return updateCarriedLeftVisible(mWeaponType); } bool CharacterController::isKnockedDown() const { return mHitState == CharState_KnockDown || mHitState == CharState_SwimKnockDown; } bool CharacterController::isKnockedOut() const { return mHitState == CharState_KnockOut || mHitState == CharState_SwimKnockOut; } bool CharacterController::isTurning() const { return mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight || mMovementState == CharState_SwimTurnLeft || mMovementState == CharState_SwimTurnRight; } bool CharacterController::isRecovery() const { return mHitState == CharState_Hit || mHitState == CharState_SwimHit; } bool CharacterController::isAttackingOrSpell() const { return mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped; } bool CharacterController::isSneaking() const { return mIdleState == CharState_IdleSneak || mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; } bool CharacterController::isRunning() const { return mMovementState == CharState_RunForward || mMovementState == CharState_RunBack || mMovementState == CharState_RunLeft || mMovementState == CharState_RunRight || mMovementState == CharState_SwimRunForward || mMovementState == CharState_SwimRunBack || mMovementState == CharState_SwimRunLeft || mMovementState == CharState_SwimRunRight; } void CharacterController::setAttackingOrSpell(bool attackingOrSpell) const { mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); } void CharacterController::castSpell(const std::string& spellId, bool manualSpell) { setAttackingOrSpell(true); mCastingManualSpell = manualSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); } void CharacterController::setAIAttackType(std::string_view attackType) { mAttackType = attackType; } std::string_view CharacterController::getRandomAttackType() { MWBase::World* world = MWBase::Environment::get().getWorld(); float random = Misc::Rng::rollProbability(world->getPrng()); if (random >= 2/3.f) return "thrust"; if (random >= 1/3.f) return "slash"; return "chop"; } bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) && mUpperBodyState <= UpperCharState_WeapEquiped; } bool CharacterController::readyToStartAttack() const { if (mHitState != CharState_None && mHitState != CharState_Block) return false; return mUpperBodyState == UpperCharState_WeapEquiped; } float CharacterController::getAttackStrength() const { return mAttackStrength; } bool CharacterController::getAttackingOrSpell() const { return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell(); } void CharacterController::setActive(int active) const { mAnimation->setActive(active); } void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) { mHeadTrackTarget = target; } void CharacterController::playSwishSound(float attackStrength) const { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); std::string sound = "Weapon Swish"; if(attackStrength < 0.5f) sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack else if(attackStrength < 1.0f) sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack else sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack } void CharacterController::updateHeadTracking(float duration) { const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (!head) return; double zAngleRadians = 0.f; double xAngleRadians = 0.f; if (!mHeadTrackTarget.isEmpty()) { osg::NodePathList nodepaths = head->getParentalNodePaths(); if (nodepaths.empty()) return; osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3f headPos = mat.getTrans(); osg::Vec3f direction; if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) { const osg::Node* node = anim->getNode("Head"); if (node == nullptr) node = anim->getNode("Bip01 Head"); if (node != nullptr) { nodepaths = node->getParentalNodePaths(); if (!nodepaths.empty()) direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; } else // no head node to look at, fall back to look at center of collision box direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false); } direction.normalize(); if (!mPtr.getRefData().getBaseNode()) return; const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y()); zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw(); zAngleRadians *= (1 - direction.z() * direction.z()); xAngleRadians = std::asin(direction.z()); } const double xLimit = osg::DegreesToRadians(40.0); const double zLimit = osg::DegreesToRadians(30.0); double zLimitOffset = mAnimation->getUpperBodyYawRadians(); xAngleRadians = std::clamp(xAngleRadians, -xLimit, xLimit); zAngleRadians = std::clamp(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); float factor = duration*5; factor = std::min(factor, 1.f); xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians; zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians; mAnimation->setHeadPitch(xAngleRadians); mAnimation->setHeadYaw(zAngleRadians); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/character.hpp000066400000000000000000000200771445372753700236550ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_CHARACTER_HPP #define GAME_MWMECHANICS_CHARACTER_HPP #include #include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" #include "../mwrender/animation.hpp" #include "weapontype.hpp" namespace MWWorld { class InventoryStore; } namespace MWRender { class Animation; } namespace MWMechanics { struct Movement; class CreatureStats; enum Priority { Priority_Default, Priority_WeaponLowerBody, Priority_SneakIdleLowerBody, Priority_SwimIdle, Priority_Jump, Priority_Movement, Priority_Hit, Priority_Weapon, Priority_Block, Priority_Knockdown, Priority_Torch, Priority_Storm, Priority_Death, Priority_Persistent, Num_Priorities }; enum CharacterState { CharState_None, CharState_SpecialIdle, CharState_Idle, CharState_IdleSwim, CharState_IdleSneak, CharState_WalkForward, CharState_WalkBack, CharState_WalkLeft, CharState_WalkRight, CharState_SwimWalkForward, CharState_SwimWalkBack, CharState_SwimWalkLeft, CharState_SwimWalkRight, CharState_RunForward, CharState_RunBack, CharState_RunLeft, CharState_RunRight, CharState_SwimRunForward, CharState_SwimRunBack, CharState_SwimRunLeft, CharState_SwimRunRight, CharState_SneakForward, CharState_SneakBack, CharState_SneakLeft, CharState_SneakRight, CharState_TurnLeft, CharState_TurnRight, CharState_SwimTurnLeft, CharState_SwimTurnRight, CharState_Death1, CharState_Death2, CharState_Death3, CharState_Death4, CharState_Death5, CharState_SwimDeath, CharState_SwimDeathKnockDown, CharState_SwimDeathKnockOut, CharState_DeathKnockDown, CharState_DeathKnockOut, CharState_Hit, CharState_SwimHit, CharState_KnockDown, CharState_KnockOut, CharState_SwimKnockDown, CharState_SwimKnockOut, CharState_Block }; enum UpperBodyCharacterState { UpperCharState_Nothing, UpperCharState_EquipingWeap, UpperCharState_UnEquipingWeap, UpperCharState_WeapEquiped, UpperCharState_StartToMinAttack, UpperCharState_MinAttackToMaxAttack, UpperCharState_MaxAttackToMinHit, UpperCharState_MinHitToHit, UpperCharState_FollowStartToFollowStop, UpperCharState_CastingSpell }; enum JumpingState { JumpState_None, JumpState_InAir, JumpState_Landing }; struct WeaponInfo; class CharacterController : public MWRender::Animation::TextKeyListener { MWWorld::Ptr mPtr; MWWorld::Ptr mWeapon; MWRender::Animation *mAnimation; struct AnimationQueueEntry { std::string mGroup; size_t mLoopCount; bool mPersist; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; CharacterState mIdleState{CharState_None}; std::string mCurrentIdle; CharacterState mMovementState{CharState_None}; std::string mCurrentMovement; float mMovementAnimSpeed{0.f}; bool mAdjustMovementAnimSpeed{false}; bool mHasMovedInXY{false}; bool mMovementAnimationControlled{true}; CharacterState mDeathState{CharState_None}; std::string mCurrentDeath; bool mFloatToSurface{true}; CharacterState mHitState{CharState_None}; std::string mCurrentHit; UpperBodyCharacterState mUpperBodyState{UpperCharState_Nothing}; JumpingState mJumpState{JumpState_None}; std::string mCurrentJump; int mWeaponType{ESM::Weapon::None}; std::string mCurrentWeapon; float mAttackStrength{0.f}; bool mSkipAnim{false}; // counted for skill increase float mSecondsOfSwimming{0.f}; float mSecondsOfRunning{0.f}; MWWorld::ConstPtr mHeadTrackTarget; float mTurnAnimationThreshold{0.f}; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust bool mCanCast{false}; bool mCastingManualSpell{false}; bool mIsMovingBackward{false}; osg::Vec2f mSmoothedSpeed; std::string_view getMovementBasedAttackType() const; void clearStateAnimation(std::string &anim) const; void resetCurrentJumpState(); void resetCurrentMovementState(); void resetCurrentIdleState(); void resetCurrentHitState(); void resetCurrentWeaponState(); void resetCurrentDeathState(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(); void refreshJumpAnims(JumpingState jump, bool force=false); void refreshMovementAnims(CharacterState movement, bool force=false); void refreshIdleAnims(CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(CharacterState idle); void updateIdleStormState(bool inwater) const; std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); bool isPersistentAnimPlaying() const; void updateAnimQueue(); void updateHeadTracking(float duration); void updateMagicEffects() const; void playDeath(float startpoint, CharacterState death); CharacterState chooseRandomDeathState() const; void playRandomDeath(float startpoint = 0.0f); /// choose a random animation group with \a prefix and numeric suffix /// @param num if non-nullptr, the chosen animation number will be written here std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const; bool updateCarriedLeftVisible(int weaptype) const; std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr) const; std::string_view getWeaponAnimation(int weaponType) const; std::string_view getWeaponShortGroup(int weaponType) const; bool getAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell) const; public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); CharacterController(const CharacterController&) = delete; CharacterController(CharacterController&&) = delete; const MWWorld::Ptr& getPtr() const { return mPtr; } void handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) override; // Be careful when to call this, see comment in Actors void updateContinuousVfx() const; void updatePtr(const MWWorld::Ptr &ptr); void update(float duration); bool onOpen() const; void onClose() const; void persistAnimationState() const; void unpersistAnimationState(); bool playGroup(const std::string &groupname, int mode, int count, bool persist=false); void skipAnim(); bool isAnimPlaying(const std::string &groupName) const; enum KillResult { Result_DeathAnimStarted, Result_DeathAnimPlaying, Result_DeathAnimJustFinished, Result_DeathAnimFinished }; KillResult kill(); void resurrect(); bool isDead() const { return mDeathState != CharState_None; } void forceStateUpdate(); bool isAttackPreparing() const; bool isCastingSpell() const; bool isReadyToBlock() const; bool isKnockedDown() const; bool isKnockedOut() const; bool isRecovery() const; bool isSneaking() const; bool isRunning() const; bool isTurning() const; bool isAttackingOrSpell() const; void setVisibility(float visibility) const; void castSpell(const std::string& spellId, bool manualSpell=false); void setAIAttackType(std::string_view attackType); static std::string_view getRandomAttackType(); bool readyToPrepareAttack() const; bool readyToStartAttack() const; float getAttackStrength() const; /// @see Animation::setActive void setActive(int active) const; /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. void setHeadTrackTarget(const MWWorld::ConstPtr& target); void playSwishSound(float attackStrength) const; }; } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ openmw-openmw-0.48.0/apps/openmw/mwmechanics/combat.cpp000066400000000000000000000604731445372753700231650ustar00rootroot00000000000000 #include "combat.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "npcstats.hpp" #include "movement.hpp" #include "spellcasting.hpp" #include "spellresistance.hpp" #include "difficultyscaling.hpp" #include "actorutil.hpp" #include "pathfinding.hpp" namespace { float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal) { return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); } } namespace MWMechanics { bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, const bool fromProjectile) { std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; if (!enchantmentName.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( enchantmentName); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; cast.cast(object, 0, false); // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills if(!victim.isEmpty() && victim.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); return true; } } return false; } bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) return false; MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); if (blockerStats.getKnockedDown() // Used for both knockout or knockdown || blockerStats.getHitRecovery() || blockerStats.isParalyzed()) return false; if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) return false; MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) return false; if (!blocker.getRefData().getBaseNode()) return false; // shouldn't happen float angleDegrees = osg::RadiansToDegrees( signedAngleRadians ( (attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()), blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0), osg::Vec3f(0,0,1))); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCombatBlockLeftAngle = gmst.find("fCombatBlockLeftAngle")->mValue.getFloat(); if (angleDegrees < fCombatBlockLeftAngle) return false; static const float fCombatBlockRightAngle = gmst.find("fCombatBlockRightAngle")->mValue.getFloat(); if (angleDegrees > fCombatBlockRightAngle) return false; MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); float enemySwing = attackStrength; static const float fSwingBlockMult = gmst.find("fSwingBlockMult")->mValue.getFloat(); static const float fSwingBlockBase = gmst.find("fSwingBlockBase")->mValue.getFloat(); float swingTerm = enemySwing * fSwingBlockMult + fSwingBlockBase; float blockerTerm = blockTerm * swingTerm; if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) { static const float fBlockStillBonus = gmst.find("fBlockStillBonus")->mValue.getFloat(); blockerTerm *= fBlockStillBonus; } blockerTerm *= blockerStats.getFatigueTerm(); float attackerSkill = 0; if (weapon.isEmpty()) attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand); else attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); float attackerTerm = attackerSkill + 0.2f * attackerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); attackerTerm *= attackerStats.getFatigueTerm(); static const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); static const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); int x = std::clamp(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) < x) { // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); shieldhealth -= std::min(shieldhealth, int(damage)); shield->getCellRef().setCharge(shieldhealth); if (shieldhealth == 0) inv.unequipItem(*shield, blocker); // Reduce blocker fatigue static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); static const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); MWMechanics::DynamicStat fatigue = blockerStats.getFatigue(); float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker); normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult; if (!weapon.isEmpty()) fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueBlockMult; fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); blockerStats.setFatigue(fatigue); blockerStats.setBlock(true); if (blocker == getPlayer()) blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); return true; } return false; } bool isNormalWeapon(const MWWorld::Ptr &weapon) { if (weapon.isEmpty()) return false; const int flags = weapon.get()->mBase->mData.mFlags; bool isSilver = flags & ESM::Weapon::Silver; bool isMagical = flags & ESM::Weapon::Magical; bool isEnchanted = !weapon.getClass().getEnchantment(weapon).empty(); return !isSilver && !isMagical && (!isEnchanted || !Settings::Manager::getBool("enchanted weapons are magical", "Game")); } void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage) { if (damage == 0 || weapon.isEmpty() || !isNormalWeapon(weapon)) return; const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); const float resistance = effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f; const float weakness = effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f; damage *= 1.f - std::min(1.f, resistance-weakness); if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } void applyWerewolfDamageMult(const MWWorld::Ptr &actor, const MWWorld::Ptr &weapon, float &damage) { if (damage == 0 || weapon.isEmpty() || !actor.getClass().isNpc()) return; const int flags = weapon.get()->mBase->mData.mFlags; bool isSilver = flags & ESM::Weapon::Silver; if (isSilver && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); damage *= store.get().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat(); } } void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); bool validVictim = !victim.isEmpty() && victim.getClass().isActor(); int weaponSkill = ESM::Skill::Marksman; if (!weapon.isEmpty()) weaponSkill = weapon.getClass().getEquipmentSkill(weapon); float damage = 0.f; if (validVictim) { if (attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int skillValue = attacker.getClass().getSkill(attacker, weaponSkill); if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue)) { victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); return; } const unsigned char* attack = weapon.get()->mBase->mData.mChop; damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage // Arrow/bolt damage // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon attack = projectile.get()->mBase->mData.mChop; damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); adjustWeaponDamage(damage, weapon, attacker); } reduceWeaponCondition(damage, validVictim, weapon, attacker); if (validVictim) { if (weapon == projectile || Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game") || isNormalWeapon(weapon)) resistNormalWeapon(victim, attacker, projectile, damage); applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); bool unaware = attacker == getPlayer() && !sequence.isInCombat() && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown(); if (knockedDown || unaware) { static const float fCombatKODamageMult = gmst.find("fCombatKODamageMult")->mValue.getFloat(); damage *= fCombatKODamageMult; if (!knockedDown) MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } } // Apply "On hit" effect of the projectile bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); if (validVictim) { // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) { static const float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); } } float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) { MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); float defenseTerm = 0; MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); if (victimStats.getFatigue().getCurrent() >= 0) { // Maybe we should keep an aware state for actors updated every so often instead of testing every time bool unaware = (!victimStats.getAiSequence().isInCombat()) && (attacker == getPlayer()) && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); if (!(victimStats.getKnockedDown() || victimStats.isParalyzed() || unaware )) { defenseTerm = victimStats.getEvasion(); } static const float fCombatInvisoMult = gmst.find("fCombatInvisoMult")->mValue.getFloat(); defenseTerm += std::min(100.f, fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()); } float attackTerm = skillValue + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); attackTerm *= stats.getFatigueTerm(); attackTerm += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude() - mageffects.get(ESM::MagicEffect::Blind).getMagnitude(); return round(attackTerm - defenseTerm); } void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) { // Don't let elemental shields harm the player in god mode. bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (godmode) return; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (int i=0; i<3; ++i) { float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); if (!magnitude) continue; CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction) + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified() + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); float fatigueMax = attackerStats.getFatigue().getModified(); float fatigueCurrent = attackerStats.getFatigue().getCurrent(); float normalisedFatigue = floor(fatigueMax)==0 ? 1 : std::max (0.0f, (fatigueCurrent/fatigueMax)); saveTerm *= 1.25f * normalisedFatigue; float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng)); int element = ESM::MagicEffect::FireDamage; if (i == 1) element = ESM::MagicEffect::ShockDamage; if (i == 2) element = ESM::MagicEffect::FrostDamage; float elementResistance = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects()); x = std::min(100.f, x + elementResistance); static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->mValue.getFloat(); x = fElementalShieldMult * magnitude * (1.f - 0.01f * x); // Note swapped victim and attacker, since the attacker takes the damage here. x = scaleDamage(x, victim, attacker); MWMechanics::DynamicStat health = attackerStats.getHealth(); health.setCurrent(health.getCurrent() - x); attackerStats.setHealth(health); MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f); } } void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker) { if (weapon.isEmpty()) return; if (!hit) damage = 0.f; const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); if(weaphashealth) { int weaphealth = weapon.getClass().getItemHealth(weapon); bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); // weapon condition does not degrade when godmode is on if (!godmode) { const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->mValue.getFloat(); float x = std::max(1.f, fWeaponDamageMult * damage); weaphealth -= std::min(int(x), weaphealth); weapon.getCellRef().setCharge(weaphealth); } // Weapon broken? unequip it if (weaphealth == 0) weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); } } void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon, const MWWorld::Ptr& attacker) { if (weapon.isEmpty()) return; const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); if (weaphashealth) { damage *= weapon.getClass().getItemNormalizedHealth(weapon); } static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get() .find("fDamageStrengthBase")->mValue.getFloat(); static const float fDamageStrengthMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fDamageStrengthMult")->mValue.getFloat(); damage *= fDamageStrengthBase + (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1f); } void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); static const float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); static const float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); damage *= minstrike + ((maxstrike-minstrike)*attackStrength); MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim); healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown(); bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); // Options in the launcher's combo box: unarmedFactorsStrengthComboBox // 0 = Do not factor strength into hand-to-hand combat. // 1 = Factor into werewolf hand-to-hand combat. // 2 = Ignore werewolves. int factorStrength = Settings::Manager::getInt("strength influences hand to hand", "Game"); if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf)) { damage *= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() / 40.0f; } if(isWerewolf) { healthdmg = true; // GLOB instead of GMST because it gets updated during a quest damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult"); } if (healthdmg) { static const float fHandtoHandHealthPer = store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); damage *= fHandtoHandHealthPer; } MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfHit", prng); if(sound) sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f); } else if (!healthdmg) sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); } void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon, float attackStrength) { // somewhat of a guess, but using the weapon weight makes sense const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); static const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); static const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); static const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); MWMechanics::DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (!godmode) { float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; if (!weapon.isEmpty()) fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueMult; fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); } } float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) { osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); float d = getAggroDistance(actor1, pos1, pos2); static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( "iFightDistanceBase")->mValue.getInteger(); static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( "fFightDistanceMultiplier")->mValue.getFloat(); return (iFightDistanceBase - fFightDistanceMultiplier * d); } float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) { if (canActorMoveByZAxis(actor)) return distanceIgnoreZ(lhs, rhs); return distance(lhs, rhs); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/combat.hpp000066400000000000000000000053521445372753700231650ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_COMBAT_H #define OPENMW_MECHANICS_COMBAT_H namespace osg { class Vec3f; } namespace MWWorld { class Ptr; } namespace MWMechanics { bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, const bool fromProjectile=false); /// @return can we block the attack? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); /// @return does normal weapon resistance and weakness apply to the weapon? bool isNormalWeapon (const MWWorld::Ptr& weapon); void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); void applyWerewolfDamageMult (const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float &damage); /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt /// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength); /// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); /// Applies damage to attacker based on the victim's elemental shields. void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); /// @param damage Unmitigated weapon damage of the attack /// @param hit Was the attack successful? /// @param weapon The weapon used. /// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); /// Adjust weapon damage based on its condition. A used weapon will be less effective. void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength); /// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength); float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/creaturecustomdataresetter.hpp000066400000000000000000000006211445372753700273670ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_CREATURECUSTOMDATARESETTER_H #define OPENMW_MWMECHANICS_CREATURECUSTOMDATARESETTER_H #include "../mwworld/ptr.hpp" namespace MWMechanics { struct CreatureCustomDataResetter { MWWorld::Ptr mPtr; ~CreatureCustomDataResetter() { if (!mPtr.isEmpty()) mPtr.getRefData().setCustomData({}); } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/creaturestats.cpp000066400000000000000000000456501445372753700246110ustar00rootroot00000000000000#include "creaturestats.hpp" #include #include #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" namespace MWMechanics { int CreatureStats::sActorId = 0; CreatureStats::CreatureStats() : mDrawState (DrawState::Nothing), mDead (false), mDeathAnimationFinished(false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) , mAttackingOrSpell(false) { } const AiSequence& CreatureStats::getAiSequence() const { return mAiSequence; } AiSequence& CreatureStats::getAiSequence() { return mAiSequence; } float CreatureStats::getFatigueTerm() const { float max = getFatigue().getModified(); float current = getFatigue().getCurrent(); float normalised = std::floor(max) == 0 ? 1 : std::max (0.0f, current / max); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fFatigueBase = gmst.find("fFatigueBase")->mValue.getFloat(); static const float fFatigueMult = gmst.find("fFatigueMult")->mValue.getFloat(); return fFatigueBase - fFatigueMult * (1-normalised); } const AttributeValue &CreatureStats::getAttribute(int index) const { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } return mAttributes[index]; } const DynamicStat &CreatureStats::getHealth() const { return mDynamic[0]; } const DynamicStat &CreatureStats::getMagicka() const { return mDynamic[1]; } const DynamicStat &CreatureStats::getFatigue() const { return mDynamic[2]; } const Spells &CreatureStats::getSpells() const { return mSpells; } const ActiveSpells &CreatureStats::getActiveSpells() const { return mActiveSpells; } const MagicEffects &CreatureStats::getMagicEffects() const { return mMagicEffects; } int CreatureStats::getLevel() const { return mLevel; } Stat CreatureStats::getAiSetting (AiSetting index) const { return mAiSettings[static_cast>(index)]; } const DynamicStat &CreatureStats::getDynamic(int index) const { if (index < 0 || index > 2) { throw std::runtime_error("dynamic stat index is out of range"); } return mDynamic[index]; } Spells &CreatureStats::getSpells() { return mSpells; } ActiveSpells &CreatureStats::getActiveSpells() { return mActiveSpells; } MagicEffects &CreatureStats::getMagicEffects() { return mMagicEffects; } void CreatureStats::setAttribute(int index, float base) { AttributeValue current = getAttribute(index); current.setBase(base); setAttribute(index, current); } void CreatureStats::setAttribute(int index, const AttributeValue &value) { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } const AttributeValue& currentValue = mAttributes[index]; if (value != currentValue) { mAttributes[index] = value; if (index == ESM::Attribute::Intelligence) recalculateMagicka(); else if (index == ESM::Attribute::Strength || index == ESM::Attribute::Willpower || index == ESM::Attribute::Agility || index == ESM::Attribute::Endurance) { float strength = getAttribute(ESM::Attribute::Strength).getModified(); float willpower = getAttribute(ESM::Attribute::Willpower).getModified(); float agility = getAttribute(ESM::Attribute::Agility).getModified(); float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance)); fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true); setFatigue(fatigue); } } } void CreatureStats::setHealth(const DynamicStat &value) { setDynamic (0, value); } void CreatureStats::setMagicka(const DynamicStat &value) { setDynamic (1, value); } void CreatureStats::setFatigue(const DynamicStat &value) { setDynamic (2, value); } void CreatureStats::setDynamic (int index, const DynamicStat &value) { if (index < 0 || index > 2) throw std::runtime_error("dynamic stat index is out of range"); mDynamic[index] = value; if (index==0 && mDynamic[index].getCurrent()<1) { if (!mDead) mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); mDead = true; mDynamic[index].setCurrent(0); } } void CreatureStats::setLevel(int level) { mLevel = level; } void CreatureStats::modifyMagicEffects(const MagicEffects &effects) { bool recalc = effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier(); mMagicEffects.setModifiers(effects); if(recalc) recalculateMagicka(); } void CreatureStats::setAiSetting (AiSetting index, Stat value) { mAiSettings[static_cast>(index)] = value; } void CreatureStats::setAiSetting (AiSetting index, int base) { Stat stat = getAiSetting(index); stat.setBase(base); setAiSetting(index, stat); } bool CreatureStats::isParalyzed() const { return mMagicEffects.get(ESM::MagicEffect::Paralyze).getMagnitude() > 0; } bool CreatureStats::isDead() const { return mDead; } bool CreatureStats::isDeathAnimationFinished() const { return mDeathAnimationFinished; } void CreatureStats::setDeathAnimationFinished(bool finished) { mDeathAnimationFinished = finished; } void CreatureStats::notifyDied() { mDied = true; } bool CreatureStats::hasDied() const { return mDied; } void CreatureStats::clearHasDied() { mDied = false; } bool CreatureStats::hasBeenMurdered() const { return mMurdered; } void CreatureStats::notifyMurder() { mMurdered = true; } void CreatureStats::clearHasBeenMurdered() { mMurdered = false; } void CreatureStats::resurrect() { if (mDead) { mDynamic[0].setCurrent(mDynamic[0].getBase()); mDead = false; mDeathAnimationFinished = false; } } bool CreatureStats::hasCommonDisease() const { return mSpells.hasCommonDisease(); } bool CreatureStats::hasBlightDisease() const { return mSpells.hasBlightDisease(); } int CreatureStats::getFriendlyHits() const { return mFriendlyHits; } void CreatureStats::friendlyHit() { ++mFriendlyHits; } bool CreatureStats::hasTalkedToPlayer() const { return mTalkedTo; } void CreatureStats::talkedToPlayer() { mTalkedTo = true; } bool CreatureStats::isAlarmed() const { return mAlarmed; } void CreatureStats::setAlarmed (bool alarmed) { mAlarmed = alarmed; } bool CreatureStats::getAttacked() const { return mAttacked; } void CreatureStats::setAttacked (bool attacked) { mAttacked = attacked; } float CreatureStats::getEvasion() const { float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); evasion *= getFatigueTerm(); evasion += std::min(100.f, mMagicEffects.get(ESM::MagicEffect::Sanctuary).getMagnitude()); return evasion; } void CreatureStats::setLastHitObject(const std::string& objectid) { mLastHitObject = objectid; } void CreatureStats::clearLastHitObject() { mLastHitObject.clear(); } const std::string &CreatureStats::getLastHitObject() const { return mLastHitObject; } void CreatureStats::setLastHitAttemptObject(const std::string& objectid) { mLastHitAttemptObject = objectid; } void CreatureStats::clearLastHitAttemptObject() { mLastHitAttemptObject.clear(); } const std::string &CreatureStats::getLastHitAttemptObject() const { return mLastHitAttemptObject; } void CreatureStats::setHitAttemptActorId(int actorId) { mHitAttemptActorId = actorId; } int CreatureStats::getHitAttemptActorId() const { return mHitAttemptActorId; } void CreatureStats::addToFallHeight(float height) { mFallHeight += height; } float CreatureStats::getFallHeight() const { return mFallHeight; } float CreatureStats::land(bool isPlayer) { if (isPlayer) MWBase::Environment::get().getWorld()->getPlayer().setJumping(false); float height = mFallHeight; mFallHeight = 0; return height; } void CreatureStats::recalculateMagicka() { auto world = MWBase::Environment::get().getWorld(); float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified(); float base = 1.f; const auto& player = world->getPlayerPtr(); if (this == &player.getClass().getCreatureStats(player)) base = world->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); else base = world->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; DynamicStat magicka = getMagicka(); float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; magicka.setBase(magickaFactor * intelligence); magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); setMagicka(magicka); } void CreatureStats::setKnockedDown(bool value) { mKnockdown = value; if(!value) //Resets the "OverOneFrame" flag setKnockedDownOverOneFrame(false); } bool CreatureStats::getKnockedDown() const { return mKnockdown; } void CreatureStats::setKnockedDownOneFrame(bool value) { mKnockdownOneFrame = value; } bool CreatureStats::getKnockedDownOneFrame() const { return mKnockdownOneFrame; } void CreatureStats::setKnockedDownOverOneFrame(bool value) { mKnockdownOverOneFrame = value; } bool CreatureStats::getKnockedDownOverOneFrame() const { return mKnockdownOverOneFrame; } void CreatureStats::setHitRecovery(bool value) { mHitRecovery = value; } bool CreatureStats::getHitRecovery() const { return mHitRecovery; } void CreatureStats::setBlock(bool value) { mBlock = value; } bool CreatureStats::getBlock() const { return mBlock; } bool CreatureStats::getMovementFlag (Flag flag) const { return (mMovementFlags & flag) != 0; } void CreatureStats::setMovementFlag (Flag flag, bool state) { if (state) mMovementFlags |= flag; else mMovementFlags &= ~flag; } bool CreatureStats::getStance(Stance flag) const { switch (flag) { case Stance_Run: return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun); case Stance_Sneak: return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak); default: return false; } } DrawState CreatureStats::getDrawState() const { return mDrawState; } void CreatureStats::setDrawState(DrawState state) { mDrawState = state; } void CreatureStats::writeState (ESM::CreatureStats& state) const { for (int i=0; i(mDrawState); state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; state.mTimeOfDeath = mTimeOfDeath.toEsm(); //state.mHitAttemptActorId = mHitAttemptActorId; mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); mAiSequence.writeState(state.mAiSequence); mMagicEffects.writeState(state.mMagicEffects); state.mSummonedCreatures = mSummonedCreatures; state.mSummonGraveyard = mSummonGraveyard; state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); state.mMissingACDT = false; } void CreatureStats::readState (const ESM::CreatureStats& state) { if (!state.mMissingACDT) { for (int i=0; i& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } std::vector& CreatureStats::getSummonedCreatureGraveyard() { return mSummonGraveyard; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/creaturestats.hpp000066400000000000000000000213041445372753700246040ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_CREATURESTATS_H #define GAME_MWMECHANICS_CREATURESTATS_H #include #include #include #include #include "stat.hpp" #include "magiceffects.hpp" #include "spells.hpp" #include "activespells.hpp" #include "aisequence.hpp" #include "drawstate.hpp" #include "aisetting.hpp" #include #include namespace ESM { struct CreatureStats; } namespace MWMechanics { struct CorprusStats { static constexpr int sWorseningPeriod = 24; int mWorsenings[ESM::Attribute::Length]; MWWorld::TimeStamp mNextWorsening; }; /// \brief Common creature stats /// /// class CreatureStats { static int sActorId; DrawState mDrawState; AttributeValue mAttributes[ESM::Attribute::Length]; DynamicStat mDynamic[3]; // health, magicka, fatigue Spells mSpells; ActiveSpells mActiveSpells; MagicEffects mMagicEffects; Stat mAiSettings[4]; AiSequence mAiSequence; bool mDead; bool mDeathAnimationFinished; bool mDied; // flag for OnDeath script function bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed; bool mAttacked; bool mKnockdown; bool mKnockdownOneFrame; bool mKnockdownOverOneFrame; bool mHitRecovery; bool mBlock; unsigned int mMovementFlags; float mFallHeight; std::string mLastHitObject; // The last object to hit this actor std::string mLastHitAttemptObject; // The last object to attempt to hit this actor // For merchants: the last time items were restocked and gold pool refilled. MWWorld::TimeStamp mLastRestock; // The pool of merchant gold (not in inventory) int mGoldPool; int mActorId; int mHitAttemptActorId; // Stores an actor that attacked this actor. Only one is stored at a time, // and it is not changed if a different actor attacks. It is cleared when combat ends. // The index of the death animation that was played, or -1 if none played signed char mDeathAnimation; MWWorld::TimeStamp mTimeOfDeath; // The difference between view direction and lower body direction. float mSideMovementAngle; private: std::multimap mSummonedCreatures; // // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; protected: int mLevel; bool mAttackingOrSpell; public: CreatureStats(); DrawState getDrawState() const; void setDrawState(DrawState state); void recalculateMagicka(); float getFallHeight() const; void addToFallHeight(float height); /// Reset the fall height /// @return total fall height float land(bool isPlayer=false); const AttributeValue & getAttribute(int index) const; const DynamicStat & getHealth() const; const DynamicStat & getMagicka() const; const DynamicStat & getFatigue() const; const DynamicStat & getDynamic (int index) const; const Spells & getSpells() const; const ActiveSpells & getActiveSpells() const; const MagicEffects & getMagicEffects() const; bool getAttackingOrSpell() const { return mAttackingOrSpell; } int getLevel() const; Spells & getSpells(); ActiveSpells & getActiveSpells(); MagicEffects & getMagicEffects(); void setAttribute(int index, const AttributeValue &value); // Shortcut to set only the base void setAttribute(int index, float base); void setHealth(const DynamicStat &value); void setMagicka(const DynamicStat &value); void setFatigue(const DynamicStat &value); void setDynamic (int index, const DynamicStat &value); /// Set Modifier for each magic effect according to \a effects. Does not touch Base values. void modifyMagicEffects(const MagicEffects &effects); void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; } void setLevel(int level); void setAiSetting (AiSetting index, Stat value); void setAiSetting (AiSetting index, int base); Stat getAiSetting (AiSetting index) const; const AiSequence& getAiSequence() const; AiSequence& getAiSequence(); float getFatigueTerm() const; ///< Return effective fatigue bool isParalyzed() const; bool isDead() const; bool isDeathAnimationFinished() const; void setDeathAnimationFinished(bool finished); void notifyDied(); bool hasDied() const; void clearHasDied(); bool hasBeenMurdered() const; void clearHasBeenMurdered(); void notifyMurder(); void resurrect(); bool hasCommonDisease() const; bool hasBlightDisease() const; int getFriendlyHits() const; ///< Number of friendly hits received. void friendlyHit(); ///< Increase number of friendly hits by one. bool hasTalkedToPlayer() const; ///< Has this creature talked with the player before? void talkedToPlayer(); bool isAlarmed() const; void setAlarmed (bool alarmed); bool getAttacked() const; void setAttacked (bool attacked); float getEvasion() const; void setKnockedDown(bool value); /// Returns true for the entire duration of the actor being knocked down or knocked out, /// including transition animations (falling down & standing up) bool getKnockedDown() const; void setKnockedDownOneFrame(bool value); ///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command bool getKnockedDownOneFrame() const; void setKnockedDownOverOneFrame(bool value); ///Returns true for all but the first frame of being knocked out; used to know to not reset mKnockedDownOneFrame bool getKnockedDownOverOneFrame() const; void setHitRecovery(bool value); bool getHitRecovery() const; void setBlock(bool value); bool getBlock() const; std::multimap& getSummonedCreatureMap(); // std::vector& getSummonedCreatureGraveyard(); // ActorIds enum Flag { Flag_ForceRun = 1, Flag_ForceSneak = 2, Flag_Run = 4, Flag_Sneak = 8, Flag_ForceJump = 16, Flag_ForceMoveJump = 32 }; enum Stance { Stance_Run, Stance_Sneak }; bool getMovementFlag (Flag flag) const; void setMovementFlag (Flag flag, bool state); /// Like getMovementFlag, but also takes into account if the flag is Forced bool getStance (Stance flag) const; void setLastHitObject(const std::string &objectid); void clearLastHitObject(); const std::string &getLastHitObject() const; void setLastHitAttemptObject(const std::string &objectid); void clearLastHitAttemptObject(); const std::string &getLastHitAttemptObject() const; void setHitAttemptActorId(const int actorId); int getHitAttemptActorId() const; void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); static void writeActorIdCounter (ESM::ESMWriter& esm); static void readActorIdCounter (ESM::ESMReader& esm); void setLastRestockTime(MWWorld::TimeStamp tradeTime); MWWorld::TimeStamp getLastRestockTime() const; void setGoldPool(int pool); int getGoldPool() const; signed char getDeathAnimation() const; // -1 means not decided void setDeathAnimation(signed char index); MWWorld::TimeStamp getTimeOfDeath() const; int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. bool matchesActorId (int id) const; ///< Check if \a id matches the actor ID of *this (if the actor does not have an ID /// assigned this function will return false). static void cleanup(); float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/difficultyscaling.cpp000066400000000000000000000021631445372753700254130ustar00rootroot00000000000000#include "difficultyscaling.hpp" #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" #include "actorutil.hpp" float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) { const MWWorld::Ptr& player = MWMechanics::getPlayer(); // [-500, 500] const int difficultySetting = std::clamp(Settings::Manager::getInt("difficulty", "Game"), -500, 500); static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->mValue.getFloat(); float difficultyTerm = 0.01f * difficultySetting; float x = 0; if (victim == player) { if (difficultyTerm > 0) x = fDifficultyMult * difficultyTerm; else x = difficultyTerm / fDifficultyMult; } else if (attacker == player) { if (difficultyTerm > 0) x = -difficultyTerm / fDifficultyMult; else x = fDifficultyMult * (-difficultyTerm); } damage *= 1 + x; return damage; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/difficultyscaling.hpp000066400000000000000000000004501445372753700254150ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_DIFFICULTYSCALING_H #define OPENMW_MWMECHANICS_DIFFICULTYSCALING_H namespace MWWorld { class Ptr; } /// Scales damage dealt to an actor based on difficulty setting float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/disease.hpp000066400000000000000000000061211445372753700233300ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_DISEASE_H #define OPENMW_MECHANICS_DISEASE_H #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "spells.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" namespace MWMechanics { /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) /// @param actor The actor that will potentially catch diseases. Currently only the player can catch diseases. /// @param carrier The disease carrier. inline void diseaseContact (const MWWorld::Ptr& actor, const MWWorld::Ptr& carrier) { if (!carrier.getClass().isActor() || actor != getPlayer()) return; float fDiseaseXferChance = MWBase::Environment::get().getWorld()->getStore().get().find( "fDiseaseXferChance")->mValue.getFloat(); const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); for (const ESM::Spell* spell : spells) { if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; float resist = 0.f; if (Spells::hasCorprusEffect(spell)) resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Disease) resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() - actorEffects.get(ESM::MagicEffect::WeaknessToCommonDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Blight) resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistBlightDisease).getMagnitude() - actorEffects.get(ESM::MagicEffect::WeaknessToBlightDisease).getMagnitude()); else continue; int x = static_cast(fDiseaseXferChance * 100 * resist); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::rollDice(10000, prng) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(spell); MWBase::Environment::get().getWorld()->applyLoopingParticles(actor); std::string msg = "sMagicContractDisease"; msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->mValue.getString(); msg = Misc::StringUtils::format(msg, spell->mName); MWBase::Environment::get().getWindowManager()->messageBox(msg); } } } } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/drawstate.hpp000066400000000000000000000003171445372753700237120ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_DRAWSTATE_H #define GAME_MWMECHANICS_DRAWSTATE_H namespace MWMechanics { enum class DrawState { Nothing = 0, Weapon = 1, Spell = 2 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/enchanting.cpp000066400000000000000000000351611445372753700240320ustar00rootroot00000000000000#include "enchanting.hpp" #include #include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" #include "spellutil.hpp" #include "actorutil.hpp" #include "weapontype.hpp" namespace MWMechanics { Enchanting::Enchanting() : mCastStyle(ESM::Enchantment::CastOnce) , mSelfEnchanting(false) , mObjectType(0) , mWeaponType(-1) {} void Enchanting::setOldItem(const MWWorld::Ptr& oldItem) { mOldItemPtr=oldItem; mWeaponType = -1; mObjectType = 0; if(!itemEmpty()) { mObjectType = mOldItemPtr.getType(); if (mObjectType == ESM::Weapon::sRecordId) mWeaponType = mOldItemPtr.get()->mBase->mData.mType; } } void Enchanting::setNewItemName(const std::string& s) { mNewItemName=s; } void Enchanting::setEffect(const ESM::EffectList& effectList) { mEffectList=effectList; } int Enchanting::getCastStyle() const { return mCastStyle; } void Enchanting::setSoulGem(const MWWorld::Ptr& soulGem) { mSoulGemPtr=soulGem; } bool Enchanting::create() { const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mFlags = 0; enchantment.mData.mType = mCastStyle; enchantment.mData.mCost = getBaseCastCost(); store.remove(mSoulGemPtr, 1, player); //Exception for Azura Star, new one will be added after enchanting if(Misc::StringUtils::ciEqual(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) store.add("Misc_SoulGem_Azura", 1, player); if(mSelfEnchanting) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if(getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } enchantment.mEffects = mEffectList; int count = getEnchantItemsCount(); if(mCastStyle==ESM::Enchantment::ConstantEffect) enchantment.mData.mCharge = 0; else enchantment.mData.mCharge = getGemCharge() / count; // Try to find a dynamic enchantment with the same stats, create a new one if not found. const ESM::Enchantment* enchantmentPtr = getRecord(enchantment); if (enchantmentPtr == nullptr) enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); // Apply the enchantment std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); // Add the new item to player inventory and remove the old one store.remove(mOldItemPtr, count, player); store.add(newItemId, count, player); if(!mSelfEnchanting) payForEnchantment(); return true; } void Enchanting::nextCastStyle() { if (itemEmpty()) return; const bool powerfulSoul = getGemCharge() >= \ MWBase::Environment::get().getWorld()->getStore().get().find ("iSoulAmountForConstantEffect")->mValue.getInteger(); if ((mObjectType == ESM::Armor::sRecordId) || (mObjectType == ESM::Clothing::sRecordId)) { // Armor or Clothing switch(mCastStyle) { case ESM::Enchantment::WhenUsed: if (powerfulSoul) mCastStyle = ESM::Enchantment::ConstantEffect; return; default: // takes care of Constant effect too mCastStyle = ESM::Enchantment::WhenUsed; return; } } else if (mWeaponType != -1) { // Weapon ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; switch(mCastStyle) { case ESM::Enchantment::WhenStrikes: if (weapclass == ESM::WeaponType::Melee || weapclass == ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenUsed; return; case ESM::Enchantment::WhenUsed: if (powerfulSoul && weapclass != ESM::WeaponType::Ammo && weapclass != ESM::WeaponType::Thrown) mCastStyle = ESM::Enchantment::ConstantEffect; else if (weapclass != ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenStrikes; return; default: // takes care of Constant effect too mCastStyle = ESM::Enchantment::WhenUsed; if (weapclass != ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenStrikes; return; } } else if(mObjectType == ESM::Book::sRecordId) { // Scroll or Book mCastStyle = ESM::Enchantment::CastOnce; return; } // Fail case mCastStyle = ESM::Enchantment::CastOnce; } /* * Vanilla enchant cost formula: * * Touch/Self: (min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025 * Target: 1.5 * ((min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025) * Constant eff: (min + max) * baseCost * 2.5 + area * baseCost * 0.025 * * For multiple effects - cost of each effect is multiplied by number of effects that follows +1. * * Note: Minimal value inside formula for 'min' and 'max' is 1. So in vanilla: * (0 + 0) == (1 + 0) == (1 + 1) => 2 or (2 + 0) == (1 + 2) => 3 * * Formula on UESPWiki is not entirely correct. */ float Enchanting::getEnchantPoints(bool precise) const { if (mEffectList.mList.empty()) // No effects added, cost = 0 return 0; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); const float fEnchantmentConstantDurationMult = store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); float enchantmentCost = 0.f; float cost = 0.f; for (const ESM::ENAMstruct& effect : mEffectList.mList) { float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; int magMin = std::max(1, effect.mMagnMin); int magMax = std::max(1, effect.mMagnMax); int area = std::max(1, effect.mArea); float duration = static_cast(effect.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) duration = fEnchantmentConstantDurationMult; cost += ((magMin + magMax) * duration + area) * baseCost * fEffectCostMult * 0.05f; cost = std::max(1.f, cost); if (effect.mRange == ESM::RT_Target) cost *= 1.5f; enchantmentCost += precise ? cost : std::floor(cost); } return enchantmentCost; } const ESM::Enchantment* Enchanting::getRecord(const ESM::Enchantment& toFind) const { const MWWorld::Store& enchantments = MWBase::Environment::get().getWorld()->getStore().get(); MWWorld::Store::iterator iter (enchantments.begin()); iter += (enchantments.getSize() - enchantments.getDynamicSize()); for (; iter != enchantments.end(); ++iter) { if (iter->mEffects.mList.size() != toFind.mEffects.mList.size()) continue; if (iter->mData.mFlags != toFind.mData.mFlags || iter->mData.mType != toFind.mData.mType || iter->mData.mCost != toFind.mData.mCost || iter->mData.mCharge != toFind.mData.mCharge) continue; // Don't choose an ID that came from the content files, would have unintended side effects if (!enchantments.isDynamic(iter->mId)) continue; bool mismatch = false; for (int i=0; i (iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; if (first.mEffectID!=second.mEffectID || first.mArea!=second.mArea || first.mRange!=second.mRange || first.mSkill!=second.mSkill || first.mAttribute!=second.mAttribute || first.mMagnMin!=second.mMagnMin || first.mMagnMax!=second.mMagnMax || first.mDuration!=second.mDuration) { mismatch = true; break; } } if (!mismatch) return &(*iter); } return nullptr; } int Enchanting::getBaseCastCost() const { if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; return static_cast(getEnchantPoints(false)); } int Enchanting::getEffectiveCastCost() const { int baseCost = getBaseCastCost(); MWWorld::Ptr player = getPlayer(); return getEffectiveEnchantmentCastCost(static_cast(baseCost), player); } int Enchanting::getEnchantPrice() const { if(mEnchanter.isEmpty()) return 0; float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentValueMult")->mValue.getFloat(); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast(getEnchantPoints() * priceMultipler), true); price *= getEnchantItemsCount() * getTypeMultiplier(); return std::max(1, price); } int Enchanting::getGemCharge() const { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); if(soulEmpty()) return 0; if(mSoulGemPtr.getCellRef().getSoul()=="") return 0; const ESM::Creature* soul = store.get().search(mSoulGemPtr.getCellRef().getSoul()); if(soul) return soul->mData.mSoul; else return 0; } int Enchanting::getMaxEnchantValue() const { if (itemEmpty()) return 0; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); return static_cast(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->mValue.getFloat()); } bool Enchanting::soulEmpty() const { return mSoulGemPtr.isEmpty(); } bool Enchanting::itemEmpty() const { return mOldItemPtr.isEmpty(); } void Enchanting::setSelfEnchanting(bool selfEnchanting) { mSelfEnchanting = selfEnchanting; } void Enchanting::setEnchanter(const MWWorld::Ptr& enchanter) { mEnchanter = enchanter; // Reset cast style mCastStyle = ESM::Enchantment::CastOnce; } int Enchanting::getEnchantChance() const { const CreatureStats& stats = mEnchanter.getClass().getCreatureStats(mEnchanter); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); const float a = static_cast(mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant)); const float b = static_cast(stats.getAttribute (ESM::Attribute::Intelligence).getModified()); const float c = static_cast(stats.getAttribute (ESM::Attribute::Luck).getModified()); const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat(); float x = (a - getEnchantPoints() * fEnchantmentChanceMult * getTypeMultiplier() * getEnchantItemsCount() + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); if (mCastStyle == ESM::Enchantment::ConstantEffect) x *= fEnchantmentConstantChanceMult; return static_cast(x); } int Enchanting::getEnchantItemsCount() const { int count = 1; float enchantPoints = getEnchantPoints(); if (mWeaponType != -1 && enchantPoints > 0) { ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) { static const float multiplier = std::clamp(Settings::Manager::getFloat("projectiles enchant multiplier", "Game"), 0.f, 1.f); MWWorld::Ptr player = getPlayer(); count = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); count = std::clamp(getGemCharge() * multiplier / enchantPoints, 1, count); } } return count; } float Enchanting::getTypeMultiplier() const { static const bool useMultiplier = Settings::Manager::getFloat("projectiles enchant multiplier", "Game") > 0; if (useMultiplier && mWeaponType != -1 && getEnchantPoints() > 0) { ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) return 0.125f; } return 1.f; } void Enchanting::payForEnchantment() const { const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); // add gold to NPC trading gold pool CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); enchanterStats.setGoldPool(enchanterStats.getGoldPool() + getEnchantPrice()); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/enchanting.hpp000066400000000000000000000041171445372753700240340ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ENCHANTING_H #define GAME_MWMECHANICS_ENCHANTING_H #include #include #include #include "../mwworld/ptr.hpp" namespace MWMechanics { class Enchanting { MWWorld::Ptr mOldItemPtr; MWWorld::Ptr mSoulGemPtr; MWWorld::Ptr mEnchanter; int mCastStyle; bool mSelfEnchanting; ESM::EffectList mEffectList; std::string mNewItemName; unsigned int mObjectType; int mWeaponType; const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const; public: Enchanting(); void setEnchanter(const MWWorld::Ptr& enchanter); void setSelfEnchanting(bool selfEnchanting); void setOldItem(const MWWorld::Ptr& oldItem); MWWorld::Ptr getOldItem() { return mOldItemPtr; } MWWorld::Ptr getGem() { return mSoulGemPtr; } void setNewItemName(const std::string& s); void setEffect(const ESM::EffectList& effectList); void setSoulGem(const MWWorld::Ptr& soulGem); bool create(); //Return true if created, false if failed. void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; float getEnchantPoints(bool precise = true) const; int getBaseCastCost() const; // To be saved in the enchantment's record int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI int getEnchantPrice() const; int getMaxEnchantValue() const; int getGemCharge() const; int getEnchantChance() const; int getEnchantItemsCount() const; float getTypeMultiplier() const; bool soulEmpty() const; //Return true if empty bool itemEmpty() const; //Return true if empty void payForEnchantment() const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/greetingstate.hpp000066400000000000000000000003361445372753700245620ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_GREETINGSTATE_H #define OPENMW_MWMECHANICS_GREETINGSTATE_H namespace MWMechanics { enum GreetingState { Greet_None, Greet_InProgress, Greet_Done }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/inventory.hpp000066400000000000000000000023201445372753700237450ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_INVENTORY_H #define OPENMW_MWMECHANICS_INVENTORY_H #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include #include #include #include namespace MWMechanics { template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) { T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(actorId); for (auto& it : copy.mInventory.mList) { if (Misc::StringUtils::ciEqual(it.mItem, itemId)) { const int sign = it.mCount < 1 ? -1 : 1; it.mCount = sign * std::max(it.mCount * sign + amount, 0); MWBase::Environment::get().getWorld()->createOverrideRecord(copy); return; } } if (amount > 0) { ESM::ContItem cont; cont.mItem = itemId; cont.mCount = amount; copy.mInventory.mList.push_back(cont); MWBase::Environment::get().getWorld()->createOverrideRecord(copy); } } } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/levelledlist.hpp000066400000000000000000000061571445372753700244140ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H #include #include #include "../mwworld/ptr.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" namespace MWMechanics { /// @return ID of resulting item, or empty if none inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng) { const std::vector& items = levItem->mList; const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); if (Misc::Rng::roll0to99(prng) < levItem->mChanceNone) return std::string(); std::vector candidates; int highestLevel = 0; for (const auto& levelledItem : items) { if (levelledItem.mLevel > highestLevel && levelledItem.mLevel <= playerLevel) highestLevel = levelledItem.mLevel; } // For levelled creatures, the flags are swapped. This file format just makes so much sense. bool allLevels = (levItem->mFlags & ESM::ItemLevList::AllLevels) != 0; if (creature) allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels; std::pair highest = std::make_pair(-1, ""); for (const auto& levelledItem : items) { if (playerLevel >= levelledItem.mLevel && (allLevels || levelledItem.mLevel == highestLevel)) { candidates.push_back(levelledItem.mId); if (levelledItem.mLevel >= highest.first) highest = std::make_pair(levelledItem.mLevel, levelledItem.mId); } } if (candidates.empty()) return std::string(); std::string item = candidates[Misc::Rng::rollDice(candidates.size(), prng)]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) { Log(Debug::Warning) << "Warning: ignoring nonexistent item '" << item << "' in levelled list '" << levItem->mId << "'"; return std::string(); } // Is this another levelled item or a real item? MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); if (ref.getPtr().getType() != ESM::ItemLevList::sRecordId && ref.getPtr().getType() != ESM::CreatureLevList::sRecordId) { return item; } else { if (ref.getPtr().getType() == ESM::ItemLevList::sRecordId) return getLevelledItem(ref.getPtr().get()->mBase, false, prng); else return getLevelledItem(ref.getPtr().get()->mBase, true, prng); } } } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/magiceffects.cpp000066400000000000000000000116511445372753700243320ustar00rootroot00000000000000#include "magiceffects.hpp" #include #include #include #include namespace { // Round value to prevent precision issues void truncate(float& value) { value = static_cast(value * 1024.f) / 1024.f; } } namespace MWMechanics { EffectKey::EffectKey() : mId (0), mArg (-1) {} EffectKey::EffectKey (const ESM::ENAMstruct& effect) { mId = effect.mEffectID; mArg = -1; if (effect.mSkill!=-1) mArg = effect.mSkill; if (effect.mAttribute!=-1) { if (mArg!=-1) throw std::runtime_error ( "magic effect can't have both a skill and an attribute argument"); mArg = effect.mAttribute; } } bool operator< (const EffectKey& left, const EffectKey& right) { if (left.mIdright.mId) return false; return left.mArgsecond += param; } } void MagicEffects::modifyBase(const EffectKey &key, int diff) { mCollection[key].modifyBase(diff); } void MagicEffects::setModifiers(const MagicEffects &effects) { for (Collection::iterator it = mCollection.begin(); it != mCollection.end(); ++it) { it->second.setModifier(effects.get(it->first).getModifier()); } for (Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { mCollection[it->first].setModifier(it->second.getModifier()); } } EffectParam MagicEffects::get (const EffectKey& key) const { Collection::const_iterator iter = mCollection.find (key); if (iter==mCollection.end()) { return EffectParam(); } else { return iter->second; } } MagicEffects MagicEffects::diff (const MagicEffects& prev, const MagicEffects& now) { MagicEffects result; // adding/changing for (Collection::const_iterator iter (now.begin()); iter!=now.end(); ++iter) { Collection::const_iterator other = prev.mCollection.find (iter->first); if (other==prev.end()) { // adding result.add (iter->first, iter->second); } else { // changing result.add (iter->first, iter->second - other->second); } } // removing for (Collection::const_iterator iter (prev.begin()); iter!=prev.end(); ++iter) { Collection::const_iterator other = now.mCollection.find (iter->first); if (other==now.end()) { result.add (iter->first, EffectParam() - iter->second); } } return result; } void MagicEffects::writeState(ESM::MagicEffects &state) const { for (const auto& [key, params] : mCollection) { if (params.getBase() != 0 || params.getModifier() != 0.f) { // Don't worry about mArg, never used by magic effect script instructions state.mEffects[key.mId] = {params.getBase(), params.getModifier()}; } } } void MagicEffects::readState(const ESM::MagicEffects &state) { for (const auto& [key, params] : state.mEffects) { mCollection[EffectKey(key)].setBase(params.first); mCollection[EffectKey(key)].setModifier(params.second); } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/magiceffects.hpp000066400000000000000000000053221445372753700243350ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_MAGICEFFECTS_H #define GAME_MWMECHANICS_MAGICEFFECTS_H #include #include namespace ESM { struct ENAMstruct; struct EffectList; struct MagicEffects; } namespace MWMechanics { struct EffectKey { int mId; int mArg; // skill or ability EffectKey(); EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} EffectKey (const ESM::ENAMstruct& effect); }; bool operator< (const EffectKey& left, const EffectKey& right); struct EffectParam { private: // Note usually this would be int, but applying partial resistance might introduce a decimal point. float mModifier; int mBase; public: /// Get the total magnitude including base and modifier. float getMagnitude() const; void setModifier(float mod); float getModifier() const; /// Change mBase by \a diff void modifyBase(int diff); void setBase(int base); int getBase() const; EffectParam(); EffectParam(float magnitude) : mModifier(magnitude), mBase(0) {} EffectParam& operator+= (const EffectParam& param); EffectParam& operator-= (const EffectParam& param); }; inline EffectParam operator+ (const EffectParam& left, const EffectParam& right) { EffectParam param (left); return param += right; } inline EffectParam operator- (const EffectParam& left, const EffectParam& right) { EffectParam param (left); return param -= right; } /// \brief Effects currently affecting a NPC or creature class MagicEffects { public: typedef std::map Collection; private: Collection mCollection; public: Collection::const_iterator begin() const { return mCollection.begin(); } Collection::const_iterator end() const { return mCollection.end(); } void readState (const ESM::MagicEffects& state); void writeState (ESM::MagicEffects& state) const; void add (const EffectKey& key, const EffectParam& param); void remove (const EffectKey& key); void modifyBase (const EffectKey& key, int diff); /// Copy Modifier values from \a effects, but keep original mBase values. void setModifiers(const MagicEffects& effects); EffectParam get (const EffectKey& key) const; ///< This function can safely be used for keys that are not present. static MagicEffects diff (const MagicEffects& prev, const MagicEffects& now); ///< Return changes from \a prev to \a now. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp000066400000000000000000002262411445372753700257100ustar00rootroot00000000000000#include "mechanicsmanagerimp.hpp" #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/ptr.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "aicombat.hpp" #include "aipursue.hpp" #include "spellutil.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" #include "actorutil.hpp" #include "combat.hpp" #include "actor.hpp" namespace { float getFightDispositionBias(float disposition) { static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fFightDispMult")->mValue.getFloat(); return ((50.f - disposition) * fFightDispMult); } void getPersuasionRatings(const MWMechanics::NpcStats& stats, float& rating1, float& rating2, float& rating3, bool player) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); float persTerm = stats.getAttribute(ESM::Attribute::Personality).getModified() / gmst.find("fPersonalityMod")->mValue.getFloat(); float luckTerm = stats.getAttribute(ESM::Attribute::Luck).getModified() / gmst.find("fLuckMod")->mValue.getFloat(); float repTerm = stats.getReputation() * gmst.find("fReputationMod")->mValue.getFloat(); float fatigueTerm = stats.getFatigueTerm(); float levelTerm = stats.getLevel() * gmst.find("fLevelMod")->mValue.getFloat(); rating1 = (repTerm + luckTerm + persTerm + stats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm; if (player) { rating2 = rating1 + levelTerm; rating3 = (stats.getSkill(ESM::Skill::Mercantile).getModified() + luckTerm + persTerm) * fatigueTerm; } else { rating2 = (levelTerm + repTerm + luckTerm + persTerm + stats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm; rating3 = (stats.getSkill(ESM::Skill::Mercantile).getModified() + repTerm + luckTerm + persTerm) * fatigueTerm; } } bool isOwned(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) { const MWWorld::CellRef& cellref = target.getCellRef(); const std::string& owner = cellref.getOwner(); bool isOwned = !owner.empty() && owner != "player"; const std::string& faction = cellref.getFaction(); bool isFactionOwned = false; if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); auto found = factions.find(Misc::StringUtils::lowerCase(faction)); if (found == factions.end() || found->second < cellref.getFactionRank()) isFactionOwned = true; } const std::string& globalVariable = cellref.getGlobalVariable(); if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(globalVariable)) { isOwned = false; isFactionOwned = false; } if (!cellref.getOwner().empty()) victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); return isOwned || isFactionOwned; } } namespace MWMechanics { void MechanicsManager::buildPlayer() { MWWorld::Ptr ptr = getPlayer(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); npcStats.recalculateMagicka(); const ESM::NPC *player = ptr.get()->mBase; // reset creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(true); creatureStats.getActiveSpells().clear(ptr); for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]); creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength); creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence); creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt.mWillpower); creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt.mAgility); creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt.mSpeed); creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt.mEndurance); creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt.mPersonality); creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt.mLuck); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); // race if (mRaceSelected) { const ESM::Race *race = esmStore.get().find(player->mRace); bool male = (player->mFlags & ESM::NPC::Female) == 0; for (int i=0; i<8; ++i) { const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i]; creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } for (int i=0; i<27; ++i) { int bonus = 0; for (int i2=0; i2<7; ++i2) if (race->mData.mBonus[i2].mSkill==i) { bonus = race->mData.mBonus[i2].mBonus; break; } npcStats.getSkill (i).setBase (5 + bonus); } for (const std::string &power : race->mPowers.mList) { creatureStats.getSpells().add(power); } } // birthsign const std::string &signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!signId.empty()) { const ESM::BirthSign *sign = esmStore.get().find(signId); for (const std::string &power : sign->mPowers.mList) { creatureStats.getSpells().add(power); } } // class if (mClassSelected) { const ESM::Class *class_ = esmStore.get().find(player->mClass); for (int i=0; i<2; ++i) { int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } for (int i=0; i<2; ++i) { int bonus = i==0 ? 10 : 25; for (int i2=0; i2<5; ++i2) { int index = class_->mData.mSkills[i2][i]; if (index>=0 && index<27) { npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + bonus); } } } const MWWorld::Store &skills = esmStore.get(); MWWorld::Store::iterator iter = skills.begin(); for (; iter != skills.end(); ++iter) { if (iter->second.mData.mSpecialization==class_->mData.mSpecialization) { int index = iter->first; if (index>=0 && index<27) { npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + 5); } } } } // F_PCStart spells const ESM::Race* race = nullptr; if (mRaceSelected) race = esmStore.get().find(player->mRace); int skills[ESM::Skill::Length]; for (int i=0; i selectedSpells = autoCalcPlayerSpells(skills, attributes, race); for (const std::string &spell : selectedSpells) creatureStats.getSpells().add(spell); // forced update and current value adjustments mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { DynamicStat stat = creatureStats.getDynamic (i); stat.setCurrent (stat.getModified()); creatureStats.setDynamic (i, stat); } // auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); for (int i=0; igetWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr()); mActors.removeActor(ptr, keepActive); mObjects.removeObject(ptr); } void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { if(old == MWBase::Environment::get().getWindowManager()->getWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(ptr); if(ptr.getClass().isActor()) mActors.updateActor(old, ptr); else mObjects.updateObject(old, ptr); } void MechanicsManager::drop(const MWWorld::CellStore *cellStore) { mActors.dropActors(cellStore, getPlayer()); mObjects.dropObjects(cellStore); } void MechanicsManager::update(float duration, bool paused) { // Note: we should do it here since game mechanics and world updates use these values MWWorld::Ptr ptr = getPlayer(); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // Update the selected spell icon MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem(); if (enchantItem != inv.end()) winMgr->setSelectedEnchantItem(*enchantItem); else { const std::string& spell = winMgr->getSelectedSpell(); if (!spell.empty()) winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, ptr))); else winMgr->unsetSelectedSpell(); } // Update the equipped weapon icon if (weapon == inv.end()) winMgr->unsetSelectedWeapon(); else winMgr->setSelectedWeapon(*weapon); if (mUpdatePlayer) { mUpdatePlayer = false; // HACK? The player has been changed, so a new Animation object may // have been made for them. Make sure they're properly updated. mActors.removeActor(ptr, true); mActors.addActor(ptr, true); } mActors.update(duration, paused); mObjects.update(duration, paused); } void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed) { for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { if (it->first == "Game" && it->second == "actors processing range") { int state = MWBase::Environment::get().getStateManager()->getState(); if (state != MWBase::StateManager::State_Running) continue; mActors.updateProcessingRange(); // Update mechanics for new processing range immediately update(0.f, false); } } } void MechanicsManager::notifyDied(const MWWorld::Ptr& actor) { mActors.notifyDied(actor); } float MechanicsManager::getActorsProcessingRange() const { return mActors.getProcessingRange(); } bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) { return mActors.isActorDetected(actor, observer); } bool MechanicsManager::isAttackPreparing(const MWWorld::Ptr& ptr) { return mActors.isAttackPreparing(ptr); } bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) { return mActors.isRunning(ptr); } bool MechanicsManager::isSneaking(const MWWorld::Ptr& ptr) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWBase::World* world = MWBase::Environment::get().getWorld(); bool animActive = mActors.isSneaking(ptr); bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); return stanceOn && (animActive || inair); } void MechanicsManager::rest(double hours, bool sleep) { if (sleep) MWBase::Environment::get().getWorld()->rest(hours); mActors.rest(hours, sleep); } void MechanicsManager::restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) { mActors.restoreDynamicStats(actor, hours, sleep); } int MechanicsManager::getHoursToRest() const { return mActors.getHoursToRest(getPlayer()); } void MechanicsManager::setPlayerName (const std::string& name) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mName = name; world->createRecord(player); mUpdatePlayer = true; } void MechanicsManager::setPlayerRace (const std::string& race, bool male, const std::string &head, const std::string &hair) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mRace = race; player.mHead = head; player.mHair = hair; player.setIsMale(male); world->createRecord(player); mRaceSelected = true; buildPlayer(); mUpdatePlayer = true; } void MechanicsManager::setPlayerBirthsign (const std::string& id) { MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(id); buildPlayer(); mUpdatePlayer = true; } void MechanicsManager::setPlayerClass (const std::string& id) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mClass = id; world->createRecord(player); mClassSelected = true; buildPlayer(); mUpdatePlayer = true; } void MechanicsManager::setPlayerClass (const ESM::Class &cls) { MWBase::World *world = MWBase::Environment::get().getWorld(); const ESM::Class *ptr = world->createRecord(cls); ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mClass = ptr->mId; world->createRecord(player); mClassSelected = true; buildPlayer(); mUpdatePlayer = true; } int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp) { const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); float x = static_cast(npcSkill.getBaseDisposition()); MWWorld::LiveCellRef* npc = ptr.get(); MWWorld::Ptr playerPtr = getPlayer(); MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fDispRaceMod = gmst.find("fDispRaceMod")->mValue.getFloat(); if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += fDispRaceMod; static const float fDispPersonalityMult = gmst.find("fDispPersonalityMult")->mValue.getFloat(); static const float fDispPersonalityBase = gmst.find("fDispPersonalityBase")->mValue.getFloat(); x += fDispPersonalityMult * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - fDispPersonalityBase); float reaction = 0; int rank = 0; std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); Misc::StringUtils::lowerCaseInPlace(npcFaction); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { if (!playerStats.getExpelled(npcFaction)) { // faction reaction towards itself. yes, that exists reaction = static_cast(MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, npcFaction)); rank = playerStats.getFactionRanks().find(npcFaction)->second; } } else if (!npcFaction.empty()) { std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) { std::string itFaction = playerFactionIt->first; // Ignore the faction, if a player was expelled from it. if (playerStats.getExpelled(itFaction)) continue; int itReaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, itFaction); if (playerFactionIt == playerStats.getFactionRanks().begin() || itReaction < reaction) { reaction = static_cast(itReaction); rank = playerFactionIt->second; } } } else { reaction = 0; rank = 0; } static const float fDispFactionRankMult = gmst.find("fDispFactionRankMult")->mValue.getFloat(); static const float fDispFactionRankBase = gmst.find("fDispFactionRankBase")->mValue.getFloat(); static const float fDispFactionMod = gmst.find("fDispFactionMod")->mValue.getFloat(); x += (fDispFactionRankMult * rank + fDispFactionRankBase) * fDispFactionMod * reaction; static const float fDispCrimeMod = gmst.find("fDispCrimeMod")->mValue.getFloat(); static const float fDispDiseaseMod = gmst.find("fDispDiseaseMod")->mValue.getFloat(); x -= fDispCrimeMod * playerStats.getBounty(); if (playerStats.hasCommonDisease() || playerStats.hasBlightDisease()) x += fDispDiseaseMod; static const float fDispWeaponDrawn = gmst.find("fDispWeaponDrawn")->mValue.getFloat(); if (playerStats.getDrawState() == MWMechanics::DrawState::Weapon) x += fDispWeaponDrawn; x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude(); if (clamp) return std::clamp(x, 0, 100);//, normally clamped to [0..100] when used return static_cast(x); } int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) { // Make sure zero base price items/services can't be bought/sold for 1 gold // and return the intended base price for creature merchants if (basePrice == 0 || ptr.getType() == ESM::Creature::sRecordId) return basePrice; const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); // I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered here, // otherwise one would get different prices when exiting and re-entering the dialogue window... int clampedDisposition = getDerivedDisposition(ptr); float a = std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100.f); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float d = std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100.f); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm)); return std::max(1, offerPrice); } int MechanicsManager::countDeaths (const std::string& id) const { return mActors.countDeaths (id); } void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); float npcRating1, npcRating2, npcRating3; getPersuasionRatings(npcStats, npcRating1, npcRating2, npcRating3, false); float playerRating1, playerRating2, playerRating3; getPersuasionRatings(playerStats, playerRating1, playerRating2, playerRating3, true); const int currentDisposition = getDerivedDisposition(npc); float d = 1 - 0.02f * abs(currentDisposition - 50); float target1 = d * (playerRating1 - npcRating1 + 50); float target2 = d * (playerRating2 - npcRating2 + 50); float bribeMod; if (type == PT_Bribe10) bribeMod = gmst.find("fBribe10Mod")->mValue.getFloat(); else if (type == PT_Bribe100) bribeMod = gmst.find("fBribe100Mod")->mValue.getFloat(); else bribeMod = gmst.find("fBribe1000Mod")->mValue.getFloat(); float target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod; const float iPerMinChance = gmst.find("iPerMinChance")->mValue.getFloat(); const float iPerMinChange = gmst.find("iPerMinChange")->mValue.getFloat(); const float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat(); const float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat(); float x = 0; float y = 0; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::roll0to99(prng); if (type == PT_Admire) { target1 = std::max(iPerMinChance, target1); success = (roll <= target1); float c = floor(fPerDieRollMult * (target1 - roll)); x = success ? std::max(iPerMinChange, c) : c; } else if (type == PT_Intimidate) { target2 = std::max(iPerMinChance, target2); success = (roll <= target2); float r; if (roll != target2) r = floor(target2 - roll); else r = 1; if (roll <= target2) { float s = floor(r * fPerDieRollMult * fPerTempMult); const int flee = npcStats.getAiSetting(MWMechanics::AiSetting::Flee).getBase(); const int fight = npcStats.getAiSetting(MWMechanics::AiSetting::Fight).getBase(); npcStats.setAiSetting (MWMechanics::AiSetting::Flee, std::clamp(flee + int(std::max(iPerMinChange, s)), 0, 100)); npcStats.setAiSetting (MWMechanics::AiSetting::Fight, std::clamp(fight + int(std::min(-iPerMinChange, -s)), 0, 100)); } float c = -std::abs(floor(r * fPerDieRollMult)); if (success) { if (std::abs(c) < iPerMinChange) { // Deviating from Morrowind here: it doesn't increase disposition on marginal wins, // which seems to be a bug (MCP fixes it too). // Original logic: x = 0, y = -iPerMinChange x = iPerMinChange; y = x; // This goes unused. } else { x = -floor(c * fPerTempMult); y = c; } } else { x = floor(c * fPerTempMult); y = c; } } else if (type == PT_Taunt) { target1 = std::max(iPerMinChance, target1); success = (roll <= target1); float c = std::abs(floor(target1 - roll)); if (success) { float s = c * fPerDieRollMult * fPerTempMult; const int flee = npcStats.getAiSetting(AiSetting::Flee).getBase(); const int fight = npcStats.getAiSetting(AiSetting::Fight).getBase(); npcStats.setAiSetting(AiSetting::Flee, std::clamp(flee + std::min(-int(iPerMinChange), int(-s)), 0, 100)); npcStats.setAiSetting(AiSetting::Fight, std::clamp(fight + std::max(int(iPerMinChange), int(s)), 0, 100)); } x = floor(-c * fPerDieRollMult); if (success && std::abs(x) < iPerMinChange) x = -iPerMinChange; } else // Bribe { target3 = std::max(iPerMinChance, target3); success = (roll <= target3); float c = floor((target3 - roll) * fPerDieRollMult); x = success ? std::max(iPerMinChange, c) : c; } if (type == PT_Intimidate) { tempChange = int(x); if (currentDisposition + tempChange > 100) tempChange = 100 - currentDisposition; else if (currentDisposition + tempChange < 0) tempChange = -currentDisposition; permChange = success ? -int(tempChange / fPerTempMult) : int(y); } else { tempChange = int(x * fPerTempMult); if (currentDisposition + tempChange > 100) tempChange = 100 - currentDisposition; else if (currentDisposition + tempChange < 0) tempChange = -currentDisposition; permChange = int(tempChange / fPerTempMult); } } void MechanicsManager::forceStateUpdate(const MWWorld::Ptr &ptr) { if(ptr.getClass().isActor()) mActors.forceStateUpdate(ptr); } bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { if(ptr.getClass().isActor()) return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); else return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { if(ptr.getClass().isActor()) mActors.skipAnimation(ptr); else mObjects.skipAnimation(ptr); } bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) { if(ptr.getClass().isActor()) return mActors.checkAnimationPlaying(ptr, groupName); else return false; } bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr) { if(ptr.getClass().isActor()) return true; else return mObjects.onOpen(ptr); } void MechanicsManager::onClose(const MWWorld::Ptr& ptr) { if(!ptr.getClass().isActor()) mObjects.onClose(ptr); } void MechanicsManager::persistAnimationStates() { mActors.persistAnimationStates(); mObjects.persistAnimationStates(); } void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) { mActors.updateMagicEffects(ptr); } bool MechanicsManager::toggleAI() { mAI = !mAI; MWBase::World* world = MWBase::Environment::get().getWorld(); world->getNavigator()->setUpdatesEnabled(mAI); if (mAI) world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); return mAI; } bool MechanicsManager::isAIActive() { return mAI; } void MechanicsManager::playerLoaded() { mUpdatePlayer = true; mClassSelected = true; mRaceSelected = true; mAI = true; } bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) { static std::set boundItemIDCache; // If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's for some reason if (boundItemIDCache.empty()) { // Build a list of known bound item ID's const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); for (const ESM::GameSetting ¤tSetting : gameSettings) { std::string currentGMSTID = currentSetting.mId; Misc::StringUtils::lowerCaseInPlace(currentGMSTID); // Don't bother checking this GMST if it's not a sMagicBound* one. const std::string& toFind = "smagicbound"; if (currentGMSTID.compare(0, toFind.length(), toFind) != 0) continue; // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.mValue.getString(); Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); } } // Perform bound item check and assign the Flag_Bound bit if it passes std::string tempItemID = item.getCellRef().getRefId(); Misc::StringUtils::lowerCaseInPlace(tempItemID); if (boundItemIDCache.count(tempItemID) != 0) return true; return false; } bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) { if (target.isEmpty()) return true; const MWWorld::CellRef& cellref = target.getCellRef(); // there is no harm to use unlocked doors int lockLevel = cellref.getLockLevel(); if (target.getClass().isDoor() && (lockLevel <= 0 || lockLevel == ESM::UnbreakableLock) && cellref.getTrap().empty()) { return true; } if (!target.getClass().hasToolTip(target)) return true; // TODO: implement a better check to check if target is owned bed if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0) return true; if (target.getClass().isNpc()) { if (target.getClass().getCreatureStats(target).isDead()) return true; if (target.getClass().getCreatureStats(target).getAiSequence().isInCombat()) return true; // check if a player tries to pickpocket a target NPC if (target.getClass().getCreatureStats(target).getKnockedDown() || isSneaking(ptr)) return false; return true; } if (isOwned(ptr, target, victim)) return false; // A special case for evidence chest - we should not allow to take items even if it is technically permitted return !Misc::StringUtils::ciEqual(cellref.getRefId(), "stolen_goods"); } bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) { if (ptr.getClass().getNpcStats(ptr).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return true; } if(MWBase::Environment::get().getWorld()->getPlayer().enemiesNearby()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; } MWWorld::Ptr victim; if (isAllowedToUse(ptr, bed, victim)) return false; if(commitCrime(ptr, victim, OT_SleepingInOwnedBed, bed.getCellRef().getFaction())) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage64}"); return true; } else return false; } void MechanicsManager::unlockAttempted(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) { MWWorld::Ptr victim; if (isOwned(ptr, item, victim)) { // Note that attempting to unlock something that has ever been locked (even ESM::UnbreakableLock) is a crime even if it's already unlocked. // Likewise, it's illegal to unlock something that has a trap but isn't otherwise locked. const auto& cellref = item.getCellRef(); if(cellref.getLockLevel() || !cellref.getTrap().empty()) commitCrime(ptr, victim, OT_Trespassing, item.getCellRef().getFaction()); } } std::vector > MechanicsManager::getStolenItemOwners(const std::string& itemid) { std::vector > result; StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); if (it == mStolenItems.end()) return result; else { const OwnerMap& owners = it->second; for (OwnerMap::const_iterator ownerIt = owners.begin(); ownerIt != owners.end(); ++ownerIt) result.emplace_back(ownerIt->first.first, ownerIt->second); return result; } } bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const MWWorld::Ptr& ptr) { StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); if (it == mStolenItems.end()) return false; const OwnerMap& owners = it->second; const std::string ownerid = ptr.getCellRef().getRefId(); OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); if (ownerFound != owners.end()) return true; const std::string factionid = ptr.getClass().getPrimaryFaction(ptr); if (!factionid.empty()) { OwnerMap::const_iterator factionOwnerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); return factionOwnerFound != owners.end(); } return false; } void MechanicsManager::confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) { if (player != getPlayer()) return; const std::string itemId = Misc::StringUtils::lowerCase(item.getCellRef().getRefId()); StolenItemsMap::iterator stolenIt = mStolenItems.find(itemId); if (stolenIt == mStolenItems.end()) return; Owner owner; owner.first = victim.getCellRef().getRefId(); owner.second = false; const std::string victimFaction = victim.getClass().getPrimaryFaction(victim); if (!victimFaction.empty() && Misc::StringUtils::ciEqual(item.getCellRef().getFaction(), victimFaction)) // Is the item faction-owned? { owner.first = victimFaction; owner.second = true; } Misc::StringUtils::lowerCaseInPlace(owner.first); // decrease count of stolen items int toRemove = std::min(count, mStolenItems[itemId][owner]); mStolenItems[itemId][owner] -= toRemove; if (mStolenItems[itemId][owner] == 0) { // erase owner from stolen items owners OwnerMap& owners = stolenIt->second; OwnerMap::iterator ownersIt = owners.find(owner); if (ownersIt != owners.end()) owners.erase(ownersIt); } MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); // move items from player to owner and report about theft victim.getClass().getContainerStore(victim).add(item, toRemove, victim); store.remove(item, toRemove, player); commitCrime(player, victim, OT_Theft, item.getCellRef().getFaction(), item.getClass().getValue(item) * toRemove); } void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& containerStore = targetContainer.getClass().getContainerStore(targetContainer); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; int itemCount = it->getRefData().getCount(); for (OwnerMap::iterator ownerIt = owners.begin(); ownerIt != owners.end();) { int toRemove = std::min(itemCount, ownerIt->second); itemCount -= toRemove; ownerIt->second -= toRemove; if (ownerIt->second == 0) owners.erase(ownerIt++); else ++ownerIt; } int toMove = it->getRefData().getCount() - itemCount; containerStore.add(*it, toMove, targetContainer); store.remove(*it, toMove, player); } // TODO: unhardcode the locklevel targetContainer.getCellRef().lock(50); } void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, int count, bool alarm) { if (ptr != getPlayer()) return; MWWorld::Ptr victim; bool isAllowed = true; const MWWorld::CellRef* ownerCellRef = &item.getCellRef(); if (!container.isEmpty()) { // Inherit the owner of the container ownerCellRef = &container.getCellRef(); isAllowed = isAllowedToUse(ptr, container, victim); } else { isAllowed = isAllowedToUse(ptr, item, victim); if (!item.getCellRef().hasContentFile()) { // this is a manually placed item, which means it was already stolen return; } } if (isAllowed) return; Owner owner; owner.second = false; if (!container.isEmpty() && container.getClass().isActor()) { // "container" is an actor inventory, so just take actor's ID owner.first = ownerCellRef->getRefId(); } else { owner.first = ownerCellRef->getOwner(); if (owner.first.empty()) { owner.first = ownerCellRef->getFaction(); owner.second = true; } } Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { if (victim.isEmpty() || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead())) mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; } if (alarm) commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), item.getClass().getValue(item) * count); } bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, const std::string& factionId, int arg, bool victimAware) { // NOTE: victim may be empty // Only player can commit crime if (player != getPlayer()) return false; // Find all the actors within the alarm radius std::vector neighbors; osg::Vec3f from (player.getRefData().getPosition().asVec3()); const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) neighbors.push_back(victim); // get the player's followers / allies (works recursively) that will not report crimes std::set playerFollowers; getActorsSidingWith(player, playerFollowers); // Did anyone see it? bool crimeSeen = false; for (const MWWorld::Ptr &neighbor : neighbors) { if (!canReportCrime(neighbor, victim, playerFollowers)) continue; if ((neighbor == victim && victimAware) // Murder crime can be reported even if no one saw it (hearing is enough, I guess). // TODO: Add mod support for stealth executions! || (type == OT_Murder && neighbor != victim) || (MWBase::Environment::get().getWorld()->getLOS(player, neighbor) && awarenessCheck(player, neighbor))) { // NPC will complain about theft even if he will do nothing about it if (type == OT_Theft || type == OT_Pickpocket) MWBase::Environment::get().getDialogueManager()->say(neighbor, "thief"); crimeSeen = true; } } if (crimeSeen) reportCrime(player, victim, type, factionId, arg); else if (type == OT_Assault && !victim.isEmpty()) { bool reported = false; if (victim.getClass().isClass(victim, "guard") && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) reported = reportCrime(player, victim, type, std::string(), arg); if (!reported) startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? } return crimeSeen; } bool MechanicsManager::canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers) { if (actor == getPlayer() || !actor.getClass().isNpc() || actor.getClass().getCreatureStats(actor).isDead()) return false; if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(victim)) return false; // Unconsious actor can not report about crime and should not become hostile if (actor.getClass().getCreatureStats(actor).getKnockedDown()) return false; // Player's followers should not attack player, or try to arrest him if (actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Follow)) { if (playerFollowers.find(actor) != playerFollowers.end()) return false; } return true; } bool MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, const std::string& factionId, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); if (type == OT_Murder && !victim.isEmpty()) victim.getClass().getCreatureStats(victim).notifyMurder(); // Bounty and disposition penalty for each type of crime float disp = 0.f, dispVictim = 0.f; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) { arg = store.find("iCrimeTresspass")->mValue.getInteger(); disp = dispVictim = store.find("iDispTresspass")->mValue.getFloat(); } else if (type == OT_Pickpocket) { arg = store.find("iCrimePickPocket")->mValue.getInteger(); disp = dispVictim = store.find("fDispPickPocketMod")->mValue.getFloat(); } else if (type == OT_Assault) { arg = store.find("iCrimeAttack")->mValue.getInteger(); disp = store.find("iDispAttackMod")->mValue.getFloat(); dispVictim = store.find("fDispAttacking")->mValue.getFloat(); } else if (type == OT_Murder) { arg = store.find("iCrimeKilling")->mValue.getInteger(); disp = dispVictim = store.find("iDispKilling")->mValue.getFloat(); } else if (type == OT_Theft) { disp = dispVictim = store.find("fDispStealing")->mValue.getFloat() * arg; arg = static_cast(arg * store.find("fCrimeStealing")->mValue.getFloat()); arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen } // Make surrounding actors within alarm distance respond to the crime std::vector neighbors; const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec3f from (player.getRefData().getPosition().asVec3()); float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) neighbors.push_back(victim); int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); // What amount of provocation did this crime generate? // Controls whether witnesses will engage combat with the criminal. int fight = 0, fightVictim = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) fight = fightVictim = esmStore.get().find("iFightTrespass")->mValue.getInteger(); else if (type == OT_Pickpocket) { fight = esmStore.get().find("iFightPickpocket")->mValue.getInteger(); fightVictim = esmStore.get().find("iFightPickpocket")->mValue.getInteger() * 4; // *4 according to research wiki } else if (type == OT_Assault) { fight = esmStore.get().find("iFightAttacking")->mValue.getInteger(); fightVictim = esmStore.get().find("iFightAttack")->mValue.getInteger(); } else if (type == OT_Murder) fight = fightVictim = esmStore.get().find("iFightKilling")->mValue.getInteger(); else if (type == OT_Theft) fight = fightVictim = esmStore.get().find("fFightStealing")->mValue.getInteger(); bool reported = false; std::set playerFollowers; getActorsSidingWith(player, playerFollowers); // Tell everyone (including the original reporter) in alarm range for (const MWWorld::Ptr &actor : neighbors) { if (!canReportCrime(actor, victim, playerFollowers)) continue; // Will the witness report the crime? if (actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase() >= 100) { reported = true; if (type == OT_Trespassing) MWBase::Environment::get().getDialogueManager()->say(actor, "intruder"); } } for (const MWWorld::Ptr &actor : neighbors) { if (!canReportCrime(actor, victim, playerFollowers)) continue; if (reported && actor.getClass().isClass(actor, "guard")) { // Mark as Alarmed for dialogue actor.getClass().getCreatureStats(actor).setAlarmed(true); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. actor.getClass().getNpcStats(actor).setCrimeId(id); if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) { actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); } } else { float dispTerm = (actor == victim) ? dispVictim : disp; float alarmTerm = 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase(); if (type == OT_Pickpocket && alarmTerm <= 0) alarmTerm = 1.0; if (actor != victim) dispTerm *= alarmTerm; float fightTerm = static_cast((actor == victim) ? fightVictim : fight); fightTerm += getFightDispositionBias(dispTerm); fightTerm += getFightDistanceBias(actor, player); fightTerm *= alarmTerm; const int observerFightRating = actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase(); if (observerFightRating + fightTerm > 100) fightTerm = static_cast(100 - observerFightRating); fightTerm = std::max(0.f, fightTerm); if (observerFightRating + fightTerm >= 100) { startCombat(actor, player); NpcStats& observerStats = actor.getClass().getNpcStats(actor); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast(fightTerm)); observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast(dispTerm)); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. observerStats.setCrimeId(id); // Mark as Alarmed for dialogue observerStats.setAlarmed(true); } } } if (reported) { player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); // If committing a crime against a faction member, expell from the faction if (!victim.isEmpty() && victim.getClass().isNpc()) { std::string factionID = victim.getClass().getPrimaryFaction(victim); const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); if (playerRanks.find(Misc::StringUtils::lowerCase(factionID)) != playerRanks.end()) { player.getClass().getNpcStats(player).expell(factionID); } } else if (!factionId.empty()) { const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); if (playerRanks.find(Misc::StringUtils::lowerCase(factionId)) != playerRanks.end()) { player.getClass().getNpcStats(player).expell(factionId); } } if (type == OT_Assault && !victim.isEmpty() && !victim.getClass().getCreatureStats(victim).getAiSequence().isInCombat(player) && victim.getClass().isNpc()) { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) startCombat(victim, player); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. victim.getClass().getNpcStats(victim).setCrimeId(id); } } return reported; } bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) { const MWWorld::Ptr& player = getPlayer(); if (target == player || !attacker.getClass().isActor()) return false; MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); if (attacker == player) { std::set followersAttacker; getActorsSidingWith(attacker, followersAttacker); if (followersAttacker.find(target) != followersAttacker.end()) { statsTarget.friendlyHit(); if (statsTarget.getFriendlyHits() < 4) { MWBase::Environment::get().getDialogueManager()->say(target, "hit"); return false; } } } if (canCommitCrimeAgainst(target, attacker)) commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault); AiSequence& seq = statsTarget.getAiSequence(); if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target) || attacker == player) && !seq.isInCombat(attacker)) { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit()) { // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player, // he will attack the player only if we will force him (e.g. via StartCombat console command) bool peaceful = false; std::string script = target.getClass().getScript(target); if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player) { const int fight = target.getClass().getCreatureStats(target).getAiSetting(AiSetting::Fight).getModified(); peaceful = (fight == 0); } if (!peaceful) { startCombat(target, attacker); // Force friendly actors into combat to prevent infighting between followers std::set followersTarget; getActorsSidingWith(target, followersTarget); for(const auto& follower : followersTarget) { if(follower != attacker && follower != player) startCombat(follower, attacker); } } } } return true; } bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) { const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) && !seq.isEngagedWithActor() && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); } void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) { if (attacker.isEmpty() || victim.isEmpty()) return; if (victim == attacker) return; // known to happen if (!victim.getClass().isNpc()) return; // TODO: implement animal rights const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); const MWWorld::Ptr &player = getPlayer(); bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker); // For now we report only about crimes of player and player's followers if (attacker != player) { std::set playerFollowers; getActorsSidingWith(player, playerFollowers); if (playerFollowers.find(attacker) == playerFollowers.end()) return; } if (!canCommit && victimStats.getCrimeId() == -1) return; // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. commitCrime(player, victim, MWBase::MechanicsManager::OT_Murder); } bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) return false; const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); float sneakTerm = 0; if (isSneaking(ptr)) { static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat(); static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat(); float sneak = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak)); float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float bootWeight = 0; if (ptr.getClass().isNpc() && MWBase::Environment::get().getWorld()->isOnGround(ptr)) { const MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); if (it != inv.end()) bootWeight = it->getClass().getWeight(*it); } sneakTerm = fSneakSkillMult * sneak + 0.2f * agility + 0.1f * luck + bootWeight * fSneakBootMult; } static float fSneakDistBase = store.find("fSneakDistanceBase")->mValue.getFloat(); static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->mValue.getFloat(); osg::Vec3f pos1 (ptr.getRefData().getPosition().asVec3()); osg::Vec3f pos2 (observer.getRefData().getPosition().asVec3()); float distTerm = fSneakDistBase + fSneakDistMult * (pos1 - pos2).length(); float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude(); float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon; if (invisibility > 0.f) x += 100.f; CreatureStats& observerStats = observer.getClass().getCreatureStats(observer); float obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); float obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude(); float obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); float obsTerm = obsSneak + 0.2f * obsAgility + 0.1f * obsLuck - obsBlind; // is ptr behind the observer? static float fSneakNoViewMult = store.find("fSneakNoViewMult")->mValue.getFloat(); static float fSneakViewMult = store.find("fSneakViewMult")->mValue.getFloat(); float y = 0; osg::Vec3f vec = pos1 - pos2; if (observer.getRefData().getBaseNode()) { osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); float angleRadians = std::acos(observerDir * vec / (observerDir.length() * vec.length())); if (angleRadians > osg::DegreesToRadians(90.f)) y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; else y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; } float target = x - y; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); return (Misc::Rng::roll0to99(prng) >= target); } void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); // Don't add duplicate packages nor add packages to dead actors. if (stats.isDead() || stats.getAiSequence().isInCombat(target)) return; // The target is somehow the same as the actor. Early-out. if (ptr == target) { // We don't care about dialogue filters since the target is invalid. // We still want to play the combat taunt. MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); return; } stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { stats.setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable for (const Actor& actor : mActors) { if (actor.getPtr().getClass().isClass(actor.getPtr(), "Guard")) { MWMechanics::AiSequence& aiSeq = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence(); if (aiSeq.getTypeId() == MWMechanics::AiPackageTypeId::Pursue) { aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable } } } } } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) { mActors.stopCombat(actor); } void MechanicsManager::getObjectsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); mObjects.getObjectsInRange(position, radius, objects); } void MechanicsManager::getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); } bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius) { return mActors.isAnyObjectInRange(position, radius); } std::vector MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); } std::vector MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); } std::vector MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingIndices(actor); } std::map MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingByIndex(actor); } std::vector MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } std::vector MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) { return mActors.getEnemiesNearby(actor); } void MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) { mActors.getActorsFollowing(actor, out); } void MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) { mActors.getActorsSidingWith(actor, out); } int MechanicsManager::countSavedGameRecords() const { return 1 // Death counter +1; // Stolen items } void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const { mActors.write(writer, listener); ESM::StolenItems items; items.mStolenItems = mStolenItems; writer.startRecord(ESM::REC_STLN); items.write(writer); writer.endRecord(ESM::REC_STLN); } void MechanicsManager::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_STLN) { ESM::StolenItems items; items.load(reader); mStolenItems = items.mStolenItems; } else mActors.readRecord(reader, type); } void MechanicsManager::clear() { mActors.clear(); mStolenItems.clear(); mClassSelected = false; mRaceSelected = false; } bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { // Don't become aggressive if a calm effect is active, since it would cause combat to cycle on/off as // combat is activated here and then canceled by the calm effect if ((ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0) || (!ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0)) return false; int disposition = 50; if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr); int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(AiSetting::Fight).getModified() + static_cast(getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition))); if (ptr.getClass().isNpc() && target.getClass().isNpc()) { if (target.getClass().getNpcStats(target).isWerewolf() || (target == getPlayer() && MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().find("iWerewolfFightMod"); fight += iWerewolfFightMod->mValue.getInteger(); } } return (fight >= 100); } void MechanicsManager::resurrect(const MWWorld::Ptr &ptr) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (stats.isDead()) { stats.resurrect(); mActors.resurrect(ptr); } } bool MechanicsManager::isCastingSpell(const MWWorld::Ptr &ptr) const { return mActors.isCastingSpell(ptr); } bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const { return mActors.isReadyToBlock(ptr); } bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr &ptr) const { return mActors.isAttackingOrSpell(ptr); } void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) { MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); // The actor does not have to change state if (npcStats.isWerewolf() == werewolf) return; MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); // Werewolfs can not cast spells, so we need to unset the prepared spell if there is one. if (npcStats.getDrawState() == MWMechanics::DrawState::Spell) npcStats.setDrawState(MWMechanics::DrawState::Nothing); npcStats.setWerewolf(werewolf); MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); if(werewolf) { inv.unequipAll(actor); inv.equip(MWWorld::InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); } else { inv.unequipSlot(MWWorld::InventoryStore::Slot_Robe, actor); inv.ContainerStore::remove("werewolfrobe", 1, actor); } if(actor == player->getPlayer()) { MWBase::Environment::get().getWorld()->reattachPlayerCamera(); // Update the GUI only when called on the player MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); // Transforming removes all temporary effects actor.getClass().getCreatureStats(actor).getActiveSpells().purge([] (const auto& params) { return params.getType() == ESM::ActiveSpells::Type_Consumable || params.getType() == ESM::ActiveSpells::Type_Temporary; }, actor); mActors.updateActor(actor, 0.f); if (werewolf) { player->saveStats(); player->setWerewolfStats(); windowManager->forceHide(MWGui::GW_Inventory); windowManager->forceHide(MWGui::GW_Magic); } else { player->restoreStats(); windowManager->unsetForceHide(MWGui::GW_Inventory); windowManager->unsetForceHide(MWGui::GW_Magic); } windowManager->setWerewolfOverlay(werewolf); // Witnesses of the player's transformation will make them a globally known werewolf std::vector neighbors; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); getActorsInRange(actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->mValue.getFloat(), neighbors); bool detected = false, reported = false; for (const MWWorld::Ptr& neighbor : neighbors) { if (neighbor == actor || !neighbor.getClass().isNpc()) continue; if (MWBase::Environment::get().getWorld()->getLOS(neighbor, actor) && awarenessCheck(actor, neighbor)) { detected = true; if (neighbor.getClass().getCreatureStats(neighbor).getAiSetting(MWMechanics::AiSetting::Alarm).getModified() > 0) { reported = true; break; } } } if (detected) { windowManager->messageBox("#{sWerewolfAlarmMessage}"); MWBase::Environment::get().getWorld()->setGlobalInt("pcknownwerewolf", 1); if (reported) { npcStats.setBounty(npcStats.getBounty()+ gmst.find("iWereWolfBounty")->mValue.getInteger()); } } } } void MechanicsManager::applyWerewolfAcrobatics(const MWWorld::Ptr &actor) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); auto& skill = stats.getSkill(ESM::Skill::Acrobatics); skill.setModifier(gmst.find("fWerewolfAcrobatics")->mValue.getFloat() - skill.getModified()); } void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr &caster, int creatureActorId) { mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId); } void MechanicsManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { stats.setAttribute(frameNumber, "Mechanics Actors", mActors.size()); stats.setAttribute(frameNumber, "Mechanics Objects", mObjects.size()); } int MechanicsManager::getGreetingTimer(const MWWorld::Ptr &ptr) const { return mActors.getGreetingTimer(ptr); } float MechanicsManager::getAngleToPlayer(const MWWorld::Ptr &ptr) const { return mActors.getAngleToPlayer(ptr); } GreetingState MechanicsManager::getGreetingState(const MWWorld::Ptr &ptr) const { return mActors.getGreetingState(ptr); } bool MechanicsManager::isTurningToPlayer(const MWWorld::Ptr &ptr) const { return mActors.isTurningToPlayer(ptr); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp000066400000000000000000000274311445372753700257150ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H #define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H #include #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" #include "objects.hpp" #include "actors.hpp" namespace MWWorld { class CellStore; } namespace MWMechanics { class MechanicsManager : public MWBase::MechanicsManager { bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; bool mAI;///< is AI active? Objects mObjects; Actors mActors; typedef std::pair Owner; // < Owner id, bool isFaction > typedef std::map OwnerMap; // < Owner, number of stolen items with this id from this owner > typedef std::map StolenItemsMap; StolenItemsMap mStolenItems; public: void buildPlayer(); ///< build player according to stored class/race/birthsign information. Will /// default to the values of the ESM::NPC object, if no explicit information is given. MechanicsManager(); void add (const MWWorld::Ptr& ptr) override; ///< Register an object for management void remove (const MWWorld::Ptr& ptr, bool keepActive) override; ///< Deregister an object for management void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override; ///< Moves an object to a new cell void drop(const MWWorld::CellStore *cellStore) override; ///< Deregister all objects in the given cell. void update(float duration, bool paused); ///< Update objects /// /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). void setPlayerName (const std::string& name) override; ///< Set player name. void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) override; ///< Set player race. void setPlayerBirthsign (const std::string& id) override; ///< Set player birthsign. void setPlayerClass (const std::string& id) override; ///< Set player class to stock class. void setPlayerClass (const ESM::Class& class_) override; ///< Set player class to custom class. void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) override; void rest(double hours, bool sleep) override; ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? int getHoursToRest() const override; ///< Calculate how many hours the player needs to rest in order to be fully healed int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) override; ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. int getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp = true) override; ///< Calculate the diposition of an NPC toward the player. int countDeaths (const std::string& id) const override; ///< Return the number of deaths for actors with the given ID. void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) override; ///< Perform a persuasion action on NPC /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; /// Makes \a ptr fight \a target. Also shouts a combat taunt. void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; void stopCombat(const MWWorld::Ptr& ptr) override; /** * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @param victimAware Is the victim already aware of the crime? * If this parameter is false, it will be determined by a line-of-sight and awareness check. * @return was the crime seen? */ bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, const std::string& factionId="", int arg=0, bool victimAware=false) override; /// @return false if the attack was considered a "friendly hit" and forgiven bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; /// Notify that actor was killed, add a murder bounty if applicable /// @note No-op for non-player attackers void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, int count, bool alarm = true) override; /// Utility to check if unlocking this object is illegal and calling commitCrime if so void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override; /// Attempt sleeping in a bed. If this is illegal, call commitCrime. /// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override; void forceStateUpdate(const MWWorld::Ptr &ptr) override; /// Attempt to play an animation group /// @return Success or error bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) override; void persistAnimationStates() override; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr) override; void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) override; void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) override; /// Check if there are actors in selected range bool isAnyActorInRange(const osg::Vec3f &position, float radius) override; std::vector getActorsSidingWith(const MWWorld::Ptr& actor) override; std::vector getActorsFollowing(const MWWorld::Ptr& actor) override; std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) override; std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; std::vector getActorsFighting(const MWWorld::Ptr& actor) override; std::vector getEnemiesNearby(const MWWorld::Ptr& actor) override; /// Recursive version of getActorsFollowing void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) override; /// Recursive version of getActorsSidingWith void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) override; bool toggleAI() override; bool isAIActive() override; void playerLoaded() override; bool onOpen(const MWWorld::Ptr& ptr) override; void onClose(const MWWorld::Ptr& ptr) override; int countSavedGameRecords() const override; void write (ESM::ESMWriter& writer, Loading::Listener& listener) const override; void readRecord (ESM::ESMReader& reader, uint32_t type) override; void clear() override; bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; void resurrect(const MWWorld::Ptr& ptr) override; bool isCastingSpell (const MWWorld::Ptr& ptr) const override; bool isReadyToBlock (const MWWorld::Ptr& ptr) const override; /// Is \a ptr casting spell or using weapon now? bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const override; void castSpell(const MWWorld::Ptr& ptr, const std::string& spellId, bool manualSpell=false) override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; float getActorsProcessingRange() const override; void notifyDied(const MWWorld::Ptr& actor) override; /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override; void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) override; /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). /// std::vector > getStolenItemOwners(const std::string& itemid) override; /// Has the player stolen this item from the given owner? bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) override; bool isBoundItem(const MWWorld::Ptr& item) override; /// @return is \a ptr allowed to take/use \a target or is it a crime? bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) override; void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override; void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override; void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override; void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) override; bool isAttackPreparing(const MWWorld::Ptr& ptr) override; bool isRunning(const MWWorld::Ptr& ptr) override; bool isSneaking(const MWWorld::Ptr& ptr) override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; int getGreetingTimer(const MWWorld::Ptr& ptr) const override; float getAngleToPlayer(const MWWorld::Ptr& ptr) const override; GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; private: bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, const std::string& factionId, int arg=0); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/movement.hpp000066400000000000000000000021071445372753700235450ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_MOVEMENT_H #define GAME_MWMECHANICS_MOVEMENT_H #include namespace MWMechanics { /// Desired movement for an actor struct Movement { // Desired movement. Direction is relative to the current orientation. // Length of the vector controls desired speed. 0 - stay, 0.5 - half-speed, 1.0 - max speed. float mPosition[3]; // Desired rotation delta (euler angles). float mRotation[3]; // Controlled by CharacterController, should not be changed from other places. // These fields can not be private fields in CharacterController, because Actor::getCurrentSpeed uses it. float mSpeedFactor; bool mIsStrafing; Movement() { mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; mSpeedFactor = 1.f; mIsStrafing = false; } osg::Vec3f asVec3() { return osg::Vec3f(mPosition[0], mPosition[1], mPosition[2]); } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/npcstats.cpp000066400000000000000000000410531445372753700235500ustar00rootroot00000000000000#include "npcstats.hpp" #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" MWMechanics::NpcStats::NpcStats() : mDisposition (0) , mReputation(0) , mCrimeId(-1) , mBounty(0) , mWerewolfKills (0) , mLevelProgress(0) , mTimeToStartDrowning(-1.0) // set breath to special value, it will be replaced during actor update , mIsWerewolf(false) { mSkillIncreases.resize (ESM::Attribute::Length, 0); mSpecIncreases.resize(3, 0); } int MWMechanics::NpcStats::getBaseDisposition() const { return mDisposition; } void MWMechanics::NpcStats::setBaseDisposition(int disposition) { mDisposition = disposition; } const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const { if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return mSkill[index]; } MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) { if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return mSkill[index]; } void MWMechanics::NpcStats::setSkill(int index, const MWMechanics::SkillValue &value) { if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); mSkill[index] = value; } const std::map& MWMechanics::NpcStats::getFactionRanks() const { return mFactionRank; } int MWMechanics::NpcStats::getFactionRank(const std::string &faction) const { const std::string lower = Misc::StringUtils::lowerCase(faction); std::map::const_iterator it = mFactionRank.find(lower); if (it != mFactionRank.end()) return it->second; return -1; } void MWMechanics::NpcStats::raiseRank(const std::string &faction) { const std::string lower = Misc::StringUtils::lowerCase(faction); std::map::iterator it = mFactionRank.find(lower); if (it != mFactionRank.end()) { // Does the next rank exist? const ESM::Faction* factionPtr = MWBase::Environment::get().getWorld()->getStore().get().find(lower); if (it->second+1 < 10 && !factionPtr->mRanks[it->second+1].empty()) it->second += 1; } } void MWMechanics::NpcStats::lowerRank(const std::string &faction) { const std::string lower = Misc::StringUtils::lowerCase(faction); std::map::iterator it = mFactionRank.find(lower); if (it != mFactionRank.end()) { it->second = it->second-1; if (it->second < 0) { mFactionRank.erase(it); mExpelled.erase(lower); } } } void MWMechanics::NpcStats::joinFaction(const std::string& faction) { const std::string lower = Misc::StringUtils::lowerCase(faction); std::map::iterator it = mFactionRank.find(lower); if (it == mFactionRank.end()) mFactionRank[lower] = 0; } bool MWMechanics::NpcStats::getExpelled(const std::string& factionID) const { return mExpelled.find(Misc::StringUtils::lowerCase(factionID)) != mExpelled.end(); } void MWMechanics::NpcStats::expell(const std::string& factionID) { std::string lower = Misc::StringUtils::lowerCase(factionID); if (mExpelled.find(lower) == mExpelled.end()) { std::string message = "#{sExpelledMessage}"; message += MWBase::Environment::get().getWorld()->getStore().get().find(factionID)->mName; MWBase::Environment::get().getWindowManager()->messageBox(message); mExpelled.insert(lower); } } void MWMechanics::NpcStats::clearExpelled(const std::string& factionID) { mExpelled.erase(Misc::StringUtils::lowerCase(factionID)); } bool MWMechanics::NpcStats::isInFaction (const std::string& faction) const { return (mFactionRank.find(Misc::StringUtils::lowerCase(faction)) != mFactionRank.end()); } int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const { std::map::const_iterator iter = mFactionReputation.find (Misc::StringUtils::lowerCase(faction)); if (iter==mFactionReputation.end()) return 0; return iter->second; } void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value) { mFactionReputation[Misc::StringUtils::lowerCase(faction)] = value; } float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const { float progressRequirement = static_cast(1 + getSkill(skillIndex).getBase()); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat(); for (int i=0; i<5; ++i) { if (class_.mData.mSkills[i][0]==skillIndex) { typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat(); break; } else if (class_.mData.mSkills[i][1]==skillIndex) { typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat(); break; } } progressRequirement *= typeFactor; if (typeFactor<=0) throw std::runtime_error ("invalid skill type factor"); float specialisationFactor = 1; const ESM::Skill *skill = MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); if (skill->mData.mSpecialization==class_.mData.mSpecialization) { specialisationFactor = gmst.find ("fSpecialSkillBonus")->mValue.getFloat(); if (specialisationFactor<=0) throw std::runtime_error ("invalid skill specialisation factor"); } progressRequirement *= specialisationFactor; return progressRequirement; } void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) { const ESM::Skill *skill = MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); float skillGain = 1; if (usageType>=4) throw std::runtime_error ("skill usage type out of range"); if (usageType>=0) { skillGain = skill->mData.mUseValue[usageType]; if (skillGain<0) throw std::runtime_error ("invalid skill gain factor"); } skillGain *= extraFactor; MWMechanics::SkillValue& value = getSkill (skillIndex); value.setProgress(value.getProgress() + skillGain); if (int(value.getProgress())>=int(getSkillProgressRequirement(skillIndex, class_))) { // skill levelled up increaseSkill(skillIndex, class_, false); } } void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress, bool readBook) { float base = getSkill (skillIndex).getBase(); if (base >= 100.f) return; base += 1; const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); // is this a minor or major skill? int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo for (int k=0; k<5; ++k) { if (class_.mData.mSkills[k][0] == skillIndex) { mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); break; } else if (class_.mData.mSkills[k][1] == skillIndex) { mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); break; } } const ESM::Skill* skill = MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); mSkillIncreases[skill->mData.mAttribute] += increase; mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs MWBase::Environment::get().getWindowManager()->playSound("skillraise"); std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""); message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), static_cast(base)); if (readBook) message = "#{sBookSkillMessage}\n" + message; MWBase::Environment::get().getWindowManager ()->messageBox(message, MWGui::ShowInDialogueMode_Never); if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { // levelup is possible now MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); } getSkill(skillIndex).setBase (base); if (!preserveProgress) getSkill(skillIndex).setProgress(0); } int MWMechanics::NpcStats::getLevelProgress () const { return mLevelProgress; } void MWMechanics::NpcStats::levelUp() { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); mLevelProgress -= gmst.find("iLevelUpTotal")->mValue.getInteger(); mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console for (int i=0; imValue.getFloat(); MWMechanics::DynamicStat health(getHealth()); health.setBase(getHealth().getBase() + healthGain); health.setCurrent(std::max(1.f, getHealth().getCurrent() + healthGain)); setHealth(health); setLevel(getLevel()+1); } void MWMechanics::NpcStats::updateHealth() { const float endurance = getAttribute(ESM::Attribute::Endurance).getBase(); const float strength = getAttribute(ESM::Attribute::Strength).getBase(); setHealth(floor(0.5f * (strength + endurance))); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; if (num == 0) return 1; num = std::min(10, num); // iLevelUp01Mult - iLevelUp10Mult std::stringstream gmst; gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->mValue.getInteger(); } int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const { return mSpecIncreases[spec]; } void MWMechanics::NpcStats::flagAsUsed (const std::string& id) { mUsedIds.insert (id); } bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const { return mUsedIds.find (id)!=mUsedIds.end(); } int MWMechanics::NpcStats::getBounty() const { return mBounty; } void MWMechanics::NpcStats::setBounty (int bounty) { mBounty = bounty; } int MWMechanics::NpcStats::getReputation() const { return mReputation; } void MWMechanics::NpcStats::setReputation(int reputation) { // Reputation is capped in original engine mReputation = std::clamp(reputation, 0, 255); } int MWMechanics::NpcStats::getCrimeId() const { return mCrimeId; } void MWMechanics::NpcStats::setCrimeId(int id) { mCrimeId = id; } bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const { if (rank<0 || rank>=10) throw std::runtime_error ("rank index out of range"); const ESM::Faction& faction = *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); std::vector skills; for (int i=0; i<7; ++i) { if (faction.mData.mSkills[i] != -1) skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getBase())); } if (skills.empty()) return true; std::sort (skills.begin(), skills.end()); std::vector::const_reverse_iterator iter = skills.rbegin(); const ESM::RankData& rankData = faction.mData.mRankData[rank]; if (*iter::const_iterator iter (mFactionRank.begin()); iter!=mFactionRank.end(); ++iter) state.mFactions[iter->first].mRank = iter->second; state.mDisposition = mDisposition; for (int i=0; i::const_iterator iter (mExpelled.begin()); iter!=mExpelled.end(); ++iter) state.mFactions[*iter].mExpelled = true; for (std::map::const_iterator iter (mFactionReputation.begin()); iter!=mFactionReputation.end(); ++iter) state.mFactions[iter->first].mReputation = iter->second; state.mReputation = mReputation; state.mWerewolfKills = mWerewolfKills; state.mLevelProgress = mLevelProgress; for (int i=0; igetStore(); for (std::map::const_iterator iter (state.mFactions.begin()); iter!=state.mFactions.end(); ++iter) if (store.get().search (iter->first)) { if (iter->second.mExpelled) mExpelled.insert (iter->first); if (iter->second.mRank >= 0) mFactionRank[iter->first] = iter->second.mRank; if (iter->second.mReputation) mFactionReputation[Misc::StringUtils::lowerCase(iter->first)] = iter->second.mReputation; } mDisposition = state.mDisposition; for (int i=0; i::const_iterator iter (state.mUsedIds.begin()); iter!=state.mUsedIds.end(); ++iter) if (store.find (*iter)) mUsedIds.insert (*iter); mTimeToStartDrowning = state.mTimeToStartDrowning; } openmw-openmw-0.48.0/apps/openmw/mwmechanics/npcstats.hpp000066400000000000000000000113441445372753700235550ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_NPCSTATS_H #define GAME_MWMECHANICS_NPCSTATS_H #include #include #include #include #include "creaturestats.hpp" namespace ESM { struct Class; struct NpcStats; } namespace MWMechanics { /// \brief Additional stats for NPCs class NpcStats : public CreatureStats { int mDisposition; SkillValue mSkill[ESM::Skill::Length]; // SkillValue.mProgress used by the player only int mReputation; int mCrimeId; // ----- used by the player only, maybe should be moved at some point ------- int mBounty; int mWerewolfKills; /// Used only for the player and for NPC's with ranks, modified by scripts; other NPCs have maximum one faction defined in their NPC record std::map mFactionRank; std::set mExpelled; std::map mFactionReputation; int mLevelProgress; // 0-10 std::vector mSkillIncreases; // number of skill increases for each attribute (resets after leveling up) std::vector mSpecIncreases; // number of skill increases for each specialization (accumulates throughout the entire game) std::set mUsedIds; // --------------------------------------------------------------------------- /// Countdown to getting damage while underwater float mTimeToStartDrowning; bool mIsWerewolf; public: NpcStats(); int getBaseDisposition() const; void setBaseDisposition(int disposition); int getReputation() const; void setReputation(int reputation); int getCrimeId() const; void setCrimeId(int id); const SkillValue& getSkill (int index) const; SkillValue& getSkill (int index); void setSkill(int index, const SkillValue& value); int getFactionRank(const std::string &faction) const; const std::map& getFactionRanks() const; /// Increase the rank in this faction by 1, if such a rank exists. void raiseRank(const std::string& faction); /// Lower the rank in this faction by 1, if such a rank exists. void lowerRank(const std::string& faction); /// Join this faction, setting the initial rank to 0. void joinFaction(const std::string& faction); const std::set& getExpelled() const { return mExpelled; } bool getExpelled(const std::string& factionID) const; void expell(const std::string& factionID); void clearExpelled(const std::string& factionID); bool isInFaction (const std::string& faction) const; float getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const; void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1, float extraFactor=1.f); ///< Increase skill by usage. void increaseSkill (int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook = false); int getLevelProgress() const; int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; void levelUp(); void updateHealth(); ///< Calculate health based on endurance and strength. /// Called at character creation. void flagAsUsed (const std::string& id); ///< @note Id must be lower-case bool hasBeenUsed (const std::string& id) const; ///< @note Id must be lower-case int getBounty() const; void setBounty (int bounty); int getFactionReputation (const std::string& faction) const; void setFactionReputation (const std::string& faction, int value); bool hasSkillsForRank (const std::string& factionId, int rank) const; bool isWerewolf() const; void setWerewolf(bool set); int getWerewolfKills() const; /// Increments mWerewolfKills by 1. void addWerewolfKill(); float getTimeToStartDrowning() const; /// Sets time left for the creature to drown if it stays underwater. /// @param time value from [0,20] void setTimeToStartDrowning(float time); void writeState (ESM::CreatureStats& state) const; void writeState (ESM::NpcStats& state) const; void readState (const ESM::CreatureStats& state); void readState (const ESM::NpcStats& state); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/objects.cpp000066400000000000000000000070021445372753700233360ustar00rootroot00000000000000#include "objects.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "character.hpp" #include "movement.hpp" namespace MWMechanics { void Objects::addObject(const MWWorld::Ptr& ptr) { removeObject(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (anim == nullptr) return; const auto it = mObjects.emplace(mObjects.end(), ptr, anim); mIndex.emplace(ptr.mRef, it); } void Objects::removeObject(const MWWorld::Ptr& ptr) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { mObjects.erase(iter->second); mIndex.erase(iter); } } void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { const auto iter = mIndex.find(old.mRef); if (iter != mIndex.end()) iter->second->updatePtr(ptr); } void Objects::dropObjects (const MWWorld::CellStore *cellStore) { for (auto iter = mObjects.begin(); iter != mObjects.end();) { if (iter->getPtr().getCell() == cellStore) { mIndex.erase(iter->getPtr().mRef); iter = mObjects.erase(iter); } else ++iter; } } void Objects::update(float duration, bool paused) { if(!paused) { for (CharacterController& object : mObjects) object.update(duration); } else { // We still should play container opening animation in the Container GUI mode. MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); if(mode != MWGui::GM_Container) return; for (CharacterController& object : mObjects) { if (object.getPtr().getType() != ESM::Container::sRecordId) continue; if (object.isAnimPlaying("containeropen")) { object.update(duration); MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(object.getPtr()); } } } } bool Objects::onOpen(const MWWorld::Ptr& ptr) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) return iter->second->onOpen(); return true; } void Objects::onClose(const MWWorld::Ptr& ptr) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) iter->second->onClose(); } bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { return iter->second->playGroup(groupName, mode, number, persist); } else { Log(Debug::Warning) << "Warning: Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); return false; } } void Objects::skipAnimation(const MWWorld::Ptr& ptr) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) iter->second->skipAnim(); } void Objects::persistAnimationStates() { for (CharacterController& object : mObjects) object.persistAnimationState(); } void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const { for (const CharacterController& object : mObjects) if ((position - object.getPtr().getRefData().getPosition().asVec3()).length2() <= radius * radius) out.push_back(object.getPtr()); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/objects.hpp000066400000000000000000000027641445372753700233550ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_ACTIVATORS_H #define GAME_MWMECHANICS_ACTIVATORS_H #include "character.hpp" #include #include #include #include namespace osg { class Vec3f; } namespace MWWorld { class Ptr; class CellStore; } namespace MWMechanics { class Objects { std::list mObjects; std::map::iterator> mIndex; public: void addObject (const MWWorld::Ptr& ptr); ///< Register an animated object void removeObject (const MWWorld::Ptr& ptr); ///< Deregister an object void updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); ///< Updates an object with a new Ptr void dropObjects(const MWWorld::CellStore *cellStore); ///< Deregister all objects in the given cell. void update(float duration, bool paused); ///< Update object animations bool onOpen(const MWWorld::Ptr& ptr); void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; std::size_t size() const { return mObjects.size(); } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/obstacle.cpp000066400000000000000000000162041445372753700235050ustar00rootroot00000000000000#include "obstacle.hpp" #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "movement.hpp" namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking static const float DIST_SAME_SPOT = 0.5f; static const float DURATION_SAME_SPOT = 1.5f; static const float DURATION_TO_EVADE = 0.4f; const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = { { 1.0f, 0.0f }, // move to side { 1.0f, -1.0f }, // move to side and backwards { -1.0f, 0.0f }, // move to other side { -1.0f, -1.0f } // move to side and backwards }; bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) { if(getNearbyDoor(actor, minDist).isEmpty()) return false; else return true; } const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) { MWWorld::CellStore *cell = actor.getCell(); // Check all the doors in this cell const MWWorld::CellRefList& doors = cell->getReadOnlyDoors(); osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); pos.z() = 0; osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); for (const auto& ref : doors.mList) { osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); // FIXME: cast const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); const auto doorState = doorPtr.getClass().getDoorState(doorPtr); float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2]; if (doorState != MWWorld::DoorState::Idle || doorRot != 0) continue; // the door is already opened/opening doorPos.z() = 0; float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length())); // Allow 60 degrees angle between actor and door if (angle < -osg::PI / 3 || angle > osg::PI / 3) continue; // Door is not close enough if ((pos - doorPos).length2() > minDist*minDist) continue; return doorPtr; // found, stop searching } return MWWorld::Ptr(); // none found } bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer, std::vector* occupyingActors) { const auto world = MWBase::Environment::get().getWorld(); const osg::Vec3f halfExtents = world->getPathfindingAgentBounds(actor).mHalfExtents; const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); if (ignorePlayer) { const std::array ignore {actor, world->getPlayerConstPtr()}; return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); } const std::array ignore {actor}; return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); } ObstacleCheck::ObstacleCheck() : mWalkState(WalkState::Initial) , mStateDuration(0) , mEvadeDirectionIndex(0) { } void ObstacleCheck::clear() { mWalkState = WalkState::Initial; } bool ObstacleCheck::isEvading() const { return mWalkState == WalkState::Evade; } /* * input - actor, duration (time since last check) * output - true if evasive action needs to be taken * * Walking state transitions (player greeting check not shown): * * Initial ----> Norm <--------> CheckStuck -------> Evade ---+ * ^ ^ | f ^ | t ^ | | * | | | | | | | | * | +-+ +---+ +---+ | u * | any < t < u | * +---------------------------------------------+ * * f = one reaction time * t = how long before considered stuck * u = how long to move sideways * */ void ObstacleCheck::update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration) { const auto position = actor.getRefData().getPosition().asVec3(); if (mWalkState == WalkState::Initial) { mWalkState = WalkState::Norm; mStateDuration = 0; mPrev = position; mInitialDistance = (destination - position).length(); mDestination = destination; return; } if (mWalkState != WalkState::Evade) { if (mDestination != destination) { mInitialDistance = (destination - mPrev).length(); mDestination = destination; } const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getCurrentSpeed(actor) * duration; const float prevDistance = (destination - mPrev).length(); const float currentDistance = (destination - position).length(); const float movedDistance = prevDistance - currentDistance; const float movedFromInitialDistance = mInitialDistance - currentDistance; mPrev = position; if (movedDistance >= distSameSpot && movedFromInitialDistance >= distSameSpot) { mWalkState = WalkState::Norm; mStateDuration = 0; return; } if (mWalkState == WalkState::Norm) { mWalkState = WalkState::CheckStuck; mStateDuration = duration; mInitialDistance = (destination - position).length(); return; } mStateDuration += duration; if (mStateDuration < DURATION_SAME_SPOT) { return; } mWalkState = WalkState::Evade; mStateDuration = 0; chooseEvasionDirection(); return; } mStateDuration += duration; if(mStateDuration >= DURATION_TO_EVADE) { // tried to evade, assume all is ok and start again mWalkState = WalkState::Norm; mStateDuration = 0; mPrev = position; } } void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const { actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0]; actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1]; } void ObstacleCheck::chooseEvasionDirection() { // change direction if attempt didn't work ++mEvadeDirectionIndex; if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS) { mEvadeDirectionIndex = 0; } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/obstacle.hpp000066400000000000000000000036121445372753700235110ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_OBSTACLE_H #define OPENMW_MECHANICS_OBSTACLE_H #include #include namespace MWWorld { class Ptr; class ConstPtr; } namespace MWMechanics { struct Movement; static constexpr int NUM_EVADE_DIRECTIONS = 4; /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, float minDist); /// Returns door pointer within range. No guarantee is given as to which one /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer = false, std::vector* occupyingActors = nullptr); class ObstacleCheck { public: ObstacleCheck(); // Clear the timers and set the state machine to default void clear(); bool isEvading() const; // Updates internal state, call each frame for moving actor void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration); // change direction to try to fix "stuck" actor void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; private: osg::Vec3f mPrev; osg::Vec3f mDestination; // directions to try moving in when get stuck static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; enum class WalkState { Initial, Norm, CheckStuck, Evade }; WalkState mWalkState; float mStateDuration; int mEvadeDirectionIndex; float mInitialDistance = 0; void chooseEvasionDirection(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/pathfinding.cpp000066400000000000000000000540321445372753700242050ustar00rootroot00000000000000#include "pathfinding.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwphysics/collisiontype.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "pathgrid.hpp" #include "actorutil.hpp" namespace { // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, const MWMechanics::PathgridGraph *graph, const osg::Vec3f& pos, int start) { assert(grid && !grid->mPoints.empty()); float closestDistanceBetween = std::numeric_limits::max(); float closestDistanceReachable = std::numeric_limits::max(); int closestIndex = 0; int closestReachableIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) { float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos); if (potentialDistBetween < closestDistanceReachable) { // found a closer one if (graph->isPointConnected(start, counter)) { closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; } if (potentialDistBetween < closestDistanceBetween) { closestDistanceBetween = potentialDistBetween; closestIndex = counter; } } } // post-condition: start and endpoint must be connected assert(graph->isPointConnected(start, closestReachableIndex)); // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start // and the end points are the same. return std::pair (closestReachableIndex, closestReachableIndex == closestIndex); } float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs) { return (lhs - rhs).length2(); } float sqrDistanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) { return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); } float getPathStepSize(const MWWorld::ConstPtr& actor) { const auto world = MWBase::Environment::get().getWorld(); const auto realHalfExtents = world->getHalfExtents(actor); return 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); } float getHeight(const MWWorld::ConstPtr& actor) { const auto world = MWBase::Environment::get().getWorld(); const auto halfExtents = world->getHalfExtents(actor); return 2.0 * halfExtents.z(); } // Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line. bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) { osg::Vec3f v1 = p1 - p2; osg::Vec3f v3 = p3 - p2; v1.z() = v3.z() = 0; float dotProduct = v1.x() * v3.x() + v1.y() * v3.y(); float crossProduct = v1.x() * v3.y() - v1.y() * v3.x(); // Check that the angle between v1 and v3 is less or equal than 5 degrees. static const float cos175 = std::cos(osg::PI * (175.0 / 180)); bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length(); // Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`. bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length(); return checkAngle && checkDist; } struct IsValidShortcut { const DetourNavigator::Navigator* mNavigator; const DetourNavigator::AgentBounds mAgentBounds; const DetourNavigator::Flags mFlags; bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const { const auto position = DetourNavigator::raycast(*mNavigator, mAgentBounds, start, end, mFlags); return position.has_value() && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1; } }; } namespace MWMechanics { float getPathDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) { if (std::abs(lhs.z() - rhs.z()) > getHeight(actor) || canActorMoveByZAxis(actor)) return distance(lhs, rhs); return distanceIgnoreZ(lhs, rhs); } bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) { osg::Vec3f dir = to - from; dir.z() = 0; dir.normalize(); float verticalOffset = 200; // instead of '200' here we want the height of the actor osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; // cast up-down ray and find height of hit in world space float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); } /* * NOTE: This method may fail to find a path. The caller must check the * result before using it. If there is no path the AI routies need to * implement some other heuristics to reach the target. * * NOTE: It may be desirable to simply go directly to the endPoint if for * example there are no pathgrids in this cell. * * NOTE: startPoint & endPoint are in world coordinates * * Updates mPath using aStarSearch() or ray test (if shortcut allowed). * mPath consists of pathgrid points, except the last element which is * endPoint. This may be useful where the endPoint is not on a pathgrid * point (e.g. combat). However, if the caller has already chosen a * pathgrid point (e.g. wander) then it may be worth while to call * pop_back() to remove the redundant entry. * * NOTE: coordinates must be converted prior to calling getClosestPoint() * * | * | cell * | +-----------+ * | | | * | | | * | | @ | * | i | j | * |<--->|<---->| | * | +-----------+ * | k * |<---------->| world * +----------------------------- * * i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert) * j = @.x in local coordinates (i.e. within the cell) * k = @.x in world coordinates */ void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, std::back_insert_iterator> out) { const auto pathgrid = pathgridGraph.getPathgrid(); // Refer to AiWander reseach topic on openmw forums for some background. // Maybe there is no pathgrid for this cell. Just go to destination and let // physics take care of any blockages. if(!pathgrid || pathgrid->mPoints.empty()) return; // NOTE: getClosestPoint expects local coordinates Misc::CoordinateConverter converter(mCell->getCell()); // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); int startNode = getClosestPoint(pathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); std::pair endNode = getClosestReachablePoint(pathgrid, &pathgridGraph, endPointInLocalCoords, startNode); // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords); float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords); if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { *out++ = endPoint; return; } // AiWander has logic that depends on whether a path was created, // deleting allowed nodes if not. Hence a path needs to be created // even if the start and the end points are the same. // NOTE: aStarSearch will return an empty path if the start and end // nodes are the same if(startNode == endNode.first) { ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]); converter.toWorld(temp); *out++ = makeOsgVec3(temp); } else { auto path = pathgridGraph.aStarSearch(startNode, endNode.first); // If nearest path node is in opposite direction from second, remove it from path. // Especially useful for wandering actors, if the nearest node is blocked for some reason. if (path.size() > 1) { ESM::Pathgrid::Point secondNode = *(++path.begin()); osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]); osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode); osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f; osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f; if (toSecondNodeVec3f * toStartPointVec3f > 0) { ESM::Pathgrid::Point temp(secondNode); converter.toWorld(temp); // Add Z offset since path node can overlap with other objects. // Also ignore doors in raytesting. const int mask = MWPhysics::CollisionType_World; bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, mask); if (isPathClear) path.pop_front(); } } // convert supplied path to world coordinates std::transform(path.begin(), path.end(), out, [&] (ESM::Pathgrid::Point& point) { converter.toWorld(point); return makeOsgVec3(point); }); } // If endNode found is NOT the closest PathGrid point to the endPoint, // assume endPoint is not reachable from endNode. In which case, // path ends at endNode. // // So only add the destination (which may be different to the closest // pathgrid point) when endNode was the closest point to endPoint. // // This logic can fail in the opposite situate, e.g. endPoint may // have been reachable but happened to be very close to an // unreachable pathgrid point. // // The AI routines will have to deal with such situations. if (endNode.second) *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const { // This should never happen (programmers should have an if statement checking // isPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.; const auto& nextPoint = mPath.front(); const float directionX = nextPoint.x() - x; const float directionY = nextPoint.y() - y; return std::atan2(directionX, directionY); } float PathFinder::getXAngleToNext(float x, float y, float z) const { // This should never happen (programmers should have an if statement checking // isPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.; const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z); return getXAngleToDir(dir); } void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight, bool canMoveByZ, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags) { if (mPath.empty()) return; while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); const IsValidShortcut isValidShortcut { MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, flags }; if (shortenIfAlmostStraight) { while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance) && isValidShortcut(mPath[0], mPath[2])) mPath.erase(mPath.begin() + 1); if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance) && isValidShortcut(position, mPath[1])) mPath.pop_front(); } if (mPath.size() > 1) { std::size_t begin = 0; for (std::size_t i = 1; i < mPath.size(); ++i) { const float sqrDistance = Misc::getVectorToLine(position, mPath[i - 1], mPath[i]).length2(); if (sqrDistance < pointTolerance * pointTolerance && isValidShortcut(position, mPath[i])) begin = i; } for (std::size_t i = 0; i < begin; ++i) mPath.pop_front(); } if (mPath.size() == 1) { float distSqr; if (canMoveByZ) distSqr = (mPath.front() - position).length2(); else distSqr = sqrDistanceIgnoreZ(mPath.front(), position); if (distSqr < destinationTolerance * destinationTolerance) mPath.pop_front(); } } void PathFinder::buildStraightPath(const osg::Vec3f& endPoint) { mPath.clear(); mPath.push_back(endPoint); mConstructed = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); mCell = cell; buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); mConstructed = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { mPath.clear(); // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); if (status == DetourNavigator::Status::NavMeshNotFound) mPath.push_back(endPoint); mConstructed = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { mPath.clear(); mCell = cell; DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound; if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); } if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() && (flags & DetourNavigator::Flag_usePathgrid) == 0) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); } if (mPath.empty()) buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); if (status == DetourNavigator::Status::NavMeshNotFound && mPath.empty()) mPath.push_back(endPoint); mConstructed = !mPath.empty(); } DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType, std::back_insert_iterator> out) { const auto world = MWBase::Environment::get().getWorld(); const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); const auto status = DetourNavigator::findPath(*navigator, agentBounds, stepSize, startPoint, endPoint, flags, areaCosts, endTolerance, out); if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath) return DetourNavigator::Status::Success; if (status != DetourNavigator::Status::Success) { Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() << ") from " << startPoint << " to " << endPoint << " with flags (" << DetourNavigator::WriteFlags {flags} << ")"; } return status; } void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) { if (mPath.empty()) return; const auto stepSize = getPathStepSize(actor); const auto startPoint = actor.getRefData().getPosition().asVec3(); if (sqrDistanceIgnoreZ(mPath.front(), startPoint) <= 4 * stepSize * stepSize) return; const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); std::deque prePath; auto prePathInserter = std::back_inserter(prePath); const float endTolerance = 0; const auto status = DetourNavigator::findPath(*navigator, agentBounds, stepSize, startPoint, mPath.front(), flags, areaCosts, endTolerance, prePathInserter); if (status == DetourNavigator::Status::NavMeshNotFound) return; if (status != DetourNavigator::Status::Success) { Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() << ") from " << startPoint << " to " << mPath.front() << " with flags (" << DetourNavigator::WriteFlags {flags} << ")"; return; } while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize) prePath.pop_front(); while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize) prePath.pop_back(); std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); } void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto maxDistance = std::min( navigator->getMaxNavmeshAreaRealRadius(), static_cast(Constants::CellSizeInUnits) ); const auto startToEnd = endPoint - startPoint; const auto distance = startToEnd.length(); if (distance <= maxDistance) return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType); const auto end = startPoint + startToEnd * maxDistance / distance; buildPath(actor, startPoint, end, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/pathfinding.hpp000066400000000000000000000212021445372753700242030ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_PATHFINDING_H #define GAME_MWMECHANICS_PATHFINDING_H #include #include #include #include #include #include #include #include namespace MWWorld { class CellStore; class ConstPtr; class Ptr; } namespace DetourNavigator { struct AgentBounds; } namespace MWMechanics { class PathgridGraph; template inline float distance(const T& lhs, const T& rhs) { static_assert(std::is_same::value || std::is_same::value, "T is not a position"); return (lhs - rhs).length(); } inline float distanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) { return distance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); } float getPathDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); inline float getZAngleToDir(const osg::Vec3f& dir) { return std::atan2(dir.x(), dir.y()); } inline float getXAngleToDir(const osg::Vec3f& dir) { float dirLen = dir.length(); return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0; } inline float getZAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest) { return getZAngleToDir(dest - origin); } inline float getXAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest) { return getXAngleToDir(dest - origin); } const float PATHFIND_Z_REACH = 50.0f; // distance after which actor (failed previously to shortcut) will try again const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; const float MIN_TOLERANCE = 1.0f; const float DEFAULT_TOLERANCE = 32.0f; // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); enum class PathType { Full, Partial, }; class PathFinder { public: PathFinder() : mConstructed(false) , mCell(nullptr) { } void clearPath() { mConstructed = false; mPath.clear(); mCell = nullptr; } void buildStraightPath(const osg::Vec3f& endPoint); void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight, bool canMoveByZ, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags); bool checkPathCompleted() const { return mConstructed && mPath.empty(); } /// In radians float getZAngleToNext(float x, float y) const; float getXAngleToNext(float x, float y, float z) const; bool isPathConstructed() const { return mConstructed && !mPath.empty(); } std::size_t getPathSize() const { return mPath.size(); } const std::deque& getPath() const { return mPath; } const MWWorld::CellStore* getPathCell() const { return mCell; } void addPointToPath(const osg::Vec3f& point) { mConstructed = true; mPath.push_back(point); } /// utility function to convert a osg::Vec3f to a Pathgrid::Point static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v) { return ESM::Pathgrid::Point(static_cast(v[0]), static_cast(v[1]), static_cast(v[2])); } /// utility function to convert an ESM::Position to a Pathgrid::Point static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p) { return ESM::Pathgrid::Point(static_cast(p.pos[0]), static_cast(p.pos[1]), static_cast(p.pos[2])); } static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p) { return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); } // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 // static float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos) { return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2(); } // Return the closest pathgrid point index from the specified position // coordinates. NOTE: Does not check if there is a sensible way to get there // (e.g. a cliff in front). // // NOTE: pos is expected to be in local coordinates, as is grid->mPoints // static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) { assert(grid && !grid->mPoints.empty()); float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) { float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); if(potentialDistBetween < distanceBetween) { distanceBetween = potentialDistBetween; closestIndex = counter; } } return closestIndex; } private: bool mConstructed; std::deque mPath; const MWWorld::CellStore* mCell; void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType, std::back_insert_iterator> out); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/pathgrid.cpp000066400000000000000000000277161445372753700235250ustar00rootroot00000000000000#include "pathgrid.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" namespace { // See https://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html // // One of the smallest cost in Seyda Neen is between points 77 & 78: // pt x y // 77 = 8026, 4480 // 78 = 7986, 4218 // // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 // (again ignoring z). Using a value of about 300 for D seems like a reasonable // starting point for experiments. If in doubt, just use value 1. // // The distance between 3 & 4 are pretty small, too. // 3 = 5435, 223 // 4 = 5948, 193 // // Approx. 514 Euclidean distance and 533 Manhattan distance. // float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) { return 300.0f * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); } // Choose a heuristics - Note that these may not be the best for directed // graphs with non-uniform edge costs. // // distance: // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) // - slower but more accurate // // Manhattan: // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| // - faster but not the shortest path float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) { //return distance(a, b); return manhattan(a, b); } } namespace MWMechanics { PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) : mCell(nullptr) , mPathgrid(nullptr) , mGraph(0) , mIsGraphConstructed(false) , mSCCId(0) , mSCCIndex(0) { load(cell); } /* * mGraph is populated with the cost of each allowed edge. * * The data structure is based on the code in buildPath2() but modified. * Please check git history if interested. * * mGraph[v].edges[i].index = w * * v = point index of location "from" * i = index of edges from point v * w = point index of location "to" * * * Example: (notice from p(0) to p(2) is not allowed in this example) * * mGraph[0].edges[0].index = 1 * .edges[1].index = 3 * * mGraph[1].edges[0].index = 0 * .edges[1].index = 2 * .edges[2].index = 3 * * mGraph[2].edges[0].index = 1 * * (etc, etc) * * * low * cost * p(0) <---> p(1) <------------> p(2) * ^ ^ * | | * | +-----> p(3) * +----------------> * high cost */ bool PathgridGraph::load(const MWWorld::CellStore *cell) { if(!cell) return false; if(mIsGraphConstructed) return true; mCell = cell->getCell(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell()); if(!mPathgrid) return false; mGraph.resize(mPathgrid->mPoints.size()); for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) { ConnectedPoint neighbour; neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); // forward path of the edge neighbour.index = mPathgrid->mEdges[i].mV1; mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); // reverse path of the edge // NOTE: These are redundant, ESM already contains the required reverse paths //neighbour.index = mPathgrid->mEdges[i].mV0; //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); } buildConnectedPoints(); mIsGraphConstructed = true; return true; } const ESM::Pathgrid *PathgridGraph::getPathgrid() const { return mPathgrid; } // v is the pathgrid point index (some call them vertices) void PathgridGraph::recursiveStrongConnect(int v) { mSCCPoint[v].first = mSCCIndex; // index mSCCPoint[v].second = mSCCIndex; // lowlink mSCCIndex++; mSCCStack.push_back(v); int w; for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) { w = mGraph[v].edges[i].index; if(mSCCPoint[w].first == -1) // not visited { recursiveStrongConnect(w); // recurse mSCCPoint[v].second = std::min(mSCCPoint[v].second, mSCCPoint[w].second); } else { if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) mSCCPoint[v].second = std::min(mSCCPoint[v].second, mSCCPoint[w].first); } } if(mSCCPoint[v].second == mSCCPoint[v].first) { // new component do { w = mSCCStack.back(); mSCCStack.pop_back(); mGraph[w].componentId = mSCCId; } while(w != v); mSCCId++; } return; } /* * mGraph contains the strongly connected component group id's along * with pre-calculated edge costs. * * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 * * mGraph for Seyda Neen will therefore have 3 different values. When * selecting a random pathgrid point for AiWander, mGraph can be checked * for quickly finding whether the destination is reachable. * * Otherwise, buildPath can automatically select a closest reachable end * pathgrid point (reachable from the closest start point). * * Using Tarjan's algorithm: * * mGraph | graph G | * mSCCPoint | V | derived from mPoints * mGraph[v].edges | E (for v) | * mSCCIndex | index | tracking smallest unused index * mSCCStack | S | * mGraph[v].edges[i].index | w | * */ void PathgridGraph::buildConnectedPoints() { // both of these are set to zero in the constructor //mSCCId = 0; // how many strongly connected components in this cell //mSCCIndex = 0; int pointsSize = static_cast (mPathgrid->mPoints.size()); mSCCPoint.resize(pointsSize, std::pair (-1, -1)); mSCCStack.reserve(pointsSize); for(int v = 0; v < pointsSize; v++) { if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } } bool PathgridGraph::isPointConnected(const int start, const int end) const { return (mGraph[start].componentId == mGraph[end].componentId); } void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const { for(int i = 0; i < static_cast (mGraph[index].edges.size()); i++) { int neighbourIndex = mGraph[index].edges[i].index; if (neighbourIndex != index) nodes.push_back(mPathgrid->mPoints[neighbourIndex]); } } /* * NOTE: Based on buildPath2(), please check git history if interested * Should consider using a 3rd party library version (e.g. boost) * * Find the shortest path to the target goal using a well known algorithm. * Uses mGraph which has pre-computed costs for allowed edges. It is assumed * that mGraph is already constructed. * * Should be possible to make this MT safe. * * Returns path which may be empty. path contains pathgrid points in local * cell coordinates (indoors) or world coordinates (external). * * Input params: * start, goal - pathgrid point indexes (for this cell) * * Variables: * openset - point indexes to be traversed, lowest cost at the front * closedset - point indexes already traversed * gScore - past accumulated costs vector indexed by point index * fScore - future estimated costs vector indexed by point index * * TODO: An intersting exercise might be to cache the paths created for a * start/goal pair. To cache the results the paths need to be in * pathgrid points form (currently they are converted to world * coordinates). Essentially trading speed w/ memory. */ std::deque PathgridGraph::aStarSearch(const int start, const int goal) const { std::deque path; if(!isPointConnected(start, goal)) { return path; // there is no path, return an empty path } int graphSize = static_cast (mGraph.size()); std::vector gScore (graphSize, -1); std::vector fScore (graphSize, -1); std::vector graphParent (graphSize, -1); // gScore & fScore keep costs for each pathgrid point in mPoints gScore[start] = 0; fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); std::list openset; std::list closedset; openset.push_back(start); int current = -1; while(!openset.empty()) { current = openset.front(); // front has the lowest cost openset.pop_front(); if(current == goal) break; closedset.push_back(current); // remember we've been here // check all edges for the current point index for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == closedset.end()) { // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].index; float tentative_g = gScore[current] + mGraph[current].edges[j].cost; bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); if(!isInOpenSet || tentative_g < gScore[dest]) { graphParent[dest] = current; gScore[dest] = tentative_g; fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], mPathgrid->mPoints[goal]); if(!isInOpenSet) { // add this edge to openset, lowest cost goes to the front // TODO: if this causes performance problems a hash table may help std::list::iterator it = openset.begin(); for(it = openset.begin(); it!= openset.end(); ++it) { if(fScore[*it] > fScore[dest]) break; } openset.insert(it, dest); } } } // if in closedset, i.e. traversed this edge already, try the next edge } } if(current != goal) return path; // for some reason couldn't build a path // reconstruct path to return, using local coordinates while(graphParent[current] != -1) { path.push_front(mPathgrid->mPoints[current]); current = graphParent[current]; } // add first node to path explicitly path.push_front(mPathgrid->mPoints[start]); return path; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/pathgrid.hpp000066400000000000000000000050611445372753700235170ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_PATHGRID_H #define GAME_MWMECHANICS_PATHGRID_H #include #include namespace ESM { struct Cell; } namespace MWWorld { class CellStore; } namespace MWMechanics { class PathgridGraph { public: PathgridGraph(const MWWorld::CellStore* cell); bool load(const MWWorld::CellStore *cell); const ESM::Pathgrid* getPathgrid() const; // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; // get neighbouring nodes for index node and put them to "nodes" vector void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const; // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) coordinates // // NOTE: if start equals end an empty path is returned std::deque aStarSearch(const int start, const int end) const; private: const ESM::Cell *mCell; const ESM::Pathgrid *mPathgrid; struct ConnectedPoint // edge { int index; // pathgrid point index of neighbour float cost; }; struct Node // point { int componentId; std::vector edges; // neighbours }; // componentId is an integer indicating the groups of connected // pathgrid points (all connected points will have the same value) // // In Seyda Neen there are 3: // // 52, 53 and 54 are one set (enclosed yard) // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) // all other pathgrid points are the third set // std::vector mGraph; bool mIsGraphConstructed; // variables used to calculate connected components int mSCCId; int mSCCIndex; std::vector mSCCStack; typedef std::pair VPair; // first is index, second is lowlink std::vector mSCCPoint; // methods used to calculate connected components void recursiveStrongConnect(int v); void buildConnectedPoints(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/pickpocket.cpp000066400000000000000000000045021445372753700240430ustar00rootroot00000000000000#include "pickpocket.hpp" #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "npcstats.hpp" namespace MWMechanics { Pickpocket::Pickpocket(const MWWorld::Ptr &thief, const MWWorld::Ptr &victim) : mThief(thief) , mVictim(victim) { } float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add) { NpcStats& stats = ptr.getClass().getNpcStats(ptr); float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float sneak = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak)); return (add + 0.2f * agility + 0.1f * luck + sneak) * stats.getFatigueTerm(); } bool Pickpocket::getDetected(float valueTerm) { float x = getChanceModifier(mThief); float y = getChanceModifier(mVictim, valueTerm); float t = 2*x - y; float pcSneak = static_cast(mThief.getClass().getSkill(mThief, ESM::Skill::Sneak)); int iPickMinChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMinChance")->mValue.getInteger(); int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMaxChance")->mValue.getInteger(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::roll0to99(prng); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); } else { t = std::min(float(iPickMaxChance), t); return (roll > int(t)); } } bool Pickpocket::pick(const MWWorld::Ptr& item, int count) { float stackValue = static_cast(item.getClass().getValue(item) * count); float fPickPocketMod = MWBase::Environment::get().getWorld()->getStore().get() .find("fPickPocketMod")->mValue.getFloat(); float valueTerm = 10 * fPickPocketMod * stackValue; return getDetected(valueTerm); } bool Pickpocket::finish() { return getDetected(0.f); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/pickpocket.hpp000066400000000000000000000012661445372753700240540ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_PICKPOCKET_H #define OPENMW_MECHANICS_PICKPOCKET_H #include "../mwworld/ptr.hpp" namespace MWMechanics { class Pickpocket { public: Pickpocket (const MWWorld::Ptr& thief, const MWWorld::Ptr& victim); /// Steal some items /// @return Was the thief detected? bool pick (const MWWorld::Ptr& item, int count); /// End the pickpocketing process /// @return Was the thief detected? bool finish (); private: bool getDetected(float valueTerm); float getChanceModifier(const MWWorld::Ptr& ptr, float add=0); MWWorld::Ptr mThief; MWWorld::Ptr mVictim; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/recharge.cpp000066400000000000000000000065551445372753700235010ustar00rootroot00000000000000#include "recharge.hpp" #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" namespace MWMechanics { bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration) { float charge = item.getCellRef().getEnchantmentCharge(); if (charge == -1 || charge == maxCharge) return false; static const float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicItemRechargePerSecond")->mValue.getFloat(); item.getCellRef().setEnchantmentCharge(std::min(charge + fMagicItemRechargePerSecond * duration, maxCharge)); return true; } bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem) { if (!gem.getRefData().getCount()) return false; MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified(); if (luckTerm < 1 || luckTerm > 10) luckTerm = 1; float intelligenceTerm = 0.2f * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); if (intelligenceTerm > 20) intelligenceTerm = 20; if (intelligenceTerm < 1) intelligenceTerm = 1; float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::roll0to99(prng); if (roll < x) { std::string soul = gem.getCellRef().getSoul(); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); float restored = creature->mData.mSoul * (roll / x); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( item.getClass().getEnchantment(item)); item.getCellRef().setEnchantmentCharge( std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); MWBase::Environment::get().getWindowManager()->playSound("Enchant Success"); player.getClass().getContainerStore(player).restack(item); } else { MWBase::Environment::get().getWindowManager()->playSound("Enchant Fail"); } player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); gem.getContainerStore()->remove(gem, 1, player); if (gem.getRefData().getCount() == 0) { std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->mValue.getString(); message = Misc::StringUtils::format(message, gem.getClass().getName(gem)); MWBase::Environment::get().getWindowManager()->messageBox(message); // special case: readd Azura's Star if (Misc::StringUtils::ciEqual(gem.get()->mBase->mId, "Misc_SoulGem_Azura")) player.getClass().getContainerStore(player).add("Misc_SoulGem_Azura", 1, player); } return true; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/recharge.hpp000066400000000000000000000004531445372753700234750ustar00rootroot00000000000000#ifndef MWMECHANICS_RECHARGE_H #define MWMECHANICS_RECHARGE_H #include "../mwworld/ptr.hpp" namespace MWMechanics { bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration); bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/repair.cpp000066400000000000000000000074211445372753700231740ustar00rootroot00000000000000#include "repair.hpp" #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" #include "actorutil.hpp" namespace MWMechanics { void Repair::repair(const MWWorld::Ptr &itemToRepair) { MWWorld::Ptr player = getPlayer(); MWWorld::LiveCellRef *ref = mTool.get(); // unstack tool if required player.getClass().getContainerStore(player).unstack(mTool, player); // reduce number of uses left int uses = mTool.getClass().getItemHealth(mTool); uses -= std::min(uses, 1); mTool.getCellRef().setCharge(uses); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); float fatigueTerm = stats.getFatigueTerm(); float pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); float pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fRepairAmountMult")->mValue.getFloat(); float toolQuality = ref->mBase->mData.mQuality; float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::roll0to99(prng); if (roll <= x) { int y = static_cast(fRepairAmountMult * toolQuality * roll); y = std::max(1, y); // repair by 'y' points int charge = itemToRepair.getClass().getItemHealth(itemToRepair); charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); itemToRepair.getCellRef().setCharge(charge); // attempt to re-stack item, in case it was fully repaired MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair); // set the OnPCRepair variable on the item's script std::string script = stacked->getClass().getScript(itemToRepair); if(script != "") stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); MWBase::Environment::get().getWindowManager()->playSound("Repair"); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); } else { MWBase::Environment::get().getWindowManager()->playSound("Repair Fail"); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairFailed}"); } // tool used up? if (mTool.getCellRef().getCharge() == 0) { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(mTool, 1, player); std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->mValue.getString(); message = Misc::StringUtils::format(message, mTool.getClass().getName(mTool)); MWBase::Environment::get().getWindowManager()->messageBox(message); // try to find a new tool of the same ID for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), mTool.getCellRef().getRefId())) { mTool = *iter; MWBase::Environment::get().getWindowManager()->playSound("Item Repair Up"); break; } } } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/repair.hpp000066400000000000000000000006141445372753700231760ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_REPAIR_H #define OPENMW_MWMECHANICS_REPAIR_H #include "../mwworld/ptr.hpp" namespace MWMechanics { class Repair { public: void setTool (const MWWorld::Ptr& tool) { mTool = tool; } MWWorld::Ptr getTool() { return mTool; } void repair (const MWWorld::Ptr& itemToRepair); private: MWWorld::Ptr mTool; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/security.cpp000066400000000000000000000104251445372753700235570ustar00rootroot00000000000000#include "security.hpp" #include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" namespace MWMechanics { Security::Security(const MWWorld::Ptr &actor) : mActor(actor) { CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); mAgility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); mLuck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); mSecuritySkill = static_cast(actor.getClass().getSkill(actor, ESM::Skill::Security)); mFatigueTerm = creatureStats.getFatigueTerm(); } void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { if (lock.getCellRef().getLockLevel() <= 0 || lock.getCellRef().getLockLevel() == ESM::UnbreakableLock || !lock.getClass().hasToolTip(lock)) //If it's unlocked or can not be unlocked back out immediately return; int uses = lockpick.getClass().getItemHealth(lockpick); if (uses == 0) return; int lockStrength = lock.getCellRef().getLockLevel(); float pickQuality = lockpick.get()->mBase->mData.mQuality; float fPickLockMult = MWBase::Environment::get().getWorld()->getStore().get().find("fPickLockMult")->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x *= pickQuality * mFatigueTerm; x += fPickLockMult * lockStrength; MWBase::Environment::get().getMechanicsManager()->unlockAttempted(mActor, lock); resultSound = "Open Lock Fail"; if (x <= 0) resultMessage = "#{sLockImpossible}"; else { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) <= x) { lock.getCellRef().unlock(); resultMessage = "#{sLockSuccess}"; resultSound = "Open Lock"; mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1); } else resultMessage = "#{sLockFail}"; } lockpick.getCellRef().setCharge(--uses); if (!uses) lockpick.getContainerStore()->remove(lockpick, 1, mActor); } void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe, std::string& resultMessage, std::string& resultSound) { if (trap.getCellRef().getTrap().empty()) return; int uses = probe.getClass().getItemHealth(probe); if (uses == 0) return; float probeQuality = probe.get()->mBase->mData.mQuality; const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get().find(trap.getCellRef().getTrap()); int trapSpellPoints = trapSpell->mData.mCost; float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fTrapCostMult")->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x += fTrapCostMult * trapSpellPoints; x *= probeQuality * mFatigueTerm; MWBase::Environment::get().getMechanicsManager()->unlockAttempted(mActor, trap); resultSound = "Disarm Trap Fail"; if (x <= 0) resultMessage = "#{sTrapImpossible}"; else { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) <= x) { trap.getCellRef().setTrap(""); resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0); } else resultMessage = "#{sTrapFail}"; } probe.getCellRef().setCharge(--uses); if (!uses) probe.getContainerStore()->remove(probe, 1, mActor); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/security.hpp000066400000000000000000000012741445372753700235660ustar00rootroot00000000000000#ifndef MWMECHANICS_SECURITY_H #define MWMECHANICS_SECURITY_H #include "../mwworld/ptr.hpp" namespace MWMechanics { /// @brief implementation of Security skill class Security { public: Security (const MWWorld::Ptr& actor); void pickLock (const MWWorld::Ptr& lock, const MWWorld::Ptr& lockpick, std::string& resultMessage, std::string& resultSound); void probeTrap (const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, std::string& resultMessage, std::string& resultSound); private: float mAgility, mLuck, mSecuritySkill, mFatigueTerm; MWWorld::Ptr mActor; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/setbaseaisetting.hpp000066400000000000000000000022011445372753700252440ustar00rootroot00000000000000#ifndef OPENMW_MWMECHANICS_SETBASEAISETTING_H #define OPENMW_MWMECHANICS_SETBASEAISETTING_H #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "aisetting.hpp" #include "creaturestats.hpp" namespace MWMechanics { template void setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) { T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); switch (setting) { case MWMechanics::AiSetting::Hello: copy.mAiData.mHello = value; break; case MWMechanics::AiSetting::Fight: copy.mAiData.mFight = value; break; case MWMechanics::AiSetting::Flee: copy.mAiData.mFlee = value; break; case MWMechanics::AiSetting::Alarm: copy.mAiData.mAlarm = value; break; default: assert(false); } MWBase::Environment::get().getWorld()->createOverrideRecord(copy); } } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellcasting.cpp000066400000000000000000000570271445372753700244110ustar00rootroot00000000000000#include "spellcasting.hpp" #include #include #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" #include "actorutil.hpp" #include "aifollow.hpp" #include "creaturestats.hpp" #include "spelleffects.hpp" #include "spellutil.hpp" #include "summoning.hpp" #include "weapontype.hpp" namespace MWMechanics { CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) , mFromProjectile(fromProjectile) , mManualSpell(manualSpell) { } void CastSpell::launchMagicBolt () { osg::Vec3f fallbackDirection(0, 1, 0); osg::Vec3f offset(0, 0, 0); if (!mTarget.isEmpty() && mTarget.getClass().isActor()) offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z(); // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) fallbackDirection = (mTarget.getRefData().getPosition().asVec3() + offset) - (mCaster.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool exploded) { const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) { // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); if (stats.isDead() && stats.isDeathAnimationFinished()) return; } // If none of the effects need to apply, we can early-out bool found = false; bool containsRecastable = false; std::vector magicEffects; magicEffects.reserve(effects.mList.size()); const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); for (const ESM::ENAMstruct& effect : effects.mList) { if (effect.mRange == range) { found = true; const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID); // caster needs to be an actor for linked effects (e.g. Absorb) if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (mCaster.isEmpty() || !mCaster.getClass().isActor())) { magicEffects.push_back(nullptr); continue; } if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable)) containsRecastable = true; magicEffects.push_back(magicEffect); } else magicEffects.push_back(nullptr); } if (!found) return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); if (spell && targetIsActor && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? ESM::MagicEffect::ResistCommonDisease : ESM::MagicEffect::ResistBlightDisease; float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) <= x) { // Fully resisted, show message if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); return; } } ActiveSpells::ActiveSpellParams params(*this, caster); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); const ActiveSpells* targetSpells = nullptr; if (targetIsActor) targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells(); // Re-casting a bound equipment effect has no effect if the spell is still active if (!containsRecastable && targetSpells && targetSpells->isSpellActive(mId)) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); return; } for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size(); ++currentEffectIndex) { const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex]; if (enam.mRange != range) continue; const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex]; if (!magicEffect) continue; ActiveSpells::ActiveEffect effect; effect.mEffectId = enam.mEffectID; effect.mArg = MWMechanics::EffectKey(enam).mArg; effect.mMagnitude = 0.f; effect.mMinMagnitude = enam.mMagnMin; effect.mMaxMagnitude = enam.mMagnMax; effect.mTimeLeft = 0.f; effect.mEffectIndex = static_cast(currentEffectIndex); effect.mFlags = ESM::ActiveEffect::Flag_None; if(mManualSpell) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); effect.mDuration = hasDuration ? static_cast(enam.mDuration) : 1.f; bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; if (!appliedOnce) effect.mDuration = std::max(1.f, effect.mDuration); effect.mTimeLeft = effect.mDuration; // add to list of active effects, to apply in next frame params.getEffects().emplace_back(effect); bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || enam.mEffectID == ESM::MagicEffect::RestoreHealth; if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth) { // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. MWBase::Environment::get().getWindowManager()->setEnemy(target); } if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { playEffects(target, *magicEffect); } } if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot); if (!target.isEmpty()) { if (!params.getEffects().empty()) { if(targetIsActor) target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); else { // Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway // and we can ignore reflection since non-actors cannot reflect spells for(auto& effect : params.getEffects()) applyMagicEffect(target, caster, params, effect, 0.f); } } } } bool CastSpell::cast(const std::string &id) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (const auto spell = store.get().search(id)) return cast(spell); if (const auto potion = store.get().search(id)) return cast(potion); if (const auto ingredient = store.get().search(id)) return cast(ingredient); throw std::runtime_error("ID type cannot be casted"); } bool CastSpell::cast(const MWWorld::Ptr &item, int slot, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) throw std::runtime_error("can't cast an item without an enchantment"); mSourceName = item.getClass().getName(item); mId = item.getCellRef().getRefId(); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); mSlot = slot; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; if (item.getType() == ESM::Weapon::sRecordId) { int type = item.get()->mBase->mData.mType; ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo); } int type = enchantment->mData.mType; // Check if there's enough charge left if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); if (item.getCellRef().getEnchantmentCharge() < castCost) { if (mCaster == getPlayer()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); // Failure sound int school = 0; if (!enchantment->mEffects.mList.empty()) { short effectId = enchantment->mEffects.mList.front().mEffectID; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); school = magicEffect->mData.mSchool; } static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); } return false; } // Reduce charge item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost); } if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); } else if (type == ESM::Enchantment::CastOnce) { if (!godmode) item.getContainerStore()->remove(item, 1, mCaster); } else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } if (isProjectile) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Self); else inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); if (launchProjectile) launchMagicBolt(); else if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; } bool CastSpell::cast(const ESM::Potion* potion) { mSourceName = potion->mName; mId = potion->mId; mType = ESM::ActiveSpells::Type_Consumable; inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); return true; } bool CastSpell::cast(const ESM::Spell* spell) { mSourceName = spell->mName; mId = spell->mId; int school = 0; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell) { school = getSpellSchool(spell, mCaster); CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); if (!godmode) { bool fail = false; // Check success float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) >= successChance) { if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); fail = true; } if (fail) { // Failure sound static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } } // A power can be used once per 24h if (spell->mData.mType == ESM::Spell::ST_Power) stats.getSpells().usePower(spell); } if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) playSpellCastingEffects(spell->mEffects.mList); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); launchMagicBolt(); return true; } bool CastSpell::cast (const ESM::Ingredient* ingredient) { mId = ingredient->mId; mType = ESM::ActiveSpells::Type_Consumable; mSourceName = ingredient->mName; ESM::ENAMstruct effect; effect.mEffectID = ingredient->mData.mEffectID[0]; effect.mSkill = ingredient->mData.mSkills[0]; effect.mAttribute = ingredient->mData.mAttributes[0]; effect.mRange = ESM::RT_Self; effect.mArea = 0; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const auto magicEffect = store.get().find(effect.mEffectID); const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) + 0.2f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::roll0to99(prng); if (roll > x) { // "X has no effect on you" std::string message = store.get().find("sNotifyMessage50")->mValue.getString(); message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } float magnitude = 0; float y = roll / std::min(x, 100.f); y *= 0.25f * x; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) effect.mDuration = 1; else effect.mDuration = static_cast(y); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); else magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); magnitude = std::max(1.f, magnitude); } else magnitude = 1; effect.mMagnMax = static_cast(magnitude); effect.mMagnMin = static_cast(magnitude); ESM::EffectList effects; effects.mList.push_back(effect); inflict(mCaster, mCaster, effects, ESM::RT_Self); return true; } void CastSpell::playSpellCastingEffects(const std::string &spellid, bool enchantment) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (enchantment) { if (const auto spell = store.get().search(spellid)) playSpellCastingEffects(spell->mEffects.mList); } else { if (const auto spell = store.get().search(spellid)) playSpellCastingEffects(spell->mEffects.mList); } } void CastSpell::playSpellCastingEffects(const std::vector& effects) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); std::vector addedEffects; const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::ENAMstruct& effectData : effects) { const auto effect = store.get().find(effectData.mEffectID); const ESM::Static* castStatic; if (!effect->mCasting.empty()) castStatic = store.get().find (effect->mCasting); else castStatic = store.get().find ("VFX_DefaultCast"); // check if the effect was already added if (std::find(addedEffects.begin(), addedEffects.end(), Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)) != addedEffects.end()) continue; MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { animation->addEffect( Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mIndex, false, "", effect->mParticle); } else { // If the caster has no animation, add the effect directly to the effectManager // We must scale and position it manually float scale = mCaster.getCellRef().getScale(); osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); if (!mCaster.getClass().isNpc()) { osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f); scale *= std::max({bounds.x(), bounds.y(), bounds.z() / 2.f}) / 64.f; float offset = 0.f; if (bounds.z() < 128.f) offset = bounds.z() - 128.f; else if (bounds.z() < bounds.x() + bounds.y()) offset = 128.f - bounds.z(); if (MWBase::Environment::get().getWorld()->isFlying(mCaster)) offset /= 20.f; pos.z() += offset * scale; } else { // Additionally use the NPC's height osg::Vec3f npcScaleVec (1.f, 1.f, 1.f); mCaster.getClass().adjustScale(mCaster, npcScaleVec, true); scale *= npcScaleVec.z(); } scale = std::max(scale, 1.f); MWBase::Environment::get().getWorld()->spawnEffect( Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mParticle, pos, scale); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); else sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } } void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping) { if (playNonLooping) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!magicEffect.mHitSound.empty()) sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f); else sndMgr->playSound3D(target, schools[magicEffect.mData.mSchool]+" hit", 1.0f, 1.0f); } // Add VFX const ESM::Static* castStatic; if (!magicEffect.mHit.empty()) castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect.mHit); else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); if(anim && !castStatic->mModel.empty()) { // Don't play particle VFX unless the effect is new or it should be looping. if (playNonLooping || loop) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); anim->addEffect( Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), magicEffect.mIndex, loop, "", magicEffect.mParticle); } } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellcasting.hpp000066400000000000000000000051151445372753700244050ustar00rootroot00000000000000#ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H #include #include #include "../mwworld/ptr.hpp" namespace ESM { struct Spell; struct Ingredient; struct Potion; struct EffectList; struct MagicEffect; } namespace MWMechanics { struct EffectKey; class CastSpell { private: MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty void playSpellCastingEffects(const std::vector& effects); public: std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb bool mAlwaysSucceed{false}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) int mSlot{0}; ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary}; public: CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false); bool cast (const ESM::Spell* spell); /// @note mCaster must be an actor /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. bool cast (const MWWorld::Ptr& item, int slot, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); bool cast (const ESM::Potion* potion); /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); void playSpellCastingEffects(const std::string &spellid, bool enchantment); /// Launch a bolt with the given effects. void launchMagicBolt (); /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false); }; void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spelleffects.cpp000066400000000000000000001520401445372753700243670ustar00rootroot00000000000000#include "spelleffects.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellresistance.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/summoning.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" namespace { float roll(const ESM::ActiveEffect& effect) { if(effect.mMinMagnitude == effect.mMaxMagnitude) return effect.mMinMagnitude; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng); } void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::AiSetting setting, float magnitude, bool& invalid) { if(target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc()) invalid = true; else { auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getAiSetting(setting); stat.setModifier(static_cast(stat.getModifier() + magnitude)); creatureStats.setAiSetting(setting, stat); } } void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false) { auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified); creatureStats.setDynamic(index, stat); } void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude) { auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); float current = stat.getCurrent(); stat.setBase(std::max(0.f, stat.getBase() + magnitude)); stat.setCurrent(current + magnitude); creatureStats.setDynamic(index, stat); } void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attr = creatureStats.getAttribute(effect.mArg); if(effect.mEffectId == ESM::MagicEffect::DamageAttribute) magnitude = std::min(attr.getModified(), magnitude); attr.damage(magnitude); creatureStats.setAttribute(effect.mArg, attr); } void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attr = creatureStats.getAttribute(effect.mArg); attr.restore(magnitude); creatureStats.setAttribute(effect.mArg, attr); } void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attr = creatureStats.getAttribute(effect.mArg); attr.setModifier(attr.getModifier() + magnitude); creatureStats.setAttribute(effect.mArg, attr); } void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); if(effect.mEffectId == ESM::MagicEffect::DamageSkill) magnitude = std::min(skill.getModified(), magnitude); skill.damage(magnitude); } void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); skill.restore(magnitude); } void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); skill.setModifier(skill.getModifier() + magnitude); } bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate) { MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator item = inv.getSlot(slot); if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) { if (!item->getClass().hasItemHealth(*item)) return false; int charge = item->getClass().getItemHealth(*item); if (charge == 0) return false; // Store remainder of disintegrate amount (automatically subtracted if > 1) item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); charge = item->getClass().getItemHealth(*item); charge -= std::min(static_cast(disintegrate), charge); item->getCellRef().setCharge(charge); if (charge == 0) { // Will unequip the broken item and try to find a replacement if (ptr != MWMechanics::getPlayer()) inv.autoEquip(ptr); else inv.unequipItem(*item, ptr); } return true; } return false; } int getBoundItemSlot(const MWWorld::Ptr& boundPtr) { const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr); if(!slots.empty()) return slots[0]; return -1; } void addBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); int slot = getBoundItemSlot(boundPtr); auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end(); MWWorld::ActionEquip action(boundPtr); action.execute(actor); if (actor != MWMechanics::getPlayer()) return; MWWorld::Ptr newItem; auto it = slot >= 0 ? store.getSlot(slot) : store.end(); // Equip can fail because beast races cannot equip boots/helmets if(it != store.end()) newItem = *it; if (newItem.isEmpty() || boundPtr != newItem) return; MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); // change draw state only if the item is in player's right hand if (slot == MWWorld::InventoryStore::Slot_CarriedRight) player.setDrawState(MWMechanics::DrawState::Weapon); if (prevItem != store.end()) player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); } void removeBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); auto item = std::find_if(store.begin(), store.end(), [&] (const auto& it) { return Misc::StringUtils::ciEqual(it.getCellRef().getRefId(), itemId); }); if(item == store.end()) return; int slot = getBoundItemSlot(*item); auto currentItem = store.getSlot(slot); bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); if (actor != MWMechanics::getPlayer()) { store.remove(itemId, 1, actor); // Equip a replacement if (!wasEquipped) return; auto type = currentItem->getType(); if (type != ESM::Weapon::sRecordId && type != ESM::Armor::sRecordId && type != ESM::Clothing::sRecordId) return; if (actor.getClass().getCreatureStats(actor).isDead()) return; if (!actor.getClass().hasInventoryStore(actor)) return; if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) return; actor.getClass().getInventoryStore(actor).autoEquip(actor); return; } MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); std::string prevItemId = player.getPreviousItem(itemId); player.erasePreviousItem(itemId); if (!prevItemId.empty() && wasEquipped) { // Find previous item (or its replacement) by id. // we should equip previous item only if expired bound item was equipped. MWWorld::Ptr prevItem = store.findReplacement(prevItemId); if (!prevItem.isEmpty()) { MWWorld::ActionEquip action(prevItem); action.execute(actor); } } store.remove(itemId, 1, actor); } bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false) { if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus) { const auto* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectId); if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful)) return true; } return false; } void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation && !absorbStatic->mModel.empty()) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); animation->addEffect( Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs), ESM::MagicEffect::SpellAbsorption, false, std::string()); } const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; if (spell) { spellCost = MWMechanics::calcSpellCost(*spell); } else { const ESM::Enchantment* enchantment = esmStore.get().search(spellId); if (enchantment) spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), caster); } // Magicka is increased by the cost of the spell auto& stats = target.getClass().getCreatureStats(target); auto magicka = stats.getMagicka(); magicka.setCurrent(magicka.getCurrent() + spellCost); stats.setMagicka(magicka); } MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect) { auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); // Apply reflect and spell absorption if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent) { bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) && !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f && !caster.isEmpty(); bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f; if(canReflect || canAbsorb) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for(const auto& activeParam : stats.getActiveSpells()) { for(const auto& activeEffect : activeParam.getEffects()) { if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied)) continue; if(activeEffect.mEffectId == ESM::MagicEffect::Reflect) { if(canReflect && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { return MWMechanics::MagicApplicationResult::REFLECTED; } } else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption) { if(canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { absorbSpell(spellParams.getId(), caster, target); return MWMechanics::MagicApplicationResult::REMOVED; } } } } } } // Notify the target actor they've been hit bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); // Apply resistances if(!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { const ESM::Spell* spell = nullptr; if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary) spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellParams.getId()); float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); if (magnitudeMult == 0) { // Fully resisted, show message if (target == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); else if (caster == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); return MWMechanics::MagicApplicationResult::REMOVED; } effect.mMinMagnitude *= magnitudeMult; effect.mMaxMagnitude *= magnitudeMult; } return MWMechanics::MagicApplicationResult::APPLIED; } static const std::map sBoundItemsMap{ {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"}, {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"}, {ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID"}, {ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID"}, {ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID"}, {ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID"}, {ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID"}, {ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID"}, {ESM::MagicEffect::BoundMace, "sMagicBoundMaceID"}, {ESM::MagicEffect::BoundShield, "sMagicBoundShieldID"}, {ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID"} }; } namespace MWMechanics { void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka) { const auto world = MWBase::Environment::get().getWorld(); bool godmode = target == getPlayer() && world->getGodModeState(); switch(effect.mEffectId) { case ESM::MagicEffect::CureCommonDisease: target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); break; case ESM::MagicEffect::CureBlightDisease: target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); break; case ESM::MagicEffect::RemoveCurse: target.getClass().getCreatureStats(target).getSpells().purgeCurses(); break; case ESM::MagicEffect::CureCorprusDisease: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus); break; case ESM::MagicEffect::CurePoison: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison); break; case ESM::MagicEffect::CureParalyzation: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Paralyze); break; case ESM::MagicEffect::Dispel: // Dispel removes entire spells at once target.getClass().getCreatureStats(target).getActiveSpells().purge([magnitude=effect.mMagnitude] (const ActiveSpells::ActiveSpellParams& params) { if(params.getType() == ESM::ActiveSpells::Type_Temporary) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(params.getId()); if (spell && spell->mData.mType == ESM::Spell::ST_Spell) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); return Misc::Rng::roll0to99(prng) < magnitude; } } return false; }, target); break; case ESM::MagicEffect::AlmsiviIntervention: case ESM::MagicEffect::DivineIntervention: if (target != getPlayer()) invalid = true; else if (world->isTeleportingEnabled()) { auto marker = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; world->teleportToClosestMarker(target, marker); if(!caster.isEmpty()) { MWRender::Animation* anim = world->getAnimation(caster); anim->removeEffect(effect.mEffectId); const ESM::Static* fx = world->getStore().get().search("VFX_Summon_end"); if (fx) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1); } } } else if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); break; case ESM::MagicEffect::Mark: if (target != getPlayer()) invalid = true; else if (world->isTeleportingEnabled()) world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); else if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); break; case ESM::MagicEffect::Recall: if (target != getPlayer()) invalid = true; else if (world->isTeleportingEnabled()) { MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; world->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell) { MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); action.execute(target); if(!caster.isEmpty()) { MWRender::Animation* anim = world->getAnimation(caster); anim->removeEffect(effect.mEffectId); } } } else if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); break; case ESM::MagicEffect::CommandCreature: case ESM::MagicEffect::CommandHumanoid: if(caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) invalid = true; else if(effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) { MWMechanics::AiFollow package(caster, true); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); } break; case ESM::MagicEffect::ExtraSpell: if(target.getClass().hasInventoryStore(target)) { auto& store = target.getClass().getInventoryStore(target); store.unequipAll(target); } else invalid = true; break; case ESM::MagicEffect::TurnUndead: if(target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) invalid = true; else { auto& creatureStats = target.getClass().getCreatureStats(target); Stat stat = creatureStats.getAiSetting(AiSetting::Flee); stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); creatureStats.setAiSetting(AiSetting::Flee, stat); } break; case ESM::MagicEffect::FrenzyCreature: case ESM::MagicEffect::FrenzyHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude, invalid); break; case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::CalmHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude, invalid); if(!invalid && effect.mMagnitude > 0) { auto& creatureStats = target.getClass().getCreatureStats(target); creatureStats.getAiSequence().stopCombat(); } break; case ESM::MagicEffect::DemoralizeCreature: case ESM::MagicEffect::DemoralizeHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude, invalid); break; case ESM::MagicEffect::RallyCreature: case ESM::MagicEffect::RallyHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude, invalid); break; case ESM::MagicEffect::SummonScamp: case ESM::MagicEffect::SummonClannfear: case ESM::MagicEffect::SummonDaedroth: case ESM::MagicEffect::SummonDremora: case ESM::MagicEffect::SummonAncestralGhost: case ESM::MagicEffect::SummonSkeletalMinion: case ESM::MagicEffect::SummonBonewalker: case ESM::MagicEffect::SummonGreaterBonewalker: case ESM::MagicEffect::SummonBonelord: case ESM::MagicEffect::SummonWingedTwilight: case ESM::MagicEffect::SummonHunger: case ESM::MagicEffect::SummonGoldenSaint: case ESM::MagicEffect::SummonFlameAtronach: case ESM::MagicEffect::SummonFrostAtronach: case ESM::MagicEffect::SummonStormAtronach: case ESM::MagicEffect::SummonCenturionSphere: case ESM::MagicEffect::SummonFabricant: case ESM::MagicEffect::SummonWolf: case ESM::MagicEffect::SummonBear: case ESM::MagicEffect::SummonBonewolf: case ESM::MagicEffect::SummonCreature04: case ESM::MagicEffect::SummonCreature05: if(!target.isInCell()) invalid = true; else effect.mArg = summonCreature(effect.mEffectId, target); break; case ESM::MagicEffect::BoundGloves: if(!target.getClass().hasInventoryStore(target)) { invalid = true; break; } addBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); // left gauntlet added below [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: case ESM::MagicEffect::BoundBattleAxe: case ESM::MagicEffect::BoundSpear: case ESM::MagicEffect::BoundLongbow: case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundHelm: case ESM::MagicEffect::BoundBoots: case ESM::MagicEffect::BoundShield: if(!target.getClass().hasInventoryStore(target)) invalid = true; else { const std::string& item = sBoundItemsMap.at(effect.mEffectId); addBoundItem(world->getStore().get().find(item)->mValue.getString(), target); } break; case ESM::MagicEffect::FireDamage: case ESM::MagicEffect::ShockDamage: case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::DamageHealth: case ESM::MagicEffect::Poison: case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageFatigue: if(!godmode) { int index = 0; if(effect.mEffectId == ESM::MagicEffect::DamageMagicka) index = 1; else if(effect.mEffectId == ESM::MagicEffect::DamageFatigue) index = 2; // Damage "Dynamic" abilities reduce the base value if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) modDynamicStat(target, index, -effect.mMagnitude); else { static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); adjustDynamicStat(target, index, -effect.mMagnitude, index == 2 && uncappedDamageFatigue); if(index == 0) receivedMagicDamage = true; } } break; case ESM::MagicEffect::DamageAttribute: if(!godmode) damageAttribute(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::DamageSkill: if(!target.getClass().isNpc()) invalid = true; else if(!godmode) { // Damage Skill abilities reduce base skill :todd: if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) { auto& npcStats = target.getClass().getNpcStats(target); SkillValue& skill = npcStats.getSkill(effect.mArg); // Damage Skill abilities reduce base skill :todd: skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); } else damageSkill(target, effect, effect.mMagnitude); } break; case ESM::MagicEffect::RestoreAttribute: restoreAttribute(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::RestoreSkill: if(!target.getClass().isNpc()) invalid = true; else restoreSkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude); break; case ESM::MagicEffect::SunDamage: { // isInCell shouldn't be needed, but updateActor called during game start if (!target.isInCell() || !target.getCell()->isExterior() || godmode) break; float time = world->getTimeStamp().getHour(); float timeDiff = std::clamp(std::abs(time - 13.f), 0.f, 7.f); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved static float fMagicSunBlockedMult = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); int weather = world->getCurrentWeather(); if (weather > 1) damageScale *= fMagicSunBlockedMult; float damage = effect.mMagnitude * damageScale; adjustDynamicStat(target, 0, -damage); if (damage > 0.f) receivedMagicDamage = true; } break; case ESM::MagicEffect::DrainHealth: case ESM::MagicEffect::DrainMagicka: case ESM::MagicEffect::DrainFatigue: if(!godmode) { static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; adjustDynamicStat(target, index, -effect.mMagnitude, uncappedDamageFatigue && index == 2); if(index == 0) receivedMagicDamage = true; } break; case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); else adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true); break; case ESM::MagicEffect::DrainAttribute: if(!godmode) damageAttribute(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) { auto& creatureStats = target.getClass().getCreatureStats(target); AttributeValue attr = creatureStats.getAttribute(effect.mArg); attr.setBase(attr.getBase() + effect.mMagnitude); creatureStats.setAttribute(effect.mArg, attr); } else fortifyAttribute(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::DrainSkill: if(!target.getClass().isNpc()) invalid = true; else if(!godmode) damageSkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifySkill: if(!target.getClass().isNpc()) invalid = true; else if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) { // Abilities affect base stats, but not for drain auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); skill.setBase(skill.getBase() + effect.mMagnitude); } else fortifySkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: recalculateMagicka = true; break; case ESM::MagicEffect::AbsorbHealth: case ESM::MagicEffect::AbsorbMagicka: case ESM::MagicEffect::AbsorbFatigue: if(!godmode) { int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth; adjustDynamicStat(target, index, -effect.mMagnitude); if(!caster.isEmpty()) adjustDynamicStat(caster, index, effect.mMagnitude); if(index == 0) receivedMagicDamage = true; } break; case ESM::MagicEffect::AbsorbAttribute: if(!godmode) { damageAttribute(target, effect, effect.mMagnitude); if(!caster.isEmpty()) fortifyAttribute(caster, effect, effect.mMagnitude); } break; case ESM::MagicEffect::AbsorbSkill: if(!target.getClass().isNpc()) invalid = true; else if(!godmode) { damageSkill(target, effect, effect.mMagnitude); if(!caster.isEmpty()) fortifySkill(caster, effect, effect.mMagnitude); } break; case ESM::MagicEffect::DisintegrateArmor: { if (!target.getClass().hasInventoryStore(target)) { invalid = true; break; } if (godmode) break; static const std::array priorities { MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet, MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Boots }; for (const int priority : priorities) { if (disintegrateSlot(target, priority, effect.mMagnitude)) break; } break; } case ESM::MagicEffect::DisintegrateWeapon: if (!target.getClass().hasInventoryStore(target)) { invalid = true; break; } if (!godmode) disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); break; } } bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect) { const auto world = MWBase::Environment::get().getWorld(); switch(effect.mEffectId) { case ESM::MagicEffect::Levitate: { if(!world->isLevitationEnabled()) { if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); return true; } break; } case ESM::MagicEffect::Recall: case ESM::MagicEffect::DivineIntervention: case ESM::MagicEffect::AlmsiviIntervention: { return effect.mFlags & ESM::ActiveEffect::Flag_Applied; } case ESM::MagicEffect::WaterWalking: { if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) return true; if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) break; if (!world->isWaterWalkingCastableOnTarget(target)) { if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); return true; } break; } } return false; } MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) { const auto world = MWBase::Environment::get().getWorld(); bool invalid = false; bool receivedMagicDamage = false; bool recalculateMagicka = false; if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) { spellParams.worsen(); for(auto& otherEffect : spellParams.getEffects()) { if(isCorprusEffect(otherEffect)) applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka); } if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); return MagicApplicationResult::APPLIED; } else if(shouldRemoveEffect(target, effect)) { onMagicEffectRemoved(target, spellParams, effect); return MagicApplicationResult::REMOVED; } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) { if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) { effect.mTimeLeft -= dt; return MagicApplicationResult::APPLIED; } else if(!dt) return MagicApplicationResult::APPLIED; } if(effect.mEffectId == ESM::MagicEffect::Lock) { if(target.getClass().canLock(target)) { MWRender::Animation* animation = world->getAnimation(target); if(animation) animation->addSpellCastGlow(magicEffect); int magnitude = static_cast(roll(effect)); if(target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { if(caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().lock(magnitude); } } else invalid = true; } else if(effect.mEffectId == ESM::MagicEffect::Open) { if(target.getClass().canLock(target)) { // Use the player instead of the caster for vanilla crime compatibility MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); MWRender::Animation* animation = world->getAnimation(target); if(animation) animation->addSpellCastGlow(magicEffect); int magnitude = static_cast(roll(effect)); if(target.getCellRef().getLockLevel() <= magnitude) { if(target.getCellRef().getLockLevel() > 0) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); if(caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } target.getCellRef().unlock(); } else { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); } } else invalid = true; } else if(!target.getClass().isActor()) { invalid = true; } else { // Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats // updated instantly. We don't want to teleport instantly though if (!dt && (effect.mEffectId == ESM::MagicEffect::Recall || effect.mEffectId == ESM::MagicEffect::DivineIntervention || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention)) return MagicApplicationResult::APPLIED; auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) { MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect); if(result != MagicApplicationResult::APPLIED) return result; } float oldMagnitude = 0.f; if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) oldMagnitude = effect.mMagnitude; else { if(spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) playEffects(target, *magicEffect, spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); if(effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); } float magnitude = roll(effect); //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here effect.mMagnitude = magnitude; if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) { if(effect.mDuration != 0) { float mult = dt; if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) mult = std::min(effect.mTimeLeft, dt); effect.mMagnitude *= mult; } if(effect.mMagnitude == 0) { effect.mMagnitude = oldMagnitude; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; effect.mTimeLeft -= dt; return MagicApplicationResult::APPLIED; } } if(effect.mEffectId == ESM::MagicEffect::Corprus) spellParams.worsen(); else applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka); effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); } effect.mTimeLeft -= dt; if(invalid) { effect.mTimeLeft = 0; effect.mFlags |= ESM::ActiveEffect::Flag_Remove; auto anim = world->getAnimation(target); if(anim) anim->removeEffect(effect.mEffectId); } else effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; if (receivedMagicDamage && target == getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); if(recalculateMagicka) target.getClass().getCreatureStats(target).recalculateMagicka(); return MagicApplicationResult::APPLIED; } void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) { const auto world = MWBase::Environment::get().getWorld(); auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); bool invalid; switch(effect.mEffectId) { case ESM::MagicEffect::CommandCreature: case ESM::MagicEffect::CommandHumanoid: if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) { auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); seq.erasePackageIf([&](const auto& package) { return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(package.get())->isCommanded(); }); } break; case ESM::MagicEffect::ExtraSpell: if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) target.getClass().getInventoryStore(target).autoEquip(target); break; case ESM::MagicEffect::TurnUndead: { auto& creatureStats = target.getClass().getCreatureStats(target); Stat stat = creatureStats.getAiSetting(AiSetting::Flee); stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); creatureStats.setAiSetting(AiSetting::Flee, stat); } break; case ESM::MagicEffect::FrenzyCreature: case ESM::MagicEffect::FrenzyHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude, invalid); break; case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::CalmHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude, invalid); break; case ESM::MagicEffect::DemoralizeCreature: case ESM::MagicEffect::DemoralizeHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude, invalid); break; case ESM::MagicEffect::RallyCreature: case ESM::MagicEffect::RallyHumanoid: modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude, invalid); break; case ESM::MagicEffect::SummonScamp: case ESM::MagicEffect::SummonClannfear: case ESM::MagicEffect::SummonDaedroth: case ESM::MagicEffect::SummonDremora: case ESM::MagicEffect::SummonAncestralGhost: case ESM::MagicEffect::SummonSkeletalMinion: case ESM::MagicEffect::SummonBonewalker: case ESM::MagicEffect::SummonGreaterBonewalker: case ESM::MagicEffect::SummonBonelord: case ESM::MagicEffect::SummonWingedTwilight: case ESM::MagicEffect::SummonHunger: case ESM::MagicEffect::SummonGoldenSaint: case ESM::MagicEffect::SummonFlameAtronach: case ESM::MagicEffect::SummonFrostAtronach: case ESM::MagicEffect::SummonStormAtronach: case ESM::MagicEffect::SummonCenturionSphere: case ESM::MagicEffect::SummonFabricant: case ESM::MagicEffect::SummonWolf: case ESM::MagicEffect::SummonBear: case ESM::MagicEffect::SummonBonewolf: case ESM::MagicEffect::SummonCreature04: case ESM::MagicEffect::SummonCreature05: { if(effect.mArg != -1) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg); auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); auto [begin, end] = summons.equal_range(effect.mEffectId); for(auto it = begin; it != end; ++it) { if(it->second == effect.mArg) { summons.erase(it); break; } } } break; case ESM::MagicEffect::BoundGloves: removeBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: case ESM::MagicEffect::BoundBattleAxe: case ESM::MagicEffect::BoundSpear: case ESM::MagicEffect::BoundLongbow: case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundHelm: case ESM::MagicEffect::BoundBoots: case ESM::MagicEffect::BoundShield: { const std::string& item = sBoundItemsMap.at(effect.mEffectId); removeBoundItem(world->getStore().get().find(item)->mValue.getString(), target); } break; case ESM::MagicEffect::DrainHealth: case ESM::MagicEffect::DrainMagicka: case ESM::MagicEffect::DrainFatigue: adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude); break; case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); else adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true); break; case ESM::MagicEffect::DrainAttribute: restoreAttribute(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) { auto& creatureStats = target.getClass().getCreatureStats(target); AttributeValue attr = creatureStats.getAttribute(effect.mArg); attr.setBase(attr.getBase() - effect.mMagnitude); creatureStats.setAttribute(effect.mArg, attr); } else fortifyAttribute(target, effect, -effect.mMagnitude); break; case ESM::MagicEffect::DrainSkill: restoreSkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifySkill: // Abilities affect base stats, but not for drain if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); skill.setBase(skill.getBase() - effect.mMagnitude); } else fortifySkill(target, effect, -effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: target.getClass().getCreatureStats(target).recalculateMagicka(); break; case ESM::MagicEffect::AbsorbAttribute: { const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); restoreAttribute(target, effect, effect.mMagnitude); if(!caster.isEmpty()) fortifyAttribute(caster, effect, -effect.mMagnitude); } break; case ESM::MagicEffect::AbsorbSkill: { const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); restoreSkill(target, effect, effect.mMagnitude); if(!caster.isEmpty()) fortifySkill(caster, effect, -effect.mMagnitude); } break; case ESM::MagicEffect::Corprus: { int worsenings = spellParams.getWorsenings(); spellParams.resetWorsenings(); if(worsenings > 0) { for(const auto& otherEffect : spellParams.getEffects()) { if(isCorprusEffect(otherEffect, true)) { for(int i = 0; i < worsenings; i++) removeMagicEffect(target, spellParams, otherEffect); } } } //Note that we remove the effects, but keep the params target.getClass().getCreatureStats(target).getActiveSpells().purge([&spellParams] (const ActiveSpells::ActiveSpellParams& params, const auto&) { return &spellParams == ¶ms; }, target); } break; } } void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) { if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) return; auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); removeMagicEffect(target, spellParams, effect); if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) { auto anim = MWBase::Environment::get().getWorld()->getAnimation(target); if(anim) anim->removeEffect(effect.mEffectId); } } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spelleffects.hpp000066400000000000000000000017051445372753700243750ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H #define GAME_MWMECHANICS_SPELLEFFECTS_H #include "activespells.hpp" #include "../mwworld/ptr.hpp" // These functions should probably be split up into separate Lua functions for each magic effect when magic is dehardcoded. // That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion. namespace MWMechanics { enum class MagicApplicationResult { APPLIED, REMOVED, REFLECTED }; // Applies a tick of a single effect. Returns true if the effect should be removed immediately MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spelllist.cpp000066400000000000000000000116541445372753700237300ustar00rootroot00000000000000#include "spelllist.hpp" #include #include #include "spells.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace { template const std::vector getSpellList(const std::string& id) { return MWBase::Environment::get().getWorld()->getStore().get().find(id)->mSpells.mList; } template bool withBaseRecord(const std::string& id, const std::function&)>& function) { T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); bool changed = function(copy.mSpells.mList); if(changed) MWBase::Environment::get().getWorld()->createOverrideRecord(copy); return changed; } } namespace MWMechanics { SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} bool SpellList::withBaseRecord(const std::function&)>& function) { switch(mType) { case ESM::REC_CREA: return ::withBaseRecord(mId, function); case ESM::REC_NPC_: return ::withBaseRecord(mId, function); default: throw std::logic_error("failed to update base record for " + mId); } } const std::vector SpellList::getSpells() const { switch(mType) { case ESM::REC_CREA: return getSpellList(mId); case ESM::REC_NPC_: return getSpellList(mId); default: throw std::logic_error("failed to get spell list for " + mId); } } const ESM::Spell* SpellList::getSpell(const std::string& id) { return MWBase::Environment::get().getWorld()->getStore().get().find(id); } void SpellList::add (const ESM::Spell* spell) { auto& id = spell->mId; bool changed = withBaseRecord([&] (auto& spells) { for(const auto& it : spells) { if(Misc::StringUtils::ciEqual(id, it)) return false; } spells.push_back(id); return true; }); if(changed) { for(auto listener : mListeners) listener->addSpell(spell); } } void SpellList::remove (const ESM::Spell* spell) { auto& id = spell->mId; bool changed = withBaseRecord([&] (auto& spells) { for(auto it = spells.begin(); it != spells.end(); it++) { if(Misc::StringUtils::ciEqual(id, *it)) { spells.erase(it); return true; } } return false; }); if(changed) { for(auto listener : mListeners) listener->removeSpell(spell); } } void SpellList::removeAll (const std::vector& ids) { bool changed = withBaseRecord([&] (auto& spells) { const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell) { const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); }; return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell); }); if (it == spells.end()) return false; spells.erase(it, spells.end()); return true; }); if(changed) { for(auto listener : mListeners) { for(auto& id : ids) { const auto spell = getSpell(id); listener->removeSpell(spell); } } } } void SpellList::clear() { bool changed = withBaseRecord([] (auto& spells) { if(spells.empty()) return false; spells.clear(); return true; }); if(changed) { for(auto listener : mListeners) listener->removeAllSpells(); } } void SpellList::addListener(Spells* spells) { if (std::find(mListeners.begin(), mListeners.end(), spells) != mListeners.end()) return; mListeners.push_back(spells); } void SpellList::removeListener(Spells* spells) { const auto it = std::find(mListeners.begin(), mListeners.end(), spells); if (it != mListeners.end()) mListeners.erase(it); } void SpellList::updateListener(Spells* before, Spells* after) { const auto it = std::find(mListeners.begin(), mListeners.end(), before); if (it == mListeners.end()) return mListeners.push_back(after); *it = after; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spelllist.hpp000066400000000000000000000041561445372753700237340ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_SPELLLIST_H #define GAME_MWMECHANICS_SPELLLIST_H #include #include #include #include #include #include namespace ESM { struct SpellState; } namespace MWMechanics { class Spells; /// Multiple instances of the same actor share the same spell list in Morrowind. /// The most obvious result of this is that adding a spell or ability to one instance adds it to all instances. /// @note The original game will only update visual effects associated with any added abilities for the originally targeted actor, /// changing cells applies the update to all actors. /// Aside from sharing the same active spell list, changes made to this list are also written to the actor's base record. /// Interestingly, it is not just scripted changes that are persisted to the base record. Curing one instance's disease will cure all instances. /// @note The original game is inconsistent in persisting this example; /// saving and loading the game might reapply the cured disease depending on which instance was cured. class SpellList { const std::string mId; const int mType; std::vector mListeners; bool withBaseRecord(const std::function&)>& function); public: SpellList(const std::string& id, int type); /// Get spell from ID, throws exception if not found static const ESM::Spell* getSpell(const std::string& id); void add (const ESM::Spell* spell); ///< Adding a spell that is already listed in *this is a no-op. void remove (const ESM::Spell* spell); void removeAll(const std::vector& spells); void clear(); ///< Remove all spells of all types. void addListener(Spells* spells); void removeListener(Spells* spells); void updateListener(Spells* before, Spells* after); const std::vector getSpells() const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellpriority.cpp000066400000000000000000000657001445372753700246370ustar00rootroot00000000000000#include "spellpriority.hpp" #include "weaponpriority.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "spellresistance.hpp" #include "weapontype.hpp" #include "summoning.hpp" #include "spellutil.hpp" namespace { int numEffectsToDispel (const MWWorld::Ptr& actor, int effectFilter=-1, bool negative = true) { int toCure=0; const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc. if (effectFilter == -1) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->getId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; for (const auto& effect : params.getEffects()) { int effectId = effect.mEffectId; if (effectFilter != -1 && effectId != effectFilter) continue; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway continue; if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) ++toCure; if (!negative && !(magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)) ++toCure; } } return toCure; } float getSpellDuration (const MWWorld::Ptr& actor, const std::string& spellId) { float duration = 0; const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { if (it->getId() != spellId) continue; const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; for (const auto& effect : params.getEffects()) { if (effect.mDuration > duration) duration = effect.mDuration; } } return duration; } bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const std::string& id) { int actorId = caster.getClass().getCreatureStats(caster).getActorId(); const auto& active = target.getClass().getCreatureStats(target).getActiveSpells(); return std::find_if(active.begin(), active.end(), [&](const auto& spell) { return spell.getCasterActorId() == actorId && Misc::StringUtils::ciEqual(spell.getId(), id); }) != active.end(); } } namespace MWMechanics { int getRangeTypes (const ESM::EffectList& effects) { int types = 0; for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) { if (it->mRange == ESM::RT_Self) types |= RangeTypes::Self; else if (it->mRange == ESM::RT_Touch) types |= RangeTypes::Touch; else if (it->mRange == ESM::RT_Target) types |= RangeTypes::Target; } return types; } float ratePotion (const MWWorld::Ptr &item, const MWWorld::Ptr& actor) { if (item.getType() != ESM::Potion::sRecordId) return 0.f; const ESM::Potion* potion = item.get()->mBase; return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); } float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { float successChance = MWMechanics::getSpellSuccessChance(spell, actor); if (successChance == 0.f) return 0.f; if (spell->mData.mType != ESM::Spell::ST_Spell) return 0.f; // Don't make use of racial bonus spells, like MW. Can be made optional later if (actor.getClass().isNpc()) { std::string raceid = actor.get()->mBase->mRace; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); if (race->mPowers.exists(spell->mId)) return 0.f; } // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(spell->mEffects); if ((types & Self) && isSpellActive(actor, actor, spell->mId)) return 0.f; if ( ((types & Touch) || (types & Target)) && isSpellActive(actor, enemy, spell->mId)) return 0.f; return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f); } float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { if (ptr.getClass().getEnchantment(ptr).empty()) return 0.f; const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(enchantment->mEffects); if ((types & Self) && actor.getClass().getCreatureStats(actor).getActiveSpells().isSpellActive(ptr.getCellRef().getRefId())) return 0.f; if (types & (Touch|Target) && getSpellDuration(enemy, ptr.getCellRef().getRefId()) > 3) return 0.f; if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { return rateEffects(enchantment->mEffects, actor, enemy); } else if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); // Creatures can not wear armor/clothing, so allow creatures to use non-equipped items, if (actor.getClass().isNpc() && !store.isEquipped(ptr)) return 0.f; int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); if (ptr.getCellRef().getEnchantmentCharge() != -1 && ptr.getCellRef().getEnchantmentCharge() < castCost) return 0.f; float rating = rateEffects(enchantment->mEffects, actor, enemy); rating *= 1.25f; // prefer rechargable magic items over spells return rating; } return 0.f; } float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { // NOTE: enemy may be empty float rating = 1; switch (effect.mEffectID) { case ESM::MagicEffect::Soultrap: case ESM::MagicEffect::AlmsiviIntervention: case ESM::MagicEffect::DivineIntervention: case ESM::MagicEffect::CalmHumanoid: case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::FrenzyHumanoid: case ESM::MagicEffect::FrenzyCreature: case ESM::MagicEffect::DemoralizeHumanoid: case ESM::MagicEffect::DemoralizeCreature: case ESM::MagicEffect::RallyHumanoid: case ESM::MagicEffect::RallyCreature: case ESM::MagicEffect::Charm: case ESM::MagicEffect::DetectAnimal: case ESM::MagicEffect::DetectEnchantment: case ESM::MagicEffect::DetectKey: case ESM::MagicEffect::Telekinesis: case ESM::MagicEffect::Mark: case ESM::MagicEffect::Recall: case ESM::MagicEffect::Jump: case ESM::MagicEffect::WaterBreathing: case ESM::MagicEffect::SwiftSwim: case ESM::MagicEffect::WaterWalking: case ESM::MagicEffect::SlowFall: case ESM::MagicEffect::Light: case ESM::MagicEffect::Lock: case ESM::MagicEffect::Open: case ESM::MagicEffect::TurnUndead: case ESM::MagicEffect::WeaknessToCommonDisease: case ESM::MagicEffect::WeaknessToBlightDisease: case ESM::MagicEffect::WeaknessToCorprusDisease: case ESM::MagicEffect::CureCommonDisease: case ESM::MagicEffect::CureBlightDisease: case ESM::MagicEffect::CureCorprusDisease: case ESM::MagicEffect::ResistBlightDisease: case ESM::MagicEffect::ResistCommonDisease: case ESM::MagicEffect::ResistCorprusDisease: case ESM::MagicEffect::Invisibility: case ESM::MagicEffect::Chameleon: case ESM::MagicEffect::NightEye: case ESM::MagicEffect::Vampirism: case ESM::MagicEffect::StuntedMagicka: case ESM::MagicEffect::ExtraSpell: case ESM::MagicEffect::RemoveCurse: case ESM::MagicEffect::CommandCreature: case ESM::MagicEffect::CommandHumanoid: return 0.f; case ESM::MagicEffect::Blind: { if (enemy.isEmpty()) return 0.f; const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't attack if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't attack if (stats.getDrawState() != MWMechanics::DrawState::Weapon) return 0.f; break; } case ESM::MagicEffect::Sound: { if (enemy.isEmpty()) return 0.f; const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't cast spells if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() > 0) return 0.f; if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't cast spells if (stats.getDrawState() != MWMechanics::DrawState::Spell) return 0.f; break; } case ESM::MagicEffect::Silence: { if (enemy.isEmpty()) return 0.f; const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't cast spells if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't cast spells if (stats.getDrawState() != MWMechanics::DrawState::Spell) return 0.f; break; } case ESM::MagicEffect::RestoreAttribute: return 0.f; // TODO: implement based on attribute damage case ESM::MagicEffect::RestoreSkill: return 0.f; // TODO: implement based on skill damage case ESM::MagicEffect::ResistFire: case ESM::MagicEffect::ResistFrost: case ESM::MagicEffect::ResistMagicka: case ESM::MagicEffect::ResistNormalWeapons: case ESM::MagicEffect::ResistParalysis: case ESM::MagicEffect::ResistPoison: case ESM::MagicEffect::ResistShock: case ESM::MagicEffect::SpellAbsorption: case ESM::MagicEffect::Reflect: return 0.f; // probably useless since we don't know in advance what the enemy will cast // don't cast these for now as they would make the NPC cast the same effect over and over again, especially when they have potions case ESM::MagicEffect::FortifyAttribute: case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: case ESM::MagicEffect::FortifySkill: case ESM::MagicEffect::FortifyMaximumMagicka: case ESM::MagicEffect::FortifyAttack: return 0.f; case ESM::MagicEffect::Burden: { if (enemy.isEmpty()) return 0.f; // Ignore enemy without inventory if (!enemy.getClass().hasInventoryStore(enemy)) return 0.f; // burden makes sense only to overburden an enemy float burden = enemy.getClass().getEncumbrance(enemy) - enemy.getClass().getCapacity(enemy); if (burden > 0) return 0.f; if ((effect.mMagnMin + effect.mMagnMax)/2.f > -burden) rating *= 3; else return 0.f; break; } case ESM::MagicEffect::Feather: { // Ignore actors without inventory if (!actor.getClass().hasInventoryStore(actor)) return 0.f; // feather makes sense only for overburden actors float burden = actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor); if (burden <= 0) return 0.f; if ((effect.mMagnMin + effect.mMagnMax)/2.f >= burden) rating *= 3; else return 0.f; break; } case ESM::MagicEffect::Levitate: return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway case ESM::MagicEffect::BoundBoots: case ESM::MagicEffect::BoundHelm: if (actor.getClass().isNpc()) { // Beast races can't wear helmets or boots std::string raceid = actor.get()->mBase->mRace; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); if (race->mData.mFlags & ESM::Race::Beast) return 0.f; } else return 0.f; break; case ESM::MagicEffect::BoundShield: if(!actor.getClass().hasInventoryStore(actor)) return 0.f; else if(!actor.getClass().isNpc()) { // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a one-handed weapon to use with the shield const auto& store = actor.getClass().getInventoryStore(actor); auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), [](const MWWorld::ConstPtr& weapon) { if(weapon.getClass().getItemHealth(weapon) <= 0.f) return false; short type = weapon.get()->mBase->mData.mType; return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); }); if(oneHanded == store.cend()) return 0.f; } break; // Creatures can not wear armor case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundGloves: if (!actor.getClass().isNpc()) return 0.f; break; case ESM::MagicEffect::BoundLongbow: // AI should not summon the bow if there is no suitable ammo. if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f) return 0.f; break; case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: if (effect.mRange == ESM::RT_Self) { int priority = 1; if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) priority = 10; const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const DynamicStat& current = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); // NB: this currently assumes the hardcoded magic effect flags are used const float magnitude = (effect.mMagnMin + effect.mMagnMax)/2.f; const float toHeal = magnitude * std::max(1, effect.mDuration); // Effect doesn't heal more than we need, *or* we are below 1/2 health if (current.getModified() - current.getCurrent() > toHeal || current.getCurrent() < current.getModified()*0.5) { return 10000.f * priority - (toHeal - (current.getModified()-current.getCurrent())); // prefer the most fitting potion } else return -10000.f * priority; // Save for later } break; case ESM::MagicEffect::Dispel: { int numPositive = 0; int numNegative = 0; int diff = 0; if (effect.mRange == ESM::RT_Self) { numPositive = numEffectsToDispel(actor, -1, false); numNegative = numEffectsToDispel(actor); diff = numNegative - numPositive; } else { if (enemy.isEmpty()) return 0.f; numPositive = numEffectsToDispel(enemy, -1, false); numNegative = numEffectsToDispel(enemy); diff = numPositive - numNegative; // if rating < 0 here, the spell will be considered as negative later rating *= -1; } if (diff <= 0) return 0.f; rating *= (diff) / 5.f; break; } // Prefer Cure effects over Dispel, because Dispel also removes positive effects case ESM::MagicEffect::CureParalyzation: return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze); case ESM::MagicEffect::CurePoison: return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison); case ESM::MagicEffect::DisintegrateArmor: { if (enemy.isEmpty()) return 0.f; // Ignore enemy without inventory if (!enemy.getClass().hasInventoryStore(enemy)) return 0.f; MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy); // According to UESP static const int armorSlots[] = { MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet, MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Boots }; bool enemyHasArmor = false; // Ignore enemy without armor for (unsigned int i=0; i= 0 && effect.mAttribute < ESM::Attribute::Length) { const float attributePriorities[ESM::Attribute::Length] = { 1.0f, // Strength 0.5f, // Intelligence 0.6f, // Willpower 0.7f, // Agility 0.5f, // Speed 0.8f, // Endurance 0.7f, // Personality 0.3f // Luck }; rating *= attributePriorities[effect.mAttribute]; } } break; case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::DrainSkill: if (enemy.isEmpty() || !enemy.getClass().isNpc()) return 0.f; if (enemy.getClass().getSkill(enemy, effect.mSkill) <= 0) return 0.f; break; default: break; } // Allow only one summoned creature at time if (isSummoningEffect(effect.mEffectID)) { MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); if (!creatureStats.getSummonedCreatureMap().empty()) return 0.f; } if(effect.mEffectID >= ESM::MagicEffect::BoundDagger && effect.mEffectID <= ESM::MagicEffect::BoundGloves) { // While rateSpell prevents actors from recasting the same spell, it doesn't prevent them from casting different spells with the same effect. // Multiple instances of the same bound item don't stack so if the effect is already active, rate it as useless. if(actor.getClass().getCreatureStats(actor).getMagicEffects().get(effect.mEffectID).getMagnitude() > 0.f) return 0.f; } // Underwater casting not possible if (effect.mRange == ESM::RT_Target) { if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.75f)) return 0.f; if (enemy.isEmpty()) return 0.f; if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; } const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { rating *= -1.f; if (enemy.isEmpty()) return 0.f; // Check resistance for harmful effects CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); float resistance = MWMechanics::getEffectResistanceAttribute(effect.mEffectID, &stats.getMagicEffects()); rating *= (1.f - std::min(resistance, 100.f) / 100.f); } // for harmful no-magnitude effects (e.g. silence) check if enemy is already has them // for non-harmful no-magnitude effects (e.g. bound items) check if actor is already has them if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) { if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); if (stats.getMagicEffects().get(effect.mEffectID).getMagnitude() > 0) return 0.f; } else { CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.getMagicEffects().get(effect.mEffectID).getMagnitude() > 0) return 0.f; } } rating *= calcEffectCost(effect, magicEffect); // Currently treating all "on target" or "on touch" effects to target the enemy actor. // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors. if (effect.mRange != ESM::RT_Self) rating *= -1.f; return rating; } float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { // NOTE: enemy may be empty float rating = 0.f; float ratingMult = 1.f; // NB: this multiplier is applied to the effect rating, not the final rating const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { ratingMult = (it->mRange == ESM::RT_Target) ? fAIRangeMagicSpellMult : fAIMagicSpellMult; rating += rateEffect(*it, actor, enemy) * ratingMult; } return rating; } float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); float mult = fAIMagicSpellMult; for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { if (!MWBase::Environment::get().getWorld()->isSwimming(enemy)) mult = fAIRangeMagicSpellMult; else mult = 0.0f; break; } } return MWMechanics::getSpellSuccessChance(spell, actor) * mult; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellpriority.hpp000066400000000000000000000022221445372753700246320ustar00rootroot00000000000000#ifndef OPENMW_SPELL_PRIORITY_H #define OPENMW_SPELL_PRIORITY_H namespace ESM { struct Spell; struct EffectList; struct ENAMstruct; } namespace MWWorld { class Ptr; } namespace MWMechanics { // RangeTypes using bitflags to allow multiple range types, as can be the case with spells having multiple effects. enum RangeTypes { Self = 0x1, Touch = 0x10, Target = 0x100 }; int getRangeTypes (const ESM::EffectList& effects); float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); /// @note target may be empty float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); /// @note target may be empty float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellresistance.cpp000066400000000000000000000070631445372753700251140ustar00rootroot00000000000000#include "spellresistance.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" #include "spellutil.hpp" namespace MWMechanics { float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { if (!actor.getClass().isActor()) return 1; float resistance = getEffectResistance(effectId, actor, caster, spell, effects); return 1 - resistance / 100.f; } float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { // Effects with no resistance attribute belonging to them can not be resisted if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) return 0.f; const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); if (effects) magicEffects = effects; float resistance = getEffectResistanceAttribute(effectId, magicEffects); float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa float castChance = 100.f; if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor()) castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance if (castChance > 0) x *= 50 / castChance; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); float roll = Misc::Rng::rollClosedProbability(prng) * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; if (x <= roll) x = 0; else { if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) x = 100; else x = roll / std::min(x, 100.f); } x = std::min(x + resistance, 100.f); return x; } float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) { short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); float resistance = 0; if (resistanceEffect != -1) resistance += actorEffects->get(resistanceEffect).getMagnitude(); if (weaknessEffect != -1) resistance -= actorEffects->get(weaknessEffect).getMagnitude(); if (effectId == ESM::MagicEffect::FireDamage) resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); if (effectId == ESM::MagicEffect::ShockDamage) resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); if (effectId == ESM::MagicEffect::FrostDamage) resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); return resistance; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellresistance.hpp000066400000000000000000000032371445372753700251200ustar00rootroot00000000000000#ifndef MWMECHANICS_SPELLRESISTANCE_H #define MWMECHANICS_SPELLRESISTANCE_H namespace ESM { struct Spell; } namespace MWWorld { class Ptr; } namespace MWMechanics { class MagicEffects; /// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional). /// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness) /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). /// @return >=100 for fully resisted. can also return negative value for damage amplification. /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the resistance attribute against an effect for a given actor. This will add together /// ResistX and Weakness to X effects relevant against the given effect. float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spells.cpp000066400000000000000000000241661445372753700232210ustar00rootroot00000000000000#include "spells.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "actorutil.hpp" #include "creaturestats.hpp" #include "magiceffects.hpp" #include "stat.hpp" namespace MWMechanics { Spells::Spells() { } Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers) { if(mSpellList) mSpellList->addListener(this); } Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)), mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)) { if (mSpellList) mSpellList->updateListener(&spells, this); } std::vector::const_iterator Spells::begin() const { return mSpells.begin(); } std::vector::const_iterator Spells::end() const { return mSpells.end(); } bool Spells::hasSpell(const std::string &spell) const { return hasSpell(SpellList::getSpell(spell)); } bool Spells::hasSpell(const ESM::Spell *spell) const { return std::find(mSpells.begin(), mSpells.end(), spell) != mSpells.end(); } void Spells::add (const ESM::Spell* spell) { mSpellList->add(spell); } void Spells::add (const std::string& spellId) { add(SpellList::getSpell(spellId)); } void Spells::addSpell(const ESM::Spell* spell) { if (!hasSpell(spell)) mSpells.emplace_back(spell); } void Spells::remove (const std::string& spellId) { const auto spell = SpellList::getSpell(spellId); removeSpell(spell); mSpellList->remove(spell); if (spellId==mSelectedSpell) mSelectedSpell.clear(); } void Spells::removeSpell(const ESM::Spell* spell) { const auto it = std::find(mSpells.begin(), mSpells.end(), spell); if(it != mSpells.end()) mSpells.erase(it); } void Spells::removeAllSpells() { mSpells.clear(); } void Spells::clear(bool modifyBase) { removeAllSpells(); if(modifyBase) mSpellList->clear(); } void Spells::setSelectedSpell (const std::string& spellId) { mSelectedSpell = spellId; } const std::string Spells::getSelectedSpell() const { return mSelectedSpell; } bool Spells::hasSpellType(const ESM::Spell::SpellType type) const { auto it = std::find_if(std::begin(mSpells), std::end(mSpells), [=](const ESM::Spell* spell) { return spell->mData.mType == type; }); return it != std::end(mSpells); } bool Spells::hasCommonDisease() const { return hasSpellType(ESM::Spell::ST_Disease); } bool Spells::hasBlightDisease() const { return hasSpellType(ESM::Spell::ST_Blight); } void Spells::purge(const SpellFilter& filter) { std::vector purged; for (auto iter = mSpells.begin(); iter!=mSpells.end();) { const ESM::Spell *spell = *iter; if (filter(spell)) { iter = mSpells.erase(iter); purged.push_back(spell->mId); } else ++iter; } if(!purged.empty()) mSpellList->removeAll(purged); } void Spells::purgeCommonDisease() { purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; }); } void Spells::purgeBlightDisease() { purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); }); } void Spells::purgeCorprusDisease() { purge(&hasCorprusEffect); } void Spells::purgeCurses() { purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (const auto& effectIt : spell->mEffects.mList) { if (effectIt.mEffectID == ESM::MagicEffect::Corprus) { return true; } } return false; } bool Spells::canUsePower(const ESM::Spell* spell) const { const auto it = std::find_if(std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) { // Updates or inserts a new entry with the current timestamp. const auto it = std::find_if(std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); const auto timestamp = MWBase::Environment::get().getWorld()->getTimeStamp(); if (it == mUsedPowers.end()) mUsedPowers.emplace_back(spell, timestamp); else it->second = timestamp; } void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { const auto& baseSpells = mSpellList->getSpells(); for (const std::string& id : state.mSpells) { // Discard spells that are no longer available due to changed content files const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell) { addSpell(spell); if (id == state.mSelectedSpell) mSelectedSpell = id; } } // Add spells from the base record for(const std::string& id : baseSpells) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if(spell) addSpell(spell); } for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (!spell) continue; mUsedPowers.emplace_back(spell, MWWorld::TimeStamp(it->second)); } // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) { const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (!spell) continue; // Import data only for player, other actors should not suffer from corprus worsening. MWWorld::Ptr player = getPlayer(); if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId()) return; // Note: if target actor has the Restore attirbute effects, stats will be restored. for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) { // Applied corprus effects are already in loaded stats modifiers if (effectIt->mId == ESM::MagicEffect::FortifyAttribute) { AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); attr.setModifier(attr.getModifier() - effectIt->mMagnitude); attr.damage(-effectIt->mMagnitude); creatureStats->setAttribute(effectIt->mArg, attr); } else if (effectIt->mId == ESM::MagicEffect::DrainAttribute) { AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); attr.setModifier(attr.getModifier() + effectIt->mMagnitude); attr.damage(effectIt->mMagnitude); creatureStats->setAttribute(effectIt->mArg, attr); } } } } void Spells::writeState(ESM::SpellState &state) const { const auto& baseSpells = mSpellList->getSpells(); for (const auto spell : mSpells) { // Don't save spells and powers stored in the base record if((spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) || std::find(baseSpells.begin(), baseSpells.end(), spell->mId) == baseSpells.end()) { state.mSpells.emplace_back(spell->mId); } } state.mSelectedSpell = mSelectedSpell; for (const auto& it : mUsedPowers) state.mUsedPowers[it.first->mId] = it.second.toEsm(); } bool Spells::setSpells(const std::string& actorId) { bool result; std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId); mSpellList->addListener(this); addAllToInstance(mSpellList->getSpells()); return result; } void Spells::addAllToInstance(const std::vector& spells) { for(const std::string& id : spells) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if(spell) addSpell(spell); else Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'"; } } Spells::~Spells() { if(mSpellList) mSpellList->removeListener(this); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spells.hpp000066400000000000000000000062371445372753700232250ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H #include #include #include #include #include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" #include "spelllist.hpp" namespace ESM { struct SpellState; } namespace MWMechanics { class CreatureStats; class MagicEffects; /// \brief Spell list /// /// This class manages known spells as well as abilities, powers and permanent negative effects like /// diseases. It also keeps track of used powers (which can only be used every 24h). class Spells { std::shared_ptr mSpellList; std::vector mSpells; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::vector> mUsedPowers; bool hasSpellType(const ESM::Spell::SpellType type) const; using SpellFilter = bool (*)(const ESM::Spell*); void purge(const SpellFilter& filter); void addSpell(const ESM::Spell* spell); void removeSpell(const ESM::Spell* spell); void removeAllSpells(); friend class SpellList; public: Spells(); Spells(const Spells&); Spells(Spells&& spells); ~Spells(); static bool hasCorprusEffect(const ESM::Spell *spell); bool canUsePower (const ESM::Spell* spell) const; void usePower (const ESM::Spell* spell); void purgeCommonDisease(); void purgeBlightDisease(); void purgeCorprusDisease(); void purgeCurses(); std::vector::const_iterator begin() const; std::vector::const_iterator end() const; bool hasSpell(const std::string& spell) const; bool hasSpell(const ESM::Spell* spell) const; void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. void add (const ESM::Spell* spell); ///< Adding a spell that is already listed in *this is a no-op. void remove (const std::string& spell); ///< If the spell to be removed is the selected spell, the selected spell will be changed to /// no spell (empty string). void clear(bool modifyBase = false); ///< Remove all spells of al types. void setSelectedSpell (const std::string& spellId); ///< This function does not verify, if the spell is available. const std::string getSelectedSpell() const; ///< May return an empty string. bool hasCommonDisease() const; bool hasBlightDisease() const; void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; bool setSpells(const std::string& id); void addAllToInstance(const std::vector& spells); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellutil.cpp000066400000000000000000000170161445372753700237300ustar00rootroot00000000000000#include "spellutil.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "actorutil.hpp" #include "creaturestats.hpp" namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school) { static const std::array schoolSkillArray { ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction, ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration }; return schoolSkillArray.at(school); } float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const EffectCostMethod method) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (!magicEffect) magicEffect = store.get().find(effect.mEffectID); bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; int duration = hasDuration ? effect.mDuration : 1; if (!appliedOnce) duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); int durationOffset = 0; int minArea = 0; if (method == EffectCostMethod::PlayerSpell) { durationOffset = 1; minArea = 1; } float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= durationOffset + duration; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; return x * fEffectCostMult; } int calcSpellCost (const ESM::Spell& spell) { if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) return spell.mData.mCost; float cost = 0; for (const ESM::ENAMstruct& effect : spell.mEffects.mList) { float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect)); // This is applied to the whole spell cost for each effect when // creating spells, but is only applied on the effect itself in TES:CS. if (effect.mRange == ESM::RT_Target) effectCost *= 1.5; cost += effectCost; } return std::round(cost); } int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) { /* * Each point of enchant skill above/under 10 subtracts/adds * one percent of enchantment cost while minimum is 1. */ int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); const float result = castCost - (castCost / 100) * (eSkill - 10); return static_cast((result < 1) ? 1 : result); } float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); float lowestSkill = 0; for (const ESM::ENAMstruct& effect : spell->mEffects.mList) { float x = static_cast(effect.mDuration); const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; if (effect.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->mValue.getFloat(); x *= fEffectCostMult; float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); if (s - x < y) { y = s - x; if (effectiveSchool) *effectiveSchool = magicEffect->mData.mSchool; lowestSkill = s; } } CreatureStats& stats = actor.getClass().getCreatureStats(actor); float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - calcSpellCost(*spell) + 0.2f * actorWillpower + 0.1f * actorLuck); return castChance; } float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { // NB: Base chance is calculated here because the effective school pointer must be filled float baseChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool); bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode) return 0; if (spell->mData.mType == ESM::Spell::ST_Power) return stats.getSpells().canUsePower(spell) ? 100 : 0; if (godmode) return 100; if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; if (checkMagicka && calcSpellCost(*spell) > 0 && stats.getMagicka().getCurrent() < calcSpellCost(*spell)) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); float castChance = baseChance + castBonus; castChance *= stats.getFatigueTerm(); if (cap) return std::clamp(castChance, 0.f, 100.f); return std::max(castChance, 0.f); } float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId)) return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); return 0.f; } int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) { int school = 0; getSpellSuccessChance(spellId, actor, &school); return school; } int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) { int school = 0; getSpellSuccessChance(spell, actor, &school); return school; } bool spellIncreasesSkill(const ESM::Spell *spell) { return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always); } bool spellIncreasesSkill(const std::string &spellId) { const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); return spell && spellIncreasesSkill(spell); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/spellutil.hpp000066400000000000000000000036651445372753700237420ustar00rootroot00000000000000#ifndef MWMECHANICS_SPELLUTIL_H #define MWMECHANICS_SPELLUTIL_H #include namespace ESM { struct ENAMstruct; struct MagicEffect; struct Spell; } namespace MWWorld { class Ptr; } namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school); enum class EffectCostMethod { GameSpell, PlayerSpell, }; float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, const EffectCostMethod method = EffectCostMethod::GameSpell); int calcSpellCost (const ESM::Spell& spell); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here * @param cap cap the result to 100%? * @param checkMagicka check magicka? * @note actor can be an NPC or a creature * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool); float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Get whether or not the given spell contributes to skill progress. bool spellIncreasesSkill(const ESM::Spell* spell); bool spellIncreasesSkill(const std::string& spellId); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/stat.cpp000066400000000000000000000112501445372753700226600ustar00rootroot00000000000000#include "stat.hpp" #include namespace MWMechanics { template Stat::Stat() : mBase (0), mModifier (0) {} template Stat::Stat(T base, T modified) : mBase (base), mModifier (modified) {} template T Stat::getModified(bool capped) const { if(capped) return std::max({}, mModifier + mBase); return mModifier + mBase; } template void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; state.mMod = mModifier; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; mModifier = state.mMod; } template DynamicStat::DynamicStat() : mStatic(0, 0), mCurrent(0) {} template DynamicStat::DynamicStat(T base) : mStatic(base, 0), mCurrent(base) {} template DynamicStat::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} template DynamicStat::DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} template void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) { if (value > mCurrent) { // increase if (value <= getModified() || allowIncreaseAboveModified) mCurrent = value; else if (mCurrent > getModified()) return; else mCurrent = getModified(); } else if (value > 0 || allowDecreaseBelowZero) { // allowed decrease mCurrent = value; } else if (mCurrent > 0) { // capped decrease mCurrent = 0; } } template T DynamicStat::getRatio(bool nanIsZero) const { T modified = getModified(); if(modified == T{}) { if(nanIsZero) return modified; return {1}; } return getCurrent() / modified; } template void DynamicStat::writeState (ESM::StatState& state) const { mStatic.writeState (state); state.mCurrent = mCurrent; } template void DynamicStat::readState (const ESM::StatState& state) { mStatic.readState (state); mCurrent = state.mCurrent; } AttributeValue::AttributeValue() : mBase(0.f), mModifier(0.f), mDamage(0.f) { } float AttributeValue::getModified() const { return std::max(0.f, mBase - mDamage + mModifier); } float AttributeValue::getBase() const { return mBase; } float AttributeValue::getModifier() const { return mModifier; } void AttributeValue::setBase(float base, bool clearModifier) { mBase = base; if(clearModifier) { mModifier = 0.f; mDamage = 0.f; } } void AttributeValue::setModifier(float mod) { if(mod < 0) { mModifier = 0.f; mDamage -= mod; } else mModifier = mod; } void AttributeValue::damage(float damage) { mDamage += damage; } void AttributeValue::restore(float amount) { if (mDamage <= 0) return; mDamage -= std::min(mDamage, amount); } float AttributeValue::getDamage() const { return mDamage; } void AttributeValue::writeState (ESM::StatState& state) const { state.mBase = mBase; state.mMod = mModifier; state.mDamage = mDamage; } void AttributeValue::readState (const ESM::StatState& state) { mBase = state.mBase; mModifier = state.mMod; mDamage = state.mDamage; } SkillValue::SkillValue() : mProgress(0) { } float SkillValue::getProgress() const { return mProgress; } void SkillValue::setProgress(float progress) { mProgress = progress; } void SkillValue::writeState (ESM::StatState& state) const { AttributeValue::writeState (state); state.mProgress = mProgress; } void SkillValue::readState (const ESM::StatState& state) { AttributeValue::readState (state); mProgress = state.mProgress; } } template class MWMechanics::Stat; template class MWMechanics::Stat; template class MWMechanics::DynamicStat; template class MWMechanics::DynamicStat; openmw-openmw-0.48.0/apps/openmw/mwmechanics/stat.hpp000066400000000000000000000114341445372753700226710ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_STAT_H #define GAME_MWMECHANICS_STAT_H #include #include namespace ESM { template struct StatState; } namespace MWMechanics { template class Stat { T mBase; T mModifier; public: typedef T Type; Stat(); Stat(T base, T modified); const T& getBase() const { return mBase; }; T getModified(bool capped = true) const; T getModifier() const { return mModifier; }; void setBase(const T& value) { mBase = value; }; void setModifier(const T& modifier) { mModifier = modifier; }; void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); }; template inline bool operator== (const Stat& left, const Stat& right) { return left.getBase()==right.getBase() && left.getModifier()==right.getModifier(); } template inline bool operator!= (const Stat& left, const Stat& right) { return !(left==right); } template class DynamicStat { Stat mStatic; T mCurrent; public: typedef T Type; DynamicStat(); DynamicStat(T base); DynamicStat(T base, T modified, T current); DynamicStat(const Stat &stat, T current); const T& getBase() const { return mStatic.getBase(); }; T getModified(bool capped = true) const { return mStatic.getModified(capped); }; const T& getCurrent() const { return mCurrent; }; T getRatio(bool nanIsZero = true) const; /// Set base and adjust current accordingly. void setBase(const T& value) { mStatic.setBase(value); }; void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); T getModifier() const { return mStatic.getModifier(); } void setModifier(T value) { mStatic.setModifier(value); } void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); }; template inline bool operator== (const DynamicStat& left, const DynamicStat& right) { return left.getBase()==right.getBase() && left.getModifier()==right.getModifier() && left.getCurrent()==right.getCurrent(); } template inline bool operator!= (const DynamicStat& left, const DynamicStat& right) { return !(left==right); } class AttributeValue { float mBase; float mModifier; float mDamage; // needs to be float to allow continuous damage public: AttributeValue(); float getModified() const; float getBase() const; float getModifier() const; void setBase(float base, bool clearModifier = false); void setModifier(float mod); // Maximum attribute damage is limited to the modified value. // Note: MW applies damage directly to mModified, however it does track how much // a damaged attribute that has been fortified beyond its base can be restored. // Getting rid of mDamage would require calculating its value by ignoring active effects when restoring void damage(float damage); void restore(float amount); float getDamage() const; void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); }; class SkillValue : public AttributeValue { float mProgress; public: SkillValue(); float getProgress() const; void setProgress(float progress); void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); }; inline bool operator== (const AttributeValue& left, const AttributeValue& right) { return left.getBase() == right.getBase() && left.getModifier() == right.getModifier() && left.getDamage() == right.getDamage(); } inline bool operator!= (const AttributeValue& left, const AttributeValue& right) { return !(left == right); } inline bool operator== (const SkillValue& left, const SkillValue& right) { return left.getBase() == right.getBase() && left.getModifier() == right.getModifier() && left.getDamage() == right.getDamage() && left.getProgress() == right.getProgress(); } inline bool operator!= (const SkillValue& left, const SkillValue& right) { return !(left == right); } } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/steering.cpp000066400000000000000000000025221445372753700235270ustar00rootroot00000000000000#include "steering.hpp" #include #include #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwbase/environment.hpp" #include "movement.hpp" namespace MWMechanics { bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians) { MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]); float absDiff = std::abs(diff); // The turning animation actually moves you slightly, so the angle will be wrong again. // Use epsilon to prevent jerkiness. if (absDiff < epsilonRadians) return true; float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) limit *= std::min(absDiff / osg::PI + 0.1, 0.5); if (absDiff > limit) diff = osg::sign(diff) * limit; movement.mRotation[axis] = diff; return false; } bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians) { return smoothTurn(actor, targetAngleRadians, 2, epsilonRadians); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/steering.hpp000066400000000000000000000017761445372753700235460ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_STEERING_H #define OPENMW_MECHANICS_STEERING_H #include #include namespace MWWorld { class Ptr; } namespace MWMechanics { // Max rotating speed, radian/sec inline float getAngularVelocity(const float actorSpeed) { constexpr float degreesPerFrame = 15.f; constexpr int framesPerSecond = 60; const float baseAngularVelocity = osg::DegreesToRadians(degreesPerFrame * framesPerSecond); const float baseSpeed = 200; return baseAngularVelocity * std::max(actorSpeed / baseSpeed, 1.0f); } /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians = osg::DegreesToRadians(0.5)); bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians = osg::DegreesToRadians(0.5)); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/summoning.cpp000066400000000000000000000147741445372753700237370ustar00rootroot00000000000000#include "summoning.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" #include "creaturestats.hpp" #include "aifollow.hpp" namespace MWMechanics { bool isSummoningEffect(int effectId) { return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach) || (effectId == ESM::MagicEffect::SummonCenturionSphere) || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); } std::string getSummonedCreature(int effectId) { static const std::map summonMap { {ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"}, {ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"}, {ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"}, {ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"}, {ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"}, {ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"}, {ESM::MagicEffect::SummonDremora, "sMagicDremoraID"}, {ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"}, {ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"}, {ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"}, {ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"}, {ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"}, {ESM::MagicEffect::SummonHunger, "sMagicHungerID"}, {ESM::MagicEffect::SummonScamp, "sMagicScampID"}, {ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"}, {ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"}, {ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"}, {ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"}, {ESM::MagicEffect::SummonBear, "sMagicCreature02ID"}, {ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"}, {ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"}, {ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"} }; auto it = summonMap.find(effectId); if (it != summonMap.end()) return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); return std::string(); } int summonCreature(int effectId, const MWWorld::Ptr& summoner) { std::string creatureID = getSummonedCreature(effectId); int creatureActorId = -1; if (!creatureID.empty()) { try { auto world = MWBase::Environment::get().getWorld(); MWWorld::ManualRef ref(world->getStore(), creatureID, 1); MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // Make the summoned creature follow its master and help in fights AiFollow package(summoner); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); MWRender::Animation* anim = world->getAnimation(placed); if (anim) { const ESM::Static* fx = world->getStore().get().search("VFX_Summon_Start"); if (fx) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1, false); } } } catch (std::exception& e) { Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId); } return creatureActorId; } void updateSummons(const MWWorld::Ptr& summoner, bool cleanup) { MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner); auto& creatureMap = creatureStats.getSummonedCreatureMap(); std::vector graveyard = creatureStats.getSummonedCreatureGraveyard(); creatureStats.getSummonedCreatureGraveyard().clear(); for (const int creature : graveyard) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature); if (!cleanup) return; for (auto it = creatureMap.begin(); it != creatureMap.end(); ) { if(it->second == -1) { // Keep the spell effect active if we failed to spawn anything it++; continue; } MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired auto summon = *it; creatureMap.erase(it++); purgeSummonEffect(summoner, summon); } else ++it; } } void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); creatureStats.getActiveSpells().purge([summon] (const auto& spell, const auto& effect) { return effect.mEffectId == summon.first && effect.mArg == summon.second; }, summoner); MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/summoning.hpp000066400000000000000000000010541445372753700237270ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_SUMMONING_H #define OPENMW_MECHANICS_SUMMONING_H #include #include "../mwworld/ptr.hpp" #include #include "magiceffects.hpp" namespace MWMechanics { bool isSummoningEffect(int effectId); std::string getSummonedCreature(int effectId); void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); int summonCreature(int effectId, const MWWorld::Ptr& summoner); void updateSummons(const MWWorld::Ptr& summoner, bool cleanup); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/trading.cpp000066400000000000000000000065761445372753700233540ustar00rootroot00000000000000#include "trading.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" namespace MWMechanics { Trading::Trading() {} bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) { // accept if merchant offer is better than player offer if ( playerOffer <= merchantOffer ) { return true; } // reject if npc is a creature if ( merchant.getType() != ESM::NPC::sRecordId ) { return false; } const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); // Is the player buying? bool buying = (merchantOffer < 0); int a = std::abs(merchantOffer); int b = std::abs(playerOffer); int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); const MWMechanics::CreatureStats &merchantStats = merchant.getClass().getCreatureStats(merchant); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int roll = Misc::Rng::rollDice(100, prng) + 1; // reject if roll fails // (or if player tries to buy things and get money) if ( roll > x || (merchantOffer < 0 && 0 < playerOffer) ) { return false; } // apply skill gain on successful barter float skillGain = 0.f; int finalPrice = std::abs(playerOffer); int initialMerchantOffer = std::abs(merchantOffer); if ( !buying && (finalPrice > initialMerchantOffer) ) { skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); } else if ( buying && (finalPrice < initialMerchantOffer) ) { skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); return true; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/trading.hpp000066400000000000000000000005011445372753700233370ustar00rootroot00000000000000#ifndef OPENMW_MECHANICS_TRADING_H #define OPENMW_MECHANICS_TRADING_H namespace MWWorld { class Ptr; } namespace MWMechanics { class Trading { public: Trading(); bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/typedaipackage.hpp000066400000000000000000000015071445372753700246710ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_TYPEDAIPACKAGE_H #define GAME_MWMECHANICS_TYPEDAIPACKAGE_H #include "aipackage.hpp" namespace MWMechanics { template struct TypedAiPackage : public AiPackage { TypedAiPackage() : AiPackage(T::getTypeId(), T::makeDefaultOptions()) {} TypedAiPackage(bool repeat) : AiPackage(T::getTypeId(), T::makeDefaultOptions().withRepeat(repeat)) {} TypedAiPackage(const Options& options) : AiPackage(T::getTypeId(), options) {} template TypedAiPackage(Derived*) : AiPackage(Derived::getTypeId(), Derived::makeDefaultOptions()) {} std::unique_ptr clone() const override { return std::make_unique(*static_cast(this)); } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/weaponpriority.cpp000066400000000000000000000171561445372753700250130ustar00rootroot00000000000000#include "weaponpriority.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "combat.hpp" #include "aicombataction.hpp" #include "spellpriority.hpp" #include "spellutil.hpp" #include "weapontype.hpp" namespace MWMechanics { float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { if (enemy.isEmpty() || item.getType() != ESM::Weapon::sRecordId) return 0.f; if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) == 0) return 0.f; const ESM::Weapon* weapon = item.get()->mBase; if (type != -1 && weapon->mData.mType != type) return 0.f; const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass; if (type == -1 && weapclass == ESM::WeaponType::Ammo) return 0.f; float rating=0.f; static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); float ratingMult = fAIMeleeWeaponMult; if (weapclass != ESM::WeaponType::Melee) { // Underwater ranged combat is impossible if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) || world->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; // Use a higher rating multiplier if the actor is out of enemy's reach, use the normal mult otherwise if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy)) { static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); ratingMult = fAIRangeMeleeWeaponMult; } } const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; // We need to account for the fact that thrown weapons have 2x real damage applied to the target // as they're both the weapon and the ammo of the hit if (weapclass == ESM::WeaponType::Thrown) { rating = chop * 2; } else if (weapclass != ESM::WeaponType::Melee) { rating = chop; } else { const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f; const float thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1]) / 2.f; rating = (slash * slash + thrust * thrust + chop * chop) / (slash + thrust + chop); } adjustWeaponDamage(rating, item, actor); if (weapclass != ESM::WeaponType::Ranged) { resistNormalWeapon(enemy, actor, item, rating); applyWerewolfDamageMult(enemy, item, rating); } else { int ammotype = MWMechanics::getWeaponType(weapon->mData.mType)->mAmmoType; if (ammotype == ESM::Weapon::Arrow) { if (arrowRating <= 0.f) rating = 0.f; else rating += arrowRating; } else if (ammotype == ESM::Weapon::Bolt) { if (boltRating <= 0.f) rating = 0.f; else rating += boltRating; } } if (!weapon->mEnchant.empty()) { const ESM::Enchantment* enchantment = world->getStore().get().find(weapon->mEnchant); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); float charge = item.getCellRef().getEnchantmentCharge(); if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) rating += rateEffects(enchantment->mEffects, actor, enemy); } } int value = 50.f; if (actor.getClass().isNpc()) { int skill = item.getClass().getEquipmentSkill(item); if (skill != -1) value = actor.getClass().getSkill(actor, skill); } else { MWWorld::LiveCellRef *ref = actor.get(); value = ref->mBase->mData.mCombat; } // Take hit chance in account, but do not allow rating become negative. rating *= std::clamp(getHitChance(actor, enemy, value) / 100.f, 0.01f, 1.f); if (weapclass != ESM::WeaponType::Ammo) rating *= weapon->mData.mSpeed; return rating * ratingMult; } float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType) { float bestAmmoRating = 0.f; if (!actor.getClass().hasInventoryStore(actor)) return bestAmmoRating; MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { float rating = rateWeapon(*it, actor, enemy, ammoType); if (rating > bestAmmoRating) { bestAmmoRating = rating; bestAmmo = *it; } } return bestAmmoRating; } float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType) { MWWorld::Ptr emptyPtr; return rateAmmo(actor, enemy, emptyPtr, ammoType); } float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->mValue.getFloat(); static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); if (weapon.isEmpty()) return 0.f; float skillMult = actor.getClass().getSkill(actor, weapon.getClass().getEquipmentSkill(weapon)) * 0.01f; float chopMult = fAIMeleeWeaponMult; float bonusDamage = 0.f; const ESM::Weapon* esmWeap = weapon.get()->mBase; int type = esmWeap->mData.mType; if (getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee) { if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy)) { bonusDamage = ammo.get()->mBase->mData.mChop[1]; chopMult = fAIRangeMeleeWeaponMult; } else chopMult = 0.f; } float chopRating = (esmWeap->mData.mChop[1] + bonusDamage) * skillMult * chopMult; float slashRating = esmWeap->mData.mSlash[1] * skillMult * fAIMeleeWeaponMult; float thrustRating = esmWeap->mData.mThrust[1] * skillMult * fAIMeleeWeaponMult; return actor.getClass().getArmorRating(actor) * fAIMeleeArmorMult + std::max(std::max(chopRating, slashRating), thrustRating); } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/weaponpriority.hpp000066400000000000000000000012251445372753700250060ustar00rootroot00000000000000#ifndef OPENMW_WEAPON_PRIORITY_H #define OPENMW_WEAPON_PRIORITY_H #include "../mwworld/ptr.hpp" namespace MWMechanics { float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType); float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType); float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } #endif openmw-openmw-0.48.0/apps/openmw/mwmechanics/weapontype.cpp000066400000000000000000000034021445372753700241000ustar00rootroot00000000000000#include "weapontype.hpp" #include "drawstate.hpp" #include "creaturestats.hpp" #include "../mwworld/class.hpp" namespace MWMechanics { MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int *weaptype) { MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); CreatureStats &stats = actor.getClass().getCreatureStats(actor); if(stats.getDrawState() == MWMechanics::DrawState::Spell) { *weaptype = ESM::Weapon::Spell; return inv.end(); } if(stats.getDrawState() == MWMechanics::DrawState::Weapon) { MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end()) *weaptype = ESM::Weapon::HandToHand; else { auto type = weapon->getType(); if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); *weaptype = ref->mBase->mData.mType; } else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) *weaptype = ESM::Weapon::PickProbe; } return weapon; } return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); } const ESM::WeaponType* getWeaponType(const int weaponType) { std::map::const_iterator found = sWeaponTypeList.find(weaponType); if (found == sWeaponTypeList.end()) { // Use one-handed short blades as fallback return &sWeaponTypeList[0]; } return &found->second; } } openmw-openmw-0.48.0/apps/openmw/mwmechanics/weapontype.hpp000066400000000000000000000242311445372753700241100ustar00rootroot00000000000000#ifndef GAME_MWMECHANICS_WEAPONTYPE_H #define GAME_MWMECHANICS_WEAPONTYPE_H #include "../mwworld/inventorystore.hpp" namespace MWMechanics { static std::map sWeaponTypeList = { { ESM::Weapon::None, { /* short group */ "", /* long group */ "", /* sound ID */ "", /* attach bone */ "", /* sheath bone */ "", /* usage skill */ ESM::Skill::HandToHand, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ 0 } }, { ESM::Weapon::PickProbe, { /* short group */ "1h", /* long group */ "pickprobe", /* sound ID */ "", /* attach bone */ "", /* sheath bone */ "", /* usage skill */ ESM::Skill::Security, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ 0 } }, { ESM::Weapon::Spell, { /* short group */ "spell", /* long group */ "spellcast", /* sound ID */ "", /* attach bone */ "", /* sheath bone */ "", /* usage skill */ ESM::Skill::HandToHand, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::TwoHanded } }, { ESM::Weapon::HandToHand, { /* short group */ "hh", /* long group */ "handtohand", /* sound ID */ "", /* attach bone */ "", /* sheath bone */ "", /* usage skill */ ESM::Skill::HandToHand, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::TwoHanded } }, { ESM::Weapon::ShortBladeOneHand, { /* short group */ "1s", /* long group */ "shortbladeonehand", /* sound ID */ "Item Weapon Shortblade", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 ShortBladeOneHand", /* usage skill */ ESM::Skill::ShortBlade, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth } }, { ESM::Weapon::LongBladeOneHand, { /* short group */ "1h", /* long group */ "weapononehand", /* sound ID */ "Item Weapon Longblade", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 LongBladeOneHand", /* usage skill */ ESM::Skill::LongBlade, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth } }, { ESM::Weapon::BluntOneHand, { /* short group */ "1b", /* long group */ "bluntonehand", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 BluntOneHand", /* usage skill */ ESM::Skill::BluntWeapon, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth } }, { ESM::Weapon::AxeOneHand, { /* short group */ "1b", /* long group */ "bluntonehand", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 LongBladeOneHand", /* usage skill */ ESM::Skill::Axe, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth } }, { ESM::Weapon::LongBladeTwoHand, { /* short group */ "2c", /* long group */ "weapontwohand", /* sound ID */ "Item Weapon Longblade", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 LongBladeTwoClose", /* usage skill */ ESM::Skill::LongBlade, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::AxeTwoHand, { /* short group */ "2b", /* long group */ "blunttwohand", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 AxeTwoClose", /* usage skill */ ESM::Skill::Axe, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::BluntTwoClose, { /* short group */ "2b", /* long group */ "blunttwohand", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 BluntTwoClose", /* usage skill */ ESM::Skill::BluntWeapon, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::BluntTwoWide, { /* short group */ "2w", /* long group */ "weapontwowide", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 BluntTwoWide", /* usage skill */ ESM::Skill::BluntWeapon, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::SpearTwoWide, { /* short group */ "2w", /* long group */ "weapontwowide", /* sound ID */ "Item Weapon Spear", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 SpearTwoWide", /* usage skill */ ESM::Skill::Spear, /* weapon class*/ ESM::WeaponType::Melee, /* ammo type */ ESM::Weapon::None, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::MarksmanBow, { /* short group */ "bow", /* long group */ "bowandarrow", /* sound ID */ "Item Weapon Bow", /* attach bone */ "Weapon Bone Left", /* sheath bone */ "Bip01 MarksmanBow", /* usage skill */ ESM::Skill::Marksman, /* weapon class*/ ESM::WeaponType::Ranged, /* ammo type */ ESM::Weapon::Arrow, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::MarksmanCrossbow, { /* short group */ "crossbow", /* long group */ "crossbow", /* sound ID */ "Item Weapon Crossbow", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 MarksmanCrossbow", /* usage skill */ ESM::Skill::Marksman, /* weapon class*/ ESM::WeaponType::Ranged, /* ammo type */ ESM::Weapon::Bolt, /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded } }, { ESM::Weapon::MarksmanThrown, { /* short group */ "1t", /* long group */ "throwweapon", /* sound ID */ "Item Weapon Blunt", /* attach bone */ "Weapon Bone", /* sheath bone */ "Bip01 MarksmanThrown", /* usage skill */ ESM::Skill::Marksman, /* weapon class*/ ESM::WeaponType::Thrown, /* ammo type */ ESM::Weapon::None, /* flags */ 0 } }, { ESM::Weapon::Arrow, { /* short group */ "", /* long group */ "", /* sound ID */ "Item Ammo", /* attach bone */ "Bip01 Arrow", /* sheath bone */ "", /* usage skill */ ESM::Skill::Marksman, /* weapon class*/ ESM::WeaponType::Ammo, /* ammo type */ ESM::Weapon::None, /* flags */ 0 } }, { ESM::Weapon::Bolt, { /* short group */ "", /* long group */ "", /* sound ID */ "Item Ammo", /* attach bone */ "ArrowBone", /* sheath bone */ "", /* usage skill */ ESM::Skill::Marksman, /* weapon class*/ ESM::WeaponType::Ammo, /* ammo type */ ESM::Weapon::None, /* flags */ 0 } } }; MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int *weaptype); const ESM::WeaponType* getWeaponType(const int weaponType); } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/000077500000000000000000000000001445372753700207325ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwphysics/actor.cpp000066400000000000000000000221011445372753700225420ustar00rootroot00000000000000#include "actor.hpp" #include #include #include #include #include #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" #include "trace.h" #include namespace MWPhysics { Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mMeshTranslation(shape->mCollisionBox.mCenter), mOriginalHalfExtents(shape->mCollisionBox.mExtents) , mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mActive(false) , mTaskScheduler(scheduler) { mPtr = ptr; // We can not create actor without collisions - he will fall through the ground. // In this case we should autogenerate collision box based on mesh shape // (NPCs have bodyparts and use a different approach) if (!ptr.getClass().isNpc() && mOriginalHalfExtents.length2() == 0.f) { if (shape->mCollisionShape) { btTransform transform; transform.setIdentity(); btVector3 min; btVector3 max; shape->mCollisionShape->getAabb(transform, min, max); mOriginalHalfExtents.x() = (max[0] - min[0])/2.f; mOriginalHalfExtents.y() = (max[1] - min[1])/2.f; mOriginalHalfExtents.z() = (max[2] - min[2])/2.f; mMeshTranslation = osg::Vec3f(0.f, 0.f, mOriginalHalfExtents.z()); } if (mOriginalHalfExtents.length2() == 0.f) Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } const btVector3 halfExtents = Misc::Convert::toBullet(mOriginalHalfExtents); if ((mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2) { switch (collisionShapeType) { case DetourNavigator::CollisionShapeType::Aabb: mShape = std::make_unique(halfExtents); mRotationallyInvariant = true; break; case DetourNavigator::CollisionShapeType::RotatingBox: mShape = std::make_unique(halfExtents); mRotationallyInvariant = false; break; case DetourNavigator::CollisionShapeType::Cylinder: mShape = std::make_unique(halfExtents); mRotationallyInvariant = true; break; } mCollisionShapeType = collisionShapeType; } else { mShape = std::make_unique(halfExtents); mRotationallyInvariant = false; mCollisionShapeType = DetourNavigator::CollisionShapeType::RotatingBox; } mConvexShape = static_cast(mShape.get()); mConvexShape->setMargin(0.001); // make sure bullet isn't using the huge default convex shape margin of 0.04 mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setUserPointer(this); updateScale(); if(!mRotationallyInvariant) setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); updatePosition(); addCollisionMask(getCollisionMask()); updateCollisionObjectPosition(); } Actor::~Actor() { mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) { mInternalCollisionMode = collision; } void Actor::enableCollisionBody(bool collision) { if (mExternalCollisionMode != collision) { mExternalCollisionMode = collision; updateCollisionMask(); } } void Actor::addCollisionMask(int collisionMask) { mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); } void Actor::updateCollisionMask() { mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask()); } int Actor::getCollisionMask() const { int collisionMask = CollisionType_World | CollisionType_HeightMap; if (mExternalCollisionMode) collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door; if (mCanWaterWalk) collisionMask |= CollisionType_Water; return collisionMask; } void Actor::updatePosition() { std::scoped_lock lock(mPositionMutex); const auto worldPosition = mPtr.getRefData().getPosition().asVec3(); mPreviousPosition = worldPosition; mPosition = worldPosition; mSimulationPosition = worldPosition; mPositionOffset = osg::Vec3f(); mStandingOnPtr = nullptr; mSkipSimulation = true; } void Actor::setSimulationPosition(const osg::Vec3f& position) { if (!std::exchange(mSkipSimulation, false)) mSimulationPosition = position; } osg::Vec3f Actor::getScaledMeshTranslation() const { return mRotation * osg::componentMultiply(mMeshTranslation, mScale); } void Actor::updateCollisionObjectPosition() { std::scoped_lock lock(mPositionMutex); mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); osg::Vec3f newPosition = getScaledMeshTranslation() + mPosition; auto& trans = mCollisionObject->getWorldTransform(); trans.setOrigin(Misc::Convert::toBullet(newPosition)); trans.setRotation(Misc::Convert::toBullet(mRotation)); mCollisionObject->setWorldTransform(trans); } osg::Vec3f Actor::getCollisionObjectPosition() const { std::scoped_lock lock(mPositionMutex); return getScaledMeshTranslation() + mPosition; } bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); const bool worldPositionChanged = mPositionOffset.length2() != 0; applyOffsetChange(); if (worldPositionChanged || mSkipSimulation) return true; mPreviousPosition = mPosition; mPosition = position; return mPreviousPosition != mPosition; } void Actor::adjustPosition(const osg::Vec3f& offset) { std::scoped_lock lock(mPositionMutex); mPositionOffset += offset; } osg::Vec3f Actor::applyOffsetChange() { if (mPositionOffset.length2() != 0) { mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; mSimulationPosition += mPositionOffset; mPositionOffset = osg::Vec3f(); } return mPosition; } void Actor::setRotation(osg::Quat quat) { std::scoped_lock lock(mPositionMutex); mRotation = quat; } bool Actor::isRotationallyInvariant() const { return mRotationallyInvariant; } void Actor::updateScale() { std::scoped_lock lock(mPositionMutex); float scale = mPtr.getCellRef().getScale(); osg::Vec3f scaleVec(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; mHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); } osg::Vec3f Actor::getHalfExtents() const { return mHalfExtents; } osg::Vec3f Actor::getOriginalHalfExtents() const { return mOriginalHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { return mRenderingHalfExtents; } void Actor::setInertialForce(const osg::Vec3f &force) { mForce = force; } void Actor::setOnGround(bool grounded) { mOnGround = grounded; } void Actor::setOnSlope(bool slope) { mOnSlope = slope; } bool Actor::isWalkingOnWater() const { return mWalkingOnWater; } void Actor::setWalkingOnWater(bool walkingOnWater) { mWalkingOnWater = walkingOnWater; } void Actor::setCanWaterWalk(bool waterWalk) { if (waterWalk != mCanWaterWalk) { mCanWaterWalk = waterWalk; updateCollisionMask(); } } MWWorld::Ptr Actor::getStandingOnPtr() const { std::scoped_lock lock(mPositionMutex); return mStandingOnPtr; } void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) { std::scoped_lock lock(mPositionMutex); mStandingOnPtr = ptr; } bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const { const float halfZ = getHalfExtents().z(); const osg::Vec3f actorPosition = getPosition(); const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); MWPhysics::ActorTracer tracer; tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world); return (tracer.mFraction >= 1.0f); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/actor.hpp000066400000000000000000000146041445372753700225600ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H #include #include #include "ptrholder.hpp" #include #include #include #include class btCollisionShape; class btCollisionObject; class btCollisionWorld; class btConvexShape; namespace Resource { struct BulletShape; } namespace MWPhysics { class PhysicsTaskScheduler; class Actor final : public PtrHolder { public: Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType); ~Actor() override; /** * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. */ void enableCollisionMode(bool collision); bool getCollisionMode() const { return mInternalCollisionMode; } btConvexShape* getConvexShape() const { return mConvexShape; } /** * Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor. */ void enableCollisionBody(bool collision); void updateScale(); void setRotation(osg::Quat quat); /** * Return true if the collision shape looks the same no matter how its Z rotated. */ bool isRotationallyInvariant() const; /** * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition * to account for e.g. scripted movements */ void setSimulationPosition(const osg::Vec3f& position); void updateCollisionObjectPosition(); /** * Returns the half extents of the collision body (scaled according to collision scale) */ osg::Vec3f getHalfExtents() const; /** * Returns the half extents of the collision body (not scaled) */ osg::Vec3f getOriginalHalfExtents() const; /** * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. */ osg::Vec3f getCollisionObjectPosition() const; /** * Store the current position into mPreviousPosition, then move to this position. * Returns true if the new position is different. */ bool setPosition(const osg::Vec3f& position); // force set actor position to be as in Ptr::RefData void updatePosition(); // register a position offset that will be applied during simulation. void adjustPosition(const osg::Vec3f& offset); // apply position offset. Can't be called during simulation osg::Vec3f applyOffsetChange(); /** * Returns the half extents of the collision body (scaled according to rendering scale) * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, * most likely to make environment collision testing easier. However in some cases (swimming level) we want the actual scale. */ osg::Vec3f getRenderingHalfExtents() const; /** * Sets the current amount of inertial force (incl. gravity) affecting this physic actor */ void setInertialForce(const osg::Vec3f &force); /** * Gets the current amount of inertial force (incl. gravity) affecting this physic actor */ const osg::Vec3f &getInertialForce() const { return mForce; } void setOnGround(bool grounded); bool getOnGround() const { return mOnGround; } void setOnSlope(bool slope); bool getOnSlope() const { return mOnSlope; } /// Sets whether this actor should be able to collide with the water surface void setCanWaterWalk(bool waterWalk); /// Sets whether this actor has been walking on the water surface in the last frame void setWalkingOnWater(bool walkingOnWater); bool isWalkingOnWater() const; MWWorld::Ptr getStandingOnPtr() const; void setStandingOnPtr(const MWWorld::Ptr& ptr); unsigned int getStuckFrames() const { return mStuckFrames; } void setStuckFrames(unsigned int frames) { mStuckFrames = frames; } const osg::Vec3f &getLastStuckPosition() const { return mLastStuckPosition; } void setLastStuckPosition(osg::Vec3f position) { mLastStuckPosition = position; } bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; bool isActive() const { return mActive; } void setActive(bool value) { mActive = value; } DetourNavigator::CollisionShapeType getCollisionShapeType() const { return mCollisionShapeType; } private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world void updateCollisionMask(); void addCollisionMask(int collisionMask); int getCollisionMask() const; /// Returns the mesh translation, scaled and rotated as necessary osg::Vec3f getScaledMeshTranslation() const; bool mCanWaterWalk; bool mWalkingOnWater; bool mRotationallyInvariant; DetourNavigator::CollisionShapeType mCollisionShapeType; std::unique_ptr mShape; btConvexShape* mConvexShape; osg::Vec3f mMeshTranslation; osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; osg::Vec3f mRenderingHalfExtents; osg::Quat mRotation; osg::Vec3f mScale; osg::Vec3f mPositionOffset; bool mSkipSimulation; mutable std::mutex mPositionMutex; unsigned int mStuckFrames; osg::Vec3f mLastStuckPosition; osg::Vec3f mForce; bool mOnGround; bool mOnSlope; bool mInternalCollisionMode; bool mExternalCollisionMode; bool mActive; PhysicsTaskScheduler* mTaskScheduler; Actor(const Actor&); Actor& operator=(const Actor&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/actorconvexcallback.cpp000066400000000000000000000112041445372753700254440ustar00rootroot00000000000000#include "actorconvexcallback.hpp" #include "collisiontype.hpp" #include "contacttestwrapper.h" #include #include #include "projectile.hpp" namespace MWPhysics { class ActorOverlapTester : public btCollisionWorld::ContactResultCallback { public: bool overlapping = false; btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override { if(cp.getDistance() <= 0.0f) overlapping = true; return btScalar(1); } }; ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); // override data for actor-actor collisions // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them // For some reason this doesn't work as well as it should when using capsules, but it still helps a lot. if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) { ActorOverlapTester isOverlapping; // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct. ContactTestWrapper::contactPairTest(const_cast(mWorld), const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); if(isOverlapping.overlapping) { auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); osg::Vec3f motion = Misc::Convert::toOsg(mMotion); osg::Vec3f normal = (originA-originB); normal.z() = 0; normal.normalize(); // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted) // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them. // It happens in vanilla Morrowind too, but much less often. // I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug. if(normal * motion > 0.0f) { convexResult.m_hitFraction = 0.0f; convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal); return ClosestConvexResultCallback::addSingleResult(convexResult, true); } else { return btScalar(1); } } } if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); if (projectileHolder->isValidTarget(mMe)) projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } btVector3 hitNormalWorld; if (normalInWorldSpace) hitNormalWorld = convexResult.m_hitNormalLocal; else { ///need to transform normal into worldspace hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; } // dot product of the motion vector against the collision contact normal btScalar dotCollision = mMotion.dot(hitNormalWorld); if (dotCollision <= mMinCollisionDot) return btScalar(1); return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/actorconvexcallback.hpp000066400000000000000000000013651445372753700254600ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H #define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H #include class btCollisionObject; namespace MWPhysics { class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; protected: const btCollisionObject *mMe; const btVector3 mMotion; const btScalar mMinCollisionDot; const btCollisionWorld * mWorld; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp000066400000000000000000000022431445372753700276060ustar00rootroot00000000000000#include "closestnotmerayresultcallback.hpp" #include #include #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics { ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) , mMe(me), mTargets(std::move(targets)) { } btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { const auto* hitObject = rayResult.m_collisionObject; if (hitObject == mMe) return 1.f; if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), hitObject) == mTargets.end())) return 1.f; } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp000066400000000000000000000014201445372753700276070ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H #define OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H #include #include class btCollisionObject; namespace MWPhysics { class Projectile; class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; const std::vector mTargets; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/collisiontype.hpp000066400000000000000000000012571445372753700243450ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_COLLISIONTYPE_H #define OPENMW_MWPHYSICS_COLLISIONTYPE_H namespace MWPhysics { enum CollisionType { CollisionType_World = 1<<0, CollisionType_Door = 1<<1, CollisionType_Actor = 1<<2, CollisionType_HeightMap = 1<<3, CollisionType_Projectile = 1<<4, CollisionType_Water = 1<<5, CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, CollisionType_AnyPhysical = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door|CollisionType_Projectile|CollisionType_Water, CollisionType_CameraOnly = 1<<6, CollisionType_VisualOnly = 1<<7 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/constants.hpp000066400000000000000000000023151445372753700234600ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_CONSTANTS_H #define OPENMW_MWPHYSICS_CONSTANTS_H namespace MWPhysics { static constexpr float sStepSizeDown = 62.0f; static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance static constexpr bool sDoExtraStairHacks = true; static constexpr float sGroundOffset = 1.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. static constexpr float sCollisionMargin = 0.2f; // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues static constexpr float sAllowedPenetration = 0.0f; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/contacttestresultcallback.cpp000066400000000000000000000021741445372753700267110ustar00rootroot00000000000000#include "contacttestresultcallback.hpp" #include #include "components/misc/convert.hpp" #include "ptrholder.hpp" namespace MWPhysics { ContactTestResultCallback::ContactTestResultCallback(const btCollisionObject* testedAgainst) : mTestedAgainst(testedAgainst) { } btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) collisionObject = col1Wrap->m_collisionObject; PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) mResult.emplace_back(ContactPoint{holder->getPtr(), Misc::Convert::toOsg(cp.m_positionWorldOnB), Misc::Convert::toOsg(cp.m_normalWorldOnB)}); return 0.f; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/contacttestresultcallback.hpp000066400000000000000000000015441445372753700267160ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_CONTACTTESTRESULTCALLBACK_H #define OPENMW_MWPHYSICS_CONTACTTESTRESULTCALLBACK_H #include #include #include "physicssystem.hpp" class btCollisionObject; struct btCollisionObjectWrapper; namespace MWPhysics { class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback { const btCollisionObject* mTestedAgainst; public: ContactTestResultCallback(const btCollisionObject* testedAgainst); btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override; std::vector mResult; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/contacttestwrapper.cpp000066400000000000000000000014431445372753700253740ustar00rootroot00000000000000#include #include "contacttestwrapper.h" namespace MWPhysics { // Concurrent calls to contactPairTest (and by extension contactTest) are forbidden. static std::mutex contactMutex; void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::unique_lock lock(contactMutex); collisionWorld->contactTest(colObj, resultCallback); } void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback) { std::unique_lock lock(contactMutex); collisionWorld->contactPairTest(colObjA, colObjB, resultCallback); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/contacttestwrapper.h000066400000000000000000000010671445372753700250430ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H #define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H #include namespace MWPhysics { struct ContactTestWrapper { static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp000066400000000000000000000032701445372753700313240ustar00rootroot00000000000000#include "deepestnotmecontacttestresultcallback.hpp" #include #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics { DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3 &origin) : mMe(me), mTargets(targets), mOrigin(origin), mLeastDistSqr(std::numeric_limits::max()) { } btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; if (collisionObject != mMe) { if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) return 0.f; } btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); if(!mObject || distsqr < mLeastDistSqr) { mObject = collisionObject; mLeastDistSqr = distsqr; mContactPoint = cp.getPositionWorldOnA(); mContactNormal = cp.m_normalWorldOnB; } } return 0.f; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp000066400000000000000000000022521445372753700313300ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H #define OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H #include #include class btCollisionObject; namespace MWPhysics { class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback { const btCollisionObject* mMe; const std::vector mTargets; // Store the real origin, since the shape's origin is its center btVector3 mOrigin; public: const btCollisionObject *mObject{nullptr}; btVector3 mContactPoint{0,0,0}; btVector3 mContactNormal{0,0,0}; btScalar mLeastDistSqr; DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3 &origin); btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/hasspherecollisioncallback.hpp000066400000000000000000000051511445372753700270200ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H #define OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H #include #include #include namespace MWPhysics { // https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection bool testAabbAgainstSphere(const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius) { const btVector3 nearest( std::clamp(position.x(), aabbMin.x(), aabbMax.x()), std::clamp(position.y(), aabbMin.y(), aabbMax.y()), std::clamp(position.z(), aabbMin.z(), aabbMax.z()) ); return nearest.distance(position) < radius; } template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group, const Ignore& ignore, OnCollision* onCollision) : mPosition(position), mRadius(radius), mIgnore(ignore), mCollisionFilterMask(mask), mCollisionFilterGroup(group), mOnCollision(onCollision) { } bool process(const btBroadphaseProxy* proxy) override { if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); if (mIgnore(collisionObject) || !needsCollision(*proxy) || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; mResult = true; if (mOnCollision != nullptr) { (*mOnCollision)(collisionObject); return true; } return !mResult; } bool getResult() const { return mResult; } private: btVector3 mPosition; btScalar mRadius; Ignore mIgnore; int mCollisionFilterMask; int mCollisionFilterGroup; OnCollision* mOnCollision; bool mResult = false; bool needsCollision(const btBroadphaseProxy& proxy) const { bool collides = (proxy.m_collisionFilterGroup & mCollisionFilterMask) != 0; collides = collides && (mCollisionFilterGroup & proxy.m_collisionFilterMask); return collides; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/heightfield.cpp000066400000000000000000000072361445372753700237220ustar00rootroot00000000000000#include "heightfield.hpp" #include "mtphysics.hpp" #include #include #include #include #include #include #if BT_BULLET_VERSION < 310 // Older Bullet versions only support `btScalar` heightfields. // Our heightfield data is `float`. // // These functions handle conversion from `float` to `double` when // `btScalar` is `double` (`BT_USE_DOUBLE_PRECISION`). namespace { template auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { return {}; } template auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { return std::vector(heights, heights + static_cast(verts * verts)); } template auto getHeights(const T* floatHeights, const std::vector&) -> std::enable_if_t::value, const btScalar*> { return floatHeights; } template auto getHeights(const T*, const std::vector& btScalarHeights) -> std::enable_if_t::value, const btScalar*> { return btScalarHeights.data(); } } #endif namespace MWPhysics { HeightField::HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) : mHoldObject(holdObject) #if BT_BULLET_VERSION < 310 , mHeights(makeHeights(heights, verts)) #endif , mTaskScheduler(scheduler) { #if BT_BULLET_VERSION < 310 mShape = std::make_unique( verts, verts, getHeights(heights, mHeights), 1, minH, maxH, 2, PHY_FLOAT, false ); #else mShape = std::make_unique( verts, verts, heights, minH, maxH, 2, false); #endif mShape->setUseDiamondSubdivision(true); const float scaling = static_cast(size) / static_cast(verts - 1); mShape->setLocalScaling(btVector3(scaling, scaling, 1)); #if BT_BULLET_VERSION >= 289 // Accelerates some collision tests. // // Note: The accelerator data structure in Bullet is only used // in some operations. This could be improved, see: // https://github.com/bulletphysics/bullet3/issues/3276 mShape->buildAccelerator(); #endif const btTransform transform(btQuaternion::getIdentity(), BulletHelpers::getHeightfieldShift(x, y, size, minH, maxH)); mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setWorldTransform(transform); mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile); } HeightField::~HeightField() { mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } btCollisionObject* HeightField::getCollisionObject() { return mCollisionObject.get(); } const btCollisionObject* HeightField::getCollisionObject() const { return mCollisionObject.get(); } const btHeightfieldTerrainShape* HeightField::getShape() const { return mShape.get(); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/heightfield.hpp000066400000000000000000000022051445372753700237160ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H #define OPENMW_MWPHYSICS_HEIGHTFIELD_H #include #include #include #include class btCollisionObject; class btHeightfieldTerrainShape; namespace osg { class Object; } namespace MWPhysics { class PhysicsTaskScheduler; class HeightField { public: HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); btCollisionObject* getCollisionObject(); const btCollisionObject* getCollisionObject() const; const btHeightfieldTerrainShape* getShape() const; private: std::unique_ptr mShape; std::unique_ptr mCollisionObject; osg::ref_ptr mHoldObject; #if BT_BULLET_VERSION < 310 std::vector mHeights; #endif PhysicsTaskScheduler* mTaskScheduler; void operator=(const HeightField&); HeightField(const HeightField&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/movementsolver.cpp000066400000000000000000000637331445372753700245370ustar00rootroot00000000000000#include "movementsolver.hpp" #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/refdata.hpp" #include "actor.hpp" #include "collisiontype.hpp" #include "constants.hpp" #include "contacttestwrapper.h" #include "physicssystem.hpp" #include "projectile.hpp" #include "projectileconvexcallback.hpp" #include "stepper.hpp" #include "trace.h" #include namespace MWPhysics { static bool isActor(const btCollisionObject *obj) { assert(obj); return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { public: ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) { m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; mVelocity = Misc::Convert::toBullet(velocity); } btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override { if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) return 0.0; // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving) if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) return 0.0; auto delta = contact.m_normalWorldOnB * -contact.m_distance1; mContactSum += delta; mMaxX = std::max(std::abs(delta.x()), mMaxX); mMaxY = std::max(std::abs(delta.y()), mMaxY); mMaxZ = std::max(std::abs(delta.z()), mMaxZ); if (contact.m_distance1 < mDistance) { mDistance = contact.m_distance1; mNormal = contact.m_normalWorldOnB; mDelta = delta; return mDistance; } else { return 0.0; } } btScalar mMaxX = 0.0; btScalar mMaxY = 0.0; btScalar mMaxZ = 0.0; btVector3 mContactSum{0.0, 0.0, 0.0}; btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me" btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me" btScalar mDistance = 0.0; // negative or zero protected: btVector3 mVelocity; const btCollisionObject * mMe; }; osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) { osg::Vec3f offset = actor->getCollisionObjectPosition() - ptr.getRefData().getPosition().asVec3(); ActorTracer tracer; tracer.findGround(actor, position + offset, position + offset - osg::Vec3f(0,0,maxHeight), collisionWorld); if (tracer.mFraction >= 1.0f) { actor->setOnGround(false); return position; } actor->setOnGround(true); // Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account btVector3 from = Misc::Convert::toBullet(position); btVector3 to = from - btVector3(0,0,maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); resultCallback1.m_collisionFilterGroup = CollisionType_AnyPhysical; resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; collisionWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit() && ((Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos + offset).length2() > 35*35 || !isWalkableSlope(tracer.mPlaneNormal))) { actor->setOnSlope(!isWalkableSlope(resultCallback1.m_hitNormalWorld)); return Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset); } actor->setOnSlope(!isWalkableSlope(tracer.mPlaneNormal)); return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset); } void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData) { // Reset per-frame data actor.mWalkingOnWater = false; // Anything to collide with? if(actor.mSkipCollisionDetection) { actor.mPosition += (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1)) ) * actor.mMovement * time; return; } // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; ActorTracer tracer; osg::Vec3f velocity; // Dead and paralyzed actors underwater will float to the surface, // if the CharacterController tells us to do so if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) { velocity = osg::Vec3f(0,0,1) * 25; } else if (actor.mPosition.z() < swimlevel || actor.mFlying) { velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; } else { velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) || (velocity.z() > 0.f && velocity.z() + actor.mInertia.z() <= -velocity.z() && actor.mIsOnSlope)) actor.mInertia = velocity; else if (!actor.mIsOnGround || actor.mIsOnSlope) velocity = velocity + actor.mInertia; } // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get().find("fStromWalkMult")->mValue.getFloat(); velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } Stepper stepper(collisionWorld, actor.mCollisionObject); osg::Vec3f origVelocity = velocity; osg::Vec3f newPosition = actor.mPosition; /* * A loop to find newPosition using tracer, if successful different from the starting position. * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime * The initial velocity was set earlier (see above). */ float remainingTime = time; int numTimesSlid = 0; osg::Vec3f lastSlideNormal(0,0,1); osg::Vec3f lastSlideNormalFallback(0,0,1); bool forceGroundTest = false; for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air if(!actor.mFlying && nextpos.z() > swimlevel && underwater) { const osg::Vec3f down(0,0,-1); velocity = reject(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } if((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); // check for obstructions if(tracer.mFraction >= 1.0f) { newPosition = tracer.mEndPos; // ok to move, so set newPosition break; } } else { // The current position and next position are nearly the same, so just exit. // Note: Bullet can trigger an assert in debug modes if the positions // are the same, since that causes it to attempt to normalize a zero // length vector (which can also happen with nearly identical vectors, since // precision can be lost due to any math Bullet does internally). Since we // aren't performing any collision detection, we want to reject the next // position, so that we don't slowly move inside another object. break; } bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) { // Try to step up onto it. // NOTE: this modifies newPosition and velocity on its own if successful usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); } if (usedStepLogic) { if (actor.mIsAquatic && newPosition.z() + actor.mHalfExtentsZ > actor.mWaterlevel) newPosition = oldPosition; else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) forceGroundTest = true; } else { // Can't step up, so slide against what we ran into remainingTime *= (1.0f-tracer.mFraction); auto planeNormal = tracer.mPlaneNormal; // need to know the unadjusted normal to handle certain types of seams properly const auto origPlaneNormal = planeNormal; // If we touched the ground this frame, and whatever we ran into is a wall of some sort, // pretend that its collision normal is pointing horizontally // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin) if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) { planeNormal.z() = 0; planeNormal.normalize(); } // Move up to what we ran into (with a bit of a collision margin) if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin) { auto direction = velocity; direction.normalize(); newPosition = tracer.mEndPos; newPosition -= direction*sCollisionMargin; } osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity; bool usedSeamLogic = false; // check for the current and previous collision planes forming an acute angle; slide along the seam if they do // for this, we want to use the original plane normal, or else certain types of geometry will snag if(numTimesSlid > 0) { auto dotA = lastSlideNormal * origPlaneNormal; auto dotB = lastSlideNormalFallback * origPlaneNormal; if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide dotB = 1.0; if(dotA <= 0.0 || dotB <= 0.0) { osg::Vec3f bestNormal = lastSlideNormal; // use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't if(dotB < dotA) { bestNormal = lastSlideNormalFallback; lastSlideNormal = lastSlideNormalFallback; } auto constraintVector = bestNormal ^ origPlaneNormal; // cross product if(constraintVector.length2() > 0) // only if it's not zero length { constraintVector.normalize(); newVelocity = project(velocity, constraintVector); // version of surface rejection for acute crevices/seams auto averageNormal = bestNormal + origPlaneNormal; averageNormal.normalize(); tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; usedSeamLogic = true; } } } // otherwise just keep the normal vector rejection // move away from the collision plane slightly, if possible // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings // this is different from the normal collision margin, because the normal collision margin is along the movement path, // but this is along the collision normal if(!usedSeamLogic) { tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } // short circuit if we went backwards, but only if it was mostly horizontal and we're on the ground if (seenGround && newVelocity * origVelocity <= 0.0f) { auto perpendicular = newVelocity ^ origVelocity; if (perpendicular.length2() > 0.0f) { perpendicular.normalize(); if (std::abs(perpendicular.z()) > 0.7071f) break; } } // Do not allow sliding up steep slopes if there is gravity. // The purpose of this is to prevent air control from letting you slide up tall, unwalkable slopes. // For that purpose, it is not necessary to do it when trying to slide along acute seams/crevices (i.e. usedSeamLogic) // and doing so would actually break air control in some situations where vanilla allows air control. // Vanilla actually allows you to slide up slopes as long as you're in the "walking" animation, which can be true even // in the air, so allowing this for seams isn't a compatibility break. if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal) && !usedSeamLogic) newVelocity.z() = std::min(newVelocity.z(), velocity.z()); numTimesSlid += 1; lastSlideNormalFallback = lastSlideNormal; lastSlideNormal = origPlaneNormal; velocity = newVelocity; } } bool isOnGround = false; bool isOnSlope = false; if (forceGroundTest || (actor.mInertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) { isOnGround = true; isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); actor.mStandingOn = tracer.mHitObject; if (actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { if (tracer.mFraction*dropDistance > sGroundOffset) newPosition.z() = tracer.mEndPos.z() + sGroundOffset; else { newPosition.z() = tracer.mEndPos.z(); tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); newPosition = (newPosition+tracer.mEndPos)/2.0; } } } else { // Vanilla allows actors to float on top of other actors. Do not push them off. if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z()) newPosition.z() = tracer.mEndPos.z() + sGroundOffset; isOnGround = false; } } // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection if(actor.mStuckFrames > 0) { isOnGround = true; isOnSlope = false; } } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) actor.mInertia = osg::Vec3f(0.f, 0.f, 0.f); else { actor.mInertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; if (actor.mInertia.z() < 0) actor.mInertia.z() *= actor.mSlowFall; if (actor.mSlowFall < 1.f) { actor.mInertia.x() *= actor.mSlowFall; actor.mInertia.y() *= actor.mSlowFall; } } actor.mIsOnGround = isOnGround; actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate } void MovementSolver::move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld) { btVector3 btFrom = Misc::Convert::toBullet(projectile.mPosition); btVector3 btTo = Misc::Convert::toBullet(projectile.mPosition + projectile.mMovement * time); if (btFrom == btTo) return; ProjectileConvexCallback resultCallback(projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; const btQuaternion btrot = btQuaternion::getIdentity(); btTransform from_ (btrot, btFrom); btTransform to_ (btrot, btTo); const btCollisionShape* shape = projectile.mCollisionObject->getCollisionShape(); assert(shape->isConvex()); collisionWorld->convexSweepTest(static_cast(shape), from_, to_, resultCallback); projectile.mPosition = Misc::Convert::toOsg(projectile.mProjectile->isActive() ? btTo : resultCallback.m_hitPointWorld); } btVector3 addMarginToDelta(btVector3 delta) { if(delta.length2() == 0.0) return delta; return delta + delta.normalized() * sCollisionMargin; } void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { if(actor.mSkipCollisionDetection) // noclipping/tcl return; if (actor.mMovement.length2() == 0) // no AI nor player attempted to move, current position is assumed correct return; auto tempPosition = actor.mPosition; if(actor.mStuckFrames >= 10) { if((actor.mLastStuckPosition - actor.mPosition).length2() < 100) return; else { actor.mStuckFrames = 0; actor.mLastStuckPosition = {0, 0, 0}; } } // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it if (!actor.mIsOnGround || actor.mIsOnSlope) velocity += actor.mInertia; // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, // we need to replicate part of the collision box's transform process from scratch osg::Vec3f refPosition = tempPosition + verticalHalfExtent; osg::Vec3f goodPosition = refPosition; const btTransform oldTransform = actor.mCollisionObject->getWorldTransform(); btTransform newTransform = oldTransform; auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback { goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); actor.mCollisionObject->setWorldTransform(newTransform); ContactCollectionCallback callback{actor.mCollisionObject, velocity}; ContactTestWrapper::contactTest(const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; }; // check whether we're inside the world with our collision box with manually-derived offset auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); if(contactCallback.mDistance < -sAllowedPenetration) { ++actor.mStuckFrames; actor.mLastStuckPosition = actor.mPosition; // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections if(std::abs(positionDelta.x()) > contactCallback.mMaxX) positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x()); if(std::abs(positionDelta.y()) > contactCallback.mMaxY) positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y()); if(std::abs(positionDelta.z()) > contactCallback.mMaxZ) positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z()); auto contactCallback2 = gatherContacts(positionDelta); // successfully moved further out from contact (does not have to be in open space, just less inside of things) if(contactCallback2.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; // try again but only upwards (fixes some bad coc floors) else { // upwards-only offset auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())}); // success if(contactCallback3.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; else // try again but fixed distance up { auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0}); // success if(contactCallback4.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; } } } else { actor.mStuckFrames = 0; actor.mLastStuckPosition = {0, 0, 0}; } actor.mCollisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/movementsolver.hpp000066400000000000000000000027251445372753700245360ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_MOVEMENTSOLVER_H #define OPENMW_MWPHYSICS_MOVEMENTSOLVER_H #include #include #include "../mwworld/ptr.hpp" class btCollisionWorld; namespace MWWorld { class Ptr; } namespace MWPhysics { /// Vector projection static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) { return v * (u * v); } /// Vector rejection static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) { return direction - project(direction, planeNormal); } template static bool isWalkableSlope(const Vec3 &normal) { static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); return (normal.z() > sMaxSlopeCos); } class Actor; struct ActorFrameData; struct ProjectileFrameData; struct WorldFrameData; class MovementSolver { public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData); static void move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/mtphysics.cpp000066400000000000000000001016271445372753700234700ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "components/debug/debuglog.hpp" #include #include "components/misc/convert.hpp" #include "components/settings/settings.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "actor.hpp" #include "contacttestwrapper.h" #include "movementsolver.hpp" #include "mtphysics.hpp" #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" namespace { template std::optional> makeExclusiveLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) { if (lockingPolicy == MWPhysics::LockingPolicy::NoLocks) return {}; return std::unique_lock(mutex); } /// @brief A scoped lock that is either exclusive or inexistent depending on configuration template class MaybeExclusiveLock { public: explicit MaybeExclusiveLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) : mImpl(makeExclusiveLock(mutex, lockingPolicy)) { } private: std::optional> mImpl; }; template std::optional> makeSharedLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) { if (lockingPolicy == MWPhysics::LockingPolicy::NoLocks) return {}; return std::shared_lock(mutex); } /// @brief A scoped lock that is either shared or inexistent depending on configuration template class MaybeSharedLock { public: explicit MaybeSharedLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) : mImpl(makeSharedLock(mutex, lockingPolicy)) { } private: std::optional> mImpl; }; template std::variant, std::shared_lock> makeLock( Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) { switch (lockingPolicy) { case MWPhysics::LockingPolicy::NoLocks: return std::monostate{}; case MWPhysics::LockingPolicy::ExclusiveLocksOnly: return std::unique_lock(mutex); case MWPhysics::LockingPolicy::AllowSharedLocks: return std::shared_lock(mutex); }; throw std::runtime_error("Unsupported LockingPolicy: " + std::to_string(static_cast>(lockingPolicy))); } /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration template class MaybeLock { public: explicit MaybeLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy) : mImpl(makeLock(mutex, lockingPolicy)) { } private: std::variant, std::shared_lock> mImpl; }; bool isUnderWater(const MWPhysics::ActorFrameData& actorData) { return actorData.mPosition.z() < actorData.mSwimLevel; } osg::Vec3f interpolateMovements(const MWPhysics::PtrHolder& ptr, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor); } using LockedActorSimulation = std::pair< std::shared_ptr, std::reference_wrapper >; using LockedProjectileSimulation = std::pair< std::shared_ptr, std::reference_wrapper >; namespace Visitors { template class Lock> struct WithLockedPtr { const Impl& mImpl; std::shared_mutex& mCollisionWorldMutex; const MWPhysics::LockingPolicy mLockingPolicy; template void operator()(MWPhysics::SimulationImpl& sim) const { auto locked = sim.lock(); if (!locked.has_value()) return; auto&& [ptr, frameData] = *std::move(locked); // Locked shared_ptr has to be destructed after releasing mCollisionWorldMutex to avoid // possible deadlock. Ptr destructor also acquires mCollisionWorldMutex. const std::pair arg(std::move(ptr), frameData); const Lock lock(mCollisionWorldMutex, mLockingPolicy); mImpl(arg); } }; struct InitPosition { const btCollisionWorld* mCollisionWorld; void operator()(MWPhysics::ActorSimulation& sim) const { auto locked = sim.lock(); if (!locked.has_value()) return; auto& [actor, frameDataRef] = *locked; auto& frameData = frameDataRef.get(); frameData.mPosition = actor->applyOffsetChange(); if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) { const auto offset = osg::Vec3f(0, 0, frameData.mWaterlevel - frameData.mPosition.z()); MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset, false); frameData.mPosition = actor->applyOffsetChange(); } actor->updateCollisionObjectPosition(); frameData.mOldHeight = frameData.mPosition.z(); const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3(); frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z()); frameData.mInertia = actor->getInertialForce(); frameData.mStuckFrames = actor->getStuckFrames(); frameData.mLastStuckPosition = actor->getLastStuckPosition(); } void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const { } }; struct PreStep { btCollisionWorld* mCollisionWorld; void operator()(const LockedActorSimulation& sim) const { MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); } void operator()(const LockedProjectileSimulation& /*sim*/) const { } }; struct UpdatePosition { btCollisionWorld* mCollisionWorld; void operator()(const LockedActorSimulation& sim) const { auto& [actor, frameDataRef] = sim; auto& frameData = frameDataRef.get(); if (actor->setPosition(frameData.mPosition)) { frameData.mPosition = actor->getPosition(); // account for potential position change made by script actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } void operator()(const LockedProjectileSimulation& sim) const { auto& [proj, frameDataRef] = sim; auto& frameData = frameDataRef.get(); proj->setPosition(frameData.mPosition); proj->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(proj->getCollisionObject()); } }; struct Move { const float mPhysicsDt; const btCollisionWorld* mCollisionWorld; const MWPhysics::WorldFrameData& mWorldFrameData; void operator()(const LockedActorSimulation& sim) const { MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); } void operator()(const LockedProjectileSimulation& sim) const { if (sim.first->isActive()) MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); } }; struct Sync { const bool mAdvanceSimulation; const float mTimeAccum; const float mPhysicsDt; const MWPhysics::PhysicsTaskScheduler* scheduler; void operator()(MWPhysics::ActorSimulation& sim) const { auto locked = sim.lock(); if (!locked.has_value()) return; auto& [actor, frameDataRef] = *locked; auto& frameData = frameDataRef.get(); auto ptr = actor->getPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const float heightDiff = frameData.mPosition.z() - frameData.mOldHeight; const bool isStillOnGround = (mAdvanceSimulation && frameData.mWasOnGround && frameData.mIsOnGround); if (isStillOnGround || frameData.mFlying || isUnderWater(frameData) || frameData.mSlowFall < 1) stats.land(ptr == MWMechanics::getPlayer() && (frameData.mFlying || isUnderWater(frameData))); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); actor->setSimulationPosition(::interpolateMovements(*actor, mTimeAccum, mPhysicsDt)); actor->setLastStuckPosition(frameData.mLastStuckPosition); actor->setStuckFrames(frameData.mStuckFrames); if (mAdvanceSimulation) { MWWorld::Ptr standingOn; auto* ptrHolder = static_cast(scheduler->getUserPointer(frameData.mStandingOn)); if (ptrHolder) standingOn = ptrHolder->getPtr(); actor->setStandingOnPtr(standingOn); // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change if (actor->getOnGround() == frameData.mWasOnGround) actor->setOnGround(frameData.mIsOnGround); actor->setOnSlope(frameData.mIsOnSlope); actor->setWalkingOnWater(frameData.mWalkingOnWater); actor->setInertialForce(frameData.mInertia); } } void operator()(MWPhysics::ProjectileSimulation& sim) const { auto locked = sim.lock(); if (!locked.has_value()) return; auto& [proj, frameData] = *locked; proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt)); } }; } } namespace MWPhysics { namespace { unsigned getMaxBulletSupportedThreads() { auto broad = std::make_unique(); assert(BT_MAX_THREAD_COUNT > 0); return std::min(broad->m_rayTestStacks.size(), BT_MAX_THREAD_COUNT - 1); } LockingPolicy detectLockingPolicy() { if (Settings::Manager::getInt("async num threads", "Physics") < 1) return LockingPolicy::NoLocks; if (getMaxBulletSupportedThreads() > 1) return LockingPolicy::AllowSharedLocks; Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; return LockingPolicy::ExclusiveLocksOnly; } unsigned getNumThreads(LockingPolicy lockingPolicy) { switch (lockingPolicy) { case LockingPolicy::NoLocks: return 0; case LockingPolicy::ExclusiveLocksOnly: return 1; case LockingPolicy::AllowSharedLocks: return std::clamp( Settings::Manager::getInt("async num threads", "Physics"), 0, getMaxBulletSupportedThreads()); } throw std::runtime_error("Unsupported LockingPolicy: " + std::to_string(static_cast>(lockingPolicy))); } } class PhysicsTaskScheduler::WorkersSync { public: void waitForWorkers() { std::unique_lock lock(mWorkersDoneMutex); if (mFrameCounter != mWorkersFrameCounter) mWorkersDone.wait(lock); } void wakeUpWorkers() { const std::lock_guard lock(mHasJobMutex); ++mFrameCounter; mHasJob.notify_all(); } void stopWorkers() { const std::lock_guard lock(mHasJobMutex); mShouldStop = true; mHasJob.notify_all(); } void workIsDone() { const std::lock_guard lock(mWorkersDoneMutex); ++mWorkersFrameCounter; mWorkersDone.notify_all(); } template void runWorker(F&& f) noexcept { std::size_t lastFrame = 0; std::unique_lock lock(mHasJobMutex); while (!mShouldStop) { mHasJob.wait(lock, [&] { return mShouldStop || mFrameCounter != lastFrame; }); lastFrame = mFrameCounter; lock.unlock(); f(); lock.lock(); } } private: std::size_t mWorkersFrameCounter = 0; std::condition_variable mWorkersDone; std::mutex mWorkersDoneMutex; std::condition_variable mHasJob; bool mShouldStop = false; std::size_t mFrameCounter = 0; std::mutex mHasJobMutex; }; PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) , mDebugDrawer(debugDrawer) , mLockingPolicy(detectLockingPolicy()) , mNumThreads(getNumThreads(mLockingPolicy)) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) , mAdvanceSimulation(false) , mNextJob(0) , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) , mPrevStepCount(1) , mBudget(physicsDt) , mAsyncBudget(0.0f) , mBudgetCursor(0) , mAsyncStartTime(0) , mTimeBegin(0) , mTimeEnd(0) , mFrameStart(0) , mWorkersSync(mNumThreads >= 1 ? std::make_unique() : nullptr) { if (mNumThreads >= 1) { Log(Debug::Info) << "Using " << mNumThreads << " async physics threads"; for (unsigned i = 0; i < mNumThreads; ++i) mThreads.emplace_back([&] { worker(); } ); } else { mLOSCacheExpiry = 0; } mPreStepBarrier = std::make_unique(mNumThreads); mPostStepBarrier = std::make_unique(mNumThreads); mPostSimBarrier = std::make_unique(mNumThreads); } PhysicsTaskScheduler::~PhysicsTaskScheduler() { waitForWorkers(); { MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); mNumJobs = 0; mRemainingSteps = 0; } if (mWorkersSync != nullptr) mWorkersSync->stopWorkers(); for (auto& thread : mThreads) thread.join(); } std::tuple PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const { int maxAllowedSteps = 2; int numSteps = timeAccum / mDefaultPhysicsDt; // adjust maximum step count based on whether we're likely physics bottlenecked or not // if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget // if it ends up lower than numSteps and also 1, we will run a single delta time physics step // if we did not do this, and had a fixed step count limit, // we would have an unnecessarily low render framerate if we were only physics bottlenecked, // and we would be unnecessarily invoking true delta time if we were only render bottlenecked // get physics timing stats float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get()); // time spent per step in terms of the intended physics framerate budgetMeasurement /= mDefaultPhysicsDt; // ensure sane minimum value budgetMeasurement = std::max(0.00001f, budgetMeasurement); // we're spending almost or more than realtime per physics frame; limit to a single step if (budgetMeasurement > 0.95) maxAllowedSteps = 1; // physics is fairly cheap; limit based on expense if (budgetMeasurement < 0.5) maxAllowedSteps = std::ceil(1.0/budgetMeasurement); // limit to a reasonable amount maxAllowedSteps = std::min(10, maxAllowedSteps); // fall back to delta time for this frame if fixed timestep physics would fall behind float actualDelta = mDefaultPhysicsDt; if (numSteps > maxAllowedSteps) { numSteps = maxAllowedSteps; // ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency // this causes interpolation to 100% use the most recent physics result when true delta time is happening // and we deliberately simulate up to exactly the timestamp that we want to render actualDelta = timeAccum/float(numSteps+1); // actually: if this results in a per-step delta less than the target physics steptime, clamp it // this might reintroduce some stutter, but only comes into play in obscure cases // (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun) actualDelta = std::max(actualDelta, mDefaultPhysicsDt); } return std::make_tuple(numSteps, actualDelta); } void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { waitForWorkers(); prepareWork(timeAccum, std::move(simulations), frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); double timeStart = mTimer->tick(); // start by finishing previous background computation if (mNumThreads != 0) { syncWithMainThread(); if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); } auto [numSteps, newDelta] = calculateStepConfig(timeAccum); timeAccum -= numSteps*newDelta; // init const Visitors::InitPosition vis{mCollisionWorld}; for (auto& sim : simulations) { std::visit(vis, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; mSimulations = std::move(simulations); mAdvanceSimulation = (mRemainingSteps != 0); mNumJobs = mSimulations.size(); mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); if (mAdvanceSimulation) mBudgetCursor += 1; if (mNumThreads == 0) { doSimulation(); syncWithMainThread(); if(mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return; } mAsyncStartTime = mTimer->tick(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); } void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { waitForWorkers(); MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); mSimulations.clear(); for (const auto& [_, actor] : actors) { actor->updatePosition(); actor->updateCollisionObjectPosition(); } } void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); rayTo.setOrigin(target->getWorldTransform().getOrigin()); btCollisionWorld::ClosestRayResultCallback cb(from.getOrigin(), rayTo.getOrigin()); mCollisionWorld->rayTestSingle(from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb); if (!cb.hasHit()) // didn't hit the target. this could happen if point is already inside the collision box return std::nullopt; return {cb.m_hitPointWorld}; } void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback) { MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); } void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) { MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); } void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) { MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; } void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { mCollisionObjects.insert(collisionObject); MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { mCollisionObjects.erase(collisionObject); MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->removeCollisionObject(collisionObject); } void PhysicsTaskScheduler::updateSingleAabb(const std::shared_ptr& ptr, bool immediate) { if (immediate || mNumThreads == 0) { updatePtrAabb(ptr); } else { MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy); mUpdateAabb.insert(ptr); } } bool PhysicsTaskScheduler::getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2) { MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy); auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); if (result == mLOSCache.end()) { req.mResult = hasLineOfSight(actor1.get(), actor2.get()); mLOSCache.push_back(req); return req.mResult; } result->mAge = 0; return result->mResult; } void PhysicsTaskScheduler::refreshLOSCache() { MaybeSharedLock lock(mLOSCacheMutex, mLockingPolicy); int job = 0; int numLOS = mLOSCache.size(); while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS) { auto& req = mLOSCache[job]; auto actorPtr1 = req.mActors[0].lock(); auto actorPtr2 = req.mActors[1].lock(); if (req.mAge++ > mLOSCacheExpiry || !actorPtr1 || !actorPtr2) req.mStale = true; else req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get()); } } void PhysicsTaskScheduler::updateAabbs() { MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { auto p = ptr.lock(); if (p != nullptr) updatePtrAabb(p); }); mUpdateAabb.clear(); } void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr& ptr) { MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); if (const auto actor = std::dynamic_pointer_cast(ptr)) { actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } else if (const auto object = std::dynamic_pointer_cast(ptr)) { object->commitPositionChange(); mCollisionWorld->updateSingleAabb(object->getCollisionObject()); } else if (const auto projectile = std::dynamic_pointer_cast(ptr)) { projectile->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); } } void PhysicsTaskScheduler::worker() { mWorkersSync->runWorker([this] { std::shared_lock lock(mSimulationMutex); doSimulation(); }); } void PhysicsTaskScheduler::updateActorsPositions() { const Visitors::UpdatePosition impl{mCollisionWorld}; const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mLockingPolicy}; for (Simulation& sim : mSimulations) std::visit(vis, sim); } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) { btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9)); btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2); resultCallback.m_collisionFilterGroup = CollisionType_AnyPhysical; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; MaybeLock lockColWorld(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); } void PhysicsTaskScheduler::doSimulation() { while (mRemainingSteps) { mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; const Visitors::Move impl{mPhysicsDt, mCollisionWorld, *mWorldFrameData}; const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mLockingPolicy}; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) std::visit(vis, mSimulations[job]); mPostStepBarrier->wait([this] { afterPostStep(); }); } refreshLOSCache(); mPostSimBarrier->wait([this] { afterPostSim(); }); } void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { if (!stats.collectStats("engine")) return; if (mFrameNumber == frameNumber - 1) { stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd)); stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd)); } mFrameStart = frameStart; mTimeBegin = mTimer->tick(); mFrameNumber = frameNumber; } void PhysicsTaskScheduler::debugDraw() { MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); mDebugDrawer->step(); } void* PhysicsTaskScheduler::getUserPointer(const btCollisionObject* object) const { auto it = mCollisionObjects.find(object); if (it == mCollisionObjects.end()) return nullptr; return (*it)->getUserPointer(); } void PhysicsTaskScheduler::releaseSharedStates() { waitForWorkers(); std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); mSimulations.clear(); mUpdateAabb.clear(); } void PhysicsTaskScheduler::afterPreStep() { updateAabbs(); if (!mRemainingSteps) return; const Visitors::PreStep impl{mCollisionWorld}; const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mLockingPolicy}; for (auto& sim : mSimulations) std::visit(vis, sim); } void PhysicsTaskScheduler::afterPostStep() { if (mRemainingSteps) { --mRemainingSteps; updateActorsPositions(); } mNextJob.store(0, std::memory_order_release); } void PhysicsTaskScheduler::afterPostSim() { { MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy); mLOSCache.erase( std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); } void PhysicsTaskScheduler::syncWithMainThread() { const Visitors::Sync vis{mAdvanceSimulation, mTimeAccum, mPhysicsDt, this}; for (auto& sim : mSimulations) std::visit(vis, sim); } // Attempt to acquire unique lock on mSimulationMutex while not all worker // threads are holding shared lock but will have to may lead to a deadlock because // C++ standard does not guarantee priority for exclusive and shared locks // for std::shared_mutex. For example microsoft STL implementation points out // for the absence of such priority: // https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks void PhysicsTaskScheduler::waitForWorkers() { if (mWorkersSync != nullptr) mWorkersSync->waitForWorkers(); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/mtphysics.hpp000066400000000000000000000131051445372753700234660ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_MTPHYSICS_H #define OPENMW_MWPHYSICS_MTPHYSICS_H #include #include #include #include #include #include #include #include #include #include "physicssystem.hpp" #include "ptrholder.hpp" #include "components/misc/budgetmeasurement.hpp" namespace Misc { class Barrier; } namespace MWRender { class DebugDrawer; } namespace MWPhysics { enum class LockingPolicy { NoLocks, ExclusiveLocksOnly, AllowSharedLocks, }; class PhysicsTaskScheduler { public: PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); ~PhysicsTaskScheduler(); /// @brief move actors taking into account desired movements and collisions /// @param numSteps how much simulation step to run /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor void applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; void convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const; void contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); std::optional getHitPoint(const btTransform& from, btCollisionObject* target); void aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback); void getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max); void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void removeCollisionObject(btCollisionObject* collisionObject); void updateSingleAabb(const std::shared_ptr& ptr, bool immediate=false); bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); void debugDraw(); void* getUserPointer(const btCollisionObject* object) const; void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() private: class WorkersSync; void doSimulation(); void worker(); void updateActorsPositions(); bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); void updatePtrAabb(const std::shared_ptr& ptr); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::tuple calculateStepConfig(float timeAccum) const; void afterPreStep(); void afterPostStep(); void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); void prepareWork(float& timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector mSimulations; std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing std::unique_ptr mPreStepBarrier; std::unique_ptr mPostStepBarrier; std::unique_ptr mPostSimBarrier; LockingPolicy mLockingPolicy; unsigned mNumThreads; int mNumJobs; int mRemainingSteps; int mLOSCacheExpiry; bool mAdvanceSimulation; std::atomic mNextJob; std::atomic mNextLOS; std::vector mThreads; mutable std::shared_mutex mSimulationMutex; mutable std::shared_mutex mCollisionWorldMutex; mutable std::shared_mutex mLOSCacheMutex; mutable std::mutex mUpdateAabbMutex; unsigned int mFrameNumber; const osg::Timer* mTimer; int mPrevStepCount; Misc::BudgetMeasurement mBudget; Misc::BudgetMeasurement mAsyncBudget; unsigned int mBudgetCursor; osg::Timer_t mAsyncStartTime; osg::Timer_t mTimeBegin; osg::Timer_t mTimeEnd; osg::Timer_t mFrameStart; std::unique_ptr mWorkersSync; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/object.cpp000066400000000000000000000126521445372753700227120ustar00rootroot00000000000000#include "object.hpp" #include "mtphysics.hpp" #include #include #include #include #include #include #include #include namespace MWPhysics { Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(std::move(shapeInstance)) , mSolid(true) , mScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()) , mPosition(ptr.getRefData().getPosition().asVec3()) , mRotation(rotation) , mTaskScheduler(scheduler) { mPtr = ptr; mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); mCollisionObject->setUserPointer(this); mShapeInstance->setLocalScaling(mScale); mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } Object::~Object() { mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } const Resource::BulletShapeInstance* Object::getShapeInstance() const { return mShapeInstance.get(); } void Object::setScale(float scale) { std::unique_lock lock(mPositionMutex); mScale = { scale,scale,scale }; mScaleUpdatePending = true; } void Object::setRotation(osg::Quat quat) { std::unique_lock lock(mPositionMutex); mRotation = quat; mTransformUpdatePending = true; } void Object::updatePosition() { std::unique_lock lock(mPositionMutex); mPosition = mPtr.getRefData().getPosition().asVec3(); mTransformUpdatePending = true; } void Object::commitPositionChange() { std::unique_lock lock(mPositionMutex); if (mScaleUpdatePending) { mShapeInstance->setLocalScaling(mScale); mScaleUpdatePending = false; } if (mTransformUpdatePending) { btTransform trans; trans.setOrigin(Misc::Convert::toBullet(mPosition)); trans.setRotation(Misc::Convert::toBullet(mRotation)); mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); btTransform trans; trans.setOrigin(Misc::Convert::toBullet(mPosition)); trans.setRotation(Misc::Convert::toBullet(mRotation)); return trans; } bool Object::isSolid() const { return mSolid; } void Object::setSolid(bool solid) { mSolid = solid; } bool Object::isAnimated() const { return mShapeInstance->isAnimated(); } bool Object::animateCollisionShapes() { if (mShapeInstance->mAnimatedShapes.empty()) return false; assert (mShapeInstance->mCollisionShape->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); bool result = false; for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes) { auto nodePathFound = mRecIndexToNodePath.find(recIndex); if (nodePathFound == mRecIndexToNodePath.end()) { NifOsg::FindGroupByRecIndex visitor(recIndex); mPtr.getRefData().getBaseNode()->accept(visitor); if (!visitor.mFound) { Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId(); // Remove nonexistent nodes from animated shapes map and early out mShapeInstance->mAnimatedShapes.erase(recIndex); return false; } osg::NodePath nodePath = visitor.mFoundPath; nodePath.erase(nodePath.begin()); nodePathFound = mRecIndexToNodePath.emplace(recIndex, nodePath).first; } osg::NodePath& nodePath = nodePathFound->second; osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); matrix.orthoNormalize(matrix); btTransform transform; transform.setOrigin(Misc::Convert::toBullet(matrix.getTrans()) * compound->getLocalScaling()); for (int i=0; i<3; ++i) for (int j=0; j<3; ++j) transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference // Note: we can not apply scaling here for now since we treat scaled shapes // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now if (!(transform == compound->getChildTransform(shapeIndex))) { compound->updateChildTransform(shapeIndex, transform); result = true; } } return result; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/object.hpp000066400000000000000000000031151445372753700227110ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_OBJECT_H #define OPENMW_MWPHYSICS_OBJECT_H #include "ptrholder.hpp" #include #include #include #include #include namespace Resource { class BulletShapeInstance; } class btCollisionObject; class btVector3; namespace MWPhysics { class PhysicsTaskScheduler; class Object final : public PtrHolder { public: Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); void setRotation(osg::Quat quat); void updatePosition(); void commitPositionChange(); btTransform getTransform() const; /// Return solid flag. Not used by the object itself, true by default. bool isSolid() const; void setSolid(bool solid); bool isAnimated() const; /// @brief update object shape /// @return true if shape changed bool animateCollisionShapes(); private: osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; btVector3 mScale; osg::Vec3f mPosition; osg::Quat mRotation; bool mScaleUpdatePending = false; bool mTransformUpdatePending = false; mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/physicssystem.cpp000066400000000000000000001156111445372753700243720ustar00rootroot00000000000000#include "physicssystem.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // FindRecIndexVisitor #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/movement.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "actor.hpp" #include "projectile.hpp" #include "trace.h" #include "object.hpp" #include "heightfield.hpp" #include "hasspherecollisioncallback.hpp" #include "deepestnotmecontacttestresultcallback.hpp" #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" #include "projectileconvexcallback.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" namespace { void handleJump(const MWWorld::Ptr &ptr) { if (!ptr.getClass().isActor()) return; if (ptr.getClass().getMovementSettings(ptr).mPosition[2] == 0) return; const bool isPlayer = (ptr == MWMechanics::getPlayer()); // Advance acrobatics and set flag for GetPCJumping if (isPlayer) { ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); } // Decrease fatigue if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); } ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; } } namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) : mShapeManager(std::make_unique(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) , mProjectileId(0) , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) , mPhysicsDt(1.f / 60.f) , mActorCollisionShapeType(DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game"))) { mResourceSystem->addResourceManager(mShapeManager.get()); mCollisionConfiguration = std::make_unique(); mDispatcher = std::make_unique(mCollisionConfiguration.get()); mBroadphase = std::make_unique(); mCollisionWorld = std::make_unique(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. mCollisionWorld->setForceUpdateAllAabbs(false); // Check if a user decided to override a physics system FPS const char* env = getenv("OPENMW_PHYSICS_FPS"); if (env) { float physFramerate = std::atof(env); if (physFramerate > 0) { mPhysicsDt = 1.f / physFramerate; Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS)."; } } mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get()); } PhysicsSystem::~PhysicsSystem() { mResourceSystem->removeResourceManager(mShapeManager.get()); if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); mTaskScheduler->releaseSharedStates(); mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); } Resource::BulletShapeManager *PhysicsSystem::getShapeManager() { return mShapeManager.get(); } bool PhysicsSystem::toggleDebugRendering() { mDebugDrawEnabled = !mDebugDrawEnabled; mCollisionWorld->setDebugDrawer(mDebugDrawEnabled ? mDebugDrawer.get() : nullptr); mDebugDrawer->setDebugMode(mDebugDrawEnabled); return mDebugDrawEnabled; } void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) { ObjectMap::iterator found = mObjects.find(ptr.mRef); if (found == mObjects.end()) return; found->second->setSolid(false); } bool PhysicsSystem::isOnSolidGround (const MWWorld::Ptr& actor) const { const Actor* physactor = getActor(actor); if (!physactor || !physactor->getOnGround() || !physactor->getCollisionMode()) return false; const auto obj = physactor->getStandingOnPtr(); if (obj.isEmpty()) return true; // assume standing on terrain (which is a non-object, so not collision tracked) ObjectMap::const_iterator foundObj = mObjects.find(obj.mRef); if (foundObj == mObjects.end()) return false; if (!foundObj->second->isSolid()) return false; return true; } std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orient, float queryDistance, std::vector& targets) { // First of all, try to hit where you aim to int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; RayCastingResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, hitmask, CollisionType_Actor); if (result.mHit) { reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); return std::make_pair(result.mHitObject, result.mHitPos); } // Use cone shape as fallback const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat()/2.0f), queryDistance); shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat()/2.0f) / shape.getRadius())); // The shape origin is its center, so we have to move it forward by half the length. The // real origin will be provided to getFilteredContact to find the closest. osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance*0.5f, 0.0f)); btCollisionObject object; object.setCollisionShape(&shape); object.setWorldTransform(btTransform(Misc::Convert::toBullet(orient), Misc::Convert::toBullet(center))); const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; const Actor* physactor = getActor(actor); if (physactor) me = physactor->getCollisionObject(); if (!targets.empty()) { for (MWWorld::Ptr& target : targets) { const Actor* targetActor = getActor(target); if (targetActor) targetCollisionObjects.push_back(targetActor->getCollisionObject()); } } DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin)); resultCallback.m_collisionFilterGroup = CollisionType_Actor; resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; mTaskScheduler->contactTest(&object, resultCallback); if (resultCallback.mObject) { PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); if (holder) { reportCollision(resultCallback.mContactPoint, resultCallback.mContactNormal); return std::make_pair(holder->getPtr(), Misc::Convert::toOsg(resultCallback.mContactPoint)); } } return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); } float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const { btCollisionObject* targetCollisionObj = nullptr; const Actor* actor = getActor(target); if (actor) targetCollisionObj = actor->getCollisionObject(); if (!targetCollisionObj) return 0.f; btTransform rayFrom; rayFrom.setIdentity(); rayFrom.setOrigin(Misc::Convert::toBullet(point)); auto hitpoint = mTaskScheduler->getHitPoint(rayFrom, targetCollisionObj); if (hitpoint) return (point - Misc::Convert::toOsg(*hitpoint)).length(); // didn't hit the target. this could happen if point is already inside the collision box return 0.f; } RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const { if (from == to) { RayCastingResult result; result.mHit = false; return result; } btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; if (!ignore.isEmpty()) { const Actor* actor = getActor(ignore); if (actor) me = actor->getCollisionObject(); else { const Object* object = getObject(ignore); if (object) me = object->getCollisionObject(); } } if (!targets.empty()) { for (const MWWorld::Ptr& target : targets) { const Actor* actor = getActor(target); if (actor) targetCollisionObjects.push_back(actor->getCollisionObject()); } } ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; mTaskScheduler->rayTest(btFrom, btTo, resultCallback); RayCastingResult result; result.mHit = resultCallback.hasHit(); if (resultCallback.hasHit()) { result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) result.mHitObject = ptrHolder->getPtr(); } return result; } RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius, int mask, int group) const { btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); callback.m_collisionFilterGroup = group; callback.m_collisionFilterMask = mask; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); btTransform from_ (btrot, Misc::Convert::toBullet(from)); btTransform to_ (btrot, Misc::Convert::toBullet(to)); mTaskScheduler->convexSweepTest(&shape, from_, to_, callback); RayCastingResult result; result.mHit = callback.hasHit(); if (result.mHit) { result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld); if (auto* ptrHolder = static_cast(callback.m_hitCollisionObject->getUserPointer())) result.mHitObject = ptrHolder->getPtr(); } return result; } bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { if (actor1 == actor2) return true; const auto it1 = mActors.find(actor1.mRef); const auto it2 = mActors.find(actor2.mRef); if (it1 == mActors.end() || it2 == mActors.end()) return false; return mTaskScheduler->getLineOfSight(it1->second, it2->second); } bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) { Actor* physactor = getActor(actor); return physactor && physactor->getOnGround() && physactor->getCollisionMode(); } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { const auto* physactor = getActor(actor); return physactor && physactor->canMoveToWaterSurface(waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) return physactor->getHalfExtents(); else return osg::Vec3f(); } osg::Vec3f PhysicsSystem::getOriginalHalfExtents(const MWWorld::ConstPtr &actor) const { if (const Actor* physactor = getActor(actor)) return physactor->getOriginalHalfExtents(); else return osg::Vec3f(); } osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) return physactor->getRenderingHalfExtents(); else return osg::Vec3f(); } osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const { const Object * physobject = getObject(object); if (!physobject) return osg::BoundingBox(); btVector3 min, max; mTaskScheduler->getAabb(physobject->getCollisionObject(), min, max); return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max)); } osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) return physactor->getCollisionObjectPosition(); else return osg::Vec3f(); } std::vector PhysicsSystem::getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const { btCollisionObject* me = nullptr; auto found = mObjects.find(ptr.mRef); if (found != mObjects.end()) me = found->second->getCollisionObject(); else return {}; ContactTestResultCallback resultCallback (me); resultCallback.m_collisionFilterGroup = collisionGroup; resultCallback.m_collisionFilterMask = collisionMask; mTaskScheduler->contactTest(me, resultCallback); return resultCallback.mResult; } std::vector PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const { std::vector actors; for (auto& [actor, point, normal] : getCollisionsPoints(ptr, collisionGroup, collisionMask)) actors.emplace_back(actor); return actors; } osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight) { ActorMap::iterator found = mActors.find(ptr.mRef); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } void PhysicsSystem::addHeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject) { mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, size, verts, minH, maxH, holdObject, mTaskScheduler.get()); } void PhysicsSystem::removeHeightField (int x, int y) { HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); if(heightfield != mHeightFields.end()) mHeightFields.erase(heightfield); } const HeightField* PhysicsSystem::getHeightField(int x, int y) const { const auto heightField = mHeightFields.find(std::make_pair(x, y)); if (heightField == mHeightFields.end()) return nullptr; return heightField->second.get(); } void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { if (ptr.mRef->mData.mPhysicsPostponed) return; osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->mCollisionShape) return; assert(!getObject(ptr)); // Override collision type based on shape content. switch (shapeInstance->mVisualCollisionType) { case Resource::VisualCollisionType::None: break; case Resource::VisualCollisionType::Default: collisionType = CollisionType_VisualOnly; break; case Resource::VisualCollisionType::Camera: collisionType = CollisionType_CameraOnly; break; } auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr.mRef, obj); if (obj->isAnimated()) mAnimatedObjects.emplace(obj.get(), false); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { mAnimatedObjects.erase(foundObject->second.get()); mObjects.erase(foundObject); } else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { mActors.erase(foundActor); } } void PhysicsSystem::removeProjectile(const int projectileId) { ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); if (foundProjectile != mProjectiles.end()) mProjectiles.erase(foundProjectile); } void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { if (auto foundObject = mObjects.find(old.mRef); foundObject != mObjects.end()) foundObject->second->updatePtr(updated); else if (auto foundActor = mActors.find(old.mRef); foundActor != mActors.end()) foundActor->second->updatePtr(updated); for (auto& [_, actor] : mActors) { if (actor->getStandingOnPtr() == old) actor->setStandingOnPtr(updated); } for (auto& [_, projectile] : mProjectiles) { if (projectile->getCaster() == old) projectile->setCaster(updated); } } Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) { ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; } const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const { ActorMap::const_iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; } const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const { ObjectMap::const_iterator found = mObjects.find(ptr.mRef); if (found != mObjects.end()) return found->second.get(); return nullptr; } Projectile* PhysicsSystem::getProjectile(int projectileId) const { ProjectileMap::const_iterator found = mProjectiles.find(projectileId); if (found != mProjectiles.end()) return found->second.get(); return nullptr; } void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { float scale = ptr.getCellRef().getScale(); foundObject->second->setScale(scale); mTaskScheduler->updateSingleAabb(foundObject->second); } else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { foundActor->second->updateScale(); mTaskScheduler->updateSingleAabb(foundActor->second); } } void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { foundObject->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundObject->second); } else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { if (!foundActor->second->isRotationallyInvariant()) { foundActor->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundActor->second); } } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { foundObject->second->updatePosition(); mTaskScheduler->updateSingleAabb(foundObject->second); } else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { foundActor->second->updatePosition(); mTaskScheduler->updateSingleAabb(foundActor->second, true); } } void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { osg::ref_ptr shape = mShapeManager->getShape(mesh); // Try to get shape from basic model as fallback for creatures if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.mExtents.length2() == 0) { const std::string fallbackModel = ptr.getClass().getModel(ptr); if (fallbackModel != mesh) { shape = mShapeManager->getShape(fallbackModel); } } if (!shape) return; // check if Actor should spawn above water const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); const bool canWaterWalk = effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk, mActorCollisionShapeType); mActors.emplace(ptr.mRef, std::move(actor)); } int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f; mProjectileId++; auto projectile = std::make_shared(caster, position, radius, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; } void PhysicsSystem::setCaster(int projectileId, const MWWorld::Ptr& caster) { const auto foundProjectile = mProjectiles.find(projectileId); assert(foundProjectile != mProjectiles.end()); auto* projectile = foundProjectile->second.get(); projectile->setCaster(caster); } bool PhysicsSystem::toggleCollisionMode() { ActorMap::iterator found = mActors.find(MWMechanics::getPlayer().mRef); if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); cmode = !cmode; found->second->enableCollisionMode(cmode); // NB: Collision body isn't disabled for vanilla TCL compatibility return cmode; } return false; } void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) { ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) found->second->setVelocity(velocity); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { actor->setVelocity(osg::Vec3f()); actor->setInertialForce(osg::Vec3f()); } } std::vector PhysicsSystem::prepareSimulation(bool willSimulate) { std::vector simulations; simulations.reserve(mActors.size() + mProjectiles.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ref, physicActor] : mActors) { if (!physicActor->isActive()) continue; auto ptr = physicActor->getPtr(); if (!ptr.getClass().isMobile(ptr)) continue; float waterlevel = -std::numeric_limits::max(); const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); const auto& stats = ptr.getClass().getCreatureStats(ptr); const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), ptr.getRefData().getPosition().asVec3())) waterCollision = true; } physicActor->setCanWaterWalk(waterCollision); // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::clamp(effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); simulations.emplace_back(ActorSimulation{physicActor, ActorFrameData{*physicActor, inert, waterCollision, slowFall, waterlevel}}); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) handleJump(ptr); } for (const auto& [id, projectile] : mProjectiles) { simulations.emplace_back(ProjectileSimulation{projectile, ProjectileFrameData{*projectile}}); } return simulations; } void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { for (auto& [animatedObject, changed] : mAnimatedObjects) { if (animatedObject->animateCollisionShapes()) { auto obj = mObjects.find(animatedObject->getPtr().mRef); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); changed = true; } else { changed = false; } } #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); #endif mTimeAccum += dt; if (skipSimulation) mTaskScheduler->resetSimulation(mActors); else { auto simulations = prepareSimulation(mTimeAccum >= mPhysicsDt); // modifies mTimeAccum mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(simulations), frameStart, frameNumber, stats); } } void PhysicsSystem::moveActors() { auto* player = getActor(MWMechanics::getPlayer()); const auto world = MWBase::Environment::get().getWorld(); // copy new ptr position in temporary vector. player is handled separately as its movement might change active cell. std::vector> newPositions; newPositions.reserve(mActors.size() - 1); for (const auto& [ptr, physicActor] : mActors) { if (physicActor.get() == player) continue; newPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition()); } for (auto& [ptr, pos] : newPositions) world->moveObject(ptr, pos, false, false); world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false); } void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) { ObjectMap::iterator found = mObjects.find(object.mRef); if (found != mObjects.end()) if (found->second->animateCollisionShapes()) mTaskScheduler->updateSingleAabb(found->second); } void PhysicsSystem::debugDraw() { if (mDebugDrawEnabled) mTaskScheduler->debugDraw(); } bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { const auto physActor = mActors.find(actor.mRef); if (physActor != mActors.end()) return physActor->second->getStandingOnPtr() == object; return false; } void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const { for (const auto& [_, actor] : mActors) { if (actor->getStandingOnPtr() == object) out.emplace_back(actor->getPtr()); } } bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); } void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr &object, std::vector &out) const { std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); out.insert(out.end(), collisions.begin(), collisions.end()); } void PhysicsSystem::disableWater() { if (mWaterEnabled) { mWaterEnabled = false; updateWater(); } } void PhysicsSystem::enableWater(float height) { if (!mWaterEnabled || mWaterHeight != height) { mWaterEnabled = true; mWaterHeight = height; updateWater(); } } void PhysicsSystem::setWaterHeight(float height) { if (mWaterHeight != height) { mWaterHeight = height; updateWater(); } } void PhysicsSystem::updateWater() { if (mWaterCollisionObject) { mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); } if (!mWaterEnabled) { mWaterCollisionObject.reset(); return; } mWaterCollisionObject = std::make_unique(); mWaterCollisionShape = std::make_unique(btVector3(0,0,1), mWaterHeight); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor|CollisionType_Projectile); } bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const Misc::Span& ignore, std::vector* occupyingActors) const { std::vector ignoredObjects; ignoredObjects.reserve(ignore.size()); for (const auto& v : ignore) if (const auto it = mActors.find(v.mRef); it != mActors.end()) ignoredObjects.push_back(it->second->getCollisionObject()); std::sort(ignoredObjects.begin(), ignoredObjects.end()); ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end()); const auto ignoreFilter = [&] (const btCollisionObject* v) { return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v); }; const auto bulletPosition = Misc::Convert::toBullet(position); const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; const int group = MWPhysics::CollisionType_AnyPhysical; if (occupyingActors == nullptr) { HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, static_cast(nullptr)); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } const auto onCollision = [&] (const btCollisionObject* object) { if (PtrHolder* holder = static_cast(object->getUserPointer())) occupyingActors->push_back(holder->getPtr()); }; HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } void PhysicsSystem::reportStats(unsigned int frameNumber, osg::Stats& stats) const { stats.setAttribute(frameNumber, "Physics Actors", mActors.size()); stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); stats.setAttribute(frameNumber, "Physics Projectiles", mProjectiles.size()); stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); } void PhysicsSystem::reportCollision(const btVector3& position, const btVector3& normal) { if (mDebugDrawEnabled) mDebugDrawer->addCollision(position, normal); } ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) : mPosition() , mStandingOn(nullptr) , mIsOnGround(actor.getOnGround()) , mIsOnSlope(actor.getOnSlope()) , mWalkingOnWater(false) , mInert(inert) , mCollisionObject(actor.getCollisionObject()) , mSwimLevel(waterlevel - (actor.getRenderingHalfExtents().z() * 2 * MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() , mMovement(actor.velocity()) , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) , mWasOnGround(actor.getOnGround()) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(!actor.getCollisionMode()) { } ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) , mMovement(projectile.velocity()) , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) { } WorldFrameData::WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm()) , mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection()) {} LOSRequest::LOSRequest(const std::weak_ptr& a1, const std::weak_ptr& a2) : mResult(false), mStale(false), mAge(0) { // we use raw actor pointer pair to uniquely identify request // sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and getLOS(B, A) auto* raw1 = a1.lock().get(); auto* raw2 = a2.lock().get(); assert(raw1 != raw2); if (raw1 < raw2) { mActors = {a1, a2}; mRawActors = {raw1, raw2}; } else { mActors = {a2, a1}; mRawActors = {raw2, raw1}; } } bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept { return lhs.mRawActors == rhs.mRawActors; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/physicssystem.hpp000066400000000000000000000322201445372753700243710ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/ptr.hpp" #include "collisiontype.hpp" #include "raycasting.hpp" namespace osg { class Group; class Object; class Stats; } namespace MWRender { class DebugDrawer; } namespace Resource { class BulletShapeManager; class ResourceSystem; } class btCollisionWorld; class btBroadphaseInterface; class btDefaultCollisionConfiguration; class btCollisionDispatcher; class btCollisionObject; class btCollisionShape; class btVector3; namespace MWPhysics { class HeightField; class Object; class Actor; class PhysicsTaskScheduler; class Projectile; using ActorMap = std::unordered_map>; struct ContactPoint { MWWorld::Ptr mObject; osg::Vec3f mPoint; osg::Vec3f mNormal; }; struct LOSRequest { LOSRequest(const std::weak_ptr& a1, const std::weak_ptr& a2); std::array, 2> mActors; std::array mRawActors; bool mResult; bool mStale; int mAge; }; bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept; struct ActorFrameData { ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); osg::Vec3f mPosition; osg::Vec3f mInertia; const btCollisionObject* mStandingOn; bool mIsOnGround; bool mIsOnSlope; bool mWalkingOnWater; const bool mInert; btCollisionObject* mCollisionObject; const float mSwimLevel; const float mSlowFall; osg::Vec2f mRotation; osg::Vec3f mMovement; osg::Vec3f mLastStuckPosition; const float mWaterlevel; const float mHalfExtentsZ; float mOldHeight; unsigned int mStuckFrames; const bool mFlying; const bool mWasOnGround; const bool mIsAquatic; const bool mWaterCollision; const bool mSkipCollisionDetection; }; struct ProjectileFrameData { explicit ProjectileFrameData(Projectile& projectile); osg::Vec3f mPosition; osg::Vec3f mMovement; const btCollisionObject* mCaster; const btCollisionObject* mCollisionObject; Projectile* mProjectile; }; struct WorldFrameData { WorldFrameData(); bool mIsInStorm; osg::Vec3f mStormDirection; }; template class SimulationImpl { public: explicit SimulationImpl(const std::weak_ptr& ptr, FrameData&& data) : mPtr(ptr), mData(data) {} std::optional, std::reference_wrapper>> lock() { if (auto locked = mPtr.lock()) return {{std::move(locked), std::ref(mData)}}; return std::nullopt; } private: std::weak_ptr mPtr; FrameData mData; }; using ActorSimulation = SimulationImpl; using ProjectileSimulation = SimulationImpl; using Simulation = std::variant; class PhysicsSystem : public RayCastingInterface { public: PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); virtual ~PhysicsSystem (); Resource::BulletShapeManager* getShapeManager(); void enableWater(float height); void setWaterHeight(float height); void disableWater(); void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void removeProjectile(const int projectileId); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); Actor* getActor(const MWWorld::Ptr& ptr); const Actor* getActor(const MWWorld::ConstPtr& ptr) const; const Object* getObject(const MWWorld::ConstPtr& ptr) const; Projectile* getProjectile(int projectileId) const; // Object or Actor void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject); void removeHeightField (int x, int y); const HeightField* getHeightField(int x, int y) const; bool toggleCollisionMode(); /// Determine new position based on all queued movements, then clear the list. void stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); /// Apply new positions to actors void moveActors(); void debugDraw(); std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with std::vector getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight); std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orientation, float queryDistance, std::vector& targets); /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the /// target vector hits the collision shape and then calculates distance from the intersection point. /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. /// \note Only Actor targets are supported at the moment. float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), int mask = CollisionType_Default, int group=0xff) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, int mask = CollisionType_Default, int group=0xff) const override; /// Return true if actor1 can see actor2. bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const override; bool isOnGround (const MWWorld::Ptr& actor); bool canMoveToWaterSurface (const MWWorld::ConstPtr &actor, const float waterlevel); /// Get physical half extents (scaled) of the given actor. osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; /// Get physical half extents (not scaled) of the given actor. osg::Vec3f getOriginalHalfExtents(const MWWorld::ConstPtr& actor) const; /// @see MWPhysics::Actor::getRenderingHalfExtents osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; /// Get bounding box in world space of the given object. osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Clear the queued movements list without applying. void clearQueuedMovement(); /// Return true if \a actor has been standing on \a object in this frame /// This will trigger whenever the object is directly below the actor. /// It doesn't matter if the actor is stationary or moving. bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; /// Get the handle of all actors standing on \a object in this frame. void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; /// Return true if \a actor has collided with \a object in this frame. /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; /// Get the handle of all actors colliding with \a object in this frame. void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; bool toggleDebugRendering(); /// Mark the given object as a 'non-solid' object. A non-solid object means that /// \a isOnSolidGround will return false for actors standing on that object. void markAsNonSolid (const MWWorld::ConstPtr& ptr); bool isOnSolidGround (const MWWorld::Ptr& actor) const; void updateAnimatedCollisionShape(const MWWorld::Ptr& object); template void forEachAnimatedObject(Function&& function) const { std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const Misc::Span& ignore, std::vector* occupyingActors) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); private: void updateWater(); std::vector prepareSimulation(bool willSimulate); std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration; std::unique_ptr mDispatcher; std::unique_ptr mCollisionWorld; std::unique_ptr mTaskScheduler; std::unique_ptr mShapeManager; Resource::ResourceSystem* mResourceSystem; using ObjectMap = std::unordered_map>; ObjectMap mObjects; std::map mAnimatedObjects; // stores pointers to elements in mObjects ActorMap mActors; using ProjectileMap = std::map>; ProjectileMap mProjectiles; using HeightFieldMap = std::map, std::unique_ptr>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; float mTimeAccum; unsigned int mProjectileId; float mWaterHeight; bool mWaterEnabled; std::unique_ptr mWaterCollisionObject; std::unique_ptr mWaterCollisionShape; std::unique_ptr mDebugDrawer; osg::ref_ptr mParentNode; float mPhysicsDt; DetourNavigator::CollisionShapeType mActorCollisionShapeType; PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/projectile.cpp000066400000000000000000000070521445372753700236020ustar00rootroot00000000000000#include #include #include #include "../mwworld/class.hpp" #include "actor.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" #include "object.hpp" #include "projectile.hpp" namespace MWPhysics { Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mHitWater(false) , mActive(true) , mHitTarget(nullptr) , mPhysics(physicssystem) , mTaskScheduler(scheduler) { mShape = std::make_unique(radius); mConvexShape = static_cast(mShape.get()); mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setUserPointer(this); mPosition = position; mPreviousPosition = position; mSimulationPosition = position; setCaster(caster); const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); updateCollisionObjectPosition(); } Projectile::~Projectile() { if (!mActive) mPhysics->reportCollision(mHitPosition, mHitNormal); mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Projectile::updateCollisionObjectPosition() { std::scoped_lock lock(mMutex); auto& trans = mCollisionObject->getWorldTransform(); trans.setOrigin(Misc::Convert::toBullet(mPosition)); mCollisionObject->setWorldTransform(trans); } void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) { bool active = true; if (!mActive.compare_exchange_strong(active, false, std::memory_order_relaxed) || !active) return; mHitTarget = target; mHitPosition = pos; mHitNormal = normal; } MWWorld::Ptr Projectile::getTarget() const { assert(!mActive); auto* target = static_cast(mHitTarget->getUserPointer()); return target ? target->getPtr() : MWWorld::Ptr(); } MWWorld::Ptr Projectile::getCaster() const { return mCaster; } void Projectile::setCaster(const MWWorld::Ptr& caster) { mCaster = caster; mCasterColObj = [this,&caster]() -> const btCollisionObject* { const Actor* actor = mPhysics->getActor(caster); if (actor) return actor->getCollisionObject(); const Object* object = mPhysics->getObject(caster); if (object) return object->getCollisionObject(); return nullptr; }(); } void Projectile::setValidTargets(const std::vector& targets) { std::scoped_lock lock(mMutex); mValidTargets.clear(); for (const auto& ptr : targets) { const auto* physicActor = mPhysics->getActor(ptr); if (physicActor) mValidTargets.push_back(physicActor->getCollisionObject()); } } bool Projectile::isValidTarget(const btCollisionObject* target) const { assert(target); std::scoped_lock lock(mMutex); if (mCasterColObj == target) return false; if (mValidTargets.empty()) return true; return std::any_of(mValidTargets.begin(), mValidTargets.end(), [target](const btCollisionObject* actor) { return target == actor; }); } } openmw-openmw-0.48.0/apps/openmw/mwphysics/projectile.hpp000066400000000000000000000043441445372753700236100ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PROJECTILE_H #define OPENMW_MWPHYSICS_PROJECTILE_H #include #include #include #include #include "ptrholder.hpp" class btCollisionObject; class btCollisionShape; class btConvexShape; namespace osg { class Vec3f; } namespace Resource { struct BulletShape; } namespace MWPhysics { class PhysicsTaskScheduler; class PhysicsSystem; class Projectile final : public PtrHolder { public: Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } void updateCollisionObjectPosition(); bool isActive() const { return mActive.load(std::memory_order_acquire); } MWWorld::Ptr getTarget() const; MWWorld::Ptr getCaster() const; void setCaster(const MWWorld::Ptr& caster); const btCollisionObject* getCasterCollisionObject() const { return mCasterColObj; } void setHitWater() { mHitWater = true; } bool getHitWater() const { return mHitWater; } void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const btCollisionObject* target) const; btVector3 getHitPosition() const { return mHitPosition; } private: std::unique_ptr mShape; btConvexShape* mConvexShape; bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; const btCollisionObject* mCasterColObj; const btCollisionObject* mHitTarget; btVector3 mHitPosition; btVector3 mHitNormal; std::vector mValidTargets; mutable std::mutex mMutex; PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/projectileconvexcallback.cpp000066400000000000000000000040061445372753700264760ustar00rootroot00000000000000#include #include "../mwworld/class.hpp" #include "actor.hpp" #include "collisiontype.hpp" #include "projectile.hpp" #include "projectileconvexcallback.hpp" #include "ptrholder.hpp" namespace MWPhysics { ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestConvexResultCallback(from, to) , mCaster(caster) , mMe(me) , mProjectile(proj) { assert(mProjectile); } btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { const auto* hitObject = result.m_hitCollisionObject; // don't hit the caster if (hitObject == mCaster) return 1.f; // don't hit the projectile if (hitObject == mMe) return 1.f; btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); switch (hitObject->getBroadphaseHandle()->m_collisionFilterGroup) { case CollisionType_Actor: { if (!mProjectile->isValidTarget(hitObject)) return 1.f; break; } case CollisionType_Projectile: { auto* target = static_cast(hitObject->getUserPointer()); if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) return 1.f; target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: { mProjectile->setHitWater(); break; } } mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/projectileconvexcallback.hpp000066400000000000000000000013751445372753700265110ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H #define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H #include class btCollisionObject; namespace MWPhysics { class Projectile; class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: const btCollisionObject* mCaster; const btCollisionObject* mMe; Projectile* mProjectile; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/ptrholder.hpp000066400000000000000000000032511445372753700234470ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H #include #include #include #include #include #include "../mwworld/ptr.hpp" namespace MWPhysics { class PtrHolder { public: virtual ~PtrHolder() = default; void updatePtr(const MWWorld::Ptr& updated) { mPtr = updated; } MWWorld::Ptr getPtr() const { return mPtr; } btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } osg::Vec3f getSimulationPosition() const { return mSimulationPosition; } void setPosition(const osg::Vec3f& position) { mPreviousPosition = mPosition; mPosition = position; } osg::Vec3d getPosition() const { return mPosition; } osg::Vec3d getPreviousPosition() const { return mPreviousPosition; } protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; osg::Vec3f mVelocity; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/raycasting.hpp000066400000000000000000000033771445372753700236210ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_RAYCASTING_H #define OPENMW_MWPHYSICS_RAYCASTING_H #include #include "../mwworld/ptr.hpp" #include "collisiontype.hpp" namespace MWPhysics { class RayCastingResult { public: bool mHit; osg::Vec3f mHitPos; osg::Vec3f mHitNormal; MWWorld::Ptr mHitObject; }; class RayCastingInterface { public: /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the /// target vector hits the collision shape and then calculates distance from the intersection point. /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. /// \note Only Actor targets are supported at the moment. virtual float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const = 0; /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), int mask = CollisionType_Default, int group=0xff) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, int mask = CollisionType_Default, int group=0xff) const = 0; /// Return true if actor1 can see actor2. virtual bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/stepper.cpp000066400000000000000000000163611445372753700231270ustar00rootroot00000000000000#include "stepper.hpp" #include #include #include #include "collisiontype.hpp" #include "constants.hpp" #include "movementsolver.hpp" namespace MWPhysics { static bool canStepDown(const ActorTracer &stepper) { if (!stepper.mHitObject) return false; static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); if (stepper.mPlaneNormal.z() <= sMaxSlopeCos) return false; return stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor; } Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) : mColWorld(colWorld) , mColObj(colObj) { } bool Stepper::step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration) { if(velocity.x() == 0.0 && velocity.y() == 0.0) return false; // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround); float upDistance = 0; if(!mUpStepper.mHitObject) upDistance = Constants::sStepSizeUp; else if(mUpStepper.mFraction * Constants::sStepSizeUp > sCollisionMargin) upDistance = mUpStepper.mFraction * Constants::sStepSizeUp - sCollisionMargin; else { return false; } auto toMove = velocity * remainingTime; osg::Vec3f tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); osg::Vec3f tracerDest; auto normalMove = toMove; auto moveDistance = normalMove.normalize(); // attempt 1: normal movement // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to avoid a glitch // attempt 3: further, less tall fixed distance movement, same as above // If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets. int attempt = 0; float downStepSize = 0; while(attempt < 3) { attempt++; if(attempt == 1) tracerDest = tracerPos + toMove; else if (!sDoExtraStairHacks) // early out if we have extra hacks disabled { return false; } else if(attempt == 2) { moveDistance = sMinStep; tracerDest = tracerPos + normalMove*sMinStep; } else if(attempt == 3) { if(upDistance > Constants::sStepSizeUp) { upDistance = Constants::sStepSizeUp; tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); } moveDistance = sMinStep2; tracerDest = tracerPos + normalMove*sMinStep2; } mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld); if(mTracer.mHitObject) { // map against what we hit, minus the safety margin moveDistance *= mTracer.mFraction; if(moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything { return false; } moveDistance -= sCollisionMargin; tracerDest = tracerPos + normalMove*moveDistance; // safely eject from what we hit by the safety margin auto tempDest = tracerDest + mTracer.mPlaneNormal*sCollisionMargin*2; ActorTracer tempTracer; tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld); if(tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked sCollisionMargin*2 distance) { auto effectiveFraction = tempTracer.mFraction*2.0f - 1.0f; tracerDest += mTracer.mPlaneNormal*sCollisionMargin*effectiveFraction; } } if(attempt > 2) // do not allow stepping down below original height for attempt 3 downStepSize = upDistance; else downStepSize = moveDistance + upDistance + sStepSizeDown; mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld, onGround); // can't step down onto air, non-walkable-slopes, or actors // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs // (like the bottoms of the staircases in aldruhn's guild of mages) // The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep (10.0) but it caused all sorts of other problems. // Switched back to cylinders to avoid that and similer problems. if(canStepDown(mDownStepper)) { break; } else { // do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large // (forces actor to get snug against the defective ledge for attempt 3 to be tried) if(attempt == 2 && moveDistance > upDistance-(mDownStepper.mFraction*downStepSize)) { return false; } // do next attempt if first iteration of movement solver and not out of attempts if(firstIteration && attempt < 3) { continue; } return false; } } // note: can't downstep onto actors so no need to pick safety margin float downDistance = 0; if(mDownStepper.mFraction*downStepSize > sCollisionMargin) downDistance = mDownStepper.mFraction*downStepSize - sCollisionMargin; if(downDistance-sCollisionMargin-sGroundOffset > upDistance && !onGround) return false; auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance); if((position-newpos).length2() < sCollisionMargin*sCollisionMargin) return false; if(mTracer.mHitObject) { auto planeNormal = mTracer.mPlaneNormal; if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) { planeNormal.z() = 0; planeNormal.normalize(); } velocity = reject(velocity, planeNormal); } velocity = reject(velocity, mDownStepper.mPlaneNormal); position = newpos; remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance return true; } } openmw-openmw-0.48.0/apps/openmw/mwphysics/stepper.hpp000066400000000000000000000011551445372753700231270ustar00rootroot00000000000000#ifndef OPENMW_MWPHYSICS_STEPPER_H #define OPENMW_MWPHYSICS_STEPPER_H #include "trace.h" class btCollisionObject; class btCollisionWorld; namespace osg { class Vec3f; } namespace MWPhysics { class Stepper { private: const btCollisionWorld *mColWorld; const btCollisionObject *mColObj; ActorTracer mTracer, mUpStepper, mDownStepper; public: Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj); bool step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwphysics/trace.cpp000066400000000000000000000123031445372753700225330ustar00rootroot00000000000000#include "trace.h" #include #include #include #include "collisiontype.hpp" #include "actor.hpp" #include "actorconvexcallback.hpp" namespace MWPhysics { ActorConvexCallback sweepHelper(const btCollisionObject *actor, const btVector3& from, const btVector3& to, const btCollisionWorld* world, bool actorFilter) { const btTransform &trans = actor->getWorldTransform(); btTransform transFrom(trans); btTransform transTo(trans); transFrom.setOrigin(from); transTo.setOrigin(to); const btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); const btVector3 motion = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too ActorConvexCallback traceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask traceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; traceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; if(actorFilter) traceCallback.m_collisionFilterMask &= ~CollisionType_Actor; world->convexSweepTest(static_cast(shape), transFrom, transTo, traceCallback); return traceCallback; } void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace) { const btVector3 btstart = Misc::Convert::toBullet(start); btVector3 btend = Misc::Convert::toBullet(end); // Because Bullet's collision trace tests touch *all* geometry in its path, a lot of long collision tests // will unnecessarily test against complex meshes that are dozens of units away. This wouldn't normally be // a problem, but bullet isn't the fastest in the world when it comes to doing tests against triangle meshes. // Therefore, we try out a short trace first, then only fall back to the full length trace if needed. // This trace needs to be at least a couple units long, but there's no one particular ideal length. // The length of 2.1 chosen here is a "works well in practice after testing a few random lengths" value. // (Also, we only do this short test if the intended collision trace is long enough for it to make sense.) const float fallback_length = 2.1f; bool doing_short_trace = false; // For some reason, typical scenes perform a little better if we increase the threshold length for the length test. // (Multiplying by 2 in 'square distance' units gives us about 1.4x the threshold length. In benchmarks this was // slightly better for the performance of normal scenes than 4.0, and just plain better than 1.0.) if(attempt_short_trace && (btend-btstart).length2() > fallback_length*fallback_length*2.0) { btend = btstart + (btend-btstart).normalized()*fallback_length; doing_short_trace = true; } const auto traceCallback = sweepHelper(actor, btstart, btend, world, false); // Copy the hit data over to our trace results struct: if(traceCallback.hasHit()) { mFraction = traceCallback.m_closestHitFraction; // ensure fraction is correct (covers intended distance traveled instead of actual distance traveled) if(doing_short_trace && (end-start).length2() > 0.0) mFraction *= (btend-btstart).length() / (end-start).length(); mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld); mHitObject = traceCallback.m_hitCollisionObject; } else { if(doing_short_trace) { btend = Misc::Convert::toBullet(end); const auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); if(newTraceCallback.hasHit()) { mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); mHitObject = newTraceCallback.m_hitCollisionObject; return; } } // fallthrough mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; mHitPoint = end; mHitObject = nullptr; } } void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { const auto traceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); if(traceCallback.hasHit()) { mFraction = traceCallback.m_closestHitFraction; mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; } else { mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; } } } openmw-openmw-0.48.0/apps/openmw/mwphysics/trace.h000066400000000000000000000012441445372753700222020ustar00rootroot00000000000000#ifndef OENGINE_BULLET_TRACE_H #define OENGINE_BULLET_TRACE_H #include class btCollisionObject; class btCollisionWorld; namespace MWPhysics { class Actor; struct ActorTracer { osg::Vec3f mEndPos; osg::Vec3f mPlaneNormal; osg::Vec3f mHitPoint; const btCollisionObject* mHitObject; float mFraction; void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace = false); void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/000077500000000000000000000000001445372753700205275ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwrender/.gitignore000066400000000000000000000000041445372753700225110ustar00rootroot00000000000000old openmw-openmw-0.48.0/apps/openmw/mwrender/actoranimation.cpp000066400000000000000000000521271445372753700242520ustar00rootroot00000000000000#include "actoranimation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/creaturestats.hpp" #include "vismask.hpp" namespace MWRender { ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : Animation(ptr, parentNode, resourceSystem) { MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr); for (MWWorld::ConstContainerStoreIterator iter = store.cbegin(MWWorld::ContainerStore::Type_Light); iter != store.cend(); ++iter) { const ESM::Light* light = iter->get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) { addHiddenItemLight(*iter, light); } } // Make sure we cleaned object from effects, just in cast if we re-use node removeEffects(); } ActorAnimation::~ActorAnimation() { removeFromSceneImpl(); } PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) { osg::Group* parent = getBoneByName(bonename); if (!parent) return nullptr; osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) return PartHolderPtr(); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(instance)); } osg::ref_ptr ActorAnimation::attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight) { osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); const NodeMap& nodeMap = getNodeMap(); auto found = nodeMap.find(bonename); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); if(isLight) { osg::Quat rotation(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0)); return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); } return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); } std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const { const ESM::Armor *armor = shield.get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; // Try to recover the body part model, use ground model as a fallback otherwise. if (!bodyparts.empty()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); for (const auto& part : bodyparts) { if (part.mPart != ESM::PRT_Shield) continue; std::string bodypartName; if (female && !part.mFemale.empty()) bodypartName = part.mFemale; else if (!part.mMale.empty()) bodypartName = part.mMale; if (!bodypartName.empty()) { const ESM::BodyPart *bodypart = partStore.search(bodypartName); if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) return std::string(); if (!bodypart->mModel.empty()) return Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); } } } return shield.getClass().getModel(shield); } std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const { std::string mesh = getShieldMesh(shield, false); if (mesh.empty()) return mesh; std::string holsteredName = mesh; holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); if(mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); shieldTemplate->accept(findVisitor); osg::ref_ptr sheathNode = findVisitor.mFoundNode; if(!sheathNode) return std::string(); } return mesh; } bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const { static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (shieldSheathing) { const MWWorld::Class &cls = mPtr.getClass(); MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing) { SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); mObjectRoot->accept(findVisitor); if (findVisitor.mFoundNode) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) return false; } } } return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); } void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) { static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (!shieldSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) return; mHolsteredShield.reset(); if (showCarriedLeft) return; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) return; // Can not show holdstered shields with two-handed weapons at all const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end()) return; auto type = weapon->getType(); if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded) return; } std::string mesh = getSheathedShieldMesh(*shield); if (mesh.empty()) return; std::string boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); std::string holsteredName = mesh; holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. if (!mResourceSystem->getVFS()->exists(holsteredName)) mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); else mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); if (!mHolsteredShield) return; SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); mHolsteredShield->getNode()->accept(findVisitor); osg::Group* shieldNode = findVisitor.mFoundNode; // If mesh author declared an empty sheath node, use transformation from this node, but use the common shield mesh. // This approach allows to tweak shield position without need to store the whole shield mesh in the _sh file. if (shieldNode && !shieldNode->getNumChildren()) { osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode); if (isEnchanted) SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); } } bool ActorAnimation::useShieldAnimations() const { static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (!shieldSheathing) return false; const MWWorld::Class &cls = mPtr.getClass(); if (!cls.hasInventoryStore(mPtr)) return false; if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0) return false; const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (weapon != inv.end() && shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) { auto type = weapon->getType(); if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); } else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) return true; } return false; } osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) const { if (!mObjectRoot) return nullptr; SceneUtil::FindByNameVisitor findVisitor (boneName); mObjectRoot->accept(findVisitor); return findVisitor.mFoundNode; } std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) { std::string boneName; if(weapon.isEmpty()) return boneName; auto type = weapon.getClass().getType(); if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon.get(); int weaponType = ref->mBase->mData.mType; return MWMechanics::getWeaponType(weaponType)->mSheathingBone; } return boneName; } void ActorAnimation::resetControllers(osg::Node* node) { if (node == nullptr) return; SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared()); node->accept(removeVisitor); } void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) return; mScabbard.reset(); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; // Since throwing weapons stack themselves, do not show such weapon itself int type = weapon->get()->mBase->mData.mType; if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) showHolsteredWeapons = false; std::string mesh = weapon->getClass().getModel(*weapon); std::string scabbardName = mesh; std::string boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use a weapon mesh as fallback. // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. // We use the similar approach for other bodyparts. scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if(!mResourceSystem->getVFS()->exists(scabbardName)) { if (showHolsteredWeapons) { osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor); if (mScabbard) resetControllers(mScabbard->getNode()); } return; } mScabbard = attachMesh(scabbardName, boneName); if (mScabbard) resetControllers(mScabbard->getNode()); osg::Group* weaponNode = getBoneByName("Bip01 Weapon"); if (!weaponNode) return; // When we draw weapon, hide the Weapon node from sheath model. // Otherwise add the enchanted glow to it. if (!showHolsteredWeapons) { weaponNode->setNodeMask(0); } else { // If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh. // This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file. if (!weaponNode->getNumChildren()) { osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); resetControllers(fallbackNode); } if (isEnchanted) { osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor); } } } void ActorAnimation::updateQuiver() { static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) return; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; std::string mesh = weapon->getClass().getModel(*weapon); std::string boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; osg::Group* ammoNode = getBoneByName("Bip01 Ammo"); if (!ammoNode) return; // Special case for throwing weapons - they do not use ammo, but they stack themselves bool suitableAmmo = false; MWWorld::ConstContainerStoreIterator ammo = weapon; unsigned int ammoCount = 0; int type = weapon->get()->mBase->mData.mType; const auto& weaponType = MWMechanics::getWeaponType(type); if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { ammoCount = ammo->getRefData().getCount(); osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; suitableAmmo = true; } else { ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; ammoCount = ammo->getRefData().getCount(); bool arrowAttached = isArrowAttached(); if (arrowAttached) ammoCount--; suitableAmmo = ammo->get()->mBase->mData.mType == weaponType->mAmmoType; } if (!suitableAmmo) return; // We should not show more ammo than equipped and more than quiver mesh has ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); // Remove existing ammo nodes for (unsigned int i=0; igetNumChildren(); ++i) { osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); if (!arrowNode->getNumChildren()) continue; osg::ref_ptr arrowChildNode = arrowNode->getChild(0); arrowNode->removeChild(arrowChildNode); } // Add new ones osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo); std::string model = ammo->getClass().getModel(*ammo); for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); if (!ammo->getClass().getEnchantment(*ammo).empty()) mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor); } } void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) { if (item.getType() == ESM::Light::sRecordId) { const ESM::Light* light = item.get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) { addHiddenItemLight(item, light); } } if (!mPtr.getClass().hasInventoryStore(mPtr)) return; // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); int type = weapon->get()->mBase->mData.mType; if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) updateQuiver(); } void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) { if (item.getType() == ESM::Light::sRecordId) { ItemLightMap::iterator iter = mItemLights.find(item); if (iter != mItemLights.end()) { if (!item.getRefData().getCount()) { removeHiddenItemLight(item); } } } if (!mPtr.getClass().hasInventoryStore(mPtr)) return; // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); int type = weapon->get()->mBase->mData.mType; if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) updateQuiver(); } void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight) { if (mItemLights.find(item) != mItemLights.end()) return; bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); osg::Vec4f ambient(1,1,1,1); osg::ref_ptr lightSource = SceneUtil::createLightSource(esmLight, Mask_Lighting, exterior, ambient); mInsert->addChild(lightSource); if (mLightListCallback && mPtr == MWMechanics::getPlayer()) mLightListCallback->getIgnoredLightSources().insert(lightSource.get()); mItemLights.insert(std::make_pair(item, lightSource)); } void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item) { ItemLightMap::iterator iter = mItemLights.find(item); if (iter == mItemLights.end()) return; if (mLightListCallback && mPtr == MWMechanics::getPlayer()) { std::set::iterator ignoredIter = mLightListCallback->getIgnoredLightSources().find(iter->second.get()); if (ignoredIter != mLightListCallback->getIgnoredLightSources().end()) mLightListCallback->getIgnoredLightSources().erase(ignoredIter); } mInsert->removeChild(iter->second); mItemLights.erase(iter); } void ActorAnimation::removeFromScene() { removeFromSceneImpl(); Animation::removeFromScene(); } void ActorAnimation::removeFromSceneImpl() { for (const auto& [k, v] : mItemLights) mInsert->removeChild(v); } } openmw-openmw-0.48.0/apps/openmw/mwrender/actoranimation.hpp000066400000000000000000000047331445372753700242570ustar00rootroot00000000000000#ifndef GAME_RENDER_ACTORANIMATION_H #define GAME_RENDER_ACTORANIMATION_H #include #include #include "../mwworld/containerstore.hpp" #include "animation.hpp" namespace osg { class Node; } namespace MWWorld { class ConstPtr; } namespace SceneUtil { class LightSource; class LightListCallback; } namespace MWRender { class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener { public: ActorAnimation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~ActorAnimation(); void itemAdded(const MWWorld::ConstPtr& item, int count) override; void itemRemoved(const MWWorld::ConstPtr& item, int count) override; virtual bool isArrowAttached() const { return false; } bool useShieldAnimations() const override; bool updateCarriedLeftVisible(const int weaptype) const override; void removeFromScene() override; protected: osg::Group* getBoneByName(const std::string& boneName) const; virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename) { osg::Vec4f stubColor = osg::Vec4f(0,0,0,0); return attachMesh(model, bonename, false, &stubColor); }; osg::ref_ptr attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight); PartHolderPtr mScabbard; PartHolderPtr mHolsteredShield; private: void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight); void removeHiddenItemLight(const MWWorld::ConstPtr& item); void resetControllers(osg::Node* node); void removeFromSceneImpl(); typedef std::map > ItemLightMap; ItemLightMap mItemLights; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/actorspaths.cpp000066400000000000000000000057071445372753700235770ustar00rootroot00000000000000#include "actorspaths.hpp" #include "vismask.hpp" #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include namespace MWRender { ActorsPaths::ActorsPaths(const osg::ref_ptr& root, bool enabled) : mRootNode(root) , mEnabled(enabled) { } ActorsPaths::~ActorsPaths() { if (mEnabled) disable(); } bool ActorsPaths::toggle() { if (mEnabled) disable(); else enable(); return mEnabled; } void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::Settings& settings) { if (!mEnabled) return; const auto group = mGroups.find(actor.mRef); if (group != mGroups.end()) mRootNode->removeChild(group->second.mNode); auto newGroup = SceneUtil::createAgentPathGroup(path, agentBounds, start, end, settings.mRecast); if (newGroup) { MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); newGroup->setNodeMask(Mask_Debug); mRootNode->addChild(newGroup); mGroups[actor.mRef] = Group {actor.mCell, std::move(newGroup)}; } } void ActorsPaths::remove(const MWWorld::ConstPtr& actor) { const auto group = mGroups.find(actor.mRef); if (group != mGroups.end()) { mRootNode->removeChild(group->second.mNode); mGroups.erase(group); } } void ActorsPaths::removeCell(const MWWorld::CellStore* const store) { for (auto it = mGroups.begin(); it != mGroups.end(); ) { if (it->second.mCell == store) { mRootNode->removeChild(it->second.mNode); it = mGroups.erase(it); } else ++it; } } void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { const auto it = mGroups.find(old.mRef); if (it == mGroups.end()) return; it->second.mCell = updated.mCell; } void ActorsPaths::enable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const Groups::value_type& v) { mRootNode->addChild(v.second.mNode); }); mEnabled = true; } void ActorsPaths::disable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const Groups::value_type& v) { mRootNode->removeChild(v.second.mNode); }); mEnabled = false; } } openmw-openmw-0.48.0/apps/openmw/mwrender/actorspaths.hpp000066400000000000000000000024741445372753700236020ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_AGENTSPATHS_H #define OPENMW_MWRENDER_AGENTSPATHS_H #include #include #include #include #include namespace osg { class Group; } namespace DetourNavigator { struct Settings; struct AgentBounds; } namespace MWRender { class ActorsPaths { public: ActorsPaths(const osg::ref_ptr& root, bool enabled); ~ActorsPaths(); bool toggle(); void update(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::Settings& settings); void remove(const MWWorld::ConstPtr& actor); void removeCell(const MWWorld::CellStore* const store); void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated); void enable(); void disable(); private: struct Group { const MWWorld::CellStore* mCell; osg::ref_ptr mNode; }; using Groups = std::map; osg::ref_ptr mRootNode; Groups mGroups; bool mEnabled; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/animation.cpp000066400000000000000000001770661445372753700232330ustar00rootroot00000000000000#include "animation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority #include "vismask.hpp" #include "util.hpp" #include "rotatecontroller.hpp" namespace { class MarkDrawablesVisitor : public osg::NodeVisitor { public: MarkDrawablesVisitor(osg::Node::NodeMask mask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mMask(mask) { } void apply(osg::Drawable& drawable) override { drawable.setNodeMask(mMask); } private: osg::Node::NodeMask mMask = 0; }; /// Removes all particle systems and related nodes in a subgraph. class RemoveParticlesVisitor : public osg::NodeVisitor { public: RemoveParticlesVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Node &node) override { if (dynamic_cast(&node)) mToRemove.emplace_back(&node); traverse(node); } void apply(osg::Drawable& drw) override { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) mToRemove.emplace_back(partsys); } void remove() { for (osg::Node* node : mToRemove) { // FIXME: a Drawable might have more than one parent if (node->getNumParents()) node->getParent(0)->removeChild(node); } mToRemove.clear(); } private: std::vector > mToRemove; }; class DayNightCallback : public SceneUtil::NodeCallback { public: DayNightCallback() : mCurrentState(0) { } void operator()(osg::Switch* node, osg::NodeVisitor* nv) { unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); const unsigned int newState = node->getNumChildren() > state ? state : 0; if (newState != mCurrentState) { mCurrentState = newState; node->setSingleChildOn(mCurrentState); } traverse(node, nv); } private: unsigned int mCurrentState; }; class AddSwitchCallbacksVisitor : public osg::NodeVisitor { public: AddSwitchCallbacksVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Switch &switchNode) override { if (switchNode.getName() == Constants::NightDayLabel) switchNode.addUpdateCallback(new DayNightCallback()); traverse(switchNode); } }; class HarvestVisitor : public osg::NodeVisitor { public: HarvestVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Switch& node) override { if (node.getName() == Constants::HerbalismLabel) { node.setSingleChildOn(1); } traverse(node); } }; float calcAnimVelocity(const SceneUtil::TextKeyMap& keys, SceneUtil::KeyframeController *nonaccumctrl, const osg::Vec3f& accum, const std::string &groupname) { const std::string start = groupname+": start"; const std::string loopstart = groupname+": loop start"; const std::string loopstop = groupname+": loop stop"; const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; // Pick the last Loop Stop key and the last Loop Start key. // This is required because of broken text keys in AshVampire.nif. // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback // but the animation velocity calculation uses the second one. // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. auto keyiter = keys.rbegin(); while(keyiter != keys.rend()) { if(keyiter->second == start || keyiter->second == loopstart) { starttime = keyiter->first; break; } ++keyiter; } keyiter = keys.rbegin(); while(keyiter != keys.rend()) { if (keyiter->second == stop) stoptime = keyiter->first; else if (keyiter->second == loopstop) { stoptime = keyiter->first; break; } ++keyiter; } if(stoptime > starttime) { osg::Vec3f startpos = osg::componentMultiply(nonaccumctrl->getTranslation(starttime), accum); osg::Vec3f endpos = osg::componentMultiply(nonaccumctrl->getTranslation(stoptime), accum); return (startpos-endpos).length() / (stoptime - starttime); } return 0.0f; } class GetExtendedBonesVisitor : public osg::NodeVisitor { public: GetExtendedBonesVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Node& node) override { if (SceneUtil::hasUserDescription(&node, "CustomBone")) { mFoundBones.emplace_back(&node, node.getParent(0)); return; } traverse(node); } std::vector > mFoundBones; }; class RemoveFinishedCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; RemoveFinishedCallbackVisitor() : RemoveVisitor() , mHasMagicEffects(false) { } void apply(osg::Node &node) override { traverse(node); } void apply(osg::Group &group) override { traverse(group); osg::Callback* callback = group.getUpdateCallback(); if (callback) { // We should remove empty transformation nodes and finished callbacks here MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { if (vfxCallback->mFinished) mToRemove.emplace_back(group.asNode(), group.getParent(0)); else mHasMagicEffects = true; } } } void apply(osg::MatrixTransform &node) override { traverse(node); } void apply(osg::Geometry&) override { } }; class RemoveCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; RemoveCallbackVisitor() : RemoveVisitor() , mHasMagicEffects(false) , mEffectId(-1) { } RemoveCallbackVisitor(int effectId) : RemoveVisitor() , mHasMagicEffects(false) , mEffectId(effectId) { } void apply(osg::Node &node) override { traverse(node); } void apply(osg::Group &group) override { traverse(group); osg::Callback* callback = group.getUpdateCallback(); if (callback) { MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { bool toRemove = mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId; if (toRemove) mToRemove.emplace_back(group.asNode(), group.getParent(0)); else mHasMagicEffects = true; } } } void apply(osg::MatrixTransform &node) override { traverse(node); } void apply(osg::Geometry&) override { } private: int mEffectId; }; class FindVfxCallbacksVisitor : public osg::NodeVisitor { public: std::vector mCallbacks; FindVfxCallbacksVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mEffectId(-1) { } FindVfxCallbacksVisitor(int effectId) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mEffectId(effectId) { } void apply(osg::Node &node) override { traverse(node); } void apply(osg::Group &group) override { osg::Callback* callback = group.getUpdateCallback(); if (callback) { MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId) { mCallbacks.push_back(vfxCallback); } } } traverse(group); } void apply(osg::MatrixTransform &node) override { traverse(node); } void apply(osg::Geometry&) override { } private: int mEffectId; }; osg::ref_ptr getVFXLightModelInstance() { static osg::ref_ptr lightModel = nullptr; if (!lightModel) { lightModel = new osg::LightModel; lightModel->setAmbientIntensity({1,1,1,1}); } return lightModel; } } namespace MWRender { class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: TransparencyUpdater(const float alpha) : mAlpha(alpha) { } void setAlpha(const float alpha) { mAlpha = alpha; } protected: void setDefaults(osg::StateSet* stateset) override { osg::BlendFunc* blendfunc (new osg::BlendFunc); stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; material->setColorMode(osg::Material::OFF); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,mAlpha)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha); } private: float mAlpha; }; struct Animation::AnimSource { osg::ref_ptr mKeyframes; typedef std::map > ControllerMap; ControllerMap mControllerMap[Animation::sNumBlendMasks]; const SceneUtil::TextKeyMap& getTextKeys() const; }; void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { traverse(node, nv); if (mFinished) return; double newTime = nv->getFrameStamp()->getSimulationTime(); if (mStartingTime == 0) { mStartingTime = newTime; return; } double duration = newTime - mStartingTime; mStartingTime = newTime; mParams.mAnimTime->addTime(duration); if (mParams.mAnimTime->getTime() >= mParams.mMaxControllerLength) { if (mParams.mLoop) { // Start from the beginning again; carry over the remainder // Not sure if this is actually needed, the controller function might already handle loops float remainder = mParams.mAnimTime->getTime() - mParams.mMaxControllerLength; mParams.mAnimTime->resetTime(remainder); } else { // Hide effect immediately node->setNodeMask(0); mFinished = true; } } } class ResetAccumRootCallback : public SceneUtil::NodeCallback { public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); mat.setTrans(position); transform->setMatrix(mat); traverse(transform, nv); } void setAccumulate(const osg::Vec3f& accumulate) { // anything that accumulates (1.f) should be reset in the callback to (0.f) mResetAxes.x() = accumulate.x() != 0.f ? 0.f : 1.f; mResetAxes.y() = accumulate.y() != 0.f ? 0.f : 1.f; mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } private: osg::Vec3f mResetAxes; }; Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) , mSkeleton(nullptr) , mNodeMapCreated(false) , mPtr(ptr) , mResourceSystem(resourceSystem) , mAccumulate(1.f, 1.f, 0.f) , mTextKeyListener(nullptr) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) , mUpperBodyYawRadians(0.f) , mLegsYawRadians(0.f) , mBodyPitchRadians(0.f) , mHasMagicEffects(false) , mAlpha(1.f) { for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i] = std::make_shared(); mLightListCallback = new SceneUtil::LightListCallback; } Animation::~Animation() { removeFromSceneImpl(); } void Animation::setActive(int active) { if (mSkeleton) mSkeleton->setActive(static_cast(active)); } void Animation::updatePtr(const MWWorld::Ptr &ptr) { mPtr = ptr; } void Animation::setAccumulation(const osg::Vec3f& accum) { mAccumulate = accum; if (mResetAccumRootCallback) mResetAccumRootCallback->setAccumulate(mAccumulate); } size_t Animation::detectBlendMask(const osg::Node* node) const { static const char sBlendMaskRoots[sNumBlendMasks][32] = { "", /* Lower body / character root */ "Bip01 Spine1", /* Torso */ "Bip01 L Clavicle", /* Left arm */ "Bip01 R Clavicle", /* Right arm */ }; while(node != mObjectRoot) { const std::string &name = node->getName(); for(size_t i = 1;i < sNumBlendMasks;i++) { if(name == sBlendMaskRoots[i]) return i; } assert(node->getNumParents() > 0); node = node->getParent(0); } return 0; } const SceneUtil::TextKeyMap &Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; } void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel) { std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } animationPath.replace(animationPath.size()-3, 3, "/"); for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { if (Misc::getFileExtension(name) == "kf") addSingleAnimSource(name, baseModel); } } void Animation::addAnimSource(const std::string &model, const std::string& baseModel) { std::string kfname = model; Misc::StringUtils::lowerCaseInPlace(kfname); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); addSingleAnimSource(kfname, baseModel); static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); if (useAdditionalSources) loadAllAnimationsInFolder(kfname, baseModel); } void Animation::addSingleAnimSource(const std::string &kfname, const std::string& baseModel) { if(!mResourceSystem->getVFS()->exists(kfname)) return; auto animsrc = std::make_shared(); animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) return; const NodeMap& nodeMap = getNodeMap(); const auto& controllerMap = animsrc->mKeyframes->mKeyframeControllers; for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); it != controllerMap.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) { Log(Debug::Warning) << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")"; continue; } osg::Node* node = found->second; size_t blendMask = detectBlendMask(node); // clone the controller, because each Animation needs its own ControllerSource osg::ref_ptr cloned = osg::clone(it->second.get(), osg::CopyOp::SHALLOW_COPY); cloned->setSource(mAnimationTimePtr[blendMask]); animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); } mAnimSources.push_back(std::move(animsrc)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); mObjectRoot->accept(assignVisitor); // Determine the movement accumulation bone if necessary if (!mAccumRoot) { // Priority matters! bip01 is preferred. static const std::array accumRootNames = { "bip01", "root bone" }; NodeMap::const_iterator found = nodeMap.end(); for (const std::string& name : accumRootNames) { found = nodeMap.find(name); if (found == nodeMap.end()) continue; for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); it != controllerMap.end(); ++it) { if (Misc::StringUtils::lowerCase(it->first) == name) { mAccumRoot = found->second; break; } } if (mAccumRoot) break; } } } void Animation::clearAnimSources() { mStates.clear(); for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i]->setTimePtr(std::shared_ptr()); mAccumCtrl = nullptr; mAnimSources.clear(); mAnimVelocities.clear(); } bool Animation::hasAnimation(std::string_view anim) const { AnimSourceList::const_iterator iter(mAnimSources.begin()); for(;iter != mAnimSources.end();++iter) { const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); if (keys.hasGroupStart(anim)) return true; } return false; } float Animation::getStartTime(const std::string &groupname) const { for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); const auto found = keys.findGroupStart(groupname); if(found != keys.end()) return found->first; } return -1.f; } float Animation::getTextKeyTime(const std::string &textKey) const { for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); for(auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey) { if(iterKey->second.compare(0, textKey.size(), textKey) == 0) return iterKey->first; } } return -1.f; } void Animation::handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { const std::string &evt = key->second; size_t off = groupname.size()+2; size_t len = evt.size() - off; if(evt.compare(0, groupname.size(), groupname) == 0 && evt.compare(groupname.size(), 2, ": ") == 0) { if(evt.compare(off, len, "loop start") == 0) state.mLoopStartTime = key->first; else if(evt.compare(off, len, "loop stop") == 0) state.mLoopStopTime = key->first; } if (mTextKeyListener) { try { mTextKeyListener->handleTextKey(groupname, key, map); } catch (std::exception& e) { Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what(); } } } void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) { if(!mObjectRoot || mAnimSources.empty()) return; if(groupname.empty()) { resetActiveGroups(); return; } AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { if(stateiter->second.mPriority == priority) mStates.erase(stateiter++); else ++stateiter; } stateiter = mStates.find(groupname); if(stateiter != mStates.end()) { stateiter->second.mPriority = priority; resetActiveGroups(); return; } /* Look in reverse; last-inserted source has priority. */ AnimState state; AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); for(;iter != mAnimSources.rend();++iter) { const SceneUtil::TextKeyMap &textkeys = (*iter)->getTextKeys(); if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) { state.mSource = *iter; state.mSpeedMult = speedmult; state.mLoopCount = loops; state.mPlaying = (state.getTime() < state.mStopTime); state.mPriority = priority; state.mBlendMask = blendMask; state.mAutoDisable = autodisable; mStates[groupname] = state; if (state.mPlaying) { auto textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) { state.mLoopCount--; state.setTime(state.mLoopStartTime); state.mPlaying = true; if(state.getTime() >= state.mLoopStopTime) break; auto textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } break; } } resetActiveGroups(); } bool Animation::reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. auto groupend = keys.rbegin(); for(;groupend != keys.rend();++groupend) { if(groupend->second.compare(0, groupname.size(), groupname) == 0 && groupend->second.compare(groupname.size(), 2, ": ") == 0) break; } std::string starttag = groupname+": "+start; auto startkey = groupend; while(startkey != keys.rend() && startkey->second != starttag) ++startkey; if(startkey == keys.rend() && start == "loop start") { starttag = groupname+": start"; startkey = groupend; while(startkey != keys.rend() && startkey->second != starttag) ++startkey; } if(startkey == keys.rend()) return false; const std::string stoptag = groupname+": "+stop; auto stopkey = groupend; while(stopkey != keys.rend() // We have to ignore extra garbage at the end. // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". // Why, just why? :( && (stopkey->second.size() < stoptag.size() || stopkey->second.compare(0,stoptag.size(), stoptag) != 0)) ++stopkey; if(stopkey == keys.rend()) return false; if(startkey->first > stopkey->first) return false; state.mStartTime = startkey->first; if (loopfallback) { state.mLoopStartTime = startkey->first; state.mLoopStopTime = stopkey->first; } else { state.mLoopStartTime = startkey->first; state.mLoopStopTime = std::numeric_limits::max(); } state.mStopTime = stopkey->first; state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint)); // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now. const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; auto key = groupend; for (; key != startkey && key != keys.rend(); ++key) { if (key->first > state.getTime()) continue; if (key->second == loopstarttag) state.mLoopStartTime = key->first; else if (key->second == loopstoptag) state.mLoopStopTime = key->first; } return true; } void Animation::setTextKeyListener(Animation::TextKeyListener *listener) { mTextKeyListener = listener; } const Animation::NodeMap &Animation::getNodeMap() const { if (!mNodeMapCreated && mObjectRoot) { SceneUtil::NodeMapVisitor visitor(mNodeMap); mObjectRoot->accept(visitor); mNodeMapCreated = true; } return mNodeMap; } void Animation::resetActiveGroups() { // remove all previous external controllers from the scene graph for (auto it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) { osg::Node* node = it->first; node->removeUpdateCallback(it->second); // Should be no longer needed with OSG 3.4 it->second->setNestedCallback(nullptr); } mActiveControllers.clear(); mAccumCtrl = nullptr; for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++) { AnimStateMap::const_iterator active = mStates.end(); AnimStateMap::const_iterator state = mStates.begin(); for(;state != mStates.end();++state) { if(!(state->second.mBlendMask&(1<second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask]) active = state; } mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? std::shared_ptr() : active->second.mTime); // add external controllers for the AnimSource active in this blend mask if (active != mStates.end()) { std::shared_ptr animsrc = active->second.mSource; for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource osg::Callback* callback = it->second->getAsCallback(); node->addUpdateCallback(callback); mActiveControllers.emplace_back(node, callback); if (blendMask == 0 && node == mAccumRoot) { mAccumCtrl = it->second; // make sure reset is last in the chain of callbacks if (!mResetAccumRootCallback) { mResetAccumRootCallback = new ResetAccumRootCallback; mResetAccumRootCallback->setAccumulate(mAccumulate); } mAccumRoot->addUpdateCallback(mResetAccumRootCallback); mActiveControllers.emplace_back(mAccumRoot, mResetAccumRootCallback); } } } } addControllers(); } void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) { AnimStateMap::iterator state(mStates.find(groupname)); if(state != mStates.end()) state->second.mSpeedMult = speedmult; } bool Animation::isPlaying(const std::string &groupname) const { AnimStateMap::const_iterator state(mStates.find(groupname)); if(state != mStates.end()) return state->second.mPlaying; return false; } bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) { if(complete) *complete = 0.0f; if(speedmult) *speedmult = 0.0f; return false; } if(complete) { if(iter->second.mStopTime > iter->second.mStartTime) *complete = (iter->second.getTime() - iter->second.mStartTime) / (iter->second.mStopTime - iter->second.mStartTime); else *complete = (iter->second.mPlaying ? 0.0f : 1.0f); } if(speedmult) *speedmult = iter->second.mSpeedMult; return true; } float Animation::getCurrentTime(const std::string &groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) return -1.f; return iter->second.getTime(); } size_t Animation::getCurrentLoopCount(const std::string& groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) return 0; return iter->second.mLoopCount; } void Animation::disable(const std::string &groupname) { AnimStateMap::iterator iter = mStates.find(groupname); if(iter != mStates.end()) mStates.erase(iter); resetActiveGroups(); } float Animation::getVelocity(const std::string &groupname) const { if (!mAccumRoot) return 0.0f; std::map::const_iterator found = mAnimVelocities.find(groupname); if (found != mAnimVelocities.end()) return found->second; // Look in reverse; last-inserted source has priority. AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); for(;animsrc != mAnimSources.rend();++animsrc) { const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); if (keys.hasGroupStart(groupname)) break; } if(animsrc == mAnimSources.rend()) return 0.0f; float velocity = 0.0f; const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); const AnimSource::ControllerMap& ctrls = (*animsrc)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName())) { velocity = calcAnimVelocity(keys, it->second, mAccumulate, groupname); break; } } // If there's no velocity, keep looking if(!(velocity > 1.0f)) { AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); while(*animiter != *animsrc) ++animiter; while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) { const SceneUtil::TextKeyMap &keys2 = (*animiter)->getTextKeys(); const AnimSource::ControllerMap& ctrls2 = (*animiter)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls2.begin(); it != ctrls2.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName())) { velocity = calcAnimVelocity(keys2, it->second, mAccumulate, groupname); break; } } } } mAnimVelocities.insert(std::make_pair(groupname, velocity)); return velocity; } void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) { // Get the difference from the last update, and move the position osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); } osg::Vec3f Animation::runAnimation(float duration) { // If we have scripted animations, play only them bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying) { hasScriptedAnims = true; break; } } osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { AnimState &state = stateiter->second; if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) { ++stateiter; continue; } const SceneUtil::TextKeyMap &textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); float timepassed = duration * state.mSpeedMult; while(state.mPlaying) { if (!state.shouldLoop()) { float targetTime = state.getTime() + timepassed; if(textkey == textkeys.end() || textkey->first > targetTime) { if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), targetTime, movement); state.setTime(std::min(targetTime, state.mStopTime)); } else { if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), textkey->first, movement); state.setTime(textkey->first); } state.mPlaying = (state.getTime() < state.mStopTime); timepassed = targetTime - state.getTime(); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } } if(state.shouldLoop()) { state.mLoopCount--; state.setTime(state.mLoopStartTime); state.mPlaying = true; textkey = textkeys.lowerBound(state.getTime()); while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } if(state.getTime() >= state.mLoopStopTime) break; } if(timepassed <= 0.0f) break; } if(!state.mPlaying && state.mAutoDisable) { mStates.erase(stateiter++); resetActiveGroups(); } else ++stateiter; } updateEffects(); const float epsilon = 0.001f; float yawOffset = 0; if (mRootController) { bool enable = std::abs(mLegsYawRadians) > epsilon || std::abs(mBodyPitchRadians) > epsilon; mRootController->setEnabled(enable); if (enable) { mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1)) * osg::Quat(mBodyPitchRadians, osg::Vec3f(1,0,0))); yawOffset = mLegsYawRadians; } } if (mSpineController) { float yaw = mUpperBodyYawRadians - yawOffset; bool enable = std::abs(yaw) > epsilon; mSpineController->setEnabled(enable); if (enable) { mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1))); yawOffset = mUpperBodyYawRadians; } } if (mHeadController) { float yaw = mHeadYawRadians - yawOffset; bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon); mHeadController->setEnabled(enable); if (enable) mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1))); } // Scripted animations should not cause movement if (hasScriptedAnims) return osg::Vec3f(0, 0, 0); return movement; } void Animation::setLoopingEnabled(const std::string &groupname, bool enabled) { AnimStateMap::iterator state(mStates.find(groupname)); if(state != mStates.end()) state->second.mLoopingEnabled = enabled; } void loadBonesFromFile(osg::ref_ptr& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem) { const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get(); osg::ref_ptr sheathSkeleton (const_cast(node)); // const-trickery required because there is no const version of NodeVisitor GetExtendedBonesVisitor getBonesVisitor; sheathSkeleton->accept(getBonesVisitor); for (auto& nodePair : getBonesVisitor.mFoundBones) { SceneUtil::FindByNameVisitor findVisitor (nodePair.second->getName()); baseNode->accept(findVisitor); osg::Group* sheathParent = findVisitor.mFoundNode; if (sheathParent) { osg::Node* copy = static_cast(nodePair.first->clone(osg::CopyOp::DEEP_COPY_NODES)); sheathParent->addChild(copy); } } } void injectCustomBones(osg::ref_ptr& node, const std::string& model, Resource::ResourceSystem* resourceSystem) { if (model.empty()) return; std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } animationPath.replace(animationPath.size()-4, 4, "/"); for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { if (Misc::getFileExtension(name) == "nif") loadBonesFromFile(node, name, resourceSystem); } } osg::ref_ptr getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, bool inject, const std::string& defaultSkeleton) { Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager(); if (baseonly) { typedef std::map > Cache; static Cache cache; Cache::iterator found = cache.find(model); if (found == cache.end()) { osg::ref_ptr created = sceneMgr->getInstance(model); if (inject) { injectCustomBones(created, defaultSkeleton, resourceSystem); injectCustomBones(created, model, resourceSystem); } SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); cache.insert(std::make_pair(model, created)); return sceneMgr->getInstance(created); } else return sceneMgr->getInstance(found->second); } else { osg::ref_ptr created = sceneMgr->getInstance(model); if (inject) { injectCustomBones(created, defaultSkeleton, resourceSystem); injectCustomBones(created, model, resourceSystem); } return created; } } void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) { osg::ref_ptr previousStateset; if (mObjectRoot) { if (mLightListCallback) mObjectRoot->removeCullCallback(mLightListCallback); if (mTransparencyUpdater) mObjectRoot->removeCullCallback(mTransparencyUpdater); previousStateset = mObjectRoot->getStateSet(); mObjectRoot->getParent(0)->removeChild(mObjectRoot); } mObjectRoot = nullptr; mSkeleton = nullptr; mNodeMap.clear(); mNodeMapCreated = false; mActiveControllers.clear(); mAccumRoot = nullptr; mAccumCtrl = nullptr; static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); std::string defaultSkeleton; bool inject = false; if (useAdditionalSources && mPtr.getClass().isActor()) { if (isCreature) { MWWorld::LiveCellRef *ref = mPtr.get(); if(ref->mBase->mFlags & ESM::Creature::Bipedal) { defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); inject = true; } } else { inject = true; MWWorld::LiveCellRef *ref = mPtr.get(); if (!ref->mBase->mModel.empty()) { // If NPC has a custom animation model attached, we should inject bones from default skeleton for given race and gender as well // Since it is a quite rare case, there should not be a noticable performance loss // Note: consider that player and werewolves have no custom animation files attached for now const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(ref->mBase->mRace); bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; bool isFemale = !ref->mBase->isMale(); defaultSkeleton = SceneUtil::getActorSkeleton(false, isFemale, isBeast, false); defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); } } } if (!forceskeleton) { osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) { mInsert->removeChild(created); mObjectRoot = new osg::Group; mObjectRoot->addChild(created); mInsert->addChild(mObjectRoot); } osg::ref_ptr skel = dynamic_cast(mObjectRoot.get()); if (skel) mSkeleton = skel.get(); } else { osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { skel = new SceneUtil::Skeleton; skel->addChild(created); } mSkeleton = skel.get(); mObjectRoot = skel; mInsert->addChild(mObjectRoot); } if (previousStateset) mObjectRoot->setStateSet(previousStateset); if (isCreature) { SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; mObjectRoot->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); } if (!mLightListCallback) mLightListCallback = new SceneUtil::LightListCallback; mObjectRoot->addCullCallback(mLightListCallback); if (mTransparencyUpdater) mObjectRoot->addCullCallback(mTransparencyUpdater); } osg::Group* Animation::getObjectRoot() { return mObjectRoot.get(); } osg::Group* Animation::getOrCreateObjectRoot() { if (mObjectRoot) return mObjectRoot.get(); mObjectRoot = new osg::Group; mInsert->addChild(mObjectRoot); return mObjectRoot.get(); } void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration) { osg::Vec4f glowColor(1,1,1,1); glowColor.x() = effect->mData.mRed / 255.f; glowColor.y() = effect->mData.mGreen / 255.f; glowColor.z() = effect->mData.mBlue / 255.f; if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) { if (mGlowUpdater && mGlowUpdater->isDone()) mObjectRoot->removeUpdateCallback(mGlowUpdater); if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) { mGlowUpdater->setColor(glowColor); mGlowUpdater->setDuration(glowDuration); } else mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration); } } void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_Lighting, exterior); mExtraLightSource->setActorFade(mAlpha); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) { if (!mObjectRoot.get()) return; // Early out if we already have this effect FindVfxCallbacksVisitor visitor(effectId); mInsert->accept(visitor); for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) { UpdateVfxCallback* callback = *it; if (loop && !callback->mFinished && callback->mParams.mLoop && callback->mParams.mBoneName == bonename) return; } EffectParams params; params.mModelName = model; osg::ref_ptr parentNode; if (bonename.empty()) parentNode = mInsert; else { NodeMap::const_iterator found = getNodeMap().find(bonename); if (found == getNodeMap().end()) throw std::runtime_error("Can't find bone " + bonename); parentNode = found->second; } osg::ref_ptr trans = new SceneUtil::PositionAttitudeTransform; if (!mPtr.getClass().isNpc()) { osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mPtr) * 2.f); float scale = std::max({bounds.x(), bounds.y(), bounds.z() / 2.f}) / 64.f; if (scale > 1.f) trans->setScale(osg::Vec3f(scale, scale, scale)); float offset = 0.f; if (bounds.z() < 128.f) offset = bounds.z() - 128.f; else if (bounds.z() < bounds.x() + bounds.y()) offset = 128.f - bounds.z(); if (MWBase::Environment::get().getWorld()->isFlying(mPtr)) offset /= 20.f; trans->setPosition(osg::Vec3f(0.f, 0.f, offset * scale)); } parentNode->addChild(trans); osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); // Morrowind has a white ambient light attached to the root VFX node of the scenegraph node->getOrCreateStateSet()->setAttributeAndModes(getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); node->setNodeMask(Mask_Effect); MarkDrawablesVisitor markVisitor(Mask_Effect); node->accept(markVisitor); params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; params.mAnimTime = std::make_shared(); trans->addUpdateCallback(new UpdateVfxCallback(params)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(params.mAnimTime)); node->accept(assignVisitor); // Notify that this animation has attached magic effects mHasMagicEffects = true; overrideFirstRootTexture(texture, mResourceSystem, node); } void Animation::removeEffect(int effectId) { RemoveCallbackVisitor visitor(effectId); mInsert->accept(visitor); visitor.remove(); mHasMagicEffects = visitor.mHasMagicEffects; } void Animation::removeEffects() { removeEffect(-1); } void Animation::getLoopingEffects(std::vector &out) const { if (!mHasMagicEffects) return; FindVfxCallbacksVisitor visitor; mInsert->accept(visitor); for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) { UpdateVfxCallback* callback = *it; if (callback->mParams.mLoop && !callback->mFinished) out.push_back(callback->mParams.mEffectId); } } void Animation::updateEffects() { // We do not need to visit scene every frame. // We can use a bool flag to check in spellcasting effect found. if (!mHasMagicEffects) return; // TODO: objects without animation still will have // transformation nodes with finished callbacks RemoveFinishedCallbackVisitor visitor; mInsert->accept(visitor); visitor.remove(); mHasMagicEffects = visitor.mHasMagicEffects; } bool Animation::upperBodyReady() const { for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit)) || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon)) || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown)) || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death))) return false; } return true; } const osg::Node* Animation::getNode(const std::string &name) const { NodeMap::const_iterator found = getNodeMap().find(name); if (found == getNodeMap().end()) return nullptr; else return found->second; } void Animation::setAlpha(float alpha) { if (alpha == mAlpha) return; mAlpha = alpha; // TODO: we use it to fade actors away too, but it would be nice to have a dithering shader instead. if (alpha != 1.f) { if (mTransparencyUpdater == nullptr) { mTransparencyUpdater = new TransparencyUpdater(alpha); mObjectRoot->addCullCallback(mTransparencyUpdater); } else mTransparencyUpdater->setAlpha(alpha); } else { mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; } if (mExtraLightSource) mExtraLightSource->setActorFade(alpha); } void Animation::setLightEffect(float effect) { if (effect == 0) { if (mGlowLight) { mInsert->removeChild(mGlowLight); mGlowLight = nullptr; } } else { // 1 pt of Light magnitude corresponds to 1 foot of radius float radius = effect * std::ceil(Constants::UnitsPerFoot); // Arbitrary multiplier used to make the obvious cut-off less obvious float cutoffMult = 3; if (!mGlowLight || (radius * cutoffMult) != mGlowLight->getRadius()) { if (mGlowLight) { mInsert->removeChild(mGlowLight); mGlowLight = nullptr; } osg::ref_ptr light (new osg::Light); light->setDiffuse(osg::Vec4f(0,0,0,0)); light->setSpecular(osg::Vec4f(0,0,0,0)); light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); bool isExterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); SceneUtil::configureLight(light, radius, isExterior); mGlowLight = new SceneUtil::LightSource; mGlowLight->setNodeMask(Mask_Lighting); mInsert->addChild(mGlowLight); mGlowLight->setLight(light); } mGlowLight->setRadius(radius * cutoffMult); } } void Animation::addControllers() { mHeadController = addRotateController("bip01 head"); mSpineController = addRotateController("bip01 spine1"); mRootController = addRotateController("bip01"); } RotateController* Animation::addRotateController(const std::string &bone) { auto iter = getNodeMap().find(bone); if (iter == getNodeMap().end()) return nullptr; osg::MatrixTransform* node = iter->second; bool foundKeyframeCtrl = false; osg::Callback* cb = node->getUpdateCallback(); while (cb) { if (dynamic_cast(cb)) { foundKeyframeCtrl = true; break; } cb = cb->getNestedCallback(); } // Without KeyframeController the orientation will not be reseted each frame, so // RotateController shouldn't be used for such nodes. if (!foundKeyframeCtrl) return nullptr; RotateController* controller = new RotateController(mObjectRoot.get()); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); return controller; } void Animation::setHeadPitch(float pitchRadians) { mHeadPitchRadians = pitchRadians; } void Animation::setHeadYaw(float yawRadians) { mHeadYawRadians = yawRadians; } float Animation::getHeadPitch() const { return mHeadPitchRadians; } float Animation::getHeadYaw() const { return mHeadYawRadians; } void Animation::removeFromScene() { removeFromSceneImpl(); } void Animation::removeFromSceneImpl() { if (mGlowLight != nullptr) mInsert->removeChild(mGlowLight); if (mObjectRoot != nullptr) mInsert->removeChild(mObjectRoot); } // ------------------------------------------------------ float Animation::AnimationTime::getValue(osg::NodeVisitor*) { if (mTimePtr) return *mTimePtr; return 0.f; } float EffectAnimationTime::getValue(osg::NodeVisitor*) { return mTime; } void EffectAnimationTime::addTime(float duration) { mTime += duration; } void EffectAnimationTime::resetTime(float time) { mTime = time; } float EffectAnimationTime::getTime() const { return mTime; } // -------------------------------------------------------------------------------- ObjectAnimation::ObjectAnimation(const MWWorld::Ptr &ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight) : Animation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { if (!model.empty()) { setObjectRoot(model, false, false, false); if (animated) addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); } if (ptr.getType() == ESM::Light::sRecordId && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); if (!allowLight && mObjectRoot) { RemoveParticlesVisitor visitor; mObjectRoot->accept(visitor); visitor.remove(); } if (Settings::Manager::getBool("day night switches", "Game") && SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) { AddSwitchCallbacksVisitor visitor; mObjectRoot->accept(visitor); } if (ptr.getRefData().getCustomData() != nullptr && ObjectAnimation::canBeHarvested()) { const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); if (!store.hasVisibleItems()) { HarvestVisitor visitor; mObjectRoot->accept(visitor); } } } bool ObjectAnimation::canBeHarvested() const { if (mPtr.getType() != ESM::Container::sRecordId) return false; const MWWorld::LiveCellRef* ref = mPtr.get(); if (!(ref->mBase->mFlags & ESM::Container::Organic)) return false; return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel); } // ------------------------------ PartHolder::PartHolder(osg::ref_ptr node) : mNode(node) { } PartHolder::~PartHolder() { if (mNode.get() && !mNode->getNumParents()) Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has no parents" ; if (mNode.get() && mNode->getNumParents()) { if (mNode->getNumParents() > 1) Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has multiple (" << mNode->getNumParents() << ") parents"; mNode->getParent(0)->removeChild(mNode); } } } openmw-openmw-0.48.0/apps/openmw/mwrender/animation.hpp000066400000000000000000000446351445372753700232330ustar00rootroot00000000000000#ifndef GAME_RENDER_ANIMATION_H #define GAME_RENDER_ANIMATION_H #include "../mwworld/ptr.hpp" #include #include #include #include #include #include #include namespace ESM { struct Light; struct MagicEffect; } namespace Resource { class ResourceSystem; } namespace SceneUtil { class KeyframeHolder; class KeyframeController; class LightSource; class LightListCallback; class Skeleton; } namespace MWRender { class ResetAccumRootCallback; class RotateController; class TransparencyUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource { private: float mTime; public: float getValue(osg::NodeVisitor* nv) override; void addTime(float duration); void resetTime(float time); float getTime() const; EffectAnimationTime() : mTime(0) { } }; /// @brief Detaches the node from its parent when the object goes out of scope. class PartHolder { public: PartHolder(osg::ref_ptr node); ~PartHolder(); const osg::ref_ptr& getNode() const { return mNode; } private: osg::ref_ptr mNode; void operator= (const PartHolder&); PartHolder(const PartHolder&); }; using PartHolderPtr = std::unique_ptr; struct EffectParams { std::string mModelName; // Just here so we don't add the same effect twice std::shared_ptr mAnimTime; float mMaxControllerLength; int mEffectId; bool mLoop; std::string mBoneName; }; class Animation : public osg::Referenced { public: enum BoneGroup { BoneGroup_LowerBody = 0, BoneGroup_Torso, BoneGroup_LeftArm, BoneGroup_RightArm }; enum BlendMask { BlendMask_LowerBody = 1<<0, BlendMask_Torso = 1<<1, BlendMask_LeftArm = 1<<2, BlendMask_RightArm = 1<<3, BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody }; /* This is the number of *discrete* blend masks. */ static constexpr size_t sNumBlendMasks = 4; /// Holds an animation priority value for each BoneGroup. struct AnimPriority { /// Convenience constructor, initialises all priorities to the same value. AnimPriority(int priority) { for (unsigned int i=0; i, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> NodeMap; protected: class AnimationTime : public SceneUtil::ControllerSource { private: std::shared_ptr mTimePtr; public: void setTimePtr(std::shared_ptr time) { mTimePtr = time; } std::shared_ptr getTimePtr() const { return mTimePtr; } float getValue(osg::NodeVisitor* nv) override; }; class NullAnimationTime : public SceneUtil::ControllerSource { public: float getValue(osg::NodeVisitor *nv) override { return 0.f; } }; struct AnimSource; struct AnimState { std::shared_ptr mSource; float mStartTime; float mLoopStartTime; float mLoopStopTime; float mStopTime; typedef std::shared_ptr TimePtr; TimePtr mTime; float mSpeedMult; bool mPlaying; bool mLoopingEnabled; size_t mLoopCount; AnimPriority mPriority; int mBlendMask; bool mAutoDisable; AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopingEnabled(true), mLoopCount(0), mPriority(0), mBlendMask(0), mAutoDisable(true) { } ~AnimState() = default; float getTime() const { return *mTime; } void setTime(float time) { *mTime = time; } bool shouldLoop() const { return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; } }; typedef std::map AnimStateMap; AnimStateMap mStates; typedef std::vector > AnimSourceList; AnimSourceList mAnimSources; osg::ref_ptr mInsert; osg::ref_ptr mObjectRoot; SceneUtil::Skeleton* mSkeleton; // The node expected to accumulate movement during movement animations. osg::ref_ptr mAccumRoot; // The controller animating that node. osg::ref_ptr mAccumCtrl; // Used to reset the position of the accumulation root every frame - the movement should be applied to the physics system osg::ref_ptr mResetAccumRootCallback; // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. std::vector, osg::ref_ptr>> mActiveControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; mutable NodeMap mNodeMap; mutable bool mNodeMapCreated; MWWorld::Ptr mPtr; Resource::ResourceSystem* mResourceSystem; osg::Vec3f mAccumulate; TextKeyListener* mTextKeyListener; osg::ref_ptr mHeadController; osg::ref_ptr mSpineController; osg::ref_ptr mRootController; float mHeadYawRadians; float mHeadPitchRadians; float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; RotateController* addRotateController(const std::string& bone); bool mHasMagicEffects; osg::ref_ptr mGlowLight; osg::ref_ptr mGlowUpdater; osg::ref_ptr mTransparencyUpdater; osg::ref_ptr mExtraLightSource; float mAlpha; mutable std::map mAnimVelocities; osg::ref_ptr mLightListCallback; const NodeMap& getNodeMap() const; /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); size_t detectBlendMask(const osg::Node* node) const; /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ void updatePosition(float oldtime, float newtime, osg::Vec3f& position); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If * the marker is not found, or if the markers are the same, it returns * false. */ bool reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback); void handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map); /** Sets the root model of the object. * * Note that you must make sure all animation sources are cleared before resetting the object * root. All nodes previously retrieved with getNode will also become invalidated. * @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if you intend to add skinned parts manually. * @param baseonly If true, then any meshes or particle systems in the model are ignored * (useful for NPCs, where only the skeleton is needed for the root, and the actual NPC parts are then assembled from separate files). */ void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); void loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel); /** Adds the keyframe controllers in the specified model as a new animation source. * @note Later added animation sources have the highest priority when it comes to finding a particular animation. * @param model The file to add the keyframes for. Note that the .nif file extension will be replaced with .kf. * @param baseModel The filename of the mObjectRoot, only used for error messages. */ void addAnimSource(const std::string &model, const std::string& baseModel); void addSingleAnimSource(const std::string &model, const std::string& baseModel); /** Adds an additional light to the given node using the specified ESM record. */ void addExtraLight(osg::ref_ptr parent, const ESM::Light *light); void clearAnimSources(); /** * Provided to allow derived classes adding their own controllers. Note, the controllers must be added to mActiveControllers * so they get cleaned up properly on the next controller rebuild. A controller rebuild may be necessary to ensure correct ordering. */ virtual void addControllers(); void removeFromSceneImpl(); public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); /// Must be thread safe virtual ~Animation(); MWWorld::ConstPtr getPtr() const { return mPtr; } MWWorld::Ptr getPtr() { return mPtr; } /// Set active flag on the object skeleton, if one exists. /// @see SceneUtil::Skeleton::setActive /// 0 = Inactive, 1 = Active in place, 2 = Active void setActive(int active); osg::Group* getOrCreateObjectRoot(); osg::Group* getObjectRoot(); /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); void removeEffect (int effectId); void removeEffects (); void getLoopingEffects (std::vector& out) const; // Add a spell casting glow to an object. From measuring video taken from the original engine, // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. void addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration = 1.5); virtual void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(std::string_view anim) const; // Specifies the axis' to accumulate on. Non-accumulated axis will just // move visually, but not affect the actual movement. Each x/y/z value // should be on the scale of 0 to 1. void setAccumulation(const osg::Vec3f& accum); /** Plays an animation. * \param groupname Name of the animation group to play. * \param priority Priority of the animation. The animation will play on * bone groups that don't have another animation set of a * higher priority. * \param blendMask Bone groups to play the animation on. * \param autodisable Automatically disable the animation when it stops * playing. * \param speedmult Speed multiplier for the animation. * \param start Key marker from which to start. * \param stop Key marker to stop at. * \param startpoint How far in between the two markers to start. 0 starts * at the start marker, 1 starts at the stop marker. * \param loops How many times to loop the animation. This will use the * "loop start" and "loop stop" markers if they exist, * otherwise it may fall back to "start" and "stop", but only if * the \a loopFallback parameter is true. * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use * the "start" and "stop" keys for looping? */ void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback=false); /** Adjust the speed multiplier of an already playing animation. */ void adjustSpeedMult (const std::string& groupname, float speedmult); /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; /** Gets info about the given animation group. * \param groupname Animation group to check. * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. * \param speedmult Stores the animation speed multiplier * \return True if the animation is active, false otherwise. */ bool getInfo(const std::string &groupname, float *complete=nullptr, float *speedmult=nullptr) const; /// Get the absolute position in the animation track of the first text key with the given group. float getStartTime(const std::string &groupname) const; /// Get the absolute position in the animation track of the text key float getTextKeyTime(const std::string &textKey) const; /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; size_t getCurrentLoopCount(const std::string& groupname) const; /** Disables the specified animation group; * \param groupname Animation group to disable. */ void disable(const std::string &groupname); /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(const std::string &groupname) const; virtual osg::Vec3f runAnimation(float duration); void setLoopingEnabled(const std::string &groupname, bool enabled); /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(); /// Return a node with the specified name, or nullptr if not existing. /// @note The matching is case-insensitive. const osg::Node* getNode(const std::string& name) const; virtual bool useShieldAnimations() const { return false; } virtual void showWeapons(bool showWeapon) {} virtual bool getCarriedLeftShown() const { return false; } virtual void showCarriedLeft(bool show) {} virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {} virtual void setVampire(bool vampire) {} /// A value < 1 makes the animation translucent, 1.f = fully opaque void setAlpha(float alpha); virtual void setPitchFactor(float factor) {} virtual void attachArrow() {} virtual void detachArrow() {} virtual void releaseArrow(float attackStrength) {} virtual void enableHeadAnimation(bool enable) {} // TODO: move outside of this class /// Makes this object glow, by placing a Light in its center. /// @param effect Controls the radius and intensity of the light. virtual void setLightEffect(float effect); virtual void setHeadPitch(float pitchRadians); virtual void setHeadYaw(float yawRadians); virtual float getHeadPitch() const; virtual float getHeadYaw() const; virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; } virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; } virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; } virtual float getLegsYawRadians() const { return mLegsYawRadians; } virtual void setBodyPitchRadians(float v) { mBodyPitchRadians = v; } virtual float getBodyPitchRadians() const { return mBodyPitchRadians; } virtual void setAccurateAiming(bool enabled) {} virtual bool canBeHarvested() const { return false; } virtual void removeFromScene(); private: Animation(const Animation&); void operator=(Animation&); }; class ObjectAnimation : public Animation { public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); bool canBeHarvested() const override; }; class UpdateVfxCallback : public SceneUtil::NodeCallback { public: UpdateVfxCallback(EffectParams& params) : mFinished(false) , mParams(params) , mStartingTime(0) { } bool mFinished; EffectParams mParams; void operator()(osg::Node* node, osg::NodeVisitor* nv); private: double mStartingTime; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/bulletdebugdraw.cpp000066400000000000000000000162171445372753700244160ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "bulletdebugdraw.hpp" #include "vismask.hpp" #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" namespace MWRender { DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode) : mParentNode(parentNode), mWorld(world) { DebugDrawer::setDebugMode(debugMode); } void DebugDrawer::createGeometry() { if (!mLinesGeometry) { mLinesGeometry = new osg::Geometry; mTrisGeometry = new osg::Geometry; mLinesGeometry->setNodeMask(Mask_Debug); mTrisGeometry->setNodeMask(Mask_Debug); mLinesVertices = new osg::Vec3Array; mTrisVertices = new osg::Vec3Array; mLinesColors = new osg::Vec4Array; mLinesDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); mTrisDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES); mLinesGeometry->setUseDisplayList(false); mLinesGeometry->setVertexArray(mLinesVertices); mLinesGeometry->setColorArray(mLinesColors); mLinesGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); mLinesGeometry->setDataVariance(osg::Object::DYNAMIC); mLinesGeometry->addPrimitiveSet(mLinesDrawArrays); mTrisGeometry->setUseDisplayList(false); mTrisGeometry->setVertexArray(mTrisVertices); mTrisGeometry->setDataVariance(osg::Object::DYNAMIC); mTrisGeometry->addPrimitiveSet(mTrisDrawArrays); mParentNode->addChild(mLinesGeometry); mParentNode->addChild(mTrisGeometry); auto* stateSet = new osg::StateSet; stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); stateSet->setAttributeAndModes(new osg::PolygonOffset(SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0, SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0)); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); stateSet->setAttribute(material); mLinesGeometry->setStateSet(stateSet); mTrisGeometry->setStateSet(stateSet); mShapesRoot = new osg::Group; mShapesRoot->setStateSet(stateSet); mShapesRoot->setDataVariance(osg::Object::DYNAMIC); mShapesRoot->setNodeMask(Mask_Debug); mParentNode->addChild(mShapesRoot); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mLinesGeometry, "debug"); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mTrisGeometry, "debug"); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mShapesRoot, "debug"); } } void DebugDrawer::destroyGeometry() { if (mLinesGeometry) { mParentNode->removeChild(mLinesGeometry); mParentNode->removeChild(mTrisGeometry); mParentNode->removeChild(mShapesRoot); mLinesGeometry = nullptr; mLinesVertices = nullptr; mLinesColors = nullptr; mLinesDrawArrays = nullptr; mTrisGeometry = nullptr; mTrisVertices = nullptr; mTrisDrawArrays = nullptr; } } DebugDrawer::~DebugDrawer() { destroyGeometry(); } void DebugDrawer::step() { if (mDebugOn) { mLinesVertices->clear(); mTrisVertices->clear(); mLinesColors->clear(); mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); mWorld->debugDrawWorld(); showCollisions(); mLinesDrawArrays->setCount(mLinesVertices->size()); mTrisDrawArrays->setCount(mTrisVertices->size()); mLinesVertices->dirty(); mTrisVertices->dirty(); mLinesColors->dirty(); mLinesGeometry->dirtyBound(); mTrisGeometry->dirtyBound(); } } void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btVector3 &color) { mLinesVertices->push_back(Misc::Convert::toOsg(from)); mLinesVertices->push_back(Misc::Convert::toOsg(to)); mLinesColors->push_back({1,1,1,1}); mLinesColors->push_back({1,1,1,1}); #if BT_BULLET_VERSION < 317 size_t size = mLinesVertices->size(); if (size >= 6 && (*mLinesVertices)[size - 1] == (*mLinesVertices)[size - 6] && (*mLinesVertices)[size - 2] == (*mLinesVertices)[size - 3] && (*mLinesVertices)[size - 4] == (*mLinesVertices)[size - 5]) { mTrisVertices->push_back(mLinesVertices->back()); mLinesVertices->pop_back(); mLinesColors->pop_back(); mTrisVertices->push_back(mLinesVertices->back()); mLinesVertices->pop_back(); mLinesColors->pop_back(); mLinesVertices->pop_back(); mLinesColors->pop_back(); mTrisVertices->push_back(mLinesVertices->back()); mLinesVertices->pop_back(); mLinesColors->pop_back(); mLinesVertices->pop_back(); mLinesColors->pop_back(); mLinesVertices->pop_back(); mLinesColors->pop_back(); } #endif } void DebugDrawer::drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) { mTrisVertices->push_back(Misc::Convert::toOsg(v0)); mTrisVertices->push_back(Misc::Convert::toOsg(v1)); mTrisVertices->push_back(Misc::Convert::toOsg(v2)); } void DebugDrawer::addCollision(const btVector3& orig, const btVector3& normal) { mCollisionViews.emplace_back(orig, normal); } void DebugDrawer::showCollisions() { const auto now = std::chrono::steady_clock::now(); for (auto& [from, to , created] : mCollisionViews) { if (now - created < std::chrono::seconds(2)) { mLinesVertices->push_back(Misc::Convert::toOsg(from)); mLinesVertices->push_back(Misc::Convert::toOsg(to)); mLinesColors->push_back({1,0,0,1}); mLinesColors->push_back({1,0,0,1}); } } mCollisionViews.erase(std::remove_if(mCollisionViews.begin(), mCollisionViews.end(), [&now](const CollisionView& view) { return now - view.mCreated >= std::chrono::seconds(2); }), mCollisionViews.end()); } void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) { auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius)); geom->setColor(osg::Vec4(1, 1, 1, 1)); mShapesRoot->addChild(geom); } void DebugDrawer::reportErrorWarning(const char *warningString) { Log(Debug::Warning) << warningString; } void DebugDrawer::setDebugMode(int isOn) { mDebugOn = (isOn != 0); if (!mDebugOn) destroyGeometry(); else createGeometry(); } int DebugDrawer::getDebugMode() const { return mDebugOn; } } openmw-openmw-0.48.0/apps/openmw/mwrender/bulletdebugdraw.hpp000066400000000000000000000044251445372753700244210ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_BULLETDEBUGDRAW_H #define OPENMW_MWRENDER_BULLETDEBUGDRAW_H #include #include #include #include #include #include class btCollisionWorld; namespace osg { class Group; class Geometry; } namespace MWRender { class DebugDrawer : public btIDebugDraw { private: struct CollisionView { btVector3 mOrig; btVector3 mEnd; std::chrono::time_point mCreated; CollisionView(btVector3 orig, btVector3 normal) : mOrig(orig), mEnd(orig + normal * 20), mCreated(std::chrono::steady_clock::now()) {}; }; std::vector mCollisionViews; osg::ref_ptr mShapesRoot; protected: osg::ref_ptr mParentNode; btCollisionWorld *mWorld; osg::ref_ptr mLinesGeometry; osg::ref_ptr mTrisGeometry; osg::ref_ptr mLinesVertices; osg::ref_ptr mTrisVertices; osg::ref_ptr mLinesColors; osg::ref_ptr mLinesDrawArrays; osg::ref_ptr mTrisDrawArrays; bool mDebugOn; void createGeometry(); void destroyGeometry(); public: DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode = 1); ~DebugDrawer(); void step(); void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) override; void drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) override; void addCollision(const btVector3& orig, const btVector3& normal); void showCollisions(); void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override {}; void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override; void reportErrorWarning(const char* warningString) override; void draw3dText(const btVector3& location,const char* textString) override {} //0 for off, anything else for on. void setDebugMode(int isOn) override; //0 for off, anything else for on. int getDebugMode() const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/camera.cpp000066400000000000000000000345251445372753700224740ustar00rootroot00000000000000#include "camera.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" #include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwphysics/raycasting.hpp" #include "npcanimation.hpp" namespace { class UpdateRenderCameraCallback : public SceneUtil::NodeCallback { public: UpdateRenderCameraCallback(MWRender::Camera* cam) : mCamera(cam) { } void operator()(osg::Camera* cam, osg::NodeVisitor* nv) { // traverse first to update animations, in case the camera is attached to an animated node traverse(cam, nv); mCamera->updateCamera(cam); } private: MWRender::Camera* mCamera; }; } namespace MWRender { Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), mCollisionType((MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor) | MWPhysics::CollisionType_CameraOnly), mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), mMode(Mode::FirstPerson), mVanityAllowed(true), mDeferredRotationAllowed(true), mProcessViewChange(false), mHeight(124.f), mPitch(0.f), mYaw(0.f), mRoll(0.f), mCameraDistance(0.f), mPreferredCameraDistance(0.f), mFocalPointCurrentOffset(osg::Vec2d()), mFocalPointTargetOffset(osg::Vec2d()), mFocalPointTransitionSpeedCoef(1.f), mSkipFocalPointTransition(true), mPreviousTransitionInfluence(0.f), mShowCrosshair(false), mDeferredRotation(osg::Vec3f()), mDeferredRotationDisabled(false) { mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } Camera::~Camera() { mCamera->removeUpdateCallback(mUpdateCallback); } osg::Vec3d Camera::calculateTrackedPosition() const { if (!mTrackingNode) return osg::Vec3d(); osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths(); if (nodepaths.empty()) return osg::Vec3d(); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3d res = worldMat.getTrans(); if (mMode != Mode::FirstPerson) res.z() += mHeight * mHeightScale; return res; } osg::Vec3d Camera::getFocalPointOffset() const { osg::Vec3d offset; offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw); offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw); offset.z() = mFocalPointCurrentOffset.y(); return offset; } void Camera::updateCamera(osg::Camera *cam) { osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); osg::Vec3d pos = mPosition; if (mMode == Mode::FirstPerson) { // It is a hack. Camera position depends on neck animation. // Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we // recalculate the position here. Note that it becomes different from mPosition that // is used in other parts of the code. // TODO: detach camera from OSG animation and get rid of this hack. osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition(); pos = calculateFirstPersonPosition(recalculatedTrackedPosition); } cam->setViewMatrixAsLookAt(pos, pos + forward, up); mViewMatrix = cam->getViewMatrix(); } void Camera::update(float duration, bool paused) { mLockPitch = mLockYaw = false; if (mQueuedMode && mAnimation->upperBodyReady()) setMode(*mQueuedMode); if (mProcessViewChange) processViewChange(); if (paused) return; // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair); updateFocalPointOffset(duration); updatePosition(); } osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const { osg::Vec3d res = trackedPosition; osg::Vec2f horizontalOffset = Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw); res.x() += horizontalOffset.x(); res.y() += horizontalOffset.y(); res.z() += mFirstPersonOffset.z(); return res; } void Camera::updatePosition() { mTrackedPosition = calculateTrackedPosition(); if (mMode == Mode::Static) return; if (mMode == Mode::FirstPerson) { mPosition = calculateFirstPersonPosition(mTrackedPosition); mCameraDistance = 0; return; } constexpr float cameraObstacleLimit = 5.0f; constexpr float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); // Adjust focal point to prevent clipping. osg::Vec3d focalOffset = getFocalPointOffset(); osg::Vec3d focal = mTrackedPosition + focalOffset; focalOffset.z() += 10.f; // Needed to avoid camera clipping through the ceiling because // character's head can be a bit higher than the collision area. float offsetLen = focalOffset.length(); if (offsetLen > 0) { MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, mCollisionType); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; focal += focalOffset * std::max(-1.0, adjustmentCoef); } } // Adjust camera distance. mCameraDistance = mPreferredCameraDistance; osg::Quat orient = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1,0,0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0,0,1)); osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, mCollisionType); if (result.mHit) { mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); } mPosition = focal + offset; } void Camera::setMode(Mode newMode, bool force) { if (mMode == newMode) { mQueuedMode = std::nullopt; return; } Mode oldMode = mMode; if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation && !mAnimation->upperBodyReady()) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later mQueuedMode = newMode; return; } mMode = newMode; mQueuedMode = std::nullopt; if (newMode == Mode::FirstPerson) mFirstPersonView = true; else if (newMode == Mode::ThirdPerson) mFirstPersonView = false; calculateDeferredRotation(); if (oldMode == Mode::FirstPerson || newMode == Mode::FirstPerson) { instantTransition(); mProcessViewChange = true; } } void Camera::setFocalPointTargetOffset(const osg::Vec2d& v) { mFocalPointTargetOffset = v; mPreviousTransitionSpeed = mFocalPointTransitionSpeed; mPreviousTransitionInfluence = 1.0f; } void Camera::updateFocalPointOffset(float duration) { if (duration <= 0) return; if (mSkipFocalPointTransition) { mSkipFocalPointTransition = false; mPreviousExtraOffset = osg::Vec2d(); mPreviousTransitionInfluence = 0.f; mFocalPointCurrentOffset = mFocalPointTargetOffset; } osg::Vec2d oldOffset = mFocalPointCurrentOffset; if (mPreviousTransitionInfluence > 0) { mFocalPointCurrentOffset -= mPreviousExtraOffset; mPreviousExtraOffset = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration; mPreviousTransitionInfluence = std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef); mPreviousExtraOffset *= mPreviousTransitionInfluence; mFocalPointCurrentOffset += mPreviousExtraOffset; } osg::Vec2d delta = mFocalPointTargetOffset - mFocalPointCurrentOffset; if (delta.length2() > 0) { float coef = duration * (1.0 + 5.0 / delta.length()) * mFocalPointTransitionSpeedCoef * (1.0f - mPreviousTransitionInfluence); mFocalPointCurrentOffset += delta * std::min(coef, 1.0f); } else { mPreviousExtraOffset = osg::Vec2d(); mPreviousTransitionInfluence = 0.f; } mFocalPointTransitionSpeed = (mFocalPointCurrentOffset - oldOffset) / duration; } void Camera::toggleViewMode(bool force) { setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force); } bool Camera::toggleVanityMode(bool enable) { if (!enable) setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson, false); else if (mVanityAllowed) setMode(Mode::Vanity, false); return (mMode == Mode::Vanity) == enable; } void Camera::setSneakOffset(float offset) { mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } void Camera::setYaw(float angle, bool force) { if (!mLockYaw || force) mYaw = Misc::normalizeAngle(angle); if (force) mLockYaw = true; } void Camera::setPitch(float angle, bool force) { const float epsilon = 0.000001f; float limit = static_cast(osg::PI_2) - epsilon; if (!mLockPitch || force) mPitch = std::clamp(angle, -limit, limit); if (force) mLockPitch = true; } void Camera::setStaticPosition(const osg::Vec3d& pos) { if (mMode != Mode::Static) throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode"); mPosition = pos; } void Camera::setAnimation(NpcAnimation *anim) { mAnimation = anim; mProcessViewChange = true; } void Camera::processViewChange() { if (mTrackingPtr.isEmpty()) return; if (mMode == Mode::FirstPerson) { mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); mTrackingNode = mAnimation->getNode("Camera"); if (!mTrackingNode) mTrackingNode = mAnimation->getNode("Head"); mHeightScale = 1.f; } else { mAnimation->setViewMode(NpcAnimation::VM_Normal); SceneUtil::PositionAttitudeTransform* transform = mTrackingPtr.getRefData().getBaseNode(); mTrackingNode = transform; if (transform) mHeightScale = transform->getScale().z(); else mHeightScale = 1.f; } mProcessViewChange = false; } void Camera::applyDeferredPreviewRotationToPlayer(float dt) { if (mMode != Mode::ThirdPerson || mTrackingPtr.isEmpty()) return; osg::Vec3f rot = mDeferredRotation; float delta = rot.normalize(); delta = std::min(delta, (delta + 1.f) * 3 * dt); rot *= delta; mDeferredRotation -= rot; if (mDeferredRotationDisabled) { mDeferredRotationDisabled = delta > 0.0001; rotateCameraToTrackingPtr(); return; } auto& movement = mTrackingPtr.getClass().getMovementSettings(mTrackingPtr); movement.mRotation[0] += rot.x(); movement.mRotation[1] += rot.y(); movement.mRotation[2] += rot.z(); if (std::abs(mDeferredRotation.z()) > 0.0001) { float s = std::sin(mDeferredRotation.z()); float c = std::cos(mDeferredRotation.z()); float x = movement.mPosition[0]; float y = movement.mPosition[1]; movement.mPosition[0] = x * c + y * s; movement.mPosition[1] = x * -s + y * c; } } void Camera::rotateCameraToTrackingPtr() { if (mMode == Mode::Static || mTrackingPtr.isEmpty()) return; setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); } void Camera::instantTransition() { mSkipFocalPointTransition = true; mDeferredRotationDisabled = false; mDeferredRotation = osg::Vec3f(); rotateCameraToTrackingPtr(); } void Camera::calculateDeferredRotation() { if (mMode == Mode::Static) { mDeferredRotation = osg::Vec3f(); return; } MWWorld::Ptr ptr = mTrackingPtr; if (mMode == Mode::Preview || mMode == Mode::Vanity || ptr.isEmpty()) return; if (mFirstPersonView) { instantTransition(); return; } mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch); mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw); if (!mDeferredRotationAllowed) mDeferredRotationDisabled = true; } } openmw-openmw-0.48.0/apps/openmw/mwrender/camera.hpp000066400000000000000000000132171445372753700224740ustar00rootroot00000000000000#ifndef GAME_MWRENDER_CAMERA_H #define GAME_MWRENDER_CAMERA_H #include #include #include #include #include #include #include "../mwworld/ptr.hpp" namespace osg { class Camera; class Callback; class Node; } namespace MWRender { class NpcAnimation; /// \brief Camera control class Camera { public: enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4}; Camera(osg::Camera* camera); ~Camera(); /// Attach camera to object void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; } MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } float getFocalPointTransitionSpeed() const { return mFocalPointTransitionSpeedCoef; } void setFocalPointTargetOffset(const osg::Vec2d& v); osg::Vec2d getFocalPointTargetOffset() const { return mFocalPointTargetOffset; } void instantTransition(); void showCrosshair(bool v) { mShowCrosshair = v; } /// Update the view matrix of \a cam void updateCamera(osg::Camera* cam); /// Reset to defaults void reset() { setMode(Mode::FirstPerson); } void rotateCameraToTrackingPtr(); float getPitch() const { return mPitch; } float getYaw() const { return mYaw; } float getRoll() const { return mRoll; } void setPitch(float angle, bool force = false); void setYaw(float angle, bool force = false); void setRoll(float angle) { mRoll = angle; } float getExtraPitch() const { return mExtraPitch; } float getExtraYaw() const { return mExtraYaw; } float getExtraRoll() const { return mExtraRoll; } void setExtraPitch(float angle) { mExtraPitch = angle; } void setExtraYaw(float angle) { mExtraYaw = angle; } void setExtraRoll(float angle) { mExtraRoll = angle; } /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force=false); bool toggleVanityMode(bool enable); void applyDeferredPreviewRotationToPlayer(float dt); void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } /// \brief Lowers the camera for sneak. void setSneakOffset(float offset); void processViewChange(); void update(float duration, bool paused=false); float getCameraDistance() const { return mCameraDistance; } void setPreferredCameraDistance(float v) { mPreferredCameraDistance = v; } void setAnimation(NpcAnimation *anim); osg::Vec3d getTrackedPosition() const { return mTrackedPosition; } const osg::Vec3d& getPosition() const { return mPosition; } void setStaticPosition(const osg::Vec3d& pos); bool isVanityOrPreviewModeEnabled() const { return mMode == Mode::Vanity || mMode == Mode::Preview; } Mode getMode() const { return mMode; } std::optional getQueuedMode() const { return mQueuedMode; } void setMode(Mode mode, bool force = true); void allowCharacterDeferredRotation(bool v) { mDeferredRotationAllowed = v; } void calculateDeferredRotation(); void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; } osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; } int getCollisionType() const { return mCollisionType; } void setCollisionType(int collisionType) { mCollisionType = collisionType; } const osg::Matrixf& getViewMatrix() const { return mViewMatrix; } private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; osg::Vec3d mTrackedPosition; float mHeightScale; int mCollisionType; osg::ref_ptr mCamera; NpcAnimation *mAnimation; // Always 'true' if mMode == `FirstPerson`. Also it is 'true' in `Vanity` or `Preview` modes if // the camera should return to `FirstPerson` view after it. bool mFirstPersonView; Mode mMode; std::optional mQueuedMode; bool mVanityAllowed; bool mDeferredRotationAllowed; bool mProcessViewChange; float mHeight; float mPitch, mYaw, mRoll; float mExtraPitch = 0, mExtraYaw = 0, mExtraRoll = 0; bool mLockPitch = false, mLockYaw = false; osg::Vec3d mPosition; osg::Matrixf mViewMatrix; float mCameraDistance, mPreferredCameraDistance; osg::Vec3f mFirstPersonOffset{0, 0, 0}; osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointTargetOffset; float mFocalPointTransitionSpeedCoef; bool mSkipFocalPointTransition; // This fields are used to make focal point transition smooth if previous transition was not finished. float mPreviousTransitionInfluence; osg::Vec2d mFocalPointTransitionSpeed; osg::Vec2d mPreviousTransitionSpeed; osg::Vec2d mPreviousExtraOffset; bool mShowCrosshair; osg::Vec3d calculateTrackedPosition() const; osg::Vec3d calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const; osg::Vec3d getFocalPointOffset() const; void updateFocalPointOffset(float duration); void updatePosition(); osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; bool mDeferredRotationDisabled; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/cell.hpp000066400000000000000000000017151445372753700221630ustar00rootroot00000000000000#ifndef GAME_RENDER_CELL_H #define GAME_RENDER_CELL_H #include namespace MWRender { class CellRender { public: virtual ~CellRender() {}; /// Make the cell visible. Load the cell if necessary. virtual void show() = 0; /// Remove the cell from rendering, but don't remove it from /// memory. virtual void hide() = 0; /// Destroy all rendering objects connected with this cell. virtual void destroy() = 0; /// Make the reference with the given handle visible. virtual void enable (const std::string& handle) = 0; /// Make the reference with the given handle invisible. virtual void disable (const std::string& handle) = 0; /// Remove the reference with the given handle permanently from the scene. virtual void deleteObject (const std::string& handle) = 0; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/characterpreview.cpp000066400000000000000000000612541445372753700246010ustar00rootroot00000000000000#include "characterpreview.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" #include "npcanimation.hpp" #include "vismask.hpp" namespace MWRender { class DrawOnceCallback : public SceneUtil::NodeCallback { public: DrawOnceCallback(osg::Node* subgraph) : mRendered(false) , mLastRenderedFrame(0) , mSubgraph(subgraph) { } void operator () (osg::Node* node, osg::NodeVisitor* nv) { if (!mRendered) { mRendered = true; mLastRenderedFrame = nv->getTraversalNumber(); osg::ref_ptr previousFramestamp = const_cast(nv->getFrameStamp()); osg::FrameStamp* fs = new osg::FrameStamp(*previousFramestamp); fs->setSimulationTime(0.0); nv->setFrameStamp(fs); // Update keyframe controllers in the scene graph first... // RTTNode does not continue update traversal, so manually continue the update traversal since we need it. mSubgraph->accept(*nv); traverse(node, nv); nv->setFrameStamp(previousFramestamp); } else { node->setNodeMask(0); } } void redrawNextFrame() { mRendered = false; } unsigned int getLastRenderedFrame() const { return mLastRenderedFrame; } private: bool mRendered; unsigned int mLastRenderedFrame; osg::ref_ptr mSubgraph; }; // Set up alpha blending mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO // This makes the RTT have premultiplied alpha, though, so the source blend factor must be GL_ONE when it's applied class SetUpBlendVisitor : public osg::NodeVisitor { public: SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void apply(osg::Node& node) override { if (osg::ref_ptr stateset = node.getStateSet()) { osg::ref_ptr newStateSet; if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) { osg::BlendFunc* blendFunc = static_cast(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); if (blendFunc) { newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); node.setStateSet(newStateSet); osg::ref_ptr newBlendFunc = new osg::BlendFunc(*blendFunc); newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); // I *think* (based on some by-hand maths) that the RGB and dest alpha factors are unchanged, and only dest determines source alpha factor // This has the benefit of being idempotent if we assume nothing used glBlendFuncSeparate before we touched it if (blendFunc->getDestination() == osg::BlendFunc::ONE_MINUS_SRC_ALPHA) newBlendFunc->setSourceAlpha(osg::BlendFunc::ONE); else if (blendFunc->getDestination() == osg::BlendFunc::ONE) newBlendFunc->setSourceAlpha(osg::BlendFunc::ZERO); // Other setups barely exist in the wild and aren't worth supporting as they're not equippable gear else Log(Debug::Info) << "Unable to adjust blend mode for character preview. Source factor 0x" << std::hex << blendFunc->getSource() << ", destination factor 0x" << blendFunc->getDestination() << std::dec; } } if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) { if (!newStateSet) { newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); node.setStateSet(newStateSet); } // Disable noBlendAlphaEnv newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); newStateSet->setDefine("FORCE_OPAQUE", "0", osg::StateAttribute::ON); } } traverse(node); } }; class CharacterPreviewRTTNode : public SceneUtil::RTTNode { static constexpr float fovYDegrees = 12.3f; static constexpr float znear = 4.0f; static constexpr float zfar = 10000.f; public: CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, StereoAwareness::Unaware_MultiViewShaders) , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) { if (SceneUtil::AutoDepth::isReversed()) mPerspectiveMatrix = static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar)); else mPerspectiveMatrix = osg::Matrixf::perspective(fovYDegrees, mAspectRatio, znear, zfar); mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); mViewMatrix = osg::Matrixf::identity(); setColorBufferInternalFormat(GL_RGBA); setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void setDefaults(osg::Camera* camera) override { camera->setName("CharacterPreview"); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); camera->setViewport(0, 0, width(), height()); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setCullMask(~(Mask_UpdateVisitor)); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); SceneUtil::setCameraClearDepth(camera); camera->setNodeMask(Mask_RenderToTexture); camera->addChild(mGroup); }; void apply(osg::Camera* camera) override { if(mCameraStateset) camera->setStateSet(mCameraStateset); camera->setViewMatrix(mViewMatrix); if (shouldDoTextureArray()) Stereo::setMultiviewMatrices(mGroup->getOrCreateStateSet(), { mPerspectiveMatrix, mPerspectiveMatrix }); }; void addChild(osg::Node* node) { mGroup->addChild(node); } void setCameraStateset(osg::StateSet* stateset) { mCameraStateset = stateset; } void setViewMatrix(const osg::Matrixf& viewMatrix) { mViewMatrix = viewMatrix; } osg::ref_ptr mGroup = new osg::Group; osg::Matrixf mPerspectiveMatrix; osg::Matrixf mViewMatrix; osg::ref_ptr mCameraStateset; float mAspectRatio; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) : mParent(parent) , mResourceSystem(resourceSystem) , mPosition(position) , mLookAt(lookAt) , mCharacter(character) , mAnimation(nullptr) , mSizeX(sizeX) , mSizeY(sizeY) { mTextureStateSet = new osg::StateSet; mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY); mRTTNode->setNodeMask(Mask_RenderToTexture); bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setDefine("FORCE_OPAQUE", "1", osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); osg::ref_ptr defaultMat (new osg::Material); defaultMat->setColorMode(osg::Material::OFF); defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); fog->setStart(10000000); fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); // TODO: Clean up this mess of loose uniforms that shaders depend on. // turn off sky blending stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); stateset->addUniform(new osg::Uniform("sky", 0)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{1, 1})); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); noBlendAlphaEnv->setSource0_Alpha(osg::TexEnvCombine::CONSTANT); noBlendAlphaEnv->setConstantColor(osg::Vec4(0.0, 0.0, 0.0, 1.0)); noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); osg::ref_ptr dummyTexture = new osg::Texture2D(); dummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); dummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); dummyTexture->setInternalFormat(GL_DEPTH_COMPONENT); dummyTexture->setTextureSize(1, 1); // This might clash with a shadow map, so make sure it doesn't cast shadows dummyTexture->setShadowComparison(true); dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); osg::ref_ptr light = new osg::Light; float diffuseR = Fallback::Map::getFloat("Inventory_DirectionalDiffuseR"); float diffuseG = Fallback::Map::getFloat("Inventory_DirectionalDiffuseG"); float diffuseB = Fallback::Map::getFloat("Inventory_DirectionalDiffuseB"); float ambientR = Fallback::Map::getFloat("Inventory_DirectionalAmbientR"); float ambientG = Fallback::Map::getFloat("Inventory_DirectionalAmbientG"); float ambientB = Fallback::Map::getFloat("Inventory_DirectionalAmbientB"); float azimuth = osg::DegreesToRadians(Fallback::Map::getFloat("Inventory_DirectionalRotationX")); float altitude = osg::DegreesToRadians(Fallback::Map::getFloat("Inventory_DirectionalRotationY")); float positionX = -std::cos(azimuth) * std::sin(altitude); float positionY = std::sin(azimuth) * std::sin(altitude); float positionZ = std::cos(altitude); light->setPosition(osg::Vec4(positionX,positionY,positionZ, 0.0)); light->setDiffuse(osg::Vec4(diffuseR,diffuseG,diffuseB,1)); osg::Vec4 ambientRGBA = osg::Vec4(ambientR,ambientG,ambientB,1); if (mResourceSystem->getSceneManager()->getForceShaders()) { // When using shaders, we now skip the ambient sun calculation as this is the only place it's used. // Using the scene ambient will give identical results. lightmodel->setAmbientIntensity(ambientRGBA); light->setAmbient(osg::Vec4(0,0,0,1)); } else light->setAmbient(ambientRGBA); light->setSpecular(osg::Vec4(0,0,0,0)); light->setLightNum(0); light->setConstantAttenuation(1.f); light->setLinearAttenuation(0.f); light->setQuadraticAttenuation(0.f); lightManager->setSunlight(light); osg::ref_ptr lightSource = new osg::LightSource; lightSource->setLight(light); lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON); lightManager->addChild(lightSource); mRTTNode->addChild(lightManager); mNode = new osg::PositionAttitudeTransform; lightManager->addChild(mNode); mDrawOnceCallback = new DrawOnceCallback(mRTTNode->mGroup); mRTTNode->addUpdateCallback(mDrawOnceCallback); mParent->addChild(mRTTNode); mCharacter.mCell = nullptr; } CharacterPreview::~CharacterPreview () { mParent->removeChild(mRTTNode); } int CharacterPreview::getTextureWidth() const { return mSizeX; } int CharacterPreview::getTextureHeight() const { return mSizeY; } void CharacterPreview::setBlendMode() { SetUpBlendVisitor visitor; mNode->accept(visitor); } void CharacterPreview::onSetup() { setBlendMode(); } osg::ref_ptr CharacterPreview::getTexture() { return static_cast(mRTTNode->getColorTexture(nullptr)); } void CharacterPreview::rebuild() { mAnimation = nullptr; mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); onSetup(); redraw(); } void CharacterPreview::redraw() { mRTTNode->setNodeMask(Mask_RenderToTexture); mDrawOnceCallback->redrawNextFrame(); } // -------------------------------------------------------------------------------------------------- InventoryPreview::InventoryPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character) : CharacterPreview(parent, resourceSystem, character, 512, 1024, osg::Vec3f(0, 700, 71), osg::Vec3f(0,0,71)) { } void InventoryPreview::setViewport(int sizeX, int sizeY) { sizeX = std::max(sizeX, 0); sizeY = std::max(sizeY, 0); // NB Camera::setViewport has threading issues osg::ref_ptr stateset = new osg::StateSet; mViewport = new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); stateset->setAttributeAndModes(mViewport); mRTTNode->setCameraStateset(stateset); redraw(); } void InventoryPreview::update() { if (!mAnimation.get()) return; mAnimation->showWeapons(true); mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname = "inventoryhandtohand"; bool showCarriedLeft = true; if(iter != inv.end()) { groupname = "inventoryweapononehand"; if(iter->getType() == ESM::Weapon::sRecordId) { MWWorld::LiveCellRef *ref = iter->get(); int type = ref->mBase->mData.mType; const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type); showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded); std::string inventoryGroup = weaponInfo->mLongGroup; inventoryGroup = "inventory" + inventoryGroup; // We still should use one-handed animation as fallback if (mAnimation->hasAnimation(inventoryGroup)) groupname = inventoryGroup; else { static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; static const std::string twoHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded && weaponInfo->mWeaponClass == ESM::WeaponType::Melee) groupname = twoHandFallback; else groupname = oneHandFallback; } } } mAnimation->showCarriedLeft(showCarriedLeft); mCurrentAnimGroup = groupname; mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { if(!mAnimation->getInfo("torch")) mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); mAnimation->runAnimation(0.0f); setBlendMode(); redraw(); } int InventoryPreview::getSlotSelected (int posX, int posY) { if (!mViewport) return -1; float projX = (posX / mViewport->width()) * 2 - 1.f; float projY = (posY / mViewport->height()) * 2 - 1.f; // With Intersector::WINDOW, the intersection ratios are slightly inaccurate. Seems to be a // precision issue - compiling with OSG_USE_FLOAT_MATRIX=0, Intersector::WINDOW works ok. // Using Intersector::PROJECTION results in better precision because the start/end points and the model matrices // don't go through as many transformations. osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, projX, projY)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); auto* camera = mRTTNode->getCamera(nullptr); osg::Node::NodeMask nodeMask = camera->getNodeMask(); camera->setNodeMask(~0u); camera->accept(visitor); camera->setNodeMask(nodeMask); if (intersector->containsIntersections()) { osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); return mAnimation->getSlot(intersection.nodePath); } return -1; } void InventoryPreview::updatePtr(const MWWorld::Ptr &ptr) { mCharacter = MWWorld::Ptr(ptr.getBase(), nullptr); } void InventoryPreview::onSetup() { CharacterPreview::onSetup(); osg::Vec3f scale (1.f, 1.f, 1.f); mCharacter.getClass().adjustScale(mCharacter, scale, true); mNode->setScale(scale); auto viewMatrix = osg::Matrixf::lookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0, 0, 1)); mRTTNode->setViewMatrix(viewMatrix); } // -------------------------------------------------------------------------------------------------- RaceSelectionPreview::RaceSelectionPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : CharacterPreview(parent, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0,0,8)) , mBase (*mCharacter.get()->mBase) , mRef(&mBase) , mPitchRadians(osg::DegreesToRadians(6.f)) { mCharacter = MWWorld::Ptr(&mRef, nullptr); } RaceSelectionPreview::~RaceSelectionPreview() { } void RaceSelectionPreview::setAngle(float angleRadians) { mNode->setAttitude(osg::Quat(mPitchRadians, osg::Vec3(1,0,0)) * osg::Quat(angleRadians, osg::Vec3(0,0,1))); redraw(); } void RaceSelectionPreview::setPrototype(const ESM::NPC &proto) { mBase = proto; mBase.mId = "player"; rebuild(); } class UpdateCameraCallback : public SceneUtil::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) : mNodeToFollow(nodeToFollow) , mPosOffset(posOffset) , mLookAtOffset(lookAtOffset) { } void operator()(CharacterPreviewRTTNode* node, osg::NodeVisitor* nv) { // Update keyframe controllers in the scene graph first... traverse(node, nv); // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); if (nodepaths.empty()) return; osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); auto viewMatrix = osg::Matrixf::lookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0, 0, 1)); node->setViewMatrix(viewMatrix); } private: osg::ref_ptr mNodeToFollow; osg::Vec3 mPosOffset; osg::Vec3 mLookAtOffset; }; void RaceSelectionPreview::onSetup () { CharacterPreview::onSetup(); mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->runAnimation(0.f); // attach camera to follow the head node if (mUpdateCameraCallback) mRTTNode->removeUpdateCallback(mUpdateCameraCallback); const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (head) { mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); mRTTNode->addUpdateCallback(mUpdateCameraCallback); } else Log(Debug::Error) << "Error: Bip01 Head node not found"; } } openmw-openmw-0.48.0/apps/openmw/mwrender/characterpreview.hpp000066400000000000000000000062401445372753700246000ustar00rootroot00000000000000#ifndef MWRENDER_CHARACTERPREVIEW_H #define MWRENDER_CHARACTERPREVIEW_H #include #include #include #include #include #include "../mwworld/ptr.hpp" namespace osg { class Texture2D; class Camera; class Group; class Viewport; class StateSet; } namespace MWRender { class NpcAnimation; class DrawOnceCallback; class CharacterPreviewRTTNode; class CharacterPreview { public: CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt); virtual ~CharacterPreview(); int getTextureWidth() const; int getTextureHeight() const; void redraw(); void rebuild(); osg::ref_ptr getTexture(); /// Get the osg::StateSet required to render the texture correctly, if any. osg::StateSet* getTextureStateSet() { return mTextureStateSet; } private: CharacterPreview(const CharacterPreview&); CharacterPreview& operator=(const CharacterPreview&); protected: virtual bool renderHeadOnly() { return false; } void setBlendMode(); virtual void onSetup(); osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mTextureStateSet; osg::ref_ptr mDrawOnceCallback; osg::ref_ptr mRTTNode; osg::Vec3f mPosition; osg::Vec3f mLookAt; MWWorld::Ptr mCharacter; osg::ref_ptr mAnimation; osg::ref_ptr mNode; std::string mCurrentAnimGroup; int mSizeX; int mSizeY; }; class InventoryPreview : public CharacterPreview { public: InventoryPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character); void updatePtr(const MWWorld::Ptr& ptr); void update(); // Render preview again, e.g. after changed equipment void setViewport(int sizeX, int sizeY); int getSlotSelected(int posX, int posY); protected: osg::ref_ptr mViewport; void onSetup() override; }; class UpdateCameraCallback; class RaceSelectionPreview : public CharacterPreview { ESM::NPC mBase; MWWorld::LiveCellRef mRef; protected: bool renderHeadOnly() override { return true; } void onSetup() override; public: RaceSelectionPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem); virtual ~RaceSelectionPreview(); void setAngle(float angleRadians); const ESM::NPC &getPrototype() const { return mBase; } void setPrototype(const ESM::NPC &proto); private: osg::ref_ptr mUpdateCameraCallback; float mPitchRadians; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/creatureanimation.cpp000066400000000000000000000205021445372753700247440ustar00rootroot00000000000000#include "creatureanimation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender { CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { MWWorld::LiveCellRef *ref = mPtr.get(); if(!model.empty()) { setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); addAnimSource(model, model); } } CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) , mShowWeapons(false) , mShowCarriedLeft(false) { MWWorld::LiveCellRef *ref = mPtr.get(); if(!model.empty()) { setObjectRoot(model, true, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); } addAnimSource(model, model); mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); updateParts(); } mWeaponAnimationTime = std::make_shared(this); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) { if (showWeapon != mShowWeapons) { mShowWeapons = showWeapon; updateParts(); } } void CreatureWeaponAnimation::showCarriedLeft(bool show) { if (show != mShowCarriedLeft) { mShowCarriedLeft = show; updateParts(); } } void CreatureWeaponAnimation::updateParts() { mAmmunition.reset(); mWeapon.reset(); mShield.reset(); updateHolsteredWeapon(!mShowWeapons); updateQuiver(); updateHolsteredShield(mShowCarriedLeft); if (mShowWeapons) updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); if (mShowCarriedLeft) updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft); } void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) { if (!mObjectRoot) return; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator it = inv.getSlot(slot); if (it == inv.end()) { scene.reset(); return; } MWWorld::ConstPtr item = *it; std::string bonename; std::string itemModel = item.getClass().getModel(item); if (slot == MWWorld::InventoryStore::Slot_CarriedRight) { if(item.getType() == ESM::Weapon::sRecordId) { int type = item.get()->mBase->mData.mType; bonename = MWMechanics::getWeaponType(type)->mAttachBone; if (bonename != "Weapon Bone") { const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) bonename = "Weapon Bone"; } } else bonename = "Weapon Bone"; } else { bonename = "Shield Bone"; if (item.getType() == ESM::Armor::sRecordId) { itemModel = getShieldMesh(item, false); } } try { osg::ref_ptr attached = attach(itemModel, bonename, bonename, item.getType() == ESM::Light::sRecordId); scene = std::make_unique(attached); if (!item.getClass().getEnchantment(item).empty()) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item)); // Crossbows start out with a bolt attached // FIXME: code duplicated from NpcAnimation if (slot == MWWorld::InventoryStore::Slot_CarriedRight && item.getType() == ESM::Weapon::sRecordId && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && ammo->get()->mBase->mData.mType == weaponInfo->mAmmoType) attachArrow(); else mAmmunition.reset(); } else mAmmunition.reset(); std::shared_ptr source; if (slot == MWWorld::InventoryStore::Slot_CarriedRight) source = mWeaponAnimationTime; else source = std::make_shared(); SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); attached->accept(assignVisitor); } catch (std::exception& e) { Log(Debug::Error) << "Can not add creature part: " << e.what(); } } bool CreatureWeaponAnimation::isArrowAttached() const { return mAmmunition != nullptr; } void CreatureWeaponAnimation::detachArrow() { WeaponAnimation::detachArrow(mPtr); updateQuiver(); } void CreatureWeaponAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) { osg::Group* bone = getArrowBone(); if (bone != nullptr && bone->getNumChildren()) SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); } updateQuiver(); } void CreatureWeaponAnimation::releaseArrow(float attackStrength) { WeaponAnimation::releaseArrow(mPtr, attackStrength); updateQuiver(); } osg::Group *CreatureWeaponAnimation::getArrowBone() { if (!mWeapon) return nullptr; if (!mPtr.getClass().hasInventoryStore(mPtr)) return nullptr; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return nullptr; int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; if (ammoType == ESM::Weapon::None) return nullptr; // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); if (bone == nullptr) { SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); mWeapon->getNode()->accept(findVisitor); bone = findVisitor.mFoundNode; } return bone; } osg::Node *CreatureWeaponAnimation::getWeaponNode() { return mWeapon ? mWeapon->getNode().get() : nullptr; } Resource::ResourceSystem *CreatureWeaponAnimation::getResourceSystem() { return mResourceSystem; } void CreatureWeaponAnimation::addControllers() { Animation::addControllers(); WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) { osg::Vec3f ret = Animation::runAnimation(duration); WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); return ret; } } openmw-openmw-0.48.0/apps/openmw/mwrender/creatureanimation.hpp000066400000000000000000000046651445372753700247650ustar00rootroot00000000000000#ifndef GAME_RENDER_CREATUREANIMATION_H #define GAME_RENDER_CREATUREANIMATION_H #include "actoranimation.hpp" #include "weaponanimation.hpp" #include "../mwworld/inventorystore.hpp" namespace MWWorld { class Ptr; } namespace MWRender { class CreatureAnimation : public ActorAnimation { public: CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); virtual ~CreatureAnimation() {} }; // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. class CreatureWeaponAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); virtual ~CreatureWeaponAnimation() {} void equipmentChanged() override { updateParts(); } void showWeapons(bool showWeapon) override; bool getCarriedLeftShown() const override { return mShowCarriedLeft; } void showCarriedLeft(bool show) override; void updateParts(); void updatePart(PartHolderPtr& scene, int slot); void attachArrow() override; void detachArrow() override; void releaseArrow(float attackStrength) override; // WeaponAnimation osg::Group* getArrowBone() override; osg::Node* getWeaponNode() override; Resource::ResourceSystem* getResourceSystem() override; void showWeapon(bool show) override { showWeapons(show); } void setWeaponGroup(const std::string& group, bool relativeDuration) override { mWeaponAnimationTime->setGroup(group, relativeDuration); } void addControllers() override; osg::Vec3f runAnimation(float duration) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. void setPitchFactor(float factor) override { mPitchFactor = factor; } protected: bool isArrowAttached() const override; private: PartHolderPtr mWeapon; PartHolderPtr mShield; bool mShowWeapons; bool mShowCarriedLeft; std::shared_ptr mWeaponAnimationTime; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/effectmanager.cpp000066400000000000000000000045241445372753700240270ustar00rootroot00000000000000#include "effectmanager.hpp" #include #include #include #include #include "animation.hpp" #include "vismask.hpp" #include "util.hpp" #include namespace MWRender { EffectManager::EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem) : mParentNode(parent) , mResourceSystem(resourceSystem) { } EffectManager::~EffectManager() { clear(); } void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); node->setNodeMask(Mask_Effect); Effect effect; effect.mAnimTime = std::make_shared(); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); effect.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); osg::ref_ptr trans = new osg::PositionAttitudeTransform; trans->setPosition(worldPosition); trans->setScale(osg::Vec3f(scale, scale, scale)); trans->addChild(node); effect.mTransform = trans; SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); node->accept(assignVisitor); if (isMagicVFX) overrideFirstRootTexture(textureOverride, mResourceSystem, node); else overrideTexture(textureOverride, mResourceSystem, node); mParentNode->addChild(trans); mEffects.push_back(std::move(effect)); } void EffectManager::update(float dt) { mEffects.erase( std::remove_if( mEffects.begin(), mEffects.end(), [dt, this](Effect& effect) { effect.mAnimTime->addTime(dt); const auto remove = effect.mAnimTime->getTime() >= effect.mMaxControllerLength; if (remove) mParentNode->removeChild(effect.mTransform); return remove; }), mEffects.end() ); } void EffectManager::clear() { for(const auto& effect : mEffects) { mParentNode->removeChild(effect.mTransform); } mEffects.clear(); } } openmw-openmw-0.48.0/apps/openmw/mwrender/effectmanager.hpp000066400000000000000000000027141445372753700240330ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_EFFECTMANAGER_H #define OPENMW_MWRENDER_EFFECTMANAGER_H #include #include #include #include namespace osg { class Group; class Vec3f; class PositionAttitudeTransform; } namespace Resource { class ResourceSystem; } namespace MWRender { class EffectAnimationTime; // Note: effects attached to another object should be managed by MWRender::Animation::addEffect. // This class manages "free" effects, i.e. attached to a dedicated scene node in the world. class EffectManager { public: EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem); EffectManager(const EffectManager&) = delete; ~EffectManager(); /// Add an effect. When it's finished playing, it will be removed automatically. void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); void update(float dt); /// Remove all effects void clear(); private: struct Effect { float mMaxControllerLength; std::shared_ptr mAnimTime; osg::ref_ptr mTransform; }; std::vector mEffects; osg::ref_ptr mParentNode; Resource::ResourceSystem* mResourceSystem; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/fogmanager.cpp000066400000000000000000000072271445372753700233510ustar00rootroot00000000000000#include "fogmanager.hpp" #include #include #include #include #include namespace { float DLLandFogStart; float DLLandFogEnd; float DLUnderwaterFogStart; float DLUnderwaterFogEnd; float DLInteriorFogStart; float DLInteriorFogEnd; } namespace MWRender { FogManager::FogManager() : mLandFogStart(0.f) , mLandFogEnd(std::numeric_limits::max()) , mUnderwaterFogStart(0.f) , mUnderwaterFogEnd(std::numeric_limits::max()) , mFogColor(osg::Vec4f()) , mDistantFog(Settings::Manager::getBool("use distant fog", "Fog")) , mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor")) , mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight")) , mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog")) { DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog"); DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog"); DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog"); DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog"); DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog"); DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog"); } void FogManager::configure(float viewDistance, const ESM::Cell *cell) { osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); if (mDistantFog) { float density = std::max(0.2f, cell->mAmbi.mFogDensity); mLandFogStart = DLInteriorFogEnd * (1.0f - density) + DLInteriorFogStart*density; mLandFogEnd = DLInteriorFogEnd; mUnderwaterFogStart = DLUnderwaterFogStart; mUnderwaterFogEnd = DLUnderwaterFogEnd; mFogColor = color; } else configure(viewDistance, cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); } void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) { if (mDistantFog) { mLandFogStart = dlFactor * (DLLandFogStart - dlOffset * DLLandFogEnd); mLandFogEnd = dlFactor * (1.0f - dlOffset) * DLLandFogEnd; mUnderwaterFogStart = DLUnderwaterFogStart; mUnderwaterFogEnd = DLUnderwaterFogEnd; } else { if (fogDepth == 0.0) { mLandFogStart = 0.0f; mLandFogEnd = std::numeric_limits::max(); } else { mLandFogStart = viewDistance * (1 - fogDepth); mLandFogEnd = viewDistance; } mUnderwaterFogStart = std::min(viewDistance, 7168.f) * (1 - underwaterFog); mUnderwaterFogEnd = std::min(viewDistance, 7168.f); } mFogColor = color; } float FogManager::getFogStart(bool isUnderwater) const { return isUnderwater ? mUnderwaterFogStart : mLandFogStart; } float FogManager::getFogEnd(bool isUnderwater) const { return isUnderwater ? mUnderwaterFogEnd : mLandFogEnd; } osg::Vec4f FogManager::getFogColor(bool isUnderwater) const { if (isUnderwater) { return mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight); } return mFogColor; } } openmw-openmw-0.48.0/apps/openmw/mwrender/fogmanager.hpp000066400000000000000000000016031445372753700233460ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_FOGMANAGER_H #define OPENMW_MWRENDER_FOGMANAGER_H #include namespace ESM { struct Cell; } namespace MWRender { class FogManager { public: FogManager(); void configure(float viewDistance, const ESM::Cell *cell); void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color); osg::Vec4f getFogColor(bool isUnderwater) const; float getFogStart(bool isUnderwater) const; float getFogEnd(bool isUnderwater) const; private: float mLandFogStart; float mLandFogEnd; float mUnderwaterFogStart; float mUnderwaterFogEnd; osg::Vec4f mFogColor; bool mDistantFog; osg::Vec4f mUnderwaterColor; float mUnderwaterWeight; float mUnderwaterIndoorFog; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/globalmap.cpp000066400000000000000000000577441445372753700232120ustar00rootroot00000000000000#include "globalmap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "vismask.hpp" #include "util.hpp" namespace { // Create a screen-aligned quad with given texture coordinates. // Assumes a top-left origin of the sampled image. osg::ref_ptr createTexturedQuad(float leftTexCoord, float topTexCoord, float rightTexCoord, float bottomTexCoord) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-1, -1, 0)); verts->push_back(osg::Vec3f(-1, 1, 0)); verts->push_back(osg::Vec3f(1, 1, 0)); verts->push_back(osg::Vec3f(1, -1, 0)); geom->setVertexArray(verts); osg::ref_ptr texcoords = new osg::Vec2Array; texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-bottomTexCoord)); texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-topTexCoord)); texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-topTexCoord)); texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-bottomTexCoord)); osg::ref_ptr colors = new osg::Vec4Array; colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); geom->setColorArray(colors, osg::Array::BIND_OVERALL); geom->setTexCoordArray(0, texcoords, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); return geom; } class CameraUpdateGlobalCallback : public SceneUtil::NodeCallback { public: CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) : mRendered(false) , mParent(parent) { } void operator()(osg::Camera* node, osg::NodeVisitor* nv) { if (mRendered) { if (mParent->copyResult(node, nv->getTraversalNumber())) { node->setNodeMask(0); mParent->markForRemoval(node); } return; } traverse(node, nv); mRendered = true; } private: bool mRendered; MWRender::GlobalMap* mParent; }; std::vector writePng(const osg::Image& overlayImage) { std::ostringstream ostream; osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; return std::vector(); } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream); if (!result.success()) { Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); return std::vector(); } std::string data = ostream.str(); return std::vector(data.begin(), data.end()); } } namespace MWRender { class CreateMapWorkItem : public SceneUtil::WorkItem { public: CreateMapWorkItem(int width, int height, int minX, int minY, int maxX, int maxY, int cellSize, const MWWorld::Store& landStore) : mWidth(width), mHeight(height), mMinX(minX), mMinY(minY), mMaxX(maxX), mMaxY(maxY), mCellSize(cellSize), mLandStore(landStore) { } void doWork() override { osg::ref_ptr image = new osg::Image; image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE); unsigned char* data = image->data(); osg::ref_ptr alphaImage = new osg::Image; alphaImage->allocateImage(mWidth, mHeight, 1, GL_ALPHA, GL_UNSIGNED_BYTE); unsigned char* alphaData = alphaImage->data(); for (int x = mMinX; x <= mMaxX; ++x) { for (int y = mMinY; y <= mMaxY; ++y) { const ESM::Land* land = mLandStore.search (x,y); for (int cellY=0; cellY(float(cellX) / float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); int texelX = (x-mMinX) * mCellSize + cellX; int texelY = (y-mMinY) * mCellSize + cellY; unsigned char r,g,b; float y2 = 0; if (land && (land->mDataTypes & ESM::Land::DATA_WNAM)) y2 = land->mWnam[vertexY * 9 + vertexX] / 128.f; else y2 = SCHAR_MIN / 128.f; if (y2 < 0) { r = static_cast(14 * y2 + 38); g = static_cast(20 * y2 + 56); b = static_cast(18 * y2 + 51); } else if (y2 < 0.3f) { if (y2 < 0.1f) y2 *= 8.f; else { y2 -= 0.1f; y2 += 0.8f; } r = static_cast(66 - 32 * y2); g = static_cast(48 - 23 * y2); b = static_cast(33 - 16 * y2); } else { y2 -= 0.3f; y2 *= 1.428f; r = static_cast(34 - 29 * y2); g = static_cast(25 - 20 * y2); b = static_cast(17 - 12 * y2); } data[texelY * mWidth * 3 + texelX * 3] = r; data[texelY * mWidth * 3 + texelX * 3+1] = g; data[texelY * mWidth * 3 + texelX * 3+2] = b; alphaData[texelY * mWidth+ texelX] = (y2 < 0) ? static_cast(0) : static_cast(255); } } } } mBaseTexture = new osg::Texture2D; mBaseTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mBaseTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mBaseTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mBaseTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mBaseTexture->setImage(image); mBaseTexture->setResizeNonPowerOfTwoHint(false); mAlphaTexture = new osg::Texture2D; mAlphaTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mAlphaTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mAlphaTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mAlphaTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mAlphaTexture->setImage(alphaImage); mAlphaTexture->setResizeNonPowerOfTwoHint(false); mOverlayImage = new osg::Image; mOverlayImage->allocateImage(mWidth, mHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE); assert(mOverlayImage->isDataContiguous()); memset(mOverlayImage->data(), 0, mOverlayImage->getTotalSizeInBytes()); mOverlayTexture = new osg::Texture2D; mOverlayTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mOverlayTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mOverlayTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mOverlayTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mOverlayTexture->setResizeNonPowerOfTwoHint(false); mOverlayTexture->setInternalFormat(GL_RGBA); mOverlayTexture->setTextureSize(mWidth, mHeight); } int mWidth, mHeight; int mMinX, mMinY, mMaxX, mMaxY; int mCellSize; const MWWorld::Store& mLandStore; osg::ref_ptr mBaseTexture; osg::ref_ptr mAlphaTexture; osg::ref_ptr mOverlayImage; osg::ref_ptr mOverlayTexture; }; struct GlobalMap::WritePng final : public SceneUtil::WorkItem { osg::ref_ptr mOverlayImage; std::vector mImageData; explicit WritePng(osg::ref_ptr overlayImage) : mOverlayImage(std::move(overlayImage)) {} void doWork() override { mImageData = writePng(*mOverlayImage); } }; GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) : mRoot(root) , mWorkQueue(workQueue) , mWidth(0) , mHeight(0) , mMinX(0), mMaxX(0) , mMinY(0), mMaxY(0) { mCellSize = Settings::Manager::getInt("global map cell size", "Map"); } GlobalMap::~GlobalMap() { for (auto& camera : mCamerasPendingRemoval) removeCamera(camera); for (auto& camera : mActiveCameras) removeCamera(camera); if (mWorkItem) mWorkItem->waitTillDone(); } void GlobalMap::render () { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); // get the size of the world MWWorld::Store::iterator it = esmStore.get().extBegin(); for (; it != esmStore.get().extEnd(); ++it) { if (it->getGridX() < mMinX) mMinX = it->getGridX(); if (it->getGridX() > mMaxX) mMaxX = it->getGridX(); if (it->getGridY() < mMinY) mMinY = it->getGridY(); if (it->getGridY() > mMaxY) mMaxY = it->getGridY(); } mWidth = mCellSize*(mMaxX-mMinX+1); mHeight = mCellSize*(mMaxY-mMinY+1); mWorkItem = new CreateMapWorkItem(mWidth, mHeight, mMinX, mMinY, mMaxX, mMaxY, mCellSize, esmStore.get()); mWorkQueue->addWorkItem(mWorkItem); } void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY) { imageX = (float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1)) * getWidth(); imageY = (1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1)) * getHeight(); } void GlobalMap::requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, float srcLeft, float srcTop, float srcRight, float srcBottom) { osg::ref_ptr camera (new osg::Camera); camera->setNodeMask(Mask_RenderToTexture); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setViewMatrix(osg::Matrix::identity()); camera->setProjectionMatrix(osg::Matrix::identity()); camera->setProjectionResizePolicy(osg::Camera::FIXED); camera->setRenderOrder(osg::Camera::PRE_RENDER, 1); // Make sure the global map is rendered after the local map y = mHeight - y - height; // convert top-left origin to bottom-left camera->setViewport(x, y, width, height); if (clear) { camera->setClearMask(GL_COLOR_BUFFER_BIT); camera->setClearColor(osg::Vec4(0,0,0,0)); } else camera->setClearMask(GL_NONE); camera->setUpdateCallback(new CameraUpdateGlobalCallback(this)); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->attach(osg::Camera::COLOR_BUFFER, mOverlayTexture); // no need for a depth buffer camera->setImplicitBufferAttachmentMask(osg::DisplaySettings::IMPLICIT_COLOR_BUFFER_ATTACHMENT); if (cpuCopy) { // Attach an image to copy the render back to the CPU when finished osg::ref_ptr image (new osg::Image); image->setPixelFormat(mOverlayImage->getPixelFormat()); image->setDataType(mOverlayImage->getDataType()); camera->attach(osg::Camera::COLOR_BUFFER, image); ImageDest imageDest; imageDest.mImage = image; imageDest.mX = x; imageDest.mY = y; mPendingImageDest[camera] = imageDest; } // Create a quad rendering the updated texture if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); if (mAlphaTexture) { osg::ref_ptr texcoords = new osg::Vec2Array; float x1 = x / static_cast(mWidth); float x2 = (x + width) / static_cast(mWidth); float y1 = y / static_cast(mHeight); float y2 = (y + height) / static_cast(mHeight); texcoords->push_back(osg::Vec2f(x1, y1)); texcoords->push_back(osg::Vec2f(x1, y2)); texcoords->push_back(osg::Vec2f(x2, y2)); texcoords->push_back(osg::Vec2f(x2, y1)); geom->setTexCoordArray(1, texcoords, osg::Array::BIND_PER_VERTEX); stateset->setTextureAttributeAndModes(1, mAlphaTexture, osg::StateAttribute::ON); osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); stateset->setTextureAttributeAndModes(1, texEnvCombine); } camera->addChild(geom); } mRoot->addChild(camera); mActiveCameras.push_back(camera); } void GlobalMap::exploreCell(int cellX, int cellY, osg::ref_ptr localMapTexture) { ensureLoaded(); if (!localMapTexture) return; int originX = (cellX - mMinX) * mCellSize; int originY = (cellY - mMinY + 1) * mCellSize; // +1 because we want the top left corner of the cell, not the bottom left if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; requestOverlayTextureUpdate(originX, mHeight - originY, mCellSize, mCellSize, localMapTexture, false, true); } void GlobalMap::clear() { ensureLoaded(); memset(mOverlayImage->data(), 0, mOverlayImage->getTotalSizeInBytes()); mPendingImageDest.clear(); // just push a Camera to clear the FBO, instead of setImage()/dirty() // easier, since we don't need to worry about synchronizing access :) requestOverlayTextureUpdate(0, 0, mWidth, mHeight, osg::ref_ptr(), true, false); } void GlobalMap::write(ESM::GlobalMap& map) { ensureLoaded(); map.mBounds.mMinX = mMinX; map.mBounds.mMaxX = mMaxX; map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; if (mWritePng != nullptr) { mWritePng->waitTillDone(); map.mImageData = std::move(mWritePng->mImageData); mWritePng = nullptr; return; } map.mImageData = writePng(*mOverlayImage); } struct Box { int mLeft, mTop, mRight, mBottom; Box(int left, int top, int right, int bottom) : mLeft(left), mTop(top), mRight(right), mBottom(bottom) { } bool operator == (const Box& other) const { return mLeft == other.mLeft && mTop == other.mTop && mRight == other.mRight && mBottom == other.mBottom; } }; void GlobalMap::read(ESM::GlobalMap& map) { ensureLoaded(); const ESM::GlobalMap::Bounds& bounds = map.mBounds; if (bounds.mMaxX-bounds.mMinX < 0) return; if (bounds.mMaxY-bounds.mMinY < 0) return; if (bounds.mMinX > bounds.mMaxX || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); if (map.mImageData.empty()) return; Files::IMemStream istream(map.mImageData.data(), map.mImageData.size()); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { Log(Debug::Error) << "Error: Can't read map overlay: no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(istream); if (!result.success()) { Log(Debug::Error) << "Error: Can't read map overlay: " << result.message() << " code " << result.status(); return; } osg::ref_ptr image = result.getImage(); int imageWidth = image->s(); int imageHeight = image->t(); int xLength = (bounds.mMaxX-bounds.mMinX+1); int yLength = (bounds.mMaxY-bounds.mMinY+1); // Size of one cell in image space int cellImageSizeSrc = imageWidth / xLength; if (int(imageHeight / yLength) != cellImageSizeSrc) throw std::runtime_error("cell size must be quadratic"); // If cell bounds of the currently loaded content and the loaded savegame do not match, // we need to resize source/dest boxes to accommodate // This means nonexisting cells will be dropped silently int cellImageSizeDst = mCellSize; // Completely off-screen? -> no need to blit anything if (bounds.mMaxX < mMinX || bounds.mMaxY < mMinY || bounds.mMinX > mMaxX || bounds.mMinY > mMaxY) return; int leftDiff = (mMinX - bounds.mMinX); int topDiff = (bounds.mMaxY - mMaxY); int rightDiff = (bounds.mMaxX - mMaxX); int bottomDiff = (mMinY - bounds.mMinY); Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), std::max(0, topDiff * cellImageSizeSrc), std::min(imageWidth, imageWidth - rightDiff * cellImageSizeSrc), std::min(imageHeight, imageHeight - bottomDiff * cellImageSizeSrc)); Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), std::max(0, -topDiff * cellImageSizeDst), std::min(mWidth, mWidth + rightDiff * cellImageSizeDst), std::min(mHeight, mHeight + bottomDiff * cellImageSizeDst)); osg::ref_ptr texture (new osg::Texture2D); texture->setImage(image); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setResizeNonPowerOfTwoHint(false); if (srcBox == destBox && imageWidth == mWidth && imageHeight == mHeight) { mOverlayImage = image; requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); } else { // Dimensions don't match. This could mean a changed map region, or a changed map resolution. // In the latter case, we'll want filtering. // Create a RTT Camera and draw the image onto mOverlayImage in the next frame. requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight-destBox.mLeft, destBox.mBottom-destBox.mTop, texture, true, true, srcBox.mLeft/float(imageWidth), srcBox.mTop/float(imageHeight), srcBox.mRight/float(imageWidth), srcBox.mBottom/float(imageHeight)); } } osg::ref_ptr GlobalMap::getBaseTexture() { ensureLoaded(); return mBaseTexture; } osg::ref_ptr GlobalMap::getOverlayTexture() { ensureLoaded(); return mOverlayTexture; } void GlobalMap::ensureLoaded() { if (mWorkItem) { mWorkItem->waitTillDone(); mOverlayImage = mWorkItem->mOverlayImage; mBaseTexture = mWorkItem->mBaseTexture; mAlphaTexture = mWorkItem->mAlphaTexture; mOverlayTexture = mWorkItem->mOverlayTexture; requestOverlayTextureUpdate(0, 0, mWidth, mHeight, osg::ref_ptr(), true, false); mWorkItem = nullptr; } } bool GlobalMap::copyResult(osg::Camera *camera, unsigned int frame) { ImageDestMap::iterator it = mPendingImageDest.find(camera); if (it == mPendingImageDest.end()) return true; else { ImageDest& imageDest = it->second; if (imageDest.mFrameDone == 0) imageDest.mFrameDone = frame+2; // wait an extra frame to ensure the draw thread has completed its frame. if (imageDest.mFrameDone > frame) { ++it; return false; } mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage); mPendingImageDest.erase(it); return true; } } void GlobalMap::markForRemoval(osg::Camera *camera) { CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera); if (found == mActiveCameras.end()) { Log(Debug::Error) << "Error: GlobalMap trying to remove an inactive camera"; return; } mActiveCameras.erase(found); mCamerasPendingRemoval.push_back(camera); } void GlobalMap::cleanupCameras() { for (auto& camera : mCamerasPendingRemoval) removeCamera(camera); mCamerasPendingRemoval.clear(); } void GlobalMap::removeCamera(osg::Camera *cam) { cam->removeChildren(0, cam->getNumChildren()); mRoot->removeChild(cam); } void GlobalMap::asyncWritePng() { if (mOverlayImage == nullptr) return; // Use deep copy to avoid any sychronization mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL)); mWorkQueue->addWorkItem(mWritePng, /*front=*/true); } } openmw-openmw-0.48.0/apps/openmw/mwrender/globalmap.hpp000066400000000000000000000070241445372753700232010ustar00rootroot00000000000000#ifndef GAME_RENDER_GLOBALMAP_H #define GAME_RENDER_GLOBALMAP_H #include #include #include #include namespace osg { class Texture2D; class Image; class Group; class Camera; } namespace ESM { struct GlobalMap; } namespace SceneUtil { class WorkQueue; } namespace MWRender { class CreateMapWorkItem; class GlobalMap { public: GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue); ~GlobalMap(); void render(); int getWidth() const { return mWidth; } int getHeight() const { return mHeight; } void worldPosToImageSpace(float x, float z, float& imageX, float& imageY); void exploreCell (int cellX, int cellY, osg::ref_ptr localMapTexture); /// Clears the overlay void clear(); /** * Removes cameras that have already been rendered. Should be called every frame to ensure that * we do not render the same map more than once. Note, this cleanup is difficult to implement in an * automated fashion, since we can't alter the scene graph structure from within an update callback. */ void cleanupCameras(); void removeCamera(osg::Camera* cam); bool copyResult(osg::Camera* cam, unsigned int frame); /** * Mark a camera for cleanup in the next update. For internal use only. */ void markForRemoval(osg::Camera* camera); void write (ESM::GlobalMap& map); void read (ESM::GlobalMap& map); osg::ref_ptr getBaseTexture(); osg::ref_ptr getOverlayTexture(); void ensureLoaded(); void asyncWritePng(); private: struct WritePng; /** * Request rendering a 2d quad onto mOverlayTexture. * x, y, width and height are the destination coordinates (top-left coordinate origin) * @param cpuCopy copy the resulting render onto mOverlayImage as well? */ void requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, float srcLeft = 0.f, float srcTop = 0.f, float srcRight = 1.f, float srcBottom = 1.f); int mCellSize; osg::ref_ptr mRoot; typedef std::vector > CameraVector; CameraVector mActiveCameras; CameraVector mCamerasPendingRemoval; struct ImageDest { ImageDest() : mX(0), mY(0) , mFrameDone(0) { } osg::ref_ptr mImage; int mX, mY; unsigned int mFrameDone; }; typedef std::map, ImageDest> ImageDestMap; ImageDestMap mPendingImageDest; std::vector< std::pair > mExploredCells; osg::ref_ptr mBaseTexture; osg::ref_ptr mAlphaTexture; // GPU copy of overlay // Note, uploads are pushed through a Camera, instead of through mOverlayImage osg::ref_ptr mOverlayTexture; // CPU copy of overlay osg::ref_ptr mOverlayImage; osg::ref_ptr mWorkQueue; osg::ref_ptr mWorkItem; osg::ref_ptr mWritePng; int mWidth; int mHeight; int mMinX, mMaxX, mMinY, mMaxY; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/groundcover.cpp000066400000000000000000000250521445372753700235740ustar00rootroot00000000000000#include "groundcover.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/groundcoverstore.hpp" #include "vismask.hpp" namespace MWRender { class InstancingVisitor : public osg::NodeVisitor { public: InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mInstances(instances) , mChunkPosition(chunkPosition) { } void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) { geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); } osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); osg::BoundingBox box; float radius = geom.getBoundingBox().radius(); for (unsigned int i = 0; i < transforms->getNumElements(); i++) { osg::Vec3f pos(mInstances[i].mPos.asVec3()); osg::Vec3f relativePos = pos - mChunkPosition; (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); // Use an additional margin due to groundcover animation float instanceRadius = radius * mInstances[i].mScale * 1.1f; osg::BoundingSphere instanceBounds(relativePos, instanceRadius); box.expandBy(instanceBounds); } geom.setInitialBound(box); osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); for (unsigned int i = 0; i < rotations->getNumElements(); i++) { (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); } // Display lists do not support instancing in OSG 3.4 geom.setUseDisplayList(false); geom.setUseVertexBufferObjects(true); geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); } private: std::vector mInstances; osg::Vec3f mChunkPosition; }; class DensityCalculator { public: DensityCalculator(float density) : mDensity(density) { } bool isInstanceEnabled() { if (mDensity >= 1.f) return true; mCurrentGroundcover += mDensity; if (mCurrentGroundcover < 1.f) return false; mCurrentGroundcover -= 1.f; return true; } void reset() { mCurrentGroundcover = 0.f; } private: float mCurrentGroundcover = 0.f; float mDensity = 0.f; }; class ViewDistanceCallback : public SceneUtil::NodeCallback { public: ViewDistanceCallback(float dist, const osg::BoundingBox& box) : mViewDistance(dist), mBox(box) {} void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) traverse(node, nv); } private: float mViewDistance; osg::BoundingBox mBox; }; inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) { osg::Vec2f size = maxBound - minBound; if (size.x() >=1 && size.y() >=1) return true; osg::Vec3f pos = ref.mPos.asVec3(); osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) return false; return true; } osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { if (lod > getMaxLodLevel()) return nullptr; GroundcoverChunkId id = std::make_tuple(center, size); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); else { InstanceMap instances; collectInstances(instances, size, center); osg::ref_ptr node = createChunk(instances, center); mCache->addEntryToObjectCache(id, node.get()); return node; } } Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) , mGroundcoverStore(store) { setViewDistance(viewDistance); // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties // Force a unified alpha handling instead of data from meshes osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); mStateset->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); mStateset->setAttributeAndModes(new osg::BlendFunc, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? Shader::ShaderManager::cloneProgram(mSceneManager->getShaderManager().getProgramTemplate()) : osg::ref_ptr(new osg::Program); mProgramTemplate->addBindAttribLocation("aOffset", 6); mProgramTemplate->addBindAttribLocation("aRotation", 7); } Groundcover::~Groundcover() { } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { if (mDensity <=0.f) return; osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); DensityCalculator calculator(mDensity); ESM::ReadersCache readers; osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { ESM::Cell cell; mGroundcoverStore.initCell(cell, cellX, cellY); if (cell.mContextList.empty()) continue; calculator.reset(); std::map refs; for (size_t i=0; i(cell.mContextList[i].index); const ESM::ReadersCache::BusyItem reader = readers.get(index); cell.restore(*reader, i); ESM::CellRef ref; ref.mRefNum.unset(); bool deleted = false; while (cell.getNextRef(*reader, ref, deleted)) { if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) deleted = true; if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) deleted = true; if (deleted) { refs.erase(ref.mRefNum); continue; } refs[ref.mRefNum] = std::move(ref); } } for (auto& pair : refs) { ESM::CellRef& ref = pair.second; const std::string& model = mGroundcoverStore.getGroundcoverModel(ref.mRefID); if (!model.empty()) instances[model].emplace_back(std::move(ref)); } } } } osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) { osg::ref_ptr group = new osg::Group; osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; for (auto& pair : instances) { const osg::Node* temp = mSceneManager->getTemplate(pair.first); osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES|osg::CopyOp::DEEP_COPY_USERDATA|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES)); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } osg::ComputeBoundsVisitor cbv; group->accept(cbv); osg::BoundingBox box = cbv.getBoundingBox(); group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box)); group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->addCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); return group; } unsigned int Groundcover::getNodeMask() { return Mask_Groundcover; } void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); } } openmw-openmw-0.48.0/apps/openmw/mwrender/groundcover.hpp000066400000000000000000000034131445372753700235760ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_GROUNDCOVER_H #define OPENMW_MWRENDER_GROUNDCOVER_H #include #include #include namespace MWWorld { class ESMStore; class GroundcoverStore; } namespace osg { class Program; } namespace MWRender { typedef std::tuple GroundcoverChunkId; // Center, Size class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store); ~Groundcover(); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; unsigned int getNodeMask() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; struct GroundcoverEntry { ESM::Position mPos; float mScale; GroundcoverEntry(const ESM::CellRef& ref) : mPos(ref.mPos), mScale(ref.mScale) {} }; private: Resource::SceneManager* mSceneManager; float mDensity; osg::ref_ptr mStateset; osg::ref_ptr mProgramTemplate; const MWWorld::GroundcoverStore& mGroundcoverStore; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/landmanager.cpp000066400000000000000000000023401445372753700235030ustar00rootroot00000000000000#include "landmanager.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender { LandManager::LandManager(int loadFlags) : GenericResourceManager >(nullptr) , mLoadFlags(loadFlags) { mCache = new CacheType; } osg::ref_ptr LandManager::getLand(int x, int y) { osg::ref_ptr obj = mCache->getRefFromObjectCache(std::make_pair(x,y)); if (obj) return static_cast(obj.get()); else { const auto world = MWBase::Environment::get().getWorld(); if (!world) return nullptr; const ESM::Land* land = world->getStore().get().search(x,y); if (!land) return nullptr; osg::ref_ptr landObj (new ESMTerrain::LandObject(land, mLoadFlags)); mCache->addEntryToObjectCache(std::make_pair(x,y), landObj.get()); return landObj; } } void LandManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); } } openmw-openmw-0.48.0/apps/openmw/mwrender/landmanager.hpp000066400000000000000000000012241445372753700235100ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_LANDMANAGER_H #define OPENMW_MWRENDER_LANDMANAGER_H #include #include #include namespace ESM { struct Land; } namespace MWRender { class LandManager : public Resource::GenericResourceManager > { public: LandManager(int loadFlags); /// @note Will return nullptr if not found. osg::ref_ptr getLand(int x, int y); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: int mLoadFlags; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/localmap.cpp000066400000000000000000000673341445372753700230400ustar00rootroot00000000000000#include "localmap.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "vismask.hpp" namespace { float square(float val) { return val*val; } std::pair divideIntoSegments(const osg::BoundingBox& bounds, float mapSize) { osg::Vec2f min(bounds.xMin(), bounds.yMin()); osg::Vec2f max(bounds.xMax(), bounds.yMax()); osg::Vec2f length = max - min; const int segsX = static_cast(std::ceil(length.x() / mapSize)); const int segsY = static_cast(std::ceil(length.y() / mapSize)); return {segsX, segsY}; } } namespace MWRender { class LocalMapRenderToTexture: public SceneUtil::RTTNode { public: LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax); void setDefaults(osg::Camera* camera) override; bool isActive() { return mActive; } void setIsActive(bool active) { mActive = active; } osg::Node* mSceneRoot; osg::Matrix mProjectionMatrix; osg::Matrix mViewMatrix; bool mActive; }; class CameraLocalUpdateCallback : public SceneUtil::NodeCallback { public: void operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv); }; LocalMap::LocalMap(osg::Group* root) : mRoot(root) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(Constants::CellSizeInUnits) , mCellDistance(Constants::CellGridRadius) , mAngle(0.f) , mInterior(false) { // Increase map resolution, if use UI scaling float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); mMapResolution *= uiScale; SceneUtil::FindByNameVisitor find("Scene Root"); mRoot->accept(find); mSceneRoot = find.mFoundNode; if (!mSceneRoot) throw std::runtime_error("no scene root found"); } LocalMap::~LocalMap() { for (auto& rtt : mLocalMapRTTs) mRoot->removeChild(rtt); } const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) { return osg::Vec2f( std::cos(angle) * (point.x() - center.x()) - std::sin(angle) * (point.y() - center.y()) + center.x(), std::sin(angle) * (point.x() - center.x()) + std::cos(angle) * (point.y() - center.y()) + center.y()); } void LocalMap::clear() { mExteriorSegments.clear(); mInteriorSegments.clear(); } void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { const MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (segment.mFogOfWarImage && segment.mHasFogState) { auto fog = std::make_unique(); fog->mFogTextures.emplace_back(); segment.saveFogOfWar(fog->mFogTextures.back()); cell->setFog(std::move(fog)); } } else { auto segments = divideIntoSegments(mBounds, mMapWorldSize); auto fog = std::make_unique(); fog->mBounds.mMinX = mBounds.xMin(); fog->mBounds.mMaxX = mBounds.xMax(); fog->mBounds.mMinY = mBounds.yMin(); fog->mBounds.mMaxY = mBounds.yMax(); fog->mNorthMarkerAngle = mAngle; fog->mFogTextures.reserve(segments.first * segments.second); for (int x = 0; x < segments.first; ++x) { for (int y = 0; y < segments.second; ++y) { const MapSegment& segment = mInteriorSegments[std::make_pair(x,y)]; fog->mFogTextures.emplace_back(); // saving even if !segment.mHasFogState so we don't mess up the segmenting // plus, older openmw versions can't deal with empty images segment.saveFogOfWar(fog->mFogTextures.back()); fog->mFogTextures.back().mX = x; fog->mFogTextures.back().mY = y; } } cell->setFog(std::move(fog)); } } void LocalMap::setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax) { mLocalMapRTTs.emplace_back(new LocalMapRenderToTexture(mSceneRoot, mMapResolution, mMapWorldSize, left, top, upVector, zmin, zmax)); mRoot->addChild(mLocalMapRTTs.back()); MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(segment_x, segment_y)] : mExteriorSegments[std::make_pair(segment_x, segment_y)]; segment.mMapTexture = static_cast(mLocalMapRTTs.back()->getColorTexture(nullptr)); } void LocalMap::requestMap(const MWWorld::CellStore* cell) { if (!cell->isExterior()) { requestInteriorMap(cell); return; } int cellX = cell->getCell()->getGridX(); int cellY = cell->getCell()->getGridY(); MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)]; const std::uint8_t neighbourFlags = getExteriorNeighbourFlags(cellX, cellY); if ((segment.mLastRenderNeighbourFlags & neighbourFlags) == neighbourFlags) return; requestExteriorMap(cell, segment); segment.mLastRenderNeighbourFlags = neighbourFlags; } void LocalMap::addCell(MWWorld::CellStore *cell) { if (cell->isExterior()) mExteriorSegments.emplace( std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()), MapSegment{}); } void LocalMap::removeExteriorCell(int x, int y) { mExteriorSegments.erase({ x, y }); } void LocalMap::removeCell(MWWorld::CellStore *cell) { saveFogOfWar(cell); if (cell->isExterior()) mExteriorSegments.erase({ cell->getCell()->getGridX(), cell->getCell()->getGridY() }); else mInteriorSegments.clear(); } osg::ref_ptr LocalMap::getMapTexture(int x, int y) { auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); SegmentMap::iterator found = segments.find(std::make_pair(x, y)); if (found == segments.end()) return osg::ref_ptr(); else return found->second.mMapTexture; } osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) { auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); SegmentMap::iterator found = segments.find(std::make_pair(x, y)); if (found == segments.end()) return osg::ref_ptr(); else return found->second.mFogOfWarTexture; } void LocalMap::cleanupCameras() { auto it = mLocalMapRTTs.begin(); while (it != mLocalMapRTTs.end()) { if (!(*it)->isActive()) { mRoot->removeChild(*it); it = mLocalMapRTTs.erase(it); } else it++; } } void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment) { mInterior = false; int x = cell->getCell()->getGridX(); int y = cell->getCell()->getGridY(); osg::BoundingSphere bound = mSceneRoot->getBound(); float zmin = bound.center().z() - bound.radius(); float zmax = bound.center().z() + bound.radius(); setupRenderToTexture(x, y, x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f, osg::Vec3d(0, 1, 0), zmin, zmax); if (segment.mFogOfWarImage != nullptr) return; if (cell->getFog()) segment.loadFogOfWar(cell->getFog()->mFogTextures.back()); else segment.initFogOfWar(); } void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object | Mask_Static); mSceneRoot->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); // If we're in an empty cell, bail out // The operations in this function are only valid for finite bounds if (!bounds.valid() || bounds.radius2() == 0.0) return; mInterior = true; mBounds = bounds; // Get the cell's NorthMarker rotation. This is used to rotate the entire map. osg::Vec2f north = MWBase::Environment::get().getWorld()->getNorthVector(cell); mAngle = std::atan2(north.x(), north.y()); // Rotate the cell and merge the rotated corners to the bounding box osg::Vec2f origCenter(bounds.center().x(), bounds.center().y()); osg::Vec3f origCorners[8]; for (int i=0; i<8; ++i) origCorners[i] = mBounds.corner(i); for (int i=0; i<8; ++i) { osg::Vec3f corner = origCorners[i]; osg::Vec2f corner2d (corner.x(), corner.y()); corner2d = rotatePoint(corner2d, origCenter, mAngle); mBounds.expandBy(osg::Vec3f(corner2d.x(), corner2d.y(), 0)); } // Do NOT change padding! This will break older savegames. // If the padding really needs to be changed, then it must be saved in the ESM::FogState and // assume the old (500) value as default for older savegames. const float padding = 500.0f; // Apply a little padding mBounds.set(mBounds._min - osg::Vec3f(padding,padding,0.f), mBounds._max + osg::Vec3f(padding,padding,0.f)); float zMin = mBounds.zMin(); float zMax = mBounds.zMax(); // If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks // to see if this state is still valid. // Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models. // If they changed by too much then parts of the interior might not be covered by the map anymore. // The following code detects this, and discards the CellStore's fog state if it needs to. std::vector> segmentMappings; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) { // Expand mBounds so the saved textures fit the same grid int xOffset = 0; int yOffset = 0; if(fog->mBounds.mMinX < mBounds.xMin()) { mBounds.xMin() = fog->mBounds.mMinX; } else if(fog->mBounds.mMinX > mBounds.xMin()) { float diff = fog->mBounds.mMinX - mBounds.xMin(); xOffset += diff / mMapWorldSize; xOffset++; mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; } if(fog->mBounds.mMinY < mBounds.yMin()) { mBounds.yMin() = fog->mBounds.mMinY; } else if(fog->mBounds.mMinY > mBounds.yMin()) { float diff = fog->mBounds.mMinY - mBounds.yMin(); yOffset += diff / mMapWorldSize; yOffset++; mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; } if (fog->mBounds.mMaxX > mBounds.xMax()) mBounds.xMax() = fog->mBounds.mMaxX; if (fog->mBounds.mMaxY > mBounds.yMax()) mBounds.yMax() = fog->mBounds.mMaxY; if(xOffset != 0 || yOffset != 0) Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; const auto& textures = fog->mFogTextures; segmentMappings.reserve(textures.size()); osg::BoundingBox savedBounds{ fog->mBounds.mMinX, fog->mBounds.mMinY, 0, fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0 }; auto segments = divideIntoSegments(savedBounds, mMapWorldSize); for (int x = 0; x < segments.first; ++x) for (int y = 0; y < segments.second; ++y) segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); mAngle = fog->mNorthMarkerAngle; } } osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); auto segments = divideIntoSegments(mBounds, mMapWorldSize); for (int x = 0; x < segments.first; ++x) { for (int y = 0; y < segments.second; ++y) { osg::Vec2f start = min + osg::Vec2f(mMapWorldSize*x, mMapWorldSize*y); osg::Vec2f newcenter = start + osg::Vec2f(mMapWorldSize/2.f, mMapWorldSize/2.f); osg::Vec2f a = newcenter - center; osg::Vec3f rotatedCenter = cameraOrient * (osg::Vec3f(a.x(), a.y(), 0)); osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; setupRenderToTexture(x, y, pos.x(), pos.y(), osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); auto coords = std::make_pair(x,y); MapSegment& segment = mInteriorSegments[coords]; if (!segment.mFogOfWarImage) { bool loaded = false; for(size_t index{}; index < segmentMappings.size(); index++) { if(segmentMappings[index] == coords) { ESM::FogState* fog = cell->getFog(); segment.loadFogOfWar(fog->mFogTextures[index]); loaded = true; break; } } if(!loaded) segment.initFogOfWar(); } } } } void LocalMap::worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y) { pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), mAngle); osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); x = static_cast(std::ceil((pos.x() - min.x()) / mMapWorldSize) - 1); y = static_cast(std::ceil((pos.y() - min.y()) / mMapWorldSize) - 1); nX = (pos.x() - min.x() - mMapWorldSize*x)/mMapWorldSize; nY = 1.0f-(pos.y() - min.y() - mMapWorldSize*y)/mMapWorldSize; } osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int y) { osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); osg::Vec2f pos (mMapWorldSize * (nX + x) + min.x(), mMapWorldSize * (1.0f-nY + y) + min.y()); pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), -mAngle); return pos; } bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) { auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); const MapSegment& segment = segments[std::make_pair(x, y)]; if (!segment.mFogOfWarImage) return false; nX = std::clamp(nX, 0.f, 1.f); nY = std::clamp(nY, 0.f, 1.f); int texU = static_cast((sFogOfWarResolution - 1) * nX); int texV = static_cast((sFogOfWarResolution - 1) * nY); uint32_t clr = ((const uint32_t*)segment.mFogOfWarImage->data())[texV * sFogOfWarResolution + texU]; uint8_t alpha = (clr >> 24); return alpha < 200; } osg::Group* LocalMap::getRoot() { return mRoot; } void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, float& u, float& v, int& x, int& y, osg::Vec3f& direction) { // retrieve the x,y grid coordinates the player is in osg::Vec2f pos(position.x(), position.y()); if (mInterior) { worldToInteriorMapPosition(pos, u,v, x,y); osg::Quat cameraOrient (mAngle, osg::Vec3(0,0,-1)); direction = orientation * cameraOrient.inverse() * osg::Vec3f(0,1,0); } else { direction = orientation * osg::Vec3f(0,1,0); x = static_cast(std::ceil(pos.x() / mMapWorldSize) - 1); y = static_cast(std::ceil(pos.y() / mMapWorldSize) - 1); // convert from world coordinates to texture UV coordinates u = std::abs((pos.x() - (mMapWorldSize*x))/mMapWorldSize); v = 1.0f-std::abs((pos.y() - (mMapWorldSize*y))/mMapWorldSize); } // explore radius (squared) const float exploreRadius = 0.17f * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 const float sqrExploreRadius = square(exploreRadius); const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) // change the affected fog of war textures (in a 3x3 grid around the player) for (int mx = -mCellDistance; mx<=mCellDistance; ++mx) { for (int my = -mCellDistance; my<=mCellDistance; ++my) { // is this texture affected at all? bool affected = false; if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid affected = true; else { bool affectsX = (mx > 0)? (u + exploreRadiusUV > 1) : (u - exploreRadiusUV < 0); bool affectsY = (my > 0)? (v + exploreRadiusUV > 1) : (v - exploreRadiusUV < 0); affected = (affectsX && (my == 0)) || (affectsY && mx == 0) || (affectsX && affectsY); } if (!affected) continue; int texX = x + mx; int texY = y + my*-1; auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); MapSegment& segment = segments[std::make_pair(texX, texY)]; if (!segment.mFogOfWarImage || !segment.mMapTexture) continue; uint32_t* data = (uint32_t*)segment.mFogOfWarImage->data(); bool changed = false; for (int texV = 0; texV> 24); alpha = std::min(alpha, (uint8_t)(std::clamp(sqrDist/sqrExploreRadius, 0.f, 1.f) * 255)); uint32_t val = (uint32_t) (alpha << 24); if ( *data != val) { *data = val; changed = true; } ++data; } } if (changed) { segment.mHasFogState = true; segment.mFogOfWarImage->dirty(); } } } } std::uint8_t LocalMap::getExteriorNeighbourFlags(int cellX, int cellY) const { constexpr std::tuple flags[] = { { NeighbourCellTopLeft, -1, -1 }, { NeighbourCellTopCenter, 0, -1 }, { NeighbourCellTopRight, 1, -1 }, { NeighbourCellMiddleLeft, -1, 0 }, { NeighbourCellMiddleRight, 1, 0 }, { NeighbourCellBottomLeft, -1, 1 }, { NeighbourCellBottomCenter, 0, 1 }, { NeighbourCellBottomRight, 1, 1 }, }; std::uint8_t result = 0; for (const auto& [flag, dx, dy] : flags) if (mExteriorSegments.contains({cellX + dx, cellY + dy})) result |= flag; return result; } void LocalMap::MapSegment::createFogOfWarTexture() { if (mFogOfWarTexture) return; mFogOfWarTexture = new osg::Texture2D; // TODO: synchronize access? for now, the worst that could happen is the draw thread jumping a frame ahead. //mFogOfWarTexture->setDataVariance(osg::Object::DYNAMIC); mFogOfWarTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mFogOfWarTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setUnRefImageDataAfterApply(false); mFogOfWarTexture->setImage(mFogOfWarImage); } void LocalMap::MapSegment::initFogOfWar() { mFogOfWarImage = new osg::Image; // Assign a PixelBufferObject for asynchronous transfer of data to the GPU mFogOfWarImage->setPixelBufferObject(new osg::PixelBufferObject); mFogOfWarImage->allocateImage(sFogOfWarResolution, sFogOfWarResolution, 1, GL_RGBA, GL_UNSIGNED_BYTE); assert(mFogOfWarImage->isDataContiguous()); std::vector data; data.resize(sFogOfWarResolution*sFogOfWarResolution, 0xff000000); memcpy(mFogOfWarImage->data(), &data[0], data.size()*4); createFogOfWarTexture(); } void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) { const std::vector& data = esm.mImageData; if (data.empty()) { initFogOfWar(); return; } osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { Log(Debug::Error) << "Error: Unable to load fog, can't find a png ReaderWriter" ; return; } Files::IMemStream in(&data[0], data.size()); osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); if (!result.success()) { Log(Debug::Error) << "Error: Failed to read fog: " << result.message() << " code " << result.status(); return; } mFogOfWarImage = result.getImage(); mFogOfWarImage->flipVertical(); mFogOfWarImage->dirty(); createFogOfWarTexture(); mHasFogState = true; } void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const { if (!mFogOfWarImage) return; std::ostringstream ostream; osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { Log(Debug::Error) << "Error: Unable to write fog, can't find a png ReaderWriter"; return; } // extra flips are unfortunate, but required for compatibility with older versions mFogOfWarImage->flipVertical(); osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); if (!result.success()) { Log(Debug::Error) << "Error: Unable to write fog: " << result.message() << " code " << result.status(); return; } mFogOfWarImage->flipVertical(); std::string data = ostream.str(); fog.mImageData = std::vector(data.begin(), data.end()); } LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax) : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders) , mSceneRoot(sceneRoot) , mActive(true) { setNodeMask(Mask_RenderToTexture); if (SceneUtil::AutoDepth::isReversed()) mProjectionMatrix = SceneUtil::getReversedZProjectionMatrixAsOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); else mProjectionMatrix.makeOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); setUpdateCallback(new CameraLocalUpdateCallback); setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) { // Disable small feature culling, it's not going to be reliable for this camera osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); camera->setCullingMode(cullingMode); SceneUtil::setCameraClearDepth(camera); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); camera->setCullMaskLeft(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); camera->setCullMaskRight(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); camera->setNodeMask(Mask_RenderToTexture); camera->setProjectionMatrix(mProjectionMatrix); camera->setViewMatrix(mViewMatrix); auto* stateset = camera->getOrCreateStateSet(); stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mProjectionMatrix)), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); if (Stereo::getMultiview()) Stereo::setMultiviewMatrices(stateset, { mProjectionMatrix, mProjectionMatrix }); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog(new osg::Fog); fog->setStart(10000000); fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); // turn of sky blending stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); stateset->addUniform(new osg::Uniform("sky", 0)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{1, 1})); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); osg::ref_ptr light = new osg::Light; light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); light->setAmbient(osg::Vec4(0, 0, 0, 1)); light->setSpecular(osg::Vec4(0, 0, 0, 0)); light->setLightNum(0); light->setConstantAttenuation(1.f); light->setLinearAttenuation(0.f); light->setQuadraticAttenuation(0.f); osg::ref_ptr lightSource = new osg::LightSource; lightSource->setLight(light); lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); camera->addChild(lightSource); camera->addChild(mSceneRoot); } void CameraLocalUpdateCallback::operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv) { if (!node->isActive()) node->setNodeMask(0); if (node->isActive()) { node->setIsActive(false); } // Rtt-nodes do not forward update traversal to their cameras so we can traverse safely. // Traverse in case there are nested callbacks. traverse(node, nv); } } openmw-openmw-0.48.0/apps/openmw/mwrender/localmap.hpp000066400000000000000000000117371445372753700230410ustar00rootroot00000000000000#ifndef GAME_RENDER_LOCALMAP_H #define GAME_RENDER_LOCALMAP_H #include #include #include #include #include #include #include namespace MWWorld { class CellStore; } namespace ESM { struct FogTexture; } namespace osg { class Texture2D; class Image; class Camera; class Group; class Node; } namespace MWRender { class LocalMapRenderToTexture; /// /// \brief Local map rendering /// class LocalMap { public: LocalMap(osg::Group* root); ~LocalMap(); /** * Clear all savegame-specific data (i.e. fog of war textures) */ void clear(); /** * Request a map render for the given cell. Render textures will be immediately created and can be retrieved with the getMapTexture function. */ void requestMap (const MWWorld::CellStore* cell); void addCell(MWWorld::CellStore* cell); void removeExteriorCell(int x, int y); void removeCell (MWWorld::CellStore* cell); osg::ref_ptr getMapTexture (int x, int y); osg::ref_ptr getFogOfWarTexture (int x, int y); /** * Removes cameras that have already been rendered. Should be called every frame to ensure that * we do not render the same map more than once. Note, this cleanup is difficult to implement in an * automated fashion, since we can't alter the scene graph structure from within an update callback. */ void cleanupCameras(); /** * Set the position & direction of the player, and returns the position in map space through the reference parameters. * @remarks This is used to draw a "fog of war" effect * to hide areas on the map the player has not discovered yet. */ void updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, float& u, float& v, int& x, int& y, osg::Vec3f& direction); /** * Save the fog of war for this cell to its CellStore. * @remarks This should be called when unloading a cell, and for all active cells prior to saving the game. */ void saveFogOfWar(MWWorld::CellStore* cell); /** * Get the interior map texture index and normalized position on this texture, given a world position */ void worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y); osg::Vec2f interiorMapToWorldPosition (float nX, float nY, int x, int y); /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) */ bool isPositionExplored (float nX, float nY, int x, int y); osg::Group* getRoot(); private: osg::ref_ptr mRoot; osg::ref_ptr mSceneRoot; typedef std::vector< osg::ref_ptr > RTTVector; RTTVector mLocalMapRTTs; typedef std::set > Grid; Grid mCurrentGrid; enum NeighbourCellFlag : std::uint8_t { NeighbourCellTopLeft = 1, NeighbourCellTopCenter = 1 << 1, NeighbourCellTopRight = 1 << 2, NeighbourCellMiddleLeft = 1 << 3, NeighbourCellMiddleRight = 1 << 4, NeighbourCellBottomLeft = 1 << 5, NeighbourCellBottomCenter = 1 << 6, NeighbourCellBottomRight = 1 << 7, }; struct MapSegment { void initFogOfWar(); void loadFogOfWar(const ESM::FogTexture& fog); void saveFogOfWar(ESM::FogTexture& fog) const; void createFogOfWarTexture(); std::uint8_t mLastRenderNeighbourFlags = 0; bool mHasFogState = false; osg::ref_ptr mMapTexture; osg::ref_ptr mFogOfWarTexture; osg::ref_ptr mFogOfWarImage; }; typedef std::map, MapSegment> SegmentMap; SegmentMap mExteriorSegments; SegmentMap mInteriorSegments; int mMapResolution; // the dynamic texture is a bottleneck, so don't set this too high static const int sFogOfWarResolution = 32; // size of a map segment (for exteriors, 1 cell) float mMapWorldSize; int mCellDistance; float mAngle; const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); void requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment); void requestInteriorMap(const MWWorld::CellStore* cell); void setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax); bool mInterior; osg::BoundingBox mBounds; std::uint8_t getExteriorNeighbourFlags(int cellX, int cellY) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/luminancecalculator.cpp000066400000000000000000000157631445372753700252740ustar00rootroot00000000000000#include "luminancecalculator.hpp" #include #include #include "pingpongcanvas.hpp" namespace MWRender { LuminanceCalculator::LuminanceCalculator(Shader::ShaderManager& shaderManager) { const float hdrExposureTime = std::max(Settings::Manager::getFloat("auto exposure speed", "Post Processing"), 0.0001f); constexpr float minLog = -9.0; constexpr float maxLog = 4.0; constexpr float logLumRange = (maxLog - minLog); constexpr float invLogLumRange = 1.0 / logLumRange; constexpr float epsilon = 0.004; Shader::ShaderManager::DefineMap defines = { {"minLog", std::to_string(minLog)}, {"maxLog", std::to_string(maxLog)}, {"logLumRange", std::to_string(logLumRange)}, {"invLogLumRange", std::to_string(invLogLumRange)}, {"hdrExposureTime", std::to_string(hdrExposureTime)}, {"epsilon", std::to_string(epsilon)}, }; auto vertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", {}, osg::Shader::VERTEX); auto luminanceFragment = shaderManager.getShader("hdr_luminance_fragment.glsl", defines, osg::Shader::FRAGMENT); auto resolveFragment = shaderManager.getShader("hdr_resolve_fragment.glsl", defines, osg::Shader::FRAGMENT); mResolveProgram = shaderManager.getProgram(vertex, resolveFragment); mLuminanceProgram = shaderManager.getProgram(vertex, luminanceFragment); } void LuminanceCalculator::compile() { int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); for (auto& buffer : mBuffers) { buffer.mipmappedSceneLuminanceTex = new osg::Texture2D; buffer.mipmappedSceneLuminanceTex->setInternalFormat(GL_R16F); buffer.mipmappedSceneLuminanceTex->setSourceFormat(GL_RED); buffer.mipmappedSceneLuminanceTex->setSourceType(GL_FLOAT); buffer.mipmappedSceneLuminanceTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); buffer.mipmappedSceneLuminanceTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); buffer.luminanceTex = new osg::Texture2D; buffer.luminanceTex->setInternalFormat(GL_R16F); buffer.luminanceTex->setSourceFormat(GL_RED); buffer.luminanceTex->setSourceType(GL_FLOAT); buffer.luminanceTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); buffer.luminanceTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); buffer.luminanceTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); buffer.luminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); buffer.luminanceTex->setTextureSize(1, 1); buffer.luminanceProxyTex = new osg::Texture2D(*buffer.luminanceTex); buffer.luminanceProxyTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); buffer.luminanceProxyTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); buffer.resolveFbo = new osg::FrameBufferObject; buffer.resolveFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.luminanceTex)); buffer.luminanceProxyFbo = new osg::FrameBufferObject; buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.luminanceProxyTex)); buffer.resolveSceneLumFbo = new osg::FrameBufferObject; buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); buffer.sceneLumFbo = new osg::FrameBufferObject; buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); buffer.sceneLumSS = new osg::StateSet; buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram); buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0)); buffer.resolveSS = new osg::StateSet; buffer.resolveSS->setAttributeAndModes(mResolveProgram); buffer.resolveSS->setTextureAttributeAndModes(0, buffer.luminanceProxyTex); buffer.resolveSS->addUniform(new osg::Uniform("luminanceSceneTex", 0)); buffer.resolveSS->addUniform(new osg::Uniform("prevLuminanceSceneTex", 1)); } mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex); mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex); mCompiled = true; } void LuminanceCalculator::draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId) { if (!mEnabled) return; bool dirty = !mCompiled; if (dirty) compile(); auto& buffer = mBuffers[frameId]; buffer.sceneLumFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); buffer.sceneLumSS->setTextureAttributeAndModes(0, canvas.getSceneTexture(frameId)); state.apply(buffer.sceneLumSS); canvas.drawGeometry(renderInfo); state.applyTextureAttribute(0, buffer.mipmappedSceneLuminanceTex); ext->glGenerateMipmap(GL_TEXTURE_2D); buffer.resolveSceneLumFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); if (dirty) { // Use current frame data for previous frame to warm up calculations and prevent popin mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); } buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); state.apply(buffer.resolveSS); canvas.drawGeometry(renderInfo); ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0); } osg::ref_ptr LuminanceCalculator::getLuminanceTexture(size_t frameId) const { return mBuffers[frameId].luminanceTex; } }openmw-openmw-0.48.0/apps/openmw/mwrender/luminancecalculator.hpp000066400000000000000000000035711445372753700252730ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_LUMINANCECALCULATOR_H #define OPENMW_MWRENDER_LUMINANCECALCULATOR_H #include #include #include #include namespace Shader { class ShaderManager; } namespace MWRender { class PingPongCanvas; class LuminanceCalculator { public: LuminanceCalculator() = default; LuminanceCalculator(Shader::ShaderManager& shaderManager); void draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId); bool isEnabled() const { return mEnabled; } void enable() { mEnabled = true; } void disable() { mEnabled = false; } void dirty(int w, int h) { constexpr float scale = 0.5; mWidth = w * scale; mHeight = h * scale; mCompiled = false; } osg::ref_ptr getLuminanceTexture(size_t frameId) const; private: void compile(); struct Container { osg::ref_ptr sceneLumFbo; osg::ref_ptr resolveSceneLumFbo; osg::ref_ptr resolveFbo; osg::ref_ptr luminanceProxyFbo; osg::ref_ptr mipmappedSceneLuminanceTex; osg::ref_ptr luminanceTex; osg::ref_ptr luminanceProxyTex; osg::ref_ptr sceneLumSS; osg::ref_ptr resolveSS; }; std::array mBuffers; osg::ref_ptr mLuminanceProgram; osg::ref_ptr mResolveProgram; bool mCompiled = false; bool mEnabled = false; int mWidth = 1; int mHeight = 1; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/navmesh.cpp000066400000000000000000000252261445372753700227030ustar00rootroot00000000000000#include "navmesh.hpp" #include "vismask.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include namespace MWRender { struct NavMesh::LessByTilePosition { bool operator()(const DetourNavigator::TilePosition& lhs, const std::pair& rhs) const { return lhs < rhs.first; } bool operator()(const std::pair& lhs, const DetourNavigator::TilePosition& rhs) const { return lhs.first < rhs; } }; struct NavMesh::CreateNavMeshTileGroups final : SceneUtil::WorkItem { std::size_t mId; DetourNavigator::Version mVersion; const std::weak_ptr mNavMesh; const osg::ref_ptr mGroupStateSet; const osg::ref_ptr mDebugDrawStateSet; const DetourNavigator::Settings mSettings; std::map mTiles; NavMeshMode mMode; std::atomic_bool mAborted {false}; std::mutex mMutex; bool mStarted = false; std::vector> mUpdatedTiles; std::vector mRemovedTiles; explicit CreateNavMeshTileGroups(std::size_t id, DetourNavigator::Version version, std::weak_ptr navMesh, const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, const DetourNavigator::Settings& settings, const std::map& tiles, NavMeshMode mode) : mId(id) , mVersion(version) , mNavMesh(navMesh) , mGroupStateSet(groupStateSet) , mDebugDrawStateSet(debugDrawStateSet) , mSettings(settings) , mTiles(tiles) , mMode(mode) { } void doWork() final { using DetourNavigator::TilePosition; using DetourNavigator::Version; const std::lock_guard lock(mMutex); mStarted = true; if (mAborted.load(std::memory_order_acquire)) return; const auto navMeshPtr = mNavMesh.lock(); if (navMeshPtr == nullptr) return; std::vector> existingTiles; unsigned minSalt = std::numeric_limits::max(); unsigned maxSalt = 0; navMeshPtr->lockConst()->forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& meshTile) { existingTiles.emplace_back(position, version); minSalt = std::min(minSalt, meshTile.salt); maxSalt = std::max(maxSalt, meshTile.salt); }); if (mAborted.load(std::memory_order_acquire)) return; std::sort(existingTiles.begin(), existingTiles.end()); std::vector removedTiles; for (const auto& [position, tile] : mTiles) if (!std::binary_search(existingTiles.begin(), existingTiles.end(), position, LessByTilePosition {})) removedTiles.push_back(position); std::vector> updatedTiles; const unsigned char flags = SceneUtil::NavMeshTileDrawFlagsOffMeshConnections | SceneUtil::NavMeshTileDrawFlagsClosedList | (mMode == NavMeshMode::UpdateFrequency ? SceneUtil::NavMeshTileDrawFlagsHeat : 0); for (const auto& [position, version] : existingTiles) { const auto it = mTiles.find(position); if (it != mTiles.end() && it->second.mGroup != nullptr && it->second.mVersion == version && mMode != NavMeshMode::UpdateFrequency) continue; osg::ref_ptr group; { const auto navMesh = navMeshPtr->lockConst(); const dtMeshTile* meshTile = DetourNavigator::getTile(navMesh->getImpl(), position); if (meshTile == nullptr) continue; if (mAborted.load(std::memory_order_acquire)) return; group = SceneUtil::createNavMeshTileGroup(navMesh->getImpl(), *meshTile, mSettings, mGroupStateSet, mDebugDrawStateSet, flags, minSalt, maxSalt); } if (group == nullptr) { removedTiles.push_back(position); continue; } MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); updatedTiles.emplace_back(position, Tile {version, std::move(group)}); } if (mAborted.load(std::memory_order_acquire)) return; mUpdatedTiles = std::move(updatedTiles); mRemovedTiles = std::move(removedTiles); } void abort() final { mAborted.store(true, std::memory_order_release); } }; struct NavMesh::DeallocateCreateNavMeshTileGroups final : SceneUtil::WorkItem { osg::ref_ptr mWorkItem; explicit DeallocateCreateNavMeshTileGroups(osg::ref_ptr&& workItem) : mWorkItem(std::move(workItem)) {} }; NavMesh::NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, bool enabled, NavMeshMode mode) : mRootNode(root) , mWorkQueue(workQueue) , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) , mDebugDrawStateSet(SceneUtil::DebugDraw::makeStateSet()) , mEnabled(enabled) , mMode(mode) , mId(std::numeric_limits::max()) { } NavMesh::~NavMesh() { if (mEnabled) disable(); for (const auto& workItem : mWorkItems) workItem->abort(); } bool NavMesh::toggle() { if (mEnabled) disable(); else enable(); return mEnabled; } void NavMesh::update(const std::shared_ptr& navMesh, std::size_t id, const DetourNavigator::Settings& settings) { using DetourNavigator::TilePosition; using DetourNavigator::Version; if (!mEnabled) return; { std::pair lastest {0, Version {}}; osg::ref_ptr latestCandidate; for (auto it = mWorkItems.begin(); it != mWorkItems.end();) { if (!(*it)->isDone()) { ++it; continue; } const std::pair order {(*it)->mId, (*it)->mVersion}; if (lastest < order) { lastest = order; std::swap(latestCandidate, *it); } if (*it != nullptr) mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(*it))); it = mWorkItems.erase(it); } if (latestCandidate != nullptr) { for (const TilePosition& position : latestCandidate->mRemovedTiles) { const auto it = mTiles.find(position); if (it == mTiles.end()) continue; mRootNode->removeChild(it->second.mGroup); mTiles.erase(it); } for (auto& [position, tile] : latestCandidate->mUpdatedTiles) { const auto it = mTiles.find(position); if (it == mTiles.end()) { mRootNode->addChild(tile.mGroup); mTiles.emplace_hint(it, position, std::move(tile)); } else { mRootNode->replaceChild(it->second.mGroup, tile.mGroup); std::swap(it->second, tile); } } mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(latestCandidate))); } } const auto version = navMesh->lock()->getVersion(); if (!mTiles.empty() && mId == id && mVersion == version) return; if (mId != id) { reset(); mId = id; } mVersion = version; for (auto& workItem : mWorkItems) { const std::unique_lock lock(workItem->mMutex, std::try_to_lock); if (!lock.owns_lock()) continue; if (workItem->mStarted) continue; workItem->mId = id; workItem->mVersion = version; workItem->mTiles = mTiles; workItem->mMode = mMode; return; } osg::ref_ptr workItem = new CreateNavMeshTileGroups(id, version, navMesh, mGroupStateSet, mDebugDrawStateSet, settings, mTiles, mMode); mWorkQueue->addWorkItem(workItem); mWorkItems.push_back(std::move(workItem)); } void NavMesh::reset() { for (auto& workItem : mWorkItems) workItem->abort(); mWorkItems.clear(); for (auto& [position, tile] : mTiles) mRootNode->removeChild(tile.mGroup); mTiles.clear(); } void NavMesh::enable() { mEnabled = true; } void NavMesh::disable() { reset(); mEnabled = false; } void NavMesh::setMode(NavMeshMode value) { if (mMode == value) return; reset(); mMode = value; } } openmw-openmw-0.48.0/apps/openmw/mwrender/navmesh.hpp000066400000000000000000000036731445372753700227120ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H #include "navmeshmode.hpp" #include #include #include #include #include #include #include #include #include class dtNavMesh; namespace osg { class Group; class Geometry; class StateSet; } namespace DetourNavigator { class NavMeshCacheItem; struct Settings; } namespace SceneUtil { class WorkQueue; } namespace MWRender { class NavMesh { public: explicit NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, bool enabled, NavMeshMode mode); ~NavMesh(); bool toggle(); void update(const std::shared_ptr>& navMesh, std::size_t id, const DetourNavigator::Settings& settings); void reset(); void enable(); void disable(); bool isEnabled() const { return mEnabled; } void setMode(NavMeshMode value); private: struct Tile { DetourNavigator::Version mVersion; osg::ref_ptr mGroup; }; struct LessByTilePosition; struct CreateNavMeshTileGroups; struct DeallocateCreateNavMeshTileGroups; osg::ref_ptr mRootNode; osg::ref_ptr mWorkQueue; osg::ref_ptr mGroupStateSet; osg::ref_ptr mDebugDrawStateSet; bool mEnabled; NavMeshMode mMode; std::size_t mId; DetourNavigator::Version mVersion; std::map mTiles; std::vector> mWorkItems; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/navmeshmode.cpp000066400000000000000000000006531445372753700235450ustar00rootroot00000000000000#include "navmeshmode.hpp" #include #include namespace MWRender { NavMeshMode parseNavMeshMode(std::string_view value) { if (value == "area type") return NavMeshMode::AreaType; if (value == "update frequency") return NavMeshMode::UpdateFrequency; throw std::logic_error("Unsupported navigation mesh rendering mode: " + std::string(value)); } } openmw-openmw-0.48.0/apps/openmw/mwrender/navmeshmode.hpp000066400000000000000000000004221445372753700235440ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_NAVMESHMODE_H #define OPENMW_MWRENDER_NAVMESHMODE_H #include namespace MWRender { enum class NavMeshMode { AreaType, UpdateFrequency, }; NavMeshMode parseNavMeshMode(std::string_view value); } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/npcanimation.cpp000066400000000000000000001365211445372753700237230ustar00rootroot00000000000000#include "npcanimation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "camera.hpp" #include "rotatecontroller.hpp" #include "renderbin.hpp" #include "vismask.hpp" #include "util.hpp" #include "postprocessor.hpp" namespace { std::string getVampireHead(const std::string& race, bool female, const VFS::Manager& vfs) { static std::map , const ESM::BodyPart* > sVampireMapping; std::pair thisCombination = std::make_pair(race, int(female)); if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (const ESM::BodyPart& bodypart : store.get()) { if (!bodypart.mData.mVampire) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) continue; if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) continue; sVampireMapping[thisCombination] = &bodypart; } } sVampireMapping.emplace(thisCombination, nullptr); const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) return std::string(); return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel, &vfs); } } namespace MWRender { class HeadAnimationTime : public SceneUtil::ControllerSource { private: MWWorld::Ptr mReference; float mTalkStart; float mTalkStop; float mBlinkStart; float mBlinkStop; float mBlinkTimer; bool mEnabled; float mValue; private: void resetBlinkTimer(); public: HeadAnimationTime(const MWWorld::Ptr& reference); void updatePtr(const MWWorld::Ptr& updated); void update(float dt); void setEnabled(bool enabled); void setTalkStart(float value); void setTalkStop(float value); void setBlinkStart(float value); void setBlinkStop(float value); float getValue(osg::NodeVisitor* nv) override; }; // -------------------------------------------------------------------------------------------------------------- HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference) : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mEnabled(true), mValue(0) { resetBlinkTimer(); } void HeadAnimationTime::updatePtr(const MWWorld::Ptr &updated) { mReference = updated; } void HeadAnimationTime::setEnabled(bool enabled) { mEnabled = enabled; } void HeadAnimationTime::resetBlinkTimer() { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6, prng)); } void HeadAnimationTime::update(float dt) { if (!mEnabled) return; if (!MWBase::Environment::get().getSoundManager()->sayActive(mReference)) { mBlinkTimer += dt; float duration = mBlinkStop - mBlinkStart; if (mBlinkTimer >= 0 && mBlinkTimer <= duration) { mValue = mBlinkStart + mBlinkTimer; } else mValue = mBlinkStop; if (mBlinkTimer > duration) resetBlinkTimer(); } else { // FIXME: would be nice to hold on to the SoundPtr so we don't have to retrieve it every frame mValue = mTalkStart + (mTalkStop - mTalkStart) * std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) } } float HeadAnimationTime::getValue(osg::NodeVisitor*) { return mValue; } void HeadAnimationTime::setTalkStart(float value) { mTalkStart = value; } void HeadAnimationTime::setTalkStop(float value) { mTalkStop = value; } void HeadAnimationTime::setBlinkStart(float value) { mBlinkStart = value; } void HeadAnimationTime::setBlinkStop(float value) { mBlinkStop = value; } // ---------------------------------------------------- NpcAnimation::NpcType NpcAnimation::getNpcType() const { const MWWorld::Class &cls = mPtr.getClass(); // Dead vampires should typically stay vampires. if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf()) return mNpcType; return getNpcType(mPtr); } NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr) { const MWWorld::Class &cls = ptr.getClass(); NpcAnimation::NpcType curType = Type_Normal; if (cls.getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) curType = Type_Vampire; if (cls.getNpcStats(ptr).isWerewolf()) curType = Type_Werewolf; return curType; } static const inline NpcAnimation::PartBoneMap createPartListMap() { return { {ESM::PRT_Head, "Head"}, {ESM::PRT_Hair, "Head"}, // note it uses "Head" as attach bone, but "Hair" as filter {ESM::PRT_Neck, "Neck"}, {ESM::PRT_Cuirass, "Chest"}, {ESM::PRT_Groin, "Groin"}, {ESM::PRT_Skirt, "Groin"}, {ESM::PRT_RHand, "Right Hand"}, {ESM::PRT_LHand, "Left Hand"}, {ESM::PRT_RWrist, "Right Wrist"}, {ESM::PRT_LWrist, "Left Wrist"}, {ESM::PRT_Shield, "Shield Bone"}, {ESM::PRT_RForearm, "Right Forearm"}, {ESM::PRT_LForearm, "Left Forearm"}, {ESM::PRT_RUpperarm, "Right Upper Arm"}, {ESM::PRT_LUpperarm, "Left Upper Arm"}, {ESM::PRT_RFoot, "Right Foot"}, {ESM::PRT_LFoot, "Left Foot"}, {ESM::PRT_RAnkle, "Right Ankle"}, {ESM::PRT_LAnkle, "Left Ankle"}, {ESM::PRT_RKnee, "Right Knee"}, {ESM::PRT_LKnee, "Left Knee"}, {ESM::PRT_RLeg, "Right Upper Leg"}, {ESM::PRT_LLeg, "Left Upper Leg"}, {ESM::PRT_RPauldron, "Right Clavicle"}, {ESM::PRT_LPauldron, "Left Clavicle"}, {ESM::PRT_Weapon, "Weapon Bone"}, // Fallback. The real node name depends on the current weapon type. {ESM::PRT_Tail, "Tail"}}; } const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { mAmmunition.reset(); } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) : ActorAnimation(ptr, parentNode, resourceSystem), mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), mNpcType(getNpcType(ptr)), mFirstPersonFieldOfView(firstPersonFieldOfView), mSoundsDisabled(disableSounds), mAccurateAiming(false), mAimingFactor(0.f) { mNpc = mPtr.get()->mBase; mHeadAnimationTime = std::make_shared(mPtr); mWeaponAnimationTime = std::make_shared(this); for(size_t i = 0;i < ESM::PRT_Count;i++) { mPartslots[i] = -1; //each slot is empty mPartPriorities[i] = 0; } std::fill(mSounds.begin(), mSounds.end(), nullptr); updateNpcBase(); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) { assert(viewMode != VM_HeadOnly); if(mViewMode == viewMode) return; // FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but // third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues. bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson; mViewMode = viewMode; MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change mAmmunition.reset(); rebuild(); setRenderBin(); static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (viewChange && shieldSheathing) { int weaptype = ESM::Weapon::None; MWMechanics::getActiveWeapon(mPtr, &weaptype); showCarriedLeft(updateCarriedLeftVisible(weaptype)); } } /// @brief A RenderBin callback to clear the depth buffer before rendering. /// Switches depth attachments to a proxy renderbuffer, reattaches original depth then redraws first person root. /// This gives a complete depth buffer which can be used for postprocessing, buffer resolves as if depth was never cleared. class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: DepthClearCallback() { mDepth = new SceneUtil::AutoDepth; mDepth->setWriteMask(true); mStateSet = new osg::StateSet; mStateSet->setAttributeAndModes(new osg::ColorMask(false, false, false, false), osg::StateAttribute::ON); mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override { osg::State* state = renderInfo.getState(); PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); state->applyAttribute(mDepth); unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)) { postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); auto primaryFBO = postProcessor->getPrimaryFbo(frameId); if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); else primaryFBO->apply(*state); // depth accumulation pass osg::ref_ptr restore = bin->getStateSet(); bin->setStateSet(mStateSet); bin->drawImplementation(renderInfo, previous); bin->setStateSet(restore); if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) primaryFBO->apply(*state); } else { // fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); bin->drawImplementation(renderInfo, previous); } state->checkGLErrors("after DepthClearCallback::drawImplementation"); } osg::ref_ptr mDepth; osg::ref_ptr mStateSet; }; /// Overrides Field of View to given value for rendering the subgraph. /// Must be added as cull callback. class OverrideFieldOfViewCallback : public osg::NodeCallback { public: OverrideFieldOfViewCallback(float fov) : mFov(fov) { } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); float fov, aspect, zNear, zFar; if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar) && std::abs(fov-mFov) > 0.001) { fov = mFov; osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); viewMatrix->postMult(*newProjectionMatrix); viewMatrix->postMult(*invertedOldMatrix); cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); traverse(node, nv); cv->popModelViewMatrix(); } else traverse(node, nv); } private: float mFov; }; void NpcAnimation::setRenderBin() { if (mViewMode == VM_FirstPerson) { static bool prototypeAdded = false; if (!prototypeAdded) { osg::ref_ptr depthClearBin (new osgUtil::RenderBin); depthClearBin->setDrawCallback(new DepthClearCallback); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); } else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) stateset->setRenderBinToInherit(); } void NpcAnimation::rebuild() { mScabbard.reset(); mHolsteredShield.reset(); updateNpcBase(); MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); } int NpcAnimation::getSlot(const osg::NodePath &path) const { for (int i=0; igetNode().get()) != path.end()) { return mPartslots[i]; } } return -1; } void NpcAnimation::updateNpcBase() { clearAnimSources(); for(size_t i = 0;i < ESM::PRT_Count;i++) removeIndividualPart((ESM::PartReferenceType)i); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); NpcType curType = getNpcType(); bool isWerewolf = (curType == Type_Werewolf); bool isVampire = (curType == Type_Vampire); bool isFemale = !mNpc->isMale(); mHeadModel.clear(); mHairModel.clear(); std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead; std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair; if (!headName.empty()) { const ESM::BodyPart* bp = store.get().search(headName); if (bp) mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); else Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; } if (!hairName.empty()) { const ESM::BodyPart* bp = store.get().search(hairName); if (bp) mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); else Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; } const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale, *mResourceSystem->getVFS()); if (!isWerewolf && isVampire && !vampireHead.empty()) mHeadModel = vampireHead; bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) smodel = Misc::ResourceHelpers::correctActorModelPath( Misc::ResourceHelpers::correctMeshPath(mNpc->mModel, mResourceSystem->getVFS()), mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); updateParts(); if(!is1stPerson) { const std::string base = Settings::Manager::getString("xbaseanim", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); if (smodel != defaultSkeleton && base != defaultSkeleton) addAnimSource(defaultSkeleton, smodel); addAnimSource(smodel, smodel); if(!isWerewolf && Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else { const std::string base = Settings::Manager::getString("xbaseanim1st", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); addAnimSource(smodel, smodel); mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); } mWeaponAnimationTime->updateStartTime(); } std::string NpcAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const { std::string mesh = getShieldMesh(shield, !mNpc->isMale()); if (mesh.empty()) return std::string(); std::string holsteredName = mesh; holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); if(mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); shieldTemplate->accept(findVisitor); osg::ref_ptr sheathNode = findVisitor.mFoundNode; if(!sheathNode) return std::string(); } return mesh; } void NpcAnimation::updateParts() { if (!mObjectRoot.get()) return; NpcType curType = getNpcType(); if (curType != mNpcType) { mNpcType = curType; rebuild(); return; } static const struct { int mSlot; int mBasePriority; } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. { MWWorld::InventoryStore::Slot_Robe, 11 }, { MWWorld::InventoryStore::Slot_Skirt, 3 }, { MWWorld::InventoryStore::Slot_Helmet, 0 }, { MWWorld::InventoryStore::Slot_Cuirass, 0 }, { MWWorld::InventoryStore::Slot_Greaves, 0 }, { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, { MWWorld::InventoryStore::Slot_Boots, 0 }, { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, { MWWorld::InventoryStore::Slot_Shirt, 0 }, { MWWorld::InventoryStore::Slot_Pants, 0 }, { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, { MWWorld::InventoryStore::Slot_CarriedRight, 0 } }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); bool wasArrowAttached = isArrowAttached(); mAmmunition.reset(); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { MWWorld::ConstContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); removePartGroup(slotlist[i].mSlot); if(store == inv.end()) continue; if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) removeIndividualPart(ESM::PRT_Hair); int prio = 1; bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store); if(store->getType() == ESM::Clothing::sRecordId) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } else if(store->getType() == ESM::Armor::sRecordId) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); } if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) { ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; size_t parts_size = sizeof(parts)/sizeof(parts[0]); for(size_t p = 0;p < parts_size;++p) reserveIndividualPart(parts[p], slotlist[i].mSlot, prio); } else if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Skirt) { reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio); reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio); reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio); } } if(mViewMode != VM_FirstPerson) { if(mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel); if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); } if(mViewMode == VM_HeadOnly) return; if(mPartPriorities[ESM::PRT_Shield] < 1) { MWWorld::ConstContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); MWWorld::ConstPtr part; if(store != inv.end() && (part=*store).getType() == ESM::Light::sRecordId) { const ESM::Light *light = part.get()->mBase; const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, Misc::ResourceHelpers::correctMeshPath(light->mModel, vfs), false, nullptr, true); if (mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light); } } showWeapons(mShowWeapons); showCarriedLeft(mShowCarriedLeft); bool isWerewolf = (getNpcType() == Type_Werewolf); std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); const std::vector &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { if(mPartPriorities[part] < 1) { const ESM::BodyPart* bodypart = parts[part]; if(bodypart) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(static_cast(part), -1, 1, Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs)); } } } if (wasArrowAttached) attachArrow(); } PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { osg::ref_ptr attached = attach(model, bonename, bonefilter, isLight); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(attached)); } osg::Vec3f NpcAnimation::runAnimation(float timepassed) { osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); if (mFirstPersonNeckController) { if (mAccurateAiming) mAimingFactor = 1.f; else mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f); float rotateFactor = 0.75f + 0.25f * mAimingFactor; mFirstPersonNeckController->setRotate(osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1,0,0))); mFirstPersonNeckController->setOffset(mFirstPersonOffset); } WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); return ret; } void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) { mPartPriorities[type] = 0; mPartslots[type] = -1; mObjectParts[type].reset(); if (mSounds[type] != nullptr && !mSoundsDisabled) { MWBase::Environment::get().getSoundManager()->stopSound(mSounds[type]); mSounds[type] = nullptr; } } void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) { if(priority > mPartPriorities[type]) { removeIndividualPart(type); mPartPriorities[type] = priority; mPartslots[type] = group; } } void NpcAnimation::removePartGroup(int group) { for(int i = 0; i < ESM::PRT_Count; i++) { if(mPartslots[i] == group) removeIndividualPart((ESM::PartReferenceType)i); } } bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart) { return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st"; } bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) { return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female; } bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { if(priority <= mPartPriorities[type]) return false; removeIndividualPart(type); mPartslots[type] = group; mPartPriorities[type] = priority; try { std::string bonename = sPartList.at(type); if (type == ESM::PRT_Weapon) { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId) { int weaponType = weapon->get()->mBase->mData.mType; const std::string weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; if (weaponBonename != bonename) { const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(weaponBonename); if (found != nodeMap.end()) bonename = weaponBonename; } } } // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor, isLight); } catch (std::exception& e) { Log(Debug::Error) << "Error adding NPC part: " << e.what(); return false; } if (!mSoundsDisabled) { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator csi = inv.getSlot(group < 0 ? MWWorld::InventoryStore::Slot_Helmet : group); if (csi != inv.end()) { const auto soundId = csi->getClass().getSound(*csi); if (!soundId.empty()) { mSounds[type] = MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop ); } } } osg::Node* node = mObjectParts[type]->getNode(); if (node->getNumChildrenRequiringUpdateTraversal() > 0) { std::shared_ptr src; if (type == ESM::PRT_Head) { src = mHeadAnimationTime; if (node->getUserDataContainer()) { for (unsigned int i=0; igetUserDataContainer()->getNumUserObjects(); ++i) { osg::Object* obj = node->getUserDataContainer()->getUserObject(i); if (SceneUtil::TextKeyMapHolder* keys = dynamic_cast(obj)) { for (const auto &key : keys->mTextKeys) { if (Misc::StringUtils::ciEqual(key.second, "talk: start")) mHeadAnimationTime->setTalkStart(key.first); if (Misc::StringUtils::ciEqual(key.second, "talk: stop")) mHeadAnimationTime->setTalkStop(key.first); if (Misc::StringUtils::ciEqual(key.second, "blink: start")) mHeadAnimationTime->setBlinkStart(key.first); if (Misc::StringUtils::ciEqual(key.second, "blink: stop")) mHeadAnimationTime->setBlinkStop(key.first); } break; } } } SceneUtil::ForceControllerSourcesVisitor assignVisitor(src); node->accept(assignVisitor); } else { if (type == ESM::PRT_Weapon) src = mWeaponAnimationTime; else src = std::make_shared(); SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); node->accept(assignVisitor); } } return true; } void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, osg::Vec4f* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; for(const ESM::PartReference& part : parts) { const ESM::BodyPart *bodypart = nullptr; if(!mNpc->isMale() && !part.mFemale.empty()) { bodypart = partStore.search(part.mFemale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { bodypart = partStore.search(part.mFemale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = nullptr; } else if (!bodypart) Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'"; } if(!bodypart && !part.mMale.empty()) { bodypart = partStore.search(part.mMale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { bodypart = partStore.search(part.mMale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = nullptr; } else if (!bodypart) Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'"; } if(bodypart) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(static_cast(part.mPart), group, priority, Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs), enchantedGlow, glowColor); } else reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); } } void NpcAnimation::addControllers() { Animation::addControllers(); mFirstPersonNeckController = nullptr; WeaponAnimation::deleteControllers(); if (mViewMode == VM_FirstPerson) { NodeMap::iterator found = mNodeMap.find("bip01 neck"); if (found != mNodeMap.end()) { osg::MatrixTransform* node = found->second.get(); mFirstPersonNeckController = new RotateController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); mActiveControllers.emplace_back(node, mFirstPersonNeckController); } } else if (mViewMode == VM_Normal) { WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } } void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; mAmmunition.reset(); if(showWeapon) { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) { osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); std::string mesh = weapon->getClass().getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); // Crossbows start out with a bolt attached if (weapon->getType() == ESM::Weapon::sRecordId && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype) attachArrow(); } } } else { removeIndividualPart(ESM::PRT_Weapon); // If we remove/hide weapon from player, we should reset attack animation as well if (mPtr == MWMechanics::getPlayer()) mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false); } updateHolsteredWeapon(!mShowWeapons); updateQuiver(); } bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const { static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (shieldSheathing) { const MWWorld::Class &cls = mPtr.getClass(); MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (stats.getDrawState() == MWMechanics::DrawState::Nothing) { SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); mObjectRoot->accept(findVisitor); if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) return false; } } } return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); } void NpcAnimation::showCarriedLeft(bool show) { mShowCarriedLeft = show; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(show && iter != inv.end()) { osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); // For shields we must try to use the body part model if (iter->getType() == ESM::Armor::sRecordId) { mesh = getShieldMesh(*iter, !mNpc->isMale()); } if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor, iter->getType() == ESM::Light::sRecordId)) { if (mesh.empty()) reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); if (iter->getType() == ESM::Light::sRecordId && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } } else removeIndividualPart(ESM::PRT_Shield); updateHolsteredShield(mShowCarriedLeft); } void NpcAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) { osg::Group* bone = getArrowBone(); if (bone != nullptr && bone->getNumChildren()) SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); } updateQuiver(); } void NpcAnimation::detachArrow() { WeaponAnimation::detachArrow(mPtr); updateQuiver(); } void NpcAnimation::releaseArrow(float attackStrength) { WeaponAnimation::releaseArrow(mPtr, attackStrength); updateQuiver(); } osg::Group* NpcAnimation::getArrowBone() { const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); if (part == nullptr) return nullptr; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return nullptr; int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; if (ammoType == ESM::Weapon::None) return nullptr; // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); if (bone == nullptr) { SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); part->getNode()->accept(findVisitor); bone = findVisitor.mFoundNode; } return bone; } osg::Node* NpcAnimation::getWeaponNode() { const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); if (part == nullptr) return nullptr; return part->getNode(); } Resource::ResourceSystem* NpcAnimation::getResourceSystem() { return mResourceSystem; } void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); } void NpcAnimation::setWeaponGroup(const std::string &group, bool relativeDuration) { mWeaponAnimationTime->setGroup(group, relativeDuration); } void NpcAnimation::equipmentChanged() { static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (shieldSheathing) { int weaptype = ESM::Weapon::None; MWMechanics::getActiveWeapon(mPtr, &weaptype); showCarriedLeft(updateCarriedLeftVisible(weaptype)); } updateParts(); } void NpcAnimation::setVampire(bool vampire) { if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we return; if ((mNpcType == Type_Vampire) != vampire) { if (mPtr == MWMechanics::getPlayer()) MWBase::Environment::get().getWorld()->reattachPlayerCamera(); else rebuild(); } } void NpcAnimation::setFirstPersonOffset(const osg::Vec3f &offset) { mFirstPersonOffset = offset; } void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) { Animation::updatePtr(updated); mHeadAnimationTime->updatePtr(updated); } // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination typedef std::map< std::pair,std::vector > RaceMapping; static RaceMapping sRaceMapping; const std::vector& NpcAnimation::getBodyParts(const std::string &race, bool female, bool firstPerson, bool werewolf) { static const int Flag_FirstPerson = 1<<1; static const int Flag_Female = 1<<0; int flags = (werewolf ? -1 : 0); if(female) flags |= Flag_Female; if(firstPerson) flags |= Flag_FirstPerson; RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags)); if (found != sRaceMapping.end()) return found->second; else { std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; typedef std::multimap BodyPartMapType; static const BodyPartMapType sBodyPartMap = { {ESM::BodyPart::MP_Neck, ESM::PRT_Neck}, {ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass}, {ESM::BodyPart::MP_Groin, ESM::PRT_Groin}, {ESM::BodyPart::MP_Hand, ESM::PRT_RHand}, {ESM::BodyPart::MP_Hand, ESM::PRT_LHand}, {ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist}, {ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist}, {ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm}, {ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm}, {ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm}, {ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm}, {ESM::BodyPart::MP_Foot, ESM::PRT_RFoot}, {ESM::BodyPart::MP_Foot, ESM::PRT_LFoot}, {ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle}, {ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle}, {ESM::BodyPart::MP_Knee, ESM::PRT_RKnee}, {ESM::BodyPart::MP_Knee, ESM::PRT_LKnee}, {ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg}, {ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg}, {ESM::BodyPart::MP_Tail, ESM::PRT_Tail} }; parts.resize(ESM::PRT_Count, nullptr); if (werewolf) return parts; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for(const ESM::BodyPart& bodypart : store.get()) { if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) continue; bool partFirstPerson = isFirstPersonPart(&bodypart); bool isHand = bodypart.mData.mPart == ESM::BodyPart::MP_Hand || bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm; bool isSameGender = isFemalePart(&bodypart) == female; /* A fallback for the arms if 1st person is missing: 1. Try to use 3d person skin for same gender 2. Try to use 1st person skin for male, if female == true 3. Try to use 3d person skin for male, if female == true A fallback in another cases: allow to use male bodyparts, if female == true */ if (firstPerson && isHand && !partFirstPerson) { // Allow 3rd person skins as a fallback for the arms if 1st person is missing BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { // If we have no fallback bodypart now and bodypart is for same gender (1) if(!parts[bIt->second] && isSameGender) parts[bIt->second] = &bodypart; // If we have fallback bodypart for other gender and found fallback for current gender (1) else if(isSameGender && isFemalePart(parts[bIt->second]) != female) parts[bIt->second] = &bodypart; // If we have no fallback bodypart and searching for female bodyparts (3) else if(!parts[bIt->second] && female) parts[bIt->second] = &bodypart; ++bIt; } continue; } // Don't allow to use podyparts for a different view if (partFirstPerson != firstPerson) continue; if (female && !isFemalePart(&bodypart)) { // Allow male parts as fallback for females if female parts are missing BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { // If we have no fallback bodypart now if(!parts[bIt->second]) parts[bIt->second] = &bodypart; // If we have 3d person fallback bodypart for hand and 1st person fallback found (2) else if(isHand && !isFirstPersonPart(parts[bIt->second]) && partFirstPerson) parts[bIt->second] = &bodypart; ++bIt; } continue; } // Don't allow to use podyparts for another gender if (female != isFemalePart(&bodypart)) continue; // Use properly found bodypart, replacing fallbacks BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { parts[bIt->second] = &bodypart; ++bIt; } } return parts; } } void NpcAnimation::setAccurateAiming(bool enabled) { mAccurateAiming = enabled; } bool NpcAnimation::isArrowAttached() const { return mAmmunition != nullptr; } } openmw-openmw-0.48.0/apps/openmw/mwrender/npcanimation.hpp000066400000000000000000000135301445372753700237220ustar00rootroot00000000000000#ifndef GAME_RENDER_NPCANIMATION_H #define GAME_RENDER_NPCANIMATION_H #include "animation.hpp" #include "../mwworld/inventorystore.hpp" #include "actoranimation.hpp" #include "weaponanimation.hpp" #include namespace ESM { struct NPC; struct BodyPart; } namespace MWSound { class Sound; } namespace MWRender { class RotateController; class HeadAnimationTime; class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: void equipmentChanged() override; public: typedef std::map PartBoneMap; enum ViewMode { VM_Normal, VM_FirstPerson, VM_HeadOnly }; private: static const PartBoneMap sPartList; // Bounded Parts PartHolderPtr mObjectParts[ESM::PRT_Count]; std::array mSounds; const ESM::NPC *mNpc; std::string mHeadModel; std::string mHairModel; ViewMode mViewMode; bool mShowWeapons; bool mShowCarriedLeft; enum NpcType { Type_Normal, Type_Werewolf, Type_Vampire }; NpcType mNpcType; int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty int mPartPriorities[ESM::PRT_Count]; osg::Vec3f mFirstPersonOffset; // Field of view to use when rendering first person meshes float mFirstPersonFieldOfView; std::shared_ptr mHeadAnimationTime; std::shared_ptr mWeaponAnimationTime; bool mSoundsDisabled; bool mAccurateAiming; float mAimingFactor; void updateNpcBase(); NpcType getNpcType() const; PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr, bool isLight = false); void removePartGroup(int group); void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); void setRenderBin(); osg::ref_ptr mFirstPersonNeckController; static bool isFirstPersonPart(const ESM::BodyPart* bodypart); static bool isFemalePart(const ESM::BodyPart* bodypart); static NpcType getNpcType(const MWWorld::Ptr& ptr); protected: void addControllers() override; bool isArrowAttached() const override; std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const override; public: /** * @param ptr * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports * one listener at a time, so you shouldn't do this if creating several NpcAnimations * for the same Ptr, eg preview dolls for the player. * Those need to be manually rendered anyway. * @param disableSounds Same as \a disableListener but for playing items sounds * @param viewMode */ NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableSounds = false, ViewMode viewMode=VM_Normal, float firstPersonFieldOfView=55.f); virtual ~NpcAnimation(); void enableHeadAnimation(bool enable) override; /// 1: the first person meshes follow the camera's rotation completely /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands void setAccurateAiming(bool enabled) override; void setWeaponGroup(const std::string& group, bool relativeDuration) override; osg::Vec3f runAnimation(float timepassed) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. void setPitchFactor(float factor) override { mPitchFactor = factor; } void showWeapons(bool showWeapon) override; bool updateCarriedLeftVisible(const int weaptype) const override; bool getCarriedLeftShown() const override { return mShowCarriedLeft; } void showCarriedLeft(bool show) override; void attachArrow() override; void detachArrow() override; void releaseArrow(float attackStrength) override; osg::Group* getArrowBone() override; osg::Node* getWeaponNode() override; Resource::ResourceSystem* getResourceSystem() override; // WeaponAnimation void showWeapon(bool show) override { showWeapons(show); } void setViewMode(ViewMode viewMode); void updateParts(); /// Rebuilds the NPC, updating their root model, animation sources, and equipment. void rebuild(); /// Get the inventory slot that the given node path leads into, or -1 if not found. int getSlot(const osg::NodePath& path) const; void setVampire(bool vampire) override; /// Set a translation offset (in object root space) to apply to meshes when in first person mode. void setFirstPersonOffset(const osg::Vec3f& offset); void updatePtr(const MWWorld::Ptr& updated) override; /// Get a list of body parts that may be used by an NPC of given race and gender. /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain nullptr body parts. static const std::vector& getBodyParts(const std::string& raceId, bool female, bool firstperson, bool werewolf); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/objectpaging.cpp000066400000000000000000001061511445372753700236730ustar00rootroot00000000000000#include "objectpaging.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" #include "vismask.hpp" #include namespace MWRender { bool typeFilter(int type, bool far) { switch (type) { case ESM::REC_STAT: case ESM::REC_ACTI: case ESM::REC_DOOR: return true; case ESM::REC_CONT: return !far; default: return false; } } std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store) { switch (type) { case ESM::REC_STAT: return store.get().searchStatic(id)->mModel; case ESM::REC_ACTI: return store.get().searchStatic(id)->mModel; case ESM::REC_DOOR: return store.get().searchStatic(id)->mModel; case ESM::REC_CONT: return store.get().searchStatic(id)->mModel; default: return {}; } } osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { if (activeGrid && !mActiveGrid) return nullptr; ChunkId id = std::make_tuple(center, size, activeGrid); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); mCache->addEntryToObjectCache(id, node.get()); return node; } } class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const override { return true; } bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const override { return (node->getDataVariance() != osg::Object::DYNAMIC); } }; class CopyOp : public osg::CopyOp { public: bool mOptimizeBillboards = true; float mSqrDistance = 0.f; osg::Vec3f mViewVector; osg::Node::NodeMask mCopyMask = ~0u; mutable std::vector mNodePath; void copy(const osg::Node* toCopy, osg::Group* attachTo) { const osg::Group* groupToCopy = toCopy->asGroup(); if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy) attachTo->addChild(operator()(toCopy)); else { for (unsigned int i=0; igetNumChildren(); ++i) attachTo->addChild(operator()(groupToCopy->getChild(i))); } } osg::Node* operator() (const osg::Node* node) const override { if (!(node->getNodeMask() & mCopyMask)) return nullptr; if (const osg::Drawable* d = node->asDrawable()) return operator()(d); if (dynamic_cast(node)) return nullptr; if (dynamic_cast(node)) return nullptr; if (const osg::Switch* sw = node->asSwitch()) { osg::Group* n = new osg::Group; for (unsigned int i=0; igetNumChildren(); ++i) if (sw->getValue(i)) n->addChild(operator()(sw->getChild(i))); n->setDataVariance(osg::Object::STATIC); return n; } if (const osg::LOD* lod = dynamic_cast(node)) { osg::Group* n = new osg::Group; for (unsigned int i=0; igetNumChildren(); ++i) if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) n->addChild(operator()(lod->getChild(i))); n->setDataVariance(osg::Object::STATIC); return n; } if (const osg::Sequence* sq = dynamic_cast(node)) { osg::Group* n = new osg::Group; n->addChild(operator()(sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0))); n->setDataVariance(osg::Object::STATIC); return n; } mNodePath.push_back(node); osg::Node* cloned = static_cast(node->clone(*this)); cloned->setDataVariance(osg::Object::STATIC); cloned->setUserDataContainer(nullptr); cloned->setName(""); mNodePath.pop_back(); handleCallbacks(node, cloned); return cloned; } void handleCallbacks(const osg::Node* node, osg::Node *cloned) const { for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback()) { if (callback->className() == std::string("BillboardCallback")) { if (mOptimizeBillboards) { handleBillboard(cloned); continue; } else cloned->setDataVariance(osg::Object::DYNAMIC); } if (node->getCullCallback()->getNestedCallback()) { osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); clonedCallback->setNestedCallback(nullptr); cloned->addCullCallback(clonedCallback); } else cloned->addCullCallback(const_cast(callback)); } } void handleBillboard(osg::Node* node) const { osg::Transform* transform = node->asTransform(); if (!transform) return; osg::MatrixTransform* matrixTransform = transform->asMatrixTransform(); if (!matrixTransform) return; osg::Matrix worldToLocal = osg::Matrix::identity(); for (auto pathNode : mNodePath) if (const osg::Transform* t = pathNode->asTransform()) t->computeWorldToLocalMatrix(worldToLocal, nullptr); worldToLocal = osg::Matrix::orthoNormal(worldToLocal); osg::Matrix billboardMatrix; osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans()); viewVector.normalize(); osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1); right.normalize(); osg::Vec3f up = right ^ viewVector; up.normalize(); billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up); billboardMatrix.invert(billboardMatrix); const osg::Matrix& oldMatrix = matrixTransform->getMatrix(); float mag[3]; // attempt to preserve scale for (int i=0;i<3;++i) mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i)); osg::Matrix newMatrix; worldToLocal.setTrans(0,0,0); newMatrix *= worldToLocal; newMatrix.preMult(billboardMatrix); newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2])); newMatrix.setTrans(oldMatrix.getTrans()); matrixTransform->setMatrix(newMatrix); } osg::Drawable* operator() (const osg::Drawable* drawable) const override { if (!(drawable->getNodeMask() & mCopyMask)) return nullptr; if (dynamic_cast(drawable)) return nullptr; if (dynamic_cast(drawable)) return nullptr; if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) return operator()(rig->getSourceGeometry()); if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) return operator()(morph->getSourceGeometry()); if (getCopyFlags() & DEEP_COPY_DRAWABLES) { osg::Drawable* d = static_cast(drawable->clone(*this)); d->setDataVariance(osg::Object::STATIC); d->setUserDataContainer(nullptr); d->setName(""); return d; } else return const_cast(drawable); } osg::Callback* operator() (const osg::Callback* callback) const override { return nullptr; } }; class RefnumSet : public osg::Object { public: RefnumSet(){} RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {} META_Object(MWRender, RefnumSet) std::vector mRefnums; }; class AnalyzeVisitor : public osg::NodeVisitor { public: AnalyzeVisitor(osg::Node::NodeMask analyzeMask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCurrentStateSet(nullptr) , mCurrentDistance(0.f) { setTraversalMask(analyzeMask); } typedef std::unordered_map StateSetCounter; struct Result { StateSetCounter mStateSetCounter; unsigned int mNumVerts = 0; }; void apply(osg::Node& node) override { if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); if (osg::Switch* sw = node.asSwitch()) { for (unsigned int i=0; igetNumChildren(); ++i) if (sw->getValue(i)) traverse(*sw->getChild(i)); return; } if (osg::LOD* lod = dynamic_cast(&node)) { for (unsigned int i=0; igetNumChildren(); ++i) if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) traverse(*lod->getChild(i)); return; } if (osg::Sequence* sq = dynamic_cast(&node)) { traverse(*sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0)); return; } traverse(node); } void apply(osg::Geometry& geom) override { if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); ++mResult.mStateSetCounter[mCurrentStateSet]; ++mGlobalStateSetCounter[mCurrentStateSet]; } Result retrieveResult() { Result result = mResult; mResult = Result(); mCurrentStateSet = nullptr; return result; } void addInstance(const Result& result) { for (auto pair : result.mStateSetCounter) mGlobalStateSetCounter[pair.first] += pair.second; } float getMergeBenefit(const Result& result) { if (result.mStateSetCounter.empty()) return 1; float mergeBenefit = 0; for (auto pair : result.mStateSetCounter) { mergeBenefit += mGlobalStateSetCounter[pair.first]; } mergeBenefit /= result.mStateSetCounter.size(); return mergeBenefit; } Result mResult; osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; float mCurrentDistance; }; class DebugVisitor : public osg::NodeVisitor { public: DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} void apply(osg::Drawable& node) override { osg::ref_ptr m (new osg::Material); osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); color.normalize(); m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); m->setColorMode(osg::Material::OFF); m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color)); osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); stateset->addUniform(new osg::Uniform("specStrength", 1.f)); node.setStateSet(stateset); } }; class AddRefnumMarkerVisitor : public osg::NodeVisitor { public: AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {} ESM::RefNum mRefnum; void apply(osg::Geometry &node) override { osg::ref_ptr marker (new RefnumMarker); marker->mRefnum = mRefnum; if (osg::Array* array = node.getVertexArray()) marker->mNumVertices = array->getNumElements(); node.getOrCreateUserDataContainer()->addUserObject(marker); } }; ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mRefTrackerLocked(false) { mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; osg::Vec3f relativeViewPoint = viewPoint - worldCenter; std::map refs; ESM::ReadersCache readers; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); if (!cell) continue; for (size_t i=0; imContextList.size(); ++i) { try { const std::size_t index = static_cast(cell->mContextList[i].index); const ESM::ReadersCache::BusyItem reader = readers.get(index); cell->restore(*reader, i); ESM::CellRef ref; ref.mRefNum.unset(); ESM::MovedCellRef cMRef; cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } refs[ref.mRefNum] = std::move(ref); } } catch (std::exception&) { continue; } } for (auto [ref, deleted] : cell->mLeasedRefs) { if (deleted) { refs.erase(ref.mRefNum); continue; } Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; refs[ref.mRefNum] = std::move(ref); } } } if (activeGrid) { std::lock_guard lock(mRefTrackerMutex); for (auto ref : getRefTracker().mBlacklist) refs.erase(ref); } osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); struct InstanceList { std::vector mInstances; AnalyzeVisitor::Result mAnalyzeResult; bool mNeedCompile = false; }; typedef std::map, InstanceList> NodeMap; NodeMap nodes; osg::ref_ptr refnumSet = activeGrid ? new RefnumSet : nullptr; // Mask_UpdateVisitor is used in such cases in NIF loader: // 1. For collision nodes, which is not supposed to be rendered. // 2. For nodes masked via Flag_Hidden (VisController can change this flag value at runtime). // Since ObjectPaging does not handle VisController, we can just ignore both types of nodes. constexpr auto copyMask = ~Mask_UpdateVisitor; AnalyzeVisitor analyzeVisitor(copyMask); analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2(); float minSize = mMinSize; if (mMinSizeMergeFactor) minSize *= mMinSizeMergeFactor; for (const auto& pair : refs) { const ESM::CellRef& ref = pair.second; osg::Vec3f pos = ref.mPos.asVec3(); if (size < 1.f) { osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) continue; } float dSqr = (viewPoint - pos).length2(); if (!activeGrid) { std::lock_guard lock(mSizeCacheMutex); SizeCache::iterator found = mSizeCache.find(pair.first); if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize) continue; } if (Misc::ResourceHelpers::isHiddenMarker(ref.mRefID)) continue; int type = store.findStatic(ref.mRefID); std::string model = getModel(type, ref.mRefID, store); if (model.empty()) continue; model = Misc::ResourceHelpers::correctMeshPath(model, mSceneManager->getVFS()); if (activeGrid && type != ESM::REC_STAT) { model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); std::string kfname = Misc::StringUtils::lowerCase(model); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) continue; } } osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); if (activeGrid) { if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) continue; else refnumSet->mRefnums.push_back(pair.first); } { std::lock_guard lock(mRefTrackerMutex); if (getRefTracker().mDisabled.count(pair.first)) continue; } float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale; if (radius2 < dSqr*minSize*minSize && !activeGrid) { std::lock_guard lock(mSizeCacheMutex); mSizeCache[pair.first] = radius2; continue; } auto emplaced = nodes.emplace(cnode, InstanceList()); if (emplaced.second) { const_cast(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult(); emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3; } else analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult); emplaced.first->second.mInstances.push_back(&ref); } osg::ref_ptr group = new osg::Group; osg::ref_ptr mergeGroup = new osg::Group; osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; copyop.mCopyMask = copyMask; for (const auto& pair : nodes) { const osg::Node* cnode = pair.first; const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult; float mergeCost = analyzeResult.mNumVerts * size; float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor; bool merge = mergeBenefit > mergeCost; float minSizeMerged = mMinSize; float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1; float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2; if (minSizeMergeFactor2 > 0) minSizeMerged *= minSizeMergeFactor2; unsigned int numinstances = 0; for (auto cref : pair.second.mInstances) { const ESM::CellRef& ref = *cref; osg::Vec3f pos = ref.mPos.asVec3(); if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged) continue; osg::Vec3f nodePos = pos - worldCenter; osg::Quat nodeAttitude = osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) * osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)); osg::Vec3f nodeScale = osg::Vec3f(ref.mScale, ref.mScale, ref.mScale); osg::ref_ptr trans; if (merge) { // Optimizer currently supports only MatrixTransforms. osg::Matrixf matrix; matrix.preMultTranslate(nodePos); matrix.preMultRotate(nodeAttitude); matrix.preMultScale(nodeScale); trans = new osg::MatrixTransform(matrix); trans->setDataVariance(osg::Object::STATIC); } else { trans = new SceneUtil::PositionAttitudeTransform; SceneUtil::PositionAttitudeTransform* pat = static_cast(trans.get()); pat->setPosition(nodePos); pat->setScale(nodeScale); pat->setAttitude(nodeAttitude); } // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. // In this specific case the operation is safe under the following two assumptions: // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateMultiRef) // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp) copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); copyop.mOptimizeBillboards = (size > 1/4.f); copyop.mNodePath.push_back(trans); copyop.mSqrDistance = (viewPoint - pos).length2(); copyop.mViewVector = (viewPoint - worldCenter); copyop.copy(cnode, trans); copyop.mNodePath.pop_back(); if (activeGrid) { if (merge) { AddRefnumMarkerVisitor visitor(ref.mRefNum); trans->accept(visitor); } else { osg::ref_ptr marker = new RefnumMarker; marker->mRefnum = ref.mRefNum; trans->getOrCreateUserDataContainer()->addUserObject(marker); } } osg::Group* attachTo = merge ? mergeGroup : group; attachTo->addChild(trans); ++numinstances; } if (numinstances > 0) { // add a ref to the original template to help verify the safety of shallow cloning operations // in addition, we hint to the cache that it's still being used and should be kept in cache templateRefs->addRef(cnode); if (pair.second.mNeedCompile) { int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES; if (!merge) mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; stateToCompile._mode = mode; const_cast(cnode)->accept(stateToCompile); } } } if (mergeGroup->getNumChildren()) { SceneUtil::Optimizer optimizer; if (size > 1/8.f) { optimizer.setViewPoint(relativeViewPoint); optimizer.setMergeAlphaBlending(true); } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); if (mDebugBatches) { DebugVisitor dv; mergeGroup->accept(dv); } if (compile) { stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS; mergeGroup->accept(stateToCompile); } } auto ico = mSceneManager->getIncrementalCompileOperation(); if (!stateToCompile.empty() && ico) { auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group); compileSet->buildCompileMap(ico->getContextSet(), stateToCompile); ico->add(compileSet, false); } group->getBound(); group->setNodeMask(Mask_Static); osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); if (activeGrid) { std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()); refnumSet->mRefnums.erase(std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end()); udc->addUserObject(refnumSet); group->addCullCallback(new SceneUtil::LightListCallback); } udc->addUserObject(templateRefs); return group; } unsigned int ObjectPaging::getNodeMask() { return Mask_Static; } struct ClearCacheFunctor { void operator()(MWRender::ChunkId id, osg::Object* obj) { if (intersects(id, mPosition)) mToClear.insert(id); } bool intersects(ChunkId id, osg::Vec3f pos) { if (mActiveGridOnly && !std::get<2>(id)) return false; pos /= ESM::Land::REAL_SIZE; clampToCell(pos); osg::Vec2f center = std::get<0>(id); float halfSize = std::get<1>(id)/2; return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize; } void clampToCell(osg::Vec3f& cellPos) { cellPos.x() = std::clamp(cellPos.x(), mCell.x(), mCell.x() + 1); cellPos.y() = std::clamp(cellPos.y(), mCell.y(), mCell.y() + 1); } osg::Vec3f mPosition; osg::Vec2i mCell; std::set mToClear; bool mActiveGridOnly = false; }; bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false; if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false; if (mRefTrackerLocked) return false; } ClearCacheFunctor ccf; ccf.mPosition = pos; ccf.mCell = cell; mCache->call(ccf); if (ccf.mToClear.empty()) return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false; if (mRefTrackerLocked) return false; } ClearCacheFunctor ccf; ccf.mPosition = pos; ccf.mCell = cell; ccf.mActiveGridOnly = true; mCache->call(ccf); if (ccf.mToClear.empty()) return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } void ObjectPaging::clear() { std::lock_guard lock(mRefTrackerMutex); mRefTrackerNew.mDisabled.clear(); mRefTrackerNew.mBlacklist.clear(); mRefTrackerLocked = true; } bool ObjectPaging::unlockCache() { if (!mRefTrackerLocked) return false; { std::lock_guard lock(mRefTrackerMutex); mRefTrackerLocked = false; if (mRefTracker == mRefTrackerNew) return false; else mRefTracker = mRefTrackerNew; } mCache->clear(); return true; } struct GetRefnumsFunctor { GetRefnumsFunctor(std::vector& output) : mOutput(output) {} void operator()(MWRender::ChunkId chunkId, osg::Object* obj) { if (!std::get<2>(chunkId)) return; const osg::Vec2f& center = std::get<0>(chunkId); bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); if (!activeGrid) return; osg::UserDataContainer* udc = obj->getUserDataContainer(); if (udc && udc->getNumUserObjects()) { RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); if (!refnums) return; mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end()); } } osg::Vec4i mActiveGrid; std::vector& mOutput; }; void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out) { GetRefnumsFunctor grf(out); grf.mActiveGrid = activeGrid; mCache->call(grf); std::sort(out.begin(), out.end()); out.erase(std::unique(out.begin(), out.end()), out.end()); } void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); } } openmw-openmw-0.48.0/apps/openmw/mwrender/objectpaging.hpp000066400000000000000000000057641445372753700237100ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_OBJECTPAGING_H #define OPENMW_MWRENDER_OBJECTPAGING_H #include #include #include #include namespace Resource { class SceneManager; } namespace MWWorld { class ESMStore; } namespace MWRender { typedef std::tuple ChunkId; // Center, Size, ActiveGrid class ObjectPaging : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: ObjectPaging(Resource::SceneManager* sceneManager); ~ObjectPaging() = default; osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile); unsigned int getNodeMask() override; /// @return true if view needs rebuild bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled); /// @return true if view needs rebuild bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell); void clear(); /// Must be called after clear() before rendering starts. /// @return true if view needs rebuild bool unlockCache(); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out); private: Resource::SceneManager* mSceneManager; bool mActiveGrid; bool mDebugBatches; float mMergeFactor; float mMinSize; float mMinSizeMergeFactor; float mMinSizeCostMultiplier; std::mutex mRefTrackerMutex; struct RefTracker { std::set mDisabled; std::set mBlacklist; bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } }; RefTracker mRefTracker; RefTracker mRefTrackerNew; bool mRefTrackerLocked; const RefTracker& getRefTracker() const { return mRefTracker; } RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; } std::mutex mSizeCacheMutex; typedef std::map SizeCache; SizeCache mSizeCache; }; class RefnumMarker : public osg::Object { public: RefnumMarker() : mNumVertices(0) { mRefnum.unset(); } RefnumMarker(const RefnumMarker ©, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {} META_Object(MWRender, RefnumMarker) ESM::RefNum mRefnum; unsigned int mNumVertices; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/objects.cpp000066400000000000000000000150361445372753700226710ustar00rootroot00000000000000#include "objects.hpp" #include #include #include #include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "animation.hpp" #include "npcanimation.hpp" #include "creatureanimation.hpp" #include "vismask.hpp" namespace MWRender { Objects::Objects(Resource::ResourceSystem* resourceSystem, const osg::ref_ptr& rootNode, SceneUtil::UnrefQueue& unrefQueue) : mRootNode(rootNode) , mResourceSystem(resourceSystem) , mUnrefQueue(unrefQueue) { } Objects::~Objects() { mObjects.clear(); for (CellMap::iterator iter = mCellSceneNodes.begin(); iter != mCellSceneNodes.end(); ++iter) iter->second->getParent(0)->removeChild(iter->second); mCellSceneNodes.clear(); } void Objects::insertBegin(const MWWorld::Ptr& ptr) { assert(mObjects.find(ptr.mRef) == mObjects.end()); osg::ref_ptr cellnode; CellMap::iterator found = mCellSceneNodes.find(ptr.getCell()); if (found == mCellSceneNodes.end()) { cellnode = new osg::Group; cellnode->setName("Cell Root"); mRootNode->addChild(cellnode); mCellSceneNodes[ptr.getCell()] = cellnode; } else cellnode = found->second; osg::ref_ptr insert (new SceneUtil::PositionAttitudeTransform); cellnode->addChild(insert); insert->getOrCreateUserDataContainer()->addUserObject(new PtrHolder(ptr)); const float *f = ptr.getRefData().getPosition().pos; insert->setPosition(osg::Vec3(f[0], f[1], f[2])); const float scale = ptr.getCellRef().getScale(); osg::Vec3f scaleVec(scale, scale, scale); ptr.getClass().adjustScale(ptr, scaleVec, true); insert->setScale(scaleVec); ptr.getRefData().setBaseNode(insert); } void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); mObjects.emplace(ptr.mRef, std::move(anim)); } void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, bool weaponsShields) { insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); // CreatureAnimation osg::ref_ptr anim; if (weaponsShields) anim = new CreatureWeaponAnimation(ptr, mesh, mResourceSystem); else anim = new CreatureAnimation(ptr, mesh, mResourceSystem); if (mObjects.emplace(ptr.mRef, anim).second) ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); } void Objects::insertNPC(const MWWorld::Ptr &ptr) { insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); osg::ref_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); if (mObjects.emplace(ptr.mRef, anim).second) { ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get(), ptr); ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); } } bool Objects::removeObject (const MWWorld::Ptr& ptr) { if(!ptr.getRefData().getBaseNode()) return true; const auto iter = mObjects.find(ptr.mRef); if(iter != mObjects.end()) { iter->second->removeFromScene(); mUnrefQueue.push(std::move(iter->second)); mObjects.erase(iter); if (ptr.getClass().isActor()) { if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); ptr.getClass().getContainerStore(ptr).setContListener(nullptr); } ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); ptr.getRefData().setBaseNode(nullptr); return true; } return false; } void Objects::removeCell(const MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) { MWWorld::Ptr ptr = iter->second->getPtr(); if(ptr.getCell() == store) { if (ptr.getClass().isActor() && ptr.getRefData().getCustomData()) { if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); ptr.getClass().getContainerStore(ptr).setContListener(nullptr); } iter->second->removeFromScene(); mUnrefQueue.push(std::move(iter->second)); iter = mObjects.erase(iter); } else ++iter; } CellMap::iterator cell = mCellSceneNodes.find(store); if(cell != mCellSceneNodes.end()) { cell->second->getParent(0)->removeChild(cell->second); mCellSceneNodes.erase(cell); } } void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { osg::ref_ptr objectNode = cur.getRefData().getBaseNode(); if (!objectNode) return; MWWorld::CellStore *newCell = cur.getCell(); osg::Group* cellnode; if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) { cellnode = new osg::Group; mRootNode->addChild(cellnode); mCellSceneNodes[newCell] = cellnode; } else { cellnode = mCellSceneNodes[newCell]; } osg::UserDataContainer* userDataContainer = objectNode->getUserDataContainer(); if (userDataContainer) for (unsigned int i=0; igetNumUserObjects(); ++i) { if (dynamic_cast(userDataContainer->getUserObject(i))) userDataContainer->setUserObject(i, new PtrHolder(cur)); } if (objectNode->getNumParents()) objectNode->getParent(0)->removeChild(objectNode); cellnode->addChild(objectNode); PtrAnimationMap::iterator iter = mObjects.find(old.mRef); if(iter != mObjects.end()) iter->second->updatePtr(cur); } Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) { PtrAnimationMap::const_iterator iter = mObjects.find(ptr.mRef); if(iter != mObjects.end()) return iter->second; return nullptr; } const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const { PtrAnimationMap::const_iterator iter = mObjects.find(ptr.mRef); if(iter != mObjects.end()) return iter->second; return nullptr; } } openmw-openmw-0.48.0/apps/openmw/mwrender/objects.hpp000066400000000000000000000042741445372753700227000ustar00rootroot00000000000000#ifndef GAME_RENDER_OBJECTS_H #define GAME_RENDER_OBJECTS_H #include #include #include #include #include "../mwworld/ptr.hpp" namespace osg { class Group; } namespace Resource { class ResourceSystem; } namespace MWWorld { class CellStore; } namespace SceneUtil { class UnrefQueue; } namespace MWRender{ class Animation; class PtrHolder : public osg::Object { public: PtrHolder(const MWWorld::Ptr& ptr) : mPtr(ptr) { } PtrHolder() { } PtrHolder(const PtrHolder& copy, const osg::CopyOp& copyop) : mPtr(copy.mPtr) { } META_Object(MWRender, PtrHolder) MWWorld::Ptr mPtr; }; class Objects { using PtrAnimationMap = std::map>; typedef std::map > CellMap; CellMap mCellSceneNodes; PtrAnimationMap mObjects; osg::ref_ptr mRootNode; Resource::ResourceSystem* mResourceSystem; SceneUtil::UnrefQueue& mUnrefQueue; void insertBegin(const MWWorld::Ptr& ptr); public: Objects(Resource::ResourceSystem* resourceSystem, const osg::ref_ptr& rootNode, SceneUtil::UnrefQueue& unrefQueue); ~Objects(); /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? /// @param allowLight If false, no lights will be created, and particles systems will be removed. void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool animated=false, bool allowLight=true); void insertNPC(const MWWorld::Ptr& ptr); void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); Animation* getAnimation(const MWWorld::Ptr &ptr); const Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? void removeCell(const MWWorld::CellStore* store); /// Updates containing cell for object rendering data void updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); private: void operator = (const Objects&); Objects(const Objects&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/pathgrid.cpp000066400000000000000000000100151445372753700230320ustar00rootroot00000000000000#include "pathgrid.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/pathfinding.hpp" #include "vismask.hpp" namespace MWRender { Pathgrid::Pathgrid(osg::ref_ptr root) : mPathgridEnabled(false) , mRootNode(root) , mPathGridRoot(nullptr) , mInteriorPathgridNode(nullptr) { } Pathgrid::~Pathgrid() { if (mPathgridEnabled) { togglePathgrid(); } } bool Pathgrid::toggleRenderMode (int mode){ switch (mode) { case Render_Pathgrid: togglePathgrid(); return mPathgridEnabled; default: return false; } return false; } void Pathgrid::addCell(const MWWorld::CellStore *store) { mActiveCells.push_back(store); if (mPathgridEnabled) enableCellPathgrid(store); } void Pathgrid::removeCell(const MWWorld::CellStore *store) { mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); if (mPathgridEnabled) disableCellPathgrid(store); } void Pathgrid::togglePathgrid() { mPathgridEnabled = !mPathgridEnabled; if (mPathgridEnabled) { // add path grid meshes to already loaded cells mPathGridRoot = new osg::Group; mPathGridRoot->setNodeMask(Mask_Debug); mRootNode->addChild(mPathGridRoot); for(const MWWorld::CellStore* cell : mActiveCells) { enableCellPathgrid(cell); } } else { // remove path grid meshes from already loaded cells for(const MWWorld::CellStore* cell : mActiveCells) { disableCellPathgrid(cell); } if (mPathGridRoot) { mRootNode->removeChild(mPathGridRoot); mPathGridRoot = nullptr; } } } void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = world->getStore().get().search(*store->getCell()); if (!pathgrid) return; osg::Vec3f cellPathGridPos(0, 0, 0); Misc::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); osg::ref_ptr geometry = SceneUtil::createPathgridGeometry(*pathgrid); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(geometry, "debug"); cellPathGrid->addChild(geometry); mPathGridRoot->addChild(cellPathGrid); if (store->getCell()->isExterior()) { mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; } else { assert(mInteriorPathgridNode == nullptr); mInteriorPathgridNode = cellPathGrid; } } void Pathgrid::disableCellPathgrid(const MWWorld::CellStore *store) { if (store->getCell()->isExterior()) { ExteriorPathgridNodes::iterator it = mExteriorPathgridNodes.find(std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())); if (it != mExteriorPathgridNodes.end()) { mPathGridRoot->removeChild(it->second); mExteriorPathgridNodes.erase(it); } } else { if (mInteriorPathgridNode) { mPathGridRoot->removeChild(mInteriorPathgridNode); mInteriorPathgridNode = nullptr; } } } } openmw-openmw-0.48.0/apps/openmw/mwrender/pathgrid.hpp000066400000000000000000000022511445372753700230420ustar00rootroot00000000000000#ifndef GAME_RENDER_MWSCENE_H #define GAME_RENDER_MWSCENE_H #include #include #include #include namespace ESM { struct Pathgrid; } namespace osg { class Group; class Geometry; } namespace MWWorld { class Ptr; class CellStore; } namespace MWRender { class Pathgrid { bool mPathgridEnabled; void togglePathgrid(); typedef std::vector CellList; CellList mActiveCells; osg::ref_ptr mRootNode; osg::ref_ptr mPathGridRoot; typedef std::map, osg::ref_ptr > ExteriorPathgridNodes; ExteriorPathgridNodes mExteriorPathgridNodes; osg::ref_ptr mInteriorPathgridNode; void enableCellPathgrid(const MWWorld::CellStore *store); void disableCellPathgrid(const MWWorld::CellStore *store); public: Pathgrid(osg::ref_ptr root); ~Pathgrid(); bool toggleRenderMode (int mode); void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/pingpongcanvas.cpp000066400000000000000000000275601445372753700242620ustar00rootroot00000000000000#include "pingpongcanvas.hpp" #include #include #include #include #include #include "postprocessor.hpp" namespace MWRender { PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) : mFallbackStateSet(new osg::StateSet) , mMultiviewResolveStateSet(new osg::StateSet) { setUseDisplayList(false); setUseVertexBufferObjects(true); osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-1, -1, 0)); verts->push_back(osg::Vec3f(-1, 3, 0)); verts->push_back(osg::Vec3f(3, -1, 0)); setVertexArray(verts); addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); mLuminanceCalculator = LuminanceCalculator(shaderManager); mLuminanceCalculator.disable(); Shader::ShaderManager::DefineMap defines; Stereo::Manager::instance().shaderStereoDefines(defines); auto fallbackVertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", defines, osg::Shader::VERTEX); auto fallbackFragment = shaderManager.getShader("fullscreen_tri_fragment.glsl", defines, osg::Shader::FRAGMENT); mFallbackProgram = shaderManager.getProgram(fallbackVertex, fallbackFragment); mFallbackStateSet->setAttributeAndModes(mFallbackProgram); mFallbackStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", 0)); auto multiviewResolveVertex = shaderManager.getShader("multiview_resolve_vertex.glsl", {}, osg::Shader::VERTEX); auto multiviewResolveFragment = shaderManager.getShader("multiview_resolve_fragment.glsl", {}, osg::Shader::FRAGMENT); mMultiviewResolveProgram = shaderManager.getProgram(multiviewResolveVertex, multiviewResolveFragment); mMultiviewResolveStateSet->setAttributeAndModes(mMultiviewResolveProgram); mMultiviewResolveStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", 0)); } void PingPongCanvas::setCurrentFrameData(size_t frameId, fx::DispatchArray&& data) { mBufferData[frameId].data = std::move(data); } void PingPongCanvas::setMask(size_t frameId, bool underwater, bool exterior) { mBufferData[frameId].mask = 0; mBufferData[frameId].mask |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; mBufferData[frameId].mask |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; } void PingPongCanvas::drawGeometry(osg::RenderInfo& renderInfo) const { osg::Geometry::drawImplementation(renderInfo); } static void attachCloneOfTemplate(osg::FrameBufferObject* fbo, osg::Camera::BufferComponent component, osg::Texture* tex) { osg::ref_ptr clone = static_cast(tex->clone(osg::CopyOp::SHALLOW_COPY)); fbo->setAttachment(component, Stereo::createMultiviewCompatibleAttachment(clone)); } void PingPongCanvas::drawImplementation(osg::RenderInfo& renderInfo) const { osg::State& state = *renderInfo.getState(); osg::GLExtensions* ext = state.get(); size_t frameId = state.getFrameStamp()->getFrameNumber() % 2; auto& bufferData = mBufferData[frameId]; const auto& data = bufferData.data; std::vector filtered; filtered.reserve(data.size()); for (size_t i = 0; i < data.size(); ++i) { const auto& node = data[i]; if (bufferData.mask & node.mFlags) continue; filtered.push_back(i); } auto* resolveViewport = state.getCurrentViewport(); if (filtered.empty() || !bufferData.postprocessing) { state.pushStateSet(mFallbackStateSet); state.apply(); if (Stereo::getMultiview()) { state.pushStateSet(mMultiviewResolveStateSet); state.apply(); } state.applyTextureAttribute(0, bufferData.sceneTex); resolveViewport->apply(state); drawGeometry(renderInfo); state.popStateSet(); if (Stereo::getMultiview()) { state.popStateSet(); } return; } const unsigned int handle = mFbos[0] ? mFbos[0]->getHandle(state.getContextID()) : 0; if (handle == 0 || bufferData.dirty) { for (auto& fbo : mFbos) { fbo = new osg::FrameBufferObject; attachCloneOfTemplate(fbo, osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); fbo->apply(state); glClearColor(0.5, 0.5, 0.5, 1); glClear(GL_COLOR_BUFFER_BIT); } if (Stereo::getMultiview()) { mMultiviewResolveFramebuffer = new osg::FrameBufferObject(); attachCloneOfTemplate(mMultiviewResolveFramebuffer, osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); mMultiviewResolveFramebuffer->apply(state); glClearColor(0.5, 0.5, 0.5, 1); glClear(GL_COLOR_BUFFER_BIT); mMultiviewResolveStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, (osg::Texture*)mMultiviewResolveFramebuffer->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); } mLuminanceCalculator.dirty(bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); if (Stereo::getStereo()) mRenderViewport = new osg::Viewport(0, 0, bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); else mRenderViewport = nullptr; bufferData.dirty = false; } constexpr std::array, 3> buffers = {{ {GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT}, {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT}, {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT} }}; (bufferData.hdr) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly supported, so that's what we use for now. mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId); auto buffer = buffers[0]; int lastDraw = 0; int lastShader = 0; unsigned int lastApplied = handle; const unsigned int cid = state.getContextID(); const osg::ref_ptr& destinationFbo = bufferData.destination ? bufferData.destination : nullptr; unsigned int destinationHandle = destinationFbo ? destinationFbo->getHandle(cid) : 0; auto bindDestinationFbo = [&]() { if (destinationFbo) { destinationFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); lastApplied = destinationHandle; } else if (Stereo::getMultiview()) { mMultiviewResolveFramebuffer->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); lastApplied = mMultiviewResolveFramebuffer->getHandle(cid); } else { ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); lastApplied = 0; } }; for (const size_t& index : filtered) { const auto& node = data[index]; node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, bufferData.depthTex); if (bufferData.hdr) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); if (bufferData.normalsTex) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, bufferData.normalsTex); state.pushStateSet(node.mRootStateSet); state.apply(); for (size_t passIndex = 0; passIndex < node.mPasses.size(); ++passIndex) { if (mRenderViewport) mRenderViewport->apply(state); const auto& pass = node.mPasses[passIndex]; bool lastPass = passIndex == node.mPasses.size() - 1; //VR-TODO: This won't actually work for tex2darrays if (lastShader == 0) pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, bufferData.sceneTex); else pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, (osg::Texture*)mFbos[lastShader - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); if (lastDraw == 0) pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, bufferData.sceneTex); else pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, (osg::Texture*)mFbos[lastDraw - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); if (pass.mRenderTarget) { pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); if (pass.mRenderTexture->getNumMipmapLevels() > 0) { state.setActiveTextureUnit(0); state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture()); ext->glGenerateMipmap(GL_TEXTURE_2D); } lastApplied = pass.mRenderTarget->getHandle(state.getContextID());; } else if (pass.mResolve && index == filtered.back()) { bindDestinationFbo(); if (!destinationFbo && !Stereo::getMultiview()) { resolveViewport->apply(state); } } else if (lastPass) { lastDraw = buffer[0]; lastShader = buffer[0]; mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); buffer = buffers[lastShader - GL_COLOR_ATTACHMENT0_EXT]; lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); } else { mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); lastDraw = buffer[0]; std::swap(buffer[0], buffer[1]); lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); } state.pushStateSet(pass.mStateSet); state.apply(); if (!state.getLastAppliedProgramObject()) mFallbackProgram->apply(state); drawGeometry(renderInfo); state.popStateSet(); state.apply(); } state.popStateSet(); } if (Stereo::getMultiview()) { ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); lastApplied = 0; resolveViewport->apply(state); state.pushStateSet(mMultiviewResolveStateSet); state.apply(); drawGeometry(renderInfo); state.popStateSet(); state.apply(); } if (lastApplied != destinationHandle) { bindDestinationFbo(); } } } openmw-openmw-0.48.0/apps/openmw/mwrender/pingpongcanvas.hpp000066400000000000000000000057311445372753700242630ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_PINGPONGCANVAS_H #define OPENMW_MWRENDER_PINGPONGCANVAS_H #include #include #include #include #include #include #include "postprocessor.hpp" #include "luminancecalculator.hpp" namespace Shader { class ShaderManager; } namespace MWRender { class PingPongCanvas : public osg::Geometry { public: PingPongCanvas(Shader::ShaderManager& shaderManager); void drawImplementation(osg::RenderInfo& renderInfo) const override; void dirty(size_t frameId) { mBufferData[frameId].dirty = true; } const fx::DispatchArray& getCurrentFrameData(size_t frame) { return mBufferData[frame % 2].data; } // Sets current frame pass data and stores copy of dispatch array to apply to next frame data void setCurrentFrameData(size_t frameId, fx::DispatchArray&& data); void setMask(size_t frameId, bool underwater, bool exterior); void setSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTex = tex; } void setLDRSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTexLDR = tex; } void setDepthTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].depthTex = tex; } void setNormalsTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].normalsTex = tex; } void setHDR(size_t frameId, bool hdr) { mBufferData[frameId].hdr = hdr; } void setPostProcessing(size_t frameId, bool postprocessing) { mBufferData[frameId].postprocessing = postprocessing; } const osg::ref_ptr& getSceneTexture(size_t frameId) const { return mBufferData[frameId].sceneTex; } void drawGeometry(osg::RenderInfo& renderInfo) const; private: void copyNewFrameData(size_t frameId) const; mutable LuminanceCalculator mLuminanceCalculator; osg::ref_ptr mFallbackProgram; osg::ref_ptr mMultiviewResolveProgram; osg::ref_ptr mFallbackStateSet; osg::ref_ptr mMultiviewResolveStateSet; mutable osg::ref_ptr mMultiviewResolveFramebuffer; struct BufferData { bool dirty = false; bool hdr = false; bool postprocessing = true; fx::DispatchArray data; fx::FlagsType mask; osg::ref_ptr destination; osg::ref_ptr sceneTex; osg::ref_ptr depthTex; osg::ref_ptr sceneTexLDR; osg::ref_ptr normalsTex; }; mutable std::array mBufferData; mutable std::array, 3> mFbos; mutable osg::ref_ptr mRenderViewport; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/pingpongcull.cpp000066400000000000000000000066131445372753700237420ustar00rootroot00000000000000#include "pingpongcull.hpp" #include #include #include #include #include #include #include #include "postprocessor.hpp" #include "pingpongcanvas.hpp" namespace MWRender { PingPongCull::PingPongCull(PostProcessor* pp) : mViewportStateset(nullptr) , mPostProcessor(pp) { if (Stereo::getStereo()) { mViewportStateset = new osg::StateSet(); mViewport = new osg::Viewport(0, 0, pp->renderWidth(), pp->renderHeight()); mViewportStateset->setAttribute(mViewport); } } PingPongCull::~PingPongCull() { // Instantiate osg::ref_ptr<> destructor } void PingPongCull::operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); size_t frame = cv->getTraversalNumber(); size_t frameId = frame % 2; MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); if (!postProcessor) throw std::runtime_error("PingPongCull: failed to get a PostProcessor!"); if (Stereo::getStereo()) { auto& sm = Stereo::Manager::instance(); auto view = sm.getEye(cv); int index = view == Stereo::Eye::Right ? 1 : 0; auto projectionMatrix = sm.computeEyeProjection(index, true); postProcessor->getStateUpdater()->setProjectionMatrix(projectionMatrix); } postProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); postProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix[0]); mLastViewMatrix[0] = cv->getCurrentCamera()->getViewMatrix(); postProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); postProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); if (!postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) { renderStage->setMultisampleResolveFramebufferObject(nullptr); renderStage->setFrameBufferObject(nullptr); } else if (!postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) { renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); } else { renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); // The MultiView patch has a bug where it does not update resolve layers if the resolve framebuffer is changed. // So we do blit manually in this case if (Stereo::getMultiview() && !renderStage->getDrawCallback()) Stereo::setMultiviewMSAAResolveCallback(renderStage); } if (mViewportStateset) { mViewport->setViewport(0, 0, mPostProcessor->renderWidth(), mPostProcessor->renderHeight()); renderStage->setViewport(mViewport); cv->pushStateSet(mViewportStateset.get()); traverse(node, cv); cv->popStateSet(); } else traverse(node, cv); } } openmw-openmw-0.48.0/apps/openmw/mwrender/pingpongcull.hpp000066400000000000000000000013741445372753700237460ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_PINGPONGCULL_H #define OPENMW_MWRENDER_PINGPONGCULL_H #include #include #include "postprocessor.hpp" namespace osg { class StateSet; class Viewport; } namespace MWRender { class PostProcessor; class PingPongCull : public SceneUtil::NodeCallback { public: PingPongCull(PostProcessor* pp); ~PingPongCull(); void operator()(osg::Node* node, osgUtil::CullVisitor* nv); private: std::array mLastViewMatrix; osg::ref_ptr mViewportStateset; osg::ref_ptr mViewport; PostProcessor* mPostProcessor; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/postprocessor.cpp000066400000000000000000000776741445372753700242050ustar00rootroot00000000000000#include "postprocessor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwgui/postprocessorhud.hpp" #include "transparentpass.hpp" #include "pingpongcull.hpp" #include "renderingmanager.hpp" #include "vismask.hpp" #include "sky.hpp" namespace { struct ResizedCallback : osg::GraphicsContext::ResizedCallback { ResizedCallback(MWRender::PostProcessor* postProcessor) : mPostProcessor(postProcessor) { } void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override { gc->resizedImplementation(x, y, width, height); mPostProcessor->setRenderTargetSize(width, height); mPostProcessor->resize(); } MWRender::PostProcessor* mPostProcessor; }; class HUDCullCallback : public SceneUtil::NodeCallback { public: void operator()(osg::Camera* camera, osgUtil::CullVisitor* cv) { osg::ref_ptr stateset = new osg::StateSet; auto& sm = Stereo::Manager::instance(); auto* fullViewport = camera->getViewport(); if (sm.getEye(cv) == Stereo::Eye::Left) stateset->setAttributeAndModes(new osg::Viewport(0, 0, fullViewport->width() / 2, fullViewport->height())); if (sm.getEye(cv) == Stereo::Eye::Right) stateset->setAttributeAndModes(new osg::Viewport(fullViewport->width() / 2, 0, fullViewport->width() / 2, fullViewport->height())); cv->pushStateSet(stateset); traverse(camera, cv); cv->popStateSet(); } }; enum class Usage { RENDER_BUFFER, TEXTURE, }; static osg::FrameBufferAttachment createFrameBufferAttachmentFromTemplate(Usage usage, int width, int height, osg::Texture* template_, int samples) { if (usage == Usage::RENDER_BUFFER && !Stereo::getMultiview()) { osg::ref_ptr attachment = new osg::RenderBuffer(width, height, template_->getInternalFormat(), samples); return osg::FrameBufferAttachment(attachment); } auto texture = Stereo::createMultiviewCompatibleTexture(width, height, samples); texture->setSourceFormat(template_->getSourceFormat()); texture->setSourceType(template_->getSourceType()); texture->setInternalFormat(template_->getInternalFormat()); texture->setFilter(osg::Texture2D::MIN_FILTER, template_->getFilter(osg::Texture2D::MIN_FILTER)); texture->setFilter(osg::Texture2D::MAG_FILTER, template_->getFilter(osg::Texture2D::MAG_FILTER)); texture->setWrap(osg::Texture::WRAP_S, template_->getWrap(osg::Texture2D::WRAP_S)); texture->setWrap(osg::Texture::WRAP_T, template_->getWrap(osg::Texture2D::WRAP_T)); return Stereo::createMultiviewCompatibleAttachment(texture); } } namespace MWRender { PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs) : osg::Group() , mEnableLiveReload(false) , mRootNode(rootNode) , mSamples(Settings::Manager::getInt("antialiasing", "Video")) , mDirty(false) , mDirtyFrameId(0) , mRendering(rendering) , mViewer(viewer) , mVFS(vfs) , mTriggerShaderReload(false) , mReload(false) , mEnabled(false) , mUsePostProcessing(false) , mSoftParticles(false) , mDisableDepthPasses(false) , mLastFrameNumber(0) , mLastSimulationTime(0.f) , mExteriorFlag(false) , mUnderwater(false) , mHDR(false) , mNormals(false) , mPrevNormals(false) , mNormalsSupported(false) , mPassLights(false) , mPrevPassLights(false) { mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders"); mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); mWidth = gc->getTraits()->width; mHeight = gc->getTraits()->height; if (!ext->glDisablei && ext->glDisableIndexedEXT) ext->glDisablei = ext->glDisableIndexedEXT; #ifdef ANDROID ext->glDisablei = nullptr; #endif if (ext->glDisablei) mNormalsSupported = true; else Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; if (mSoftParticles) { for (int i = 0; i < 2; ++i) { if (Stereo::getMultiview()) mTextures[i][Tex_OpaqueDepth] = new osg::Texture2DArray; else mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); } } mGLSLVersion = ext->glslLanguageVersion * 100; mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing) return; enable(mUsePostProcessing); } PostProcessor::~PostProcessor() { if (auto* bin = osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")) bin->setDrawCallback(nullptr); } void PostProcessor::resize() { mHUDCamera->resize(mWidth, mHeight); mViewer->getCamera()->resize(mWidth, mHeight); if (Stereo::getStereo()) Stereo::Manager::instance().screenResolutionChanged(); auto width = renderWidth(); auto height = renderHeight(); for (auto& technique : mTechniques) { for (auto& [name, rt] : technique->getRenderTargetsMap()) { const auto [w, h] = rt.mSize.get(width, height); rt.mTarget->setTextureSize(w, h); } } size_t frameId = frame() % 2; createTexturesAndCamera(frameId); createObjectsForFrame(frameId); mRendering.updateProjectionMatrix(); mRendering.setScreenRes(width, height); dirtyTechniques(); mPingPongCanvas->dirty(frameId); mDirty = true; mDirtyFrameId = !frameId; } void PostProcessor::populateTechniqueFiles() { for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) { boost::filesystem::path path = name; std::string fileExt = Misc::StringUtils::lowerCase(path.extension().string()); if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt) { auto absolutePath = boost::filesystem::path(mVFS->getAbsoluteFileName(name)); mTechniqueFileMap[absolutePath.stem().string()] = absolutePath; } } } void PostProcessor::enable(bool usePostProcessing) { mReload = true; mEnabled = true; bool postPass = Settings::Manager::getBool("transparent postpass", "Post Processing"); mUsePostProcessing = usePostProcessing; mDisableDepthPasses = !mSoftParticles && !postPass; #ifdef ANDROID mDisableDepthPasses = true; #endif if (!mDisableDepthPasses) { mTransparentDepthPostPass = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), postPass); osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } if (mUsePostProcessing && mTechniqueFileMap.empty()) { populateTechniqueFiles(); } createTexturesAndCamera(frame() % 2); removeChild(mHUDCamera); removeChild(mRootNode); addChild(mHUDCamera); addChild(mRootNode); mViewer->setSceneData(this); mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); mViewer->getCamera()->setUserData(this); setCullCallback(mStateUpdater); mHUDCamera->setCullCallback(new HUDCullCallback); } void PostProcessor::disable() { if (!mSoftParticles) osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles) { removeChild(mHUDCamera); setCullCallback(nullptr); mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); mViewer->getCamera()->getGraphicsContext()->setResizedCallback(nullptr); mViewer->getCamera()->setUserData(nullptr); mEnabled = false; } mUsePostProcessing = false; mRendering.getSkyManager()->setSunglare(true); } void PostProcessor::traverse(osg::NodeVisitor& nv) { if (!mEnabled) { osg::Group::traverse(nv); return; } size_t frameId = nv.getTraversalNumber() % 2; if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) cull(frameId, static_cast(&nv)); else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) update(frameId); osg::Group::traverse(nv); } void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) { const auto& fbo = getFbo(FBO_Intercept, frameId); if (fbo) { osgUtil::RenderStage* rs = cv->getRenderStage(); if (rs && rs->getMultisampleResolveFramebufferObject()) rs->setMultisampleResolveFramebufferObject(fbo); } mPingPongCanvas->setPostProcessing(frameId, mUsePostProcessing); mPingPongCanvas->setNormalsTexture(frameId, mNormals ? getTexture(Tex_Normal, frameId) : nullptr); mPingPongCanvas->setMask(frameId, mUnderwater, mExteriorFlag); mPingPongCanvas->setHDR(frameId, getHDR()); mPingPongCanvas->setSceneTexture(frameId, getTexture(Tex_Scene, frameId)); if (mDisableDepthPasses) mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_Depth, frameId)); else mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_OpaqueDepth, frameId)); mPingPongCanvas->setLDRSceneTexture(frameId, getTexture(Tex_Scene_LDR, frameId)); if (mTransparentDepthPostPass) { mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; mTransparentDepthPostPass->dirtyFrame(frameId); } size_t frame = cv->getTraversalNumber(); mStateUpdater->setResolution(osg::Vec2f(cv->getViewport()->width(), cv->getViewport()->height())); // per-frame data if (frame != mLastFrameNumber) { mLastFrameNumber = frame; auto stamp = cv->getFrameStamp(); mStateUpdater->setSimulationTime(static_cast(stamp->getSimulationTime())); mStateUpdater->setDeltaSimulationTime(static_cast(stamp->getSimulationTime() - mLastSimulationTime)); mLastSimulationTime = stamp->getSimulationTime(); for (const auto& dispatchNode : mPingPongCanvas->getCurrentFrameData(frame)) { for (auto& uniform : dispatchNode.mHandle->getUniformMap()) { if (uniform->getType().has_value() && !uniform->mSamplerType) if (auto* u = dispatchNode.mRootStateSet->getUniform(uniform->mName)) uniform->setUniform(u); } } } } void PostProcessor::updateLiveReload() { if (!mEnableLiveReload && !mTriggerShaderReload) return; mTriggerShaderReload = false;//Done only once for (auto& technique : mTechniques) { if (technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; const auto lastWriteTime = boost::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); const bool isDirty = technique->setLastModificationTime(lastWriteTime); if (!isDirty) continue; // TODO: Temporary workaround to avoid conflicts with external programs saving the file, especially problematic on Windows. // If we move to a file watcher using native APIs this should be removed. std::this_thread::sleep_for(std::chrono::milliseconds(5)); if (technique->compile()) Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()].string(); mReload = technique->isValid(); } } void PostProcessor::reloadIfRequired() { if (!mReload) return; mReload = false; loadChain(); resize(); } void PostProcessor::update(size_t frameId) { while (!mQueuedTemplates.empty()) { mTemplates.push_back(std::move(mQueuedTemplates.back())); mQueuedTemplates.pop_back(); } updateLiveReload(); reloadIfRequired(); if (mDirty && mDirtyFrameId == frameId) { createTexturesAndCamera(frameId); createObjectsForFrame(frameId); mDirty = false; mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); } if ((mNormalsSupported && mNormals != mPrevNormals) || (mPassLights != mPrevPassLights)) { mPrevNormals = mNormals; mPrevPassLights = mPassLights; mViewer->stopThreading(); auto& shaderManager = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); auto defines = shaderManager.getGlobalDefines(); defines["disableNormals"] = mNormals ? "0" : "1"; shaderManager.setGlobalDefines(defines); mRendering.getLightRoot()->setCollectPPLights(mPassLights); mStateUpdater->bindPointLights(mPassLights ? mRendering.getLightRoot()->getPPLightsBuffer() : nullptr); mStateUpdater->reset(); mViewer->startThreading(); createTexturesAndCamera(frameId); createObjectsForFrame(frameId); mDirty = true; mDirtyFrameId = !frameId; } } void PostProcessor::createObjectsForFrame(size_t frameId) { auto& fbos = mFbos[frameId]; auto& textures = mTextures[frameId]; auto width = renderWidth(); auto height = renderHeight(); for (auto& tex : textures) { if (!tex) continue; Stereo::setMultiviewCompatibleTextureSize(tex, width, height); tex->dirtyTextureObject(); } fbos[FBO_Primary] = new osg::FrameBufferObject; fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER0, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); if (mNormals && mNormalsSupported) fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER1, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); fbos[FBO_Primary]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Depth])); fbos[FBO_FirstPerson] = new osg::FrameBufferObject; auto fpDepthRb = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb)); if (mSamples > 1) { fbos[FBO_Multisample] = new osg::FrameBufferObject; auto colorRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Scene], mSamples); if (mNormals && mNormalsSupported) { auto normalRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); } auto depthRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, depthRB); fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB); fbos[FBO_Intercept] = new osg::FrameBufferObject; fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); } else { fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); if (mNormals && mNormalsSupported) fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); } if (textures[Tex_OpaqueDepth]) { fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); } #ifdef __APPLE__ if (textures[Tex_OpaqueDepth]) fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(textures[Tex_OpaqueDepth]->getTextureWidth(), textures[Tex_OpaqueDepth]->getTextureHeight(), textures[Tex_Scene]->getInternalFormat()))); #endif } void PostProcessor::dirtyTechniques() { if (!isEnabled()) return; size_t frameId = frame() % 2; mDirty = true; mDirtyFrameId = !frameId; mTemplateData = {}; bool sunglare = true; mHDR = false; mNormals = false; mPassLights = false; for (const auto& technique : mTechniques) { if (!technique->isValid()) continue; if (technique->getGLSLVersion() > mGLSLVersion) { Log(Debug::Warning) << "Technique " << technique->getName() << " requires GLSL version " << technique->getGLSLVersion() << " which is unsupported by your hardware."; continue; } fx::DispatchNode node; node.mFlags = technique->getFlags(); if (technique->getHDR()) mHDR = true; if (technique->getNormals()) mNormals = true; if (technique->getLights()) mPassLights = true; if (node.mFlags & fx::Technique::Flag_Disable_SunGlare) sunglare = false; // required default samplers available to every shader pass node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); if (mNormals) node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); if (technique->getHDR()) node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); int texUnit = Unit_NextFree; // user-defined samplers for (const osg::Texture* texture : technique->getTextures()) { if (const auto* tex1D = dynamic_cast(texture)) node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture1D(*tex1D)); else if (const auto* tex2D = dynamic_cast(texture)) node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture2D(*tex2D)); else if (const auto* tex3D = dynamic_cast(texture)) node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture3D(*tex3D)); node.mRootStateSet->addUniform(new osg::Uniform(texture->getName().c_str(), texUnit++)); } // user-defined uniforms for (auto& uniform : technique->getUniformMap()) { if (uniform->mSamplerType) continue; if (auto type = uniform->getType()) uniform->setUniform(node.mRootStateSet->getOrCreateUniform(uniform->mName.c_str(), *type, uniform->getNumElements())); } std::unordered_map renderTargetCache; for (const auto& pass : technique->getPasses()) { int subTexUnit = texUnit; fx::DispatchNode::SubPass subPass; pass->prepareStateSet(subPass.mStateSet, technique->getName()); node.mHandle = technique; if (!pass->getTarget().empty()) { const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight()); subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); renderTargetCache[rt.mTarget] = subPass.mRenderTexture; subPass.mRenderTexture->setTextureSize(w, h); subPass.mRenderTexture->setName(std::string(pass->getTarget())); if (rt.mMipMap) subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); } for (const auto& whitelist : pass->getRenderTargets()) { auto it = technique->getRenderTargetsMap().find(whitelist); if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget]) { subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]); subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++)); } } node.mPasses.emplace_back(std::move(subPass)); } node.compile(); mTemplateData.emplace_back(std::move(node)); } mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); if (auto hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) hud->updateTechniques(); mRendering.getSkyManager()->setSunglare(sunglare); } PostProcessor::Status PostProcessor::enableTechnique(std::shared_ptr technique, std::optional location) { if (!isEnabled()) { Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << technique->getName() << "'"; return Status_Error; } if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0)) return Status_Error; disableTechnique(technique, false); int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); dirtyTechniques(); return Status_Toggled; } PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) { if (technique->getLocked()) return Status_Error; auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); if (it == std::end(mTechniques)) return Status_Unchanged; mTechniques.erase(it); if (dirty) dirtyTechniques(); return Status_Toggled; } bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const { if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) return false; return technique->isValid(); } void PostProcessor::createTexturesAndCamera(size_t frameId) { auto& textures = mTextures[frameId]; auto width = renderWidth(); auto height = renderHeight(); for (auto& texture : textures) { if (!texture) { if (Stereo::getMultiview()) texture = new osg::Texture2DArray; else texture = new osg::Texture2D; } Stereo::setMultiviewCompatibleTextureSize(texture, width, height); texture->setSourceFormat(GL_RGBA); texture->setSourceType(GL_UNSIGNED_BYTE); texture->setInternalFormat(GL_RGBA); texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setResizeNonPowerOfTwoHint(false); } textures[Tex_Normal]->setSourceFormat(GL_RGB); textures[Tex_Normal]->setInternalFormat(GL_RGB); auto setupDepth = [] (osg::Texture* tex) { tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); }; setupDepth(textures[Tex_Depth]); if (mDisableDepthPasses) { textures[Tex_OpaqueDepth] = nullptr; } else { setupDepth(textures[Tex_OpaqueDepth]); textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); } if (mHUDCamera) return; mHUDCamera = new osg::Camera; mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); mHUDCamera->setClearMask(0); mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); mHUDCamera->setViewport(0, 0, mWidth, mHeight); mViewer->getCamera()->removeCullCallback(mPingPongCull); mPingPongCull = new PingPongCull(this); mViewer->getCamera()->addCullCallback(mPingPongCull); mPingPongCanvas = new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()); mHUDCamera->addChild(mPingPongCanvas); mHUDCamera->setNodeMask(Mask_RenderToTexture); mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool loadNextFrame) { if (!isEnabled()) { Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << name << "'"; return nullptr; } for (const auto& technique : mTemplates) if (Misc::StringUtils::ciEqual(technique->getName(), name)) return technique; for (const auto& technique : mQueuedTemplates) if (Misc::StringUtils::ciEqual(technique->getName(), name)) return technique; auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), name, renderWidth(), renderHeight(), mUBO, mNormalsSupported); technique->compile(); if (technique->getStatus() != fx::Technique::Status::File_Not_exists) technique->setLastModificationTime(boost::filesystem::last_write_time(mTechniqueFileMap[technique->getName()])); if (loadNextFrame) { mQueuedTemplates.push_back(technique); return technique; } mTemplates.push_back(std::move(technique)); return mTemplates.back(); } void PostProcessor::loadChain() { if (!isEnabled()) return; mTechniques.clear(); std::vector techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing"); for (auto& techniqueName : techniqueStrings) { if (techniqueName.empty()) continue; mTechniques.push_back(loadTechnique(techniqueName)); } dirtyTechniques(); } void PostProcessor::saveChain() { std::vector chain; for (const auto& technique : mTechniques) { if (!technique || technique->getDynamic()) continue; chain.push_back(technique->getName()); } Settings::Manager::setStringArray("chain", "Post Processing", chain); } void PostProcessor::toggleMode() { for (auto& technique : mTemplates) technique->compile(); dirtyTechniques(); } void PostProcessor::disableDynamicShaders() { for (auto& technique : mTechniques) if (technique->getDynamic()) disableTechnique(technique); } int PostProcessor::renderWidth() const { if (Stereo::getStereo()) return Stereo::Manager::instance().eyeResolution().x(); return mWidth; } int PostProcessor::renderHeight() const { if (Stereo::getStereo()) return Stereo::Manager::instance().eyeResolution().y(); return mHeight; } void PostProcessor::triggerShaderReload() { mTriggerShaderReload = true; } } openmw-openmw-0.48.0/apps/openmw/mwrender/postprocessor.hpp000066400000000000000000000154111445372753700241670ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_POSTPROCESSOR_H #define OPENMW_MWRENDER_POSTPROCESSOR_H #include #include #include #include #include #include #include #include #include #include #include #include #include "pingpongcanvas.hpp" #include "transparentpass.hpp" #include namespace osgViewer { class Viewer; } namespace Stereo { class MultiviewFramebuffer; } namespace VFS { class Manager; } namespace Shader { class ShaderManager; } namespace MWRender { class RenderingManager; class PingPongCull; class PingPongCanvas; class TransparentDepthBinCallback; class PostProcessor : public osg::Group { public: using FBOArray = std::array, 5>; using TextureArray = std::array, 5>; using TechniqueList = std::vector>; enum TextureIndex { Tex_Scene, Tex_Scene_LDR, Tex_Depth, Tex_OpaqueDepth, Tex_Normal }; enum FBOIndex { FBO_Primary, FBO_Multisample, FBO_FirstPerson, FBO_OpaqueDepth, FBO_Intercept }; enum TextureUnits { Unit_LastShader = 0, Unit_LastPass, Unit_Depth, Unit_EyeAdaptation, Unit_Normals, Unit_NextFree }; enum Status { Status_Error, Status_Toggled, Status_Unchanged }; PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs); ~PostProcessor(); void traverse(osg::NodeVisitor& nv) override; osg::ref_ptr getFbo(FBOIndex index, unsigned int frameId) { return mFbos[frameId][index]; } osg::ref_ptr getTexture(TextureIndex index, unsigned int frameId) { return mTextures[frameId][index]; } osg::ref_ptr getPrimaryFbo(unsigned int frameId) { return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; } osg::ref_ptr getStateUpdater() { return mStateUpdater; } const TechniqueList& getTechniques() { return mTechniques; } const TechniqueList& getTemplates() const { return mTemplates; } osg::ref_ptr getCanvas() { return mPingPongCanvas; } const auto& getTechniqueMap() const { return mTechniqueFileMap; } void resize(); Status enableTechnique(std::shared_ptr technique, std::optional location = std::nullopt); Status disableTechnique(std::shared_ptr technique, bool dirty = true); bool getSupportsNormalsRT() const { return mNormalsSupported; } template void setUniform(std::shared_ptr technique, const std::string& name, const T& value) { if (!isEnabled()) return; auto it = technique->findUniform(name); if (it == technique->getUniformMap().end()) return; if ((*it)->mStatic) { Log(Debug::Warning) << "Attempting to set a configration variable [" << name << "] as a uniform"; return; } (*it)->setValue(value); } std::optional getUniformSize(std::shared_ptr technique, const std::string& name) { auto it = technique->findUniform(name); if (it == technique->getUniformMap().end()) return std::nullopt; return (*it)->getNumElements(); } bool isTechniqueEnabled(const std::shared_ptr& technique) const; void setExteriorFlag(bool exterior) { mExteriorFlag = exterior; } void setUnderwaterFlag(bool underwater) { mUnderwater = underwater; } void toggleMode(); std::shared_ptr loadTechnique(const std::string& name, bool loadNextFrame=false); bool isEnabled() const { return mUsePostProcessing && mEnabled; } bool softParticlesEnabled() const {return mSoftParticles; } bool getHDR() const { return mHDR; } void disable(); void enable(bool usePostProcessing = true); void setRenderTargetSize(int width, int height) { mWidth = width; mHeight = height; } void disableDynamicShaders(); int renderWidth() const; int renderHeight() const; void triggerShaderReload(); bool mEnableLiveReload; void loadChain(); void saveChain(); private: void populateTechniqueFiles(); size_t frame() const { return mViewer->getFrameStamp()->getFrameNumber(); } void createObjectsForFrame(size_t frameId); void createTexturesAndCamera(size_t frameId); void reloadMainPass(fx::Technique& technique); void dirtyTechniques(); void update(size_t frameId); void reloadIfRequired(); void updateLiveReload(); void cull(size_t frameId, osgUtil::CullVisitor* cv); osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; std::array mTextures; std::array mFbos; TechniqueList mTechniques; TechniqueList mTemplates; TechniqueList mQueuedTemplates; std::unordered_map mTechniqueFileMap; int mSamples; bool mDirty; size_t mDirtyFrameId; RenderingManager& mRendering; osgViewer::Viewer* mViewer; const VFS::Manager* mVFS; bool mTriggerShaderReload; bool mReload; bool mEnabled; bool mUsePostProcessing; bool mSoftParticles; bool mDisableDepthPasses; size_t mLastFrameNumber; float mLastSimulationTime; bool mExteriorFlag; bool mUnderwater; bool mHDR; bool mNormals; bool mPrevNormals; bool mNormalsSupported; bool mPassLights; bool mPrevPassLights; bool mUBO; int mGLSLVersion; osg::ref_ptr mStateUpdater; osg::ref_ptr mPingPongCull; osg::ref_ptr mPingPongCanvas; osg::ref_ptr mTransparentDepthPostPass; int mWidth; int mHeight; fx::DispatchArray mTemplateData; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/recastmesh.cpp000066400000000000000000000061311445372753700233720ustar00rootroot00000000000000#include "recastmesh.hpp" #include #include #include #include #include #include #include "vismask.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" namespace MWRender { RecastMesh::RecastMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) , mEnabled(enabled) { } RecastMesh::~RecastMesh() { if (mEnabled) disable(); } bool RecastMesh::toggle() { if (mEnabled) disable(); else enable(); return mEnabled; } void RecastMesh::update(const DetourNavigator::RecastMeshTiles& tiles, const DetourNavigator::Settings& settings) { if (!mEnabled) return; for (auto it = mGroups.begin(); it != mGroups.end();) { const auto tile = tiles.find(it->first); if (tile == tiles.end()) { mRootNode->removeChild(it->second.mValue); it = mGroups.erase(it); continue; } if (it->second.mGeneration != tile->second->getGeneration() || it->second.mRevision != tile->second->getRevision()) { const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mRootNode->removeChild(it->second.mValue); mRootNode->addChild(group); it->second.mValue = group; it->second.mGeneration = tile->second->getGeneration(); it->second.mRevision = tile->second->getRevision(); } ++it; } for (const auto& tile : tiles) { if (mGroups.count(tile.first)) continue; const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); mRootNode->addChild(group); } } void RecastMesh::reset() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); mGroups.clear(); } void RecastMesh::enable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->addChild(v.second.mValue); }); mEnabled = true; } void RecastMesh::disable() { std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); mEnabled = false; } } openmw-openmw-0.48.0/apps/openmw/mwrender/recastmesh.hpp000066400000000000000000000020451445372753700233770ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RECASTMESH_H #define OPENMW_MWRENDER_RECASTMESH_H #include #include #include namespace osg { class Group; class Geometry; } namespace DetourNavigator { struct Settings; } namespace MWRender { class RecastMesh { public: RecastMesh(const osg::ref_ptr& root, bool enabled); ~RecastMesh(); bool toggle(); void update(const DetourNavigator::RecastMeshTiles& recastMeshTiles, const DetourNavigator::Settings& settings); void reset(); void enable(); void disable(); bool isEnabled() const { return mEnabled; } private: struct Group { std::size_t mGeneration; std::size_t mRevision; osg::ref_ptr mValue; }; osg::ref_ptr mRootNode; bool mEnabled; std::map mGroups; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/renderbin.hpp000066400000000000000000000010521445372753700232060ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RENDERBIN_H #define OPENMW_MWRENDER_RENDERBIN_H namespace MWRender { /// Defines the render bin numbers used in the OpenMW scene graph. The bin with the lowest number is rendered first. enum RenderBins { RenderBin_Sky = -1, RenderBin_Default = 0, // osg::StateSet::OPAQUE_BIN RenderBin_Water = 9, RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN RenderBin_OcclusionQuery = 11, RenderBin_FirstPerson = 12, RenderBin_SunGlare = 13 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/renderinginterface.hpp000066400000000000000000000004441445372753700251000ustar00rootroot00000000000000#ifndef GAME_RENDERING_INTERFACE_H #define GAME_RENDERING_INTERFACE_H namespace MWRender { class Objects; class Actors; class RenderingInterface { public: virtual MWRender::Objects& getObjects() = 0; virtual ~RenderingInterface(){} }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/renderingmanager.cpp000066400000000000000000001754411445372753700245570ustar00rootroot00000000000000#include "renderingmanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" #include "../mwgui/loadingscreen.hpp" #include "../mwgui/postprocessorhud.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "sky.hpp" #include "effectmanager.hpp" #include "npcanimation.hpp" #include "vismask.hpp" #include "pathgrid.hpp" #include "camera.hpp" #include "water.hpp" #include "terrainstorage.hpp" #include "navmesh.hpp" #include "actorspaths.hpp" #include "recastmesh.hpp" #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" #include "groundcover.hpp" #include "postprocessor.hpp" namespace MWRender { class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater { public: PerViewUniformStateUpdater(Resource::SceneManager* sceneManager) : mSceneManager(sceneManager) { mOpaqueTextureUnit = mSceneManager->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); } void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); if (mSkyRTT) stateset->addUniform(new osg::Uniform("sky", mSkyTextureUnit)); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mProjectionMatrix); if (mSkyRTT && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { osg::Texture* skyTexture = mSkyRTT->getColorTexture(static_cast(nv)); stateset->setTextureAttribute(mSkyTextureUnit, skyTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); } stateset->setTextureAttribute(mOpaqueTextureUnit, mSceneManager->getOpaqueDepthTex(nv->getTraversalNumber()), osg::StateAttribute::ON); } void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); } void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); } void setProjectionMatrix(const osg::Matrixf& projectionMatrix) { mProjectionMatrix = projectionMatrix; } const osg::Matrixf& getProjectionMatrix() const { return mProjectionMatrix; } void enableSkyRTT(int skyTextureUnit, SceneUtil::RTTNode* skyRTT) { mSkyTextureUnit = skyTextureUnit; mSkyRTT = skyRTT; } private: osg::Matrixf mProjectionMatrix; int mSkyTextureUnit = -1; SceneUtil::RTTNode* mSkyRTT = nullptr; Resource::SceneManager* mSceneManager; int mOpaqueTextureUnit = -1; }; class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater { public: SharedUniformStateUpdater(bool usePlayerUniforms) : mLinearFac(0.f) , mNear(0.f) , mFar(0.f) , mUsePlayerUniforms(usePlayerUniforms) , mWindSpeed(0.f) { } void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 0.f)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{})); if (mUsePlayerUniforms) { stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); } } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { auto* uLinearFac = stateset->getUniform("linearFac"); if (uLinearFac) uLinearFac->set(mLinearFac); auto* uNear = stateset->getUniform("near"); if (uNear) uNear->set(mNear); auto* uFar = stateset->getUniform("far"); if (uFar) uFar->set(mFar); static const float mSkyBlendingStartCoef = Settings::Manager::getFloat("sky blending start", "Fog"); auto* uSkyBlendingStart = stateset->getUniform("skyBlendingStart"); if (uSkyBlendingStart) uSkyBlendingStart->set(mFar * mSkyBlendingStartCoef); auto* uScreenRes = stateset->getUniform("screenRes"); if (uScreenRes) uScreenRes->set(mScreenRes); if (mUsePlayerUniforms) { auto* windSpeed = stateset->getUniform("windSpeed"); if (windSpeed) windSpeed->set(mWindSpeed); auto* playerPos = stateset->getUniform("playerPos"); if (playerPos) playerPos->set(mPlayerPos); } } void setLinearFac(float linearFac) { mLinearFac = linearFac; } void setNear(float near) { mNear = near; } void setFar(float far) { mFar = far; } void setScreenRes(float width, float height) { mScreenRes = osg::Vec2f(width, height); } void setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; } void setPlayerPos(osg::Vec3f playerPos) { mPlayerPos = playerPos; } private: float mLinearFac; float mNear; float mFar; bool mUsePlayerUniforms; float mWindSpeed; osg::Vec3f mPlayerPos; osg::Vec2f mScreenRes; }; class StateUpdater : public SceneUtil::StateSetUpdater { public: StateUpdater() : mFogStart(0.f) , mFogEnd(0.f) , mWireframe(false) { } void setDefaults(osg::StateSet *stateset) override { osg::LightModel* lightModel = new osg::LightModel; stateset->setAttribute(lightModel, osg::StateAttribute::ON); osg::Fog* fog = new osg::Fog; fog->setMode(osg::Fog::LINEAR); stateset->setAttributeAndModes(fog, osg::StateAttribute::ON); if (mWireframe) { osg::PolygonMode* polygonmode = new osg::PolygonMode; polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateset->setAttributeAndModes(polygonmode, osg::StateAttribute::ON); } else stateset->removeAttribute(osg::StateAttribute::POLYGONMODE); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { osg::LightModel* lightModel = static_cast(stateset->getAttribute(osg::StateAttribute::LIGHTMODEL)); lightModel->setAmbientIntensity(mAmbientColor); osg::Fog* fog = static_cast(stateset->getAttribute(osg::StateAttribute::FOG)); fog->setColor(mFogColor); fog->setStart(mFogStart); fog->setEnd(mFogEnd); } void setAmbientColor(const osg::Vec4f& col) { mAmbientColor = col; } void setFogColor(const osg::Vec4f& col) { mFogColor = col; } void setFogStart(float start) { mFogStart = start; } void setFogEnd(float end) { mFogEnd = end; } void setWireframe(bool wireframe) { if (mWireframe != wireframe) { mWireframe = wireframe; reset(); } } bool getWireframe() const { return mWireframe; } private: osg::Vec4f mAmbientColor; osg::Vec4f mFogColor; float mFogStart; float mFogEnd; bool mWireframe; }; class PreloadCommonAssetsWorkItem : public SceneUtil::WorkItem { public: PreloadCommonAssetsWorkItem(Resource::ResourceSystem* resourceSystem) : mResourceSystem(resourceSystem) { } void doWork() override { try { for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) mResourceSystem->getSceneManager()->getTemplate(*it); for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) mResourceSystem->getImageManager()->getImage(*it); for (std::vector::const_iterator it = mKeyframes.begin(); it != mKeyframes.end(); ++it) mResourceSystem->getKeyframeManager()->get(*it); } catch (std::exception&) { // ignore error (will be shown when these are needed proper) } } std::vector mModels; std::vector mTextures; std::vector mKeyframes; private: Resource::ResourceSystem* mResourceSystem; }; RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore, SceneUtil::UnrefQueue& unrefQueue) : mSkyBlending(Settings::Manager::getBool("sky blending", "Fog")) , mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mNavigator(navigator) , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations CPU-side. // See issue: #6072 , mNearClip(std::max(0.005f, Settings::Manager::getFloat("near clip", "Camera"))) , mViewDistance(Settings::Manager::getFloat("viewing distance", "Camera")) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) , mFieldOfView(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)) , mFirstPersonFieldOfView(std::clamp(Settings::Manager::getFloat("first person field of view", "Camera"), 1.f, 179.f)) { bool reverseZ = SceneUtil::AutoDepth::isReversed(); auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::Manager::getBool("radial fog", "Fog") || Settings::Manager::getBool("exponential fog", "Fog") || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); resourceSystem->getSceneManager()->setAdjustCoverageForAlphaTest(Settings::Manager::getBool("adjust coverage for alpha test", "Shaders")); // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); sceneRoot->setNodeMask(Mask_Scene); sceneRoot->setName("Scene Root"); int shadowCastingTraversalMask = Mask_Scene; if (Settings::Manager::getBool("actor shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Actor; if (Settings::Manager::getBool("player shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Player; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= (Mask_Object|Mask_Static); if (Settings::Manager::getBool("terrain shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Terrain; mShadowManager = std::make_unique(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, Mask_Terrain|Mask_Object|Mask_Static, mResourceSystem->getSceneManager()->getShaderManager()); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) globalDefines[itr->first] = itr->second; globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; bool exponentialFog = Settings::Manager::getBool("exponential fog", "Fog"); globalDefines["radialFog"] = (exponentialFog || Settings::Manager::getBool("radial fog", "Fog")) ? "1" : "0"; globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; globalDefines["refraction_enabled"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; globalDefines["disableNormals"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; // Refactor this at some point - most shaders don't care about these defines float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); globalDefines["groundcoverStompMode"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp mode", "Groundcover"), 0, 2)); globalDefines["groundcoverStompIntensity"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp intensity", "Groundcover"), 0, 2)); globalDefines["reverseZ"] = reverseZ ? "1" : "0"; // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh = std::make_unique(mRootNode, mWorkQueue, Settings::Manager::getBool("enable nav mesh render", "Navigator"), parseNavMeshMode(Settings::Manager::getString("nav mesh render mode", "Navigator"))); mActorsPaths = std::make_unique(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")); mRecastMesh = std::make_unique(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator")); mPathgrid = std::make_unique(mRootNode); mObjects = std::make_unique(mResourceSystem, sceneRoot, unrefQueue); if (getenv("OPENMW_DONT_PRECOMPILE") == nullptr) { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); } mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mEffectManager = std::make_unique(sceneRoot, mResourceSystem); const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); mTerrainStorage = std::make_unique(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); bool groundcover = Settings::Manager::getBool("enabled", "Groundcover"); bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); if (distantTerrain || groundcover) { const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); mTerrain = std::make_unique( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks); if (Settings::Manager::getBool("object paging", "Terrain")) { mObjectPaging = std::make_unique(mResourceSystem->getSceneManager()); static_cast(mTerrain.get())->addChunkManager(mObjectPaging.get()); mResourceSystem->addResourceManager(mObjectPaging.get()); } } else mTerrain = std::make_unique(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); if (groundcover) { float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); mGroundcover = std::make_unique(mResourceSystem->getSceneManager(), density, groundcoverDistance, groundcoverStore); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); } mStateUpdater = new StateUpdater; sceneRoot->addUpdateCallback(mStateUpdater); mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(mResourceSystem->getSceneManager()); rootNode->addCullCallback(mPerViewUniformStateUpdater); mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS()); resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1)); resourceSystem->getSceneManager()->setSoftParticles(mPostProcessor->softParticlesEnabled()); resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT()); // water goes after terrain for correct waterculling order mWater = std::make_unique(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath); mCamera = std::make_unique(mViewer->getCamera()); mScreenshotManager = std::make_unique(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get()); mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; source->setNodeMask(Mask_Lighting); mSunLight = new osg::Light; source->setLight(mSunLight); mSunLight->setDiffuse(osg::Vec4f(0,0,0,1)); mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); sceneRoot->setSunlight(mSunLight); sceneRoot->addChild(source); sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); sceneRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); sceneRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); osg::ref_ptr defaultMat (new osg::Material); defaultMat->setColorMode(osg::Material::OFF); defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); mFog = std::make_unique(); mSky = std::make_unique(sceneRoot, resourceSystem->getSceneManager(), mSkyBlending); mSky->setCamera(mViewer->getCamera()); if (mSkyBlending) { int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::SkyTexture); Log(Debug::Info) << "Reserving texture unit for sky RTT: " << skyTextureUnit; mPerViewUniformStateUpdater->enableSkyRTT(skyTextureUnit, mSky->getSkyRTT()); } source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; if (!Settings::Manager::getBool("small feature culling", "Camera")) cullingMode &= ~(osg::CullStack::SMALL_FEATURE_CULLING); else { mViewer->getCamera()->setSmallFeatureCullingPixelSize(Settings::Manager::getFloat("small feature culling pixel size", "Camera")); cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING; } mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); mViewer->getCamera()->setName(Constants::SceneCamera); auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater); MWBase::Environment::get().getWindowManager()->setCullMask(mask); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); mStateUpdater->setFogEnd(mViewDistance); // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); if (reverseZ) { osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::ZERO_TO_ONE); mRootNode->getOrCreateStateSet()->setAttributeAndModes(new SceneUtil::AutoDepth, osg::StateAttribute::ON); mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); } SceneUtil::setCameraClearDepth(mViewer->getCamera()); updateProjectionMatrix(); mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } RenderingManager::~RenderingManager() { // let background loading thread finish before we delete anything else mWorkQueue = nullptr; } osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation() { return mViewer->getIncrementalCompileOperation(); } MWRender::Objects& RenderingManager::getObjects() { return *mObjects.get(); } Resource::ResourceSystem* RenderingManager::getResourceSystem() { return mResourceSystem; } SceneUtil::WorkQueue* RenderingManager::getWorkQueue() { return mWorkQueue.get(); } Terrain::World* RenderingManager::getTerrain() { return mTerrain.get(); } void RenderingManager::preloadCommonAssets() { osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models")); workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models")); workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models")); workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models")); workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models")); workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models")); workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models")); workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models")); workItem->mTextures.emplace_back("textures/_land_default.dds"); mWorkQueue->addWorkItem(std::move(workItem)); } double RenderingManager::getReferenceTime() const { return mViewer->getFrameStamp()->getReferenceTime(); } SceneUtil::LightManager* RenderingManager::getLightRoot() { return mSceneRoot.get(); } void RenderingManager::setNightEyeFactor(float factor) { if (factor != mNightEyeFactor) { mNightEyeFactor = factor; updateAmbient(); } } void RenderingManager::setAmbientColour(const osg::Vec4f &colour) { mAmbientColor = colour; updateAmbient(); } void RenderingManager::skySetDate(int day, int month) { mSky->setDate(day, month); } int RenderingManager::skyGetMasserPhase() const { return mSky->getMasserPhase(); } int RenderingManager::skyGetSecundaPhase() const { return mSky->getSecundaPhase(); } void RenderingManager::skySetMoonColour(bool red) { mSky->setMoonColour(red); } void RenderingManager::configureAmbient(const ESM::Cell *cell) { bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); bool needsAdjusting = false; if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) needsAdjusting = isInterior; auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); if (needsAdjusting) { constexpr float pR = 0.2126; constexpr float pG = 0.7152; constexpr float pB = 0.0722; // we already work in linear RGB so no conversions are needed for the luminosity function float relativeLuminance = pR*ambient.r() + pG*ambient.g() + pB*ambient.b(); if (relativeLuminance < mMinimumAmbientLuminance) { // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); else ambient *= mMinimumAmbientLuminance / relativeLuminance; } } setAmbientColour(ambient); osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(diffuse); mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis) { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(specular); mPostProcessor->getStateUpdater()->setSunColor(diffuse); mPostProcessor->getStateUpdater()->setSunVis(sunVis); } void RenderingManager::setSunDirection(const osg::Vec3f &direction) { osg::Vec3 position = direction * -1; // need to wrap this in a StateUpdater? mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSky->setSunDirection(position); mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); } void RenderingManager::addCell(const MWWorld::CellStore *store) { mPathgrid->addCell(store); mWater->changeCell(store); if (store->getCell()->isExterior()) { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } } void RenderingManager::removeCell(const MWWorld::CellStore *store) { mPathgrid->removeCell(store); mActorsPaths->removeCell(store); mObjects->removeCell(store); if (store->getCell()->isExterior()) { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } mWater->removeCell(store); } void RenderingManager::enableTerrain(bool enable) { if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); if (enabled) mShadowManager->enableOutdoorMode(); else mShadowManager->enableIndoorMode(); mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } bool RenderingManager::toggleBorders() { bool borders = !mTerrain->getBordersVisible(); mTerrain->setBordersVisible(borders); return borders; } bool RenderingManager::toggleRenderMode(RenderMode mode) { if (mode == Render_CollisionDebug || mode == Render_Pathgrid) return mPathgrid->toggleRenderMode(mode); else if (mode == Render_Wireframe) { bool wireframe = !mStateUpdater->getWireframe(); mStateUpdater->setWireframe(wireframe); return wireframe; } else if (mode == Render_Water) { return mWater->toggle(); } else if (mode == Render_Scene) { const auto wm = MWBase::Environment::get().getWindowManager(); unsigned int mask = wm->getCullMask(); bool enabled = !(mask&sToggleWorldMask); if (enabled) mask |= sToggleWorldMask; else mask &= ~sToggleWorldMask; mWater->showWorld(enabled); wm->setCullMask(mask); return enabled; } else if (mode == Render_NavMesh) { return mNavMesh->toggle(); } else if (mode == Render_ActorsPaths) { return mActorsPaths->toggle(); } else if (mode == Render_RecastMesh) { return mRecastMesh->toggle(); } return false; } void RenderingManager::configureFog(const ESM::Cell *cell) { mFog->configure(mViewDistance, cell); } void RenderingManager::configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) { mFog->configure(mViewDistance, fogDepth, underwaterFog, dlFactor, dlOffset, color); } SkyManager* RenderingManager::getSkyManager() { return mSky.get(); } void RenderingManager::update(float dt, bool paused) { reportStats(); mResourceSystem->getSceneManager()->getShaderManager().update(*mViewer); float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); if (!paused) { mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); float windSpeed = mSky->getBaseWindSpeed(); mSharedUniformStateUpdater->setWindSpeed(windSpeed); mSharedUniformStateUpdater->setPlayerPos(playerPos); } updateNavMesh(); updateRecastMesh(); if (mUpdateProjectionMatrix) { mUpdateProjectionMatrix = false; updateProjectionMatrix(); } mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); float fogStart = mFog->getFogStart(isUnderwater); float fogEnd = mFog->getFogEnd(isUnderwater); osg::Vec4f fogColor = mFog->getFogColor(isUnderwater); mStateUpdater->setFogStart(fogStart); mStateUpdater->setFogEnd(fogEnd); setFogColor(fogColor); auto world = MWBase::Environment::get().getWorld(); const auto& stateUpdater = mPostProcessor->getStateUpdater(); stateUpdater->setFogRange(fogStart, fogEnd); stateUpdater->setNearFar(mNearClip, mViewDistance); stateUpdater->setIsUnderwater(isUnderwater); stateUpdater->setFogColor(fogColor); stateUpdater->setGameHour(world->getTimeStamp().getHour()); stateUpdater->setWeatherId(world->getCurrentWeather()); stateUpdater->setNextWeatherId(world->getNextWeather()); stateUpdater->setWeatherTransition(world->getWeatherTransition()); stateUpdater->setWindSpeed(world->getWindSpeed()); mPostProcessor->setUnderwaterFlag(isUnderwater); } void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) { if(mPlayerAnimation.get()) { setupPlayer(ptr); mPlayerAnimation->updatePtr(ptr); } mCamera->attachTo(ptr); } void RenderingManager::removePlayer(const MWWorld::Ptr &player) { mWater->removeEmitter(player); } void RenderingManager::rotateObject(const MWWorld::Ptr &ptr, const osg::Quat& rot) { if(ptr == mCamera->getTrackingPtr() && !mCamera->isVanityOrPreviewModeEnabled()) { mCamera->rotateCameraToTrackingPtr(); } ptr.getRefData().getBaseNode()->setAttitude(rot); } void RenderingManager::moveObject(const MWWorld::Ptr &ptr, const osg::Vec3f &pos) { ptr.getRefData().getBaseNode()->setPosition(pos); } void RenderingManager::scaleObject(const MWWorld::Ptr &ptr, const osg::Vec3f &scale) { ptr.getRefData().getBaseNode()->setScale(scale); if (ptr == mCamera->getTrackingPtr()) // update height of camera mCamera->processViewChange(); } void RenderingManager::removeObject(const MWWorld::Ptr &ptr) { mActorsPaths->remove(ptr); mObjects->removeObject(ptr); mWater->removeEmitter(ptr); } void RenderingManager::setWaterEnabled(bool enabled) { mWater->setEnabled(enabled); mSky->setWaterEnabled(enabled); mPostProcessor->getStateUpdater()->setIsWaterEnabled(enabled); } void RenderingManager::setWaterHeight(float height) { mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->setWaterHeight(height); mPostProcessor->getStateUpdater()->setWaterHeight(height); } void RenderingManager::screenshot(osg::Image* image, int w, int h) { mScreenshotManager->screenshot(image, w, h); } bool RenderingManager::screenshot360(osg::Image* image) { if (mCamera->isVanityOrPreviewModeEnabled()) { Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode."; return false; } mScreenshotManager->screenshot360(image); return true; } osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { if (!worldbb.valid()) return osg::Vec4f(); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; for (int i=0; i<8; ++i) { osg::Vec3f corner = worldbb.corner(i); corner = corner * viewProj; float x = (corner.x() + 1.f) * 0.5f; float y = (corner.y() - 1.f) * (-0.5f); if (x < min_x) min_x = x; if (x > max_x) max_x = x; if (y < min_y) min_y = y; if (y > max_y) max_y = y; } return osg::Vec4f(min_x, min_y, max_x, max_y); } RenderingManager::RayResult getIntersectionResult (osgUtil::LineSegmentIntersector* intersector) { RenderingManager::RayResult result; result.mHit = false; result.mHitRefnum.unset(); result.mRatio = 0; if (intersector->containsIntersections()) { result.mHit = true; osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); result.mHitPointWorld = intersection.getWorldIntersectPoint(); result.mHitNormalWorld = intersection.getWorldIntersectNormal(); result.mRatio = intersection.ratio; PtrHolder* ptrHolder = nullptr; std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) { osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); if (!userDataContainer) continue; for (unsigned int i=0; igetNumUserObjects(); ++i) { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) ptrHolder = p; if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) refnumMarkers.push_back(r); } } if (ptrHolder) result.mHitObject = ptrHolder->mPtr; unsigned int vertexCounter = 0; for (unsigned int i=0; imNumVertices || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) { result.mHitRefnum = refnumMarkers[i]->mRefnum; break; } vertexCounter += refnumMarkers[i]->mNumVertices; } } return result; } osg::ref_ptr RenderingManager::getIntersectionVisitor(osgUtil::Intersector *intersector, bool ignorePlayer, bool ignoreActors) { if (!mIntersectionVisitor) mIntersectionVisitor = new osgUtil::IntersectionVisitor; mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); mIntersectionVisitor->setIntersector(intersector); unsigned int mask = ~0u; mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) mask &= ~(Mask_Actor|Mask_Player); mIntersectionVisitor->setTraversalMask(mask); return mIntersectionVisitor; } RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) { osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } RenderingManager::RayResult RenderingManager::castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors) { osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::PROJECTION, nX * 2.f - 1.f, nY * (-2.f) + 1.f)); osg::Vec3d dist (0.f, 0.f, -maxDistance); dist = dist * mViewer->getCamera()->getProjectionMatrix(); osg::Vec3d end = intersector->getEnd(); end.z() = dist.z(); intersector->setEnd(end); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { mObjects->updatePtr(old, updated); mActorsPaths->updatePtr(old, updated); } void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); } void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); mWater->clearRipples(); } void RenderingManager::clear() { mSky->setMoonColour(false); notifyWorldSpaceChanged(); if (mObjectPaging) mObjectPaging->clear(); } MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) return mPlayerAnimation.get(); return mObjects->getAnimation(ptr); } const MWRender::Animation* RenderingManager::getAnimation(const MWWorld::ConstPtr &ptr) const { if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) return mPlayerAnimation.get(); return mObjects->getAnimation(ptr); } PostProcessor* RenderingManager::getPostProcessor() { return mPostProcessor; } void RenderingManager::setupPlayer(const MWWorld::Ptr &player) { if (!mPlayerNode) { mPlayerNode = new SceneUtil::PositionAttitudeTransform; mPlayerNode->setNodeMask(Mask_Player); mPlayerNode->setName("Player Root"); mSceneRoot->addChild(mPlayerNode); } mPlayerNode->setUserDataContainer(new osg::DefaultUserDataContainer); mPlayerNode->getUserDataContainer()->addUserObject(new PtrHolder(player)); player.getRefData().setBaseNode(mPlayerNode); mWater->removeEmitter(player); mWater->addEmitter(player); } void RenderingManager::renderPlayer(const MWWorld::Ptr &player) { mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal, mFirstPersonFieldOfView); mCamera->setAnimation(mPlayerAnimation.get()); mCamera->attachTo(player); } void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) { NpcAnimation *anim = nullptr; if(ptr == mPlayerAnimation->getPtr()) anim = mPlayerAnimation.get(); else anim = dynamic_cast(mObjects->getAnimation(ptr)); if(anim) { anim->rebuild(); if(mCamera->getTrackingPtr() == ptr) { mCamera->attachTo(ptr); mCamera->setAnimation(anim); } } } void RenderingManager::addWaterRippleEmitter(const MWWorld::Ptr &ptr) { mWater->addEmitter(ptr); } void RenderingManager::removeWaterRippleEmitter(const MWWorld::Ptr &ptr) { mWater->removeEmitter(ptr); } void RenderingManager::emitWaterRipple(const osg::Vec3f &pos) { mWater->emitRipple(pos); } void RenderingManager::updateProjectionMatrix() { if (mNearClip < 0.0f) throw std::runtime_error("Near clip is less than zero"); if (mViewDistance < mNearClip) throw std::runtime_error("Viewing distance is less than near clip"); double width = Settings::Manager::getInt("resolution x", "Video"); double height = Settings::Manager::getInt("resolution y", "Video"); double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); if (SceneUtil::AutoDepth::isReversed()) { mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); mPerViewUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); } else mPerViewUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); if (Stereo::getStereo()) { auto res = Stereo::Manager::instance().eyeResolution(); mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); } else if (!mPostProcessor->isEnabled()) { mSharedUniformStateUpdater->setScreenRes(width, height); } // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); if (mPostProcessor) { mPostProcessor->getStateUpdater()->setProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); mPostProcessor->getStateUpdater()->setFov(fov); } } void RenderingManager::setScreenRes(int width, int height) { mSharedUniformStateUpdater->setScreenRes(width, height); } void RenderingManager::updateTextureFiltering() { mViewer->stopThreading(); mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General") ); mTerrain->updateTextureFiltering(); mWater->processChangedSettings({}); mViewer->startThreading(); } void RenderingManager::updateAmbient() { osg::Vec4f color = mAmbientColor; if (mNightEyeFactor > 0.f) color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; mStateUpdater->setAmbientColor(color); } void RenderingManager::setFogColor(const osg::Vec4f &color) { mViewer->getCamera()->setClearColor(color); mStateUpdater->setFogColor(color); } void RenderingManager::reportStats() const { osg::Stats* stats = mViewer->getViewerStats(); unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (stats->collectStats("resource")) { mTerrain->reportStats(frameNumber, stats); } } void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) { // Only perform a projection matrix update once if a relevant setting is changed. bool updateProjection = false; for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { if (it->first == "Camera" && it->second == "field of view") { mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); updateProjection = true; } else if (it->first == "Video" && (it->second == "resolution x" || it->second == "resolution y")) { updateProjection = true; } else if (it->first == "Camera" && it->second == "viewing distance") { setViewDistance(Settings::Manager::getFloat("viewing distance", "Camera")); } else if (it->first == "General" && (it->second == "texture filter" || it->second == "texture mipmap" || it->second == "anisotropy")) { updateTextureFiltering(); } else if (it->first == "Water") { mWater->processChangedSettings(changed); } else if (it->first == "Shaders" && it->second == "minimum interior brightness") { mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); if (MWMechanics::getPlayer().isInCell()) configureAmbient(MWMechanics::getPlayer().getCell()->getCell()); } else if (it->first == "Shaders" && (it->second == "light bounds multiplier" || it->second == "maximum light distance" || it->second == "light fade start" || it->second == "max lights")) { auto* lightManager = getLightRoot(); lightManager->processChangedSettings(changed); if (it->second == "max lights" && !lightManager->usingFFP()) { mViewer->stopThreading(); lightManager->updateMaxLights(); auto defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); for (const auto& [name, key] : lightManager->getLightDefines()) defines[name] = key; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mStateUpdater->reset(); mViewer->startThreading(); } } else if (it->first == "Post Processing" && it->second == "enabled") { if (Settings::Manager::getBool("enabled", "Post Processing")) mPostProcessor->enable(); else { mPostProcessor->disable(); if (auto* hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) hud->setVisible(false); } } } if (updateProjection) { updateProjectionMatrix(); } } void RenderingManager::setViewDistance(float distance, bool delay) { mViewDistance = distance; if (delay) { mUpdateProjectionMatrix = true; return; } updateProjectionMatrix(); } float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos) { return mTerrain->getHeightAt(pos); } void RenderingManager::overrideFieldOfView(float val) { if (mFieldOfViewOverridden != true || mFieldOfViewOverride != val) { mFieldOfViewOverridden = true; mFieldOfViewOverride = val; updateProjectionMatrix(); } } void RenderingManager::setFieldOfView(float val) { mFieldOfView = val; mUpdateProjectionMatrix = true; } float RenderingManager::getFieldOfView() const { return mFieldOfViewOverridden ? mFieldOfViewOverridden : mFieldOfView; } osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const { osg::Vec3f halfExtents(0, 0, 0); std::string modelName = object.getClass().getModel(object); if (modelName.empty()) return halfExtents; osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(modelName); osg::ComputeBoundsVisitor computeBoundsVisitor; computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); const_cast(node.get())->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); if (bounds.valid()) { halfExtents[0] = std::abs(bounds.xMax() - bounds.xMin()) / 2.f; halfExtents[1] = std::abs(bounds.yMax() - bounds.yMin()) / 2.f; halfExtents[2] = std::abs(bounds.zMax() - bounds.zMin()) / 2.f; } return halfExtents; } void RenderingManager::resetFieldOfView() { if (mFieldOfViewOverridden == true) { mFieldOfViewOverridden = false; updateProjectionMatrix(); } } void RenderingManager::exportSceneGraph(const MWWorld::Ptr &ptr, const std::string &filename, const std::string &format) { osg::Node* node = mViewer->getSceneData(); if (!ptr.isEmpty()) node = ptr.getRefData().getBaseNode(); SceneUtil::writeScene(node, filename, format); } LandManager *RenderingManager::getLandManager() const { return mTerrainStorage->getLandManager(); } void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const { mActorsPaths->update(actor, path, agentBounds, start, end, mNavigator.getSettings()); } void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const { mActorsPaths->remove(actor); } void RenderingManager::setNavMeshNumber(const std::size_t value) { mNavMeshNumber = value; } void RenderingManager::updateNavMesh() { if (!mNavMesh->isEnabled()) return; const auto navMeshes = mNavigator.getNavMeshes(); auto it = navMeshes.begin(); for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i) ++it; if (it == navMeshes.end()) { mNavMesh->reset(); } else { try { mNavMesh->update(it->second, mNavMeshNumber, mNavigator.getSettings()); } catch (const std::exception& e) { Log(Debug::Error) << "NavMesh render update exception: " << e.what(); } } } void RenderingManager::updateRecastMesh() { if (!mRecastMesh->isEnabled()) return; mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); } void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) return false; if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()), enabled)) { mTerrain->rebuildViews(); return true; } return false; } void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr) { if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) return; const ESM::RefNum & refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile()) return; if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3(), osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()))) mTerrain->rebuildViews(); } bool RenderingManager::pagingUnlockCache() { if (mObjectPaging && mObjectPaging->unlockCache()) { mTerrain->rebuildViews(); return true; } return false; } void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out) { if (mObjectPaging) mObjectPaging->getPagedRefnums(activeGrid, out); } void RenderingManager::setNavMeshMode(NavMeshMode value) { mNavMesh->setMode(value); } } openmw-openmw-0.48.0/apps/openmw/mwrender/renderingmanager.hpp000066400000000000000000000241541445372753700245560ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H #include #include #include #include #include #include "objects.hpp" #include "navmeshmode.hpp" #include "renderinginterface.hpp" #include "rendermode.hpp" #include #include namespace osg { class Group; class PositionAttitudeTransform; } namespace osgUtil { class IntersectionVisitor; class Intersector; } namespace Resource { class ResourceSystem; } namespace osgViewer { class Viewer; } namespace ESM { struct Cell; struct RefNum; } namespace Terrain { class World; } namespace Fallback { class Map; } namespace SceneUtil { class ShadowManager; class WorkQueue; class LightManager; class UnrefQueue; } namespace DetourNavigator { struct Navigator; struct Settings; struct AgentBounds; } namespace MWWorld { class GroundcoverStore; } namespace MWRender { class StateUpdater; class SharedUniformStateUpdater; class PerViewUniformStateUpdater; class EffectManager; class ScreenshotManager; class FogManager; class SkyManager; class NpcAnimation; class Pathgrid; class Camera; class Water; class TerrainStorage; class LandManager; class NavMesh; class ActorsPaths; class RecastMesh; class ObjectPaging; class Groundcover; class PostProcessor; class RenderingManager : public MWRender::RenderingInterface { public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore, SceneUtil::UnrefQueue& unrefQueue); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); MWRender::Objects& getObjects() override; Resource::ResourceSystem* getResourceSystem(); SceneUtil::WorkQueue* getWorkQueue(); Terrain::World* getTerrain(); void preloadCommonAssets(); double getReferenceTime() const; SceneUtil::LightManager* getLightRoot(); void setNightEyeFactor(float factor); void setAmbientColour(const osg::Vec4f& colour); void skySetDate(int day, int month); int skyGetMasserPhase() const; int skyGetSecundaPhase() const; void skySetMoonColour(bool red); void setSunDirection(const osg::Vec3f& direction); void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis); void setNight(bool isNight) { mNight = isNight; } void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); void configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& colour); void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); void enableTerrain(bool enable); void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); void rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot); void moveObject(const MWWorld::Ptr& ptr, const osg::Vec3f& pos); void scaleObject(const MWWorld::Ptr& ptr, const osg::Vec3f& scale); void removeObject(const MWWorld::Ptr& ptr); void setWaterEnabled(bool enabled); void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h); bool screenshot360(osg::Image* image); struct RayResult { bool mHit; osg::Vec3f mHitNormalWorld; osg::Vec3f mHitPointWorld; MWWorld::Ptr mHitObject; ESM::RefNum mHitRefnum; float mRatio; }; RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors=false); /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen coordinates, /// where (0,0) is the top left corner. RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. osg::Vec4f getScreenBounds(const osg::BoundingBox &worldbb); void setSkyEnabled(bool enabled); bool toggleRenderMode(RenderMode mode); SkyManager* getSkyManager(); void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f, bool isMagicVFX = true); /// Clear all savegame-specific data void clear(); /// Clear all worldspace-specific data void notifyWorldSpaceChanged(); void update(float dt, bool paused); Animation* getAnimation(const MWWorld::Ptr& ptr); const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; PostProcessor* getPostProcessor(); void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); void emitWaterRipple(const osg::Vec3f& pos); void updatePlayerPtr(const MWWorld::Ptr &ptr); void removePlayer(const MWWorld::Ptr& player); void setupPlayer(const MWWorld::Ptr& player); void renderPlayer(const MWWorld::Ptr& player); void rebuildPtr(const MWWorld::Ptr& ptr); void processChangedSettings(const Settings::CategorySettingVector& settings); float getNearClipDistance() const { return mNearClip; } float getViewDistance() const { return mViewDistance; } void setViewDistance(float distance, bool delay = false); float getTerrainHeightAt(const osg::Vec3f& pos); // camera stuff Camera* getCamera() { return mCamera.get(); } /// temporarily override the field of view with given value. void overrideFieldOfView(float val); void setFieldOfView(float val); float getFieldOfView() const; /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const; void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format); LandManager* getLandManager() const; bool toggleBorders(); void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const; void removeActorPath(const MWWorld::ConstPtr& actor) const; void setNavMeshNumber(const std::size_t value); void setActiveGrid(const osg::Vec4i &grid); bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); bool pagingUnlockCache(); void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out); void updateProjectionMatrix(); void setScreenRes(int width, int height); void setNavMeshMode(NavMeshMode value); private: void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); void updateThirdPersonViewMode(); void reportStats() const; void updateNavMesh(); void updateRecastMesh(); const bool mSkyBlending; osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; osg::ref_ptr mSunLight; DetourNavigator::Navigator& mNavigator; std::unique_ptr mNavMesh; std::size_t mNavMeshNumber = 0; std::unique_ptr mActorsPaths; std::unique_ptr mRecastMesh; std::unique_ptr mPathgrid; std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; std::unique_ptr mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; osg::ref_ptr mPostProcessor; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; osg::ref_ptr mPerViewUniformStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; float mNightEyeFactor; float mNearClip; float mViewDistance; bool mFieldOfViewOverridden; float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; bool mUpdateProjectionMatrix = false; bool mNight = false; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/rendermode.hpp000066400000000000000000000005321445372753700233640ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RENDERMODE_H #define OPENMW_MWRENDER_RENDERMODE_H namespace MWRender { enum RenderMode { Render_CollisionDebug, Render_Wireframe, Render_Pathgrid, Render_Water, Render_Scene, Render_NavMesh, Render_ActorsPaths, Render_RecastMesh, }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/ripplesimulation.cpp000066400000000000000000000176211445372753700246420ustar00rootroot00000000000000#include "ripplesimulation.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vismask.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwmechanics/actorutil.hpp" namespace { void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem,osg::Node* node) { int rippleFrameCount = Fallback::Map::getInt("Water_RippleFrameCount"); if (rippleFrameCount <= 0) return; const std::string& tex = Fallback::Map::getString("Water_RippleTexture"); std::vector > textures; for (int i=0; i tex2 (new osg::Texture2D(resourceSystem->getImageManager()->getImage(texname.str()))); tex2->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex2->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); resourceSystem->getSceneManager()->applyFilterSettings(tex2); textures.push_back(tex2); } osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); controller->setSource(std::make_shared()); node->addUpdateCallback(controller); osg::ref_ptr stateset (new osg::StateSet); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); osg::ref_ptr polygonOffset (new osg::PolygonOffset); polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1 : -1); polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 1 : -1); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); osg::ref_ptr mat (new osg::Material); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); mat->setColorMode(osg::Material::DIFFUSE); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); node->setStateSet(stateset); } } namespace MWRender { RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) { mParticleSystem = new osgParticle::ParticleSystem; mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); mParticleSystem->setAlignVectorX(osg::Vec3f(1,0,0)); mParticleSystem->setAlignVectorY(osg::Vec3f(0,1,0)); osgParticle::Particle& particleTemplate = mParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(15, 180)); particleTemplate.setColorRange(osgParticle::rangev4(osg::Vec4f(1,1,1,0.7), osg::Vec4f(1,1,1,0.7))); particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 0.f)); particleTemplate.setAngularVelocity(osg::Vec3f(0,0,Fallback::Map::getFloat("Water_RippleRotSpeed"))); particleTemplate.setLifeTime(Fallback::Map::getFloat("Water_RippleLifetime")); osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); updater->addParticleSystem(mParticleSystem); mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->setName("Ripple Root"); mParticleNode->addChild(updater); mParticleNode->addChild(mParticleSystem); mParticleNode->setNodeMask(Mask_Water); createWaterRippleStateSet(resourceSystem, mParticleNode); resourceSystem->getSceneManager()->recreateShaders(mParticleNode); mParent->addChild(mParticleNode); } RippleSimulation::~RippleSimulation() { mParent->removeChild(mParticleNode); } void RippleSimulation::update(float dt) { const MWBase::World* world = MWBase::Environment::get().getWorld(); for (Emitter& emitter : mEmitters) { MWWorld::ConstPtr& ptr = emitter.mPtr; if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) { // fetch a new ptr (to handle cell change etc) // for non-player actors this is done in updateObjectCell ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } osg::Vec3f currentPos (ptr.getRefData().getPosition().asVec3()); bool shouldEmit = (world->isUnderwater(ptr.getCell(), currentPos) && !world->isSubmerged(ptr)) || world->isWalkingOnWater(ptr); if (shouldEmit && (currentPos - emitter.mLastEmitPosition).length() > 10) { emitter.mLastEmitPosition = currentPos; currentPos.z() = mParticleNode->getPosition().z(); if (mParticleSystem->numParticles()-mParticleSystem->numDeadParticles() > 500) continue; // TODO: remove the oldest particle to make room? emitRipple(currentPos); } } } void RippleSimulation::addEmitter(const MWWorld::ConstPtr& ptr, float scale, float force) { Emitter newEmitter; newEmitter.mPtr = ptr; newEmitter.mScale = scale; newEmitter.mForce = force; newEmitter.mLastEmitPosition = osg::Vec3f(0,0,0); mEmitters.push_back (newEmitter); } void RippleSimulation::removeEmitter (const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { if (it->mPtr == ptr) { mEmitters.erase(it); return; } } } void RippleSimulation::updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { if (it->mPtr == old) { it->mPtr = ptr; return; } } } void RippleSimulation::removeCell(const MWWorld::CellStore *store) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) { if ((it->mPtr.isInCell() && it->mPtr.getCell() == store) && it->mPtr != MWMechanics::getPlayer()) { it = mEmitters.erase(it); } else ++it; } } void RippleSimulation::emitRipple(const osg::Vec3f &pos) { if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) { osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); } } void RippleSimulation::setWaterHeight(float height) { mParticleNode->setPosition(osg::Vec3f(0,0,height)); } void RippleSimulation::clear() { for (int i=0; inumParticles(); ++i) mParticleSystem->destroyParticle(i); } } openmw-openmw-0.48.0/apps/openmw/mwrender/ripplesimulation.hpp000066400000000000000000000031351445372753700246420ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_RIPPLESIMULATION_H #define OPENMW_MWRENDER_RIPPLESIMULATION_H #include #include "../mwworld/ptr.hpp" namespace osg { class Group; class PositionAttitudeTransform; } namespace osgParticle { class ParticleSystem; } namespace Resource { class ResourceSystem; } namespace Fallback { class Map; } namespace MWRender { struct Emitter { MWWorld::ConstPtr mPtr; osg::Vec3f mLastEmitPosition; float mScale; float mForce; }; class RippleSimulation { public: RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem); ~RippleSimulation(); /// @param dt Time since the last frame void update(float dt); /// adds an emitter, position will be tracked automatically void addEmitter (const MWWorld::ConstPtr& ptr, float scale = 1.f, float force = 1.f); void removeEmitter (const MWWorld::ConstPtr& ptr); void updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr); void removeCell(const MWWorld::CellStore* store); void emitRipple(const osg::Vec3f& pos); /// Change the height of the water surface, thus moving all ripples with it void setWaterHeight(float height); /// Remove all active ripples void clear(); private: osg::ref_ptr mParent; osg::ref_ptr mParticleSystem; osg::ref_ptr mParticleNode; std::vector mEmitters; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/rotatecontroller.cpp000066400000000000000000000026671445372753700246500ustar00rootroot00000000000000#include "rotatecontroller.hpp" #include namespace MWRender { RotateController::RotateController(osg::Node *relativeTo) : mEnabled(true) , mRelativeTo(relativeTo) { } void RotateController::setEnabled(bool enabled) { mEnabled = enabled; } void RotateController::setRotate(const osg::Quat &rotate) { mRotate = rotate; } void RotateController::setOffset(const osg::Vec3f& offset) { mOffset = offset; } void RotateController::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { if (!mEnabled) { traverse(node, nv); return; } osg::Matrix matrix = node->getMatrix(); osg::Quat worldOrient = getWorldOrientation(node); osg::Quat worldOrientInverse = worldOrient.inverse(); osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); node->setMatrix(matrix); traverse(node,nv); } osg::Quat RotateController::getWorldOrientation(osg::Node *node) { // this could be optimized later, we just need the world orientation, not the full matrix osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); osg::Quat worldOrient; if (!nodepaths.empty()) { osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldOrient = worldMat.getRotate(); } return worldOrient; } } openmw-openmw-0.48.0/apps/openmw/mwrender/rotatecontroller.hpp000066400000000000000000000017241445372753700246460ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H #define OPENMW_MWRENDER_ROTATECONTROLLER_H #include #include namespace osg { class MatrixTransform; } namespace MWRender { /// Applies a rotation in \a relativeTo's space. /// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. /// The rotation is then applied on top of that orientation. class RotateController : public SceneUtil::NodeCallback { public: RotateController(osg::Node* relativeTo); void setEnabled(bool enabled); void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); protected: osg::Quat getWorldOrientation(osg::Node* node); bool mEnabled; osg::Vec3f mOffset; osg::Quat mRotate; osg::Node* mRelativeTo; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/screenshotmanager.cpp000066400000000000000000000342721445372753700247530ustar00rootroot00000000000000#include "screenshotmanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "util.hpp" #include "vismask.hpp" #include "water.hpp" #include "postprocessor.hpp" namespace MWRender { enum Screenshot360Type { Spherical, Cylindrical, Planet, RawCubemap }; class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback { public: NotifyDrawCompletedCallback() : mDone(false), mFrame(0) { } void operator () (osg::RenderInfo& renderInfo) const override { std::lock_guard lock(mMutex); if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame && !mDone) { mDone = true; mCondition.notify_one(); } } void waitTillDone() { std::unique_lock lock(mMutex); if (mDone) return; mCondition.wait(lock); } void reset(unsigned int frame) { std::lock_guard lock(mMutex); mDone = false; mFrame = frame; } mutable std::condition_variable mCondition; mutable std::mutex mMutex; mutable bool mDone; unsigned int mFrame; }; class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback { public: ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) : mWidth(width), mHeight(height), mImage(image) { } void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override { int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); if (Stereo::getStereo()) { auto eyeRes = Stereo::Manager::instance().eyeResolution(); screenW = eyeRes.x(); screenH = eyeRes.y(); } double imageaspect = (double)mWidth/(double)mHeight; int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); int width = screenW - leftPadding*2; int height = screenH - topPadding*2; // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure that the readbuffer is set correctly with rendeirng to FBO. // glReadPixel() cannot read from multisampled targets PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); if (ext) { size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2; osg::FrameBufferObject* fbo = nullptr; if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId); if (fbo) fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER); } mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); mImage->scaleImage(mWidth, mHeight, 1); } private: int mWidth; int mHeight; osg::ref_ptr mImage; }; ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) : mViewer(viewer) , mRootNode(rootNode) , mSceneRoot(sceneRoot) , mDrawCompleteCallback(new NotifyDrawCompletedCallback) , mResourceSystem(resourceSystem) , mWater(water) { } ScreenshotManager::~ScreenshotManager() { } void ScreenshotManager::screenshot(osg::Image* image, int w, int h) { osg::Camera* camera = mViewer->getCamera(); osg::ref_ptr tempDrw = new osg::Drawable; tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); tempDrw->setCullingActive(false); tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera camera->addChild(tempDrw); traversalsAndWait(mViewer->getFrameStamp()->getFrameNumber()); // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); camera->removeChild(tempDrw); } bool ScreenshotManager::screenshot360(osg::Image* image) { int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); Screenshot360Type screenshotMapping = Spherical; const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); std::vector settingArgs; Misc::StringUtils::split(settingStr, settingArgs); if (settingArgs.size() > 0) { std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"}; bool found = false; for (int i = 0; i < 4; ++i) { if (settingArgs[0].compare(typeStrings[i]) == 0) { screenshotMapping = static_cast(i); found = true; break; } } if (!found) { Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; return false; } } // planet mapping needs higher resolution int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; if (settingArgs.size() > 1) screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str())); if (settingArgs.size() > 2) screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str())); if (settingArgs.size() > 3) cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str())); bool rawCubemap = screenshotMapping == RawCubemap; if (rawCubemap) screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row else if (screenshotMapping == Planet) screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; images.reserve(6); for (int i = 0; i < 6; ++i) images.push_back(new osg::Image); osg::Vec3 directions[6] = { rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), osg::Vec3(0,0,-1), osg::Vec3(-1,0,0), rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), osg::Vec3(0,1,0), osg::Vec3(0,-1,0)}; double rotations[] = { -osg::PI / 2.0, osg::PI / 2.0, osg::PI, 0, osg::PI / 2.0, osg::PI / 2.0 }; for (int i = 0; i < 6; ++i) // for each cubemap side { osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]); if (!rawCubemap) transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); osg::Image *sideImage = images[i].get(); makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform); if (!rawCubemap) sideImage->flipHorizontal(); } if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images { image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); for (int i = 0; i < 6; ++i) osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); return true; } // run on GPU now: osg::ref_ptr cubeTexture (new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); for (int i = 0; i < 6; ++i) cubeTexture->setImage(i, images[i].get()); osg::ref_ptr screenshotCamera(new osg::Camera); osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); std::map defineMap; Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); osg::ref_ptr stateset = quad->getOrCreateStateSet(); osg::ref_ptr program(new osg::Program); program->addShader(fragmentShader); program->addShader(vertexShader); stateset->setAttributeAndModes(program, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); screenshotCamera->addChild(quad); renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); return true; } void ScreenshotManager::traversalsAndWait(unsigned int frame) { // Ref https://gitlab.com/OpenMW/openmw/-/issues/6013 mDrawCompleteCallback->reset(frame); mViewer->getCamera()->setFinalDrawCallback(mDrawCompleteCallback); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); mDrawCompleteCallback->waitTillDone(); } void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) { camera->setNodeMask(Mask_RenderToTexture); camera->attach(osg::Camera::COLOR_BUFFER, image); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); camera->setViewport(0, 0, w, h); SceneUtil::setCameraClearDepth(camera); osg::ref_ptr texture (new osg::Texture2D); texture->setInternalFormat(GL_RGB); texture->setTextureSize(w,h); texture->setResizeNonPowerOfTwoHint(false); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); camera->attach(osg::Camera::COLOR_BUFFER,texture); image->setDataType(GL_UNSIGNED_BYTE); image->setPixelFormat(texture->getInternalFormat()); mRootNode->addChild(camera); MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); // The draw needs to complete before we can copy back our image. traversalsAndWait(0); MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); camera->removeChildren(0, camera->getNumChildren()); mRootNode->removeChild(camera); } void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, const osg::Matrixd& cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); float nearClip = Settings::Manager::getFloat("near clip", "Camera"); float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); // each cubemap side sees 90 degrees if (SceneUtil::AutoDepth::isReversed()) rttCamera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w/float(h), nearClip)); else rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); rttCamera->addChild(mWater->getReflectionNode()); rttCamera->addChild(mWater->getRefractionNode()); rttCamera->setCullMask(MWBase::Environment::get().getWindowManager()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); renderCameraToImage(rttCamera.get(),image,w,h); } } openmw-openmw-0.48.0/apps/openmw/mwrender/screenshotmanager.hpp000066400000000000000000000023251445372753700247520ustar00rootroot00000000000000#ifndef MWRENDER_SCREENSHOTMANAGER_H #define MWRENDER_SCREENSHOTMANAGER_H #include #include #include #include namespace Resource { class ResourceSystem; } namespace MWRender { class Water; class NotifyDrawCompletedCallback; class ScreenshotManager { public: ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); ~ScreenshotManager(); void screenshot(osg::Image* image, int w, int h); bool screenshot360(osg::Image* image); private: osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; osg::ref_ptr mDrawCompleteCallback; Resource::ResourceSystem* mResourceSystem; Water* mWater; void traversalsAndWait(unsigned int frame); void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); void makeCubemapScreenshot(osg::Image* image, int w, int h, const osg::Matrixd &cameraTransform=osg::Matrixd()); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/sky.cpp000066400000000000000000001020731445372753700220440ustar00rootroot00000000000000#include "sky.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/weather.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "vismask.hpp" #include "renderbin.hpp" #include "util.hpp" #include "skyutil.hpp" namespace { class WrapAroundOperator : public osgParticle::Operator { public: WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange) : osgParticle::Operator() , mCamera(camera) , mWrapRange(wrapRange) , mHalfWrapRange(mWrapRange / 2.0) { mPreviousCameraPosition = getCameraPosition(); } osg::Object *cloneType() const override { return nullptr; } osg::Object *clone(const osg::CopyOp &op) const override { return nullptr; } void operate(osgParticle::Particle *P, double dt) override { } void operateParticles(osgParticle::ParticleSystem *ps, double dt) override { osg::Vec3 position = getCameraPosition(); osg::Vec3 positionDifference = position - mPreviousCameraPosition; osg::Matrix toWorld, toLocal; std::vector worldMatrices = ps->getWorldMatrices(); if (!worldMatrices.empty()) { toWorld = worldMatrices[0]; toLocal.invert(toWorld); } for (int i = 0; i < ps->numParticles(); ++i) { osgParticle::Particle *p = ps->getParticle(i); p->setPosition(toWorld.preMult(p->getPosition())); p->setPosition(p->getPosition() - positionDifference); for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions { osg::Vec3 pos = p->getPosition(); if (pos[j] < -mHalfWrapRange[j]) pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); else if (pos[j] > mHalfWrapRange[j]) pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; p->setPosition(pos); } p->setPosition(toLocal.preMult(p->getPosition())); } mPreviousCameraPosition = position; } protected: osg::Camera *mCamera; osg::Vec3 mPreviousCameraPosition; osg::Vec3 mWrapRange; osg::Vec3 mHalfWrapRange; osg::Vec3 getCameraPosition() { return mCamera->getInverseViewMatrix().getTrans(); } }; class WeatherAlphaOperator : public osgParticle::Operator { public: WeatherAlphaOperator(float& alpha, bool rain) : mAlpha(alpha) , mIsRain(rain) { } osg::Object *cloneType() const override { return nullptr; } osg::Object *clone(const osg::CopyOp &op) const override { return nullptr; } void operate(osgParticle::Particle *particle, double dt) override { constexpr float rainThreshold = 0.6f; // Rain_Threshold? float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); } private: float &mAlpha; bool mIsRain; }; // Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. class AlphaFader : public SceneUtil::StateSetUpdater { public: /// @param alpha the variable alpha value is recovered from AlphaFader(const float& alpha) : mAlpha(alpha) { } void setDefaults(osg::StateSet* stateset) override { // need to create a deep copy of StateAttributes we will modify osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, mAlpha)); } protected: const float &mAlpha; }; // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: SetupVisitor(const float &alpha) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAlpha(alpha) { } void apply(osg::Node &node) override { if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { SceneUtil::CompositeStateSetUpdater* composite = nullptr; osg::Callback* callback = node.getUpdateCallback(); while (callback) { composite = dynamic_cast(callback); if (composite) break; callback = callback->getNestedCallback(); } osg::ref_ptr alphaFader = new AlphaFader(mAlpha); if (composite) composite->addController(alphaFader); else node.addUpdateCallback(alphaFader); } } traverse(node); } private: const float &mAlpha; }; class SkyRTT : public SceneUtil::RTTNode { public: SkyRTT(osg::Vec2f size, osg::Group* earlyRenderBinRoot) : RTTNode(static_cast(size.x()), static_cast(size.y()), 0, false, 1, StereoAwareness::Aware), mEarlyRenderBinRoot(earlyRenderBinRoot) { setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setName("SkyCamera"); camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } private: osg::ref_ptr mEarlyRenderBinRoot; }; } namespace MWRender { SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT) : mSceneManager(sceneManager) , mCamera(nullptr) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) , mDay(0) , mMonth(0) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) , mStormParticleDirection(MWWorld::Weather::defaultDirection()) , mStormDirection(MWWorld::Weather::defaultDirection()) , mClouds() , mNextClouds() , mCloudBlendFactor(0.f) , mCloudSpeed(0.f) , mStarsOpacity(0.f) , mRemainingTransitionTime(0.f) , mRainEnabled(false) , mRainSpeed(0.f) , mRainDiameter(0.f) , mRainMinHeight(0.f) , mRainMaxHeight(0.f) , mRainEntranceSpeed(1.f) , mRainMaxRaindrops(0) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) , mSunglareEnabled(true) , mPrecipitationAlpha(0.f) , mDirtyParticlesEffect(false) { osg::ref_ptr skyroot = new CameraRelativeTransform; skyroot->setName("Sky Root"); // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; // render before the world is rendered mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); // Prevent unwanted clipping by water reflection camera's clipping plane mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); if (enableSkyRTT) { mSkyRTT = new SkyRTT(Settings::Manager::getVector2("sky rtt resolution", "Fog"), mEarlyRenderBinRoot); skyroot->addChild(mSkyRTT); mRootNode = new osg::Group; skyroot->addChild(mRootNode); } else mRootNode = skyroot; mRootNode->setNodeMask(Mask_Sky); mRootNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } void SkyManager::create() { assert(!mCreated); bool forceShaders = mSceneManager->getForceShaders(); mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); mAtmosphereDay->accept(modAtmosphere); mAtmosphereUpdater = new AtmosphereUpdater; mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); mAtmosphereNightNode = new osg::PositionAttitudeTransform; mAtmosphereNightNode->setNodeMask(0); mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); osg::ref_ptr atmosphereNight; if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); else atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); ModVertexAlphaVisitor modStars(ModVertexAlphaVisitor::Stars); atmosphereNight->accept(modStars); mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager(), forceShaders); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); mSun = std::make_unique(mEarlyRenderBinRoot, *mSceneManager); mSun->setSunglare(mSunglareEnabled); mMasser = std::make_unique(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser); mSecunda = std::make_unique(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda); mCloudNode = new osg::Group; mEarlyRenderBinRoot->addChild(mCloudNode); mCloudMesh = new osg::PositionAttitudeTransform; osg::ref_ptr cloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudMesh); mCloudUpdater = new CloudUpdater(forceShaders); mCloudUpdater->setOpacity(1.f); cloudMeshChild->addUpdateCallback(mCloudUpdater); mCloudMesh->addChild(cloudMeshChild); mNextCloudMesh = new osg::PositionAttitudeTransform; osg::ref_ptr nextCloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mNextCloudMesh); mNextCloudUpdater = new CloudUpdater(forceShaders); mNextCloudUpdater->setOpacity(0.f); nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); mNextCloudMesh->setNodeMask(0); mNextCloudMesh->addChild(nextCloudMeshChild); mCloudNode->addChild(mCloudMesh); mCloudNode->addChild(mNextCloudMesh); ModVertexAlphaVisitor modClouds(ModVertexAlphaVisitor::Clouds); mCloudMesh->accept(modClouds); mNextCloudMesh->accept(modClouds); if (mSceneManager->getForceShaders()) { Shader::ShaderManager::DefineMap defines = {}; Stereo::Manager::instance().shaderStereoDefines(defines); auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", defines, osg::Shader::VERTEX); auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", defines, osg::Shader::FRAGMENT); auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment); mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); mCreated = true; } void SkyManager::setCamera(osg::Camera *camera) { mCamera = camera; } void SkyManager::createRain() { if (mRainNode) return; mRainNode = new osg::Group; mRainParticleSystem = new NifOsg::ParticleSystem; osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); osg::ref_ptr stateset = mRainParticleSystem->getOrCreateStateSet(); osg::ref_ptr raindropTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds")); raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); stateset->setTextureAttributeAndModes(0, raindropTex); stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); osg::ref_ptr mat = new osg::Material; mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); stateset->setAttributeAndModes(mat); osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); particleTemplate.setLifeTime(1); osg::ref_ptr emitter = new osgParticle::ModularEmitter; emitter->setParticleSystem(mRainParticleSystem); osg::ref_ptr placer = new osgParticle::BoxPlacer; placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); emitter->setPlacer(placer); mPlacer = placer; // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. osg::ref_ptr counter = new RainCounter; counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); emitter->setCounter(counter); mCounter = counter; osg::ref_ptr shooter = new RainShooter; mRainShooter = shooter; emitter->setShooter(shooter); osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; updater->addParticleSystem(mRainParticleSystem); osg::ref_ptr program = new osgParticle::ModularProgram; program->addOperator(new WrapAroundOperator(mCamera,rainRange)); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); program->setParticleSystem(mRainParticleSystem); mRainNode->addChild(program); mRainNode->addChild(emitter); mRainNode->addChild(mRainParticleSystem); mRainNode->addChild(updater); // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. mRainNode->addCullCallback(mUnderwaterSwitch); mRainNode->setNodeMask(Mask_WeatherParticles); mRainParticleSystem->setUserValue("simpleLighting", true); mSceneManager->recreateShaders(mRainNode); mRootNode->addChild(mRainNode); } void SkyManager::destroyRain() { if (!mRainNode) return; mRootNode->removeChild(mRainNode); mRainNode = nullptr; mPlacer = nullptr; mCounter = nullptr; mRainParticleSystem = nullptr; mRainShooter = nullptr; } SkyManager::~SkyManager() { if (mRootNode) { mRootNode->getParent(0)->removeChild(mRootNode); mRootNode = nullptr; } } int SkyManager::getMasserPhase() const { if (!mCreated) return 0; return mMasser->getPhaseInt(); } int SkyManager::getSecundaPhase() const { if (!mCreated) return 0; return mSecunda->getPhaseInt(); } bool SkyManager::isEnabled() { return mEnabled; } bool SkyManager::hasRain() const { return mRainNode != nullptr; } float SkyManager::getPrecipitationAlpha() const { if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) return mPrecipitationAlpha; return 0.f; } void SkyManager::update(float duration) { if (!mEnabled) return; switchUnderwaterRain(); if (mIsStorm && mParticleNode) { osg::Quat quat; quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) quat.makeRotate(osg::Vec3f(-1,0,0), mStormParticleDirection); mParticleNode->setAttitude(quat); } // UV Scroll the clouds mCloudAnimationTimer += duration * mCloudSpeed * 0.003; mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); mCloudUpdater->setTextureCoord(mCloudAnimationTimer); // morrowind rotates each cloud mesh independently osg::Quat rotation; rotation.makeRotate(MWWorld::Weather::defaultDirection(), mStormDirection); mCloudMesh->setAttitude(rotation); if (mNextCloudMesh->getNodeMask()) { rotation.makeRotate(MWWorld::Weather::defaultDirection(), mNextStormDirection); mNextCloudMesh->setAttitude(rotation); } // rotate the stars by 360 degrees every 4 days mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); } void SkyManager::setEnabled(bool enabled) { if (enabled && !mCreated) create(); const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u; mEarlyRenderBinRoot->setNodeMask(mask); mRootNode->setNodeMask(mask); if (!enabled && mParticleNode && mParticleEffect) { mCurrentParticleEffect.clear(); mDirtyParticlesEffect = true; } mEnabled = enabled; } void SkyManager::setMoonColour (bool red) { if (!mCreated) return; mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); } void SkyManager::updateRainParameters() { if (mRainShooter) { float angle = -std::atan(mWindSpeed/50.f); mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); mRainShooter->setAngle(angle); osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); } } void SkyManager::switchUnderwaterRain() { if (!mRainParticleSystem) return; bool freeze = mUnderwaterSwitch->isUnderwater(); mRainParticleSystem->setFrozen(freeze); } void SkyManager::setWeather(const WeatherResult& weather) { if (!mCreated) return; mRainEntranceSpeed = weather.mRainEntranceSpeed; mRainMaxRaindrops = weather.mRainMaxRaindrops; mRainDiameter = weather.mRainDiameter; mRainMinHeight = weather.mRainMinHeight; mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; mWindSpeed = weather.mWindSpeed; mBaseWindSpeed = weather.mBaseWindSpeed; if (mRainEffect != weather.mRainEffect) { mRainEffect = weather.mRainEffect; if (!mRainEffect.empty()) { createRain(); } else { destroyRain(); } } updateRainParameters(); mIsStorm = weather.mIsStorm; if (mIsStorm) mStormDirection = weather.mStormDirection; if (mDirtyParticlesEffect || (mCurrentParticleEffect != weather.mParticleEffect)) { mDirtyParticlesEffect = false; mCurrentParticleEffect = weather.mParticleEffect; // cleanup old particles if (mParticleEffect) { mParticleNode->removeChild(mParticleEffect); mParticleEffect = nullptr; } if (mCurrentParticleEffect.empty()) { if (mParticleNode) { mRootNode->removeChild(mParticleNode); mParticleNode = nullptr; } } else { if (!mParticleNode) { mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->setNodeMask(Mask_WeatherParticles); mRootNode->addChild(mParticleNode); } mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::make_shared()); mParticleEffect->accept(assignVisitor); SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); mParticleEffect->accept(alphaFaderSetupVisitor); SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); mParticleEffect->accept(findPSVisitor); for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); osg::ref_ptr program = new osgParticle::ModularProgram; if (!mIsStorm) program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); program->setParticleSystem(ps); mParticleNode->addChild(program); for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) { ps->getParticle(particleIndex)->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); ps->getParticle(particleIndex)->update(0, true); } ps->setUserValue("simpleLighting", true); } mSceneManager->recreateShaders(mParticleNode); } } if (mClouds != weather.mCloudTexture) { mClouds = weather.mCloudTexture; std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mCloudUpdater->setTexture(cloudTex); } if (mStormDirection != weather.mStormDirection) mStormDirection = weather.mStormDirection; if (mNextStormDirection != weather.mNextStormDirection) mNextStormDirection = weather.mNextStormDirection; if (mNextClouds != weather.mNextCloudTexture) { mNextClouds = weather.mNextCloudTexture; if (!mNextClouds.empty()) { std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mNextCloudUpdater->setTexture(cloudTex); mNextStormDirection = weather.mStormDirection; } } if (mCloudBlendFactor != weather.mCloudBlendFactor) { mCloudBlendFactor = std::clamp(weather.mCloudBlendFactor, 0.f, 1.f); mCloudUpdater->setOpacity(1.f - mCloudBlendFactor); mNextCloudUpdater->setOpacity(mCloudBlendFactor); mNextCloudMesh->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); } if (mCloudColour != weather.mFogColor) { osg::Vec4f clr (weather.mFogColor); clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); mCloudUpdater->setEmissionColor(clr); mNextCloudUpdater->setEmissionColor(clr); mCloudColour = weather.mFogColor; } if (mSkyColour != weather.mSkyColor) { mSkyColour = weather.mSkyColor; mAtmosphereUpdater->setEmissionColor(mSkyColour); mMasser->setAtmosphereColor(mSkyColour); mSecunda->setAtmosphereColor(mSkyColour); } if (mFogColour != weather.mFogColor) { mFogColour = weather.mFogColor; } mCloudSpeed = weather.mCloudSpeed; mMasser->adjustTransparency(weather.mGlareView); mSecunda->adjustTransparency(weather.mGlareView); mSun->setColor(weather.mSunDiscColor); mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); float nextStarsOpacity = weather.mNightFade * weather.mGlareView; if (weather.mNight && mStarsOpacity != nextStarsOpacity) { mStarsOpacity = nextStarsOpacity; mAtmosphereNightUpdater->setFade(mStarsOpacity); } mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); mPrecipitationAlpha = weather.mPrecipitationAlpha; } float SkyManager::getBaseWindSpeed() const { if (!mCreated) return 0.f; return mBaseWindSpeed; } void SkyManager::setSunglare(bool enabled) { mSunglareEnabled = enabled; if (mSun) mSun->setSunglare(mSunglareEnabled); } void SkyManager::sunEnable() { if (!mCreated) return; mSun->setVisible(true); } void SkyManager::sunDisable() { if (!mCreated) return; mSun->setVisible(false); } void SkyManager::setStormParticleDirection(const osg::Vec3f &direction) { mStormParticleDirection = direction; } void SkyManager::setSunDirection(const osg::Vec3f& direction) { if (!mCreated) return; mSun->setDirection(direction); } void SkyManager::setMasserState(const MoonState& state) { if(!mCreated) return; mMasser->setState(state); } void SkyManager::setSecundaState(const MoonState& state) { if(!mCreated) return; mSecunda->setState(state); } void SkyManager::setDate(int day, int month) { mDay = day; mMonth = month; } void SkyManager::setGlareTimeOfDayFade(float val) { mSun->setGlareTimeOfDayFade(val); } void SkyManager::setWaterHeight(float height) { mUnderwaterSwitch->setWaterLevel(height); } void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) { models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) models.emplace_back(Settings::Manager::getString("skynight02", "Models")); models.emplace_back(Settings::Manager::getString("skynight01", "Models")); models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); textures.emplace_back("textures/tx_mooncircle_full_s.dds"); textures.emplace_back("textures/tx_mooncircle_full_m.dds"); textures.emplace_back("textures/tx_masser_new.dds"); textures.emplace_back("textures/tx_masser_one_wax.dds"); textures.emplace_back("textures/tx_masser_half_wax.dds"); textures.emplace_back("textures/tx_masser_three_wax.dds"); textures.emplace_back("textures/tx_masser_one_wan.dds"); textures.emplace_back("textures/tx_masser_half_wan.dds"); textures.emplace_back("textures/tx_masser_three_wan.dds"); textures.emplace_back("textures/tx_masser_full.dds"); textures.emplace_back("textures/tx_secunda_new.dds"); textures.emplace_back("textures/tx_secunda_one_wax.dds"); textures.emplace_back("textures/tx_secunda_half_wax.dds"); textures.emplace_back("textures/tx_secunda_three_wax.dds"); textures.emplace_back("textures/tx_secunda_one_wan.dds"); textures.emplace_back("textures/tx_secunda_half_wan.dds"); textures.emplace_back("textures/tx_secunda_three_wan.dds"); textures.emplace_back("textures/tx_secunda_full.dds"); textures.emplace_back("textures/tx_sun_05.dds"); textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); textures.emplace_back("textures/tx_raindrop_01.dds"); } void SkyManager::setWaterEnabled(bool enabled) { mUnderwaterSwitch->setEnabled(enabled); } } openmw-openmw-0.48.0/apps/openmw/mwrender/sky.hpp000066400000000000000000000126471445372753700220600ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_SKY_H #define OPENMW_MWRENDER_SKY_H #include #include #include #include #include #include "skyutil.hpp" namespace osg { class Group; class Node; class Material; class PositionAttitudeTransform; class Camera; } namespace osgParticle { class ParticleSystem; class BoxPlacer; } namespace Resource { class SceneManager; } namespace SceneUtil { class RTTNode; } namespace MWRender { ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered /// relative to the camera (e.g. weather particle effects) class SkyManager { public: SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT); ~SkyManager(); void update(float duration); void setEnabled(bool enabled); void setHour (double hour); ///< will be called even when sky is disabled. void setDate (int day, int month); ///< will be called even when sky is disabled. int getMasserPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon int getSecundaPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon void setMoonColour (bool red); ///< change Secunda colour to red void setWeather(const WeatherResult& weather); void sunEnable(); void sunDisable(); bool isEnabled(); bool hasRain() const; float getPrecipitationAlpha() const; void setRainSpeed(float speed); void setStormParticleDirection(const osg::Vec3f& direction); void setSunDirection(const osg::Vec3f& direction); void setMasserState(const MoonState& state); void setSecundaState(const MoonState& state); void setGlareTimeOfDayFade(float val); /// Enable or disable the water plane (used to remove underwater weather particles) void setWaterEnabled(bool enabled); /// Set height of water plane (used to remove underwater weather particles) void setWaterHeight(float height); void listAssetsToPreload(std::vector& models, std::vector& textures); void setCamera(osg::Camera *camera); float getBaseWindSpeed() const; void setSunglare(bool enabled); SceneUtil::RTTNode* getSkyRTT() { return mSkyRTT.get(); } private: void create(); ///< no need to call this, automatically done on first enable() void createRain(); void destroyRain(); void switchUnderwaterRain(); void updateRainParameters(); Resource::SceneManager* mSceneManager; osg::Camera *mCamera; osg::ref_ptr mRootNode; osg::ref_ptr mEarlyRenderBinRoot; osg::ref_ptr mParticleNode; osg::ref_ptr mParticleEffect; osg::ref_ptr mUnderwaterSwitch; osg::ref_ptr mCloudNode; osg::ref_ptr mCloudUpdater; osg::ref_ptr mNextCloudUpdater; osg::ref_ptr mCloudMesh; osg::ref_ptr mNextCloudMesh; osg::ref_ptr mAtmosphereDay; osg::ref_ptr mAtmosphereNightNode; float mAtmosphereNightRoll; osg::ref_ptr mAtmosphereNightUpdater; osg::ref_ptr mAtmosphereUpdater; std::unique_ptr mSun; std::unique_ptr mMasser; std::unique_ptr mSecunda; osg::ref_ptr mRainNode; osg::ref_ptr mRainParticleSystem; osg::ref_ptr mPlacer; osg::ref_ptr mCounter; osg::ref_ptr mRainShooter; bool mCreated; bool mIsStorm; int mDay; int mMonth; float mCloudAnimationTimer; float mRainTimer; // particle system rotation is independent of cloud rotation internally osg::Vec3f mStormParticleDirection; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; // remember some settings so we don't have to apply them again if they didn't change std::string mClouds; std::string mNextClouds; float mCloudBlendFactor; float mCloudSpeed; float mStarsOpacity; osg::Vec4f mCloudColour; osg::Vec4f mSkyColour; osg::Vec4f mFogColour; std::string mCurrentParticleEffect; float mRemainingTransitionTime; bool mRainEnabled; std::string mRainEffect; float mRainSpeed; float mRainDiameter; float mRainMinHeight; float mRainMaxHeight; float mRainEntranceSpeed; int mRainMaxRaindrops; float mWindSpeed; float mBaseWindSpeed; bool mEnabled; bool mSunEnabled; bool mSunglareEnabled; float mPrecipitationAlpha; bool mDirtyParticlesEffect; osg::Vec4f mMoonScriptColor; osg::ref_ptr mSkyRTT; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/skyutil.cpp000066400000000000000000001310521445372753700227410ustar00rootroot00000000000000#include "skyutil.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/weather.hpp" #include "vismask.hpp" #include "renderbin.hpp" namespace { enum class Pass { Atmosphere, Atmosphere_Night, Clouds, Moon, Sun, Sunflash_Query, Sunglare, }; osg::ref_ptr createTexturedQuad(int numUvSets = 1, float scale = 1.f) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-0.5 * scale, -0.5 * scale, 0)); verts->push_back(osg::Vec3f(-0.5 * scale, 0.5 * scale, 0)); verts->push_back(osg::Vec3f(0.5 * scale, 0.5 * scale, 0)); verts->push_back(osg::Vec3f(0.5 * scale, -0.5 * scale, 0)); geom->setVertexArray(verts); osg::ref_ptr texcoords = new osg::Vec2Array; texcoords->push_back(osg::Vec2f(0, 1)); texcoords->push_back(osg::Vec2f(0, 0)); texcoords->push_back(osg::Vec2f(1, 0)); texcoords->push_back(osg::Vec2f(1, 1)); osg::ref_ptr colors = new osg::Vec4Array; colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); geom->setColorArray(colors, osg::Array::BIND_OVERALL); for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); return geom; } struct DummyComputeBoundCallback : osg::Node::ComputeBoundingSphereCallback { osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } }; } namespace MWRender { osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode) { osg::ref_ptr mat = new osg::Material; mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); mat->setColorMode(colorMode); return mat; } osg::ref_ptr createAlphaTrackingUnlitMaterial() { return createUnlitMaterial(osg::Material::DIFFUSE); } class SunUpdater : public SceneUtil::StateSetUpdater { public: osg::Vec4f mColor; SunUpdater() : mColor(1.f, 1.f, 1.f, 1.f) { } void setDefaults(osg::StateSet* stateset) override { stateset->setAttributeAndModes(createUnlitMaterial()); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); } }; OcclusionCallback::OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : mOcclusionQueryVisiblePixels(oqnVisible) , mOcclusionQueryTotalPixels(oqnTotal) { } float OcclusionCallback::getVisibleRatio (osg::Camera* camera) { int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); float visibleRatio = 0.f; if (total > 0) visibleRatio = static_cast(visible) / static_cast(total); float dt = MWBase::Environment::get().getFrameDuration(); float lastRatio = mLastRatio[osg::observer_ptr(camera)]; float change = dt*10; if (visibleRatio > lastRatio) visibleRatio = std::min(visibleRatio, lastRatio + change); else visibleRatio = std::max(visibleRatio, lastRatio - change); mLastRatio[osg::observer_ptr(camera)] = visibleRatio; return visibleRatio; } /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : OcclusionCallback(oqnVisible, oqnTotal) , mGlareView(1.f) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); osg::ref_ptr stateset; if (visibleRatio > 0.f) { const float fadeThreshold = 0.1; if (visibleRatio < fadeThreshold) { float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; osg::ref_ptr mat (createUnlitMaterial()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); stateset = new osg::StateSet; stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } else if (visibleRatio < 1.f) { const float threshold = 0.6; visibleRatio = visibleRatio * (1.f - threshold) + threshold; } } float scale = visibleRatio; if (scale == 0.f) { // no traverse return; } else if (scale == 1.f) traverse(node, cv); else { osg::Matrix modelView = *cv->getModelViewMatrix(); modelView.preMultScale(osg::Vec3f(scale, scale, scale)); if (stateset) cv->pushStateSet(stateset); cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); if (stateset) cv->popStateSet(); } } void setGlareView(float value) { mGlareView = value; } private: float mGlareView; }; /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. /// Must be attached as a cull callback to the node above the glare node. class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, osg::ref_ptr sunTransform) : OcclusionCallback(oqnVisible, oqnTotal) , mSunTransform(sunTransform) , mTimeOfDayFade(1.f) , mGlareView(1.f) { mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, // so the resulting color looks more orange than red. mColor *= 2; for (int i=0; i<3; ++i) mColor[i] = std::min(1.f, mColor[i]); } void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) { float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); float fade = value * mSunGlareFaderMax; fade *= mTimeOfDayFade * mGlareView * visibleRatio; if (fade == 0.f) { // no traverse return; } else { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr mat = createUnlitMaterial(); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); stateset->setAttributeAndModes(mat); cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); } } void setTimeOfDayFade(float val) { mTimeOfDayFade = val; } void setGlareView(float glareView) { mGlareView = glareView; } private: float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const { osg::Vec3d eye, center, up; viewMatrix.getLookAt(eye, center, up); osg::Vec3d forward = center - eye; osg::Vec3d sun = mSunTransform->getPosition(); forward.normalize(); sun.normalize(); float angleRadians = std::acos(forward * sun); return angleRadians; } osg::ref_ptr mSunTransform; float mTimeOfDayFade; float mGlareView; osg::Vec4f mColor; float mSunGlareFaderMax; float mSunGlareFaderAngleMax; }; struct MoonUpdater : SceneUtil::StateSetUpdater { Resource::ImageManager& mImageManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; float mShadowBlend; osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; bool mForceShaders; MoonUpdater(Resource::ImageManager& imageManager, bool forceShaders) : mImageManager(imageManager) , mPhaseTex() , mCircleTex() , mTransparency(1.0f) , mShadowBlend(1.0f) , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) , mForceShaders(forceShaders) { } void setDefaults(osg::StateSet* stateset) override { if (mForceShaders) { stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon))); stateset->setTextureAttributeAndModes(0, mPhaseTex); stateset->setTextureAttributeAndModes(1, mCircleTex); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); stateset->addUniform(new osg::Uniform("diffuseMap", 0)); stateset->addUniform(new osg::Uniform("maskMap", 1)); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } else { stateset->setTextureAttributeAndModes(0, mPhaseTex); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor stateset->setTextureAttributeAndModes(0, texEnv); stateset->setTextureAttributeAndModes(1, mCircleTex); osg::ref_ptr texEnv2 = new osg::TexEnvCombine; texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency stateset->setTextureAttributeAndModes(1, texEnv2); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { if (mForceShaders) { stateset->setTextureAttribute(0, mPhaseTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttribute(1, mCircleTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); if (auto* uMoonBlend = stateset->getUniform("moonBlend")) uMoonBlend->set(mMoonColor * mShadowBlend); if (auto* uAtmosphereFade = stateset->getUniform("atmosphereFade")) uAtmosphereFade->set(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); } else { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(mMoonColor * mShadowBlend); osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); } } void setTextures(const std::string& phaseTex, const std::string& circleTex) { mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); reset(); } }; class CameraRelativeTransformCullCallback : public SceneUtil::NodeCallback { public: void operator() (osg::Node* node, osgUtil::CullVisitor* cv) { // XXX have to remove unwanted culling plane of the water reflection camera // Remove all planes that aren't from the standard frustum unsigned int numPlanes = 4; if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) ++numPlanes; if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) ++numPlanes; unsigned int mask = 0x1; unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) { if (i >= numPlanes) { // turn off this culling plane resultMask &= (~mask); } mask <<= 1; } cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); cv->getProjectionCullingStack().back().pushCurrentMask(); cv->getCurrentCullingSet().pushCurrentMask(); traverse(node, cv); cv->getProjectionCullingStack().back().popCurrentMask(); cv->getCurrentCullingSet().popCurrentMask(); } }; void AtmosphereUpdater::setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) { stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere))); } void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); } AtmosphereNightUpdater::AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders) : mColor(osg::Vec4f(0,0,0,0)) , mTexture(new osg::Texture2D(imageManager->getWarningImage())) , mForceShaders(forceShaders) { mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); } void AtmosphereNightUpdater::setFade(float fade) { mColor.a() = fade; } void AtmosphereNightUpdater::setDefaults(osg::StateSet* stateset) { if (mForceShaders) { stateset->addUniform(new osg::Uniform("opacity", 0.f)); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night))); } else { osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void AtmosphereNightUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { if (mForceShaders) { stateset->getUniform("opacity")->set(mColor.a()); } else { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(mColor); } } CloudUpdater::CloudUpdater(bool forceShaders) : mOpacity(0.f) , mForceShaders(forceShaders) { } void CloudUpdater::setTexture(osg::ref_ptr texture) { mTexture = texture; } void CloudUpdater::setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } void CloudUpdater::setOpacity(float opacity) { mOpacity = opacity; } void CloudUpdater::setTextureCoord(float timer) { mTexMat = osg::Matrixf::translate(osg::Vec3f(0.f, -timer, 0.f)); } void CloudUpdater::setDefaults(osg::StateSet *stateset) { stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::ref_ptr texmat = new osg::TexMat; stateset->setTextureAttributeAndModes(0, texmat); if (mForceShaders) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("opacity", 1.f)); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds))); } else { stateset->setTextureAttributeAndModes(1, texmat); // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); stateset->setTextureAttributeAndModes(1, texEnvCombine); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void CloudUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); texMat->setMatrix(mTexMat); if (mForceShaders) { stateset->getUniform("opacity")->set(mOpacity); } else { stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); } } class SkyStereoStatesetUpdater : public SceneUtil::StateSetUpdater { public: SkyStereoStatesetUpdater() { } protected: void setDefaults(osg::StateSet* stateset) override { if (!Stereo::getMultiview()) stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrix"), osg::StateAttribute::OVERRIDE); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { if (Stereo::getMultiview()) { std::array projectionMatrices; auto& sm = Stereo::Manager::instance(); for (int view : {0, 1}) { auto projectionMatrix = sm.computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()); auto viewOffsetMatrix = sm.computeEyeViewOffset(view); for (int col : {0, 1, 2}) viewOffsetMatrix(3, col) = 0; projectionMatrices[view] = viewOffsetMatrix * projectionMatrix; } Stereo::setMultiviewMatrices(stateset, projectionMatrices); } } void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* /*cv*/) override { auto& sm = Stereo::Manager::instance(); auto* projectionMatrixUniform = stateset->getUniform("projectionMatrix"); auto projectionMatrix = sm.computeEyeProjection(0, SceneUtil::AutoDepth::isReversed()); projectionMatrixUniform->set(projectionMatrix); } void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* /*cv*/) override { auto& sm = Stereo::Manager::instance(); auto* projectionMatrixUniform = stateset->getUniform("projectionMatrix"); auto projectionMatrix = sm.computeEyeProjection(1, SceneUtil::AutoDepth::isReversed()); projectionMatrixUniform->set(projectionMatrix); } private: }; CameraRelativeTransform::CameraRelativeTransform() { // Culling works in node-local space, not in camera space, so we can't cull this node correctly // That's not a problem though, children of this node can be culled just fine // Just make sure you do not place a CameraRelativeTransform deep in the scene graph setCullingActive(false); addCullCallback(new CameraRelativeTransformCullCallback); if (Stereo::getStereo()) addCullCallback(new SkyStereoStatesetUpdater); } CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) : osg::Transform(copy, copyop) { } const osg::Vec3f& CameraRelativeTransform::getLastViewPoint() const { return mViewPoint; } bool CameraRelativeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const { if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { mViewPoint = static_cast(nv)->getViewPoint(); } if (_referenceFrame==RELATIVE_RF) { matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); return false; } else // absolute { matrix.makeIdentity(); return true; } } osg::BoundingSphere CameraRelativeTransform::computeBound() const { return osg::BoundingSphere(); } UnderwaterSwitchCallback::UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) : mCameraRelativeTransform(cameraRelativeTransform) , mEnabled(true) , mWaterLevel(0.f) { } bool UnderwaterSwitchCallback::isUnderwater() { osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); return mEnabled && viewPoint.z() < mWaterLevel; } void UnderwaterSwitchCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { if (isUnderwater()) return; traverse(node, nv); } void UnderwaterSwitchCallback::setEnabled(bool enabled) { mEnabled = enabled; } void UnderwaterSwitchCallback::setWaterLevel(float waterLevel) { mWaterLevel = waterLevel; } const float CelestialBody::mDistance = 1000.0f; CelestialBody::CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask) : mVisibleMask(visibleMask) { mGeom = createTexturedQuad(numUvSets); mGeom->getOrCreateStateSet(); mTransform = new osg::PositionAttitudeTransform; mTransform->setNodeMask(mVisibleMask); mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); mTransform->addChild(mGeom); parentNode->addChild(mTransform); } void CelestialBody::setVisible(bool visible) { mTransform->setNodeMask(visible ? mVisibleMask : 0); } Sun::Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new SunUpdater) { mTransform->addUpdateCallback(mUpdater); Resource::ImageManager& imageManager = *sceneManager.getImageManager(); osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); sunTex->setName("diffuseMap"); mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex); mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); osg::ref_ptr queryNode = new osg::Group; // Need to render after the world geometry so we can correctly test for occlusions osg::StateSet* stateset = queryNode->getOrCreateStateSet(); stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); stateset->setNestRenderBins(false); // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun if (!sceneManager.getForceShaders()) { osg::ref_ptr alphaFunc = new osg::AlphaFunc; alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); stateset->setAttributeAndModes(alphaFunc); } stateset->setTextureAttributeAndModes(0, sunTex); stateset->setAttributeAndModes(createUnlitMaterial()); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); stateset->setAttributeAndModes(colormask); if (sceneManager.getSupportsNormalsRT()) stateset->setAttributeAndModes(new osg::ColorMaski(1, false, false, false, false)); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); createSunFlash(imageManager); createSunGlare(); } Sun::~Sun() { mTransform->removeUpdateCallback(mUpdater); destroySunFlash(); destroySunGlare(); } void Sun::setColor(const osg::Vec4f& color) { mUpdater->mColor.r() = color.r(); mUpdater->mColor.g() = color.g(); mUpdater->mColor.b() = color.b(); } void Sun::adjustTransparency(const float ratio) { mUpdater->mColor.a() = ratio; if (mSunGlareCallback) mSunGlareCallback->setGlareView(ratio); if (mSunFlashCallback) mSunFlashCallback->setGlareView(ratio); } void Sun::setDirection(const osg::Vec3f& direction) { osg::Vec3f normalizedDirection = direction / direction.length(); mTransform->setPosition(normalizedDirection * mDistance); osg::Quat quat; quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); mTransform->setAttitude(quat); } void Sun::setGlareTimeOfDayFade(float val) { if (mSunGlareCallback) mSunGlareCallback->setTimeOfDayFade(val); } void Sun::setSunglare(bool enabled) { mSunGlareNode->setNodeMask(enabled ? ~0u : 0); mSunFlashNode->setNodeMask(enabled ? ~0u : 0); } osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry // is only called once. // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. queryGeom->setDataVariance(osg::Object::STATIC); // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to // circumvent this. queryGeom->setVertexArray(mGeom->getVertexArray()); queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); // Still need a proper bounding sphere. oqn->setInitialBound(queryGeom->getBound()); oqn->setQueryGeometry(queryGeom.release()); osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LEQUAL); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. double far = SceneUtil::AutoDepth::isReversed() ? 0.0 : 1.0; depth->setFunction(osg::Depth::LEQUAL); depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); queryStateSet->setAttributeAndModes(depth); } else { queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } oqn->setQueryStateSet(queryStateSet); parent->addChild(oqn); return oqn; } void Sun::createSunFlash(Resource::ImageManager& imageManager) { osg::ref_ptr tex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); tex->setName("diffuseMap"); osg::ref_ptr group (new osg::Group); mTransform->addChild(group); const float scale = 2.6f; osg::ref_ptr geom = createTexturedQuad(1, scale); group->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, tex); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); mSunFlashNode = group; mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); mSunFlashNode->addCullCallback(mSunFlashCallback); } void Sun::destroySunFlash() { if (mSunFlashNode) { mSunFlashNode->removeCullCallback(mSunFlashCallback); mSunFlashCallback = nullptr; } } void Sun::createSunGlare() { osg::ref_ptr camera = new osg::Camera; camera->setProjectionMatrix(osg::Matrix::identity()); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? camera->setViewMatrix(osg::Matrix::identity()); camera->setClearMask(0); camera->setRenderOrder(osg::Camera::NESTED_RENDER); camera->setAllowEventFocus(false); camera->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix()))); SceneUtil::setCameraClearDepth(camera); osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); camera->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare))); // set up additive blending osg::ref_ptr blendFunc = new osg::BlendFunc; blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); blendFunc->setDestination(osg::BlendFunc::ONE); stateset->setAttributeAndModes(blendFunc); mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); mSunGlareNode = camera; mSunGlareNode->addCullCallback(mSunGlareCallback); mTransform->addChild(camera); } void Sun::destroySunGlare() { if (mSunGlareNode) { mSunGlareNode->removeCullCallback(mSunGlareCallback); mSunGlareCallback = nullptr; } } Moon::Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase::Unspecified) , mUpdater(new MoonUpdater(*sceneManager.getImageManager(), sceneManager.getForceShaders())) { setPhase(MoonState::Phase::Full); setVisible(true); mGeom->addUpdateCallback(mUpdater); } Moon::~Moon() { mGeom->removeUpdateCallback(mUpdater); } void Moon::adjustTransparency(const float ratio) { mUpdater->mTransparency *= ratio; } void Moon::setState(const MoonState state) { float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); mTransform->setPosition(direction * mDistance); // The moon quad is initially oriented facing down, so we need to offset its X-axis // rotation to rotate it to face the camera when sitting at the horizon. osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); mTransform->setAttitude(attX * rotZ); setPhase(state.mPhase); mUpdater->mTransparency = state.mMoonAlpha; mUpdater->mShadowBlend = state.mShadowBlend; } void Moon::setAtmosphereColor(const osg::Vec4f& color) { mUpdater->mAtmosphereColor = color; } void Moon::setColor(const osg::Vec4f& color) { mUpdater->mMoonColor = color; } unsigned int Moon::getPhaseInt() const { switch (mPhase) { case MoonState::Phase::New: return 0; case MoonState::Phase::WaxingCrescent: return 1; case MoonState::Phase::WaningCrescent: return 1; case MoonState::Phase::FirstQuarter: return 2; case MoonState::Phase::ThirdQuarter: return 2; case MoonState::Phase::WaxingGibbous: return 3; case MoonState::Phase::WaningGibbous: return 3; case MoonState::Phase::Full: return 4; default: return 0; } } void Moon::setPhase(const MoonState::Phase& phase) { if(mPhase == phase) return; mPhase = phase; std::string textureName = "textures/tx_"; if (mType == Moon::Type_Secunda) textureName += "secunda_"; else textureName += "masser_"; switch (mPhase) { case MoonState::Phase::New: textureName += "new"; break; case MoonState::Phase::WaxingCrescent: textureName += "one_wax"; break; case MoonState::Phase::FirstQuarter: textureName += "half_wax"; break; case MoonState::Phase::WaxingGibbous: textureName += "three_wax"; break; case MoonState::Phase::WaningCrescent: textureName += "one_wan"; break; case MoonState::Phase::ThirdQuarter: textureName += "half_wan"; break; case MoonState::Phase::WaningGibbous: textureName += "three_wan"; break; case MoonState::Phase::Full: textureName += "full"; break; default: break; } textureName += ".dds"; if (mType == Moon::Type_Secunda) mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); else mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); } int RainCounter::numParticlesToCreate(double dt) const { // limit dt to avoid large particle emissions if there are jumps in the simulation time // 0.2 seconds is the same cap as used in Engine's frame loop dt = std::min(dt, 0.2); return ConstantRateCounter::numParticlesToCreate(dt); } RainShooter::RainShooter() : mAngle(0.f) { } void RainShooter::shoot(osgParticle::Particle* particle) const { particle->setVelocity(mVelocity); particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); } void RainShooter::setVelocity(const osg::Vec3f& velocity) { mVelocity = velocity; } void RainShooter::setAngle(float angle) { mAngle = angle; } osg::Object* RainShooter::cloneType() const { return new RainShooter; } osg::Object* RainShooter::clone(const osg::CopyOp &) const { return new RainShooter(*this); } ModVertexAlphaVisitor::ModVertexAlphaVisitor(ModVertexAlphaVisitor::MeshType type) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mType(type) { } void ModVertexAlphaVisitor::apply(osg::Geometry& geometry) { osg::ref_ptr colors = new osg::Vec4Array(geometry.getVertexArray()->getNumElements()); for (unsigned int i=0; isize(); ++i) { float alpha = 1.f; switch (mType) { case ModVertexAlphaVisitor::Atmosphere: { // this is a cylinder, so every second vertex belongs to the bottom-most row alpha = (i%2) ? 0.f : 1.f; break; } case ModVertexAlphaVisitor::Clouds: { if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row else if (i>= 33 && i <= 48) alpha = 0.25098; // second row else alpha = 1.f; break; } case ModVertexAlphaVisitor::Stars: { if (geometry.getColorArray()) { osg::Vec4Array* origColors = static_cast(geometry.getColorArray()); alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; } else alpha = 1.f; break; } } (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); } geometry.setColorArray(colors, osg::Array::BIND_PER_VERTEX); } } openmw-openmw-0.48.0/apps/openmw/mwrender/skyutil.hpp000066400000000000000000000222541445372753700227510ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_SKYUTIL_H #define OPENMW_MWRENDER_SKYUTIL_H #include #include #include #include #include #include #include #include #include namespace Resource { class ImageManager; class SceneManager; } namespace MWRender { struct MoonUpdater; class SunUpdater; class SunFlashCallback; class SunGlareCallback; struct WeatherResult { std::string mCloudTexture; std::string mNextCloudTexture; float mCloudBlendFactor; osg::Vec4f mFogColor; osg::Vec4f mAmbientColor; osg::Vec4f mSkyColor; // sun light color osg::Vec4f mSunColor; // alpha is the sun transparency osg::Vec4f mSunDiscColor; float mFogDepth; float mDLFogFactor; float mDLFogOffset; float mWindSpeed; float mBaseWindSpeed; float mCurrentWindSpeed; float mNextWindSpeed; float mCloudSpeed; float mGlareView; bool mNight; // use night skybox float mNightFade; // fading factor for night skybox bool mIsStorm; std::string mAmbientLoopSoundID; float mAmbientSoundVolume; std::string mParticleEffect; std::string mRainEffect; float mPrecipitationAlpha; float mRainDiameter; float mRainMinHeight; float mRainMaxHeight; float mRainSpeed; float mRainEntranceSpeed; int mRainMaxRaindrops; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; }; struct MoonState { enum class Phase { Full, WaningGibbous, ThirdQuarter, WaningCrescent, New, WaxingCrescent, FirstQuarter, WaxingGibbous, Unspecified }; float mRotationFromHorizon; float mRotationFromNorth; Phase mPhase; float mShadowBlend; float mMoonAlpha; }; osg::ref_ptr createAlphaTrackingUnlitMaterial(); osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode = osg::Material::OFF); class OcclusionCallback { public: OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal); protected: float getVisibleRatio (osg::Camera* camera); private: osg::ref_ptr mOcclusionQueryVisiblePixels; osg::ref_ptr mOcclusionQueryTotalPixels; std::map, float> mLastRatio; }; class AtmosphereUpdater : public SceneUtil::StateSetUpdater { public: void setEmissionColor(const osg::Vec4f& emissionColor); protected: void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; private: osg::Vec4f mEmissionColor; }; class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater { public: AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders); void setFade(float fade); protected: void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; private: osg::Vec4f mColor; osg::ref_ptr mTexture; bool mForceShaders; }; class CloudUpdater : public SceneUtil::StateSetUpdater { public: CloudUpdater(bool forceShaders); void setTexture(osg::ref_ptr texture); void setEmissionColor(const osg::Vec4f& emissionColor); void setOpacity(float opacity); void setTextureCoord(float timer); protected: void setDefaults(osg::StateSet *stateset) override; void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; private: osg::ref_ptr mTexture; osg::Vec4f mEmissionColor; float mOpacity; bool mForceShaders; osg::Matrixf mTexMat; }; /// Transform that removes the eyepoint of the modelview matrix, /// i.e. its children are positioned relative to the camera. class CameraRelativeTransform : public osg::Transform { public: CameraRelativeTransform(); CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop); META_Node(MWRender, CameraRelativeTransform) const osg::Vec3f& getLastViewPoint() const; bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override; osg::BoundingSphere computeBound() const override; private: // viewPoint for the current frame mutable osg::Vec3f mViewPoint; }; /// @brief Hides the node subgraph if the eye point is below water. /// @note Must be added as cull callback. /// @note Meant to be used on a node that is child of a CameraRelativeTransform. /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. class UnderwaterSwitchCallback : public SceneUtil::NodeCallback { public: UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform); bool isUnderwater(); void operator()(osg::Node* node, osg::NodeVisitor* nv); void setEnabled(bool enabled); void setWaterLevel(float waterLevel); private: osg::ref_ptr mCameraRelativeTransform; bool mEnabled; float mWaterLevel; }; /// A base class for the sun and moons. class CelestialBody { public: CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u); virtual ~CelestialBody() = default; virtual void adjustTransparency(const float ratio) = 0; void setVisible(bool visible); protected: unsigned int mVisibleMask; static const float mDistance; osg::ref_ptr mTransform; osg::ref_ptr mGeom; }; class Sun : public CelestialBody { public: Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager); ~Sun(); void setColor(const osg::Vec4f& color); void adjustTransparency(const float ratio) override; void setDirection(const osg::Vec3f& direction); void setGlareTimeOfDayFade(float val); void setSunglare(bool enabled); private: /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible); void createSunFlash(Resource::ImageManager& imageManager); void destroySunFlash(); void createSunGlare(); void destroySunGlare(); osg::ref_ptr mUpdater; osg::ref_ptr mSunFlashNode; osg::ref_ptr mSunGlareNode; osg::ref_ptr mSunFlashCallback; osg::ref_ptr mSunGlareCallback; osg::ref_ptr mOcclusionQueryVisiblePixels; osg::ref_ptr mOcclusionQueryTotalPixels; }; class Moon : public CelestialBody { public: enum Type { Type_Masser = 0, Type_Secunda }; Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type); ~Moon(); void adjustTransparency(const float ratio) override; void setState(const MoonState state); void setAtmosphereColor(const osg::Vec4f& color); void setColor(const osg::Vec4f& color); unsigned int getPhaseInt() const; private: Type mType; MoonState::Phase mPhase; osg::ref_ptr mUpdater; void setPhase(const MoonState::Phase& phase); }; class RainCounter : public osgParticle::ConstantRateCounter { public: int numParticlesToCreate(double dt) const override; }; class RainShooter : public osgParticle::Shooter { public: RainShooter(); osg::Object* cloneType() const override; osg::Object* clone(const osg::CopyOp &) const override; void shoot(osgParticle::Particle* particle) const override; void setVelocity(const osg::Vec3f& velocity); void setAngle(float angle); private: osg::Vec3f mVelocity; float mAngle; }; class ModVertexAlphaVisitor : public osg::NodeVisitor { public: enum MeshType { Atmosphere, Stars, Clouds }; ModVertexAlphaVisitor(MeshType type); void apply(osg::Geometry& geometry) override; private: MeshType mType; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/terrainstorage.cpp000066400000000000000000000051511445372753700242660ustar00rootroot00000000000000#include "terrainstorage.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" #include "landmanager.hpp" namespace MWRender { TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : ESMTerrain::Storage(resourceSystem->getVFS(), normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) , mLandManager(new LandManager(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX)) , mResourceSystem(resourceSystem) { mResourceSystem->addResourceManager(mLandManager.get()); } TerrainStorage::~TerrainStorage() { mResourceSystem->removeResourceManager(mLandManager.get()); } bool TerrainStorage::hasData(int cellX, int cellY) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Land* land = esmStore.get().search(cellX, cellY); return land != nullptr; } void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { minX = 0; minY = 0; maxX = 0; maxY = 0; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); MWWorld::Store::iterator it = esmStore.get().begin(); for (; it != esmStore.get().end(); ++it) { if (it->mX < minX) minX = static_cast(it->mX); if (it->mX > maxX) maxX = static_cast(it->mX); if (it->mY < minY) minY = static_cast(it->mY); if (it->mY > maxY) maxY = static_cast(it->mY); } // since grid coords are at cell origin, we need to add 1 cell maxX += 1; maxY += 1; } LandManager *TerrainStorage::getLandManager() const { return mLandManager.get(); } osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) { return mLandManager->getLand(cellX, cellY); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); return esmStore.get().search(index, plugin); } } openmw-openmw-0.48.0/apps/openmw/mwrender/terrainstorage.hpp000066400000000000000000000023211445372753700242670ustar00rootroot00000000000000#ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H #include #include #include namespace MWRender { class LandManager; /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. class TerrainStorage : public ESMTerrain::Storage { public: TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); ~TerrainStorage(); osg::ref_ptr getLand (int cellX, int cellY) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; bool hasData(int cellX, int cellY) override; /// Get bounds of the whole terrain in cell units void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; LandManager* getLandManager() const; private: std::unique_ptr mLandManager; Resource::ResourceSystem* mResourceSystem; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/transparentpass.cpp000066400000000000000000000141101445372753700244600ustar00rootroot00000000000000#include "transparentpass.hpp" #include #include #include #include #include #include #include #include #include #include "vismask.hpp" namespace MWRender { TransparentDepthBinCallback::TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass) : mStateSet(new osg::StateSet) , mPostPass(postPass) { osg::ref_ptr image = new osg::Image; image->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); image->setColor(osg::Vec4(1,1,1,1), 0, 0); osg::ref_ptr dummyTexture = new osg::Texture2D(image); dummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); dummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); constexpr osg::StateAttribute::OverrideValue modeOff = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE; constexpr osg::StateAttribute::OverrideValue modeOn = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE; mStateSet->setTextureAttributeAndModes(0, dummyTexture); Shader::ShaderManager::DefineMap defines; Stereo::Manager::instance().shaderStereoDefines(defines); osg::ref_ptr vertex = shaderManager.getShader("blended_depth_postpass_vertex.glsl", defines, osg::Shader::VERTEX); osg::ref_ptr fragment = shaderManager.getShader("blended_depth_postpass_fragment.glsl", defines, osg::Shader::FRAGMENT); mStateSet->setAttributeAndModes(new osg::BlendFunc, modeOff); mStateSet->setAttributeAndModes(shaderManager.getProgram(vertex, fragment), modeOn); mStateSet->setAttributeAndModes(new SceneUtil::AutoDepth, modeOn); for (unsigned int unit = 1; unit < 8; ++unit) mStateSet->setTextureMode(unit, GL_TEXTURE_2D, modeOff); } void TransparentDepthBinCallback::drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) { osg::State& state = *renderInfo.getState(); osg::GLExtensions* ext = state.get(); bool validFbo = false; unsigned int frameId = state.getFrameStamp()->getFrameNumber() % 2; const auto& fbo = mFbo[frameId]; const auto& msaaFbo = mMsaaFbo[frameId]; const auto& opaqueFbo = mOpaqueFbo[frameId]; if (bin->getStage()->getMultisampleResolveFramebufferObject() && bin->getStage()->getMultisampleResolveFramebufferObject() == fbo) validFbo = true; else if (bin->getStage()->getFrameBufferObject() && (bin->getStage()->getFrameBufferObject() == fbo || bin->getStage()->getFrameBufferObject() == msaaFbo)) validFbo = true; if (!validFbo) { bin->drawImplementation(renderInfo, previous); return; } const osg::Texture* tex = opaqueFbo->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).getTexture(); if (Stereo::getMultiview()) { if (!mMultiviewResolve[frameId]) { mMultiviewResolve[frameId] = std::make_unique(msaaFbo ? msaaFbo : fbo, opaqueFbo, GL_DEPTH_BUFFER_BIT); } mMultiviewResolve[frameId]->resolveImplementation(state); } else { opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, tex->getTextureWidth(), tex->getTextureHeight(), 0, 0, tex->getTextureWidth(), tex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); } msaaFbo ? msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER) : fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); // draws scene into primary attachments bin->drawImplementation(renderInfo, previous); if (!mPostPass) return; opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); // draw transparent post-pass to populate a postprocess friendly depth texture with alpha-clipped geometry unsigned int numToPop = previous ? osgUtil::StateGraph::numToPop(previous->_parent) : 0; if (numToPop > 1) numToPop--; unsigned int insertStateSetPosition = state.getStateSetStackSize() - numToPop; state.insertStateSet(insertStateSetPosition, mStateSet); for(auto rit = bin->getRenderLeafList().begin(); rit != bin->getRenderLeafList().end(); rit++) { osgUtil::RenderLeaf* rl = *rit; const osg::StateSet* ss = rl->_parent->getStateSet(); if (rl->_drawable->getNodeMask() == Mask_ParticleSystem || rl->_drawable->getNodeMask() == Mask_Effect) continue; if (ss->getAttribute(osg::StateAttribute::MATERIAL)) { const osg::Material* mat = static_cast(ss->getAttribute(osg::StateAttribute::MATERIAL)); if (mat->getDiffuse(osg::Material::FRONT).a() < 0.5) continue; } rl->render(renderInfo,previous); previous = rl; } state.removeStateSet(insertStateSetPosition); msaaFbo ? msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER) : fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); state.checkGLErrors("after TransparentDepthBinCallback::drawImplementation"); } void TransparentDepthBinCallback::dirtyFrame(int frameId) { if (mMultiviewResolve[frameId]) mMultiviewResolve[frameId]->dirty(); } } openmw-openmw-0.48.0/apps/openmw/mwrender/transparentpass.hpp000066400000000000000000000021311445372753700244650ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_TRANSPARENTPASS_H #define OPENMW_MWRENDER_TRANSPARENTPASS_H #include #include #include #include #include "postprocessor.hpp" namespace Shader { class ShaderManager; } namespace Stereo { class MultiviewFramebufferResolve; } namespace MWRender { class TransparentDepthBinCallback : public osgUtil::RenderBin::DrawCallback { public: TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass); void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; void dirtyFrame(int frameId); std::array, 2> mFbo; std::array, 2> mMsaaFbo; std::array, 2> mOpaqueFbo; std::array, 2> mMultiviewResolve; private: osg::ref_ptr mStateSet; bool mPostPass; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/util.cpp000066400000000000000000000043261445372753700222150ustar00rootroot00000000000000#include "util.hpp" #include #include #include #include #include #include namespace MWRender { class TextureOverrideVisitor : public osg::NodeVisitor { public: TextureOverrideVisitor(const std::string& texture, Resource::ResourceSystem* resourcesystem) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mTexture(texture) , mResourcesystem(resourcesystem) { } void apply(osg::Node& node) override { int index = 0; osg::ref_ptr nodePtr(&node); if (node.getUserValue("overrideFx", index)) { if (index == 1) overrideTexture(mTexture, mResourcesystem, nodePtr); } traverse(node); } std::string mTexture; Resource::ResourceSystem* mResourcesystem; }; void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) { TextureOverrideVisitor overrideVisitor(texture, resourceSystem); node->accept(overrideVisitor); } void overrideTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) { if (texture.empty()) return; std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); // Not sure if wrap settings should be pulled from the overridden texture? osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); tex->setName("diffuseMap"); osg::ref_ptr stateset; if (node->getStateSet()) stateset = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); else stateset = new osg::StateSet; stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); node->setStateSet(stateset); } } openmw-openmw-0.48.0/apps/openmw/mwrender/util.hpp000066400000000000000000000017661445372753700222270ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_UTIL_H #define OPENMW_MWRENDER_UTIL_H #include #include #include namespace osg { class Node; } namespace Resource { class ResourceSystem; } namespace MWRender { // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty of the .NIF file's root node, // if it had a NiTexturingProperty. Used for applying "particle textures" to magic effects. void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node); void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); // Node callback to entirely skip the traversal. class NoTraverseCallback : public osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { // no traverse() } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/vismask.hpp000066400000000000000000000054671445372753700227310ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_VISMASK_H #define OPENMW_MWRENDER_VISMASK_H namespace MWRender { /// Node masks used for controlling visibility of game objects. /// @par Any node in the OSG scene graph can have a node mask. When traversing the scene graph, /// the node visitor's traversal mask is bitwise AND'ed with the node mask. If the result of this test is /// 0, then the node and all its child nodes are not processed. /// @par Important traversal masks are the camera's cull mask (determines what is visible), /// the update visitor mask (what is updated) and the intersection visitor mask (what is /// selectable through mouse clicks or other intersection tests). /// @par In practice, it can be useful to make a "hierarchy" out of the node masks - e.g. in OpenMW, /// all 3D rendering nodes are child of a Scene Root node with Mask_Scene. When we do not want 3D rendering, /// we can just omit Mask_Scene from the traversal mask, and do not need to omit all the individual /// element masks (water, sky, terrain, etc.) since the traversal will already have stopped at the Scene root node. /// @par The comments within the VisMask enum should give some hints as to what masks are commonly "child" of /// another mask, or what type of node this mask is usually set on. /// @note The mask values are not serialized within models, nor used in any other way that would break backwards /// compatibility if the enumeration values were to be changed. Feel free to change them when it makes sense. enum VisMask : unsigned int { Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors // child of Scene Mask_Effect = (1<<1), Mask_Debug = (1<<2), Mask_Actor = (1<<3), Mask_Player = (1<<4), Mask_Sky = (1<<5), Mask_Water = (1<<6), // choose Water or SimpleWater depending on detail required Mask_SimpleWater = (1<<7), Mask_Terrain = (1<<8), Mask_FirstPerson = (1<<9), Mask_Object = (1<<10), Mask_Static = (1<<11), // child of Sky Mask_Sun = (1<<12), Mask_WeatherParticles = (1<<13), // top level masks Mask_Scene = (1<<14), Mask_GUI = (1<<15), // Set on a ParticleSystem Drawable Mask_ParticleSystem = (1<<16), // Set on cameras within the main scene graph Mask_RenderToTexture = (1<<17), Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager Mask_Lighting = (1<<19), Mask_Groundcover = (1<<20), }; // Defines masks to remove when using ToggleWorld command constexpr static unsigned int sToggleWorldMask = Mask_Debug | Mask_Actor | Mask_Terrain | Mask_Object | Mask_Static | Mask_Groundcover; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/water.cpp000066400000000000000000000706101445372753700223610ustar00rootroot00000000000000#include "water.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" #include "util.hpp" namespace MWRender { // -------------------------------------------------------------------------------------------------------------------------------- /// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. /// Also handles flipping of the plane when the eye point goes below it. /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); class ClipCullNode : public osg::Group { class PlaneCullCallback : public SceneUtil::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); osg::Plane plane = *mCullPlane; plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); osg::Vec3d eyePoint = cv->getEyePoint(); if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) plane.flip(); cv->getProjectionCullingStack().back().getFrustum().add(plane); traverse(node, cv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); } private: const osg::Plane* mCullPlane; }; class FlipCallback : public SceneUtil::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::Vec3d eyePoint = cv->getEyePoint(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); // apply the height of the plane // we can't apply this height in the addClipPlane() since the "flip the below graph" function would otherwise flip the height as well modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); // flip the below graph if the eye point is above the plane if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) { modelViewMatrix->preMultScale(osg::Vec3(1,1,-1)); } // move the plane back along its normal a little bit to prevent bleeding at the water shore const float clipFudge = -5; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); } private: const osg::Plane* mCullPlane; }; public: ClipCullNode() { addCullCallback (new PlaneCullCallback(&mPlane)); mClipNodeTransform = new osg::Group; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); osg::Group::addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; mClipNodeTransform->addChild(mClipNode); } void setPlane (const osg::Plane& plane) { if (plane == mPlane) return; mPlane = plane; mClipNode->getClipPlaneList().clear(); mClipNode->addClipPlane(new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); mClipNode->setCullingActive(false); } private: osg::ref_ptr mClipNodeTransform; osg::ref_ptr mClipNode; osg::Plane mPlane; }; /// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not exist in OSG). /// We want to keep the View Point of the parent camera so we will not have to recreate LODs. class InheritViewPointCallback : public SceneUtil::NodeCallback { public: InheritViewPointCallback() {} void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); traverse(node, cv); } }; /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// Must be added as a Cull callback. class FudgeCallback : public SceneUtil::NodeCallback { public: void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { const float fudge = 0.2; if (std::abs(cv->getEyeLocal().z()) < fudge) { float diff = fudge - cv->getEyeLocal().z(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); if (cv->getEyeLocal().z() > 0) modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,-diff)); else modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); } else traverse(node, cv); } }; class RainIntensityUpdater : public SceneUtil::StateSetUpdater { public: RainIntensityUpdater() : mRainIntensity(0.f) { } void setRainIntensity(float rainIntensity) { mRainIntensity = rainIntensity; } protected: void setDefaults(osg::StateSet* stateset) override { osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); stateset->addUniform(rainIntensityUniform.get()); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); if (rainIntensityUniform != nullptr) rainIntensityUniform->set(mRainIntensity); } private: float mRainIntensity; }; osg::ref_ptr readPngImage (const std::string& file) { // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows boost::filesystem::ifstream inStream; inStream.open(file, std::ios_base::in | std::ios_base::binary); if (inStream.fail()) Log(Debug::Error) << "Error: Failed to open " << file; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { Log(Debug::Error) << "Error: Failed to read " << file << ", no png readerwriter found"; return osg::ref_ptr(); } osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); if (!result.success()) Log(Debug::Error) << "Error: Failed to read " << file << ": " << result.message() << " code " << result.status(); return result.getImage(); } class Refraction : public SceneUtil::RTTNode { public: Refraction(uint32_t rttSize) : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("RefractionCamera"); camera->addCullCallback(new InheritViewPointCallback); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog(new osg::Fog); fog->setStart(10000000); fog->setEnd(10000000); camera->getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override { camera->setViewMatrix(mViewMatrix); camera->setCullMask(mNodeMask); } void setScene(osg::Node* scene) { if (mScene) mClipCullNode->removeChild(mScene); mScene = scene; mClipCullNode->addChild(scene); } void setWaterLevel(float waterLevel) { const float refractionScale = std::clamp(Settings::Manager::getFloat("refraction scale", "Water"), 0.f, 1.f); mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) * osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel); mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, -1), osg::Vec3d(0, 0, waterLevel))); } void showWorld(bool show) { if (show) mNodeMask = Refraction::sDefaultCullMask; else mNodeMask = Refraction::sDefaultCullMask & ~sToggleWorldMask; } private: osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; unsigned int mNodeMask; static constexpr unsigned int sDefaultCullMask = Mask_Effect | Mask_Scene | Mask_Object | Mask_Static | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting | Mask_Groundcover; }; class Reflection : public SceneUtil::RTTNode { public: Reflection(uint32_t rttSize, bool isInterior) : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("ReflectionCamera"); camera->addCullCallback(new InheritViewPointCallback); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace(new osg::FrontFace); frontFace->setMode(osg::FrontFace::CLOCKWISE); camera->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override { camera->setViewMatrix(mViewMatrix); camera->setCullMask(mNodeMask); } void setInterior(bool isInterior) { mInterior = isInterior; mNodeMask = calcNodeMask(); } void setWaterLevel(float waterLevel) { mViewMatrix = osg::Matrix::scale(1, 1, -1) * osg::Matrix::translate(0, 0, 2 * waterLevel); mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, waterLevel))); } void setScene(osg::Node* scene) { if (mScene) mClipCullNode->removeChild(mScene); mScene = scene; mClipCullNode->addChild(scene); } void showWorld(bool show) { if (show) mNodeMask = calcNodeMask(); else mNodeMask = calcNodeMask() & ~sToggleWorldMask; } private: unsigned int calcNodeMask() { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); reflectionDetail = std::clamp(reflectionDetail, mInterior ? 2 : 0, 5); unsigned int extraMask = 0; if(reflectionDetail >= 1) extraMask |= Mask_Terrain; if(reflectionDetail >= 2) extraMask |= Mask_Static; if(reflectionDetail >= 3) extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object; if(reflectionDetail >= 4) extraMask |= Mask_Player | Mask_Actor; if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; return Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; } osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Node::NodeMask mNodeMask; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; bool mInterior; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. class DepthClampCallback : public osg::Drawable::DrawCallback { public: void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const override { static bool supported = osg::isGLExtensionOrVersionSupported(renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); if (!supported) { drawable->drawImplementation(renderInfo); return; } glEnable(GL_DEPTH_CLAMP); drawable->drawImplementation(renderInfo); // restore default glDisable(GL_DEPTH_CLAMP); } }; Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, const std::string& resourcePath) : mRainIntensityUpdater(nullptr) , mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) , mResourcePath(resourcePath) , mEnabled(true) , mToggled(true) , mTop(0) , mInterior(false) , mShowWorld(true) , mCullCallback(nullptr) , mShaderWaterStateSetUpdater(nullptr) { mSimulation = std::make_unique(mSceneRoot, resourceSystem); mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setDataVariance(osg::Object::STATIC); mWaterGeom->setName("Water Geometry"); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); mWaterNode->addChild(mWaterGeom); mWaterNode->addCullCallback(new FudgeCallback); // simple water fallback for the local map osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); geom2->setNodeMask(Mask_SimpleWater); geom2->setName("Simple Water Geometry"); mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); setHeight(mTop); updateWaterMaterial(); if (ico) ico->add(mWaterNode); } void Water::setCullCallback(osg::Callback* callback) { if (mCullCallback) { mWaterNode->removeCullCallback(mCullCallback); if (mReflection) mReflection->removeCullCallback(mCullCallback); if (mRefraction) mRefraction->removeCullCallback(mCullCallback); } mCullCallback = callback; if (callback) { mWaterNode->addCullCallback(callback); if (mReflection) mReflection->addCullCallback(callback); if (mRefraction) mRefraction->addCullCallback(callback); } } void Water::updateWaterMaterial() { if (mShaderWaterStateSetUpdater) { mWaterNode->removeCullCallback(mShaderWaterStateSetUpdater); mShaderWaterStateSetUpdater = nullptr; } if (mReflection) { mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mParent->removeChild(mRefraction); mRefraction = nullptr; } mWaterNode->setStateSet(nullptr); mWaterGeom->setStateSet(nullptr); mWaterGeom->setUpdateCallback(nullptr); if (Settings::Manager::getBool("shader", "Water")) { unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); mReflection = new Reflection(rttSize, mInterior); mReflection->setWaterLevel(mTop); mReflection->setScene(mSceneRoot); if (mCullCallback) mReflection->addCullCallback(mCullCallback); mParent->addChild(mReflection); if (Settings::Manager::getBool("refraction", "Water")) { mRefraction = new Refraction(rttSize); mRefraction->setWaterLevel(mTop); mRefraction->setScene(mSceneRoot); if (mCullCallback) mRefraction->addCullCallback(mCullCallback); mParent->addChild(mRefraction); } showWorld(mShowWorld); createShaderWaterStateSet(mWaterNode, mReflection, mRefraction); } else createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); updateVisible(); } osg::Node *Water::getReflectionNode() { return mReflection; } osg::Node* Water::getRefractionNode() { return mRefraction; } osg::Vec3d Water::getPosition() const { return mWaterNode->getPosition(); } void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); node->setStateSet(stateset); node->setUpdateCallback(nullptr); mRainIntensityUpdater = nullptr; // Add animated textures std::vector > textures; const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i=0; i tex (new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } if (textures.empty()) return; float fps = Fallback::Map::getFloat("Water_SurfaceFPS"); osg::ref_ptr controller (new NifOsg::FlipController(0, 1.f/fps, textures)); controller->setSource(std::make_shared()); node->setUpdateCallback(controller); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); // use a shader to render the simple water, ensuring that fog is applied per pixel as required. // this could be removed if a more detailed water mesh, using some sort of paging solution, is implemented. Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager(); bool oldValue = sceneManager->getForceShaders(); sceneManager->setForceShaders(true); sceneManager->recreateShaders(node); sceneManager->setForceShaders(oldValue); } class ShaderWaterStateSetUpdater : public SceneUtil::StateSetUpdater { public: ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction, osg::ref_ptr program, osg::ref_ptr normalMap) : mWater(water) , mReflection(reflection) , mRefraction(refraction) , mProgram(program) , mNormalMap(normalMap) { } void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("normalMap", 0)); stateset->setTextureAttributeAndModes(0, mNormalMap, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("reflectionMap", 1)); if (mRefraction) { stateset->addUniform(new osg::Uniform("refractionMap", 2)); stateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); stateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); } else { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); stateset->setTextureAttributeAndModes(1, mReflection->getColorTexture(cv), osg::StateAttribute::ON); if (mRefraction) { stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON); } stateset->getUniform("nodePosition")->set(osg::Vec3f(mWater->getPosition())); } private: Water* mWater; Reflection* mReflection; Refraction* mRefraction; osg::ref_ptr mProgram; osg::ref_ptr mNormalMap; }; void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) { // use a define map to conditionally compile the shader std::map defineMap; defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); Stereo::Manager::instance().shaderStereoDefines(defineMap); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader(shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); osg::ref_ptr program = shaderMgr.getProgram(vertexShader, fragmentShader); osg::ref_ptr normalMap(new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); if (normalMap->getImage()) normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, program, normalMap); node->addCullCallback(mShaderWaterStateSetUpdater); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) { updateWaterMaterial(); } Water::~Water() { mParent->removeChild(mWaterNode); if (mReflection) { mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mParent->removeChild(mRefraction); mRefraction = nullptr; } } void Water::listAssetsToPreload(std::vector &textures) { const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i=0; igetCell()->isExterior(); bool wasInterior = mInterior; if (!isInterior) { mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); mInterior = false; } else { mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); mInterior = true; } if(mInterior != wasInterior && mReflection) mReflection->setInterior(mInterior); } void Water::setHeight(const float height) { mTop = height; mSimulation->setWaterHeight(height); osg::Vec3f pos = mWaterNode->getPosition(); pos.z() = height; mWaterNode->setPosition(pos); if (mReflection) mReflection->setWaterLevel(mTop); if (mRefraction) mRefraction->setWaterLevel(mTop); } void Water::setRainIntensity(float rainIntensity) { if (mRainIntensityUpdater) mRainIntensityUpdater->setRainIntensity(rainIntensity); } void Water::update(float dt) { mSimulation->update(dt); } void Water::updateVisible() { bool visible = mEnabled && mToggled; mWaterNode->setNodeMask(visible ? ~0u : 0u); if (mRefraction) mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0u); if (mReflection) mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0u); } bool Water::toggle() { mToggled = !mToggled; updateVisible(); return mToggled; } bool Water::isUnderwater(const osg::Vec3f &pos) const { return pos.z() < mTop && mToggled && mEnabled; } osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) { return osg::Vec3f(static_cast(gridX * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), static_cast(gridY * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), mTop); } void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) { mSimulation->addEmitter (ptr, scale, force); } void Water::removeEmitter (const MWWorld::Ptr& ptr) { mSimulation->removeEmitter (ptr); } void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) { mSimulation->updateEmitterPtr(old, ptr); } void Water::emitRipple(const osg::Vec3f &pos) { mSimulation->emitRipple(pos); } void Water::removeCell(const MWWorld::CellStore *store) { mSimulation->removeCell(store); } void Water::clearRipples() { mSimulation->clear(); } void Water::showWorld(bool show) { if (mReflection) mReflection->showWorld(show); if (mRefraction) mRefraction->showWorld(show); mShowWorld = show; } } openmw-openmw-0.48.0/apps/openmw/mwrender/water.hpp000066400000000000000000000065211445372753700223660ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_WATER_H #define OPENMW_MWRENDER_WATER_H #include #include #include #include #include #include namespace osg { class Group; class PositionAttitudeTransform; class Geometry; class Node; class Callback; } namespace osgUtil { class IncrementalCompileOperation; } namespace Resource { class ResourceSystem; } namespace MWWorld { class CellStore; class Ptr; } namespace Fallback { class Map; } namespace MWRender { class Refraction; class Reflection; class RippleSimulation; class RainIntensityUpdater; /// Water rendering class Water { osg::ref_ptr mRainIntensityUpdater; osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; osg::ref_ptr mWaterNode; osg::ref_ptr mWaterGeom; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mIncrementalCompileOperation; std::unique_ptr mSimulation; osg::ref_ptr mRefraction; osg::ref_ptr mReflection; const std::string mResourcePath; bool mEnabled; bool mToggled; float mTop; bool mInterior; bool mShowWorld; osg::Callback* mCullCallback; osg::ref_ptr mShaderWaterStateSetUpdater; osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); void createSimpleWaterStateSet(osg::Node* node, float alpha); /// @param reflection the reflection camera (required) /// @param refraction the refraction camera (optional) void createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction); void updateWaterMaterial(); public: Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const std::string& resourcePath); ~Water(); void setCullCallback(osg::Callback* callback); void listAssetsToPreload(std::vector& textures); void setEnabled(bool enabled); bool toggle(); bool isUnderwater(const osg::Vec3f& pos) const; /// adds an emitter, position will be tracked automatically using its scene node void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); void removeEmitter (const MWWorld::Ptr& ptr); void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); void emitRipple(const osg::Vec3f& pos); void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); void setRainIntensity(const float rainIntensity); void update(float dt); osg::Node* getReflectionNode(); osg::Node* getRefractionNode(); osg::Vec3d getPosition() const; void processChangedSettings(const Settings::CategorySettingVector& settings); void showWorld(bool show); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwrender/weaponanimation.cpp000066400000000000000000000163451445372753700244350ustar00rootroot00000000000000#include "weaponanimation.hpp" #include #include #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/weapontype.hpp" #include "animation.hpp" #include "rotatecontroller.hpp" namespace MWRender { float WeaponAnimationTime::getValue(osg::NodeVisitor*) { if (mWeaponGroup.empty()) return 0; float current = mAnimation->getCurrentTime(mWeaponGroup); if (current == -1) return 0; return current - mStartTime; } void WeaponAnimationTime::setGroup(const std::string &group, bool relativeTime) { mWeaponGroup = group; mRelativeTime = relativeTime; if (mRelativeTime) mStartTime = mAnimation->getStartTime(mWeaponGroup); else mStartTime = 0; } void WeaponAnimationTime::updateStartTime() { setGroup(mWeaponGroup, mRelativeTime); } WeaponAnimation::WeaponAnimation() : mPitchFactor(0) { } WeaponAnimation::~WeaponAnimation() { } void WeaponAnimation::attachArrow(const MWWorld::Ptr& actor) { const MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); MWWorld::ConstContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weaponSlot == inv.end()) return; if (weaponSlot->getType() != ESM::Weapon::sRecordId) return; int type = weaponSlot->get()->mBase->mData.mType; ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown) { std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); if(!soundid.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(actor, soundid, 1.0f, 1.0f); } showWeapon(true); } else if (weapclass == ESM::WeaponType::Ranged) { osg::Group* parent = getArrowBone(); if (!parent) return; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; std::string model = ammo->getClass().getModel(*ammo); osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->getInstance(model, parent); mAmmunition = PartHolderPtr(new PartHolder(arrow)); } } void WeaponAnimation::detachArrow(MWWorld::Ptr actor) { mAmmunition.reset(); } void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end()) return; if (weapon->getType() != ESM::Weapon::sRecordId) return; // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength); if (MWMechanics::getWeaponType(weapon->get()->mBase->mData.mType)->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons get detached now osg::Node* weaponNode = getWeaponNode(); if (!weaponNode) return; osg::NodePathList nodepaths = weaponNode->getParentalNodePaths(); if (nodepaths.empty()) return; osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength; MWWorld::Ptr weaponPtr = *weapon; MWBase::Environment::get().getWorld()->launchProjectile(actor, weaponPtr, launchPos, orient, weaponPtr, speed, attackStrength); showWeapon(false); inv.remove(*weapon, 1, actor); } else { // With bows and crossbows only the used arrow/bolt gets detached MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; if (!mAmmunition) return; osg::ref_ptr ammoNode = mAmmunition->getNode(); osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); if (nodepaths.empty()) return; osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength; MWWorld::Ptr weaponPtr = *weapon; MWWorld::Ptr ammoPtr = *ammo; MWBase::Environment::get().getWorld()->launchProjectile(actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); inv.remove(ammoPtr, 1, actor); mAmmunition.reset(); } } void WeaponAnimation::addControllers(const Animation::NodeMap& nodes, std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { mSpineControllers[i] = nullptr; Animation::NodeMap::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); if (found != nodes.end()) { osg::Node* node = found->second; mSpineControllers[i] = new RotateController(objectRoot); node->addUpdateCallback(mSpineControllers[i]); map.emplace_back(node, mSpineControllers[i]); } } } void WeaponAnimation::deleteControllers() { for (int i=0; i<2; ++i) mSpineControllers[i] = nullptr; } void WeaponAnimation::configureControllers(float characterPitchRadians) { if (mPitchFactor == 0.f || characterPitchRadians == 0.f) { setControllerEnabled(false); return; } float pitch = characterPitchRadians * mPitchFactor; osg::Quat rotate (pitch/2, osg::Vec3f(-1,0,0)); setControllerRotate(rotate); setControllerEnabled(true); } void WeaponAnimation::setControllerRotate(const osg::Quat& rotate) { for (int i=0; i<2; ++i) if (mSpineControllers[i]) mSpineControllers[i]->setRotate(rotate); } void WeaponAnimation::setControllerEnabled(bool enabled) { for (int i=0; i<2; ++i) if (mSpineControllers[i]) mSpineControllers[i]->setEnabled(enabled); } } openmw-openmw-0.48.0/apps/openmw/mwrender/weaponanimation.hpp000066400000000000000000000045221445372753700244340ustar00rootroot00000000000000#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H #define OPENMW_MWRENDER_WEAPONANIMATION_H #include #include "../mwworld/ptr.hpp" #include "animation.hpp" namespace MWRender { class RotateController; class WeaponAnimationTime : public SceneUtil::ControllerSource { private: Animation* mAnimation; std::string mWeaponGroup; float mStartTime; bool mRelativeTime; public: WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0), mRelativeTime(false) {} void setGroup(const std::string& group, bool relativeTime); void updateStartTime(); float getValue(osg::NodeVisitor* nv) override; }; /// Handles attach & release of projectiles for ranged weapons class WeaponAnimation { public: WeaponAnimation(); virtual ~WeaponAnimation(); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. void attachArrow(const MWWorld::Ptr &actor); void detachArrow(MWWorld::Ptr actor); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. void releaseArrow(MWWorld::Ptr actor, float attackStrength); /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. void addControllers(const Animation::NodeMap& nodes, std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); /// Configure controllers, should be called every animation frame. void configureControllers(float characterPitchRadians); protected: PartHolderPtr mAmmunition; osg::ref_ptr mSpineControllers[2]; void setControllerRotate(const osg::Quat& rotate); void setControllerEnabled(bool enabled); virtual osg::Group* getArrowBone() = 0; virtual osg::Node* getWeaponNode() = 0; virtual Resource::ResourceSystem* getResourceSystem() = 0; virtual void showWeapon(bool show) = 0; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character, for ranged weapon aiming. float mPitchFactor; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/000077500000000000000000000000001445372753700205545ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwscript/aiextensions.cpp000066400000000000000000000657731445372753700240130ustar00rootroot00000000000000#include "aiextensions.hpp" #include #include #include #include #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/aiactivate.hpp" #include "../mwmechanics/aiescort.hpp" #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" #include "../mwmechanics/aiface.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Ai { template class OpAiActivate : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view objectID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; i class OpAiTravel : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; i class OpAiEscort : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; i(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpAiEscortCell : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; igetStore().get().search(std::string{cellID})) return; MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpGetAiPackageDone : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); bool done = false; if (ptr.getClass().isActor()) done = ptr.getClass().getCreatureStats(ptr).getAiSequence().isPackageDone(); runtime.push(done); } }; template class OpAiWander : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer range = static_cast(runtime[0].mFloat); runtime.pop(); Interpreter::Type_Integer duration = static_cast(runtime[0].mFloat); runtime.pop(); Interpreter::Type_Integer time = static_cast(runtime[0].mFloat); runtime.pop(); // Chance for Idle is unused if (arg0) { --arg0; runtime.pop(); } std::vector idleList; bool repeat = false; // Chances for Idle2-Idle9 for(int i=2; i<=9 && arg0; ++i) { if(!repeat) repeat = true; Interpreter::Type_Integer idleValue = std::clamp(runtime[0].mInteger, 0, 255); idleList.push_back(idleValue); runtime.pop(); --arg0; } if(arg0) { repeat = runtime[0].mInteger != 0; runtime.pop(); --arg0; } // discard additional arguments, because we have no idea what they mean. for (unsigned int i=0; i class OpGetAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpGetAiSetting(MWMechanics::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = 0; if (ptr.getClass().isActor()) value = ptr.getClass().getCreatureStats (ptr).getAiSetting(mIndex).getModified(false); runtime.push(value); } }; template class OpModAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpModAiSetting(MWMechanics::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (!ptr.getClass().isActor()) return; int modified = ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value; ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, modified); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified); } }; template class OpSetAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpSetAiSetting(MWMechanics::AiSetting index) : mIndex(index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if(ptr.getClass().isActor()) { ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); } } }; template class OpAiFollow : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; i class OpAiFollowCell : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i=0; i class OpGetCurrentAIPackage : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = -1; if(ptr.getClass().isActor()) { const auto& stats = ptr.getClass().getCreatureStats(ptr); if(!stats.isDead() || !stats.isDeathAnimationFinished()) { value = static_cast(stats.getAiSequence().getLastRunTypeId()); } } runtime.push (value); } }; template class OpGetDetected : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr observer = R()(runtime, false); // required=false std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); Interpreter::Type_Integer value = 0; if (!actor.isEmpty()) value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer); runtime.push (value); } }; template class OpGetLineOfSight : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr source = R()(runtime); std::string_view actorID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); bool value = false; if (!dest.isEmpty() && source.getClass().isActor() && dest.getClass().isActor()) { value = MWBase::Environment::get().getWorld()->getLOS(source,dest); } runtime.push (value); } }; template class OpGetTarget : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime &runtime) override { MWWorld::Ptr actor = R()(runtime); std::string_view testedTargetId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); bool targetsAreEqual = false; if (actor.getClass().isActor()) { const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); MWWorld::Ptr targetPtr; if (creatureStats.getAiSequence().getCombatTarget(targetPtr)) { if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } else if (testedTargetId == "player") // Currently the player ID is hardcoded { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress; bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); } } runtime.push(targetsAreEqual); } }; template class OpStartCombat : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime &runtime) override { MWWorld::Ptr actor = R()(runtime); std::string_view targetID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); if (!target.isEmpty()) MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); } }; template class OpStopCombat : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); if (!actor.getClass().isActor()) return; MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); } }; class OpToggleAI : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getMechanicsManager()->toggleAI(); runtime.getContext().report (enabled ? "AI -> On" : "AI -> Off"); } }; template class OpFace : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); if (!actor.getClass().isActor() || actor == MWMechanics::getPlayer()) return; MWMechanics::AiFace facePackage(x, y); actor.getClass().getCreatureStats(actor).getAiSequence().stack(facePackage, actor); } }; void installOpcodes(Interpreter::Interpreter& interpreter) { interpreter.installSegment3>(Compiler::Ai::opcodeAIActivate); interpreter.installSegment3>(Compiler::Ai::opcodeAIActivateExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiTravel); interpreter.installSegment3>(Compiler::Ai::opcodeAiTravelExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscort); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCell); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCellExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiWander); interpreter.installSegment3>(Compiler::Ai::opcodeAiWanderExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollow); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCell); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCellExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDone); interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDoneExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackage); interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackageExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetDetected); interpreter.installSegment5>(Compiler::Ai::opcodeGetDetectedExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSight); interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSightExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetTarget); interpreter.installSegment5>(Compiler::Ai::opcodeGetTargetExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeStartCombat); interpreter.installSegment5>(Compiler::Ai::opcodeStartCombatExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeStopCombat); interpreter.installSegment5>(Compiler::Ai::opcodeStopCombatExplicit); interpreter.installSegment5(Compiler::Ai::opcodeToggleAI); interpreter.installSegment5>(Compiler::Ai::opcodeSetHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeSetHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeSetFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeSetFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeSetFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeSetFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeSetAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeSetAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeModHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeModHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeModFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeModFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeModFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeModFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeModAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeModAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeGetHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeGetHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>(Compiler::Ai::opcodeGetFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeGetFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>(Compiler::Ai::opcodeGetFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeGetFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>(Compiler::Ai::opcodeGetAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeGetAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeFace); interpreter.installSegment5>(Compiler::Ai::opcodeFaceExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/aiextensions.hpp000066400000000000000000000005271445372753700240020ustar00rootroot00000000000000#ifndef GAME_SCRIPT_AIEXTENSIONS_H #define GAME_SCRIPT_AIEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief AI-related script functionality namespace Ai { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/animationextensions.cpp000066400000000000000000000074331445372753700253660ustar00rootroot00000000000000#include "animationextensions.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Animation { template class OpSkipAnim : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getMechanicsManager()->skipAnimation (ptr); } }; template class OpPlayAnim : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.getRefData().isEnabled()) return; std::string_view group = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer mode = 0; if (arg0==1) { mode = runtime[0].mInteger; runtime.pop(); if (mode<0 || mode>2) throw std::runtime_error ("animation mode out of range"); } MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, std::string{group}, mode, std::numeric_limits::max(), true); } }; template class OpLoopAnim : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.getRefData().isEnabled()) return; std::string_view group = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer loops = runtime[0].mInteger; runtime.pop(); if (loops<0) throw std::runtime_error ("number of animation loops must be non-negative"); Interpreter::Type_Integer mode = 0; if (arg0==1) { mode = runtime[0].mInteger; runtime.pop(); if (mode<0 || mode>2) throw std::runtime_error ("animation mode out of range"); } MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, std::string{group}, mode, loops + 1, true); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnim); interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnimExplicit); interpreter.installSegment3>(Compiler::Animation::opcodePlayAnim); interpreter.installSegment3>(Compiler::Animation::opcodePlayAnimExplicit); interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnim); interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnimExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/animationextensions.hpp000066400000000000000000000006021445372753700253620ustar00rootroot00000000000000#ifndef GAME_SCRIPT_ANIMATIONEXTENSIONS_H #define GAME_SCRIPT_ANIMATIONEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { namespace Animation { void registerExtensions (Compiler::Extensions& extensions); void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/cellextensions.cpp000066400000000000000000000234061445372753700243240ustar00rootroot00000000000000#include "cellextensions.hpp" #include #include "../mwworld/esmstore.hpp" #include #include #include #include #include "../mwworld/actionteleport.hpp" #include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" namespace MWScript { namespace Cell { class OpCellChanged : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWorld()->hasCellChanged() ? 1 : 0); } }; class OpTestCells : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { runtime.getContext().report("Use TestCells from the main menu, when there is no active game session."); return; } bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); if (wasConsole) MWBase::Environment::get().getWindowManager()->toggleConsole(); MWBase::Environment::get().getWorld()->testExteriorCells(); if (wasConsole) MWBase::Environment::get().getWindowManager()->toggleConsole(); } }; class OpTestInteriorCells : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { runtime.getContext().report("Use TestInteriorCells from the main menu, when there is no active game session."); return; } bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); if (wasConsole) MWBase::Environment::get().getWindowManager()->toggleConsole(); MWBase::Environment::get().getWorld()->testInteriorCells(); if (wasConsole) MWBase::Environment::get().getWindowManager()->toggleConsole(); } }; class OpCOC : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string cell{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); ESM::Position pos; MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Ptr playerPtr = world->getPlayerPtr(); if (world->findExteriorPosition(cell, pos)) { MWWorld::ActionTeleport("", pos, false).execute(playerPtr); world->adjustPosition(playerPtr, false); } else { // Change to interior even if findInteriorPosition() // yields false. In this case position will be zero-point. world->findInteriorPosition(cell, pos); MWWorld::ActionTeleport(cell, pos, false).execute(playerPtr); } } }; class OpCOE : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Integer x = runtime[0].mInteger; runtime.pop(); Interpreter::Type_Integer y = runtime[0].mInteger; runtime.pop(); ESM::Position pos; MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Ptr playerPtr = world->getPlayerPtr(); world->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); pos.pos[2] = 0; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; MWWorld::ActionTeleport("", pos, false).execute(playerPtr); world->adjustPosition(playerPtr, false); } }; class OpGetInterior : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { if (!MWMechanics::getPlayer().isInCell()) { runtime.push (0); return; } bool interior = !MWMechanics::getPlayer().getCell()->getCell()->isExterior(); runtime.push (interior ? 1 : 0); } }; class OpGetPCCell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0); return; } const MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); Misc::StringUtils::lowerCaseInPlace(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; runtime.push (match ? 1 : 0); } }; class OpGetWaterLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0.f); return; } MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->isExterior()) runtime.push(0.f); // vanilla oddity, return 0 even though water is actually at -1 else if (cell->getCell()->hasWater()) runtime.push (cell->getWaterLevel()); else runtime.push (-std::numeric_limits::max()); } }; class OpSetWaterLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float level = runtime[0].mFloat; runtime.pop(); if (!MWMechanics::getPlayer().isInCell()) { return; } MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); cell->setWaterLevel (level); MWBase::Environment::get().getWorld()->setWaterHeight (cell->getWaterLevel()); } }; class OpModWaterLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float level = runtime[0].mFloat; runtime.pop(); if (!MWMechanics::getPlayer().isInCell()) { return; } MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); cell->setWaterLevel (cell->getWaterLevel()+level); MWBase::Environment::get().getWorld()->setWaterHeight(cell->getWaterLevel()); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Cell::opcodeCellChanged); interpreter.installSegment5(Compiler::Cell::opcodeTestCells); interpreter.installSegment5(Compiler::Cell::opcodeTestInteriorCells); interpreter.installSegment5(Compiler::Cell::opcodeCOC); interpreter.installSegment5(Compiler::Cell::opcodeCOE); interpreter.installSegment5(Compiler::Cell::opcodeGetInterior); interpreter.installSegment5(Compiler::Cell::opcodeGetPCCell); interpreter.installSegment5(Compiler::Cell::opcodeGetWaterLevel); interpreter.installSegment5(Compiler::Cell::opcodeSetWaterLevel); interpreter.installSegment5(Compiler::Cell::opcodeModWaterLevel); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/cellextensions.hpp000066400000000000000000000005511445372753700243250ustar00rootroot00000000000000#ifndef GAME_SCRIPT_CELLEXTENSIONS_H #define GAME_SCRIPT_CELLEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief cell-related script functionality namespace Cell { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/compilercontext.cpp000066400000000000000000000056151445372753700245060ustar00rootroot00000000000000#include "compilercontext.hpp" #include "../mwworld/esmstore.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" namespace MWScript { CompilerContext::CompilerContext (Type type) : mType (type) {} bool CompilerContext::canDeclareLocals() const { return mType==Type_Full; } char CompilerContext::getGlobalType (const std::string& name) const { return MWBase::Environment::get().getWorld()->getGlobalVariableType (name); } std::pair CompilerContext::getMemberType (const std::string& name, const std::string& id) const { std::string script; bool reference = false; if (const ESM::Script *scriptRecord = MWBase::Environment::get().getWorld()->getStore().get().search (id)) { script = scriptRecord->mId; } else { MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); script = ref.getPtr().getClass().getScript (ref.getPtr()); reference = true; } char type = ' '; if (!script.empty()) type = MWBase::Environment::get().getScriptManager()->getLocals (script).getType ( Misc::StringUtils::lowerCase (name)); return std::make_pair (type, reference); } bool CompilerContext::isId (const std::string& name) const { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); return store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name) || store.get().search (name); } } openmw-openmw-0.48.0/apps/openmw/mwscript/compilercontext.hpp000066400000000000000000000024111445372753700245020ustar00rootroot00000000000000#ifndef GAME_SCRIPT_COMPILERCONTEXT_H #define GAME_SCRIPT_COMPILERCONTEXT_H #include namespace MWScript { class CompilerContext : public Compiler::Context { public: enum Type { Type_Full, // global, local, targeted Type_Dialogue, Type_Console }; private: Type mType; public: CompilerContext (Type type); /// Is the compiler allowed to declare local variables? bool canDeclareLocals() const override; /// 'l: long, 's': short, 'f': float, ' ': does not exist. char getGlobalType (const std::string& name) const override; std::pair getMemberType (const std::string& name, const std::string& id) const override; ///< Return type of member variable \a name in script \a id or in script of reference of /// \a id /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. /// second: true: script of reference bool isId (const std::string& name) const override; ///< Does \a name match an ID, that can be referenced? }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/consoleextensions.cpp000066400000000000000000000003471445372753700250460ustar00rootroot00000000000000#include "consoleextensions.hpp" #include namespace MWScript { namespace Console { void installOpcodes (Interpreter::Interpreter& interpreter) { } } } openmw-openmw-0.48.0/apps/openmw/mwscript/consoleextensions.hpp000066400000000000000000000005621445372753700250520ustar00rootroot00000000000000#ifndef GAME_SCRIPT_CONSOLEEXTENSIONS_H #define GAME_SCRIPT_CONSOLEEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief Script functionality limited to the console namespace Console { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/containerextensions.cpp000066400000000000000000000601331445372753700253650ustar00rootroot00000000000000#include "containerextensions.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwclass/container.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/levelledlist.hpp" #include "ref.hpp" namespace { void addToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& ptr, MWWorld::ContainerStore& store, bool resolve = true) { if (itemPtr.getClass().getScript(itemPtr).empty()) { store.add (itemPtr, count, ptr, true, resolve); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < count; i++) store.add (itemPtr, 1, ptr, true, resolve); } } void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& owner, MWWorld::ContainerStore& store, bool topLevel = true) { if(itemPtr.getType() == ESM::ItemLevList::sRecordId) { const ESM::ItemLevList* levItemList = itemPtr.get()->mBase; if(topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for(int i = 0; i < count; i++) addRandomToStore(itemPtr, 1, owner, store, true); } else { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); std::string itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false, prng); if (itemId.empty()) return; MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1); addRandomToStore(manualRef.getPtr(), count, owner, store, false); } } else addToStore(itemPtr, count, owner, store); } } namespace MWScript { namespace Container { template class OpAddItem : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); if (count<0) count = static_cast(count); // no-op if (count == 0) return; if(::Misc::StringUtils::ciEqual(item, "gold_005") || ::Misc::StringUtils::ciEqual(item, "gold_010") || ::Misc::StringUtils::ciEqual(item, "gold_025") || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; // Check if "item" can be placed in a container MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), item, 1); MWWorld::Ptr itemPtr = manualRef.getPtr(); bool isLevelledList = itemPtr.getClass().getType() == ESM::ItemLevList::sRecordId; if(!isLevelledList) MWWorld::ContainerStore::getType(itemPtr); // Explicit calls to non-unique actors affect the base record if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), std::string{item}, count); return; } // Calls to unresolved containers affect the base record if(ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), std::string{item}, count); const ESM::Container* baseRecord = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getCellRef().getRefId()); const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); for(const auto& container : ptrs) { // use the new base record container.get()->mBase = baseRecord; if(container.getRefData().getCustomData()) { auto& store = container.getClass().getContainerStore(container); if(isLevelledList) { if(store.isResolved()) { addRandomToStore(itemPtr, count, ptr, store); } } else addToStore(itemPtr, count, ptr, store, store.isResolved()); } } return; } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); if(isLevelledList) addRandomToStore(itemPtr, count, ptr, store); else addToStore(itemPtr, count, ptr, store); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() ) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory std::string msgBox; std::string itemName = itemPtr.getClass().getName(itemPtr); if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); msgBox = ::Misc::StringUtils::format(msgBox, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); msgBox = ::Misc::StringUtils::format(msgBox, count, itemName); } MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; template class OpGetItemCount : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if(::Misc::StringUtils::ciEqual(item, "gold_005") || ::Misc::StringUtils::ciEqual(item, "gold_010") || ::Misc::StringUtils::ciEqual(item, "gold_025") || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); runtime.push (store.count(item)); } }; template class OpRemoveItem : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); if (count<0) count = static_cast(count); // no-op if (count == 0) return; if(::Misc::StringUtils::ciEqual(item, "gold_005") || ::Misc::StringUtils::ciEqual(item, "gold_010") || ::Misc::StringUtils::ciEqual(item, "gold_025") || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; // Explicit calls to non-unique actors affect the base record if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), std::string{item}, -count); return; } // Calls to unresolved containers affect the base record instead else if(ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), std::string{item}, -count); const ESM::Container* baseRecord = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getCellRef().getRefId()); const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); for(const auto& container : ptrs) { container.get()->mBase = baseRecord; if(container.getRefData().getCustomData()) { auto& store = container.getClass().getContainerStore(container); // Note that unlike AddItem, RemoveItem only removes from unresolved containers if(!store.isResolved()) store.remove(item, count, ptr, false, false); } } return; } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) { if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) { itemName = iter->getClass().getName(*iter); break; } } int numRemoved = store.remove(item, count, ptr); // Spawn a messagebox (only for items removed from player's inventory) if ((numRemoved > 0) && (ptr == MWMechanics::getPlayer())) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory std::string msgBox; if (numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); msgBox = ::Misc::StringUtils::format(msgBox, numRemoved, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); msgBox = ::Misc::StringUtils::format(msgBox, itemName); } MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; template class OpEquip : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ContainerStoreIterator it = invStore.begin(); // With soul gems we prefer filled ones. const std::string soulgemPrefix = "misc_soulgem"; if (::Misc::StringUtils::ciCompareLen(item, soulgemPrefix, soulgemPrefix.length()) == 0) { it = invStore.end(); for (auto it_any = invStore.begin(); it_any != invStore.end(); ++it_any) { if (::Misc::StringUtils::ciEqual(it_any->getCellRef().getRefId(), item)) { if (!it_any->getCellRef().getSoul().empty() && MWBase::Environment::get().getWorld()->getStore().get().search(it_any->getCellRef().getSoul())) { it = it_any; break; } else if (it == invStore.end()) it = it_any; } } } else { for (; it != invStore.end(); ++it) { if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) break; } } if (it == invStore.end()) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), item, 1); it = ptr.getClass().getContainerStore (ptr).add (ref.getPtr(), 1, ptr, false); Log(Debug::Warning) << "Implicitly adding one " << item << " to the inventory store of " << ptr.getCellRef().getRefId() << " to fulfill the requirements of Equip instruction"; } if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->useItem(*it, true); else { std::unique_ptr action = it->getClass().use(*it, true); action->execute(ptr, true); } } }; template class OpGetArmorType : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer location = runtime[0].mInteger; runtime.pop(); int slot; switch (location) { case 0: slot = MWWorld::InventoryStore::Slot_Helmet; break; case 1: slot = MWWorld::InventoryStore::Slot_Cuirass; break; case 2: slot = MWWorld::InventoryStore::Slot_LeftPauldron; break; case 3: slot = MWWorld::InventoryStore::Slot_RightPauldron; break; case 4: slot = MWWorld::InventoryStore::Slot_Greaves; break; case 5: slot = MWWorld::InventoryStore::Slot_Boots; break; case 6: slot = MWWorld::InventoryStore::Slot_LeftGauntlet; break; case 7: slot = MWWorld::InventoryStore::Slot_RightGauntlet; break; case 8: slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield break; case 9: slot = MWWorld::InventoryStore::Slot_LeftGauntlet; break; case 10: slot = MWWorld::InventoryStore::Slot_RightGauntlet; break; default: throw std::runtime_error ("armor index out of range"); } const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot); if (it == invStore.end() || it->getType () != ESM::Armor::sRecordId) { runtime.push(-1); return; } int skill = it->getClass().getEquipmentSkill (*it) ; if (skill == ESM::Skill::HeavyArmor) runtime.push(2); else if (skill == ESM::Skill::MediumArmor) runtime.push(1); else if (skill == ESM::Skill::LightArmor) runtime.push(0); else runtime.push(-1); } }; template class OpHasItemEquipped : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot); if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { runtime.push(1); return; } } runtime.push(0); } }; template class OpHasSoulGem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int count = 0; const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous); it != invStore.cend(); ++it) { if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name)) count += it->getRefData().getCount(); } runtime.push(count); } }; template class OpGetWeaponType : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight); if (it == invStore.end()) { runtime.push(-1); return; } else if (it->getType() != ESM::Weapon::sRecordId) { if (it->getType() == ESM::Lockpick::sRecordId) { runtime.push(-2); } else if (it->getType() == ESM::Probe::sRecordId) { runtime.push(-3); } else { runtime.push(-1); } return; } runtime.push(it->get()->mBase->mData.mType); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Container::opcodeAddItem); interpreter.installSegment5>(Compiler::Container::opcodeAddItemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetItemCount); interpreter.installSegment5>(Compiler::Container::opcodeGetItemCountExplicit); interpreter.installSegment5>(Compiler::Container::opcodeRemoveItem); interpreter.installSegment5>(Compiler::Container::opcodeRemoveItemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeEquip); interpreter.installSegment5>(Compiler::Container::opcodeEquipExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetArmorType); interpreter.installSegment5>(Compiler::Container::opcodeGetArmorTypeExplicit); interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquipped); interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquippedExplicit); interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGem); interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponType); interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponTypeExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/containerextensions.hpp000066400000000000000000000006151445372753700253710ustar00rootroot00000000000000#ifndef GAME_SCRIPT_CONTAINEREXTENSIONS_H #define GAME_SCRIPT_CONTAINEREXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief Container-related script functionality (chests, NPCs, creatures) namespace Container { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/controlextensions.cpp000066400000000000000000000251271445372753700250670ustar00rootroot00000000000000#include "controlextensions.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwmechanics/creaturestats.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Control { class OpSetControl : public Interpreter::Opcode0 { std::string mControl; bool mEnable; public: OpSetControl (const std::string& control, bool enable) : mControl (control), mEnable (enable) {} void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get() .getInputManager() ->toggleControlSwitch(mControl, mEnable); } }; class OpGetDisabled : public Interpreter::Opcode0 { std::string mControl; public: OpGetDisabled (const std::string& control) : mControl (control) {} void execute (Interpreter::Runtime& runtime) override { runtime.push(!MWBase::Environment::get().getInputManager()->getControlSwitch (mControl)); } }; class OpToggleCollision : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleCollisionMode(); runtime.getContext().report (enabled ? "Collision -> On" : "Collision -> Off"); } }; template class OpClearMovementFlag : public Interpreter::Opcode0 { MWMechanics::CreatureStats::Flag mFlag; public: OpClearMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, false); } }; template class OpSetMovementFlag : public Interpreter::Opcode0 { MWMechanics::CreatureStats::Flag mFlag; public: OpSetMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, true); } }; template class OpGetForceRun : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); } }; template class OpGetForceJump : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); } }; template class OpGetForceMoveJump : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); } }; template class OpGetForceSneak : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); } }; class OpGetPcRunning : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWBase::World* world = MWBase::Environment::get().getWorld(); bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); runtime.push(stanceOn && (running || inair)); } }; class OpGetPcSneaking : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); } }; void installOpcodes(Interpreter::Interpreter& interpreter) { for (int i = 0; i < Compiler::Control::numberOfControls; ++i) { interpreter.installSegment5(Compiler::Control::opcodeEnable + i, Compiler::Control::controls[i], true); interpreter.installSegment5(Compiler::Control::opcodeDisable + i, Compiler::Control::controls[i], false); interpreter.installSegment5(Compiler::Control::opcodeGetDisabled + i, Compiler::Control::controls[i]); } interpreter.installSegment5(Compiler::Control::opcodeToggleCollision); //Force Run interpreter.installSegment5>(Compiler::Control::opcodeClearForceRun, MWMechanics::CreatureStats::Flag_ForceRun); interpreter.installSegment5>(Compiler::Control::opcodeClearForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); interpreter.installSegment5>(Compiler::Control::opcodeForceRun, MWMechanics::CreatureStats::Flag_ForceRun); interpreter.installSegment5>(Compiler::Control::opcodeForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); //Force Jump interpreter.installSegment5>(Compiler::Control::opcodeClearForceJump, MWMechanics::CreatureStats::Flag_ForceJump); interpreter.installSegment5>(Compiler::Control::opcodeClearForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); interpreter.installSegment5>(Compiler::Control::opcodeForceJump, MWMechanics::CreatureStats::Flag_ForceJump); interpreter.installSegment5>(Compiler::Control::opcodeForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); //Force MoveJump interpreter.installSegment5>(Compiler::Control::opcodeClearForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); interpreter.installSegment5>(Compiler::Control::opcodeClearForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); interpreter.installSegment5>(Compiler::Control::opcodeForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); interpreter.installSegment5>(Compiler::Control::opcodeForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); //Force Sneak interpreter.installSegment5>(Compiler::Control::opcodeClearForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); interpreter.installSegment5>(Compiler::Control::opcodeClearForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); interpreter.installSegment5>(Compiler::Control::opcodeForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); interpreter.installSegment5>(Compiler::Control::opcodeForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); interpreter.installSegment5(Compiler::Control::opcodeGetPcRunning); interpreter.installSegment5(Compiler::Control::opcodeGetPcSneaking); interpreter.installSegment5>(Compiler::Control::opcodeGetForceRun); interpreter.installSegment5>(Compiler::Control::opcodeGetForceRunExplicit); interpreter.installSegment5>(Compiler::Control::opcodeGetForceJump); interpreter.installSegment5>(Compiler::Control::opcodeGetForceJumpExplicit); interpreter.installSegment5>(Compiler::Control::opcodeGetForceMoveJump); interpreter.installSegment5>(Compiler::Control::opcodeGetForceMoveJumpExplicit); interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneak); interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneakExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/controlextensions.hpp000066400000000000000000000005631445372753700250710ustar00rootroot00000000000000#ifndef GAME_SCRIPT_CONTROLEXTENSIONS_H #define GAME_SCRIPT_CONTROLEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief player controls-related script functionality namespace Control { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/dialogueextensions.cpp000066400000000000000000000273261445372753700252030ustar00rootroot00000000000000#include "dialogueextensions.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Dialogue { template class OpJournal : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime, false); // required=false if (ptr.isEmpty()) ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); std::string quest{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); Interpreter::Type_Integer index = runtime[0].mInteger; runtime.pop(); // Invoking Journal with a non-existing index is allowed, and triggers no errors. Seriously? :( try { MWBase::Environment::get().getJournal()->addEntry (quest, index, ptr); } catch (...) { if (MWBase::Environment::get().getJournal()->getJournalIndex(quest) < index) MWBase::Environment::get().getJournal()->setJournalIndex(quest, index); } } }; class OpSetJournalIndex : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string quest{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); Interpreter::Type_Integer index = runtime[0].mInteger; runtime.pop(); MWBase::Environment::get().getJournal()->setJournalIndex (quest, index); } }; class OpGetJournalIndex : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string quest{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); int index = MWBase::Environment::get().getJournal()->getJournalIndex (quest); runtime.push (index); } }; class OpAddTopic : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view topic = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getDialogueManager()->addTopic(topic); } }; class OpChoice : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWBase::DialogueManager* dialogue = MWBase::Environment::get().getDialogueManager(); while(arg0>0) { std::string_view question = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); arg0 = arg0 -1; Interpreter::Type_Integer choice = 1; if(arg0>0) { choice = runtime[0].mInteger; runtime.pop(); arg0 = arg0 -1; } dialogue->addChoice(question,choice); } } }; template class OpForceGreeting : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.getRefData().isEnabled()) return; if (!ptr.getClass().isActor()) { const std::string error = "Warning: \"forcegreeting\" command works only for actors."; runtime.getContext().report(error); Log(Debug::Warning) << error; return; } MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); } }; class OpGoodbye : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWBase::Environment::get().getDialogueManager()->goodbye(); } }; template class OpModReputation : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ptr.getClass().getNpcStats (ptr).setReputation (ptr.getClass().getNpcStats (ptr).getReputation () + value); } }; template class OpSetReputation : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ptr.getClass().getNpcStats (ptr).setReputation (value); } }; template class OpGetReputation : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getClass().getNpcStats (ptr).getReputation ()); } }; template class OpSameFaction : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); runtime.push(player.getClass().getNpcStats (player).isInFaction(ptr.getClass().getPrimaryFaction(ptr))); } }; class OpModFactionReaction : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view faction1 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string_view faction2 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int modReaction = runtime[0].mInteger; runtime.pop(); MWBase::Environment::get().getDialogueManager()->modFactionReaction(faction1, faction2, modReaction); } }; class OpGetFactionReaction : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view faction1 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string_view faction2 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); runtime.push(MWBase::Environment::get().getDialogueManager() ->getFactionReaction(faction1, faction2)); } }; class OpSetFactionReaction : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view faction1 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string_view faction2 = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int newValue = runtime[0].mInteger; runtime.pop(); MWBase::Environment::get().getDialogueManager()->setFactionReaction(faction1, faction2, newValue); } }; template class OpClearInfoActor : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getDialogueManager()->clearInfoActor(ptr); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Dialogue::opcodeJournal); interpreter.installSegment5>(Compiler::Dialogue::opcodeJournalExplicit); interpreter.installSegment5(Compiler::Dialogue::opcodeSetJournalIndex); interpreter.installSegment5(Compiler::Dialogue::opcodeGetJournalIndex); interpreter.installSegment5(Compiler::Dialogue::opcodeAddTopic); interpreter.installSegment3(Compiler::Dialogue::opcodeChoice); interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreeting); interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreetingExplicit); interpreter.installSegment5(Compiler::Dialogue::opcodeGoodbye); interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputation); interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputation); interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputation); interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputationExplicit); interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputationExplicit); interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputationExplicit); interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFaction); interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFactionExplicit); interpreter.installSegment5(Compiler::Dialogue::opcodeModFactionReaction); interpreter.installSegment5(Compiler::Dialogue::opcodeSetFactionReaction); interpreter.installSegment5(Compiler::Dialogue::opcodeGetFactionReaction); interpreter.installSegment5>(Compiler::Dialogue::opcodeClearInfoActor); interpreter.installSegment5>(Compiler::Dialogue::opcodeClearInfoActorExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/dialogueextensions.hpp000066400000000000000000000005671445372753700252060ustar00rootroot00000000000000#ifndef GAME_SCRIPT_DIALOGUEEXTENSIONS_H #define GAME_SCRIPT_DIALOGUEEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief Dialogue/Journal-related script functionality namespace Dialogue { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/docs/000077500000000000000000000000001445372753700215045ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwscript/docs/vmformat.txt000066400000000000000000000366431445372753700241140ustar00rootroot00000000000000OpenMW Extensions: Segment 0: (not implemented yet) opcodes 0x20-0x3f unused Segment 1: (not implemented yet) opcodes 0x20-0x3f unused Segment 2: (not implemented yet) opcodes 0x200-0x3ff unused Segment 3: op 0x20000: AiTravel op 0x20001: AiTravel, explicit reference op 0x20002: AiEscort op 0x20003: AiEscort, explicit reference op 0x20004: Lock op 0x20005: Lock, explicit reference op 0x20006: PlayAnim op 0x20007: PlayAnim, explicit reference op 0x20008: LoopAnim op 0x20009: LoopAnim, explicit reference op 0x2000a: Choice op 0x2000b: PCRaiseRank op 0x2000c: PCLowerRank op 0x2000d: PCJoinFaction op 0x2000e: PCGetRank implicit op 0x2000f: PCGetRank explicit op 0x20010: AiWander op 0x20011: AiWander, explicit reference op 0x20012: GetPCFacRep op 0x20013: GetPCFacRep, explicit reference op 0x20014: SetPCFacRep op 0x20015: SetPCFacRep, explicit reference op 0x20016: ModPCFacRep op 0x20017: ModPCFacRep, explicit reference op 0x20018: PcExpelled op 0x20019: PcExpelled, explicit op 0x2001a: PcExpell op 0x2001b: PcExpell, explicit op 0x2001c: PcClearExpelled op 0x2001d: PcClearExpelled, explicit op 0x2001e: AIActivate op 0x2001f: AIActivate, explicit reference op 0x20020: AiEscortCell op 0x20021: AiEscortCell, explicit reference op 0x20022: AiFollow op 0x20023: AiFollow, explicit reference op 0x20024: AiFollowCell op 0x20025: AiFollowCell, explicit reference op 0x20026: ModRegion op 0x20027: RemoveSoulGem op 0x20028: RemoveSoulGem, explicit reference op 0x20029: PCRaiseRank, explicit reference op 0x2002a: PCLowerRank, explicit reference op 0x2002b: PCJoinFaction, explicit reference op 0x2002c: MenuTest op 0x2002d: BetaComment op 0x2002e: BetaComment, explicit reference op 0x2002f: ShowSceneGraph op 0x20030: ShowSceneGraph, explicit opcodes 0x20031-0x3ffff unused Segment 4: (not implemented yet) opcodes 0x200-0x3ff unused Segment 5: op 0x2000000: CellChanged op 0x2000001: Say op 0x2000002: SayDone op 0x2000003: StreamMusic op 0x2000004: PlaySound op 0x2000005: PlaySoundVP op 0x2000006: PlaySound3D op 0x2000007: PlaySound3DVP op 0x2000008: PlayLoopSound3D op 0x2000009: PlayLoopSound3DVP op 0x200000a: StopSound op 0x200000b: GetSoundPlaying op 0x200000c: XBox (always 0) op 0x200000d: OnActivate op 0x200000e: EnableBirthMenu op 0x200000f: EnableClassMenu op 0x2000010: EnableNameMenu op 0x2000011: EnableRaceMenu op 0x2000012: EnableStatsReviewMenu op 0x2000013: EnableInventoryMenu op 0x2000014: EnableMagicMenu op 0x2000015: EnableMapMenu op 0x2000016: EnableStatsMenu op 0x2000017: EnableRest op 0x2000018: ShowRestMenu op 0x2000019: Say, explicit reference op 0x200001a: SayDone, explicit reference op 0x200001b: PlaySound3D, explicit reference op 0x200001c: PlaySound3DVP, explicit reference op 0x200001d: PlayLoopSound3D, explicit reference op 0x200001e: PlayLoopSound3DVP, explicit reference op 0x200001f: StopSound, explicit reference op 0x2000020: GetSoundPlaying, explicit reference op 0x2000021: ToggleSky op 0x2000022: TurnMoonWhite op 0x2000023: TurnMoonRed op 0x2000024: GetMasserPhase op 0x2000025: GetSecundaPhase op 0x2000026: COC op 0x2000027-0x200002e: GetAttribute op 0x200002f-0x2000036: GetAttribute, explicit reference op 0x2000037-0x200003e: SetAttribute op 0x200003f-0x2000046: SetAttribute, explicit reference op 0x2000047-0x200004e: ModAttribute op 0x200004f-0x2000056: ModAttribute, explicit reference op 0x2000057-0x2000059: GetDynamic (health, magicka, fatigue) op 0x200005a-0x200005c: GetDynamic (health, magicka, fatigue), explicit reference op 0x200005d-0x200005f: SetDynamic (health, magicka, fatigue) op 0x2000060-0x2000062: SetDynamic (health, magicka, fatigue), explicit reference op 0x2000063-0x2000065: ModDynamic (health, magicka, fatigue) op 0x2000066-0x2000068: ModDynamic (health, magicka, fatigue), explicit reference op 0x2000069-0x200006b: ModDynamic (health, magicka, fatigue) op 0x200006c-0x200006e: ModDynamic (health, magicka, fatigue), explicit reference op 0x200006f-0x2000071: GetDynamic (health, magicka, fatigue) op 0x2000072-0x2000074: GetDynamic (health, magicka, fatigue), explicit reference op 0x2000075: Activate op 0x2000076: AddItem op 0x2000077: AddItem, explicit reference op 0x2000078: GetItemCount op 0x2000079: GetItemCount, explicit reference op 0x200007a: RemoveItem op 0x200007b: RemoveItem, explicit reference op 0x200007c: GetAiPackageDone op 0x200007d: GetAiPackageDone, explicit reference op 0x200007e-0x2000084: Enable Controls op 0x2000085-0x200008b: Disable Controls op 0x200008c: Unlock op 0x200008d: Unlock, explicit reference op 0x200008e-0x20000a8: GetSkill op 0x20000a9-0x20000c3: GetSkill, explicit reference op 0x20000c4-0x20000de: SetSkill op 0x20000df-0x20000f9: SetSkill, explicit reference op 0x20000fa-0x2000114: ModSkill op 0x2000115-0x200012f: ModSKill, explicit reference op 0x2000130: ToggleCollision op 0x2000131: GetInterior op 0x2000132: ToggleCollsionDebug op 0x2000133: Journal op 0x2000134: SetJournalIndex op 0x2000135: GetJournalIndex op 0x2000136: GetPCCell op 0x2000137: GetButtonPressed op 0x2000138: SkipAnim op 0x2000139: SkipAnim, expplicit reference op 0x200013a: AddTopic op 0x200013b: twf op 0x200013c: FadeIn op 0x200013d: FadeOut op 0x200013e: FadeTo op 0x200013f: GetCurrentWeather op 0x2000140: ChangeWeather op 0x2000141: GetWaterLevel op 0x2000142: SetWaterLevel op 0x2000143: ModWaterLevel op 0x2000144: ToggleWater, twa op 0x2000145: ToggleFogOfWar (tfow) op 0x2000146: TogglePathgrid op 0x2000147: AddSpell op 0x2000148: AddSpell, explicit reference op 0x2000149: RemoveSpell op 0x200014a: RemoveSpell, explicit reference op 0x200014b: GetSpell op 0x200014c: GetSpell, explicit reference op 0x200014d: ModDisposition op 0x200014e: ModDisposition, explicit reference op 0x200014f: ForceGreeting op 0x2000150: ForceGreeting, explicit reference op 0x2000151: ToggleFullHelp op 0x2000152: Goodbye op 0x2000153: DontSaveObject (left unimplemented) op 0x2000154: ClearForceRun op 0x2000155: ClearForceRun, explicit reference op 0x2000156: ForceRun op 0x2000157: ForceRun, explicit reference op 0x2000158: ClearForceSneak op 0x2000159: ClearForceSneak, explicit reference op 0x200015a: ForceSneak op 0x200015b: ForceSneak, explicit reference op 0x200015c: SetHello op 0x200015d: SetHello, explicit reference op 0x200015e: SetFight op 0x200015f: SetFight, explicit reference op 0x2000160: SetFlee op 0x2000161: SetFlee, explicit reference op 0x2000162: SetAlarm op 0x2000163: SetAlarm, explicit reference op 0x2000164: SetScale op 0x2000165: SetScale, explicit reference op 0x2000166: SetAngle op 0x2000167: SetAngle, explicit reference op 0x2000168: GetScale op 0x2000169: GetScale, explicit reference op 0x200016a: GetAngle op 0x200016b: GetAngle, explicit reference op 0x200016c: user1 (console only, requires --script-console switch) op 0x200016d: user2 (console only, requires --script-console switch) op 0x200016e: user3, explicit reference (console only, requires --script-console switch) op 0x200016f: user3 (implicit reference, console only, requires --script-console switch) op 0x2000170: user4, explicit reference (console only, requires --script-console switch) op 0x2000171: user4 (implicit reference, console only, requires --script-console switch) op 0x2000172: GetStartingAngle op 0x2000173: GetStartingAngle, explicit reference op 0x2000174: ToggleVanityMode op 0x2000175-0x200018B: Get controls disabled op 0x200018C: GetLevel op 0x200018D: GetLevel, explicit reference op 0x200018E: SetLevel op 0x200018F: SetLevel, explicit reference op 0x2000190: GetPos op 0x2000191: GetPosExplicit op 0x2000192: SetPos op 0x2000193: SetPosExplicit op 0x2000194: GetStartingPos op 0x2000195: GetStartingPosExplicit op 0x2000196: Position op 0x2000197: Position Explicit op 0x2000198: PositionCell op 0x2000199: PositionCell Explicit op 0x200019a: PlaceItemCell op 0x200019b: PlaceItem op 0x200019c: PlaceAtPc op 0x200019d: PlaceAtMe op 0x200019e: PlaceAtMe Explicit op 0x200019f: GetPcSleep op 0x20001a0: ShowMap op 0x20001a1: FillMap op 0x20001a2: WakeUpPc op 0x20001a3: GetDeadCount op 0x20001a4: SetDisposition op 0x20001a5: SetDisposition, Explicit op 0x20001a6: GetDisposition op 0x20001a7: GetDisposition, Explicit op 0x20001a8: CommonDisease op 0x20001a9: CommonDisease, explicit reference op 0x20001aa: BlightDisease op 0x20001ab: BlightDisease, explicit reference op 0x20001ac: ToggleCollisionBoxes op 0x20001ad: SetReputation op 0x20001ae: ModReputation op 0x20001af: SetReputation, explicit op 0x20001b0: ModReputation, explicit op 0x20001b1: GetReputation op 0x20001b2: GetReputation, explicit op 0x20001b3: Equip op 0x20001b4: Equip, explicit op 0x20001b5: SameFaction op 0x20001b6: SameFaction, explicit op 0x20001b7: ModHello op 0x20001b8: ModHello, explicit reference op 0x20001b9: ModFight op 0x20001ba: ModFight, explicit reference op 0x20001bb: ModFlee op 0x20001bc: ModFlee, explicit reference op 0x20001bd: ModAlarm op 0x20001be: ModAlarm, explicit reference op 0x20001bf: GetHello op 0x20001c0: GetHello, explicit reference op 0x20001c1: GetFight op 0x20001c2: GetFight, explicit reference op 0x20001c3: GetFlee op 0x20001c4: GetFlee, explicit reference op 0x20001c5: GetAlarm op 0x20001c6: GetAlarm, explicit reference op 0x20001c7: GetLocked op 0x20001c8: GetLocked, explicit reference op 0x20001c9: GetPcRunning op 0x20001ca: GetPcSneaking op 0x20001cb: GetForceRun op 0x20001cc: GetForceSneak op 0x20001cd: GetForceRun, explicit op 0x20001ce: GetForceSneak, explicit op 0x20001cf: GetEffect op 0x20001d0: GetEffect, explicit op 0x20001d1: GetArmorType op 0x20001d2: GetArmorType, explicit op 0x20001d3: GetAttacked op 0x20001d4: GetAttacked, explicit op 0x20001d5: HasItemEquipped op 0x20001d6: HasItemEquipped, explicit op 0x20001d7: GetWeaponDrawn op 0x20001d8: GetWeaponDrawn, explicit op 0x20001d9: GetRace op 0x20001da: GetRace, explicit op 0x20001db: GetSpellEffects op 0x20001dc: GetSpellEffects, explicit op 0x20001dd: GetCurrentTime op 0x20001de: HasSoulGem op 0x20001df: HasSoulGem, explicit op 0x20001e0: GetWeaponType op 0x20001e1: GetWeaponType, explicit op 0x20001e2: GetWerewolfKills op 0x20001e3: ModScale op 0x20001e4: ModScale, explicit op 0x20001e5: SetDelete op 0x20001e6: SetDelete, explicit op 0x20001e7: GetSquareRoot op 0x20001e8: RaiseRank op 0x20001e9: RaiseRank, explicit op 0x20001ea: LowerRank op 0x20001eb: LowerRank, explicit op 0x20001ec: GetPCCrimeLevel op 0x20001ed: SetPCCrimeLevel op 0x20001ee: ModPCCrimeLevel op 0x20001ef: GetCurrentAIPackage op 0x20001f0: GetCurrentAIPackage, explicit reference op 0x20001f1: GetDetected op 0x20001f2: GetDetected, explicit reference op 0x20001f3: AddSoulGem op 0x20001f4: AddSoulGem, explicit reference op 0x20001f5: unused op 0x20001f6: unused op 0x20001f7: PlayBink op 0x20001f8: Drop op 0x20001f9: Drop, explicit reference op 0x20001fa: DropSoulGem op 0x20001fb: DropSoulGem, explicit reference op 0x20001fc: OnDeath op 0x20001fd: IsWerewolf op 0x20001fe: IsWerewolf, explicit reference op 0x20001ff: Rotate op 0x2000200: Rotate, explicit reference op 0x2000201: RotateWorld op 0x2000202: RotateWorld, explicit reference op 0x2000203: SetAtStart op 0x2000204: SetAtStart, explicit op 0x2000205: OnDeath, explicit op 0x2000206: Move op 0x2000207: Move, explicit op 0x2000208: MoveWorld op 0x2000209: MoveWorld, explicit op 0x200020a: Fall op 0x200020b: Fall, explicit op 0x200020c: GetStandingPC op 0x200020d: GetStandingPC, explicit op 0x200020e: GetStandingActor op 0x200020f: GetStandingActor, explicit op 0x2000210: GetStartingAngle op 0x2000211: GetStartingAngle, explicit op 0x2000212: GetWindSpeed op 0x2000213: HitOnMe op 0x2000214: HitOnMe, explicit op 0x2000215: DisableTeleporting op 0x2000216: EnableTeleporting op 0x2000217: BecomeWerewolf op 0x2000218: BecomeWerewolfExplicit op 0x2000219: UndoWerewolf op 0x200021a: UndoWerewolfExplicit op 0x200021b: SetWerewolfAcrobatics op 0x200021c: SetWerewolfAcrobaticsExplicit op 0x200021d: ShowVars op 0x200021e: ShowVarsExplicit op 0x200021f: ToggleGodMode op 0x2000220: DisableLevitation op 0x2000221: EnableLevitation op 0x2000222: GetLineOfSight op 0x2000223: GetLineOfSightExplicit op 0x2000224: ToggleAI op 0x2000225: unused op 0x2000226: COE op 0x2000227: Cast op 0x2000228: Cast, explicit op 0x2000229: ExplodeSpell op 0x200022a: ExplodeSpell, explicit op 0x200022b: RemoveSpellEffects op 0x200022c: RemoveSpellEffects, explicit op 0x200022d: RemoveEffects op 0x200022e: RemoveEffects, explicit op 0x200022f: Resurrect op 0x2000230: Resurrect, explicit op 0x2000231: GetSpellReadied op 0x2000232: GetSpellReadied, explicit op 0x2000233: GetPcJumping op 0x2000234: ShowRestMenu, explicit op 0x2000235: GoToJail op 0x2000236: PayFine op 0x2000237: PayFineThief op 0x2000238: GetTarget op 0x2000239: GetTargetExplicit op 0x200023a: StartCombat op 0x200023b: StartCombatExplicit op 0x200023c: StopCombat op 0x200023d: StopCombatExplicit op 0x200023e: GetPcInJail op 0x200023f: GetPcTraveling op 0x2000240: onKnockout op 0x2000241: onKnockoutExplicit op 0x2000242: ModFactionReaction op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit op 0x2000245: ClearInfoActor op 0x2000246: ClearInfoActor, explicit op 0x2000247: (unused) op 0x2000248: (unused) op 0x2000249: OnMurder op 0x200024a: OnMurder, explicit op 0x200024b: ToggleMenus op 0x200024c: Face op 0x200024d: Face, explicit op 0x200024e: GetStat (dummy function) op 0x200024f: GetStat (dummy function), explicit op 0x2000250: GetCollidingPC op 0x2000251: GetCollidingPC, explicit op 0x2000252: GetCollidingActor op 0x2000253: GetCollidingActor, explicit op 0x2000254: HurtStandingActor op 0x2000255: HurtStandingActor, explicit op 0x2000256: HurtCollidingActor op 0x2000257: HurtCollidingActor, explicit op 0x2000258: ClearForceJump op 0x2000259: ClearForceJump, explicit reference op 0x200025a: ForceJump op 0x200025b: ForceJump, explicit reference op 0x200025c: ClearForceMoveJump op 0x200025d: ClearForceMoveJump, explicit reference op 0x200025e: ForceMoveJump op 0x200025f: ForceMoveJump, explicit reference op 0x2000260: GetForceJump op 0x2000261: GetForceJump, explicit reference op 0x2000262: GetForceMoveJump op 0x2000263: GetForceMoveJump, explicit reference op 0x2000264-0x200027b: GetMagicEffect op 0x200027c-0x2000293: GetMagicEffect, explicit op 0x2000294-0x20002ab: SetMagicEffect op 0x20002ac-0x20002c3: SetMagicEffect, explicit op 0x20002c4-0x20002db: ModMagicEffect op 0x20002dc-0x20002f3: ModMagicEffect, explicit op 0x20002f4: ResetActors op 0x20002f5: ToggleWorld op 0x20002f6: PCForce1stPerson op 0x20002f7: PCForce3rdPerson op 0x20002f8: PCGet3rdPerson op 0x20002f9: HitAttemptOnMe op 0x20002fa: HitAttemptOnMe, explicit op 0x20002fb: AddToLevCreature op 0x20002fc: RemoveFromLevCreature op 0x20002fd: AddToLevItem op 0x20002fe: RemoveFromLevItem op 0x20002ff: SetFactionReaction op 0x2000300: EnableLevelupMenu op 0x2000301: ToggleScripts op 0x2000302: Fixme op 0x2000303: Fixme, explicit op 0x2000304: Show op 0x2000305: Show, explicit op 0x2000306: OnActivate, explicit op 0x2000307: ToggleBorders, tb op 0x2000308: ToggleNavMesh op 0x2000309: ToggleActorsPaths op 0x200030a: SetNavMeshNumber op 0x200030b: Journal, explicit op 0x200030c: RepairedOnMe op 0x200030d: RepairedOnMe, explicit op 0x200030e: TestCells op 0x200030f: TestInteriorCells op 0x2000310: ToggleRecastMesh op 0x2000311: MenuMode op 0x2000312: Random op 0x2000313: ScriptRunning op 0x2000314: StartScript op 0x2000315: StopScript op 0x2000316: GetSecondsPassed op 0x2000317: Enable op 0x2000318: Disable op 0x2000319: GetDisabled op 0x200031a: Enable, explicit op 0x200031b: Disable, explicit op 0x200031c: GetDisabled, explicit op 0x200031d: StartScript, explicit op 0x200031e: GetDistance op 0x200031f: GetDistance, explicit op 0x2000320: Help op 0x2000321: ReloadLua opcodes 0x2000322-0x3ffffff unused openmw-openmw-0.48.0/apps/openmw/mwscript/extensions.cpp000066400000000000000000000026461445372753700234670ustar00rootroot00000000000000#include "extensions.hpp" #include #include #include "soundextensions.hpp" #include "cellextensions.hpp" #include "miscextensions.hpp" #include "guiextensions.hpp" #include "skyextensions.hpp" #include "statsextensions.hpp" #include "containerextensions.hpp" #include "aiextensions.hpp" #include "controlextensions.hpp" #include "dialogueextensions.hpp" #include "animationextensions.hpp" #include "transformationextensions.hpp" #include "consoleextensions.hpp" #include "userextensions.hpp" namespace MWScript { void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly) { Interpreter::installOpcodes (interpreter); Cell::installOpcodes (interpreter); Misc::installOpcodes (interpreter); Gui::installOpcodes (interpreter); Sound::installOpcodes (interpreter); Sky::installOpcodes (interpreter); Stats::installOpcodes (interpreter); Container::installOpcodes (interpreter); Ai::installOpcodes (interpreter); Control::installOpcodes (interpreter); Dialogue::installOpcodes (interpreter); Animation::installOpcodes (interpreter); Transformation::installOpcodes (interpreter); if (consoleOnly) { Console::installOpcodes (interpreter); User::installOpcodes (interpreter); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/extensions.hpp000066400000000000000000000005261445372753700234670ustar00rootroot00000000000000#ifndef GAME_SCRIPT_EXTENSIONS_H #define GAME_SCRIPT_EXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly = false); ///< \param consoleOnly include console only opcodes } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/globalscripts.cpp000066400000000000000000000241131445372753700241310ustar00rootroot00000000000000#include "globalscripts.hpp" #include #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "interpretercontext.hpp" namespace { struct ScriptCreatingVisitor { ESM::GlobalScript operator()(const MWWorld::Ptr &ptr) const { ESM::GlobalScript script; script.mTargetRef.unset(); script.mRunning = false; if (!ptr.isEmpty()) { if (ptr.getCellRef().hasContentFile()) { script.mTargetId = ptr.getCellRef().getRefId(); script.mTargetRef = ptr.getCellRef().getRefNum(); } else if (MWBase::Environment::get().getWorld()->getPlayerPtr() == ptr) script.mTargetId = ptr.getCellRef().getRefId(); } return script; } ESM::GlobalScript operator()(const std::pair &pair) const { ESM::GlobalScript script; script.mTargetId = pair.second; script.mTargetRef = pair.first; script.mRunning = false; return script; } }; struct PtrGettingVisitor { const MWWorld::Ptr* operator()(const MWWorld::Ptr &ptr) const { return &ptr; } const MWWorld::Ptr* operator()(const std::pair &pair) const { return nullptr; } }; struct PtrResolvingVisitor { MWWorld::Ptr operator()(const MWWorld::Ptr &ptr) const { return ptr; } MWWorld::Ptr operator()(const std::pair &pair) const { if (pair.second.empty()) return MWWorld::Ptr(); else if(pair.first.hasContentFile()) return MWBase::Environment::get().getWorld()->searchPtrViaRefNum(pair.second, pair.first); return MWBase::Environment::get().getWorld()->searchPtr(pair.second, false); } }; class MatchPtrVisitor { const MWWorld::Ptr& mPtr; public: MatchPtrVisitor(const MWWorld::Ptr& ptr) : mPtr(ptr) {} bool operator()(const MWWorld::Ptr &ptr) const { return ptr == mPtr; } bool operator()(const std::pair &pair) const { return false; } }; struct IdGettingVisitor { std::string operator()(const MWWorld::Ptr& ptr) const { if(ptr.isEmpty()) return {}; return ptr.mRef->mRef.getRefId(); } std::string operator()(const std::pair& pair) const { return pair.second; } }; } namespace MWScript { GlobalScriptDesc::GlobalScriptDesc() : mRunning (false) {} const MWWorld::Ptr* GlobalScriptDesc::getPtrIfPresent() const { return std::visit(PtrGettingVisitor(), mTarget); } MWWorld::Ptr GlobalScriptDesc::getPtr() { MWWorld::Ptr ptr = std::visit(PtrResolvingVisitor {}, mTarget); mTarget = ptr; return ptr; } std::string GlobalScriptDesc::getId() const { return std::visit(IdGettingVisitor {}, mTarget); } GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) {} void GlobalScripts::addScript(std::string_view name, const MWWorld::Ptr& target) { std::string lowerName = ::Misc::StringUtils::lowerCase(name); const auto iter = mScripts.find(lowerName); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().search(lowerName)) { auto desc = std::make_shared(); MWWorld::Ptr ptr = target; desc->mTarget = ptr; desc->mRunning = true; desc->mLocals.configure (*script); mScripts.insert (std::make_pair(lowerName, desc)); } else { Log(Debug::Error) << "Failed to add global script " << name << ": script record not found"; } } else if (!iter->second->mRunning) { iter->second->mRunning = true; MWWorld::Ptr ptr = target; iter->second->mTarget = ptr; } } void GlobalScripts::removeScript (std::string_view name) { const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) iter->second->mRunning = false; } bool GlobalScripts::isRunning (std::string_view name) const { const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; return iter->second->mRunning; } void GlobalScripts::run() { for (const auto& script : mScripts) { if (script.second->mRunning) { MWScript::InterpreterContext context(script.second); if (!MWBase::Environment::get().getScriptManager()->run(script.first, context)) script.second->mRunning = false; } } } void GlobalScripts::clear() { mScripts.clear(); } void GlobalScripts::addStartup() { // make list of global scripts to be added std::vector scripts; scripts.emplace_back("main"); for (MWWorld::Store::iterator iter = mStore.get().begin(); iter != mStore.get().end(); ++iter) { scripts.push_back (iter->mId); } // add scripts for (std::vector::const_iterator iter (scripts.begin()); iter!=scripts.end(); ++iter) { try { addScript (*iter); } catch (const std::exception& exception) { Log(Debug::Error) << "Failed to add start script " << *iter << " because an exception has " << "been thrown: " << exception.what(); } } } int GlobalScripts::countSavedGameRecords() const { return mScripts.size(); } void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (const auto& [id, desc] : mScripts) { ESM::GlobalScript script = std::visit(ScriptCreatingVisitor {}, desc->mTarget); script.mId = id; desc->mLocals.write(script.mLocals, id); script.mRunning = desc->mRunning ? 1 : 0; writer.startRecord (ESM::REC_GSCR); script.save (writer); writer.endRecord (ESM::REC_GSCR); } } bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { if (type==ESM::REC_GSCR) { ESM::GlobalScript script; script.load (reader); if (script.mTargetRef.hasContentFile()) { auto iter = contentFileMap.find(script.mTargetRef.mContentFile); if (iter != contentFileMap.end()) script.mTargetRef.mContentFile = iter->second; } auto iter = mScripts.find (script.mId); if (iter==mScripts.end()) { if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) { try { auto desc = std::make_shared(); if (!script.mTargetId.empty()) { desc->mTarget = std::make_pair(script.mTargetRef, script.mTargetId); } desc->mLocals.configure (*scriptRecord); iter = mScripts.insert (std::make_pair (script.mId, desc)).first; } catch (const std::exception& exception) { Log(Debug::Error) << "Failed to add start script " << script.mId << " because an exception has been thrown: " << exception.what(); return true; } } else // script does not exist anymore return true; } iter->second->mRunning = script.mRunning!=0; iter->second->mLocals.read (script.mLocals, script.mId); return true; } return false; } Locals& GlobalScripts::getLocals(std::string_view name) { std::string name2 = ::Misc::StringUtils::lowerCase (name); auto iter = mScripts.find (name2); if (iter==mScripts.end()) { const ESM::Script *script = mStore.get().find(name2); auto desc = std::make_shared(); desc->mLocals.configure (*script); iter = mScripts.insert (std::make_pair (name2, desc)).first; } return iter->second->mLocals; } const Locals* GlobalScripts::getLocalsIfPresent(std::string_view name) const { std::string name2 = ::Misc::StringUtils::lowerCase (name); auto iter = mScripts.find (name2); if (iter==mScripts.end()) return nullptr; return &iter->second->mLocals; } void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) { MatchPtrVisitor visitor(base); for (const auto& script : mScripts) { if (std::visit (visitor, script.second->mTarget)) script.second->mTarget = updated; } } } openmw-openmw-0.48.0/apps/openmw/mwscript/globalscripts.hpp000066400000000000000000000045721445372753700241450ustar00rootroot00000000000000#ifndef GAME_SCRIPT_GLOBALSCRIPTS_H #define GAME_SCRIPT_GLOBALSCRIPTS_H #include #include #include #include #include #include #include "locals.hpp" #include "../mwworld/ptr.hpp" namespace ESM { class ESMWriter; class ESMReader; struct RefNum; } namespace Loading { class Listener; } namespace MWWorld { class ESMStore; } namespace MWScript { struct GlobalScriptDesc { bool mRunning; Locals mLocals; std::variant> mTarget; // Used to start targeted script GlobalScriptDesc(); const MWWorld::Ptr* getPtrIfPresent() const; // Returns a Ptr if one has been resolved MWWorld::Ptr getPtr(); // Resolves mTarget to a Ptr and caches the (potentially empty) result std::string getId() const; // Returns the target's ID -- if any }; class GlobalScripts { const MWWorld::ESMStore& mStore; std::map > mScripts; public: GlobalScripts (const MWWorld::ESMStore& store); void addScript(std::string_view name, const MWWorld::Ptr& target = MWWorld::Ptr()); void removeScript (std::string_view name); bool isRunning (std::string_view name) const; void run(); ///< run all active global scripts void clear(); void addStartup(); ///< Add startup script int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); ///< Records for variables that do not exist are dropped silently. /// /// \return Known type? Locals& getLocals(std::string_view name); ///< If the script \a name has not been added as a global script yet, it is added /// automatically, but is not set to running state. const Locals* getLocalsIfPresent(std::string_view name) const; void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); ///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/guiextensions.cpp000066400000000000000000000230561445372753700241720ustar00rootroot00000000000000#include "guiextensions.hpp" #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Gui { class OpEnableWindow : public Interpreter::Opcode0 { MWGui::GuiWindow mWindow; public: OpEnableWindow (MWGui::GuiWindow window) : mWindow (window) {} void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWindowManager()->allow (mWindow); } }; class OpEnableRest : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWindowManager()->enableRest(); } }; template class OpShowRestMenu : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr bed = R()(runtime, false); if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), bed)) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest, bed); } }; class OpShowDialogue : public Interpreter::Opcode0 { MWGui::GuiMode mDialogue; public: OpShowDialogue (MWGui::GuiMode dialogue) : mDialogue (dialogue) {} void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWindowManager()->pushGuiMode(mDialogue); } }; class OpGetButtonPressed : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWindowManager()->readPressedButton()); } }; class OpToggleFogOfWar : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFogOfWar() ? "Fog of war -> On" : "Fog of war -> Off"); } }; class OpToggleFullHelp : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFullHelp() ? "Full help -> On" : "Full help -> Off"); } }; class OpShowMap : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string cell = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." // http://www.uesp.net/wiki/Tes3Mod:ShowMap const MWWorld::Store &cells = MWBase::Environment::get().getWorld()->getStore().get(); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); for (auto it = cells.extBegin(); it != cells.extEnd(); ++it) { std::string name = it->mName; ::Misc::StringUtils::lowerCaseInPlace(name); if (name.length() >= cell.length() && name.substr(0, cell.length()) == cell) winMgr->addVisitedLocation(it->mName, it->getGridX(), it->getGridY()); } } }; class OpFillMap : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { const MWWorld::Store &cells = MWBase::Environment::get().getWorld ()->getStore().get(); MWWorld::Store::iterator it = cells.extBegin(); for (; it != cells.extEnd(); ++it) { std::string name = it->mName; if (name != "") MWBase::Environment::get().getWindowManager()->addVisitedLocation ( name, it->getGridX(), it->getGridY() ); } } }; class OpMenuTest : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { int arg=0; if(arg0>0) { arg = runtime[0].mInteger; runtime.pop(); } if (arg == 0) { MWGui::GuiMode modes[] = { MWGui::GM_Inventory, MWGui::GM_Container }; for (int i=0; i<2; ++i) { if (MWBase::Environment::get().getWindowManager()->containsMode(modes[i])) MWBase::Environment::get().getWindowManager()->removeGuiMode(modes[i]); } } else { MWGui::GuiWindow gw = MWGui::GW_None; if (arg == 3) gw = MWGui::GW_Stats; if (arg == 4) gw = MWGui::GW_Inventory; if (arg == 5) gw = MWGui::GW_Magic; if (arg == 6) gw = MWGui::GW_Map; MWBase::Environment::get().getWindowManager()->pinWindow(gw); } } }; class OpToggleMenus : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { bool state = MWBase::Environment::get().getWindowManager()->toggleHud(); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); if (!state) { while (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None) // don't use isGuiMode, or we get an infinite loop for modal message boxes! MWBase::Environment::get().getWindowManager()->popGuiMode(); } } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Gui::opcodeEnableBirthMenu, MWGui::GM_Birth); interpreter.installSegment5(Compiler::Gui::opcodeEnableClassMenu, MWGui::GM_Class); interpreter.installSegment5(Compiler::Gui::opcodeEnableNameMenu, MWGui::GM_Name); interpreter.installSegment5(Compiler::Gui::opcodeEnableRaceMenu, MWGui::GM_Race); interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsReviewMenu, MWGui::GM_Review); interpreter.installSegment5(Compiler::Gui::opcodeEnableLevelupMenu, MWGui::GM_Levelup); interpreter.installSegment5(Compiler::Gui::opcodeEnableInventoryMenu, MWGui::GW_Inventory); interpreter.installSegment5(Compiler::Gui::opcodeEnableMagicMenu, MWGui::GW_Magic); interpreter.installSegment5(Compiler::Gui::opcodeEnableMapMenu, MWGui::GW_Map); interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsMenu, MWGui::GW_Stats); interpreter.installSegment5(Compiler::Gui::opcodeEnableRest); interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenu); interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenuExplicit); interpreter.installSegment5(Compiler::Gui::opcodeGetButtonPressed); interpreter.installSegment5(Compiler::Gui::opcodeToggleFogOfWar); interpreter.installSegment5(Compiler::Gui::opcodeToggleFullHelp); interpreter.installSegment5(Compiler::Gui::opcodeShowMap); interpreter.installSegment5(Compiler::Gui::opcodeFillMap); interpreter.installSegment3(Compiler::Gui::opcodeMenuTest); interpreter.installSegment5(Compiler::Gui::opcodeToggleMenus); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/guiextensions.hpp000066400000000000000000000005441445372753700241740ustar00rootroot00000000000000#ifndef GAME_SCRIPT_GUIEXTENSIONS_H #define GAME_SCRIPT_GUIEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief GUI-related script functionality namespace Gui { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/interpretercontext.cpp000066400000000000000000000403741445372753700252400ustar00rootroot00000000000000#include "interpretercontext.hpp" #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "locals.hpp" #include "globalscripts.hpp" namespace MWScript { const MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) const { if (!id.empty()) { return MWBase::Environment::get().getWorld()->getPtr (id, activeOnly); } else { if (mReference.isEmpty() && mGlobalScriptDesc) mReference = mGlobalScriptDesc->getPtr(); if (mReference.isEmpty() && doThrow) throw MissingImplicitRefError(); return mReference; } } const Locals& InterpreterContext::getMemberLocals (std::string& id, bool global) const { if (global) { return MWBase::Environment::get().getScriptManager()->getGlobalScripts(). getLocals (id); } else { const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); ptr.getRefData().setLocals ( *MWBase::Environment::get().getWorld()->getStore().get().find (id)); return ptr.getRefData().getLocals(); } } Locals& InterpreterContext::getMemberLocals (std::string& id, bool global) { if (global) { return MWBase::Environment::get().getScriptManager()->getGlobalScripts(). getLocals (id); } else { const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); ptr.getRefData().setLocals ( *MWBase::Environment::get().getWorld()->getStore().get().find (id)); return ptr.getRefData().getLocals(); } } MissingImplicitRefError::MissingImplicitRefError() : std::runtime_error("no implicit reference") {} int InterpreterContext::findLocalVariableIndex (const std::string& scriptId, std::string_view name, char type) const { int index = MWBase::Environment::get().getScriptManager()->getLocals (scriptId). searchIndex (type, name); if (index!=-1) return index; std::ostringstream stream; stream << "Failed to access "; switch (type) { case 's': stream << "short"; break; case 'l': stream << "long"; break; case 'f': stream << "float"; break; } stream << " member variable " << name << " in script " << scriptId; throw std::runtime_error (stream.str().c_str()); } InterpreterContext::InterpreterContext (MWScript::Locals *locals, const MWWorld::Ptr& reference) : mLocals (locals), mReference (reference) {} InterpreterContext::InterpreterContext (std::shared_ptr globalScriptDesc) : mLocals (&(globalScriptDesc->mLocals)) { const MWWorld::Ptr* ptr = globalScriptDesc->getPtrIfPresent(); // A nullptr here signifies that the script's target has not yet been resolved after loading the game. // Script targets are lazily resolved to MWWorld::Ptrs (which can, upon resolution, be empty) // because scripts started through dialogue often don't use their implicit target. if (ptr) mReference = *ptr; else mGlobalScriptDesc = globalScriptDesc; } std::string InterpreterContext::getTarget() const { if(!mReference.isEmpty()) return mReference.mRef->mRef.getRefId(); else if(mGlobalScriptDesc) return mGlobalScriptDesc->getId(); return {}; } int InterpreterContext::getLocalShort (int index) const { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); return mLocals->mShorts.at (index); } int InterpreterContext::getLocalLong (int index) const { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); return mLocals->mLongs.at (index); } float InterpreterContext::getLocalFloat (int index) const { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); return mLocals->mFloats.at (index); } void InterpreterContext::setLocalShort (int index, int value) { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); mLocals->mShorts.at (index) = value; } void InterpreterContext::setLocalLong (int index, int value) { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); mLocals->mLongs.at (index) = value; } void InterpreterContext::setLocalFloat (int index, float value) { if (!mLocals) throw std::runtime_error ("local variables not available in this context"); mLocals->mFloats.at (index) = value; } void InterpreterContext::messageBox (const std::string& message, const std::vector& buttons) { if (buttons.empty()) MWBase::Environment::get().getWindowManager()->messageBox (message); else MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); } void InterpreterContext::report (const std::string& message) { } int InterpreterContext::getGlobalShort(std::string_view name) const { return MWBase::Environment::get().getWorld()->getGlobalInt (name); } int InterpreterContext::getGlobalLong(std::string_view name) const { // a global long is internally a float. return MWBase::Environment::get().getWorld()->getGlobalInt (name); } float InterpreterContext::getGlobalFloat(std::string_view name) const { return MWBase::Environment::get().getWorld()->getGlobalFloat (name); } void InterpreterContext::setGlobalShort(std::string_view name, int value) { MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalLong(std::string_view name, int value) { MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalFloat(std::string_view name, float value) { MWBase::Environment::get().getWorld()->setGlobalFloat (name, value); } std::vector InterpreterContext::getGlobals() const { const MWWorld::Store& globals = MWBase::Environment::get().getWorld()->getStore().get(); std::vector ids; for (auto& globalVariable : globals) { ids.emplace_back(globalVariable.mId); } return ids; } char InterpreterContext::getGlobalType(std::string_view name) const { MWBase::World *world = MWBase::Environment::get().getWorld(); return world->getGlobalVariableType(name); } std::string InterpreterContext::getActionBinding(std::string_view targetAction) const { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); std::vector actions = input->getActionKeySorting (); for (const int action : actions) { std::string desc = input->getActionDescription (action); if(desc == "") continue; if(desc == targetAction) { if(input->joystickLastUsed()) return input->getActionControllerBindingName(action); else return input->getActionKeyBindingName(action); } } return "None"; } std::string InterpreterContext::getActorName() const { const MWWorld::Ptr& ptr = getReferenceImp(); if (ptr.getClass().isNpc()) { const ESM::NPC* npc = ptr.get()->mBase; return npc->mName; } const ESM::Creature* creature = ptr.get()->mBase; return creature->mName; } std::string InterpreterContext::getNPCRace() const { ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mRace); return race->mName; } std::string InterpreterContext::getNPCClass() const { ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mClass); return class_->mName; } std::string InterpreterContext::getNPCFaction() const { ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mFaction); return faction->mName; } std::string InterpreterContext::getNPCRank() const { const MWWorld::Ptr& ptr = getReferenceImp(); std::string faction = ptr.getClass().getPrimaryFaction(ptr); if (faction.empty()) throw std::runtime_error("getNPCRank(): NPC is not in a faction"); int rank = ptr.getClass().getPrimaryFactionRank(ptr); if (rank < 0 || rank > 9) throw std::runtime_error("getNPCRank(): invalid rank"); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *fact = store.get().find(faction); return fact->mRanks[rank]; } std::string InterpreterContext::getPCName() const { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::NPC player = *world->getPlayerPtr().get()->mBase; return player.mName; } std::string InterpreterContext::getPCRace() const { MWBase::World *world = MWBase::Environment::get().getWorld(); std::string race = world->getPlayerPtr().get()->mBase->mRace; return world->getStore().get().find(race)->mName; } std::string InterpreterContext::getPCClass() const { MWBase::World *world = MWBase::Environment::get().getWorld(); std::string class_ = world->getPlayerPtr().get()->mBase->mClass; return world->getStore().get().find(class_)->mName; } std::string InterpreterContext::getPCRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCRank(): NPC is not in a faction"); const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; // If you are not in the faction, PcRank returns the first rank, for whatever reason. // This is used by the dialogue when joining the Thieves Guild in Balmora. if (rank == -1) rank = 0; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get().find(factionId); if(rank < 0 || rank > 9) // there are only 10 ranks return ""; return faction->mRanks[rank]; } std::string InterpreterContext::getPCNextRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCNextRank(): NPC is not in a faction"); const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; ++rank; // Next rank // if we are already at max rank, there is no next rank if (rank > 9) rank = 9; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get().find(factionId); if(rank < 0) return ""; return faction->mRanks[rank]; } int InterpreterContext::getPCBounty() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); return player.getClass().getNpcStats (player).getBounty(); } std::string InterpreterContext::getCurrentCellName() const { return MWBase::Environment::get().getWorld()->getCellName(); } void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) { MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor); std::unique_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr) { updatePtr(ptr, action->getTarget()); } } int InterpreterContext::getMemberShort(std::string_view id, std::string_view name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mShorts[findLocalVariableIndex (scriptId, name, 's')]; } int InterpreterContext::getMemberLong(std::string_view id, std::string_view name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')]; } float InterpreterContext::getMemberFloat(std::string_view id, std::string_view name, bool global) const { std::string scriptId (id); const Locals& locals = getMemberLocals (scriptId, global); return locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')]; } void InterpreterContext::setMemberShort(std::string_view id, std::string_view name, int value, bool global) { std::string scriptId (id); Locals& locals = getMemberLocals (scriptId, global); locals.mShorts[findLocalVariableIndex (scriptId, name, 's')] = value; } void InterpreterContext::setMemberLong(std::string_view id, std::string_view name, int value, bool global) { std::string scriptId (id); Locals& locals = getMemberLocals (scriptId, global); locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')] = value; } void InterpreterContext::setMemberFloat(std::string_view id, std::string_view name, float value, bool global) { std::string scriptId (id); Locals& locals = getMemberLocals (scriptId, global); locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')] = value; } MWWorld::Ptr InterpreterContext::getReference(bool required) const { return getReferenceImp ("", true, required); } void InterpreterContext::updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) { if (!mReference.isEmpty() && base == mReference) { mReference = updated; if (mLocals == &base.getRefData().getLocals()) mLocals = &mReference.getRefData().getLocals(); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/interpretercontext.hpp000066400000000000000000000116321445372753700252400ustar00rootroot00000000000000#ifndef GAME_SCRIPT_INTERPRETERCONTEXT_H #define GAME_SCRIPT_INTERPRETERCONTEXT_H #include #include #include #include "globalscripts.hpp" #include "../mwworld/ptr.hpp" namespace MWScript { class Locals; class MissingImplicitRefError : public std::runtime_error { public: MissingImplicitRefError(); }; class InterpreterContext : public Interpreter::Context { Locals *mLocals; mutable MWWorld::Ptr mReference; std::shared_ptr mGlobalScriptDesc; /// If \a id is empty, a reference the script is run from is returned or in case /// of a non-local script the reference derived from the target ID. const MWWorld::Ptr getReferenceImp (const std::string& id = "", bool activeOnly = false, bool doThrow=true) const; const Locals& getMemberLocals (std::string& id, bool global) const; ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before Locals& getMemberLocals (std::string& id, bool global); ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before /// Throws an exception if local variable can't be found. int findLocalVariableIndex (const std::string& scriptId, std::string_view name, char type) const; public: InterpreterContext (std::shared_ptr globalScriptDesc); InterpreterContext (MWScript::Locals *locals, const MWWorld::Ptr& reference); ///< The ownership of \a locals is not transferred. 0-pointer allowed. std::string getTarget() const override; int getLocalShort (int index) const override; int getLocalLong (int index) const override; float getLocalFloat (int index) const override; void setLocalShort (int index, int value) override; void setLocalLong (int index, int value) override; void setLocalFloat (int index, float value) override; using Interpreter::Context::messageBox; void messageBox (const std::string& message, const std::vector& buttons) override; void report (const std::string& message) override; ///< By default, do nothing. int getGlobalShort(std::string_view name) const override; int getGlobalLong(std::string_view name) const override; float getGlobalFloat(std::string_view name) const override; void setGlobalShort(std::string_view name, int value) override; void setGlobalLong(std::string_view name, int value) override; void setGlobalFloat(std::string_view name, float value) override; std::vector getGlobals () const override; char getGlobalType(std::string_view name) const override; std::string getActionBinding(std::string_view action) const override; std::string getActorName() const override; std::string getNPCRace() const override; std::string getNPCClass() const override; std::string getNPCFaction() const override; std::string getNPCRank() const override; std::string getPCName() const override; std::string getPCRace() const override; std::string getPCClass() const override; std::string getPCRank() const override; std::string getPCNextRank() const override; int getPCBounty() const override; std::string getCurrentCellName() const override; void executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. int getMemberShort(std::string_view id, std::string_view name, bool global) const override; int getMemberLong(std::string_view id, std::string_view name, bool global) const override; float getMemberFloat(std::string_view id, std::string_view name, bool global) const override; void setMemberShort(std::string_view id, std::string_view name, int value, bool global) override; void setMemberLong(std::string_view id, std::string_view name, int value, bool global) override; void setMemberFloat(std::string_view id, std::string_view name, float value, bool global) override; MWWorld::Ptr getReference(bool required=true) const; ///< Reference, that the script is running from (can be empty) void updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/locals.cpp000066400000000000000000000176351445372753700225510ustar00rootroot00000000000000#include "locals.hpp" #include "globalscripts.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWScript { void Locals::ensure (const std::string& scriptName) { if (!mInitialised) { const ESM::Script *script = MWBase::Environment::get().getWorld()->getStore(). get().find (scriptName); configure (*script); } } Locals::Locals() : mInitialised (false) {} bool Locals::configure (const ESM::Script& script) { if (mInitialised) return false; const Locals* global = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocalsIfPresent(script.mId); if(global) { mShorts = global->mShorts; mLongs = global->mLongs; mFloats = global->mFloats; } else { const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals (script.mId); mShorts.clear(); mShorts.resize (locals.get ('s').size(), 0); mLongs.clear(); mLongs.resize (locals.get ('l').size(), 0); mFloats.clear(); mFloats.resize (locals.get ('f').size(), 0); } mInitialised = true; return true; } bool Locals::isEmpty() const { return (mShorts.empty() && mLongs.empty() && mFloats.empty()); } bool Locals::hasVar(const std::string &script, std::string_view var) { ensure (script); const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); return (index != -1); } int Locals::getIntVar(const std::string &script, std::string_view var) { ensure (script); const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) { switch(type) { case 's': return mShorts.at (index); case 'l': return mLongs.at (index); case 'f': return static_cast(mFloats.at(index)); default: return 0; } } return 0; } float Locals::getFloatVar(const std::string &script, std::string_view var) { ensure (script); const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) { switch(type) { case 's': return mShorts.at (index); case 'l': return mLongs.at (index); case 'f': return mFloats.at(index); default: return 0; } } return 0; } bool Locals::setVarByInt(const std::string& script, std::string_view var, int val) { ensure (script); const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) { switch(type) { case 's': mShorts.at (index) = val; break; case 'l': mLongs.at (index) = val; break; case 'f': mFloats.at(index) = static_cast(val); break; } return true; } return false; } bool Locals::write (ESM::Locals& locals, const std::string& script) const { if (!mInitialised) return false; const Compiler::Locals& declarations = MWBase::Environment::get().getScriptManager()->getLocals(script); for (int i=0; i<3; ++i) { char type = 0; switch (i) { case 0: type = 's'; break; case 1: type = 'l'; break; case 2: type = 'f'; break; } const std::vector& names = declarations.get (type); for (int i2=0; i2 (names.size()); ++i2) { ESM::Variant value; switch (i) { case 0: value.setType (ESM::VT_Int); value.setInteger (mShorts.at (i2)); break; case 1: value.setType (ESM::VT_Int); value.setInteger (mLongs.at (i2)); break; case 2: value.setType (ESM::VT_Float); value.setFloat (mFloats.at (i2)); break; } locals.mVariables.emplace_back (names[i2], value); } } return true; } void Locals::read (const ESM::Locals& locals, const std::string& script) { ensure (script); const Compiler::Locals& declarations = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = 0, numshorts = 0, numlongs = 0; for (unsigned int v=0; v >::const_iterator iter = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter,++index) { if (iter->first.empty()) { // no variable names available (this will happen for legacy, i.e. ESS-imported savegames only) try { if (index >= numshorts+numlongs) mFloats.at(index - (numshorts+numlongs)) = iter->second.getFloat(); else if (index >= numshorts) mLongs.at(index - numshorts) = iter->second.getInteger(); else mShorts.at(index) = iter->second.getInteger(); } catch (std::exception& e) { Log(Debug::Error) << "Failed to read local variable state for script '" << script << "' (legacy format): " << e.what() << "\nNum shorts: " << numshorts << " / " << mShorts.size() << " Num longs: " << numlongs << " / " << mLongs.size(); } } else { char type = declarations.getType (iter->first); int index2 = declarations.getIndex (iter->first); // silently ignore locals that don't exist anymore if (type == ' ' || index2 == -1) continue; try { switch (type) { case 's': mShorts.at (index2) = iter->second.getInteger(); break; case 'l': mLongs.at (index2) = iter->second.getInteger(); break; case 'f': mFloats.at (index2) = iter->second.getFloat(); break; } } catch (...) { // ignore type changes /// \todo write to log } } } } } openmw-openmw-0.48.0/apps/openmw/mwscript/locals.hpp000066400000000000000000000045241445372753700225470ustar00rootroot00000000000000#ifndef GAME_SCRIPT_LOCALS_H #define GAME_SCRIPT_LOCALS_H #include #include #include #include namespace ESM { class Script; struct Locals; } namespace MWScript { class Locals { bool mInitialised; void ensure (const std::string& scriptName); public: std::vector mShorts; std::vector mLongs; std::vector mFloats; Locals(); /// Are there any locals? /// /// \note Will return false, if locals have not been configured yet. bool isEmpty() const; /// \return Did the state of *this change from uninitialised to initialised? bool configure (const ESM::Script& script); /// @note var needs to be in lowercase /// /// \note Locals will be automatically configured first, if necessary bool setVarByInt(const std::string& script, std::string_view var, int val); /// \note Locals will be automatically configured first, if necessary // // \note If it can not be determined if the variable exists, the error will be // ignored and false will be returned. bool hasVar(const std::string& script, std::string_view var); /// if var does not exist, returns 0 /// @note var needs to be in lowercase /// /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, std::string_view var); /// if var does not exist, returns 0 /// @note var needs to be in lowercase /// /// \note Locals will be automatically configured first, if necessary float getFloatVar (const std::string& script, std::string_view var); /// \note If locals have not been configured yet, no data is written. /// /// \return Locals written? bool write (ESM::Locals& locals, const std::string& script) const; /// \note Locals will be automatically configured first, if necessary void read (const ESM::Locals& locals, const std::string& script); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/miscextensions.cpp000066400000000000000000002125641445372753700243450ustar00rootroot00000000000000#include "miscextensions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/luamanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwmechanics/aicast.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace { void addToLevList(ESM::LevelledListBase* list, std::string_view itemId, int level) { for (auto& levelItem : list->mList) { if (levelItem.mLevel == level && itemId == levelItem.mId) return; } ESM::LevelledListBase::LevelItem item; item.mId = std::string{itemId}; item.mLevel = level; list->mList.push_back(item); } void removeFromLevList(ESM::LevelledListBase* list, std::string_view itemId, int level) { // level of -1 removes all items with that itemId for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) { if (level != -1 && it->mLevel != level) { ++it; continue; } if (Misc::StringUtils::ciEqual(itemId, it->mId)) it = list->mList.erase(it); else ++it; } } } namespace MWScript { namespace Misc { class OpMenuMode : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWindowManager()->isGuiMode()); } }; class OpRandom : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Integer limit = runtime[0].mInteger; runtime.pop(); if (limit<0) throw std::runtime_error ( "random: argument out of range (Don't be so negative!)"); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); runtime.push (static_cast(::Misc::Rng::rollDice(limit, prng))); // [o, limit) } }; template class OpStartScript : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr target = R()(runtime, false); std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, target); } }; class OpScriptRunning : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); runtime.push(MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name)); } }; class OpStopScript : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript (name); } }; class OpGetSecondsPassed : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getFrameDuration()); } }; template class OpEnable : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getWorld()->enable (ptr); } }; template class OpDisable : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getWorld()->disable (ptr); } }; template class OpGetDisabled : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (!ptr.getRefData().isEnabled()); } }; class OpPlayBink : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string name{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); bool allowSkipping = runtime[0].mInteger != 0; runtime.pop(); MWBase::Environment::get().getWindowManager()->playVideo (name, allowSkipping); } }; class OpGetPcSleep : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWindowManager ()->getPlayerSleeping()); } }; class OpGetPcJumping : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); runtime.push (world->getPlayer().getJumping()); } }; class OpWakeUpPc : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWindowManager ()->wakeUpPlayer(); } }; class OpXBox : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (0); } }; template class OpOnActivate : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getRefData().onActivate()); } }; template class OpActivate : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { InterpreterContext& context = static_cast (runtime.getContext()); MWWorld::Ptr ptr = R()(runtime); if (ptr.getRefData().activateByScript() || ptr.getContainerStore()) context.executeActivation(ptr, MWMechanics::getPlayer()); } }; template class OpLock : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer lockLevel = ptr.getCellRef().getLockLevel(); if(lockLevel==0) { //no lock level was ever set, set to 100 as default lockLevel = 100; } if (arg0==1) { lockLevel = runtime[0].mInteger; runtime.pop(); } ptr.getCellRef().lock (lockLevel); // Instantly reset door to closed state // This is done when using Lock in scripts, but not when using Lock spells. if (ptr.getType() == ESM::Door::sRecordId && !ptr.getCellRef().getTeleport()) { MWBase::Environment::get().getWorld()->activateDoor(ptr, MWWorld::DoorState::Idle); } } }; template class OpUnlock : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ptr.getCellRef().unlock (); } }; class OpToggleCollisionDebug : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_CollisionDebug); runtime.getContext().report (enabled ? "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); } }; class OpToggleCollisionBoxes : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_CollisionDebug); runtime.getContext().report (enabled ? "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); } }; class OpToggleWireframe : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Wireframe); runtime.getContext().report (enabled ? "Wireframe Rendering -> On" : "Wireframe Rendering -> Off"); } }; class OpToggleBorders : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleBorders(); runtime.getContext().report (enabled ? "Border Rendering -> On" : "Border Rendering -> Off"); } }; class OpTogglePathgrid : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Pathgrid); runtime.getContext().report (enabled ? "Path Grid rendering -> On" : "Path Grid Rendering -> Off"); } }; class OpFadeIn : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWindowManager()->fadeScreenIn(time, false); } }; class OpFadeOut : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(time, false); } }; class OpFadeTo : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float alpha = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWindowManager()->fadeScreenTo(static_cast(alpha), time, false); } }; class OpToggleWater : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report(MWBase::Environment::get().getWorld()->toggleWater() ? "Water -> On" : "Water -> Off"); } }; class OpToggleWorld : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report(MWBase::Environment::get().getWorld()->toggleWorld() ? "World -> On" : "World -> Off"); } }; class OpDontSaveObject : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { // We are ignoring the DontSaveObject statement for now. Probably not worth // bothering with. The incompatibility we are creating should be marginal at most. } }; class OpPcForce1stPerson : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { if (!MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(true); } }; class OpPcForce3rdPerson : public Interpreter::Opcode0 { void execute (Interpreter::Runtime& runtime) override { if (MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(true); } }; class OpPcGet3rdPerson : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { runtime.push(!MWBase::Environment::get().getWorld()->isFirstPerson()); } }; class OpToggleVanityMode : public Interpreter::Opcode0 { static bool sActivate; public: void execute(Interpreter::Runtime &runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); if (world->toggleVanityMode(sActivate)) { runtime.getContext().report(sActivate ? "Vanity Mode -> On" : "Vanity Mode -> Off"); sActivate = !sActivate; } else { runtime.getContext().report("Vanity Mode -> No"); } } }; bool OpToggleVanityMode::sActivate = true; template class OpGetLocked : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getCellRef().getLockLevel() > 0); } }; template class OpGetEffect : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view effect = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().isActor()) { runtime.push(0); return; } char *end; long key = strtol(effect.data(), &end, 10); if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId({effect}); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); for (const auto& activeEffect : effects) { if (activeEffect.first.mId == key && activeEffect.second.getModifier() > 0) { runtime.push(1); return; } } runtime.push(0); } }; template class OpAddSoulGem : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string creature{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string_view gem = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().hasInventoryStore(ptr)) return; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); // Set the soul on just one of the gems, not the whole stack item.getContainerStore()->unstack(item, ptr); item.getCellRef().setSoul(creature); // Restack the gem with other gems with the same soul item.getContainerStore()->restack(item); } }; template class OpRemoveSoulGem : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::string_view soul = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); // throw away additional arguments for (unsigned int i=0; igetCellRef().getSoul(), soul)) { store.remove(*it, 1, ptr); return; } } } }; template class OpDrop : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view item = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer amount = runtime[0].mInteger; runtime.pop(); if (amount<0) throw std::runtime_error ("amount must be non-negative"); // no-op if (amount == 0) return; if (!ptr.getClass().isActor()) return; if (ptr.getClass().hasInventoryStore(ptr)) { // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); int numNotEquipped = store.count(item); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator it = store.getSlot (slot); if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { numNotEquipped -= it->getRefData().getCount(); } } for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) { MWWorld::ContainerStoreIterator it = store.getSlot (slot); if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); store.unequipItemQuantity(*it, ptr, numToRemove); numNotEquipped += numToRemove; } } for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) && !store.isEquipped(*iter)) { int removed = store.remove(*iter, amount, ptr); MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); dropped.getCellRef().setOwner(""); amount -= removed; if (amount <= 0) break; } } } MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), item, 1); MWWorld::Ptr itemPtr(ref.getPtr()); if (amount > 0) { if (itemPtr.getClass().getScript(itemPtr).empty()) { MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, amount); } else { // Dropping one item per time to prevent making stacks of scripted items for (int i = 0; i < amount; i++) MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, 1); } } MWBase::Environment::get().getSoundManager()->playSound3D(ptr, itemPtr.getClass().getDownSoundId(itemPtr), 1.f, 1.f); } }; template class OpDropSoulGem : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view soul = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().hasInventoryStore(ptr)) return; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { if (::Misc::StringUtils::ciEqual(iter->getCellRef().getSoul(), soul)) { MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); store.remove(*iter, 1, ptr); break; } } } }; template class OpGetAttacked : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getCreatureStats (ptr).getAttacked ()); } }; template class OpGetWeaponDrawn : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push((ptr.getClass().hasInventoryStore(ptr) || ptr.getClass().isBipedal(ptr)) && ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState::Weapon); } }; template class OpGetSpellReadied : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState::Spell); } }; template class OpGetSpellEffects : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view id = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!ptr.getClass().isActor()) { runtime.push(0); return; } const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getActiveSpells().isSpellActive(id)); } }; class OpGetCurrentTime : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); } }; template class OpSetDelete : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); int parameter = runtime[0].mInteger; runtime.pop(); if (parameter == 1) MWBase::Environment::get().getWorld()->deleteObject(ptr); else if (parameter == 0) MWBase::Environment::get().getWorld()->undeleteObject(ptr); else throw std::runtime_error("SetDelete: unexpected parameter"); } }; class OpGetSquareRoot : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { float param = runtime[0].mFloat; runtime.pop(); if (param < 0) throw std::runtime_error("square root of negative number (we aren't that imaginary)"); runtime.push(std::sqrt (param)); } }; template class OpFall : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { } }; template class OpGetStandingPc : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr)); } }; template class OpGetStandingActor : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (MWBase::Environment::get().getWorld()->getActorStandingOn(ptr)); } }; template class OpGetCollidingPc : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (MWBase::Environment::get().getWorld()->getPlayerCollidingWith(ptr)); } }; template class OpGetCollidingActor : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (MWBase::Environment::get().getWorld()->getActorCollidingWith(ptr)); } }; template class OpHurtStandingActor : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); float healthDiffPerSecond = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); } }; template class OpHurtCollidingActor : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); float healthDiffPerSecond = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); } }; class OpGetWindSpeed : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push(MWBase::Environment::get().getWorld()->getWindSpeed()); } }; template class OpHitOnMe : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view objectID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); bool hit = ::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject()); runtime.push(hit); if(hit) stats.clearLastHitObject(); } }; template class OpHitAttemptOnMe : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view objectID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); bool hit = ::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject()); runtime.push(hit); if(hit) stats.clearLastHitAttemptObject(); } }; template class OpEnableTeleporting : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); world->enableTeleporting(Enable); } }; template class OpEnableLevitation : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); world->enableLevitation(Enable); } }; template class OpShow : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime, false); std::string_view var = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::stringstream output; if (!ptr.isEmpty()) { const std::string& script = ptr.getClass().getScript(ptr); if (!script.empty()) { const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); char type = locals.getType(var); std::string refId = ptr.getCellRef().getRefId(); if (refId.find(' ') != std::string::npos) refId = '"' + refId + '"'; switch (type) { case 'l': case 's': output << refId << "." << var << " = " << ptr.getRefData().getLocals().getIntVar(script, var); break; case 'f': output << refId << "." << var << " = " << ptr.getRefData().getLocals().getFloatVar(script, var); break; } } } if (output.rdbuf()->in_avail() == 0) { MWBase::World *world = MWBase::Environment::get().getWorld(); char type = world->getGlobalVariableType (var); switch (type) { case 's': output << var << " = " << runtime.getContext().getGlobalShort (var); break; case 'l': output << var << " = " << runtime.getContext().getGlobalLong (var); break; case 'f': output << var << " = " << runtime.getContext().getGlobalFloat (var); break; default: output << "unknown variable"; } } runtime.getContext().report(output.str()); } }; template class OpShowVars : public Interpreter::Opcode0 { void printLocalVars(Interpreter::Runtime &runtime, const MWWorld::Ptr &ptr) { std::stringstream str; const std::string script = ptr.getClass().getScript(ptr); if(script.empty()) str<< ptr.getCellRef().getRefId()<<" does not have a script."; else { str<< "Local variables for "<getLocals(script); const std::vector *names = &complocals.get('s'); for(size_t i = 0;i < names->size();++i) { if(i >= locals.mShorts.size()) break; str<size();++i) { if(i >= locals.mLongs.size()) break; str<size();++i) { if(i >= locals.mFloats.size()) break; str< names = runtime.getContext().getGlobals(); for(size_t i = 0;i < names.size();++i) { char type = world->getGlobalVariableType (names[i]); str << std::endl << " " << names[i] << " = "; switch (type) { case 's': str << runtime.getContext().getGlobalShort (names[i]) << " (short)"; break; case 'l': str << runtime.getContext().getGlobalLong (names[i]) << " (long)"; break; case 'f': str << runtime.getContext().getGlobalFloat (names[i]) << " (float)"; break; default: str << ""; } } runtime.getContext().report (str.str()); } public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime, false); if (!ptr.isEmpty()) printLocalVars(runtime, ptr); else { // No reference, no problem. printGlobalVars(runtime); } } }; class OpToggleScripts : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleScripts(); runtime.getContext().report(enabled ? "Scripts -> On" : "Scripts -> Off"); } }; class OpToggleGodMode : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleGodMode(); runtime.getContext().report (enabled ? "God Mode -> On" : "God Mode -> Off"); } }; template class OpCast : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string spellId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); if (!spell) { runtime.getContext().report("spellcasting failed: cannot find spell \""+spellId+"\""); return; } if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spell->mId); return; } if (ptr.getClass().isActor()) { if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) { MWMechanics::AiCast castPackage(targetId, spell->mId, true); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); } return; } MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetId, false, false); if (target.isEmpty()) return; MWMechanics::CastSpell cast(ptr, target, false, true); cast.playSpellCastingEffects(spell->mId, false); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); } }; template class OpExplodeSpell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string spellId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); if (!spell) { runtime.getContext().report("spellcasting failed: cannot find spell \""+spellId+"\""); return; } if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spell->mId); return; } if (ptr.getClass().isActor()) { if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) { MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spell->mId, true); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); } return; } MWMechanics::CastSpell cast(ptr, ptr, false, true); cast.mHitPosition = ptr.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); } }; class OpGoToJail : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); world->goToJail(); } }; class OpPayFine : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; class OpPayFineThief : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; class OpGetPcInJail : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime &runtime) override { runtime.push (MWBase::Environment::get().getWorld()->isPlayerInJail()); } }; class OpGetPcTraveling : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime &runtime) override { runtime.push (MWBase::Environment::get().getWorld()->isPlayerTraveling()); } }; template class OpBetaComment : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime &runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); std::stringstream msg; msg << "Report time: "; std::time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); msg << std::put_time(std::gmtime(¤tTime), "%Y.%m.%d %T UTC") << std::endl; msg << "Content file: " << ptr.getCellRef().getRefNum().mContentFile; if (!ptr.getCellRef().hasContentFile()) msg << " [None]" << std::endl; else { std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); msg << " [" << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << "]" << std::endl; } msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; if (ptr.getRefData().isDeletedByContentFile()) msg << "[Deleted by content file]" << std::endl; if (!ptr.getRefData().getCount()) msg << "[Deleted]" << std::endl; msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; msg << "Memory address: " << ptr.getBase() << std::endl; if (ptr.isInCell()) { MWWorld::CellStore* cell = ptr.getCell(); msg << "Cell: " << MWBase::Environment::get().getWorld()->getCellName(cell) << std::endl; if (cell->getCell()->isExterior()) msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); msg << "Model: " << model << std::endl; if(!model.empty()) { const std::string archive = vfs->getArchive(model); if(!archive.empty()) msg << "(" << archive << ")" << std::endl; } if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } while (arg0 > 0) { std::string_view notes = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!notes.empty()) msg << "Notes: " << notes << std::endl; --arg0; } Log(Debug::Warning) << "\n" << msg.str(); runtime.getContext().report(msg.str()); } }; class OpAddToLevCreature : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { std::string levId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string_view creatureId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); addToLevList(&listCopy, creatureId, level); MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); } }; class OpRemoveFromLevCreature : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { std::string levId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string_view creatureId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); removeFromLevList(&listCopy, creatureId, level); MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); } }; class OpAddToLevItem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { std::string levId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string_view itemId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); addToLevList(&listCopy, itemId, level); MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); } }; class OpRemoveFromLevItem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime &runtime) override { std::string levId{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string_view itemId = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); removeFromLevList(&listCopy, itemId, level); MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); } }; template class OpShowSceneGraph : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime &runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime, false); int confirmed = 0; if (arg0==1) { confirmed = runtime[0].mInteger; runtime.pop(); } if (ptr.isEmpty() && !confirmed) runtime.getContext().report("Exporting the entire scene graph will result in a large file. Confirm this action using 'showscenegraph 1' or select an object instead."); else { const std::string& filename = MWBase::Environment::get().getWorld()->exportSceneGraph(ptr); runtime.getContext().report("Wrote '" + filename + "'"); } } }; class OpToggleNavMesh : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh); runtime.getContext().report (enabled ? "Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off"); } }; class OpToggleActorsPaths : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths); runtime.getContext().report (enabled ? "Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off"); } }; class OpSetNavMeshNumberToRender : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { const auto navMeshNumber = runtime[0].mInteger; runtime.pop(); if (navMeshNumber < 0) { runtime.getContext().report("Invalid navmesh number: use not less than zero values"); return; } MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast(navMeshNumber)); } }; template class OpRepairedOnMe : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { // Broken in vanilla and deliberately no-op. runtime.push(0); } }; class OpToggleRecastMesh : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_RecastMesh); runtime.getContext().report (enabled ? "Recast Mesh Rendering -> On" : "Recast Mesh Rendering -> Off"); } }; class OpHelp : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { std::stringstream message; message << MWBase::Environment::get().getWindowManager()->getVersionDescription() << "\n\n"; std::vector commands; MWBase::Environment::get().getScriptManager()->getExtensions().listKeywords(commands); for(const auto& command : commands) message << command << "\n"; runtime.getContext().report(message.str()); } }; class OpReloadLua : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getLuaManager()->reloadAllScripts(); runtime.getContext().report("All Lua scripts are reloaded"); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Misc::opcodeMenuMode); interpreter.installSegment5(Compiler::Misc::opcodeRandom); interpreter.installSegment5(Compiler::Misc::opcodeScriptRunning); interpreter.installSegment5>(Compiler::Misc::opcodeStartScript); interpreter.installSegment5>(Compiler::Misc::opcodeStartScriptExplicit); interpreter.installSegment5(Compiler::Misc::opcodeStopScript); interpreter.installSegment5(Compiler::Misc::opcodeGetSecondsPassed); interpreter.installSegment5>(Compiler::Misc::opcodeEnable); interpreter.installSegment5>(Compiler::Misc::opcodeEnableExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeDisable); interpreter.installSegment5>(Compiler::Misc::opcodeDisableExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabled); interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabledExplicit); interpreter.installSegment5(Compiler::Misc::opcodeXBox); interpreter.installSegment5>(Compiler::Misc::opcodeOnActivate); interpreter.installSegment5>(Compiler::Misc::opcodeOnActivateExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeActivate); interpreter.installSegment5>(Compiler::Misc::opcodeActivateExplicit); interpreter.installSegment3>(Compiler::Misc::opcodeLock); interpreter.installSegment3>(Compiler::Misc::opcodeLockExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeUnlock); interpreter.installSegment5>(Compiler::Misc::opcodeUnlockExplicit); interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionDebug); interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionBoxes); interpreter.installSegment5(Compiler::Misc::opcodeToggleWireframe); interpreter.installSegment5(Compiler::Misc::opcodeFadeIn); interpreter.installSegment5(Compiler::Misc::opcodeFadeOut); interpreter.installSegment5(Compiler::Misc::opcodeFadeTo); interpreter.installSegment5(Compiler::Misc::opcodeTogglePathgrid); interpreter.installSegment5(Compiler::Misc::opcodeToggleWater); interpreter.installSegment5(Compiler::Misc::opcodeToggleWorld); interpreter.installSegment5(Compiler::Misc::opcodeDontSaveObject); interpreter.installSegment5(Compiler::Misc::opcodePcForce1stPerson); interpreter.installSegment5(Compiler::Misc::opcodePcForce3rdPerson); interpreter.installSegment5(Compiler::Misc::opcodePcGet3rdPerson); interpreter.installSegment5(Compiler::Misc::opcodeToggleVanityMode); interpreter.installSegment5(Compiler::Misc::opcodeGetPcSleep); interpreter.installSegment5(Compiler::Misc::opcodeGetPcJumping); interpreter.installSegment5(Compiler::Misc::opcodeWakeUpPc); interpreter.installSegment5(Compiler::Misc::opcodePlayBink); interpreter.installSegment5(Compiler::Misc::opcodePayFine); interpreter.installSegment5(Compiler::Misc::opcodePayFineThief); interpreter.installSegment5(Compiler::Misc::opcodeGoToJail); interpreter.installSegment5>(Compiler::Misc::opcodeGetLocked); interpreter.installSegment5>(Compiler::Misc::opcodeGetLockedExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetEffect); interpreter.installSegment5>(Compiler::Misc::opcodeGetEffectExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGem); interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGemExplicit); interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGem); interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGemExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeDrop); interpreter.installSegment5>(Compiler::Misc::opcodeDropExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGem); interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGemExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetAttacked); interpreter.installSegment5>(Compiler::Misc::opcodeGetAttackedExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawn); interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawnExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadied); interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadiedExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffects); interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffectsExplicit); interpreter.installSegment5(Compiler::Misc::opcodeGetCurrentTime); interpreter.installSegment5>(Compiler::Misc::opcodeSetDelete); interpreter.installSegment5>(Compiler::Misc::opcodeSetDeleteExplicit); interpreter.installSegment5(Compiler::Misc::opcodeGetSquareRoot); interpreter.installSegment5>(Compiler::Misc::opcodeFall); interpreter.installSegment5>(Compiler::Misc::opcodeFallExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPc); interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPcExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingActor); interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingActorExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPc); interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPcExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingActor); interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingActorExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeHurtStandingActor); interpreter.installSegment5>(Compiler::Misc::opcodeHurtStandingActorExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeHurtCollidingActor); interpreter.installSegment5>(Compiler::Misc::opcodeHurtCollidingActorExplicit); interpreter.installSegment5(Compiler::Misc::opcodeGetWindSpeed); interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMe); interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMeExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMe); interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMeExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeDisableTeleporting); interpreter.installSegment5>(Compiler::Misc::opcodeEnableTeleporting); interpreter.installSegment5>(Compiler::Misc::opcodeShowVars); interpreter.installSegment5>(Compiler::Misc::opcodeShowVarsExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeShow); interpreter.installSegment5>(Compiler::Misc::opcodeShowExplicit); interpreter.installSegment5(Compiler::Misc::opcodeToggleGodMode); interpreter.installSegment5(Compiler::Misc::opcodeToggleScripts); interpreter.installSegment5>(Compiler::Misc::opcodeDisableLevitation); interpreter.installSegment5>(Compiler::Misc::opcodeEnableLevitation); interpreter.installSegment5>(Compiler::Misc::opcodeCast); interpreter.installSegment5>(Compiler::Misc::opcodeCastExplicit); interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpell); interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpellExplicit); interpreter.installSegment5(Compiler::Misc::opcodeGetPcInJail); interpreter.installSegment5(Compiler::Misc::opcodeGetPcTraveling); interpreter.installSegment3>(Compiler::Misc::opcodeBetaComment); interpreter.installSegment3>(Compiler::Misc::opcodeBetaCommentExplicit); interpreter.installSegment5(Compiler::Misc::opcodeAddToLevCreature); interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevCreature); interpreter.installSegment5(Compiler::Misc::opcodeAddToLevItem); interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevItem); interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraph); interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraphExplicit); interpreter.installSegment5(Compiler::Misc::opcodeToggleBorders); interpreter.installSegment5(Compiler::Misc::opcodeToggleNavMesh); interpreter.installSegment5(Compiler::Misc::opcodeToggleActorsPaths); interpreter.installSegment5(Compiler::Misc::opcodeSetNavMeshNumberToRender); interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMe); interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMeExplicit); interpreter.installSegment5(Compiler::Misc::opcodeToggleRecastMesh); interpreter.installSegment5(Compiler::Misc::opcodeHelp); interpreter.installSegment5(Compiler::Misc::opcodeReloadLua); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/miscextensions.hpp000066400000000000000000000004701445372753700243410ustar00rootroot00000000000000#ifndef GAME_SCRIPT_MISCEXTENSIONS_H #define GAME_SCRIPT_MISCEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { namespace Misc { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/ref.cpp000066400000000000000000000015341445372753700220370ustar00rootroot00000000000000#include "ref.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "interpretercontext.hpp" MWWorld::Ptr MWScript::ExplicitRef::operator() (Interpreter::Runtime& runtime, bool required, bool activeOnly) const { std::string_view id = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (required) return MWBase::Environment::get().getWorld()->getPtr(id, activeOnly); else return MWBase::Environment::get().getWorld()->searchPtr(id, activeOnly); } MWWorld::Ptr MWScript::ImplicitRef::operator() (Interpreter::Runtime& runtime, bool required, bool activeOnly) const { MWScript::InterpreterContext& context = static_cast (runtime.getContext()); return context.getReference(required); } openmw-openmw-0.48.0/apps/openmw/mwscript/ref.hpp000066400000000000000000000011231445372753700220360ustar00rootroot00000000000000#ifndef GAME_MWSCRIPT_REF_H #define GAME_MWSCRIPT_REF_H #include "../mwworld/ptr.hpp" namespace Interpreter { class Runtime; } namespace MWScript { struct ExplicitRef { static constexpr bool implicit = false; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; }; struct ImplicitRef { static constexpr bool implicit = true; MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/scriptmanagerimp.cpp000066400000000000000000000153271445372753700246350ustar00rootroot00000000000000#include "scriptmanagerimp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "extensions.hpp" #include "interpretercontext.hpp" namespace MWScript { ScriptManager::ScriptManager (const MWWorld::ESMStore& store, Compiler::Context& compilerContext, int warningsMode, const std::vector& scriptBlacklist) : mErrorHandler(), mStore (store), mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext), mOpcodesInstalled (false), mGlobalScripts (store) { mErrorHandler.setWarningsMode (warningsMode); mScriptBlacklist.resize (scriptBlacklist.size()); std::transform (scriptBlacklist.begin(), scriptBlacklist.end(), mScriptBlacklist.begin(), Misc::StringUtils::lowerCase); std::sort (mScriptBlacklist.begin(), mScriptBlacklist.end()); } bool ScriptManager::compile (const std::string& name) { mParser.reset(); mErrorHandler.reset(); if (const ESM::Script *script = mStore.get().find (name)) { mErrorHandler.setContext(name); bool Success = true; try { std::istringstream input (script->mScriptText); Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions()); scanner.scan (mParser); if (!mErrorHandler.isGood()) Success = false; } catch (const Compiler::SourceException&) { // error has already been reported via error handler Success = false; } catch (const std::exception& error) { Log(Debug::Error) << "Error: An exception has been thrown: " << error.what(); Success = false; } if (!Success) { Log(Debug::Error) << "Error: script compiling failed: " << name; } if (Success) { std::vector code; mParser.getCode(code); mScripts.emplace(name, CompiledScript(code, mParser.getLocals())); return true; } } return false; } bool ScriptManager::run (const std::string& name, Interpreter::Context& interpreterContext) { // compile script ScriptCollection::iterator iter = mScripts.find (name); if (iter==mScripts.end()) { if (!compile (name)) { // failed -> ignore script from now on. std::vector empty; mScripts.emplace(name, CompiledScript(empty, Compiler::Locals())); return false; } iter = mScripts.find (name); assert (iter!=mScripts.end()); } // execute script std::string target = Misc::StringUtils::lowerCase(interpreterContext.getTarget()); if (!iter->second.mByteCode.empty() && iter->second.mInactive.find(target) == iter->second.mInactive.end()) try { if (!mOpcodesInstalled) { installOpcodes (mInterpreter); mOpcodesInstalled = true; } mInterpreter.run (&iter->second.mByteCode[0], iter->second.mByteCode.size(), interpreterContext); return true; } catch (const MissingImplicitRefError& e) { Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); } catch (const std::exception& e) { Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); iter->second.mInactive.insert(target); // don't execute again. } return false; } void ScriptManager::clear() { for (auto& script : mScripts) { script.second.mInactive.clear(); } mGlobalScripts.clear(); } std::pair ScriptManager::compileAll() { int count = 0; int success = 0; for (auto& script : mStore.get()) { if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), Misc::StringUtils::lowerCase(script.mId))) { ++count; if (compile(script.mId)) ++success; } } return std::make_pair (count, success); } const Compiler::Locals& ScriptManager::getLocals (const std::string& name) { std::string name2 = Misc::StringUtils::lowerCase (name); { auto iter = mScripts.find (name2); if (iter!=mScripts.end()) return iter->second.mLocals; } { auto iter = mOtherLocals.find (name2); if (iter!=mOtherLocals.end()) return iter->second; } if (const ESM::Script *script = mStore.get().search (name2)) { Compiler::Locals locals; const Compiler::ContextOverride override(mErrorHandler, name2 + "[local variables]"); std::istringstream stream (script->mScriptText); Compiler::QuickFileParser parser (mErrorHandler, mCompilerContext, locals); Compiler::Scanner scanner (mErrorHandler, stream, mCompilerContext.getExtensions()); try { scanner.scan (parser); } catch (const Compiler::SourceException&) { // error has already been reported via error handler locals.clear(); } catch (const std::exception& error) { Log(Debug::Error) << "Error: An exception has been thrown: " << error.what(); locals.clear(); } auto iter = mOtherLocals.emplace(name2, locals).first; return iter->second; } throw std::logic_error ("script " + name + " does not exist"); } GlobalScripts& ScriptManager::getGlobalScripts() { return mGlobalScripts; } const Compiler::Extensions& ScriptManager::getExtensions() const { return *mCompilerContext.getExtensions(); } } openmw-openmw-0.48.0/apps/openmw/mwscript/scriptmanagerimp.hpp000066400000000000000000000050331445372753700246330ustar00rootroot00000000000000#ifndef GAME_SCRIPT_SCRIPTMANAGER_H #define GAME_SCRIPT_SCRIPTMANAGER_H #include #include #include #include #include #include #include #include "../mwbase/scriptmanager.hpp" #include "globalscripts.hpp" namespace MWWorld { class ESMStore; } namespace Compiler { class Context; } namespace Interpreter { class Context; class Interpreter; } namespace MWScript { class ScriptManager : public MWBase::ScriptManager { Compiler::StreamErrorHandler mErrorHandler; const MWWorld::ESMStore& mStore; Compiler::Context& mCompilerContext; Compiler::FileParser mParser; Interpreter::Interpreter mInterpreter; bool mOpcodesInstalled; struct CompiledScript { std::vector mByteCode; Compiler::Locals mLocals; std::set mInactive; CompiledScript(const std::vector& code, const Compiler::Locals& locals): mByteCode(code), mLocals(locals) {} }; typedef std::map ScriptCollection; ScriptCollection mScripts; GlobalScripts mGlobalScripts; std::map mOtherLocals; std::vector mScriptBlacklist; public: ScriptManager (const MWWorld::ESMStore& store, Compiler::Context& compilerContext, int warningsMode, const std::vector& scriptBlacklist); void clear() override; bool run (const std::string& name, Interpreter::Context& interpreterContext) override; ///< Run the script with the given name (compile first, if not compiled yet) bool compile (const std::string& name) override; ///< Compile script with the given namen /// \return Success? std::pair compileAll() override; ///< Compile all scripts /// \return count, success const Compiler::Locals& getLocals (const std::string& name) override; ///< Return locals for script \a name. GlobalScripts& getGlobalScripts() override; const Compiler::Extensions& getExtensions() const override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/skyextensions.cpp000066400000000000000000000111561445372753700242120ustar00rootroot00000000000000#include "skyextensions.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "interpretercontext.hpp" namespace MWScript { namespace Sky { class OpToggleSky : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleSky(); runtime.getContext().report (enabled ? "Sky -> On" : "Sky -> Off"); } }; class OpTurnMoonWhite : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->setMoonColour (false); } }; class OpTurnMoonRed : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->setMoonColour (true); } }; class OpGetMasserPhase : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWorld()->getMasserPhase()); } }; class OpGetSecundaPhase : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWorld()->getSecundaPhase()); } }; class OpGetCurrentWeather : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.push (MWBase::Environment::get().getWorld()->getCurrentWeather()); } }; class OpChangeWeather : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string region{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); Interpreter::Type_Integer id = runtime[0].mInteger; runtime.pop(); const ESM::Region* reg = MWBase::Environment::get().getWorld()->getStore().get().search(region); if (reg) MWBase::Environment::get().getWorld()->changeWeather(region, id); else runtime.getContext().report("Warning: Region \"" + region + "\" was not found"); } }; class OpModRegion : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { std::string region{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::vector chances; chances.reserve(10); while(arg0 > 0) { chances.push_back(std::clamp(runtime[0].mInteger, 0, 127)); runtime.pop(); arg0--; } MWBase::Environment::get().getWorld()->modRegion(region, chances); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::Sky::opcodeToggleSky); interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonWhite); interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonRed); interpreter.installSegment5(Compiler::Sky::opcodeGetMasserPhase); interpreter.installSegment5(Compiler::Sky::opcodeGetSecundaPhase); interpreter.installSegment5(Compiler::Sky::opcodeGetCurrentWeather); interpreter.installSegment5(Compiler::Sky::opcodeChangeWeather); interpreter.installSegment3(Compiler::Sky::opcodeModRegion); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/skyextensions.hpp000066400000000000000000000005431445372753700242150ustar00rootroot00000000000000#ifndef GAME_SCRIPT_SKYEXTENSIONS_H #define GAME_SCRIPT_SKYEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief sky-related script functionality namespace Sky { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/soundextensions.cpp000066400000000000000000000221511445372753700245310ustar00rootroot00000000000000#include "soundextensions.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Sound { template class OpSay : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWScript::InterpreterContext& context = static_cast (runtime.getContext()); std::string file{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); std::string text{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); MWBase::Environment::get().getSoundManager()->say (ptr, file); if (MWBase::Environment::get().getWindowManager ()->getSubtitlesEnabled()) context.messageBox (text); } }; template class OpSayDone : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (MWBase::Environment::get().getSoundManager()->sayDone (ptr)); } }; class OpStreamMusic : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string sound{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); MWBase::Environment::get().getSoundManager()->streamMusic (sound); } }; class OpPlaySound : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view sound = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound(sound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } }; class OpPlaySoundVP : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view sound = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float volume = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound(sound, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } }; template class OpPlaySound3D : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view sound = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWSound::Type::Sfx, TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); } }; template class OpPlaySoundVP3D : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view sound = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float volume = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, MWSound::Type::Sfx, TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); } }; template class OpStopSound : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view sound = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr, sound); } }; template class OpGetSoundPlaying : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); int index = runtime[0].mInteger; runtime.pop(); bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying ( ptr, runtime.getStringLiteral (index)); // GetSoundPlaying called on an equipped item should also look for sounds played by the equipping actor. if (!ret && ptr.getContainerStore()) { MWWorld::Ptr cont = MWBase::Environment::get().getWorld()->findContainer(ptr); if (!cont.isEmpty() && cont.getClass().hasInventoryStore(cont) && cont.getClass().getInventoryStore(cont).isEquipped(ptr)) { ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying ( cont, runtime.getStringLiteral (index)); } } runtime.push(ret); } }; void installOpcodes(Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Sound::opcodeSay); interpreter.installSegment5>(Compiler::Sound::opcodeSayDone); interpreter.installSegment5(Compiler::Sound::opcodeStreamMusic); interpreter.installSegment5(Compiler::Sound::opcodePlaySound); interpreter.installSegment5(Compiler::Sound::opcodePlaySoundVP); interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3D); interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DVP); interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3D); interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DVP); interpreter.installSegment5>(Compiler::Sound::opcodeStopSound); interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlaying); interpreter.installSegment5>(Compiler::Sound::opcodeSayExplicit); interpreter.installSegment5>(Compiler::Sound::opcodeSayDoneExplicit); interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DExplicit); interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DVPExplicit); interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DExplicit); interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DVPExplicit); interpreter.installSegment5>(Compiler::Sound::opcodeStopSoundExplicit); interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlayingExplicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/soundextensions.hpp000066400000000000000000000005501445372753700245350ustar00rootroot00000000000000#ifndef GAME_SCRIPT_SOUNDEXTENSIONS_H #define GAME_SCRIPT_SOUNDEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { namespace Sound { // Script-extensions related to sound void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/statsextensions.cpp000066400000000000000000001667241445372753700245560ustar00rootroot00000000000000#include "statsextensions.hpp" #include #include #include "../mwworld/esmstore.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/spellcasting.hpp" #include "ref.hpp" namespace { std::string getDialogueActorFaction(const MWWorld::ConstPtr& actor) { std::string factionId = actor.getClass().getPrimaryFaction(actor); if (factionId.empty()) throw std::runtime_error ( "failed to determine dialogue actors faction (because actor is factionless)"); return factionId; } void modStat(MWMechanics::AttributeValue& stat, float amount) { const float base = stat.getBase(); const float modifier = stat.getModifier() - stat.getDamage(); const float modified = base + modifier; // Clamp to 100 unless base < 100 and we have a fortification going if((modifier <= 0.f || base >= 100.f) && amount > 0.f) amount = std::clamp(100.f - modified, 0.f, amount); // Clamp the modified value in a way that doesn't properly account for negative numbers float newModified = modified + amount; if(newModified < 0.f) { if(modified >= 0.f) newModified = 0.f; else if(newModified < modified) newModified = modified; } // Calculate damage/fortification based on the clamped base value stat.setBase(std::clamp(base + amount, 0.f, 100.f), true); stat.setModifier(newModified - stat.getBase()); } } namespace MWScript { namespace Stats { template class OpGetLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass() .getCreatureStats (ptr) .getLevel(); runtime.push (value); } }; template class OpSetLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ptr.getClass() .getCreatureStats (ptr) .setLevel(value); } }; template class OpGetAttribute : public Interpreter::Opcode0 { int mIndex; public: OpGetAttribute (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = ptr.getClass() .getCreatureStats (ptr) .getAttribute(mIndex) .getModified(); runtime.push (value); } }; template class OpSetAttribute : public Interpreter::Opcode0 { int mIndex; public: OpSetAttribute (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); attribute.setBase(value, true); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; template class OpModAttribute : public Interpreter::Opcode0 { int mIndex; public: OpModAttribute (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass() .getCreatureStats(ptr) .getAttribute(mIndex); modStat(attribute, value); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; template class OpGetDynamic : public Interpreter::Opcode0 { int mIndex; public: OpGetDynamic (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value; if (mIndex==0 && ptr.getClass().hasItemHealth (ptr)) { // health is a special case value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); } else { value = ptr.getClass() .getCreatureStats(ptr) .getDynamic(mIndex) .getCurrent(); // GetMagicka shouldn't return negative values if(mIndex == 1 && value < 0) value = 0; } runtime.push (value); } }; template class OpSetDynamic : public Interpreter::Opcode0 { int mIndex; public: OpSetDynamic (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); stat.setBase(value); stat.setCurrent(stat.getModified(false), true, true); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } }; template class OpModDynamic : public Interpreter::Opcode0 { int mIndex; public: OpModDynamic (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { int peek = R::implicit ? 0 : runtime[0].mInteger; MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && ::Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) { runtime.push (peek); if (R()(runtime, false, true).isEmpty()) { Log(Debug::Warning) << "Warning: Compensating for broken script in Morrowind.esm by " << "ignoring remote access to dagoth_ur_1"; return; } } MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWMechanics::DynamicStat stat = stats.getDynamic(mIndex); float current = stat.getCurrent(); float base = diff + stat.getBase(); if(mIndex != 2) base = std::max(base, 0.f); stat.setBase(base); stat.setCurrent(diff + current, true, true); stats.setDynamic (mIndex, stat); } }; template class OpModCurrentDynamic : public Interpreter::Opcode0 { int mIndex; public: OpModCurrentDynamic (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); bool allowDecreaseBelowZero = false; if (mIndex == 2) // Fatigue-specific logic { // For fatigue, a negative current value is allowed and means the actor will be knocked down allowDecreaseBelowZero = true; // Knock down the actor immediately if a non-positive new value is the case if (diff + current <= 0.f) ptr.getClass().getCreatureStats(ptr).setKnockedDown(true); } stat.setCurrent (diff + current, allowDecreaseBelowZero); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } }; template class OpGetDynamicGetRatio : public Interpreter::Opcode0 { int mIndex; public: OpGetDynamicGetRatio (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getDynamic(mIndex).getRatio()); } }; template class OpGetSkill : public Interpreter::Opcode0 { int mIndex; public: OpGetSkill (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mIndex); runtime.push (value); } }; template class OpSetSkill : public Interpreter::Opcode0 { int mIndex; public: OpSetSkill (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); stats.getSkill(mIndex).setBase(value, true); } }; template class OpModSkill : public Interpreter::Opcode0 { int mIndex; public: OpModSkill (int index) : mIndex (index) {} void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::SkillValue &skill = ptr.getClass() .getNpcStats(ptr) .getSkill(mIndex); modStat(skill, value); } }; class OpGetPCCrimeLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); runtime.push (static_cast (player.getClass().getNpcStats (player).getBounty())); } }; class OpSetPCCrimeLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); int bounty = static_cast(runtime[0].mFloat); runtime.pop(); player.getClass().getNpcStats (player).setBounty(bounty); if (bounty == 0) MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; class OpModPCCrimeLevel : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); runtime.pop(); } }; template class OpAddSpell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string id{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find (id); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().add(spell); ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { // Add spell effect to *this actor's* queue immediately creatureStats.getActiveSpells().addSpell(spell, ptr); // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } } }; template class OpRemoveSpell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string id{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); if (ptr == MWMechanics::getPlayer() && id == wm->getSelectedSpell()) { wm->unsetSelectedSpell(); } } }; template class OpRemoveSpellEffects : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view spellid = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(ptr, spellid); } }; template class OpRemoveEffects : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; template class OpGetSpell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string id{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); Interpreter::Type_Integer value = 0; if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) value = 1; runtime.push (value); } }; template class OpPCJoinFaction : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID; if(arg0==0) { factionID = getDialogueActorFaction(actor); } else { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).joinFaction(factionID); } } }; template class OpPCRaiseRank : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID; if(arg0==0) { factionID = getDialogueActorFaction(actor); } else { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { MWWorld::Ptr player = MWMechanics::getPlayer(); if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) == player.getClass().getNpcStats(player).getFactionRanks().end()) { player.getClass().getNpcStats(player).joinFaction(factionID); } else { player.getClass().getNpcStats(player).raiseRank(factionID); } } } }; template class OpPCLowerRank : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID; if(arg0==0) { factionID = getDialogueActorFaction(actor); } else { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).lowerRank(factionID); } } }; template class OpGetPCRank : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID; if(arg0 >0) { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) { runtime.push(player.getClass().getNpcStats(player).getFactionRanks().at(factionID)); } else { runtime.push(-1); } } else { runtime.push(-1); } } }; template class OpModDisposition : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (ptr.getClass().isNpc()) ptr.getClass().getNpcStats (ptr).setBaseDisposition (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); // else: must not throw exception (used by an Almalexia dialogue script) } }; template class OpSetDisposition : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (ptr.getClass().isNpc()) ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); } }; template class OpGetDisposition : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.getClass().isNpc()) runtime.push(0); else runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); } }; class OpGetDeadCount : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string id{runtime.getStringLiteral(runtime[0].mInteger)}; runtime[0].mInteger = MWBase::Environment::get().getMechanicsManager()->countDeaths (id); } }; template class OpGetPCFacRep : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionId; if (arg0==1) { factionId = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push ( player.getClass().getNpcStats (player).getFactionReputation (factionId)); } }; template class OpSetPCFacRep : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); std::string factionId; if (arg0==1) { factionId = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); } }; template class OpModPCFacRep : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); std::string factionId; if (arg0==1) { factionId = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, player.getClass().getNpcStats (player).getFactionReputation (factionId)+ value); } }; template class OpGetCommonDisease : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getClass().getCreatureStats (ptr).hasCommonDisease()); } }; template class OpGetBlightDisease : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push (ptr.getClass().getCreatureStats (ptr).hasBlightDisease()); } }; template class OpGetRace : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::ConstPtr ptr = R()(runtime); std::string_view race = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); const std::string& npcRace = ptr.get()->mBase->mRace; runtime.push(::Misc::StringUtils::ciEqual(race, npcRace)); } }; class OpGetWerewolfKills : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); runtime.push (ptr.getClass().getNpcStats (ptr).getWerewolfKills ()); } }; template class OpPcExpelled : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } ::Misc::StringUtils::lowerCaseInPlace(factionID); MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); } else { runtime.push(0); } } }; template class OpPcExpell : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { player.getClass().getNpcStats(player).expell(factionID); } } }; template class OpPcClearExpelled : public Interpreter::Opcode1 { public: void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") player.getClass().getNpcStats(player).clearExpelled(factionID); } }; template class OpRaiseRank : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string factionID = ptr.getClass().getPrimaryFaction(ptr); if(factionID.empty()) return; MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) return; // If we already changed rank for this NPC, modify current rank in the NPC stats. // Otherwise take rank from base NPC record, increase it and put it to NPC data. int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); if (currentRank >= 0) ptr.getClass().getNpcStats(ptr).raiseRank(factionID); else { int rank = ptr.getClass().getPrimaryFactionRank(ptr); rank++; ptr.getClass().getNpcStats(ptr).joinFaction(factionID); for (int i=0; i class OpLowerRank : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string factionID = ptr.getClass().getPrimaryFaction(ptr); if(factionID.empty()) return; MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) return; // If we already changed rank for this NPC, modify current rank in the NPC stats. // Otherwise take rank from base NPC record, decrease it and put it to NPC data. int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); if (currentRank == 0) return; else if (currentRank > 0) ptr.getClass().getNpcStats(ptr).lowerRank(factionID); else { int rank = ptr.getClass().getPrimaryFactionRank(ptr); rank--; ptr.getClass().getNpcStats(ptr).joinFaction(factionID); for (int i=0; i class OpOnDeath : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).hasDied(); if (value) ptr.getClass().getCreatureStats (ptr).clearHasDied(); runtime.push (value); } }; template class OpOnMurder : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).hasBeenMurdered(); if (value) ptr.getClass().getCreatureStats (ptr).clearHasBeenMurdered(); runtime.push (value); } }; template class OpOnKnockout : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).getKnockedDownOneFrame(); runtime.push (value); } }; template class OpIsWerewolf : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); } }; template class OpSetWerewolf : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); } }; template class OpSetWerewolfAcrobatics : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); } }; template class OpResurrect : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended) MWBase::Environment::get().getStateManager()->resumeGame(); } else if (ptr.getClass().getCreatureStats(ptr).isDead()) { bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); auto windowManager = MWBase::Environment::get().getWindowManager(); bool wasOpen = windowManager->containsMode(MWGui::GM_Container); windowManager->onDeleteCustomData(ptr); // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(ptr); if (wasOpen && !windowManager->containsMode(MWGui::GM_Container)) { // Reopen the loot GUI if it was closed because we resurrected the actor we were looting MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); windowManager->forceLootMode(ptr); } else { MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(nullptr); } if (wasEnabled) MWBase::Environment::get().getWorld()->enable(ptr); } } }; template class OpGetStat : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { // dummy runtime.pop(); runtime.push(0); } }; template class OpGetMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpGetMagicEffect (int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); // GetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); int ret = static_cast(currentValue); runtime.push(ret); } }; template class OpSetMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpSetMagicEffect (int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); // SetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); int arg = runtime[0].mInteger; runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; template class OpModMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpModMagicEffect (int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute(Interpreter::Runtime &runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); int arg = runtime[0].mInteger; runtime.pop(); stats.getMagicEffects().modifyBase(mPositiveEffect, arg); } }; struct MagicEffect { int mPositiveEffect; int mNegativeEffect; }; void installOpcodes (Interpreter::Interpreter& interpreter) { for (int i=0; i>(Compiler::Stats::opcodeGetAttribute + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeGetAttributeExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetAttribute + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetAttributeExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModAttribute + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModAttributeExplicit + i, i); } for (int i=0; i>(Compiler::Stats::opcodeGetDynamic + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamic + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModDynamic + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModCurrentDynamic + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModCurrentDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicGetRatio + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicGetRatioExplicit + i, i); } for (int i=0; i>(Compiler::Stats::opcodeGetSkill + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeGetSkillExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetSkill + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetSkillExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModSkill + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModSkillExplicit + i, i); } interpreter.installSegment5(Compiler::Stats::opcodeGetPCCrimeLevel); interpreter.installSegment5(Compiler::Stats::opcodeSetPCCrimeLevel); interpreter.installSegment5(Compiler::Stats::opcodeModPCCrimeLevel); interpreter.installSegment5>(Compiler::Stats::opcodeAddSpell); interpreter.installSegment5>(Compiler::Stats::opcodeAddSpellExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpell); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffects); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffectsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeResurrect); interpreter.installSegment5>(Compiler::Stats::opcodeResurrectExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffects); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffectsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetSpell); interpreter.installSegment5>(Compiler::Stats::opcodeGetSpellExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRank); interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRank); interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFaction); interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRankExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRankExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFactionExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRank); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeModDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeModDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeSetDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeSetDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeGetDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetLevel); interpreter.installSegment5>(Compiler::Stats::opcodeGetLevelExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeSetLevel); interpreter.installSegment5>(Compiler::Stats::opcodeSetLevelExplicit); interpreter.installSegment5(Compiler::Stats::opcodeGetDeadCount); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRepExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRepExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRepExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDisease); interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDiseaseExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDisease); interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDiseaseExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetRace); interpreter.installSegment5>(Compiler::Stats::opcodeGetRaceExplicit); interpreter.installSegment5(Compiler::Stats::opcodeGetWerewolfKills); interpreter.installSegment3>(Compiler::Stats::opcodePcExpelled); interpreter.installSegment3>(Compiler::Stats::opcodePcExpelledExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePcExpell); interpreter.installSegment3>(Compiler::Stats::opcodePcExpellExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelled); interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelledExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRank); interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeLowerRank); interpreter.installSegment5>(Compiler::Stats::opcodeLowerRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnDeath); interpreter.installSegment5>(Compiler::Stats::opcodeOnDeathExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnMurder); interpreter.installSegment5>(Compiler::Stats::opcodeOnMurderExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockout); interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockoutExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolf); interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolfExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolf); interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolfExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolf); interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolfExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeSetWerewolfAcrobatics); interpreter.installSegment5>(Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetStat); interpreter.installSegment5>(Compiler::Stats::opcodeGetStatExplicit); static const MagicEffect sMagicEffects[] = { { ESM::MagicEffect::ResistMagicka, ESM::MagicEffect::WeaknessToMagicka }, { ESM::MagicEffect::ResistFire, ESM::MagicEffect::WeaknessToFire }, { ESM::MagicEffect::ResistFrost, ESM::MagicEffect::WeaknessToFrost }, { ESM::MagicEffect::ResistShock, ESM::MagicEffect::WeaknessToShock }, { ESM::MagicEffect::ResistCommonDisease, ESM::MagicEffect::WeaknessToCommonDisease }, { ESM::MagicEffect::ResistBlightDisease, ESM::MagicEffect::WeaknessToBlightDisease }, { ESM::MagicEffect::ResistCorprusDisease, ESM::MagicEffect::WeaknessToCorprusDisease }, { ESM::MagicEffect::ResistPoison, ESM::MagicEffect::WeaknessToPoison }, { ESM::MagicEffect::ResistParalysis, -1 }, { ESM::MagicEffect::ResistNormalWeapons, ESM::MagicEffect::WeaknessToNormalWeapons }, { ESM::MagicEffect::WaterBreathing, -1 }, { ESM::MagicEffect::Chameleon, -1 }, { ESM::MagicEffect::WaterWalking, -1 }, { ESM::MagicEffect::SwiftSwim, -1 }, { ESM::MagicEffect::Jump, -1 }, { ESM::MagicEffect::Levitate, -1 }, { ESM::MagicEffect::Shield, -1 }, { ESM::MagicEffect::Sound, -1 }, { ESM::MagicEffect::Silence, -1 }, { ESM::MagicEffect::Blind, -1 }, { ESM::MagicEffect::Paralyze, -1 }, { ESM::MagicEffect::Invisibility, -1 }, { ESM::MagicEffect::FortifyAttack, -1 }, { ESM::MagicEffect::Sanctuary, -1 }, }; for (int i=0; i<24; ++i) { int positive = sMagicEffects[i].mPositiveEffect; int negative = sMagicEffects[i].mNegativeEffect; interpreter.installSegment5>(Compiler::Stats::opcodeGetMagicEffect + i, positive, negative); interpreter.installSegment5>(Compiler::Stats::opcodeGetMagicEffectExplicit + i, positive, negative); interpreter.installSegment5>(Compiler::Stats::opcodeSetMagicEffect + i, positive, negative); interpreter.installSegment5>(Compiler::Stats::opcodeSetMagicEffectExplicit + i, positive, negative); interpreter.installSegment5>(Compiler::Stats::opcodeModMagicEffect + i, positive, negative); interpreter.installSegment5>(Compiler::Stats::opcodeModMagicEffectExplicit + i, positive, negative); } } } } openmw-openmw-0.48.0/apps/openmw/mwscript/statsextensions.hpp000066400000000000000000000006001445372753700245370ustar00rootroot00000000000000#ifndef GAME_SCRIPT_STATSEXTENSIONS_H #define GAME_SCRIPT_STATSEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief stats-related script functionality (creatures and NPCs) namespace Stats { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/transformationextensions.cpp000066400000000000000000001126501445372753700264530ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellutils.hpp" #include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Transformation { void moveStandingActors(const MWWorld::Ptr &ptr, const osg::Vec3f& diff) { std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); } template class OpGetDistance : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr from = R()(runtime, !R::implicit); std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (from.isEmpty()) { std::string error = "Missing implicit ref"; runtime.getContext().report(error); Log(Debug::Error) << error; runtime.push(0.f); return; } if (from.getContainerStore()) // is the object contained? { MWWorld::Ptr container = MWBase::Environment::get().getWorld()->findContainer(from); if (!container.isEmpty()) from = container; else { std::string error = "Failed to find the container of object '" + from.getCellRef().getRefId() + "'"; runtime.getContext().report(error); Log(Debug::Error) << error; runtime.push(0.f); return; } } const MWWorld::Ptr to = MWBase::Environment::get().getWorld()->searchPtr(name, false); if (to.isEmpty()) { std::string error = "Failed to find an instance of object '" + std::string(name) + "'"; runtime.getContext().report(error); Log(Debug::Error) << error; runtime.push(0.f); return; } float distance; // If the objects are in different worldspaces, return a large value (just like vanilla) if (!to.isInCell() || !from.isInCell() || to.getCell()->getCell()->getCellId().mWorldspace != from.getCell()->getCell()->getCellId().mWorldspace) distance = std::numeric_limits::max(); else { double diff[3]; const float* const pos1 = to.getRefData().getPosition().pos; const float* const pos2 = from.getRefData().getPosition().pos; for (int i=0; i<3; ++i) diff[i] = pos1[i] - pos2[i]; distance = static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); } runtime.push(distance); } }; template class OpSetScale : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float scale = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWorld()->scaleObject(ptr,scale); } }; template class OpGetScale : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getCellRef().getScale()); } }; template class OpModScale : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float scale = runtime[0].mFloat; runtime.pop(); // add the parameter to the object's scale. MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().getScale() + scale); } }; template class OpSetAngle : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float angle = osg::DegreesToRadians(runtime[0].mFloat); runtime.pop(); float ax = ptr.getRefData().getPosition().rot[0]; float ay = ptr.getRefData().getPosition().rot[1]; float az = ptr.getRefData().getPosition().rot[2]; // XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle. // UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game. if (axis == "x") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(angle,ay,az),MWBase::RotationFlag_inverseOrder); else if (axis == "y") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,angle,az),MWBase::RotationFlag_inverseOrder); else if (axis == "z") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,ay,angle),MWBase::RotationFlag_inverseOrder); else if (axis == "u") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(angle,ay,az),MWBase::RotationFlag_none); else if (axis == "w") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,angle,az),MWBase::RotationFlag_none); else if (axis == "v") MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,ay,angle),MWBase::RotationFlag_none); } }; template class OpGetStartingAngle : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); float ret = 0.f; if (!axis.empty()) { if (axis[0] == 'x') { ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0]); } else if (axis[0] == 'y') { ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1]); } else if (axis[0] == 'z') { ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2]); } } runtime.push(ret); } }; template class OpGetAngle : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); float ret = 0.f; if (!axis.empty()) { if (axis[0] == 'x') { ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); } else if (axis[0] == 'y') { ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); } else if (axis[0] == 'z') { ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2]); } } runtime.push(ret); } }; template class OpGetPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); float ret = 0.f; if (!axis.empty()) { if (axis[0] == 'x') { ret = ptr.getRefData().getPosition().pos[0]; } else if (axis[0] == 'y') { ret = ptr.getRefData().getPosition().pos[1]; } else if (axis[0] == 'z') { ret = ptr.getRefData().getPosition().pos[2]; } } runtime.push(ret); } }; template class OpSetPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; runtime.pop(); if (!ptr.isInCell()) return; // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. const auto curPos = ptr.getRefData().getPosition().asVec3(); auto newPos = curPos; if(axis == "x") { newPos[0] = pos; } else if(axis == "y") { newPos[1] = pos; } else if(axis == "z") { // We should not place actors under ground if (ptr.getClass().isActor()) { float terrainHeight = -std::numeric_limits::max(); if (ptr.getCell()->isExterior()) terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); if (pos < terrainHeight) pos = terrainHeight; } newPos[2] = pos; } else { return; } dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); } }; template class OpGetStartingPos : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); float ret = 0.f; if (!axis.empty()) { if (axis[0] == 'x') { ret = ptr.getCellRef().getPosition().pos[0]; } else if (axis[0] == 'y') { ret = ptr.getCellRef().getPosition().pos[1]; } else if (axis[0] == 'z') { ret = ptr.getCellRef().getPosition().pos[2]; } } runtime.push(ret); } }; template class OpPositionCell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float zRot = runtime[0].mFloat; runtime.pop(); std::string cellID{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); if (ptr.getContainerStore()) return; bool isPlayer = ptr == MWMechanics::getPlayer(); auto world = MWBase::Environment::get().getWorld(); if (isPlayer) { world->getPlayer().setTeleported(true); } MWWorld::CellStore* store = nullptr; try { store = world->getInterior(cellID); } catch(std::exception&) { // cell not found, move to exterior instead if moving the player (vanilla PositionCell compatibility) const ESM::Cell* cell = world->getExterior(cellID); if(!cell) { std::string error = "Warning: PositionCell: unknown interior cell (" + cellID + ")"; if(isPlayer) error += ", moving to exterior instead"; runtime.getContext().report (error); Log(Debug::Warning) << error; if(!isPlayer) return; } const osg::Vec2i cellIndex = MWWorld::positionToCellIndex(x, y); store = world->getExterior(cellIndex.x(), cellIndex.y()); } if(store) { MWWorld::Ptr base = ptr; ptr = world->moveObject(ptr,store,osg::Vec3f(x,y,z)); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); auto rot = ptr.getRefData().getPosition().asRotationVec3(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(!isPlayer) zRot = zRot/60.0f; rot.z() = osg::DegreesToRadians(zRot); world->rotateObject(ptr,rot); ptr.getClass().adjustPosition(ptr, isPlayer || !world->isCellActive(ptr.getCell())); } } }; template class OpPosition : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float zRot = runtime[0].mFloat; runtime.pop(); if (!ptr.isInCell()) return; bool isPlayer = ptr == MWMechanics::getPlayer(); auto world = MWBase::Environment::get().getWorld(); if (isPlayer) { world->getPlayer().setTeleported(true); } const osg::Vec2i cellIndex = MWWorld::positionToCellIndex(x, y); // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. MWWorld::Ptr base = ptr; if (isPlayer) { MWWorld::CellStore* cell = world->getExterior(cellIndex.x(), cellIndex.y()); ptr = world->moveObject(ptr, cell, osg::Vec3(x, y, z)); } else { ptr = world->moveObject(ptr, osg::Vec3f(x, y, z), true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); auto rot = ptr.getRefData().getPosition().asRotationVec3(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(!isPlayer) zRot = zRot/60.0f; rot.z() = osg::DegreesToRadians(zRot); world->rotateObject(ptr,rot); ptr.getClass().adjustPosition(ptr, isPlayer || !world->isCellActive(ptr.getCell())); } }; class OpPlaceItemCell : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view itemID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::string cellID{runtime.getStringLiteral(runtime[0].mInteger)}; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); MWWorld::CellStore* store = nullptr; try { store = MWBase::Environment::get().getWorld()->getInterior(cellID); } catch(std::exception&) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); const osg::Vec2i cellIndex = MWWorld::positionToCellIndex(x, y); store = MWBase::Environment::get().getWorld()->getExterior(cellIndex.x(), cellIndex.y()); if(!cell) { runtime.getContext().report ("unknown cell (" + cellID + ")"); Log(Debug::Error) << "Error: unknown cell (" << cellID << ")"; } } if(store) { ESM::Position pos; pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } } }; class OpPlaceItem : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { std::string_view itemID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); MWWorld::Ptr player = MWMechanics::getPlayer(); if (!player.isInCell()) throw std::runtime_error("player not in a cell"); MWWorld::CellStore* store = nullptr; if (player.getCell()->isExterior()) { const osg::Vec2i cellIndex = MWWorld::positionToCellIndex(x, y); store = MWBase::Environment::get().getWorld()->getExterior(cellIndex.x(), cellIndex.y()); } else store = player.getCell(); ESM::Position pos; pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } }; template class OpPlaceAt : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = pc ? MWMechanics::getPlayer() : R()(runtime); std::string_view itemID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); Interpreter::Type_Float distance = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Integer direction = runtime[0].mInteger; runtime.pop(); if (direction < 0 || direction > 3) throw std::runtime_error ("invalid direction"); if (count<0) throw std::runtime_error ("count must be non-negative"); if (!actor.isInCell()) throw std::runtime_error ("actor is not in a cell"); for (int i=0; igetStore(), itemID, 1); ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); MWBase::Environment::get().getWorld()->scaleObject(ptr, actor.getCellRef().getScale()); } } }; template class OpRotate : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { const MWWorld::Ptr& ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); auto rot = ptr.getRefData().getPosition().asRotationVec3(); // Regardless of the axis argument, the player may only be rotated on Z if (axis == "z" || MWMechanics::getPlayer() == ptr) rot.z() += rotation; else if (axis == "x") rot.x() += rotation; else if (axis == "y") rot.y() += rotation; MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); } }; template class OpRotateWorld : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); if (!ptr.getRefData().getBaseNode()) return; // We can rotate actors only around Z axis if (ptr.getClass().isActor() && (axis == "x" || axis == "y")) return; osg::Quat rot; if (axis == "x") rot = osg::Quat(rotation, -osg::X_AXIS); else if (axis == "y") rot = osg::Quat(rotation, -osg::Y_AXIS); else if (axis == "z") rot = osg::Quat(rotation, -osg::Z_AXIS); else return; osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude(); MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot); } }; template class OpSetAtStart : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.isInCell()) return; MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3()); dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3())); } }; template class OpMove : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { const MWWorld::Ptr& ptr = R()(runtime); if (!ptr.isInCell()) return; std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); osg::Vec3f posChange; if (axis == "x") { posChange=osg::Vec3f(movement, 0, 0); } else if (axis == "y") { posChange=osg::Vec3f(0, movement, 0); } else if (axis == "z") { posChange=osg::Vec3f(0, 0, movement); } else return; // is it correct that disabled objects can't be Move-d? if (!ptr.getRefData().getBaseNode()) return; osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; // We should move actors, standing on moving object, too. // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; template class OpMoveWorld : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.isInCell()) return; std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); osg::Vec3f diff; if (axis == "x") diff.x() = movement; else if (axis == "y") diff.y() = movement; else if (axis == "z") diff.z() = movement; else return; // We should move actors, standing on moving object, too. // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; class OpResetActors : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->resetActors(); } }; class OpFixme : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->fixPosition(); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Transformation::opcodeGetDistance); interpreter.installSegment5>(Compiler::Transformation::opcodeGetDistanceExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeSetScale); interpreter.installSegment5>(Compiler::Transformation::opcodeSetScaleExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngle); interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngleExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeGetScale); interpreter.installSegment5>(Compiler::Transformation::opcodeGetScaleExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngle); interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngleExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeGetPos); interpreter.installSegment5>(Compiler::Transformation::opcodeGetPosExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeSetPos); interpreter.installSegment5>(Compiler::Transformation::opcodeSetPosExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingPos); interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingPosExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodePosition); interpreter.installSegment5>(Compiler::Transformation::opcodePositionExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodePositionCell); interpreter.installSegment5>(Compiler::Transformation::opcodePositionCellExplicit); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem); interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtPc); interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtMe); interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtMeExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeModScale); interpreter.installSegment5>(Compiler::Transformation::opcodeModScaleExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeRotate); interpreter.installSegment5>(Compiler::Transformation::opcodeRotateExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeRotateWorld); interpreter.installSegment5>(Compiler::Transformation::opcodeRotateWorldExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStart); interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStartExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeMove); interpreter.installSegment5>(Compiler::Transformation::opcodeMoveExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorld); interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorldExplicit); interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingAngle); interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingAngleExplicit); interpreter.installSegment5(Compiler::Transformation::opcodeResetActors); interpreter.installSegment5(Compiler::Transformation::opcodeFixme); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/transformationextensions.hpp000066400000000000000000000006331445372753700264550ustar00rootroot00000000000000#ifndef GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H #define GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief stats-related script functionality (creatures and NPCs) namespace Transformation { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwscript/userextensions.cpp000066400000000000000000000045331445372753700243630ustar00rootroot00000000000000#include "userextensions.hpp" #include #include #include #include #include #include #include "ref.hpp" namespace MWScript { /// Temporary script extensions. /// /// \attention Do not commit changes to this file to a git repository! namespace User { class OpUser1 : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report ("user1: not in use"); } }; class OpUser2 : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { runtime.getContext().report ("user2: not in use"); } }; template class OpUser3 : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { // MWWorld::Ptr ptr = R()(runtime); runtime.getContext().report ("user3: not in use"); } }; template class OpUser4 : public Interpreter::Opcode0 { public: void execute (Interpreter::Runtime& runtime) override { // MWWorld::Ptr ptr = R()(runtime); runtime.getContext().report ("user4: not in use"); } }; void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5(Compiler::User::opcodeUser1); interpreter.installSegment5(Compiler::User::opcodeUser2); interpreter.installSegment5>(Compiler::User::opcodeUser3); interpreter.installSegment5>(Compiler::User::opcodeUser3Explicit); interpreter.installSegment5>(Compiler::User::opcodeUser4); interpreter.installSegment5>(Compiler::User::opcodeUser4Explicit); } } } openmw-openmw-0.48.0/apps/openmw/mwscript/userextensions.hpp000066400000000000000000000005631445372753700243670ustar00rootroot00000000000000#ifndef GAME_SCRIPT_USEREXTENSIONS_H #define GAME_SCRIPT_USEREXTENSIONS_H namespace Compiler { class Extensions; } namespace Interpreter { class Interpreter; } namespace MWScript { /// \brief Temporary script functionality limited to the console namespace User { void installOpcodes (Interpreter::Interpreter& interpreter); } } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/000077500000000000000000000000001445372753700204005ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwsound/alext.h000066400000000000000000000455241445372753700217000ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2008 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to https://www.gnu.org/copyleft/lgpl.html */ #ifndef AL_ALEXT_H #define AL_ALEXT_H #include /* Define int64_t and uint64_t types */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #include #elif defined(_WIN32) && defined(__GNUC__) #include #elif defined(_WIN32) typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else /* Fallback if nothing above works */ #include #endif #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif #ifndef AL_LOKI_IMA_ADPCM_format #define AL_LOKI_IMA_ADPCM_format 1 #define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 #define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 #endif #ifndef AL_LOKI_WAVE_format #define AL_LOKI_WAVE_format 1 #define AL_FORMAT_WAVE_EXT 0x10002 #endif #ifndef AL_EXT_vorbis #define AL_EXT_vorbis 1 #define AL_FORMAT_VORBIS_EXT 0x10003 #endif #ifndef AL_LOKI_quadriphonic #define AL_LOKI_quadriphonic 1 #define AL_FORMAT_QUAD8_LOKI 0x10004 #define AL_FORMAT_QUAD16_LOKI 0x10005 #endif #ifndef AL_EXT_float32 #define AL_EXT_float32 1 #define AL_FORMAT_MONO_FLOAT32 0x10010 #define AL_FORMAT_STEREO_FLOAT32 0x10011 #endif #ifndef AL_EXT_double #define AL_EXT_double 1 #define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 #define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 #endif #ifndef AL_EXT_MULAW #define AL_EXT_MULAW 1 #define AL_FORMAT_MONO_MULAW_EXT 0x10014 #define AL_FORMAT_STEREO_MULAW_EXT 0x10015 #endif #ifndef AL_EXT_ALAW #define AL_EXT_ALAW 1 #define AL_FORMAT_MONO_ALAW_EXT 0x10016 #define AL_FORMAT_STEREO_ALAW_EXT 0x10017 #endif #ifndef ALC_LOKI_audio_channel #define ALC_LOKI_audio_channel 1 #define ALC_CHAN_MAIN_LOKI 0x500001 #define ALC_CHAN_PCM_LOKI 0x500002 #define ALC_CHAN_CD_LOKI 0x500003 #endif #ifndef AL_EXT_MCFORMATS #define AL_EXT_MCFORMATS 1 #define AL_FORMAT_QUAD8 0x1204 #define AL_FORMAT_QUAD16 0x1205 #define AL_FORMAT_QUAD32 0x1206 #define AL_FORMAT_REAR8 0x1207 #define AL_FORMAT_REAR16 0x1208 #define AL_FORMAT_REAR32 0x1209 #define AL_FORMAT_51CHN8 0x120A #define AL_FORMAT_51CHN16 0x120B #define AL_FORMAT_51CHN32 0x120C #define AL_FORMAT_61CHN8 0x120D #define AL_FORMAT_61CHN16 0x120E #define AL_FORMAT_61CHN32 0x120F #define AL_FORMAT_71CHN8 0x1210 #define AL_FORMAT_71CHN16 0x1211 #define AL_FORMAT_71CHN32 0x1212 #endif #ifndef AL_EXT_MULAW_MCFORMATS #define AL_EXT_MULAW_MCFORMATS 1 #define AL_FORMAT_MONO_MULAW 0x10014 #define AL_FORMAT_STEREO_MULAW 0x10015 #define AL_FORMAT_QUAD_MULAW 0x10021 #define AL_FORMAT_REAR_MULAW 0x10022 #define AL_FORMAT_51CHN_MULAW 0x10023 #define AL_FORMAT_61CHN_MULAW 0x10024 #define AL_FORMAT_71CHN_MULAW 0x10025 #endif #ifndef AL_EXT_IMA4 #define AL_EXT_IMA4 1 #define AL_FORMAT_MONO_IMA4 0x1300 #define AL_FORMAT_STEREO_IMA4 0x1301 #endif #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 typedef ALvoid (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); #ifdef AL_ALEXT_PROTOTYPES AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); #endif #endif #ifndef ALC_EXT_EFX #define ALC_EXT_EFX 1 #include "efx.h" #endif #ifndef ALC_EXT_disconnect #define ALC_EXT_disconnect 1 #define ALC_CONNECTED 0x313 #endif #ifndef ALC_EXT_thread_local_context #define ALC_EXT_thread_local_context 1 typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); #ifdef AL_ALEXT_PROTOTYPES ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); #endif #endif #ifndef AL_EXT_source_distance_model #define AL_EXT_source_distance_model 1 #define AL_SOURCE_DISTANCE_MODEL 0x200 #endif #ifndef AL_SOFT_buffer_sub_data #define AL_SOFT_buffer_sub_data 1 #define AL_BYTE_RW_OFFSETS_SOFT 0x1031 #define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 typedef ALvoid (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); #ifdef AL_ALEXT_PROTOTYPES AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); #endif #endif #ifndef AL_SOFT_loop_points #define AL_SOFT_loop_points 1 #define AL_LOOP_POINTS_SOFT 0x2015 #endif #ifndef AL_EXT_FOLDBACK #define AL_EXT_FOLDBACK 1 #define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" #define AL_FOLDBACK_EVENT_BLOCK 0x4112 #define AL_FOLDBACK_EVENT_START 0x4111 #define AL_FOLDBACK_EVENT_STOP 0x4113 #define AL_FOLDBACK_MODE_MONO 0x4101 #define AL_FOLDBACK_MODE_STEREO 0x4102 typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); AL_API void AL_APIENTRY alRequestFoldbackStop(void); #endif #endif #ifndef ALC_EXT_DEDICATED #define ALC_EXT_DEDICATED 1 #define AL_DEDICATED_GAIN 0x0001 #define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 #define AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT 0x9000 #endif #ifndef AL_SOFT_buffer_samples #define AL_SOFT_buffer_samples 1 /* Channel configurations */ #define AL_MONO_SOFT 0x1500 #define AL_STEREO_SOFT 0x1501 #define AL_REAR_SOFT 0x1502 #define AL_QUAD_SOFT 0x1503 #define AL_5POINT1_SOFT 0x1504 #define AL_6POINT1_SOFT 0x1505 #define AL_7POINT1_SOFT 0x1506 /* Sample types */ #define AL_BYTE_SOFT 0x1400 #define AL_UNSIGNED_BYTE_SOFT 0x1401 #define AL_SHORT_SOFT 0x1402 #define AL_UNSIGNED_SHORT_SOFT 0x1403 #define AL_INT_SOFT 0x1404 #define AL_UNSIGNED_INT_SOFT 0x1405 #define AL_FLOAT_SOFT 0x1406 #define AL_DOUBLE_SOFT 0x1407 #define AL_BYTE3_SOFT 0x1408 #define AL_UNSIGNED_BYTE3_SOFT 0x1409 /* Storage formats */ #define AL_MONO8_SOFT 0x1100 #define AL_MONO16_SOFT 0x1101 #define AL_MONO32F_SOFT 0x10010 #define AL_STEREO8_SOFT 0x1102 #define AL_STEREO16_SOFT 0x1103 #define AL_STEREO32F_SOFT 0x10011 #define AL_QUAD8_SOFT 0x1204 #define AL_QUAD16_SOFT 0x1205 #define AL_QUAD32F_SOFT 0x1206 #define AL_REAR8_SOFT 0x1207 #define AL_REAR16_SOFT 0x1208 #define AL_REAR32F_SOFT 0x1209 #define AL_5POINT1_8_SOFT 0x120A #define AL_5POINT1_16_SOFT 0x120B #define AL_5POINT1_32F_SOFT 0x120C #define AL_6POINT1_8_SOFT 0x120D #define AL_6POINT1_16_SOFT 0x120E #define AL_6POINT1_32F_SOFT 0x120F #define AL_7POINT1_8_SOFT 0x1210 #define AL_7POINT1_16_SOFT 0x1211 #define AL_7POINT1_32F_SOFT 0x1212 /* Buffer attributes */ #define AL_INTERNAL_FORMAT_SOFT 0x2008 #define AL_BYTE_LENGTH_SOFT 0x2009 #define AL_SAMPLE_LENGTH_SOFT 0x200A #define AL_SEC_LENGTH_SOFT 0x200B typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); #endif #endif #ifndef AL_SOFT_direct_channels #define AL_SOFT_direct_channels 1 #define AL_DIRECT_CHANNELS_SOFT 0x1033 #endif #ifndef ALC_SOFT_loopback #define ALC_SOFT_loopback 1 #define ALC_FORMAT_CHANNELS_SOFT 0x1990 #define ALC_FORMAT_TYPE_SOFT 0x1991 /* Sample types */ #define ALC_BYTE_SOFT 0x1400 #define ALC_UNSIGNED_BYTE_SOFT 0x1401 #define ALC_SHORT_SOFT 0x1402 #define ALC_UNSIGNED_SHORT_SOFT 0x1403 #define ALC_INT_SOFT 0x1404 #define ALC_UNSIGNED_INT_SOFT 0x1405 #define ALC_FLOAT_SOFT 0x1406 /* Channel configurations */ #define ALC_MONO_SOFT 0x1500 #define ALC_STEREO_SOFT 0x1501 #define ALC_QUAD_SOFT 0x1503 #define ALC_5POINT1_SOFT 0x1504 #define ALC_6POINT1_SOFT 0x1505 #define ALC_7POINT1_SOFT 0x1506 typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); #ifdef AL_ALEXT_PROTOTYPES ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); #endif #endif #ifndef AL_EXT_STEREO_ANGLES #define AL_EXT_STEREO_ANGLES 1 #define AL_STEREO_ANGLES 0x1030 #endif #ifndef AL_EXT_SOURCE_RADIUS #define AL_EXT_SOURCE_RADIUS 1 #define AL_SOURCE_RADIUS 0x1031 #endif #ifndef AL_SOFT_source_latency #define AL_SOFT_source_latency 1 #define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 #define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 typedef int64_t ALint64SOFT; typedef uint64_t ALuint64SOFT; typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); #endif #endif #ifndef ALC_EXT_DEFAULT_FILTER_ORDER #define ALC_EXT_DEFAULT_FILTER_ORDER 1 #define ALC_DEFAULT_FILTER_ORDER 0x1100 #endif #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 #define AL_DEFERRED_UPDATES_SOFT 0xC002 typedef ALvoid (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); typedef ALvoid (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); #ifdef AL_ALEXT_PROTOTYPES AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void); AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void); #endif #endif #ifndef AL_SOFT_block_alignment #define AL_SOFT_block_alignment 1 #define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C #define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D #endif #ifndef AL_SOFT_MSADPCM #define AL_SOFT_MSADPCM 1 #define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 #define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 #endif #ifndef AL_SOFT_source_length #define AL_SOFT_source_length 1 /*#define AL_BYTE_LENGTH_SOFT 0x2009*/ /*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ /*#define AL_SEC_LENGTH_SOFT 0x200B*/ #endif #ifndef ALC_SOFT_pause_device #define ALC_SOFT_pause_device 1 typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); #ifdef AL_ALEXT_PROTOTYPES ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); #endif #endif #ifndef AL_EXT_BFORMAT #define AL_EXT_BFORMAT 1 #define AL_FORMAT_BFORMAT2D_8 0x20021 #define AL_FORMAT_BFORMAT2D_16 0x20022 #define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 #define AL_FORMAT_BFORMAT3D_8 0x20031 #define AL_FORMAT_BFORMAT3D_16 0x20032 #define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 #endif #ifndef AL_EXT_MULAW_BFORMAT #define AL_EXT_MULAW_BFORMAT 1 #define AL_FORMAT_BFORMAT2D_MULAW 0x10031 #define AL_FORMAT_BFORMAT3D_MULAW 0x10032 #endif #ifndef ALC_SOFT_HRTF #define ALC_SOFT_HRTF 1 #define ALC_HRTF_SOFT 0x1992 #define ALC_DONT_CARE_SOFT 0x0002 #define ALC_HRTF_STATUS_SOFT 0x1993 #define ALC_HRTF_DISABLED_SOFT 0x0000 #define ALC_HRTF_ENABLED_SOFT 0x0001 #define ALC_HRTF_DENIED_SOFT 0x0002 #define ALC_HRTF_REQUIRED_SOFT 0x0003 #define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 #define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 #define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 #define ALC_HRTF_SPECIFIER_SOFT 0x1995 #define ALC_HRTF_ID_SOFT 0x1996 typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); #ifdef AL_ALEXT_PROTOTYPES ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); #endif #endif #ifndef AL_SOFT_gain_clamp_ex #define AL_SOFT_gain_clamp_ex 1 #define AL_GAIN_LIMIT_SOFT 0x200E #endif #ifndef AL_SOFT_source_resampler #define AL_SOFT_source_resampler #define AL_NUM_RESAMPLERS_SOFT 0x1210 #define AL_DEFAULT_RESAMPLER_SOFT 0x1211 #define AL_SOURCE_RESAMPLER_SOFT 0x1212 #define AL_RESAMPLER_NAME_SOFT 0x1213 typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); #ifdef AL_ALEXT_PROTOTYPES AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); #endif #endif #ifndef AL_SOFT_source_spatialize #define AL_SOFT_source_spatialize #define AL_SOURCE_SPATIALIZE_SOFT 0x1214 #define AL_AUTO_SOFT 0x0002 #endif #ifndef ALC_SOFT_output_limiter #define ALC_SOFT_output_limiter #define ALC_OUTPUT_LIMITER_SOFT 0x199A #endif #ifdef __cplusplus } #endif #endif openmw-openmw-0.48.0/apps/openmw/mwsound/efx-presets.h000066400000000000000000001052301445372753700230170ustar00rootroot00000000000000/* Reverb presets for EFX */ #ifndef EFX_PRESETS_H #define EFX_PRESETS_H #ifndef EFXEAXREVERBPROPERTIES_DEFINED #define EFXEAXREVERBPROPERTIES_DEFINED typedef struct { float flDensity; float flDiffusion; float flGain; float flGainHF; float flGainLF; float flDecayTime; float flDecayHFRatio; float flDecayLFRatio; float flReflectionsGain; float flReflectionsDelay; float flReflectionsPan[3]; float flLateReverbGain; float flLateReverbDelay; float flLateReverbPan[3]; float flEchoTime; float flEchoDepth; float flModulationTime; float flModulationDepth; float flAirAbsorptionGainHF; float flHFReference; float flLFReference; float flRoomRolloffFactor; int iDecayHFLimit; } EFXEAXREVERBPROPERTIES, *LPEFXEAXREVERBPROPERTIES; #endif /* Default Presets */ #define EFX_REVERB_PRESET_GENERIC \ { 1.0000f, 1.0000f, 0.3162f, 0.8913f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PADDEDCELL \ { 0.1715f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.1700f, 0.1000f, 1.0000f, 0.2500f, 0.0010f, { 0.0000f, 0.0000f, 0.0000f }, 1.2691f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ROOM \ { 0.4287f, 1.0000f, 0.3162f, 0.5929f, 1.0000f, 0.4000f, 0.8300f, 1.0000f, 0.1503f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.0629f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_BATHROOM \ { 0.1715f, 1.0000f, 0.3162f, 0.2512f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.6531f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 3.2734f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_LIVINGROOM \ { 0.9766f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.5000f, 0.1000f, 1.0000f, 0.2051f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2805f, 0.0040f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_STONEROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 2.3100f, 0.6400f, 1.0000f, 0.4411f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1003f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_AUDITORIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.5781f, 1.0000f, 4.3200f, 0.5900f, 1.0000f, 0.4032f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7170f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CONCERTHALL \ { 1.0000f, 1.0000f, 0.3162f, 0.5623f, 1.0000f, 3.9200f, 0.7000f, 1.0000f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.9977f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CAVE \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 2.9100f, 1.3000f, 1.0000f, 0.5000f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.7063f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_ARENA \ { 1.0000f, 1.0000f, 0.3162f, 0.4477f, 1.0000f, 7.2400f, 0.3300f, 1.0000f, 0.2612f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.0186f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_HANGAR \ { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 10.0500f, 0.2300f, 1.0000f, 0.5000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2560f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CARPETEDHALLWAY \ { 0.4287f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 0.3000f, 0.1000f, 1.0000f, 0.1215f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.1531f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_HALLWAY \ { 0.3645f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 1.4900f, 0.5900f, 1.0000f, 0.2458f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.6615f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_STONECORRIDOR \ { 1.0000f, 1.0000f, 0.3162f, 0.7612f, 1.0000f, 2.7000f, 0.7900f, 1.0000f, 0.2472f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 1.5758f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ALLEY \ { 1.0000f, 0.3000f, 0.3162f, 0.7328f, 1.0000f, 1.4900f, 0.8600f, 1.0000f, 0.2500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.9954f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.9500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FOREST \ { 1.0000f, 0.3000f, 0.3162f, 0.0224f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.0525f, 0.1620f, { 0.0000f, 0.0000f, 0.0000f }, 0.7682f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY \ { 1.0000f, 0.5000f, 0.3162f, 0.3981f, 1.0000f, 1.4900f, 0.6700f, 1.0000f, 0.0730f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1427f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_MOUNTAINS \ { 1.0000f, 0.2700f, 0.3162f, 0.0562f, 1.0000f, 1.4900f, 0.2100f, 1.0000f, 0.0407f, 0.3000f, { 0.0000f, 0.0000f, 0.0000f }, 0.1919f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_QUARRY \ { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0000f, 0.0610f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.7000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PLAIN \ { 1.0000f, 0.2100f, 0.3162f, 0.1000f, 1.0000f, 1.4900f, 0.5000f, 1.0000f, 0.0585f, 0.1790f, { 0.0000f, 0.0000f, 0.0000f }, 0.1089f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PARKINGLOT \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 1.6500f, 1.5000f, 1.0000f, 0.2082f, 0.0080f, { 0.0000f, 0.0000f, 0.0000f }, 0.2652f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SEWERPIPE \ { 0.3071f, 0.8000f, 0.3162f, 0.3162f, 1.0000f, 2.8100f, 0.1400f, 1.0000f, 1.6387f, 0.0140f, { 0.0000f, 0.0000f, 0.0000f }, 3.2471f, 0.0210f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_UNDERWATER \ { 0.3645f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 1.4900f, 0.1000f, 1.0000f, 0.5963f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 7.0795f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 1.1800f, 0.3480f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRUGGED \ { 0.4287f, 0.5000f, 0.3162f, 1.0000f, 1.0000f, 8.3900f, 1.3900f, 1.0000f, 0.8760f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 3.1081f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DIZZY \ { 0.3645f, 0.6000f, 0.3162f, 0.6310f, 1.0000f, 17.2300f, 0.5600f, 1.0000f, 0.1392f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4937f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.8100f, 0.3100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PSYCHOTIC \ { 0.0625f, 0.5000f, 0.3162f, 0.8404f, 1.0000f, 7.5600f, 0.9100f, 1.0000f, 0.4864f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 2.4378f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 4.0000f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Castle Presets */ #define EFX_REVERB_PRESET_CASTLE_SMALLROOM \ { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 1.2200f, 0.8300f, 0.3100f, 0.8913f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE \ { 1.0000f, 0.8900f, 0.3162f, 0.3162f, 0.1000f, 2.3200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_MEDIUMROOM \ { 1.0000f, 0.9300f, 0.3162f, 0.2818f, 0.1000f, 2.0400f, 0.8300f, 0.4600f, 0.6310f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1550f, 0.0300f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_LARGEROOM \ { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.1259f, 2.5300f, 0.8300f, 0.5000f, 0.4467f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1850f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_LONGPASSAGE \ { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 3.4200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_HALL \ { 1.0000f, 0.8100f, 0.3162f, 0.2818f, 0.1778f, 3.1400f, 0.7900f, 0.6200f, 0.1778f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_CUPBOARD \ { 1.0000f, 0.8900f, 0.3162f, 0.2818f, 0.1000f, 0.6700f, 0.8700f, 0.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 3.5481f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_COURTYARD \ { 1.0000f, 0.4200f, 0.3162f, 0.4467f, 0.1995f, 2.1300f, 0.6100f, 0.2300f, 0.2239f, 0.1600f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3700f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CASTLE_ALCOVE \ { 1.0000f, 0.8900f, 0.3162f, 0.5012f, 0.1000f, 1.6400f, 0.8700f, 0.3100f, 1.0000f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } /* Factory Presets */ #define EFX_REVERB_PRESET_FACTORY_SMALLROOM \ { 0.3645f, 0.8200f, 0.3162f, 0.7943f, 0.5012f, 1.7200f, 0.6500f, 1.3100f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.1190f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE \ { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 2.5300f, 0.6500f, 1.3100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_MEDIUMROOM \ { 0.4287f, 0.8200f, 0.2512f, 0.7943f, 0.5012f, 2.7600f, 0.6500f, 1.3100f, 0.2818f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1740f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_LARGEROOM \ { 0.4287f, 0.7500f, 0.2512f, 0.7079f, 0.6310f, 4.2400f, 0.5100f, 1.3100f, 0.1778f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2310f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_LONGPASSAGE \ { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 4.0600f, 0.6500f, 1.3100f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_HALL \ { 0.4287f, 0.7500f, 0.3162f, 0.7079f, 0.6310f, 7.4300f, 0.5100f, 1.3100f, 0.0631f, 0.0730f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_CUPBOARD \ { 0.3071f, 0.6300f, 0.2512f, 0.7943f, 0.5012f, 0.4900f, 0.6500f, 1.3100f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.1070f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_COURTYARD \ { 0.3071f, 0.5700f, 0.3162f, 0.3162f, 0.6310f, 2.3200f, 0.2900f, 0.5600f, 0.2239f, 0.1400f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2900f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_ALCOVE \ { 0.3645f, 0.5900f, 0.2512f, 0.7943f, 0.5012f, 3.1400f, 0.6500f, 1.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1140f, 0.1000f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } /* Ice Palace Presets */ #define EFX_REVERB_PRESET_ICEPALACE_SMALLROOM \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 1.5100f, 1.5300f, 0.2700f, 0.8913f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1640f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_SHORTPASSAGE \ { 1.0000f, 0.7500f, 0.3162f, 0.5623f, 0.2818f, 1.7900f, 1.4600f, 0.2800f, 0.5012f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_MEDIUMROOM \ { 1.0000f, 0.8700f, 0.3162f, 0.5623f, 0.4467f, 2.2200f, 1.5300f, 0.3200f, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_LARGEROOM \ { 1.0000f, 0.8100f, 0.3162f, 0.5623f, 0.4467f, 3.1400f, 1.5300f, 0.3200f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_LONGPASSAGE \ { 1.0000f, 0.7700f, 0.3162f, 0.5623f, 0.3981f, 3.0100f, 1.4600f, 0.2800f, 0.7943f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.0400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_HALL \ { 1.0000f, 0.7600f, 0.3162f, 0.4467f, 0.5623f, 5.4900f, 1.5300f, 0.3800f, 0.1122f, 0.0540f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0520f, { 0.0000f, 0.0000f, 0.0000f }, 0.2260f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_CUPBOARD \ { 1.0000f, 0.8300f, 0.3162f, 0.5012f, 0.2239f, 0.7600f, 1.5300f, 0.2600f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1430f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_COURTYARD \ { 1.0000f, 0.5900f, 0.3162f, 0.2818f, 0.3162f, 2.0400f, 1.2000f, 0.3800f, 0.3162f, 0.1730f, { 0.0000f, 0.0000f, 0.0000f }, 0.3162f, 0.0430f, { 0.0000f, 0.0000f, 0.0000f }, 0.2350f, 0.4800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_ALCOVE \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 2.7600f, 1.4600f, 0.2800f, 1.1220f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1610f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } /* Space Station Presets */ #define EFX_REVERB_PRESET_SPACESTATION_SMALLROOM \ { 0.2109f, 0.7000f, 0.3162f, 0.7079f, 0.8913f, 1.7200f, 0.8200f, 0.5500f, 0.7943f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 0.1880f, 0.2600f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE \ { 0.2109f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 3.5700f, 0.5000f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1720f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM \ { 0.2109f, 0.7500f, 0.3162f, 0.6310f, 0.8913f, 3.0100f, 0.5000f, 0.5500f, 0.3981f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2090f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_LARGEROOM \ { 0.3645f, 0.8100f, 0.3162f, 0.6310f, 0.8913f, 3.8900f, 0.3800f, 0.6100f, 0.3162f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2330f, 0.2800f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE \ { 0.4287f, 0.8200f, 0.3162f, 0.6310f, 0.8913f, 4.6200f, 0.6200f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_HALL \ { 0.4287f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 7.1100f, 0.3800f, 0.6100f, 0.1778f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2500f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_CUPBOARD \ { 0.1715f, 0.5600f, 0.3162f, 0.7079f, 0.8913f, 0.7900f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1810f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_ALCOVE \ { 0.2109f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.1600f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1920f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } /* Wooden Galleon Presets */ #define EFX_REVERB_PRESET_WOODEN_SMALLROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.1122f, 0.3162f, 0.7900f, 0.3200f, 0.8700f, 1.0000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE \ { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.7500f, 0.5000f, 0.8700f, 0.8913f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_MEDIUMROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.2818f, 1.4700f, 0.4200f, 0.8200f, 0.8913f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_LARGEROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.2818f, 2.6500f, 0.3300f, 0.8200f, 0.8913f, 0.0660f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_LONGPASSAGE \ { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.3162f, 1.9900f, 0.4000f, 0.7900f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4467f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_HALL \ { 1.0000f, 1.0000f, 0.3162f, 0.0794f, 0.2818f, 3.4500f, 0.3000f, 0.8200f, 0.8913f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_CUPBOARD \ { 1.0000f, 1.0000f, 0.3162f, 0.1413f, 0.3162f, 0.5600f, 0.4600f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_COURTYARD \ { 1.0000f, 0.6500f, 0.3162f, 0.0794f, 0.3162f, 1.7900f, 0.3500f, 0.7900f, 0.5623f, 0.1230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_ALCOVE \ { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.2200f, 0.6200f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } /* Sports Presets */ #define EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.4467f, 0.7943f, 6.2600f, 0.5100f, 1.1000f, 0.0631f, 0.1830f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_SQUASHCOURT \ { 1.0000f, 0.7500f, 0.3162f, 0.3162f, 0.7943f, 2.2200f, 0.9100f, 1.1600f, 0.4467f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1260f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_SMALLSWIMMINGPOOL \ { 1.0000f, 0.7000f, 0.3162f, 0.7943f, 0.8913f, 2.7600f, 1.2500f, 1.1400f, 0.6310f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SPORT_LARGESWIMMINGPOOL \ { 1.0000f, 0.8200f, 0.3162f, 0.7943f, 1.0000f, 5.4900f, 1.3100f, 1.1400f, 0.4467f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2220f, 0.5500f, 1.1590f, 0.2100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SPORT_GYMNASIUM \ { 1.0000f, 0.8100f, 0.3162f, 0.4467f, 0.8913f, 3.1400f, 1.0600f, 1.3500f, 0.3981f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0450f, { 0.0000f, 0.0000f, 0.0000f }, 0.1460f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_FULLSTADIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.0708f, 0.7943f, 5.2500f, 0.1700f, 0.8000f, 0.1000f, 0.1880f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_STADIUMTANNOY \ { 1.0000f, 0.7800f, 0.3162f, 0.5623f, 0.5012f, 2.5300f, 0.8800f, 0.6800f, 0.2818f, 0.2300f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } /* Prefab Presets */ #define EFX_REVERB_PRESET_PREFAB_WORKSHOP \ { 0.4287f, 1.0000f, 0.3162f, 0.1413f, 0.3981f, 0.7600f, 1.0000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PREFAB_SCHOOLROOM \ { 0.4022f, 0.6900f, 0.3162f, 0.6310f, 0.5012f, 0.9800f, 0.4500f, 0.1800f, 1.4125f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PREFAB_PRACTISEROOM \ { 0.4022f, 0.8700f, 0.3162f, 0.3981f, 0.5012f, 1.1200f, 0.5600f, 0.1800f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PREFAB_OUTHOUSE \ { 1.0000f, 0.8200f, 0.3162f, 0.1122f, 0.1585f, 1.3800f, 0.3800f, 0.3500f, 0.8913f, 0.0240f, { 0.0000f, 0.0000f, -0.0000f }, 0.6310f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.1210f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PREFAB_CARAVAN \ { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.1259f, 0.4300f, 1.5000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Dome and Pipe Presets */ #define EFX_REVERB_PRESET_DOME_TOMB \ { 1.0000f, 0.7900f, 0.3162f, 0.3548f, 0.2239f, 4.1800f, 0.2100f, 0.1000f, 0.3868f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 1.6788f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PIPE_SMALL \ { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 5.0400f, 0.1000f, 0.1000f, 0.5012f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 2.5119f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DOME_SAINTPAULS \ { 1.0000f, 0.8700f, 0.3162f, 0.3548f, 0.2239f, 10.4800f, 0.1900f, 0.1000f, 0.1778f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0420f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PIPE_LONGTHIN \ { 0.2560f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 9.2100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PIPE_LARGE \ { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 8.4500f, 0.1000f, 0.1000f, 0.3981f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PIPE_RESONANT \ { 0.1373f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 6.8100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } /* Outdoors Presets */ #define EFX_REVERB_PRESET_OUTDOORS_BACKYARD \ { 1.0000f, 0.4500f, 0.3162f, 0.2512f, 0.5012f, 1.1200f, 0.3400f, 0.4600f, 0.4467f, 0.0690f, { 0.0000f, 0.0000f, -0.0000f }, 0.7079f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS \ { 1.0000f, 0.0000f, 0.3162f, 0.0112f, 0.6310f, 2.1300f, 0.2100f, 0.4600f, 0.1778f, 0.3000f, { 0.0000f, 0.0000f, -0.0000f }, 0.4467f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON \ { 1.0000f, 0.7400f, 0.3162f, 0.1778f, 0.6310f, 3.8900f, 0.2100f, 0.4600f, 0.3162f, 0.2230f, { 0.0000f, 0.0000f, -0.0000f }, 0.3548f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_CREEK \ { 1.0000f, 0.3500f, 0.3162f, 0.1778f, 0.5012f, 2.1300f, 0.2100f, 0.4600f, 0.3981f, 0.1150f, { 0.0000f, 0.0000f, -0.0000f }, 0.1995f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_VALLEY \ { 1.0000f, 0.2800f, 0.3162f, 0.0282f, 0.1585f, 2.8800f, 0.2600f, 0.3500f, 0.1413f, 0.2630f, { 0.0000f, 0.0000f, -0.0000f }, 0.3981f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } /* Mood Presets */ #define EFX_REVERB_PRESET_MOOD_HEAVEN \ { 1.0000f, 0.9400f, 0.3162f, 0.7943f, 0.4467f, 5.0400f, 1.1200f, 0.5600f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0800f, 2.7420f, 0.0500f, 0.9977f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_MOOD_HELL \ { 1.0000f, 0.5700f, 0.3162f, 0.3548f, 0.4467f, 3.5700f, 0.4900f, 2.0000f, 0.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1100f, 0.0400f, 2.1090f, 0.5200f, 0.9943f, 5000.0000f, 139.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_MOOD_MEMORY \ { 1.0000f, 0.8500f, 0.3162f, 0.6310f, 0.3548f, 4.0600f, 0.8200f, 0.5600f, 0.0398f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.4740f, 0.4500f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Driving Presets */ #define EFX_REVERB_PRESET_DRIVING_COMMENTATOR \ { 1.0000f, 0.0000f, 0.3162f, 0.5623f, 0.5012f, 2.4200f, 0.8800f, 0.6800f, 0.1995f, 0.0930f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_PITGARAGE \ { 0.4287f, 0.5900f, 0.3162f, 0.7079f, 0.5623f, 1.7200f, 0.9300f, 0.8700f, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_INCAR_RACER \ { 0.0832f, 0.8000f, 0.3162f, 1.0000f, 0.7943f, 0.1700f, 2.0000f, 0.4100f, 1.7783f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS \ { 0.0832f, 0.8000f, 0.3162f, 0.6310f, 1.0000f, 0.1700f, 0.7500f, 0.4100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY \ { 0.2560f, 1.0000f, 0.3162f, 0.1000f, 0.5012f, 0.1300f, 0.4100f, 0.4600f, 0.7943f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND \ { 1.0000f, 1.0000f, 0.3162f, 0.2818f, 0.6310f, 3.0100f, 1.3700f, 1.2800f, 0.3548f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.1778f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 0.7943f, 4.6200f, 1.7500f, 1.4000f, 0.2082f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_TUNNEL \ { 1.0000f, 0.8100f, 0.3162f, 0.3981f, 0.8913f, 3.4200f, 0.9400f, 1.3100f, 0.7079f, 0.0510f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.0500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 155.3000f, 0.0000f, 0x1 } /* City Presets */ #define EFX_REVERB_PRESET_CITY_STREETS \ { 1.0000f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.7900f, 1.1200f, 0.9100f, 0.2818f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 0.1995f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_SUBWAY \ { 1.0000f, 0.7400f, 0.3162f, 0.7079f, 0.8913f, 3.0100f, 1.2300f, 0.9100f, 0.7079f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_MUSEUM \ { 1.0000f, 0.8200f, 0.3162f, 0.1778f, 0.1778f, 3.2800f, 1.4000f, 0.5700f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CITY_LIBRARY \ { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.0891f, 2.7600f, 0.8900f, 0.4100f, 0.3548f, 0.0290f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CITY_UNDERPASS \ { 1.0000f, 0.8200f, 0.3162f, 0.4467f, 0.8913f, 3.5700f, 1.1200f, 0.9100f, 0.3981f, 0.0590f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1400f, 0.2500f, 0.0000f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_ABANDONED \ { 1.0000f, 0.6900f, 0.3162f, 0.7943f, 0.8913f, 3.2800f, 1.1700f, 0.9100f, 0.4467f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9966f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } /* Misc. Presets */ #define EFX_REVERB_PRESET_DUSTYROOM \ { 0.3645f, 0.5600f, 0.3162f, 0.7943f, 0.7079f, 1.7900f, 0.3800f, 0.2100f, 0.5012f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0060f, { 0.0000f, 0.0000f, 0.0000f }, 0.2020f, 0.0500f, 0.2500f, 0.0000f, 0.9886f, 13046.0000f, 163.3000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CHAPEL \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 1.0000f, 4.6200f, 0.6400f, 1.2300f, 0.4467f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.1100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SMALLWATERROOM \ { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #endif /* EFX_PRESETS_H */ openmw-openmw-0.48.0/apps/openmw/mwsound/efx.h000066400000000000000000001022601445372753700213340ustar00rootroot00000000000000#ifndef AL_EFX_H #define AL_EFX_H #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif #define ALC_EXT_EFX_NAME "ALC_EXT_EFX" #define ALC_EFX_MAJOR_VERSION 0x20001 #define ALC_EFX_MINOR_VERSION 0x20002 #define ALC_MAX_AUXILIARY_SENDS 0x20003 /* Listener properties. */ #define AL_METERS_PER_UNIT 0x20004 /* Source properties. */ #define AL_DIRECT_FILTER 0x20005 #define AL_AUXILIARY_SEND_FILTER 0x20006 #define AL_AIR_ABSORPTION_FACTOR 0x20007 #define AL_ROOM_ROLLOFF_FACTOR 0x20008 #define AL_CONE_OUTER_GAINHF 0x20009 #define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A #define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B #define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C /* Effect properties. */ /* Reverb effect parameters */ #define AL_REVERB_DENSITY 0x0001 #define AL_REVERB_DIFFUSION 0x0002 #define AL_REVERB_GAIN 0x0003 #define AL_REVERB_GAINHF 0x0004 #define AL_REVERB_DECAY_TIME 0x0005 #define AL_REVERB_DECAY_HFRATIO 0x0006 #define AL_REVERB_REFLECTIONS_GAIN 0x0007 #define AL_REVERB_REFLECTIONS_DELAY 0x0008 #define AL_REVERB_LATE_REVERB_GAIN 0x0009 #define AL_REVERB_LATE_REVERB_DELAY 0x000A #define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B #define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C #define AL_REVERB_DECAY_HFLIMIT 0x000D /* EAX Reverb effect parameters */ #define AL_EAXREVERB_DENSITY 0x0001 #define AL_EAXREVERB_DIFFUSION 0x0002 #define AL_EAXREVERB_GAIN 0x0003 #define AL_EAXREVERB_GAINHF 0x0004 #define AL_EAXREVERB_GAINLF 0x0005 #define AL_EAXREVERB_DECAY_TIME 0x0006 #define AL_EAXREVERB_DECAY_HFRATIO 0x0007 #define AL_EAXREVERB_DECAY_LFRATIO 0x0008 #define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 #define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A #define AL_EAXREVERB_REFLECTIONS_PAN 0x000B #define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C #define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D #define AL_EAXREVERB_LATE_REVERB_PAN 0x000E #define AL_EAXREVERB_ECHO_TIME 0x000F #define AL_EAXREVERB_ECHO_DEPTH 0x0010 #define AL_EAXREVERB_MODULATION_TIME 0x0011 #define AL_EAXREVERB_MODULATION_DEPTH 0x0012 #define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 #define AL_EAXREVERB_HFREFERENCE 0x0014 #define AL_EAXREVERB_LFREFERENCE 0x0015 #define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 #define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 /* Chorus effect parameters */ #define AL_CHORUS_WAVEFORM 0x0001 #define AL_CHORUS_PHASE 0x0002 #define AL_CHORUS_RATE 0x0003 #define AL_CHORUS_DEPTH 0x0004 #define AL_CHORUS_FEEDBACK 0x0005 #define AL_CHORUS_DELAY 0x0006 /* Distortion effect parameters */ #define AL_DISTORTION_EDGE 0x0001 #define AL_DISTORTION_GAIN 0x0002 #define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 #define AL_DISTORTION_EQCENTER 0x0004 #define AL_DISTORTION_EQBANDWIDTH 0x0005 /* Echo effect parameters */ #define AL_ECHO_DELAY 0x0001 #define AL_ECHO_LRDELAY 0x0002 #define AL_ECHO_DAMPING 0x0003 #define AL_ECHO_FEEDBACK 0x0004 #define AL_ECHO_SPREAD 0x0005 /* Flanger effect parameters */ #define AL_FLANGER_WAVEFORM 0x0001 #define AL_FLANGER_PHASE 0x0002 #define AL_FLANGER_RATE 0x0003 #define AL_FLANGER_DEPTH 0x0004 #define AL_FLANGER_FEEDBACK 0x0005 #define AL_FLANGER_DELAY 0x0006 /* Frequency shifter effect parameters */ #define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 #define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 #define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 /* Vocal morpher effect parameters */ #define AL_VOCAL_MORPHER_PHONEMEA 0x0001 #define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 #define AL_VOCAL_MORPHER_PHONEMEB 0x0003 #define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 #define AL_VOCAL_MORPHER_WAVEFORM 0x0005 #define AL_VOCAL_MORPHER_RATE 0x0006 /* Pitchshifter effect parameters */ #define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 #define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 /* Ringmodulator effect parameters */ #define AL_RING_MODULATOR_FREQUENCY 0x0001 #define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 #define AL_RING_MODULATOR_WAVEFORM 0x0003 /* Autowah effect parameters */ #define AL_AUTOWAH_ATTACK_TIME 0x0001 #define AL_AUTOWAH_RELEASE_TIME 0x0002 #define AL_AUTOWAH_RESONANCE 0x0003 #define AL_AUTOWAH_PEAK_GAIN 0x0004 /* Compressor effect parameters */ #define AL_COMPRESSOR_ONOFF 0x0001 /* Equalizer effect parameters */ #define AL_EQUALIZER_LOW_GAIN 0x0001 #define AL_EQUALIZER_LOW_CUTOFF 0x0002 #define AL_EQUALIZER_MID1_GAIN 0x0003 #define AL_EQUALIZER_MID1_CENTER 0x0004 #define AL_EQUALIZER_MID1_WIDTH 0x0005 #define AL_EQUALIZER_MID2_GAIN 0x0006 #define AL_EQUALIZER_MID2_CENTER 0x0007 #define AL_EQUALIZER_MID2_WIDTH 0x0008 #define AL_EQUALIZER_HIGH_GAIN 0x0009 #define AL_EQUALIZER_HIGH_CUTOFF 0x000A /* Effect type */ #define AL_EFFECT_FIRST_PARAMETER 0x0000 #define AL_EFFECT_LAST_PARAMETER 0x8000 #define AL_EFFECT_TYPE 0x8001 /* Effect types, used with the AL_EFFECT_TYPE property */ #define AL_EFFECT_NULL 0x0000 #define AL_EFFECT_REVERB 0x0001 #define AL_EFFECT_CHORUS 0x0002 #define AL_EFFECT_DISTORTION 0x0003 #define AL_EFFECT_ECHO 0x0004 #define AL_EFFECT_FLANGER 0x0005 #define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 #define AL_EFFECT_VOCAL_MORPHER 0x0007 #define AL_EFFECT_PITCH_SHIFTER 0x0008 #define AL_EFFECT_RING_MODULATOR 0x0009 #define AL_EFFECT_AUTOWAH 0x000A #define AL_EFFECT_COMPRESSOR 0x000B #define AL_EFFECT_EQUALIZER 0x000C #define AL_EFFECT_EAXREVERB 0x8000 /* Auxiliary Effect Slot properties. */ #define AL_EFFECTSLOT_EFFECT 0x0001 #define AL_EFFECTSLOT_GAIN 0x0002 #define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 /* NULL Auxiliary Slot ID to disable a source send. */ #define AL_EFFECTSLOT_NULL 0x0000 /* Filter properties. */ /* Lowpass filter parameters */ #define AL_LOWPASS_GAIN 0x0001 #define AL_LOWPASS_GAINHF 0x0002 /* Highpass filter parameters */ #define AL_HIGHPASS_GAIN 0x0001 #define AL_HIGHPASS_GAINLF 0x0002 /* Bandpass filter parameters */ #define AL_BANDPASS_GAIN 0x0001 #define AL_BANDPASS_GAINLF 0x0002 #define AL_BANDPASS_GAINHF 0x0003 /* Filter type */ #define AL_FILTER_FIRST_PARAMETER 0x0000 #define AL_FILTER_LAST_PARAMETER 0x8000 #define AL_FILTER_TYPE 0x8001 /* Filter types, used with the AL_FILTER_TYPE property */ #define AL_FILTER_NULL 0x0000 #define AL_FILTER_LOWPASS 0x0001 #define AL_FILTER_HIGHPASS 0x0002 #define AL_FILTER_BANDPASS 0x0003 /* Effect object function types. */ typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*); typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*); typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); /* Filter object function types. */ typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*); typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*); typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*); typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); /* Auxiliary Effect Slot object function types. */ typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); #ifdef AL_ALEXT_PROTOTYPES AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); #endif /* Filter ranges and defaults. */ /* Lowpass filter */ #define AL_LOWPASS_MIN_GAIN (0.0f) #define AL_LOWPASS_MAX_GAIN (1.0f) #define AL_LOWPASS_DEFAULT_GAIN (1.0f) #define AL_LOWPASS_MIN_GAINHF (0.0f) #define AL_LOWPASS_MAX_GAINHF (1.0f) #define AL_LOWPASS_DEFAULT_GAINHF (1.0f) /* Highpass filter */ #define AL_HIGHPASS_MIN_GAIN (0.0f) #define AL_HIGHPASS_MAX_GAIN (1.0f) #define AL_HIGHPASS_DEFAULT_GAIN (1.0f) #define AL_HIGHPASS_MIN_GAINLF (0.0f) #define AL_HIGHPASS_MAX_GAINLF (1.0f) #define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) /* Bandpass filter */ #define AL_BANDPASS_MIN_GAIN (0.0f) #define AL_BANDPASS_MAX_GAIN (1.0f) #define AL_BANDPASS_DEFAULT_GAIN (1.0f) #define AL_BANDPASS_MIN_GAINHF (0.0f) #define AL_BANDPASS_MAX_GAINHF (1.0f) #define AL_BANDPASS_DEFAULT_GAINHF (1.0f) #define AL_BANDPASS_MIN_GAINLF (0.0f) #define AL_BANDPASS_MAX_GAINLF (1.0f) #define AL_BANDPASS_DEFAULT_GAINLF (1.0f) /* Effect parameter ranges and defaults. */ /* Standard reverb effect */ #define AL_REVERB_MIN_DENSITY (0.0f) #define AL_REVERB_MAX_DENSITY (1.0f) #define AL_REVERB_DEFAULT_DENSITY (1.0f) #define AL_REVERB_MIN_DIFFUSION (0.0f) #define AL_REVERB_MAX_DIFFUSION (1.0f) #define AL_REVERB_DEFAULT_DIFFUSION (1.0f) #define AL_REVERB_MIN_GAIN (0.0f) #define AL_REVERB_MAX_GAIN (1.0f) #define AL_REVERB_DEFAULT_GAIN (0.32f) #define AL_REVERB_MIN_GAINHF (0.0f) #define AL_REVERB_MAX_GAINHF (1.0f) #define AL_REVERB_DEFAULT_GAINHF (0.89f) #define AL_REVERB_MIN_DECAY_TIME (0.1f) #define AL_REVERB_MAX_DECAY_TIME (20.0f) #define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) #define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) #define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) #define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) #define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) #define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) #define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) #define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) #define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) #define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) #define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) #define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) #define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) #define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) #define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) #define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) #define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) #define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) #define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) #define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE #define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE #define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* EAX reverb effect */ #define AL_EAXREVERB_MIN_DENSITY (0.0f) #define AL_EAXREVERB_MAX_DENSITY (1.0f) #define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) #define AL_EAXREVERB_MIN_DIFFUSION (0.0f) #define AL_EAXREVERB_MAX_DIFFUSION (1.0f) #define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) #define AL_EAXREVERB_MIN_GAIN (0.0f) #define AL_EAXREVERB_MAX_GAIN (1.0f) #define AL_EAXREVERB_DEFAULT_GAIN (0.32f) #define AL_EAXREVERB_MIN_GAINHF (0.0f) #define AL_EAXREVERB_MAX_GAINHF (1.0f) #define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) #define AL_EAXREVERB_MIN_GAINLF (0.0f) #define AL_EAXREVERB_MAX_GAINLF (1.0f) #define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) #define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) #define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) #define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) #define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) #define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) #define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) #define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) #define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) #define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) #define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) #define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) #define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) #define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) #define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) #define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) #define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) #define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) #define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) #define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) #define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) #define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) #define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) #define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) #define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) #define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) #define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) #define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) #define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) #define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) #define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) #define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) #define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) #define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) #define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) #define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) #define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) #define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) #define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) #define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE #define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE #define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* Chorus effect */ #define AL_CHORUS_WAVEFORM_SINUSOID (0) #define AL_CHORUS_WAVEFORM_TRIANGLE (1) #define AL_CHORUS_MIN_WAVEFORM (0) #define AL_CHORUS_MAX_WAVEFORM (1) #define AL_CHORUS_DEFAULT_WAVEFORM (1) #define AL_CHORUS_MIN_PHASE (-180) #define AL_CHORUS_MAX_PHASE (180) #define AL_CHORUS_DEFAULT_PHASE (90) #define AL_CHORUS_MIN_RATE (0.0f) #define AL_CHORUS_MAX_RATE (10.0f) #define AL_CHORUS_DEFAULT_RATE (1.1f) #define AL_CHORUS_MIN_DEPTH (0.0f) #define AL_CHORUS_MAX_DEPTH (1.0f) #define AL_CHORUS_DEFAULT_DEPTH (0.1f) #define AL_CHORUS_MIN_FEEDBACK (-1.0f) #define AL_CHORUS_MAX_FEEDBACK (1.0f) #define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) #define AL_CHORUS_MIN_DELAY (0.0f) #define AL_CHORUS_MAX_DELAY (0.016f) #define AL_CHORUS_DEFAULT_DELAY (0.016f) /* Distortion effect */ #define AL_DISTORTION_MIN_EDGE (0.0f) #define AL_DISTORTION_MAX_EDGE (1.0f) #define AL_DISTORTION_DEFAULT_EDGE (0.2f) #define AL_DISTORTION_MIN_GAIN (0.01f) #define AL_DISTORTION_MAX_GAIN (1.0f) #define AL_DISTORTION_DEFAULT_GAIN (0.05f) #define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) #define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) #define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) #define AL_DISTORTION_MIN_EQCENTER (80.0f) #define AL_DISTORTION_MAX_EQCENTER (24000.0f) #define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) #define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) #define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) #define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) /* Echo effect */ #define AL_ECHO_MIN_DELAY (0.0f) #define AL_ECHO_MAX_DELAY (0.207f) #define AL_ECHO_DEFAULT_DELAY (0.1f) #define AL_ECHO_MIN_LRDELAY (0.0f) #define AL_ECHO_MAX_LRDELAY (0.404f) #define AL_ECHO_DEFAULT_LRDELAY (0.1f) #define AL_ECHO_MIN_DAMPING (0.0f) #define AL_ECHO_MAX_DAMPING (0.99f) #define AL_ECHO_DEFAULT_DAMPING (0.5f) #define AL_ECHO_MIN_FEEDBACK (0.0f) #define AL_ECHO_MAX_FEEDBACK (1.0f) #define AL_ECHO_DEFAULT_FEEDBACK (0.5f) #define AL_ECHO_MIN_SPREAD (-1.0f) #define AL_ECHO_MAX_SPREAD (1.0f) #define AL_ECHO_DEFAULT_SPREAD (-1.0f) /* Flanger effect */ #define AL_FLANGER_WAVEFORM_SINUSOID (0) #define AL_FLANGER_WAVEFORM_TRIANGLE (1) #define AL_FLANGER_MIN_WAVEFORM (0) #define AL_FLANGER_MAX_WAVEFORM (1) #define AL_FLANGER_DEFAULT_WAVEFORM (1) #define AL_FLANGER_MIN_PHASE (-180) #define AL_FLANGER_MAX_PHASE (180) #define AL_FLANGER_DEFAULT_PHASE (0) #define AL_FLANGER_MIN_RATE (0.0f) #define AL_FLANGER_MAX_RATE (10.0f) #define AL_FLANGER_DEFAULT_RATE (0.27f) #define AL_FLANGER_MIN_DEPTH (0.0f) #define AL_FLANGER_MAX_DEPTH (1.0f) #define AL_FLANGER_DEFAULT_DEPTH (1.0f) #define AL_FLANGER_MIN_FEEDBACK (-1.0f) #define AL_FLANGER_MAX_FEEDBACK (1.0f) #define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) #define AL_FLANGER_MIN_DELAY (0.0f) #define AL_FLANGER_MAX_DELAY (0.004f) #define AL_FLANGER_DEFAULT_DELAY (0.002f) /* Frequency shifter effect */ #define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) #define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) #define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) #define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) #define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) #define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) #define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) /* Vocal morpher effect */ #define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) #define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) #define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) #define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) #define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) #define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) #define AL_VOCAL_MORPHER_PHONEME_A (0) #define AL_VOCAL_MORPHER_PHONEME_E (1) #define AL_VOCAL_MORPHER_PHONEME_I (2) #define AL_VOCAL_MORPHER_PHONEME_O (3) #define AL_VOCAL_MORPHER_PHONEME_U (4) #define AL_VOCAL_MORPHER_PHONEME_AA (5) #define AL_VOCAL_MORPHER_PHONEME_AE (6) #define AL_VOCAL_MORPHER_PHONEME_AH (7) #define AL_VOCAL_MORPHER_PHONEME_AO (8) #define AL_VOCAL_MORPHER_PHONEME_EH (9) #define AL_VOCAL_MORPHER_PHONEME_ER (10) #define AL_VOCAL_MORPHER_PHONEME_IH (11) #define AL_VOCAL_MORPHER_PHONEME_IY (12) #define AL_VOCAL_MORPHER_PHONEME_UH (13) #define AL_VOCAL_MORPHER_PHONEME_UW (14) #define AL_VOCAL_MORPHER_PHONEME_B (15) #define AL_VOCAL_MORPHER_PHONEME_D (16) #define AL_VOCAL_MORPHER_PHONEME_F (17) #define AL_VOCAL_MORPHER_PHONEME_G (18) #define AL_VOCAL_MORPHER_PHONEME_J (19) #define AL_VOCAL_MORPHER_PHONEME_K (20) #define AL_VOCAL_MORPHER_PHONEME_L (21) #define AL_VOCAL_MORPHER_PHONEME_M (22) #define AL_VOCAL_MORPHER_PHONEME_N (23) #define AL_VOCAL_MORPHER_PHONEME_P (24) #define AL_VOCAL_MORPHER_PHONEME_R (25) #define AL_VOCAL_MORPHER_PHONEME_S (26) #define AL_VOCAL_MORPHER_PHONEME_T (27) #define AL_VOCAL_MORPHER_PHONEME_V (28) #define AL_VOCAL_MORPHER_PHONEME_Z (29) #define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) #define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) #define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) #define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) #define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) #define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) #define AL_VOCAL_MORPHER_MIN_RATE (0.0f) #define AL_VOCAL_MORPHER_MAX_RATE (10.0f) #define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) /* Pitch shifter effect */ #define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) #define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) #define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) #define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) #define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) #define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) /* Ring modulator effect */ #define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) #define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) #define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) #define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) #define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) #define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) #define AL_RING_MODULATOR_SINUSOID (0) #define AL_RING_MODULATOR_SAWTOOTH (1) #define AL_RING_MODULATOR_SQUARE (2) #define AL_RING_MODULATOR_MIN_WAVEFORM (0) #define AL_RING_MODULATOR_MAX_WAVEFORM (2) #define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) /* Autowah effect */ #define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) #define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) #define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) #define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) #define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) #define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) #define AL_AUTOWAH_MIN_RESONANCE (2.0f) #define AL_AUTOWAH_MAX_RESONANCE (1000.0f) #define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) #define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) #define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) #define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) /* Compressor effect */ #define AL_COMPRESSOR_MIN_ONOFF (0) #define AL_COMPRESSOR_MAX_ONOFF (1) #define AL_COMPRESSOR_DEFAULT_ONOFF (1) /* Equalizer effect */ #define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) #define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) #define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) #define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) #define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) #define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) #define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) #define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) #define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) #define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) #define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) #define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) #define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) #define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) #define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) #define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) #define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) #define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) #define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) #define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) #define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) #define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) #define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) #define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) #define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) #define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) /* Source parameter value ranges and defaults. */ #define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) #define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) #define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) #define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_MIN_CONE_OUTER_GAINHF (0.0f) #define AL_MAX_CONE_OUTER_GAINHF (1.0f) #define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) #define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE #define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE #define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE #define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE #define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE #define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE /* Listener parameter value ranges and defaults. */ #define AL_MIN_METERS_PER_UNIT FLT_MIN #define AL_MAX_METERS_PER_UNIT FLT_MAX #define AL_DEFAULT_METERS_PER_UNIT (1.0f) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* AL_EFX_H */ openmw-openmw-0.48.0/apps/openmw/mwsound/ffmpeg_decoder.cpp000066400000000000000000000335421445372753700240440ustar00rootroot00000000000000#include "ffmpeg_decoder.hpp" #include #include #include #include #include namespace MWSound { int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { try { std::istream& stream = *static_cast(user_data)->mDataStream; stream.clear(); stream.read((char*)buf, buf_size); std::streamsize count = stream.gcount(); if (count == 0) return AVERROR_EOF; return count; } catch (std::exception& ) { return AVERROR_UNKNOWN; } } int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) { Log(Debug::Error) << "can't write to read-only stream"; return -1; } int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) { std::istream& stream = *static_cast(user_data)->mDataStream; whence &= ~AVSEEK_FORCE; stream.clear(); if(whence == AVSEEK_SIZE) { size_t prev = stream.tellg(); stream.seekg(0, std::ios_base::end); size_t size = stream.tellg(); stream.seekg(prev, std::ios_base::beg); return size; } if(whence == SEEK_SET) stream.seekg(offset, std::ios_base::beg); else if(whence == SEEK_CUR) stream.seekg(offset, std::ios_base::cur); else if(whence == SEEK_END) stream.seekg(offset, std::ios_base::end); else return -1; return stream.tellg(); } /* Used by getAV*Data to search for more compressed data, and buffer it in the * correct stream. It won't buffer data for streams that the app doesn't have a * handle for. */ bool FFmpeg_Decoder::getNextPacket() { if(!mStream) return false; int stream_idx = mStream - mFormatCtx->streams; while(av_read_frame(mFormatCtx, &mPacket) >= 0) { /* Check if the packet belongs to this stream */ if(stream_idx == mPacket.stream_index) { if(mPacket.pts != (int64_t)AV_NOPTS_VALUE) mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts; return true; } /* Free the packet and look for another */ av_packet_unref(&mPacket); } return false; } bool FFmpeg_Decoder::getAVAudioData() { bool got_frame = false; if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) return false; do { /* Decode some data, and check for errors */ int ret = avcodec_receive_frame(mCodecCtx, mFrame); if (ret == AVERROR(EAGAIN)) { if (mPacket.size == 0 && !getNextPacket()) return false; ret = avcodec_send_packet(mCodecCtx, &mPacket); av_packet_unref(&mPacket); if (ret == 0) continue; } if (ret != 0) return false; av_packet_unref(&mPacket); if (mFrame->nb_samples == 0) continue; got_frame = true; if(mSwr) { if(!mDataBuf || mDataBufLen < mFrame->nb_samples) { av_freep(&mDataBuf); if(av_samples_alloc(&mDataBuf, nullptr, av_get_channel_layout_nb_channels(mOutputChannelLayout), mFrame->nb_samples, mOutputSampleFormat, 0) < 0) return false; else mDataBufLen = mFrame->nb_samples; } if(swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, (const uint8_t**)mFrame->extended_data, mFrame->nb_samples) < 0) { return false; } mFrameData = &mDataBuf; } else mFrameData = &mFrame->data[0]; } while(!got_frame); mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate; return true; } size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) { size_t dec = 0; while(dec < length) { /* If there's no decoded data, find some */ if(mFramePos >= mFrameSize) { if(!getAVAudioData()) break; mFramePos = 0; mFrameSize = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) * av_get_bytes_per_sample(mOutputSampleFormat); } /* Get the amount of bytes remaining to be written, and clamp to * the amount of decoded data we have */ size_t rem = std::min(length-dec, mFrameSize-mFramePos); /* Copy the data to the app's buffer and increment */ memcpy(data, mFrameData[0]+mFramePos, rem); data = (char*)data + rem; dec += rem; mFramePos += rem; } /* Return the number of bytes we were able to get */ return dec; } void FFmpeg_Decoder::open(const std::string &fname) { close(); mDataStream = mResourceMgr->get(fname); if((mFormatCtx=avformat_alloc_context()) == nullptr) throw std::runtime_error("Failed to allocate context"); try { mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) { // "Note that a user-supplied AVFormatContext will be freed on failure". if (mFormatCtx) { if (mFormatCtx->pb != nullptr) { if (mFormatCtx->pb->buffer != nullptr) { av_free(mFormatCtx->pb->buffer); mFormatCtx->pb->buffer = nullptr; } av_free(mFormatCtx->pb); mFormatCtx->pb = nullptr; } avformat_free_context(mFormatCtx); } mFormatCtx = nullptr; throw std::runtime_error("Failed to allocate input stream"); } if(avformat_find_stream_info(mFormatCtx, nullptr) < 0) throw std::runtime_error("Failed to find stream info in "+fname); for(size_t j = 0;j < mFormatCtx->nb_streams;j++) { if(mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { mStream = &mFormatCtx->streams[j]; break; } } if(!mStream) throw std::runtime_error("No audio streams in "+fname); const AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); if(!codec) { std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id); throw std::runtime_error(ss); } AVCodecContext *avctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(avctx, (*mStream)->codecpar); // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); #endif mCodecCtx = avctx; if(avcodec_open2(mCodecCtx, codec, nullptr) < 0) throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); mFrame = av_frame_alloc(); if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) mOutputSampleFormat = AV_SAMPLE_FMT_U8; else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P) mOutputSampleFormat = AV_SAMPLE_FMT_S16; else mOutputSampleFormat = AV_SAMPLE_FMT_S16; mOutputChannelLayout = (*mStream)->codecpar->channel_layout; if(mOutputChannelLayout == 0) mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); mCodecCtx->channel_layout = mOutputChannelLayout; } catch(...) { if(mStream) avcodec_free_context(&mCodecCtx); mStream = nullptr; if (mFormatCtx != nullptr) { if (mFormatCtx->pb->buffer != nullptr) { av_free(mFormatCtx->pb->buffer); mFormatCtx->pb->buffer = nullptr; } av_free(mFormatCtx->pb); mFormatCtx->pb = nullptr; avformat_close_input(&mFormatCtx); } } } void FFmpeg_Decoder::close() { if(mStream) avcodec_free_context(&mCodecCtx); mStream = nullptr; av_packet_unref(&mPacket); av_freep(&mDataBuf); av_frame_free(&mFrame); swr_free(&mSwr); if(mFormatCtx) { if (mFormatCtx->pb != nullptr) { // mFormatCtx->pb->buffer must be freed by hand, // if not, valgrind will show memleak, see: // // https://trac.ffmpeg.org/ticket/1357 // if (mFormatCtx->pb->buffer != nullptr) { av_freep(&mFormatCtx->pb->buffer); } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) avio_context_free(&mFormatCtx->pb); #else av_freep(&mFormatCtx->pb); #endif } avformat_close_input(&mFormatCtx); } mDataStream.reset(); } std::string FFmpeg_Decoder::getName() { // In the FFMpeg 4.0 a "filename" field was replaced by "url" #if LIBAVCODEC_VERSION_INT < 3805796 return mFormatCtx->filename; #else return mFormatCtx->url; #endif } void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mStream) throw std::runtime_error("No audio stream info"); if(mOutputSampleFormat == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; else if(mOutputSampleFormat == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; else { mOutputSampleFormat = AV_SAMPLE_FMT_S16; *type = SampleType_Int16; } if(mOutputChannelLayout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; else if(mOutputChannelLayout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; else if(mOutputChannelLayout == AV_CH_LAYOUT_QUAD) *chans = ChannelConfig_Quad; else if(mOutputChannelLayout == AV_CH_LAYOUT_5POINT1) *chans = ChannelConfig_5point1; else if(mOutputChannelLayout == AV_CH_LAYOUT_7POINT1) *chans = ChannelConfig_7point1; else { char str[1024]; av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout); Log(Debug::Error) << "Unsupported channel layout: "<< str; if(mCodecCtx->channels == 1) { mOutputChannelLayout = AV_CH_LAYOUT_MONO; *chans = ChannelConfig_Mono; } else { mOutputChannelLayout = AV_CH_LAYOUT_STEREO; *chans = ChannelConfig_Stereo; } } *samplerate = mCodecCtx->sample_rate; int64_t ch_layout = mCodecCtx->channel_layout; if(ch_layout == 0) ch_layout = av_get_default_channel_layout(mCodecCtx->channels); if(mOutputSampleFormat != mCodecCtx->sample_fmt || mOutputChannelLayout != ch_layout) { mSwr = swr_alloc_set_opts(mSwr, // SwrContext mOutputChannelLayout, // output ch layout mOutputSampleFormat, // output sample format mCodecCtx->sample_rate, // output sample rate ch_layout, // input ch layout mCodecCtx->sample_fmt, // input sample format mCodecCtx->sample_rate, // input sample rate 0, // logging level offset nullptr); // log context if(!mSwr) throw std::runtime_error("Couldn't allocate SwrContext"); int init=swr_init(mSwr); if(init < 0) throw std::runtime_error("Couldn't initialize SwrContext: "+std::to_string(init)); } } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { if(!mStream) { Log(Debug::Error) << "No audio stream"; return 0; } return readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::readAll(std::vector &output) { if(!mStream) { Log(Debug::Error) << "No audio stream"; return; } while(getAVAudioData()) { size_t got = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) * av_get_bytes_per_sample(mOutputSampleFormat); const char *inbuf = reinterpret_cast(mFrameData[0]); output.insert(output.end(), inbuf, inbuf+got); } } size_t FFmpeg_Decoder::getSampleOffset() { int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / av_get_bytes_per_sample(mOutputSampleFormat); return (int)(mNextPts*mCodecCtx->sample_rate) - delay; } FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) : Sound_Decoder(vfs) , mFormatCtx(nullptr) , mCodecCtx(nullptr) , mStream(nullptr) , mFrame(nullptr) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) , mSwr(nullptr) , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) , mOutputChannelLayout(0) , mDataBuf(nullptr) , mFrameData(nullptr) , mDataBufLen(0) { memset(&mPacket, 0, sizeof(mPacket)); /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ static bool done_init = false; if(!done_init) { // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_register_all(); #endif av_log_set_level(AV_LOG_ERROR); done_init = true; } } FFmpeg_Decoder::~FFmpeg_Decoder() { close(); } } openmw-openmw-0.48.0/apps/openmw/mwsound/ffmpeg_decoder.hpp000066400000000000000000000043301445372753700240420ustar00rootroot00000000000000#ifndef GAME_SOUND_FFMPEG_DECODER_H #define GAME_SOUND_FFMPEG_DECODER_H #include #if defined(_MSC_VER) #pragma warning (push) #pragma warning (disable : 4244) #endif extern "C" { #include #include #include // From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: // https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d // https://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 #include } #if defined(_MSC_VER) #pragma warning (pop) #endif #include #include #include "sound_decoder.hpp" namespace MWSound { class FFmpeg_Decoder final : public Sound_Decoder { AVFormatContext *mFormatCtx; AVCodecContext *mCodecCtx; AVStream **mStream; AVPacket mPacket; AVFrame *mFrame; int mFrameSize; int mFramePos; double mNextPts; SwrContext *mSwr; enum AVSampleFormat mOutputSampleFormat; int64_t mOutputChannelLayout; uint8_t *mDataBuf; uint8_t **mFrameData; int mDataBufLen; bool getNextPacket(); Files::IStreamPtr mDataStream; static int readPacket(void *user_data, uint8_t *buf, int buf_size); static int writePacket(void *user_data, uint8_t *buf, int buf_size); static int64_t seek(void *user_data, int64_t offset, int whence); bool getAVAudioData(); size_t readAVAudioData(void *data, size_t length); void open(const std::string &fname) override; void close() override; std::string getName() override; void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override; size_t read(char *buffer, size_t bytes) override; void readAll(std::vector &output) override; size_t getSampleOffset() override; FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); public: explicit FFmpeg_Decoder(const VFS::Manager* vfs); virtual ~FFmpeg_Decoder(); friend class SoundManager; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/loudness.cpp000066400000000000000000000040441445372753700227420ustar00rootroot00000000000000#include "loudness.hpp" #include #include #include #include "soundmanagerimp.hpp" namespace MWSound { void Sound_Loudness::analyzeLoudness(const std::vector< char >& data) { mQueue.insert( mQueue.end(), data.begin(), data.end() ); if (!mQueue.size()) return; int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); int advance = framesToBytes(1, mChannelConfig, mSampleType); int segment=0; int sample=0; while (segment < numSamples/samplesPerSegment) { float sum=0; int samplesAdded = 0; while (sample < numSamples && sample < (segment+1)*samplesPerSegment) { // get sample on a scale from -1 to 1 float value = 0; if (mSampleType == SampleType_UInt8) value = ((char)(mQueue[sample*advance]^0x80))/128.f; else if (mSampleType == SampleType_Int16) { value = *reinterpret_cast(&mQueue[sample*advance]); value /= float(std::numeric_limits::max()); } else if (mSampleType == SampleType_Float32) { value = *reinterpret_cast(&mQueue[sample*advance]); value = std::clamp(value, -1.f, 1.f); // Float samples *should* be scaled to [-1,1] already. } sum += value*value; ++samplesAdded; ++sample; } float rms = 0; // root mean square if (samplesAdded > 0) rms = std::sqrt(sum / samplesAdded); mSamples.push_back(rms); ++segment; } mQueue.erase(mQueue.begin(), mQueue.begin() + sample*advance); } float Sound_Loudness::getLoudnessAtTime(float sec) const { if(mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); return mSamples[index]; } } openmw-openmw-0.48.0/apps/openmw/mwsound/loudness.hpp000066400000000000000000000035141445372753700227500ustar00rootroot00000000000000#ifndef GAME_SOUND_LOUDNESS_H #define GAME_SOUND_LOUDNESS_H #include #include #include "sound_decoder.hpp" namespace MWSound { class Sound_Loudness { float mSamplesPerSec; int mSampleRate; ChannelConfig mChannelConfig; SampleType mSampleType; // Loudness sample info std::vector mSamples; std::deque mQueue; public: /** * @param samplesPerSecond How many loudness values per second of audio to compute. * @param sampleRate the sample rate of the sound buffer * @param chans channel layout of the buffer * @param type sample type of the buffer */ Sound_Loudness(float samplesPerSecond, int sampleRate, ChannelConfig chans, SampleType type) : mSamplesPerSec(samplesPerSecond) , mSampleRate(sampleRate) , mChannelConfig(chans) , mSampleType(type) { } /** * Analyzes the energy (closely related to loudness) of a sound buffer. * The buffer will be divided into segments according to \a valuesPerSecond, * and for each segment a loudness value in the range of [0,1] will be computed. * The computed values are then added to the mSamples vector. This method should be called continuously * with chunks of audio until the whole audio file is processed. * If the size of \a data does not exactly fit a number of loudness samples, the remainder * will be kept in the mQueue and used in the next call to analyzeLoudness. * @param data the sound buffer to analyze, containing raw samples */ void analyzeLoudness(const std::vector& data); /** * Get loudness at a particular time. Before calling this, the stream has to be analyzed up to that point in time (see analyzeLoudness()). */ float getLoudnessAtTime(float sec) const; }; } #endif /* GAME_SOUND_LOUDNESS_H */ openmw-openmw-0.48.0/apps/openmw/mwsound/movieaudiofactory.cpp000066400000000000000000000136001445372753700246350ustar00rootroot00000000000000#include "movieaudiofactory.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "sound_decoder.hpp" #include "sound.hpp" namespace MWSound { class MovieAudioDecoder; class MWSoundDecoderBridge final : public Sound_Decoder { public: MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) : Sound_Decoder(nullptr) , mDecoder(decoder) { } private: MWSound::MovieAudioDecoder* mDecoder; void open(const std::string &fname) override; void close() override; std::string getName() override; void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override; size_t read(char *buffer, size_t bytes) override; size_t getSampleOffset() override; }; class MovieAudioDecoder : public Video::MovieAudioDecoder { public: MovieAudioDecoder(Video::VideoState *videoState) : Video::MovieAudioDecoder(videoState), mAudioTrack(nullptr) , mDecoderBridge(std::make_shared(this)) { } size_t getSampleOffset() { ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / av_get_bytes_per_sample(mOutputSampleFormat); return (size_t)(mAudioClock*mAudioContext->sample_rate) - clock_delay; } std::string getStreamName() { return std::string(); } private: // MovieAudioDecoder overrides double getAudioClock() override { return (double)getSampleOffset()/(double)mAudioContext->sample_rate - MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack); } void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) override { if (sampleFormat == AV_SAMPLE_FMT_U8P || sampleFormat == AV_SAMPLE_FMT_U8) sampleFormat = AV_SAMPLE_FMT_U8; else if (sampleFormat == AV_SAMPLE_FMT_S16P || sampleFormat == AV_SAMPLE_FMT_S16) sampleFormat = AV_SAMPLE_FMT_S16; else if (sampleFormat == AV_SAMPLE_FMT_FLTP || sampleFormat == AV_SAMPLE_FMT_FLT) sampleFormat = AV_SAMPLE_FMT_S16; // FIXME: check for AL_EXT_FLOAT32 support else sampleFormat = AV_SAMPLE_FMT_S16; if (channelLayout == AV_CH_LAYOUT_5POINT1 || channelLayout == AV_CH_LAYOUT_7POINT1 || channelLayout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support channelLayout = AV_CH_LAYOUT_STEREO; else if (channelLayout != AV_CH_LAYOUT_MONO && channelLayout != AV_CH_LAYOUT_STEREO) channelLayout = AV_CH_LAYOUT_STEREO; } public: ~MovieAudioDecoder() { if(mAudioTrack) MWBase::Environment::get().getSoundManager()->stopTrack(mAudioTrack); mAudioTrack = nullptr; mDecoderBridge.reset(); } MWBase::SoundStream *mAudioTrack; std::shared_ptr mDecoderBridge; }; void MWSoundDecoderBridge::open(const std::string &fname) { throw std::runtime_error("Method not implemented"); } void MWSoundDecoderBridge::close() {} std::string MWSoundDecoderBridge::getName() { return mDecoder->getStreamName(); } void MWSoundDecoderBridge::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { *samplerate = mDecoder->getOutputSampleRate(); uint64_t outputChannelLayout = mDecoder->getOutputChannelLayout(); if (outputChannelLayout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; else if (outputChannelLayout == AV_CH_LAYOUT_5POINT1) *chans = ChannelConfig_5point1; else if (outputChannelLayout == AV_CH_LAYOUT_7POINT1) *chans = ChannelConfig_7point1; else if (outputChannelLayout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; else if (outputChannelLayout == AV_CH_LAYOUT_QUAD) *chans = ChannelConfig_Quad; else throw std::runtime_error("Unsupported channel layout: "+ std::to_string(outputChannelLayout)); AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat(); if (outputSampleFormat == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; else if (outputSampleFormat == AV_SAMPLE_FMT_FLT) *type = SampleType_Float32; else if (outputSampleFormat == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else { char str[1024]; av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat); throw std::runtime_error(std::string("Unsupported sample format: ")+str); } } size_t MWSoundDecoderBridge::read(char *buffer, size_t bytes) { return mDecoder->read(buffer, bytes); } size_t MWSoundDecoderBridge::getSampleOffset() { return mDecoder->getSampleOffset(); } std::unique_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) { auto decoder = std::make_unique(videoState); decoder->setupFormat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundStream *sound = sndMgr->playTrack(decoder->mDecoderBridge, MWSound::Type::Movie); if (!sound) { decoder.reset(); return decoder; } decoder->mAudioTrack = sound; return decoder; } } openmw-openmw-0.48.0/apps/openmw/mwsound/movieaudiofactory.hpp000066400000000000000000000005461445372753700246470ustar00rootroot00000000000000#ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H #define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H #include namespace MWSound { class MovieAudioFactory : public Video::MovieAudioFactory { std::unique_ptr createDecoder(Video::VideoState* videoState) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/openal_output.cpp000066400000000000000000001320301445372753700240010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "openal_output.hpp" #include "sound_decoder.hpp" #include "sound.hpp" #include "soundmanagerimp.hpp" #include "loudness.hpp" #include "efx-presets.h" #ifndef ALC_ALL_DEVICES_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif #define MAKE_PTRID(id) ((void*)(uintptr_t)id) #define GET_PTRID(ptr) ((ALuint)(uintptr_t)ptr) namespace { const int sLoudnessFPS = 20; // loudness values per second of audio ALCenum checkALCError(ALCdevice *device, const char *func, int line) { ALCenum err = alcGetError(device); if(err != ALC_NO_ERROR) Log(Debug::Error) << "ALC error "<< alcGetString(device, err) << " (" << err << ") @ " << func << ":" << line; return err; } #define getALCError(d) checkALCError((d), __FUNCTION__, __LINE__) ALenum checkALError(const char *func, int line) { ALenum err = alGetError(); if(err != AL_NO_ERROR) Log(Debug::Error) << "AL error " << alGetString(err) << " (" << err << ") @ " << func << ":" << line; return err; } #define getALError() checkALError(__FUNCTION__, __LINE__) // Helper to get an OpenAL extension function template void convertPointer(T& dest, R src) { memcpy(&dest, &src, sizeof(src)); } template void getALCFunc(T& func, ALCdevice *device, const char *name) { void* funcPtr = alcGetProcAddress(device, name); convertPointer(func, funcPtr); } template void getALFunc(T& func, const char *name) { void* funcPtr = alGetProcAddress(name); convertPointer(func, funcPtr); } // Effect objects LPALGENEFFECTS alGenEffects; LPALDELETEEFFECTS alDeleteEffects; LPALISEFFECT alIsEffect; LPALEFFECTI alEffecti; LPALEFFECTIV alEffectiv; LPALEFFECTF alEffectf; LPALEFFECTFV alEffectfv; LPALGETEFFECTI alGetEffecti; LPALGETEFFECTIV alGetEffectiv; LPALGETEFFECTF alGetEffectf; LPALGETEFFECTFV alGetEffectfv; // Filter objects LPALGENFILTERS alGenFilters; LPALDELETEFILTERS alDeleteFilters; LPALISFILTER alIsFilter; LPALFILTERI alFilteri; LPALFILTERIV alFilteriv; LPALFILTERF alFilterf; LPALFILTERFV alFilterfv; LPALGETFILTERI alGetFilteri; LPALGETFILTERIV alGetFilteriv; LPALGETFILTERF alGetFilterf; LPALGETFILTERFV alGetFilterfv; // Auxiliary slot objects LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; void LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES &props) { ALint type = AL_NONE; alGetEffecti(effect, AL_EFFECT_TYPE, &type); if(type == AL_EFFECT_EAXREVERB) { alEffectf(effect, AL_EAXREVERB_DIFFUSION, props.flDiffusion); alEffectf(effect, AL_EAXREVERB_DENSITY, props.flDensity); alEffectf(effect, AL_EAXREVERB_GAIN, props.flGain); alEffectf(effect, AL_EAXREVERB_GAINHF, props.flGainHF); alEffectf(effect, AL_EAXREVERB_GAINLF, props.flGainLF); alEffectf(effect, AL_EAXREVERB_DECAY_TIME, props.flDecayTime); alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, props.flDecayHFRatio); alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, props.flDecayLFRatio); alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, props.flReflectionsGain); alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, props.flReflectionsPan); alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, props.flLateReverbGain); alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, props.flLateReverbPan); alEffectf(effect, AL_EAXREVERB_ECHO_TIME, props.flEchoTime); alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, props.flEchoDepth); alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, props.flModulationTime); alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, props.flModulationDepth); alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); alEffectf(effect, AL_EAXREVERB_HFREFERENCE, props.flHFReference); alEffectf(effect, AL_EAXREVERB_LFREFERENCE, props.flLFReference); alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); } else if(type == AL_EFFECT_REVERB) { alEffectf(effect, AL_REVERB_DIFFUSION, props.flDiffusion); alEffectf(effect, AL_REVERB_DENSITY, props.flDensity); alEffectf(effect, AL_REVERB_GAIN, props.flGain); alEffectf(effect, AL_REVERB_GAINHF, props.flGainHF); alEffectf(effect, AL_REVERB_DECAY_TIME, props.flDecayTime); alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props.flDecayHFRatio); alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, props.flReflectionsGain); alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, props.flLateReverbGain); alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); } getALError(); } } namespace MWSound { static ALenum getALFormat(ChannelConfig chans, SampleType type) { struct FormatEntry { ALenum format; ChannelConfig chans; SampleType type; }; struct FormatEntryExt { const char name[32]; ChannelConfig chans; SampleType type; }; static const std::array fmtlist{{ { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 }, { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 }, { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 }, { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 }, }}; for(auto &fmt : fmtlist) { if(fmt.chans == chans && fmt.type == type) return fmt.format; } if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { static const std::array mcfmtlist{{ { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, }}; for(auto &fmt : mcfmtlist) { if(fmt.chans == chans && fmt.type == type) { ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } } } if(alIsExtensionPresent("AL_EXT_FLOAT32")) { static const std::array fltfmtlist{{ { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 }, { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 }, }}; for(auto &fmt : fltfmtlist) { if(fmt.chans == chans && fmt.type == type) { ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } } if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { static const std::array fltmcfmtlist{{ { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 }, { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 }, { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 }, }}; for(auto &fmt : fltmcfmtlist) { if(fmt.chans == chans && fmt.type == type) { ALenum format = alGetEnumValue(fmt.name); if(format != 0 && format != -1) return format; } } } } Log(Debug::Warning) << "Unsupported sound format (" << getChannelConfigName(chans) << ", " << getSampleTypeName(type) << ")"; return AL_NONE; } // // A streaming OpenAL sound. // class OpenAL_SoundStream { static const ALfloat sBufferLength; private: ALuint mSource; std::array mBuffers; ALint mCurrentBufIdx; ALenum mFormat; ALsizei mSampleRate; ALuint mBufferSize; ALuint mFrameSize; ALint mSilence; DecoderPtr mDecoder; std::unique_ptr mLoudnessAnalyzer; std::atomic mIsFinished; void updateAll(bool local); OpenAL_SoundStream(const OpenAL_SoundStream &rhs); OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); friend class OpenAL_Output; public: OpenAL_SoundStream(ALuint src, DecoderPtr decoder); ~OpenAL_SoundStream(); bool init(bool getLoudnessData=false); bool isPlaying(); double getStreamDelay() const; double getStreamOffset() const; float getCurrentLoudness() const; bool process(); ALint refillQueue(); }; const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; // // A background streaming thread (keeps active streams processed) // struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; std::atomic mQuitNow; std::mutex mMutex; std::condition_variable mCondVar; std::thread mThread; StreamThread() : mQuitNow(false) , mThread([this] { run(); }) { } ~StreamThread() { mQuitNow = true; mMutex.lock(); mMutex.unlock(); mCondVar.notify_all(); mThread.join(); } // thread entry point void run() { std::unique_lock lock(mMutex); while(!mQuitNow) { StreamVec::iterator iter = mStreams.begin(); while(iter != mStreams.end()) { if((*iter)->process() == false) iter = mStreams.erase(iter); else ++iter; } mCondVar.wait_for(lock, std::chrono::milliseconds(50)); } } void add(OpenAL_SoundStream *stream) { std::lock_guard lock(mMutex); if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) { mStreams.push_back(stream); mCondVar.notify_all(); } } void remove(OpenAL_SoundStream *stream) { std::lock_guard lock(mMutex); StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); if(iter != mStreams.end()) mStreams.erase(iter); } void removeAll() { std::lock_guard lock(mMutex); mStreams.clear(); } private: StreamThread(const StreamThread &rhs); StreamThread& operator=(const StreamThread &rhs); }; OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) : mSource(src), mCurrentBufIdx(0), mFormat(AL_NONE), mSampleRate(0) , mBufferSize(0), mFrameSize(0), mSilence(0), mDecoder(std::move(decoder)) , mLoudnessAnalyzer(nullptr), mIsFinished(true) { mBuffers.fill(0); } OpenAL_SoundStream::~OpenAL_SoundStream() { if(mBuffers[0] && alIsBuffer(mBuffers[0])) alDeleteBuffers(mBuffers.size(), mBuffers.data()); alGetError(); mDecoder->close(); } bool OpenAL_SoundStream::init(bool getLoudnessData) { alGenBuffers(mBuffers.size(), mBuffers.data()); ALenum err = getALError(); if(err != AL_NO_ERROR) return false; ChannelConfig chans; SampleType type; try { mDecoder->getInfo(&mSampleRate, &chans, &type); mFormat = getALFormat(chans, type); } catch(std::exception &e) { Log(Debug::Error) << "Failed to get stream info: " << e.what(); return false; } switch(type) { case SampleType_UInt8: mSilence = 0x80; break; case SampleType_Int16: mSilence = 0x00; break; case SampleType_Float32: mSilence = 0x00; break; } mFrameSize = framesToBytes(1, chans, type); mBufferSize = static_cast(sBufferLength*mSampleRate); mBufferSize *= mFrameSize; if (getLoudnessData) mLoudnessAnalyzer = std::make_unique(sLoudnessFPS, mSampleRate, chans, type); mIsFinished = false; return true; } bool OpenAL_SoundStream::isPlaying() { ALint state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); getALError(); if(state == AL_PLAYING || state == AL_PAUSED) return true; return !mIsFinished; } double OpenAL_SoundStream::getStreamDelay() const { ALint state = AL_STOPPED; double d = 0.0; ALint offset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state == AL_PLAYING || state == AL_PAUSED) { ALint queued; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); ALint inqueue = mBufferSize/mFrameSize*queued - offset; d = (double)inqueue / (double)mSampleRate; } getALError(); return d; } double OpenAL_SoundStream::getStreamOffset() const { ALint state = AL_STOPPED; ALint offset; double t; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state == AL_PLAYING || state == AL_PAUSED) { ALint queued; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); ALint inqueue = mBufferSize/mFrameSize*queued - offset; t = (double)(mDecoder->getSampleOffset() - inqueue) / (double)mSampleRate; } else { /* Underrun, or not started yet. The decoder offset is where we'll play * next. */ t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; } getALError(); return t; } float OpenAL_SoundStream::getCurrentLoudness() const { if (!mLoudnessAnalyzer.get()) return 0.f; float time = getStreamOffset(); return mLoudnessAnalyzer->getLoudnessAtTime(time); } bool OpenAL_SoundStream::process() { try { if(refillQueue() > 0) { ALint state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state != AL_PLAYING && state != AL_PAUSED) { // Ensure all processed buffers are removed so we don't replay them. refillQueue(); alSourcePlay(mSource); } } } catch(std::exception&) { Log(Debug::Error) << "Error updating stream \"" << mDecoder->getName() << "\""; mIsFinished = true; } return !mIsFinished; } ALint OpenAL_SoundStream::refillQueue() { ALint processed; alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); while(processed > 0) { ALuint buf; alSourceUnqueueBuffers(mSource, 1, &buf); --processed; } ALint queued; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); if(!mIsFinished && (ALuint)queued < mBuffers.size()) { std::vector data(mBufferSize); for(;!mIsFinished && (ALuint)queued < mBuffers.size();++queued) { size_t got = mDecoder->read(data.data(), data.size()); if(got < data.size()) { mIsFinished = true; std::fill(data.begin()+got, data.end(), mSilence); } if(got > 0) { if (mLoudnessAnalyzer.get()) mLoudnessAnalyzer->analyzeLoudness(data); ALuint bufid = mBuffers[mCurrentBufIdx]; alBufferData(bufid, mFormat, data.data(), data.size(), mSampleRate); alSourceQueueBuffers(mSource, 1, &bufid); mCurrentBufIdx = (mCurrentBufIdx+1) % mBuffers.size(); } } } return queued; } // // An OpenAL output device // std::vector OpenAL_Output::enumerate() { std::vector devlist; const ALCchar *devnames; if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); else devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); while(devnames && *devnames) { devlist.emplace_back(devnames); devnames += strlen(devnames)+1; } return devlist; } bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) { deinit(); Log(Debug::Info) << "Initializing OpenAL..."; mDevice = alcOpenDevice(devname.c_str()); if(!mDevice && !devname.empty()) { Log(Debug::Warning) << "Failed to open \"" << devname << "\", trying default"; mDevice = alcOpenDevice(nullptr); } if(!mDevice) { Log(Debug::Error) << "Failed to open default audio device"; return false; } const ALCchar *name = nullptr; if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER); if(alcGetError(mDevice) != AL_NO_ERROR || !name) name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER); Log(Debug::Info) << "Opened \"" << name << "\""; ALCint major=0, minor=0; alcGetIntegerv(mDevice, ALC_MAJOR_VERSION, 1, &major); alcGetIntegerv(mDevice, ALC_MINOR_VERSION, 1, &minor); Log(Debug::Info) << " ALC Version: " << major << "." << minor <<"\n" << " ALC Extensions: " << alcGetString(mDevice, ALC_EXTENSIONS); ALC.EXT_EFX = alcIsExtensionPresent(mDevice, "ALC_EXT_EFX"); ALC.SOFT_HRTF = alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF"); std::vector attrs; attrs.reserve(15); if(ALC.SOFT_HRTF) { LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); attrs.push_back(ALC_HRTF_SOFT); attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE : hrtfmode == HrtfMode::Enable ? ALC_TRUE : /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); if(!hrtfname.empty()) { ALCint index = -1; ALCint num_hrtf; alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); for(ALCint i = 0;i < num_hrtf;++i) { const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); if(hrtfname == entry) { index = i; break; } } if(index < 0) Log(Debug::Warning) << "Failed to find HRTF \"" << hrtfname << "\", using default"; else { attrs.push_back(ALC_HRTF_ID_SOFT); attrs.push_back(index); } } } attrs.push_back(0); mContext = alcCreateContext(mDevice, attrs.data()); if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) { Log(Debug::Error) << "Failed to setup audio context: "<(maxmono+maxstereo, 256); if (maxtotal == 0) // workaround for broken implementations maxtotal = 256; } for(size_t i = 0;i < maxtotal;i++) { ALuint src = 0; alGenSources(1, &src); if(alGetError() != AL_NO_ERROR) break; mFreeSources.push_back(src); } if(mFreeSources.empty()) { Log(Debug::Warning) << "Could not allocate any sound sourcess"; alcMakeContextCurrent(nullptr); alcDestroyContext(mContext); mContext = nullptr; alcCloseDevice(mDevice); mDevice = nullptr; return false; } Log(Debug::Info) << "Allocated " << mFreeSources.size() << " sound sources"; if(ALC.EXT_EFX) { #define LOAD_FUNC(x) getALFunc(x, #x) LOAD_FUNC(alGenEffects); LOAD_FUNC(alDeleteEffects); LOAD_FUNC(alIsEffect); LOAD_FUNC(alEffecti); LOAD_FUNC(alEffectiv); LOAD_FUNC(alEffectf); LOAD_FUNC(alEffectfv); LOAD_FUNC(alGetEffecti); LOAD_FUNC(alGetEffectiv); LOAD_FUNC(alGetEffectf); LOAD_FUNC(alGetEffectfv); LOAD_FUNC(alGenFilters); LOAD_FUNC(alDeleteFilters); LOAD_FUNC(alIsFilter); LOAD_FUNC(alFilteri); LOAD_FUNC(alFilteriv); LOAD_FUNC(alFilterf); LOAD_FUNC(alFilterfv); LOAD_FUNC(alGetFilteri); LOAD_FUNC(alGetFilteriv); LOAD_FUNC(alGetFilterf); LOAD_FUNC(alGetFilterfv); LOAD_FUNC(alGenAuxiliaryEffectSlots); LOAD_FUNC(alDeleteAuxiliaryEffectSlots); LOAD_FUNC(alIsAuxiliaryEffectSlot); LOAD_FUNC(alAuxiliaryEffectSloti); LOAD_FUNC(alAuxiliaryEffectSlotiv); LOAD_FUNC(alAuxiliaryEffectSlotf); LOAD_FUNC(alAuxiliaryEffectSlotfv); LOAD_FUNC(alGetAuxiliaryEffectSloti); LOAD_FUNC(alGetAuxiliaryEffectSlotiv); LOAD_FUNC(alGetAuxiliaryEffectSlotf); LOAD_FUNC(alGetAuxiliaryEffectSlotfv); #undef LOAD_FUNC if(getALError() != AL_NO_ERROR) { ALC.EXT_EFX = false; goto skip_efx; } alGenFilters(1, &mWaterFilter); if(alGetError() == AL_NO_ERROR) { alFilteri(mWaterFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); if(alGetError() == AL_NO_ERROR) { Log(Debug::Info) << "Low-pass filter supported"; alFilterf(mWaterFilter, AL_LOWPASS_GAIN, 0.9f); alFilterf(mWaterFilter, AL_LOWPASS_GAINHF, 0.125f); } else { alDeleteFilters(1, &mWaterFilter); mWaterFilter = 0; } alGetError(); } alGenAuxiliaryEffectSlots(1, &mEffectSlot); alGetError(); alGenEffects(1, &mDefaultEffect); if(alGetError() == AL_NO_ERROR) { alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); if(alGetError() == AL_NO_ERROR) Log(Debug::Info) << "EAX Reverb supported"; else { alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); if(alGetError() == AL_NO_ERROR) Log(Debug::Info) << "Standard Reverb supported"; } EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM; props.flGain = 0.0f; LoadEffect(mDefaultEffect, props); } alGenEffects(1, &mWaterEffect); if(alGetError() == AL_NO_ERROR) { alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); if(alGetError() != AL_NO_ERROR) { alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); alGetError(); } LoadEffect(mWaterEffect, EFX_REVERB_PRESET_UNDERWATER); } alListenerf(AL_METERS_PER_UNIT, 1.0f / Constants::UnitsPerMeter); } skip_efx: alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Speed of sound is in units per second. Take the sound speed in air (assumed // meters per second), multiply by the units per meter to get the speed in u/s. alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); alGetError(); mInitialized = true; return true; } void OpenAL_Output::deinit() { mStreamThread->removeAll(); for(ALuint source : mFreeSources) alDeleteSources(1, &source); mFreeSources.clear(); if(mEffectSlot) alDeleteAuxiliaryEffectSlots(1, &mEffectSlot); mEffectSlot = 0; if(mDefaultEffect) alDeleteEffects(1, &mDefaultEffect); mDefaultEffect = 0; if(mWaterEffect) alDeleteEffects(1, &mWaterEffect); mWaterEffect = 0; if(mWaterFilter) alDeleteFilters(1, &mWaterFilter); mWaterFilter = 0; alcMakeContextCurrent(nullptr); if(mContext) alcDestroyContext(mContext); mContext = nullptr; if(mDevice) alcCloseDevice(mDevice); mDevice = nullptr; mInitialized = false; } std::vector OpenAL_Output::enumerateHrtf() { std::vector ret; if(!mDevice || !ALC.SOFT_HRTF) return ret; LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); ALCint num_hrtf; alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); ret.reserve(num_hrtf); for(ALCint i = 0;i < num_hrtf;++i) { const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); ret.emplace_back(entry); } return ret; } void OpenAL_Output::setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) { if(!mDevice || !ALC.SOFT_HRTF) { Log(Debug::Info) << "HRTF extension not present"; return; } LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); std::vector attrs; attrs.reserve(15); attrs.push_back(ALC_HRTF_SOFT); attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE : hrtfmode == HrtfMode::Enable ? ALC_TRUE : /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); if(!hrtfname.empty()) { ALCint index = -1; ALCint num_hrtf; alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); for(ALCint i = 0;i < num_hrtf;++i) { const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); if(hrtfname == entry) { index = i; break; } } if(index < 0) Log(Debug::Warning) << "Failed to find HRTF name \"" << hrtfname << "\", using default"; else { attrs.push_back(ALC_HRTF_ID_SOFT); attrs.push_back(index); } } attrs.push_back(0); alcResetDeviceSOFT(mDevice, attrs.data()); ALCint hrtf_state; alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); if(!hrtf_state) Log(Debug::Info) << "HRTF disabled"; else { const ALCchar *hrtf = alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT); Log(Debug::Info) << "Enabled HRTF " << hrtf; } } std::pair OpenAL_Output::loadSound(const std::string &fname) { getALError(); std::vector data; ALenum format = AL_NONE; int srate = 0; try { DecoderPtr decoder = mManager.getDecoder(); decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); ChannelConfig chans; SampleType type; decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); if(format) decoder->readAll(data); } catch(std::exception &e) { Log(Debug::Error) << "Failed to load audio from " << fname << ": " << e.what(); } if(data.empty()) { // If we failed to get any usable audio, substitute with silence. format = AL_FORMAT_MONO8; srate = 8000; data.assign(8000, -128); } ALint size; ALuint buf = 0; alGenBuffers(1, &buf); alBufferData(buf, format, data.data(), data.size(), srate); alGetBufferi(buf, AL_SIZE, &size); if(getALError() != AL_NO_ERROR) { if(buf && alIsBuffer(buf)) alDeleteBuffers(1, &buf); getALError(); return std::make_pair(nullptr, 0); } return std::make_pair(MAKE_PTRID(buf), size); } size_t OpenAL_Output::unloadSound(Sound_Handle data) { ALuint buffer = GET_PTRID(data); if(!buffer) return 0; // Make sure no sources are playing this buffer before unloading it. SoundVec::const_iterator iter = mActiveSounds.begin(); for(;iter != mActiveSounds.end();++iter) { if(!(*iter)->mHandle) continue; ALuint source = GET_PTRID((*iter)->mHandle); ALint srcbuf; alGetSourcei(source, AL_BUFFER, &srcbuf); if((ALuint)srcbuf == buffer) { alSourceStop(source); alSourcei(source, AL_BUFFER, 0); } } ALint size = 0; alGetBufferi(buffer, AL_SIZE, &size); alDeleteBuffers(1, &buffer); getALError(); return size; } void OpenAL_Output::initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); alSourcef(source, AL_MAX_DISTANCE, 1000.0f); alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); if(AL.SOFT_source_spatialize) alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); if(useenv) { if(mWaterFilter) alSourcei(source, AL_DIRECT_FILTER, (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL ); else if(mListenerEnv == Env_Underwater) { gain *= 0.9f; pitch *= 0.7f; } if(mEffectSlot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); } else { if(mWaterFilter) alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); if(mEffectSlot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); } alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { alSourcef(source, AL_REFERENCE_DISTANCE, mindist); alSourcef(source, AL_MAX_DISTANCE, maxdist); alSourcef(source, AL_ROLLOFF_FACTOR, 1.0f); alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); if(AL.SOFT_source_spatialize) alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); if((pos - mListenerPos).length2() > maxdist*maxdist) gain = 0.0f; if(useenv) { if(mWaterFilter) alSourcei(source, AL_DIRECT_FILTER, (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL ); else if(mListenerEnv == Env_Underwater) { gain *= 0.9f; pitch *= 0.7f; } if(mEffectSlot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); } else { if(mWaterFilter) alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); if(mEffectSlot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); } alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) { if(useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { gain *= 0.9f; pitch *= 0.7f; } alSourcef(source, AL_GAIN, gain); alSourcef(source, AL_PITCH, pitch); alSourcefv(source, AL_POSITION, pos.ptr()); alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } bool OpenAL_Output::playSound(Sound *sound, Sound_Handle data, float offset) { ALuint source; if(mFreeSources.empty()) { Log(Debug::Warning) << "No free sources!"; return false; } source = mFreeSources.front(); initCommon2D(source, sound->getPosition(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) { alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); alGetError(); return false; } alSourcePlay(source); if(getALError() != AL_NO_ERROR) { alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); alGetError(); return false; } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); mActiveSounds.push_back(sound); return true; } bool OpenAL_Output::playSound3D(Sound *sound, Sound_Handle data, float offset) { ALuint source; if(mFreeSources.empty()) { Log(Debug::Warning) << "No free sources!"; return false; } source = mFreeSources.front(); initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) { alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); alGetError(); return false; } alSourcePlay(source); if(getALError() != AL_NO_ERROR) { alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); alGetError(); return false; } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); mActiveSounds.push_back(sound); return true; } void OpenAL_Output::finishSound(Sound *sound) { if(!sound->mHandle) return; ALuint source = GET_PTRID(sound->mHandle); sound->mHandle = nullptr; // Rewind the stream to put the source back into an AL_INITIAL state, for // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); mFreeSources.push_back(source); mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); } bool OpenAL_Output::isSoundPlaying(Sound *sound) { if(!sound->mHandle) return false; ALuint source = GET_PTRID(sound->mHandle); ALint state = AL_STOPPED; alGetSourcei(source, AL_SOURCE_STATE, &state); getALError(); return state == AL_PLAYING || state == AL_PAUSED; } void OpenAL_Output::updateSound(Sound *sound) { if(!sound->mHandle) return; ALuint source = GET_PTRID(sound->mHandle); updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } bool OpenAL_Output::streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData) { if(mFreeSources.empty()) { Log(Debug::Warning) << "No free sources!"; return false; } ALuint source = mFreeSources.front(); if(sound->getIsLooping()) Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; initCommon2D(source, sound->getPosition(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); if(getALError() != AL_NO_ERROR) return false; OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); if(!stream->init(getLoudnessData)) { delete stream; return false; } mStreamThread->add(stream); mFreeSources.pop_front(); sound->mHandle = stream; mActiveStreams.push_back(sound); return true; } bool OpenAL_Output::streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) { if(mFreeSources.empty()) { Log(Debug::Warning) << "No free sources!"; return false; } ALuint source = mFreeSources.front(); if(sound->getIsLooping()) Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); if(getALError() != AL_NO_ERROR) return false; OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); if(!stream->init(getLoudnessData)) { delete stream; return false; } mStreamThread->add(stream); mFreeSources.pop_front(); sound->mHandle = stream; mActiveStreams.push_back(sound); return true; } void OpenAL_Output::finishStream(Stream *sound) { if(!sound->mHandle) return; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; sound->mHandle = nullptr; mStreamThread->remove(stream); // Rewind the stream to put the source back into an AL_INITIAL state, for // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); mFreeSources.push_back(source); mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); delete stream; } double OpenAL_Output::getStreamDelay(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); return stream->getStreamDelay(); } double OpenAL_Output::getStreamOffset(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); std::lock_guard lock(mStreamThread->mMutex); return stream->getStreamOffset(); } float OpenAL_Output::getStreamLoudness(Stream *sound) { if(!sound->mHandle) return 0.0; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); std::lock_guard lock(mStreamThread->mMutex); return stream->getCurrentLoudness(); } bool OpenAL_Output::isStreamPlaying(Stream *sound) { if(!sound->mHandle) return false; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); std::lock_guard lock(mStreamThread->mMutex); return stream->isPlaying(); } void OpenAL_Output::updateStream(Stream *sound) { if(!sound->mHandle) return; OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } void OpenAL_Output::startUpdate() { alcSuspendContext(alcGetCurrentContext()); } void OpenAL_Output::finishUpdate() { alcProcessContext(alcGetCurrentContext()); } void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) { if(mContext) { ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() }; alListenerfv(AL_POSITION, pos.ptr()); alListenerfv(AL_ORIENTATION, orient); if(env != mListenerEnv) { alSpeedOfSound(((env == Env_Underwater) ? Constants::SoundSpeedUnderwater : Constants::SoundSpeedInAir) * Constants::UnitsPerMeter); // Update active sources with the environment's direct filter if(mWaterFilter) { ALuint filter = (env == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL; for(Sound *sound : mActiveSounds) { if(sound->getUseEnv()) alSourcei(GET_PTRID(sound->mHandle), AL_DIRECT_FILTER, filter); } for(Stream *sound : mActiveStreams) { if(sound->getUseEnv()) alSourcei( reinterpret_cast(sound->mHandle)->mSource, AL_DIRECT_FILTER, filter ); } } // Update the environment effect if(mEffectSlot) alAuxiliaryEffectSloti(mEffectSlot, AL_EFFECTSLOT_EFFECT, (env == Env_Underwater) ? mWaterEffect : mDefaultEffect ); } getALError(); } mListenerPos = pos; mListenerEnv = env; } void OpenAL_Output::pauseSounds(int types) { std::vector sources; for(Sound *sound : mActiveSounds) { if((types&sound->getPlayType())) sources.push_back(GET_PTRID(sound->mHandle)); } for(Stream *sound : mActiveStreams) { if((types&sound->getPlayType())) { OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); sources.push_back(stream->mSource); } } if(!sources.empty()) { alSourcePausev(sources.size(), sources.data()); getALError(); } } void OpenAL_Output::pauseActiveDevice() { if (mDevice == nullptr) return; if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) { LPALCDEVICEPAUSESOFT alcDevicePauseSOFT = nullptr; getALCFunc(alcDevicePauseSOFT, mDevice, "alcDevicePauseSOFT"); alcDevicePauseSOFT(mDevice); getALCError(mDevice); } alListenerf(AL_GAIN, 0.0f); } void OpenAL_Output::resumeActiveDevice() { if (mDevice == nullptr) return; if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) { LPALCDEVICERESUMESOFT alcDeviceResumeSOFT = nullptr; getALCFunc(alcDeviceResumeSOFT, mDevice, "alcDeviceResumeSOFT"); alcDeviceResumeSOFT(mDevice); getALCError(mDevice); } alListenerf(AL_GAIN, 1.0f); } void OpenAL_Output::resumeSounds(int types) { std::vector sources; for(Sound *sound : mActiveSounds) { if((types&sound->getPlayType())) sources.push_back(GET_PTRID(sound->mHandle)); } for(Stream *sound : mActiveStreams) { if((types&sound->getPlayType())) { OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); sources.push_back(stream->mSource); } } if(!sources.empty()) { alSourcePlayv(sources.size(), sources.data()); getALError(); } } OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr) , mDevice(nullptr), mContext(nullptr) , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) , mWaterFilter(0), mWaterEffect(0), mDefaultEffect(0), mEffectSlot(0) , mStreamThread(std::make_unique()) { } OpenAL_Output::~OpenAL_Output() { OpenAL_Output::deinit(); } float OpenAL_Output::getTimeScaledPitch(SoundBase *sound) { const bool shouldScale = !(sound->mParams.mFlags & PlayMode::NoScaling); return shouldScale ? sound->getPitch() * mManager.getSimulationTimeScale() : sound->getPitch(); } } openmw-openmw-0.48.0/apps/openmw/mwsound/openal_output.hpp000066400000000000000000000066351445372753700240210ustar00rootroot00000000000000#ifndef GAME_SOUND_OPENAL_OUTPUT_H #define GAME_SOUND_OPENAL_OUTPUT_H #include #include #include #include #include "alc.h" #include "al.h" #include "alext.h" #include "sound_output.hpp" namespace MWSound { class SoundManager; class SoundBase; class Sound; class Stream; class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; ALCcontext *mContext; struct { bool EXT_EFX : 1; bool SOFT_HRTF : 1; } ALC = {false, false}; struct { bool SOFT_source_spatialize : 1; } AL = {false}; typedef std::deque IDDq; IDDq mFreeSources; typedef std::vector SoundVec; SoundVec mActiveSounds; typedef std::vector StreamVec; StreamVec mActiveStreams; osg::Vec3f mListenerPos; Environment mListenerEnv; ALuint mWaterFilter; ALuint mWaterEffect; ALuint mDefaultEffect; ALuint mEffectSlot; struct StreamThread; std::unique_ptr mStreamThread; void initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); void initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); float getTimeScaledPitch(SoundBase *sound); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); public: std::vector enumerate() override; bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) override; void deinit() override; std::vector enumerateHrtf() override; void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) override; std::pair loadSound(const std::string &fname) override; size_t unloadSound(Sound_Handle data) override; bool playSound(Sound *sound, Sound_Handle data, float offset) override; bool playSound3D(Sound *sound, Sound_Handle data, float offset) override; void finishSound(Sound *sound) override; bool isSoundPlaying(Sound *sound) override; void updateSound(Sound *sound) override; bool streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData=false) override; bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) override; void finishStream(Stream *sound) override; double getStreamDelay(Stream *sound) override; double getStreamOffset(Stream *sound) override; float getStreamLoudness(Stream *sound) override; bool isStreamPlaying(Stream *sound) override; void updateStream(Stream *sound) override; void startUpdate() override; void finishUpdate() override; void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) override; void pauseSounds(int types) override; void resumeSounds(int types) override; void pauseActiveDevice() override; void resumeActiveDevice() override; OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/regionsoundselector.cpp000066400000000000000000000042461445372753700252070ustar00rootroot00000000000000#include "regionsoundselector.hpp" #include #include #include #include #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWSound { namespace { int addChance(int result, const ESM::Region::SoundRef &v) { return result + v.mChance; } } RegionSoundSelector::RegionSoundSelector() : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) {} std::optional RegionSoundSelector::getNextRandom(float duration, const std::string& regionName, const MWBase::World& world) { mTimePassed += duration; if (mTimePassed < mTimeToNextEnvSound) return {}; const float a = Misc::Rng::rollClosedProbability(); mTimeToNextEnvSound = mMinTimeBetweenSounds + (mMaxTimeBetweenSounds - mMinTimeBetweenSounds) * a; mTimePassed = 0; if (mLastRegionName != regionName) { mLastRegionName = regionName; mSumChance = 0; } const ESM::Region* const region = world.getStore().get().search(mLastRegionName); if (region == nullptr) return {}; if (mSumChance == 0) { mSumChance = std::accumulate(region->mSoundList.begin(), region->mSoundList.end(), 0, addChance); if (mSumChance == 0) return {}; } const int r = Misc::Rng::rollDice(std::max(mSumChance, 100)); int pos = 0; const auto isSelected = [&] (const ESM::Region::SoundRef& sound) { if (r - pos < sound.mChance) return true; pos += sound.mChance; return false; }; const auto it = std::find_if(region->mSoundList.begin(), region->mSoundList.end(), isSelected); if (it == region->mSoundList.end()) return {}; return it->mSound; } } openmw-openmw-0.48.0/apps/openmw/mwsound/regionsoundselector.hpp000066400000000000000000000013261445372753700252100ustar00rootroot00000000000000#ifndef GAME_SOUND_REGIONSOUNDSELECTOR_H #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include #include namespace MWBase { class World; } namespace MWSound { class RegionSoundSelector { public: std::optional getNextRandom(float duration, const std::string& regionName, const MWBase::World& world); RegionSoundSelector(); private: float mTimeToNextEnvSound = 0.0f; int mSumChance = 0; std::string mLastRegionName; float mTimePassed = 0.0; float mMinTimeBetweenSounds; float mMaxTimeBetweenSounds; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/sound.hpp000066400000000000000000000161071445372753700222460ustar00rootroot00000000000000#ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H #include #include "sound_output.hpp" namespace MWSound { // Extra play flags, not intended for caller use enum PlayModeEx { Play_2D = 0, Play_StopAtFadeEnd = 1 << 28, Play_FadeExponential = 1 << 29, Play_InFade = 1 << 30, Play_3D = 1 << 31, Play_FadeFlagsMask = (Play_StopAtFadeEnd | Play_FadeExponential), }; // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } struct SoundParams { osg::Vec3f mPos; float mVolume = 1.0f; float mBaseVolume = 1.0f; float mPitch = 1.0f; float mMinDistance = 1.0f; float mMaxDistance = 1000.0f; int mFlags = 0; float mFadeVolume = 1.0f; float mFadeTarget = 0.0f; float mFadeStep = 0.0f; }; class SoundBase { SoundBase& operator=(const SoundBase&) = delete; SoundBase(const SoundBase&) = delete; SoundBase(SoundBase&&) = delete; SoundParams mParams; protected: Sound_Instance mHandle = nullptr; friend class OpenAL_Output; public: void setPosition(const osg::Vec3f &pos) { mParams.mPos = pos; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } /// Fade to the given linear gain within the specified amount of time. /// Note that the fade gain is independent of the sound volume. /// /// \param duration specifies the duration of the fade. For *linear* /// fades (default) this will be exactly the time at which the desired /// volume is reached. Let v0 be the initial volume, v1 be the target /// volume, and t0 be the initial time. Then the volume over time is /// given as /// /// v(t) = v0 + (v1 - v0) * (t - t0) / duration if t <= t0 + duration /// v(t) = v1 if t > t0 + duration /// /// For *exponential* fades this determines the time-constant of the /// exponential process describing the fade. In particular, we guarantee /// that we reach v0 + 0.99 * (v1 - v0) within the given duration. /// /// v(t) = v1 + (v0 - v1) * exp(-4.6 * (t0 - t) / duration) /// /// where -4.6 is approximately log(1%) (i.e., -40 dB). /// /// This interpolation mode is meant for environmental sound effects to /// achieve less jarring transitions. /// /// \param targetVolume is the linear gain that should be reached at /// the end of the fade. /// /// \param flags may be a combination of Play_FadeExponential and /// Play_StopAtFadeEnd. If Play_StopAtFadeEnd is set, stops the sound /// once the fade duration has passed or the target volume has been /// reached. If Play_FadeExponential is set, enables the exponential /// fade mode (see above). void setFade(float duration, float targetVolume, int flags = 0) { // Approximation of log(1%) (i.e., -40 dB). constexpr float minus40Decibel = -4.6f; // Do nothing if already at the target, unless we need to trigger a stop event if ((mParams.mFadeVolume == targetVolume) && !(flags & Play_StopAtFadeEnd)) return; mParams.mFadeTarget = targetVolume; mParams.mFlags = (mParams.mFlags & ~Play_FadeFlagsMask) | (flags & Play_FadeFlagsMask) | Play_InFade; if (duration > 0.0f) { if (mParams.mFlags & Play_FadeExponential) mParams.mFadeStep = -minus40Decibel / duration; else mParams.mFadeStep = (mParams.mFadeTarget - mParams.mFadeVolume) / duration; } else { mParams.mFadeVolume = mParams.mFadeTarget; mParams.mFadeStep = 0.0f; } } /// Updates the internal fading logic. /// /// \param dt is the time in seconds since the last call to update. /// /// \return true if the sound is still active, false if the sound has /// reached a fading destination that was marked with Play_StopAtFadeEnd. bool updateFade(float dt) { // Mark fade as done at this volume difference (-80dB when fading to zero) constexpr float minVolumeDifference = 1e-4f; if (!getInFade()) return true; // Perform the actual fade operation const float deltaBefore = mParams.mFadeTarget - mParams.mFadeVolume; if (mParams.mFlags & Play_FadeExponential) mParams.mFadeVolume += mParams.mFadeStep * deltaBefore * dt; else mParams.mFadeVolume += mParams.mFadeStep * dt; const float deltaAfter = mParams.mFadeTarget - mParams.mFadeVolume; // Abort fade if we overshot or reached the minimum difference if ((std::signbit(deltaBefore) != std::signbit(deltaAfter)) || (std::abs(deltaAfter) < minVolumeDifference)) { mParams.mFadeVolume = mParams.mFadeTarget; mParams.mFlags &= ~Play_InFade; } return getInFade() || !(mParams.mFlags & Play_StopAtFadeEnd); } const osg::Vec3f &getPosition() const { return mParams.mPos; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } float getMaxDistance() const { return mParams.mMaxDistance; } MWSound::Type getPlayType() const { return static_cast(mParams.mFlags & MWSound::Type::Mask); } bool getUseEnv() const { return !(mParams.mFlags & MWSound::PlayMode::NoEnv); } bool getIsLooping() const { return mParams.mFlags & MWSound::PlayMode::Loop; } bool getDistanceCull() const { return mParams.mFlags & MWSound::PlayMode::RemoveAtDistance; } bool getIs3D() const { return mParams.mFlags & Play_3D; } bool getInFade() const { return mParams.mFlags & Play_InFade; } void init(const SoundParams& params) { mParams = params; mHandle = nullptr; } SoundBase() = default; }; class Sound : public SoundBase { Sound& operator=(const Sound&) = delete; Sound(const Sound&) = delete; Sound(Sound&&) = delete; public: Sound() = default; }; class Stream : public SoundBase { Stream& operator=(const Stream&) = delete; Stream(const Stream&) = delete; Stream(Stream&&) = delete; public: Stream() = default; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/sound_buffer.cpp000066400000000000000000000116731445372753700235750ustar00rootroot00000000000000#include "sound_buffer.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include #include #include #include #include namespace MWSound { namespace { struct AudioParams { float mAudioDefaultMinDistance; float mAudioDefaultMaxDistance; float mAudioMinDistanceMult; float mAudioMaxDistanceMult; }; AudioParams makeAudioParams(const MWBase::World& world) { const auto& settings = world.getStore().get(); AudioParams params; params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat(); params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat(); return params; } } SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : mVfs(&vfs), mOutput(&output), mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) { } SoundBufferPool::~SoundBufferPool() { clear(); } Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const { const auto it = mBufferNameMap.find(soundId); if (it != mBufferNameMap.end()) { Sound_Buffer* sfx = it->second; if (sfx->getHandle() != nullptr) return sfx; } return nullptr; } Sound_Buffer* SoundBufferPool::load(const std::string& soundId) { if (mBufferNameMap.empty()) { for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); } Sound_Buffer* sfx; const auto it = mBufferNameMap.find(soundId); if (it != mBufferNameMap.end()) sfx = it->second; else { const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); if (sound == nullptr) return {}; sfx = insertSound(soundId, *sound); } if (sfx->getHandle() == nullptr) { auto [handle, size] = mOutput->loadSound(sfx->getResourceName()); if (handle == nullptr) return {}; sfx->mHandle = handle; mBufferCacheSize += size; if (mBufferCacheSize > mBufferCacheMax) { unloadUnused(); if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax) Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; } mUnusedBuffers.push_front(sfx); } return sfx; } void SoundBufferPool::clear() { for (auto &sfx : mSoundBuffers) { if(sfx.mHandle) mOutput->unloadSound(sfx.mHandle); sfx.mHandle = nullptr; } mUnusedBuffers.clear(); } Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) { static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); float min = sound.mData.mMinRange; float max = sound.mData.mMaxRange; if (min == 0 && max == 0) { min = audioParams.mAudioDefaultMinDistance; max = audioParams.mAudioDefaultMaxDistance; } min *= audioParams.mAudioMinDistanceMult; max *= audioParams.mAudioMaxDistanceMult; min = std::max(min, 1.0f); max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); sfx.mResourceName = mVfs->normalizeFilename(sfx.mResourceName); mBufferNameMap.emplace(soundId, &sfx); return &sfx; } void SoundBufferPool::unloadUnused() { while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin) { Sound_Buffer* const unused = mUnusedBuffers.back(); mBufferCacheSize -= mOutput->unloadSound(unused->getHandle()); unused->mHandle = nullptr; mUnusedBuffers.pop_back(); } } } openmw-openmw-0.48.0/apps/openmw/mwsound/sound_buffer.hpp000066400000000000000000000057541445372753700236050ustar00rootroot00000000000000#ifndef GAME_SOUND_SOUND_BUFFER_H #define GAME_SOUND_SOUND_BUFFER_H #include #include #include #include #include "sound_output.hpp" namespace ESM { struct Sound; } namespace VFS { class Manager; } namespace MWSound { class SoundBufferPool; class Sound_Buffer { public: template Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) : mResourceName(std::forward(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) {} const std::string& getResourceName() const noexcept { return mResourceName; } Sound_Handle getHandle() const noexcept { return mHandle; } float getVolume() const noexcept { return mVolume; } float getMinDist() const noexcept { return mMinDist; } float getMaxDist() const noexcept { return mMaxDist; } private: std::string mResourceName; float mVolume; float mMinDist; float mMaxDist; Sound_Handle mHandle = nullptr; std::size_t mUses = 0; friend class SoundBufferPool; }; class SoundBufferPool { public: SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); SoundBufferPool(const SoundBufferPool&) = delete; ~SoundBufferPool(); /// Lookup a soundId for its sound data (resource name, local volume, /// minRange, and maxRange) Sound_Buffer* lookup(const std::string& soundId) const; /// Lookup a soundId for its sound data (resource name, local volume, /// minRange, and maxRange), and ensure it's ready for use. Sound_Buffer* load(const std::string& soundId); void use(Sound_Buffer& sfx) { if (sfx.mUses++ == 0) { const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); if (it != mUnusedBuffers.end()) mUnusedBuffers.erase(it); } } void release(Sound_Buffer& sfx) { if (--sfx.mUses == 0) mUnusedBuffers.push_front(&sfx); } void clear(); private: const VFS::Manager* const mVfs; Sound_Output* mOutput; std::deque mSoundBuffers; std::unordered_map mBufferNameMap; std::size_t mBufferCacheMax; std::size_t mBufferCacheMin; std::size_t mBufferCacheSize = 0; // NOTE: unused buffers are stored in front-newest order. std::deque mUnusedBuffers; inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); inline void unloadUnused(); }; } #endif /* GAME_SOUND_SOUND_BUFFER_H */ openmw-openmw-0.48.0/apps/openmw/mwsound/sound_decoder.hpp000066400000000000000000000026711445372753700237340ustar00rootroot00000000000000#ifndef GAME_SOUND_SOUND_DECODER_H #define GAME_SOUND_SOUND_DECODER_H #include #include namespace VFS { class Manager; } namespace MWSound { enum SampleType { SampleType_UInt8, SampleType_Int16, SampleType_Float32 }; const char *getSampleTypeName(SampleType type); enum ChannelConfig { ChannelConfig_Mono, ChannelConfig_Stereo, ChannelConfig_Quad, ChannelConfig_5point1, ChannelConfig_7point1 }; const char *getChannelConfigName(ChannelConfig config); size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type); size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type); struct Sound_Decoder { const VFS::Manager* mResourceMgr; virtual void open(const std::string &fname) = 0; virtual void close() = 0; virtual std::string getName() = 0; virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; virtual size_t read(char *buffer, size_t bytes) = 0; virtual void readAll(std::vector &output); virtual size_t getSampleOffset() = 0; Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr) { } virtual ~Sound_Decoder() { } private: Sound_Decoder(const Sound_Decoder &rhs); Sound_Decoder& operator=(const Sound_Decoder &rhs); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/sound_output.hpp000066400000000000000000000055751445372753700236750ustar00rootroot00000000000000#ifndef GAME_SOUND_SOUND_OUTPUT_H #define GAME_SOUND_SOUND_OUTPUT_H #include #include #include #include "../mwbase/soundmanager.hpp" namespace MWSound { class SoundManager; struct Sound_Decoder; class Sound; class Stream; // An opaque handle for the implementation's sound buffers. typedef void *Sound_Handle; // An opaque handle for the implementation's sound instances. typedef void *Sound_Instance; enum class HrtfMode { Disable, Enable, Auto }; enum Environment { Env_Normal, Env_Underwater }; class Sound_Output { SoundManager &mManager; virtual std::vector enumerate() = 0; virtual bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) = 0; virtual void deinit() = 0; virtual std::vector enumerateHrtf() = 0; virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) = 0; virtual std::pair loadSound(const std::string &fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; virtual bool playSound(Sound *sound, Sound_Handle data, float offset) = 0; virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset) = 0; virtual void finishSound(Sound *sound) = 0; virtual bool isSoundPlaying(Sound *sound) = 0; virtual void updateSound(Sound *sound) = 0; virtual bool streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData=false) = 0; virtual bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) = 0; virtual void finishStream(Stream *sound) = 0; virtual double getStreamDelay(Stream *sound) = 0; virtual double getStreamOffset(Stream *sound) = 0; virtual float getStreamLoudness(Stream *sound) = 0; virtual bool isStreamPlaying(Stream *sound) = 0; virtual void updateStream(Stream *sound) = 0; virtual void startUpdate() = 0; virtual void finishUpdate() = 0; virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) = 0; virtual void pauseSounds(int types) = 0; virtual void resumeSounds(int types) = 0; virtual void pauseActiveDevice() = 0; virtual void resumeActiveDevice() = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); protected: bool mInitialized; Sound_Output(SoundManager &mgr) : mManager(mgr), mInitialized(false) { } public: virtual ~Sound_Output() { } bool isInitialized() const { return mInitialized; } friend class OpenAL_Output; friend class SoundManager; friend class SoundBufferPool; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/soundmanagerimp.cpp000066400000000000000000001171461445372753700243070ustar00rootroot00000000000000#include "soundmanagerimp.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/statemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound_output.hpp" #include "sound.hpp" #include "openal_output.hpp" #include "ffmpeg_decoder.hpp" namespace MWSound { namespace { constexpr float sMinUpdateInterval = 1.0f / 30.0f; constexpr float sSfxFadeInDuration = 1.0f; constexpr float sSfxFadeOutDuration = 1.0f; WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() { WaterSoundUpdaterSettings settings; settings.mNearWaterRadius = Fallback::Map::getInt("Water_NearWaterRadius"); settings.mNearWaterPoints = Fallback::Map::getInt("Water_NearWaterPoints"); settings.mNearWaterIndoorTolerance = Fallback::Map::getFloat("Water_NearWaterIndoorTolerance"); settings.mNearWaterOutdoorTolerance = Fallback::Map::getFloat("Water_NearWaterOutdoorTolerance"); settings.mNearWaterIndoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterIndoorID")); settings.mNearWaterOutdoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterOutdoorID")); return settings; } float initialFadeVolume(float squaredDist, Sound_Buffer *sfx, Type type, PlayMode mode) { // If a sound is farther away than its maximum distance, start playing it with a zero fade volume. // It can still become audible once the player moves closer. const float maxDist = sfx->getMaxDist(); if (squaredDist > (maxDist * maxDist)) return 0.0f; // This is a *heuristic* that causes environment sounds to fade in. The idea is the following: // - Only looped sounds playing through the effects channel are environment sounds // - Do not fade in sounds if the player is already so close that the sound plays at maximum volume const float minDist = sfx->getMinDist(); if ((squaredDist > (minDist * minDist)) && (type == Type::Sfx) && (mode & PlayMode::Loop)) return 0.0f; return 1.0; } } // For combining PlayMode and Type flags inline int operator|(PlayMode a, Type b) { return static_cast(a) | static_cast(b); } SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) : mVFS(vfs) , mOutput(new OpenAL_Output(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) , mUnderwaterSound(nullptr) , mNearWaterSound(nullptr) , mPlaybackPaused(false) , mTimePassed(0.f) , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { if(!useSound) { Log(Debug::Info) << "Sound disabled."; return; } std::string hrtfname = Settings::Manager::getString("hrtf", "Sound"); int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound"); HrtfMode hrtfmode = hrtfstate < 0 ? HrtfMode::Auto : hrtfstate > 0 ? HrtfMode::Enable : HrtfMode::Disable; std::string devname = Settings::Manager::getString("device", "Sound"); if(!mOutput->init(devname, hrtfname, hrtfmode)) { Log(Debug::Error) << "Failed to initialize audio output, sound disabled"; return; } std::vector names = mOutput->enumerate(); std::stringstream stream; stream << "Enumerated output devices:\n"; for(const std::string &name : names) stream << " " << name; Log(Debug::Info) << stream.str(); stream.str(""); names = mOutput->enumerateHrtf(); if(!names.empty()) { stream << "Enumerated HRTF names:\n"; for(const std::string &name : names) stream << " " << name; Log(Debug::Info) << stream.str(); } } SoundManager::~SoundManager() { SoundManager::clear(); mSoundBuffers.clear(); mOutput.reset(); } // Return a new decoder instance, used as needed by the output implementations DecoderPtr SoundManager::getDecoder() { return std::make_shared(mVFS); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { try { DecoderPtr decoder = getDecoder(); decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); return decoder; } catch(std::exception &e) { Log(Debug::Error) << "Failed to load audio from " << voicefile << ": " << e.what(); } return nullptr; } SoundPtr SoundManager::getSoundRef() { return mSounds.get(); } StreamPtr SoundManager::getStreamRef() { return mStreams.get(); } StreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal) { MWBase::World* world = MWBase::Environment::get().getWorld(); static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->mValue.getFloat(); static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->mValue.getFloat(); static float minDistance = std::max(fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult, 1.0f); static float maxDistance = std::max(fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult, minDistance); bool played; float basevol = volumeFromType(Type::Voice); StreamPtr sound = getStreamRef(); if(playlocal) { sound->init([&] { SoundParams params; params.mBaseVolume = basevol; params.mFlags = PlayMode::NoEnv | Type::Voice | Play_2D; return params; } ()); played = mOutput->streamSound(decoder, sound.get(), true); } else { sound->init([&] { SoundParams params; params.mPos = pos; params.mBaseVolume = basevol; params.mMinDistance = minDistance; params.mMaxDistance = maxDistance; params.mFlags = PlayMode::Normal | Type::Voice | Play_3D; return params; } ()); played = mOutput->streamSound3D(decoder, sound.get(), true); } if(!played) return nullptr; return sound; } // Gets the combined volume settings for the given sound type float SoundManager::volumeFromType(Type type) const { return mVolumeSettings.getVolumeFromType(type); } void SoundManager::stopMusic() { if(mMusic) { mOutput->finishStream(mMusic.get()); mMusic = nullptr; } } void SoundManager::streamMusicFull(const std::string& filename) { if(!mOutput->isInitialized()) return; Log(Debug::Info) << "Playing " << filename; mLastPlayedMusic = filename; stopMusic(); DecoderPtr decoder = getDecoder(); try { decoder->open(filename); } catch(std::exception &e) { Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what(); return; } mMusic = getStreamRef(); mMusic->init([&] { SoundParams params; params.mBaseVolume = volumeFromType(Type::Music); params.mFlags = PlayMode::NoEnvNoScaling | Type::Music | Play_2D; return params; } ()); mOutput->streamSound(decoder, mMusic.get()); } void SoundManager::advanceMusic(const std::string& filename) { if (!isMusicPlaying()) { streamMusicFull(filename); return; } mNextMusic = filename; mMusic->setFadeout(1.f); } void SoundManager::startRandomTitle() { const std::vector &filelist = mMusicFiles[mCurrentPlaylist]; auto &tracklist = mMusicToPlay[mCurrentPlaylist]; // Do a Fisher-Yates shuffle // Repopulate if playlist is empty if(tracklist.empty()) { tracklist.resize(filelist.size()); std::iota(tracklist.begin(), tracklist.end(), 0); } int i = Misc::Rng::rollDice(tracklist.size()); // Reshuffle if last played music is the same after a repopulation if(filelist[tracklist[i]] == mLastPlayedMusic) i = (i+1) % tracklist.size(); // Remove music from list after advancing music advanceMusic(filelist[tracklist[i]]); tracklist[i] = tracklist.back(); tracklist.pop_back(); } void SoundManager::streamMusic(const std::string& filename) { advanceMusic("Music/"+filename); } bool SoundManager::isMusicPlaying() { return mMusic && mOutput->isStreamPlaying(mMusic.get()); } void SoundManager::playPlaylist(const std::string &playlist) { if (mCurrentPlaylist == playlist) return; if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist)) filelist.push_back(name); mMusicFiles[playlist] = filelist; } if (mMusicFiles[playlist].empty()) return; mCurrentPlaylist = playlist; startRandomTitle(); } void SoundManager::playTitleMusic() { if (mCurrentPlaylist == "Title") return; if (mMusicFiles.find("Title") == mMusicFiles.end()) { std::vector filelist; // Is there an ini setting for this filename or something? std::string filename = "music/special/morrowind title.mp3"; if (mVFS->exists(filename)) { filelist.emplace_back(filename); mMusicFiles["Title"] = filelist; } else { Log(Debug::Warning) << "Title music not found"; return; } } if (mMusicFiles["Title"].empty()) return; mCurrentPlaylist = "Title"; startRandomTitle(); } void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) { if(!mOutput->isInitialized()) return; DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; MWBase::World *world = MWBase::Environment::get().getWorld(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); stopSay(ptr); StreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); if(!sound) return; mSaySoundsQueue.emplace(ptr.mRef, SaySound {ptr.mCell, std::move(sound)}); } float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr.mRef); if(snditer != mActiveSaySounds.end()) { Stream *sound = snditer->second.mStream.get(); return mOutput->getStreamLoudness(sound); } return 0.0f; } void SoundManager::say(const std::string& filename) { if(!mOutput->isInitialized()) return; DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; stopSay(MWWorld::ConstPtr()); StreamPtr sound = playVoice(decoder, osg::Vec3f(), true); if(!sound) return; mActiveSaySounds.emplace(nullptr, SaySound {nullptr, std::move(sound)}); } bool SoundManager::sayDone(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr.mRef); if(snditer != mActiveSaySounds.end()) { if(mOutput->isStreamPlaying(snditer->second.mStream.get())) return false; return true; } return true; } bool SoundManager::sayActive(const MWWorld::ConstPtr &ptr) const { SaySoundMap::const_iterator snditer = mSaySoundsQueue.find(ptr.mRef); if(snditer != mSaySoundsQueue.end()) { if(mOutput->isStreamPlaying(snditer->second.mStream.get())) return true; return false; } snditer = mActiveSaySounds.find(ptr.mRef); if(snditer != mActiveSaySounds.end()) { if(mOutput->isStreamPlaying(snditer->second.mStream.get())) return true; return false; } return false; } void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) { SaySoundMap::iterator snditer = mSaySoundsQueue.find(ptr.mRef); if(snditer != mSaySoundsQueue.end()) { mOutput->finishStream(snditer->second.mStream.get()); mSaySoundsQueue.erase(snditer); } snditer = mActiveSaySounds.find(ptr.mRef); if(snditer != mActiveSaySounds.end()) { mOutput->finishStream(snditer->second.mStream.get()); mActiveSaySounds.erase(snditer); } } Stream *SoundManager::playTrack(const DecoderPtr& decoder, Type type) { if(!mOutput->isInitialized()) return nullptr; StreamPtr track = getStreamRef(); track->init([&] { SoundParams params; params.mBaseVolume = volumeFromType(type); params.mFlags = PlayMode::NoEnvNoScaling | type | Play_2D; return params; } ()); if(!mOutput->streamSound(decoder, track.get())) return nullptr; Stream* result = track.get(); const auto it = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), track); mActiveTracks.insert(it, std::move(track)); return result; } void SoundManager::stopTrack(Stream *stream) { mOutput->finishStream(stream); TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), stream, [] (const StreamPtr& lhs, Stream* rhs) { return lhs.get() < rhs; }); if(iter != mActiveTracks.end() && iter->get() == stream) mActiveTracks.erase(iter); } double SoundManager::getTrackTimeDelay(Stream *stream) { return mOutput->getStreamDelay(stream); } Sound* SoundManager::playSound(std::string_view soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { if(!mOutput->isInitialized()) return nullptr; Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time, so stop previous copy stopSound(sfx, MWWorld::ConstPtr()); SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; Sound* result = sound.get(); mActiveSounds[nullptr].mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } Sound *SoundManager::playSound3D(const MWWorld::ConstPtr &ptr, std::string_view soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { if(!mOutput->isInitialized()) return nullptr; const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3()); const float squaredDist = (mListenerPos - objpos).length2(); if ((mode & PlayMode::RemoveAtDistance) && squaredDist > 2000 * 2000) return nullptr; // Look up the sound in the ESM data Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy stopSound(sfx, ptr); bool played; SoundPtr sound = getSoundRef(); if(!(mode&PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer()) { sound->init([&] { SoundParams params; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else { sound->init([&] { SoundParams params; params.mPos = objpos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } if(!played) return nullptr; Sound* result = sound.get(); auto it = mActiveSounds.find(ptr.mRef); if (it == mActiveSounds.end()) it = mActiveSounds.emplace(ptr.mRef, ActiveSound {ptr.mCell, {}}).first; it->second.mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } Sound *SoundManager::playSound3D(const osg::Vec3f& initialPos, std::string_view soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { if(!mOutput->isInitialized()) return nullptr; // Look up the sound in the ESM data Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; const float squaredDist = (mListenerPos - initialPos).length2(); SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; params.mPos = initialPos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; Sound* result = sound.get(); mActiveSounds[nullptr].mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } void SoundManager::stopSound(Sound *sound) { if(sound) mOutput->finishSound(sound); } void SoundManager::stopSound(Sound_Buffer *sfx, const MWWorld::ConstPtr &ptr) { SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); if(snditer != mActiveSounds.end()) { for (SoundBufferRefPair &snd : snditer->second.mList) { if(snd.second == sfx) mOutput->finishSound(snd.first.get()); } } } void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, std::string_view soundId) { if(!mOutput->isInitialized()) return; Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (!sfx) return; stopSound(sfx, ptr); } void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr) { SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); if(snditer != mActiveSounds.end()) { for (SoundBufferRefPair &snd : snditer->second.mList) mOutput->finishSound(snd.first.get()); } SaySoundMap::iterator sayiter = mSaySoundsQueue.find(ptr.mRef); if(sayiter != mSaySoundsQueue.end()) mOutput->finishStream(sayiter->second.mStream.get()); sayiter = mActiveSaySounds.find(ptr.mRef); if(sayiter != mActiveSaySounds.end()) mOutput->finishStream(sayiter->second.mStream.get()); } void SoundManager::stopSound(const MWWorld::CellStore *cell) { for (auto& [ref, sound] : mActiveSounds) { if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) { for (SoundBufferRefPair& sndbuf : sound.mList) mOutput->finishSound(sndbuf.first.get()); } } for (const auto& [ref, sound] : mSaySoundsQueue) { if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) mOutput->finishStream(sound.mStream.get()); } for (const auto& [ref, sound]: mActiveSaySounds) { if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) mOutput->finishStream(sound.mStream.get()); } } void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, std::string_view soundId, float duration) { SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (sfx == nullptr) return; for (SoundBufferRefPair &sndbuf : snditer->second.mList) { if(sndbuf.second == sfx) sndbuf.first->setFadeout(duration); } } } bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr &ptr, std::string_view soundId) const { SoundMap::const_iterator snditer = mActiveSounds.find(ptr.mRef); if(snditer != mActiveSounds.end()) { Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); return std::find_if(snditer->second.mList.cbegin(), snditer->second.mList.cend(), [this,sfx](const SoundBufferRefPair &snd) -> bool { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } ) != snditer->second.mList.cend(); } return false; } void SoundManager::pauseSounds(BlockerType blocker, int types) { if(mOutput->isInitialized()) { if (mPausedSoundTypes[blocker] != 0) resumeSounds(blocker); types = types & Type::Mask; mOutput->pauseSounds(types); mPausedSoundTypes[blocker] = types; } } void SoundManager::resumeSounds(BlockerType blocker) { if(mOutput->isInitialized()) { mPausedSoundTypes[blocker] = 0; int types = int(Type::Mask); for (int currentBlocker = 0; currentBlocker < BlockerType::MaxCount; currentBlocker++) { if (currentBlocker != blocker) types &= ~mPausedSoundTypes[currentBlocker]; } mOutput->resumeSounds(types); } } void SoundManager::pausePlayback() { if (mPlaybackPaused) return; mPlaybackPaused = true; mOutput->pauseActiveDevice(); } void SoundManager::resumePlayback() { if (!mPlaybackPaused) return; mPlaybackPaused = false; mOutput->resumeActiveDevice(); } void SoundManager::updateRegionSound(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ConstPtr player = world->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->getCell(); if (!cell->isExterior()) return; if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound)) return; if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->mRegion, *world)) mCurrentRegionSound = playSound(*next, 1.0f, 1.0f); } void SoundManager::updateWaterSound() { MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::ConstPtr player = world->getPlayerPtr(); const ESM::Cell *curcell = player.getCell()->getCell(); const auto update = mWaterSoundUpdater.update(player, *world); WaterSoundAction action; Sound_Buffer* sfx; std::tie(action, sfx) = getWaterSoundAction(update, curcell); switch (action) { case WaterSoundAction::DoNothing: break; case WaterSoundAction::SetVolume: mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); mNearWaterSound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); break; case WaterSoundAction::FinishSound: mNearWaterSound->setFade(sSfxFadeOutDuration, 0.0f, Play_FadeExponential | Play_StopAtFadeEnd); break; case WaterSoundAction::PlaySound: if (mNearWaterSound) mOutput->finishSound(mNearWaterSound); mNearWaterSound = playSound(update.mId, update.mVolume, 1.0f, Type::Sfx, PlayMode::Loop); break; } mLastCell = curcell; } std::pair SoundManager::getWaterSoundAction( const WaterSoundUpdate& update, const ESM::Cell* cell) const { if (mNearWaterSound) { if (update.mVolume == 0.0f) return {WaterSoundAction::FinishSound, nullptr}; bool soundIdChanged = false; Sound_Buffer* sfx = mSoundBuffers.lookup(update.mId); if (mLastCell != cell) { const auto snditer = mActiveSounds.find(nullptr); if (snditer != mActiveSounds.end()) { const auto pairiter = std::find_if( snditer->second.mList.begin(), snditer->second.mList.end(), [this](const SoundBufferRefPairList::value_type &item) -> bool { return mNearWaterSound == item.first.get(); } ); if (pairiter != snditer->second.mList.end() && pairiter->second != sfx) soundIdChanged = true; } } if (soundIdChanged) return {WaterSoundAction::PlaySound, nullptr}; if (sfx) return {WaterSoundAction::SetVolume, sfx}; } else if (update.mVolume > 0.0f) return {WaterSoundAction::PlaySound, nullptr}; return {WaterSoundAction::DoNothing, nullptr}; } void SoundManager::cull3DSound(SoundBase *sound) { // Hard-coded distance of 2000.0f is from vanilla Morrowind const float maxDist = sound->getDistanceCull() ? 2000.0f : sound->getMaxDistance(); const float squaredMaxDist = maxDist * maxDist; const osg::Vec3f pos = sound->getPosition(); const float squaredDist = (mListenerPos - pos).length2(); if (squaredDist > squaredMaxDist) { // If getDistanceCull() is set, delete the sound after it has faded out sound->setFade(sSfxFadeOutDuration, 0.0f, Play_FadeExponential | (sound->getDistanceCull() ? Play_StopAtFadeEnd : 0)); } else { // Fade sounds back in once they are in range sound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); } } void SoundManager::updateSounds(float duration) { // We update active say sounds map for specific actors here // because for vanilla compatibility we can't do it immediately. SaySoundMap::iterator queuesayiter = mSaySoundsQueue.begin(); while (queuesayiter != mSaySoundsQueue.end()) { const auto dst = mActiveSaySounds.find(queuesayiter->first); if (dst == mActiveSaySounds.end()) mActiveSaySounds.emplace(queuesayiter->first, std::move(queuesayiter->second)); else dst->second = std::move(queuesayiter->second); mSaySoundsQueue.erase(queuesayiter++); } mTimePassed += duration; if (mTimePassed < sMinUpdateInterval) return; duration = mTimePassed; mTimePassed = 0.0f; // Make sure music is still playing if(!isMusicPlaying() && !mCurrentPlaylist.empty()) startRandomTitle(); Environment env = Env_Normal; if (mListenerUnderwater) env = Env_Underwater; else if(mUnderwaterSound) { mOutput->finishSound(mUnderwaterSound); mUnderwaterSound = nullptr; } mOutput->startUpdate(); mOutput->updateListener( mListenerPos, mListenerDir, mListenerUp, env ); updateMusic(duration); // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { MWWorld::ConstPtr ptr = snditer->first; SoundBufferRefPairList::iterator sndidx = snditer->second.mList.begin(); while(sndidx != snditer->second.mList.end()) { Sound *sound = sndidx->first.get(); if (sound->getIs3D()) { if (!ptr.isEmpty()) sound->setPosition(ptr.getRefData().getPosition().asVec3()); cull3DSound(sound); } if(!sound->updateFade(duration) || !mOutput->isSoundPlaying(sound)) { mOutput->finishSound(sound); if (sound == mUnderwaterSound) mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.mList.erase(sndidx); } else { mOutput->updateSound(sound); ++sndidx; } } if(snditer->second.mList.empty()) snditer = mActiveSounds.erase(snditer); else ++snditer; } SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); while(sayiter != mActiveSaySounds.end()) { MWWorld::ConstPtr ptr = sayiter->first; Stream *sound = sayiter->second.mStream.get(); if (sound->getIs3D()) { if (!ptr.isEmpty()) { MWBase::World *world = MWBase::Environment::get().getWorld(); sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); } cull3DSound(sound); } if(!sound->updateFade(duration) || !mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); sayiter = mActiveSaySounds.erase(sayiter); } else { mOutput->updateStream(sound); ++sayiter; } } TrackList::iterator trkiter = mActiveTracks.begin(); while (trkiter != mActiveTracks.end()) { Stream *sound = trkiter->get(); if(!mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); trkiter = mActiveTracks.erase(trkiter); } else { sound->updateFade(duration); mOutput->updateStream(sound); ++trkiter; } } if(mListenerUnderwater) { // Play underwater sound (after updating sounds) if(!mUnderwaterSound) mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Type::Sfx, PlayMode::LoopNoEnv); } mOutput->finishUpdate(); } void SoundManager::updateMusic(float duration) { if (!mNextMusic.empty()) { mMusic->updateFade(duration); mOutput->updateStream(mMusic.get()); if (mMusic->getRealVolume() <= 0.f) { streamMusicFull(mNextMusic); mNextMusic.clear(); } } } void SoundManager::update(float duration) { if(!mOutput->isInitialized() || mPlaybackPaused) return; updateSounds(duration); if (MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { updateRegionSound(duration); updateWaterSound(); } } void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings) { mVolumeSettings.update(); if(!mOutput->isInitialized()) return; mOutput->startUpdate(); for(SoundMap::value_type &snd : mActiveSounds) { for (SoundBufferRefPair& sndbuf : snd.second.mList) { Sound *sound = sndbuf.first.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateSound(sound); } } for(SaySoundMap::value_type &snd : mActiveSaySounds) { Stream *sound = snd.second.mStream.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } for(SaySoundMap::value_type &snd : mSaySoundsQueue) { Stream *sound = snd.second.mStream.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } for (const StreamPtr& sound : mActiveTracks) { sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound.get()); } if(mMusic) { mMusic->setBaseVolume(volumeFromType(mMusic->getPlayType())); mOutput->updateStream(mMusic.get()); } mOutput->finishUpdate(); } void SoundManager::setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) { mListenerPos = pos; mListenerDir = dir; mListenerUp = up; mListenerUnderwater = underwater; mWaterSoundUpdater.setUnderwater(underwater); } void SoundManager::updatePtr(const MWWorld::ConstPtr &old, const MWWorld::ConstPtr &updated) { SoundMap::iterator snditer = mActiveSounds.find(old.mRef); if(snditer != mActiveSounds.end()) snditer->second.mCell = updated.mCell; if (const auto it = mSaySoundsQueue.find(old.mRef); it != mSaySoundsQueue.end()) it->second.mCell = updated.mCell; if (const auto it = mActiveSaySounds.find(old.mRef); it != mActiveSaySounds.end()) it->second.mCell = updated.mCell; } // Default readAll implementation, for decoders that can't do anything // better void Sound_Decoder::readAll(std::vector &output) { size_t total = output.size(); size_t got; output.resize(total+32768); while((got=read(&output[total], output.size()-total)) > 0) { total += got; output.resize(total*2); } output.resize(total); } const char *getSampleTypeName(SampleType type) { switch(type) { case SampleType_UInt8: return "U8"; case SampleType_Int16: return "S16"; case SampleType_Float32: return "Float32"; } return "(unknown sample type)"; } const char *getChannelConfigName(ChannelConfig config) { switch(config) { case ChannelConfig_Mono: return "Mono"; case ChannelConfig_Stereo: return "Stereo"; case ChannelConfig_Quad: return "Quad"; case ChannelConfig_5point1: return "5.1 Surround"; case ChannelConfig_7point1: return "7.1 Surround"; } return "(unknown channel config)"; } size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type) { switch(config) { case ChannelConfig_Mono: frames *= 1; break; case ChannelConfig_Stereo: frames *= 2; break; case ChannelConfig_Quad: frames *= 4; break; case ChannelConfig_5point1: frames *= 6; break; case ChannelConfig_7point1: frames *= 8; break; } switch(type) { case SampleType_UInt8: frames *= 1; break; case SampleType_Int16: frames *= 2; break; case SampleType_Float32: frames *= 4; break; } return frames; } size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type) { return bytes / framesToBytes(1, config, type); } void SoundManager::clear() { SoundManager::stopMusic(); for(SoundMap::value_type &snd : mActiveSounds) { for (SoundBufferRefPair &sndbuf : snd.second.mList) { mOutput->finishSound(sndbuf.first.get()); mSoundBuffers.release(*sndbuf.second); } } mActiveSounds.clear(); mUnderwaterSound = nullptr; mNearWaterSound = nullptr; for(SaySoundMap::value_type &snd : mSaySoundsQueue) mOutput->finishStream(snd.second.mStream.get()); mSaySoundsQueue.clear(); for(SaySoundMap::value_type &snd : mActiveSaySounds) mOutput->finishStream(snd.second.mStream.get()); mActiveSaySounds.clear(); for(StreamPtr& sound : mActiveTracks) mOutput->finishStream(sound.get()); mActiveTracks.clear(); mPlaybackPaused = false; std::fill(std::begin(mPausedSoundTypes), std::end(mPausedSoundTypes), 0); } } openmw-openmw-0.48.0/apps/openmw/mwsound/soundmanagerimp.hpp000066400000000000000000000227321445372753700243100ustar00rootroot00000000000000#ifndef GAME_SOUND_SOUNDMANAGER_H #define GAME_SOUND_SOUNDMANAGER_H #include #include #include #include #include #include #include #include #include "../mwbase/soundmanager.hpp" #include "regionsoundselector.hpp" #include "watersoundupdater.hpp" #include "type.hpp" #include "volumesettings.hpp" #include "sound_buffer.hpp" namespace VFS { class Manager; } namespace ESM { struct Sound; struct Cell; } namespace MWSound { class Sound_Output; struct Sound_Decoder; class SoundBase; class Sound; class Stream; using SoundPtr = Misc::ObjectPtr; using StreamPtr = Misc::ObjectPtr; class SoundManager : public MWBase::SoundManager { const VFS::Manager* mVFS; std::unique_ptr mOutput; // Caches available music tracks by std::unordered_map> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played VolumeSettings mVolumeSettings; WaterSoundUpdater mWaterSoundUpdater; SoundBufferPool mSoundBuffers; Misc::ObjectPool mSounds; Misc::ObjectPool mStreams; typedef std::pair SoundBufferRefPair; typedef std::vector SoundBufferRefPairList; struct ActiveSound { const MWWorld::CellStore* mCell = nullptr; SoundBufferRefPairList mList; }; typedef std::map SoundMap; SoundMap mActiveSounds; struct SaySound { const MWWorld::CellStore* mCell; StreamPtr mStream; }; typedef std::map SaySoundMap; SaySoundMap mSaySoundsQueue; SaySoundMap mActiveSaySounds; typedef std::vector TrackList; TrackList mActiveTracks; StreamPtr mMusic; std::string mCurrentPlaylist; bool mListenerUnderwater; osg::Vec3f mListenerPos; osg::Vec3f mListenerDir; osg::Vec3f mListenerUp; int mPausedSoundTypes[BlockerType::MaxCount] = {}; Sound *mUnderwaterSound; Sound *mNearWaterSound; std::string mNextMusic; bool mPlaybackPaused; RegionSoundSelector mRegionSoundSelector; float mTimePassed; const ESM::Cell *mLastCell; Sound* mCurrentRegionSound; Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); // returns a decoder to start streaming, or nullptr if the sound was not found DecoderPtr loadVoice(const std::string &voicefile); SoundPtr getSoundRef(); StreamPtr getStreamRef(); StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); void streamMusicFull(const std::string& filename); void advanceMusic(const std::string& filename); void startRandomTitle(); void cull3DSound(SoundBase *sound); void updateSounds(float duration); void updateRegionSound(float duration); void updateWaterSound(); void updateMusic(float duration); float volumeFromType(Type type) const; enum class WaterSoundAction { DoNothing, SetVolume, FinishSound, PlaySound, }; std::pair getWaterSoundAction(const WaterSoundUpdate& update, const ESM::Cell* cell) const; SoundManager(const SoundManager &rhs); SoundManager& operator=(const SoundManager &rhs); protected: DecoderPtr getDecoder(); friend class OpenAL_Output; void stopSound(Sound_Buffer *sfx, const MWWorld::ConstPtr &ptr); ///< Stop the given object from playing given sound buffer. public: SoundManager(const VFS::Manager* vfs, bool useSound); ~SoundManager() override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; void stopMusic() override; ///< Stops music if it's playing void streamMusic(const std::string& filename) override; ///< Play a soundifle /// \param filename name of a sound file in "Music/" in the data directory. bool isMusicPlaying() override; ///< Returns true if music is playing void playPlaylist(const std::string &playlist) override; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist void playTitleMusic() override; ///< Start playing title music void say(const MWWorld::ConstPtr &reference, const std::string& filename) override; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. void say(const std::string& filename) override; ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. bool sayActive(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const override; ///< Is actor not speaking? bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const override; ///< For scripting backward compatibility void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) override; ///< Stop an actor speaking float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const override; ///< Check the currently playing say sound for this actor /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. Stream *playTrack(const DecoderPtr& decoder, Type type) override; ///< Play a 2D audio track, using a custom decoder void stopTrack(Stream *stream) override; ///< Stop the given audio track from playing double getTrackTimeDelay(Stream *stream) override; ///< Retives the time delay, in seconds, of the audio track (must be a sound /// returned by \ref playTrack). Only intended to be called by the track /// decoder's read method. Sound *playSound(std::string_view soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) override; ///< Play a sound, independently of 3D-position ///< @param offset Number of seconds into the sound to start playback. Sound *playSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) override; ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. ///< @param offset Number of seconds into the sound to start playback. Sound *playSound3D(const osg::Vec3f& initialPos, std::string_view soundId, float volume, float pitch, Type type, PlayMode mode, float offset=0) override; ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. ///< @param offset Number of seconds into the sound to start playback. void stopSound(Sound *sound) override; ///< Stop the given sound from playing /// @note no-op if \a sound is null void stopSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId) override; ///< Stop the given object from playing the given sound, void stopSound3D(const MWWorld::ConstPtr &reference) override; ///< Stop the given object from playing all sounds. void stopSound(const MWWorld::CellStore *cell) override; ///< Stop all sounds for the given cell. void fadeOutSound3D(const MWWorld::ConstPtr &reference, std::string_view soundId, float duration) override; ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. bool getSoundPlaying(const MWWorld::ConstPtr &reference, std::string_view soundId) const override; ///< Is the given sound currently playing on the given object? void pauseSounds(MWSound::BlockerType blocker, int types=int(Type::Mask)) override; ///< Pauses all currently playing sounds, including music. void resumeSounds(MWSound::BlockerType blocker) override; ///< Resumes all previously paused sounds. void pausePlayback() override; void resumePlayback() override; void update(float duration); void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) override; void updatePtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override; void clear() override; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/type.hpp000066400000000000000000000006071445372753700220750ustar00rootroot00000000000000#ifndef GAME_SOUND_TYPE_H #define GAME_SOUND_TYPE_H namespace MWSound { enum class Type { Sfx = 1 << 5, /* Normal SFX sound */ Voice = 1 << 6, /* Voice sound */ Foot = 1 << 7, /* Footstep sound */ Music = 1 << 8, /* Music track */ Movie = 1 << 9, /* Movie audio track */ Mask = Sfx | Voice | Foot | Music | Movie }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/volumesettings.cpp000066400000000000000000000026371445372753700242040ustar00rootroot00000000000000#include "volumesettings.hpp" #include #include namespace MWSound { namespace { float clamp(float value) { return std::clamp(value, 0.f, 1.f); } } VolumeSettings::VolumeSettings() : mMasterVolume(clamp(Settings::Manager::getFloat("master volume", "Sound"))), mSFXVolume(clamp(Settings::Manager::getFloat("sfx volume", "Sound"))), mMusicVolume(clamp(Settings::Manager::getFloat("music volume", "Sound"))), mVoiceVolume(clamp(Settings::Manager::getFloat("voice volume", "Sound"))), mFootstepsVolume(clamp(Settings::Manager::getFloat("footsteps volume", "Sound"))) { } float VolumeSettings::getVolumeFromType(Type type) const { float volume = mMasterVolume; switch(type) { case Type::Sfx: volume *= mSFXVolume; break; case Type::Voice: volume *= mVoiceVolume; break; case Type::Foot: volume *= mFootstepsVolume; break; case Type::Music: volume *= mMusicVolume; break; case Type::Movie: case Type::Mask: break; } return volume; } void VolumeSettings::update() { *this = VolumeSettings(); } } openmw-openmw-0.48.0/apps/openmw/mwsound/volumesettings.hpp000066400000000000000000000007301445372753700242010ustar00rootroot00000000000000#ifndef GAME_SOUND_VOLUMESETTINGS_H #define GAME_SOUND_VOLUMESETTINGS_H #include "type.hpp" namespace MWSound { class VolumeSettings { public: VolumeSettings(); float getVolumeFromType(Type type) const; void update(); private: float mMasterVolume; float mSFXVolume; float mMusicVolume; float mVoiceVolume; float mFootstepsVolume; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwsound/watersoundupdater.cpp000066400000000000000000000046051445372753700246710ustar00rootroot00000000000000#include "watersoundupdater.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/ptr.hpp" #include #include namespace MWSound { WaterSoundUpdater::WaterSoundUpdater(const WaterSoundUpdaterSettings& settings) : mSettings(settings) { } WaterSoundUpdate WaterSoundUpdater::update(const MWWorld::ConstPtr& player, const MWBase::World& world) const { WaterSoundUpdate result; result.mId = player.getCell()->isExterior() ? mSettings.mNearWaterOutdoorID : mSettings.mNearWaterIndoorID; result.mVolume = std::min(1.0f, getVolume(player, world)); return result; } float WaterSoundUpdater::getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const { if (mListenerUnderwater) return 1.0f; const MWWorld::CellStore& cell = *player.getCell(); if (!cell.getCell()->hasWater()) return 0.0f; const osg::Vec3f pos = player.getRefData().getPosition().asVec3(); const float dist = std::abs(cell.getWaterLevel() - pos.z()); if (cell.isExterior() && dist < mSettings.mNearWaterOutdoorTolerance) { if (mSettings.mNearWaterPoints <= 1) return (mSettings.mNearWaterOutdoorTolerance - dist) / mSettings.mNearWaterOutdoorTolerance; const float step = mSettings.mNearWaterRadius * 2.0f / (mSettings.mNearWaterPoints - 1); int underwaterPoints = 0; for (int x = 0; x < mSettings.mNearWaterPoints; x++) { for (int y = 0; y < mSettings.mNearWaterPoints; y++) { const float terrainX = pos.x() - mSettings.mNearWaterRadius + x * step; const float terrainY = pos.y() - mSettings.mNearWaterRadius + y * step; const float height = world.getTerrainHeightAt(osg::Vec3f(terrainX, terrainY, 0.0f)); if (height < 0) underwaterPoints++; } } return underwaterPoints * 2.0f / (mSettings.mNearWaterPoints * mSettings.mNearWaterPoints); } if (!cell.isExterior() && dist < mSettings.mNearWaterIndoorTolerance) return (mSettings.mNearWaterIndoorTolerance - dist) / mSettings.mNearWaterIndoorTolerance; return 0.0f; } } openmw-openmw-0.48.0/apps/openmw/mwsound/watersoundupdater.hpp000066400000000000000000000022011445372753700246640ustar00rootroot00000000000000#ifndef GAME_SOUND_WATERSOUNDUPDATER_H #define GAME_SOUND_WATERSOUNDUPDATER_H #include namespace MWBase { class World; } namespace MWWorld { class ConstPtr; } namespace MWSound { struct WaterSoundUpdaterSettings { int mNearWaterRadius; int mNearWaterPoints; float mNearWaterIndoorTolerance; float mNearWaterOutdoorTolerance; std::string mNearWaterIndoorID; std::string mNearWaterOutdoorID; }; struct WaterSoundUpdate { std::string mId; float mVolume; }; class WaterSoundUpdater { public: explicit WaterSoundUpdater(const WaterSoundUpdaterSettings& settings); WaterSoundUpdate update(const MWWorld::ConstPtr& player, const MWBase::World& world) const; void setUnderwater(bool value) { mListenerUnderwater = value; } private: const WaterSoundUpdaterSettings mSettings; bool mListenerUnderwater = false; float getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwstate/000077500000000000000000000000001445372753700203705ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwstate/character.cpp000066400000000000000000000121341445372753700230310ustar00rootroot00000000000000#include "character.hpp" #include #include #include #include #include #include bool MWState::operator< (const Slot& left, const Slot& right) { return left.mTimeStamp& contentFiles) { for (const std::string& c : contentFiles) { if (Misc::StringUtils::ciEndsWith(c, ".esm") || Misc::StringUtils::ciEndsWith(c, ".omwgame")) return c; } return ""; } void MWState::Character::addSlot (const boost::filesystem::path& path, const std::string& game) { Slot slot; slot.mPath = path; slot.mTimeStamp = boost::filesystem::last_write_time (path); ESM::ESMReader reader; reader.open (slot.mPath.string()); if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); slot.mProfile.load (reader); if (!Misc::StringUtils::ciEqual(getFirstGameFile(slot.mProfile.mContentFiles), game)) return; // this file is for a different game -> ignore mSlots.push_back (slot); } void MWState::Character::addSlot (const ESM::SavedGame& profile) { Slot slot; std::ostringstream stream; // The profile description is user-supplied, so we need to escape the path Utf8Stream description(profile.mDescription); while(!description.eof()) { auto c = description.consume(); if(c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters stream << static_cast(c); else stream << '_'; } const std::string ext = ".omwsave"; slot.mPath = mPath / (stream.str() + ext); // Append an index if necessary to ensure a unique file int i=0; while (boost::filesystem::exists(slot.mPath)) { const std::string test = stream.str() + " - " + std::to_string(++i); slot.mPath = mPath / (test + ext); } slot.mProfile = profile; slot.mTimeStamp = std::time (nullptr); mSlots.push_back (slot); } MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) : mPath (saves) { if (!boost::filesystem::is_directory (mPath)) { boost::filesystem::create_directories (mPath); } else { for (boost::filesystem::directory_iterator iter (mPath); iter!=boost::filesystem::directory_iterator(); ++iter) { boost::filesystem::path slotPath = *iter; try { addSlot (slotPath, game); } catch (...) {} // ignoring bad saved game files for now } std::sort (mSlots.begin(), mSlots.end()); } } void MWState::Character::cleanup() { if (mSlots.size() == 0) { // All slots are gone, no need to keep the empty directory if (boost::filesystem::is_directory (mPath)) { // Extra safety check to make sure the directory is empty (e.g. slots failed to parse header) boost::filesystem::directory_iterator it(mPath); if (it == boost::filesystem::directory_iterator()) boost::filesystem::remove_all(mPath); } } } const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile) { addSlot (profile); return &mSlots.back(); } void MWState::Character::deleteSlot (const Slot *slot) { int index = slot - &mSlots[0]; if (index<0 || index>=static_cast (mSlots.size())) { // sanity check; not entirely reliable throw std::logic_error ("slot not found"); } boost::filesystem::remove(slot->mPath); mSlots.erase (mSlots.begin()+index); } const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile) { int index = slot - &mSlots[0]; if (index<0 || index>=static_cast (mSlots.size())) { // sanity check; not entirely reliable throw std::logic_error ("slot not found"); } Slot newSlot = *slot; newSlot.mProfile = profile; newSlot.mTimeStamp = std::time (nullptr); mSlots.erase (mSlots.begin()+index); mSlots.push_back (newSlot); return &mSlots.back(); } MWState::Character::SlotIterator MWState::Character::begin() const { return mSlots.rbegin(); } MWState::Character::SlotIterator MWState::Character::end() const { return mSlots.rend(); } ESM::SavedGame MWState::Character::getSignature() const { if (mSlots.empty()) throw std::logic_error ("character signature not available"); std::vector::const_iterator iter (mSlots.begin()); Slot slot = *iter; for (++iter; iter!=mSlots.end(); ++iter) if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel) slot = *iter; else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel && iter->mTimeStamp>slot.mTimeStamp) slot = *iter; return slot.mProfile; } const boost::filesystem::path& MWState::Character::getPath() const { return mPath; } openmw-openmw-0.48.0/apps/openmw/mwstate/character.hpp000066400000000000000000000040721445372753700230400ustar00rootroot00000000000000#ifndef GAME_STATE_CHARACTER_H #define GAME_STATE_CHARACTER_H #include #include namespace MWState { struct Slot { boost::filesystem::path mPath; ESM::SavedGame mProfile; std::time_t mTimeStamp; }; bool operator< (const Slot& left, const Slot& right); std::string getFirstGameFile(const std::vector& contentFiles); class Character { public: typedef std::vector::const_reverse_iterator SlotIterator; private: boost::filesystem::path mPath; std::vector mSlots; void addSlot (const boost::filesystem::path& path, const std::string& game); void addSlot (const ESM::SavedGame& profile); public: Character (const boost::filesystem::path& saves, const std::string& game); void cleanup(); ///< Delete the directory we used, if it is empty const Slot *createSlot (const ESM::SavedGame& profile); ///< Create new slot. /// /// \attention The ownership of the slot is not transferred. /// \note Slot must belong to this character. /// /// \attention The \a slot pointer will be invalidated by this call. void deleteSlot (const Slot *slot); const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile); /// \note Slot must belong to this character. /// /// \attention The \a slot pointer will be invalidated by this call. SlotIterator begin() const; ///< Any call to createSlot and updateSlot can invalidate the returned iterator. SlotIterator end() const; const boost::filesystem::path& getPath() const; ESM::SavedGame getSignature() const; ///< Return signature information for this character. /// /// \attention This function must not be called if there are no slots. }; } #endif openmw-openmw-0.48.0/apps/openmw/mwstate/charactermanager.cpp000066400000000000000000000063621445372753700243720ustar00rootroot00000000000000#include "charactermanager.hpp" #include #include #include #include MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::vector& contentFiles) : mPath (saves), mCurrent (nullptr), mGame (getFirstGameFile(contentFiles)) { if (!boost::filesystem::is_directory (mPath)) { boost::filesystem::create_directories (mPath); } else { for (boost::filesystem::directory_iterator iter (mPath); iter!=boost::filesystem::directory_iterator(); ++iter) { boost::filesystem::path characterDir = *iter; if (boost::filesystem::is_directory (characterDir)) { Character character (characterDir, mGame); if (character.begin()!=character.end()) mCharacters.push_back (character); } } } } MWState::Character *MWState::CharacterManager::getCurrentCharacter () { return mCurrent; } void MWState::CharacterManager::deleteSlot(const MWState::Character *character, const MWState::Slot *slot) { std::list::iterator it = findCharacter(character); it->deleteSlot(slot); if (character->begin() == character->end()) { // All slots deleted, cleanup and remove this character it->cleanup(); if (character == mCurrent) mCurrent = nullptr; mCharacters.erase(it); } } MWState::Character* MWState::CharacterManager::createCharacter(const std::string& name) { std::ostringstream stream; // The character name is user-supplied, so we need to escape the path Utf8Stream nameStream(name); while(!nameStream.eof()) { auto c = nameStream.consume(); if(c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters stream << static_cast(c); else stream << '_'; } boost::filesystem::path path = mPath / stream.str(); // Append an index if necessary to ensure a unique directory int i=0; while (boost::filesystem::exists(path)) { std::ostringstream test; test << stream.str(); test << " - " << ++i; path = mPath / test.str(); } mCharacters.emplace_back(path, mGame); return &mCharacters.back(); } std::list::iterator MWState::CharacterManager::findCharacter(const MWState::Character* character) { std::list::iterator it = mCharacters.begin(); for (; it != mCharacters.end(); ++it) { if (&*it == character) break; } if (it == mCharacters.end()) throw std::logic_error ("invalid character"); return it; } void MWState::CharacterManager::setCurrentCharacter (const Character *character) { if (!character) mCurrent = nullptr; else { std::list::iterator it = findCharacter(character); mCurrent = &*it; } } std::list::const_iterator MWState::CharacterManager::begin() const { return mCharacters.begin(); } std::list::const_iterator MWState::CharacterManager::end() const { return mCharacters.end(); } openmw-openmw-0.48.0/apps/openmw/mwstate/charactermanager.hpp000066400000000000000000000027121445372753700243720ustar00rootroot00000000000000#ifndef GAME_STATE_CHARACTERMANAGER_H #define GAME_STATE_CHARACTERMANAGER_H #include #include "character.hpp" namespace MWState { class CharacterManager { boost::filesystem::path mPath; // Uses std::list, so that mCurrent stays valid when characters are deleted std::list mCharacters; Character *mCurrent; std::string mGame; private: CharacterManager (const CharacterManager&); ///< Not implemented CharacterManager& operator= (const CharacterManager&); ///< Not implemented std::list::iterator findCharacter(const MWState::Character* character); public: CharacterManager (const boost::filesystem::path& saves, const std::vector& contentFiles); Character *getCurrentCharacter (); ///< @note May return null void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); Character* createCharacter(const std::string& name); ///< Create new character within saved game management /// \param name Name for the character (does not need to be unique) void setCurrentCharacter (const Character *character); std::list::const_iterator begin() const; std::list::const_iterator end() const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwstate/quicksavemanager.cpp000066400000000000000000000016471445372753700244320ustar00rootroot00000000000000#include "quicksavemanager.hpp" MWState::QuickSaveManager::QuickSaveManager(std::string &saveName, unsigned int maxSaves) : mSaveName(saveName) , mMaxSaves(maxSaves) , mSlotsVisited(0) , mOldestSlotVisited(nullptr) { } void MWState::QuickSaveManager::visitSave(const Slot *saveSlot) { if(mSaveName == saveSlot->mProfile.mDescription) { ++mSlotsVisited; if(isOldestSave(saveSlot)) mOldestSlotVisited = saveSlot; } } bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const { if(mOldestSlotVisited == nullptr) return true; return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); } bool MWState::QuickSaveManager::shouldCreateNewSlot() const { return (mSlotsVisited < mMaxSaves); } const MWState::Slot *MWState::QuickSaveManager::getNextQuickSaveSlot() { if(shouldCreateNewSlot()) return nullptr; return mOldestSlotVisited; } openmw-openmw-0.48.0/apps/openmw/mwstate/quicksavemanager.hpp000066400000000000000000000020641445372753700244310ustar00rootroot00000000000000#ifndef GAME_STATE_QUICKSAVEMANAGER_H #define GAME_STATE_QUICKSAVEMANAGER_H #include #include "character.hpp" namespace MWState{ class QuickSaveManager{ std::string mSaveName; unsigned int mMaxSaves; unsigned int mSlotsVisited; const Slot *mOldestSlotVisited; private: bool shouldCreateNewSlot() const; bool isOldestSave(const Slot *compare) const; public: QuickSaveManager(std::string &saveName, unsigned int maxSaves); ///< A utility class to manage multiple quicksave slots /// /// \param saveName The name of the save ("QuickSave", "AutoSave", etc) /// \param maxSaves The maximum number of save slots to create before recycling old ones void visitSave(const Slot *saveSlot); ///< Visits the given \a slot \a const Slot *getNextQuickSaveSlot(); ///< Get the slot that the next quicksave should use. /// ///\return Either the oldest quicksave slot visited, or nullptr if a new slot can be made }; } #endif openmw-openmw-0.48.0/apps/openmw/mwstate/statemanagerimp.cpp000066400000000000000000000621001445372753700242540ustar00rootroot00000000000000#include "statemanagerimp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwscript/globalscripts.hpp" #include "quicksavemanager.hpp" void MWState::StateManager::cleanup (bool force) { if (mState!=State_NoGame || force) { MWBase::Environment::get().getSoundManager()->clear(); MWBase::Environment::get().getDialogueManager()->clear(); MWBase::Environment::get().getJournal()->clear(); MWBase::Environment::get().getScriptManager()->clear(); MWBase::Environment::get().getWindowManager()->clear(); MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getInputManager()->clear(); MWBase::Environment::get().getMechanicsManager()->clear(); mState = State_NoGame; mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; MWMechanics::CreatureStats::cleanup(); } MWBase::Environment::get().getLuaManager()->clear(); } std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) const { const std::vector& current = MWBase::Environment::get().getWorld()->getContentFiles(); const std::vector& prev = reader.getGameFiles(); std::map map; for (int iPrev = 0; iPrev (prev.size()); ++iPrev) { std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) if (id==Misc::StringUtils::lowerCase (current[iCurrent])) { map.insert (std::make_pair (iPrev, iCurrent)); break; } } return map; } MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::vector& contentFiles) : mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, contentFiles), mTimePlayed (0) { } void MWState::StateManager::requestQuit() { mQuitRequest = true; } bool MWState::StateManager::hasQuitRequest() const { return mQuitRequest; } void MWState::StateManager::askLoadRecent() { if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) return; if( !mAskLoadRecent ) { const MWState::Character* character = getCurrentCharacter(); if(!character || character->begin() == character->end())//no saves { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } else { MWState::Slot lastSave = *character->begin(); std::vector buttons; buttons.emplace_back("#{sYes}"); buttons.emplace_back("#{sNo}"); std::string tag("%s"); std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); size_t pos = message.find(tag); message.replace(pos, tag.length(), lastSave.mProfile.mDescription); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); mAskLoadRecent = true; } } } MWState::StateManager::State MWState::StateManager::getState() const { return mState; } void MWState::StateManager::newGame (bool bypass) { cleanup(); if (!bypass) MWBase::Environment::get().getWindowManager()->setNewGame (true); try { Log(Debug::Info) << "Starting a new game"; MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); MWBase::Environment::get().getLuaManager()->newGameStarted(); MWBase::Environment::get().getWorld()->startNewGame (bypass); mState = State_Running; MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); } catch (std::exception& e) { std::stringstream error; error << "Failed to start new game: " << e.what(); Log(Debug::Error) << error.str(); cleanup (true); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); std::vector buttons; buttons.emplace_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); } } void MWState::StateManager::endGame() { mState = State_Ended; } void MWState::StateManager::resumeGame() { mState = State_Running; } void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) { MWState::Character* character = getCurrentCharacter(); try { const auto start = std::chrono::steady_clock::now(); MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap(); if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); std::string name = player.get()->mBase->mName; character = mCharacterManager.createCharacter(name); mCharacterManager.setCurrentCharacter(character); } ESM::SavedGame profile; MWBase::World& world = *MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world.getPlayerPtr(); profile.mContentFiles = world.getContentFiles(); profile.mPlayerName = player.get()->mBase->mName; profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); std::string classId = player.get()->mBase->mClass; if (world.getStore().get().isDynamic(classId)) profile.mPlayerClassName = world.getStore().get().find(classId)->mName; else profile.mPlayerClassId = classId; profile.mPlayerCell = world.getCellName(); profile.mInGameTime = world.getEpochTimeStamp(); profile.mTimePlayed = mTimePlayed; profile.mDescription = description; Log(Debug::Info) << "Making a screenshot for saved game '" << description << "'"; writeScreenshot(profile.mScreenshot); if (!slot) slot = character->createSlot (profile); else slot = character->updateSlot (slot, profile); // Make sure the animation state held by references is up to date before saving the game. MWBase::Environment::get().getMechanicsManager()->persistAnimationStates(); Log(Debug::Info) << "Writing saved game '" << description << "' for character '" << profile.mPlayerName << "'"; // Write to a memory stream first. If there is an exception during the save process, we don't want to trash the // existing save file we are overwriting. std::stringstream stream; ESM::ESMWriter writer; for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles()) writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0 writer.setFormat (ESM::SavedGame::sCurrentFormat); // all unused writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); int recordCount = 1 // saved game header +MWBase::Environment::get().getJournal()->countSavedGameRecords() +MWBase::Environment::get().getLuaManager()->countSavedGameRecords() +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() +MWBase::Environment::get().getInputManager()->countSavedGameRecords() +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); writer.setRecordCount (recordCount); writer.save (stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); listener.setLabel("#{sNotifyMessage4}", true); Loading::ScopedLoad load(&listener); writer.startRecord (ESM::REC_SAVE); slot->mProfile.save (writer); writer.endRecord (ESM::REC_SAVE); MWBase::Environment::get().getJournal()->write (writer, listener); MWBase::Environment::get().getDialogueManager()->write (writer, listener); // LuaManager::write should be called before World::write because world also saves // local scripts that depend on LuaManager. MWBase::Environment::get().getLuaManager()->write(writer, listener); MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); MWBase::Environment::get().getMechanicsManager()->write(writer, listener); MWBase::Environment::get().getInputManager()->write(writer, listener); MWBase::Environment::get().getWindowManager()->write(writer, listener); // Ensure we have written the number of records that was estimated if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record Log(Debug::Warning) << "Warning: number of written savegame records does not match. Estimated: " << recordCount+1 << ", written: " << writer.getRecordCount(); writer.close(); if (stream.fail()) throw std::runtime_error("Write operation failed (memory stream)"); // All good, write to file boost::filesystem::ofstream filestream (slot->mPath, std::ios::binary); filestream << stream.rdbuf(); if (filestream.fail()) throw std::runtime_error("Write operation failed (file stream)"); Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); const auto finish = std::chrono::steady_clock::now(); Log(Debug::Info) << '\'' << description << "' is saved in " << std::chrono::duration_cast>(finish - start).count() << "ms"; } catch (const std::exception& e) { std::stringstream error; error << "Failed to save game: " << e.what(); Log(Debug::Error) << error.str(); std::vector buttons; buttons.emplace_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); // If no file was written, clean up the slot if (character && slot && !boost::filesystem::exists(slot->mPath)) { character->deleteSlot(slot); character->cleanup(); } } } void MWState::StateManager::quickSave (std::string name) { if (!(mState==State_Running && MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 // char gen && MWBase::Environment::get().getWindowManager()->isSavingAllowed())) { //You can not save your game right now MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}"); return; } int maxSaves = Settings::Manager::getInt("max quicksaves", "Saves"); if(maxSaves < 1) maxSaves = 1; Character* currentCharacter = getCurrentCharacter(); //Get current character QuickSaveManager saveFinder = QuickSaveManager(name, maxSaves); if (currentCharacter) { for (auto& save : *currentCharacter) { //Visiting slots allows the quicksave finder to find the oldest quicksave saveFinder.visitSave(&save); } } //Once all the saves have been visited, the save finder can tell us which //one to replace (or create) saveGame(name, saveFinder.getNextQuickSaveSlot()); } void MWState::StateManager::loadGame(const std::string& filepath) { for (const auto& character : mCharacterManager) { for (const auto& slot : character) { if (slot.mPath == boost::filesystem::path(filepath)) { loadGame(&character, slot.mPath.string()); return; } } } MWState::Character* character = getCurrentCharacter(); loadGame(character, filepath); } void MWState::StateManager::loadGame (const Character *character, const std::string& filepath) { try { cleanup(); Log(Debug::Info) << "Reading save file " << boost::filesystem::path(filepath).filename().string(); ESM::ESMReader reader; reader.open (filepath); if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); std::map contentFileMap = buildContentFileIndexMap (reader); MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener.setProgressRange(100); listener.setLabel("#{sLoadingMessage14}"); Loading::ScopedLoad load(&listener); bool firstPersonCam = false; size_t total = reader.getFileSize(); int currentPercent = 0; while (reader.hasMoreRecs()) { ESM::NAME n = reader.getRecName(); reader.getRecHeader(); switch (n.toInt()) { case ESM::REC_SAVE: { ESM::SavedGame profile; profile.load(reader); if (!verifyProfile(profile)) { cleanup (true); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); return; } mTimePlayed = profile.mTimePlayed; Log(Debug::Info) << "Loading saved game '" << profile.mDescription << "' for character '" << profile.mPlayerName << "'"; } break; case ESM::REC_JOUR: case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: MWBase::Environment::get().getJournal()->readRecord (reader, n.toInt()); break; case ESM::REC_DIAS: MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.toInt()); break; case ESM::REC_ALCH: case ESM::REC_ARMO: case ESM::REC_BOOK: case ESM::REC_CLAS: case ESM::REC_CLOT: case ESM::REC_ENCH: case ESM::REC_NPC_: case ESM::REC_SPEL: case ESM::REC_WEAP: case ESM::REC_GLOB: case ESM::REC_PLAY: case ESM::REC_CSTA: case ESM::REC_WTHR: case ESM::REC_DYNA: case ESM::REC_ACTC: case ESM::REC_PROJ: case ESM::REC_MPRJ: case ESM::REC_ENAB: case ESM::REC_LEVC: case ESM::REC_LEVI: case ESM::REC_CREA: case ESM::REC_CONT: case ESM::REC_RAND: MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap); break; case ESM::REC_CAM_: reader.getHNT(firstPersonCam, "FIRS"); break; case ESM::REC_GSCR: MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.toInt(), contentFileMap); break; case ESM::REC_GMAP: case ESM::REC_KEYS: case ESM::REC_ASPL: case ESM::REC_MARK: MWBase::Environment::get().getWindowManager()->readRecord(reader, n.toInt()); break; case ESM::REC_DCOU: case ESM::REC_STLN: MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.toInt()); break; case ESM::REC_INPU: MWBase::Environment::get().getInputManager()->readRecord(reader, n.toInt()); break; case ESM::REC_LUAM: MWBase::Environment::get().getLuaManager()->readRecord(reader, n.toInt()); break; default: // ignore invalid records Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toStringView(); reader.skipRecord(); } int progressPercent = static_cast(float(reader.getFileOffset())/total*100); if (progressPercent > currentPercent) { listener.increaseProgress(progressPercent-currentPercent); currentPercent = progressPercent; } } mCharacterManager.setCurrentCharacter(character); mState = State_Running; if (character) Settings::Manager::setString ("character", "Saves", character->getPath().filename().string()); MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->saveLoaded(); MWBase::Environment::get().getWorld()->setupPlayer(); MWBase::Environment::get().getWorld()->renderPlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getMechanicsManager()->playerLoaded(); if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); if (ptr.isInCell()) { const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false, false); } else { // Cell no longer exists (i.e. changed game files), choose a default cell Log(Debug::Warning) << "Warning: Player character's cell no longer exists, changing to the default cell"; MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(0,0); float x,y; MWBase::Environment::get().getWorld()->indexToPosition(0,0,x,y,false); ESM::Position pos; pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = 0; // should be adjusted automatically (adjustPlayerPos=true) pos.rot[0] = 0; pos.rot[1] = 0; pos.rot[2] = 0; MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false); } MWBase::Environment::get().getWorld()->updateProjectilesCasters(); // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive, // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); MWBase::Environment::get().getLuaManager()->gameLoaded(); } catch (const std::exception& e) { std::stringstream error; error << "Failed to load saved game: " << e.what(); Log(Debug::Error) << error.str(); cleanup (true); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); std::vector buttons; buttons.emplace_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); } } void MWState::StateManager::quickLoad() { if (Character* currentCharacter = getCurrentCharacter ()) { if (currentCharacter->begin() == currentCharacter->end()) return; loadGame (currentCharacter, currentCharacter->begin()->mPath.string()); //Get newest save } } void MWState::StateManager::deleteGame(const MWState::Character *character, const MWState::Slot *slot) { mCharacterManager.deleteSlot(character, slot); } MWState::Character *MWState::StateManager::getCurrentCharacter () { return mCharacterManager.getCurrentCharacter(); } MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() { return mCharacterManager.begin(); } MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() { return mCharacterManager.end(); } void MWState::StateManager::update (float duration) { mTimePlayed += duration; // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); MWState::Character *curCharacter = getCurrentCharacter(); if(iButton==0 && curCharacter) { mAskLoadRecent = false; //Load last saved game for current character MWState::Slot lastSave = *curCharacter->begin(); loadGame(curCharacter, lastSave.mPath.string()); } else if(iButton==1) { mAskLoadRecent = false; MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } } } bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const { const std::vector& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); bool notFound = false; for (const std::string& contentFile : profile.mContentFiles) { if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile) == selectedContentFiles.end()) { Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; notFound = true; } } if (notFound) { std::vector buttons; buttons.emplace_back("#{sYes}"); buttons.emplace_back("#{sNo}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{sMissingMastersMsg}", buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return false; } return true; } void MWState::StateManager::writeScreenshot(std::vector &imageData) const { int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot(screenshot.get(), screenshotW, screenshotH); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { Log(Debug::Error) << "Error: Unable to write screenshot, can't find a jpg ReaderWriter"; return; } std::ostringstream ostream; osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream); if (!result.success()) { Log(Debug::Error) << "Error: Unable to write screenshot: " << result.message() << " code " << result.status(); return; } std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); } openmw-openmw-0.48.0/apps/openmw/mwstate/statemanagerimp.hpp000066400000000000000000000061041445372753700242630ustar00rootroot00000000000000#ifndef GAME_STATE_STATEMANAGER_H #define GAME_STATE_STATEMANAGER_H #include #include "../mwbase/statemanager.hpp" #include #include "charactermanager.hpp" namespace MWState { class StateManager : public MWBase::StateManager { bool mQuitRequest; bool mAskLoadRecent; State mState; CharacterManager mCharacterManager; double mTimePlayed; private: void cleanup (bool force = false); bool verifyProfile (const ESM::SavedGame& profile) const; void writeScreenshot (std::vector& imageData) const; std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; public: StateManager (const boost::filesystem::path& saves, const std::vector& contentFiles); void requestQuit() override; bool hasQuitRequest() const override; void askLoadRecent() override; State getState() const override; void newGame (bool bypass = false) override; ///< Start a new game. /// /// \param bypass Skip new game mechanics. void endGame(); void resumeGame() override; void deleteGame (const MWState::Character *character, const MWState::Slot *slot) override; ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. void saveGame (const std::string& description, const Slot *slot = nullptr) override; ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// /// \note Slot must belong to the current character. ///Saves a file, using supplied filename, overwritting if needed /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again \param name Name of save, defaults to "Quicksave"**/ void quickSave(std::string name = "Quicksave") override; ///Loads the last saved file /** Used for quickload **/ void quickLoad() override; void loadGame (const std::string& filepath) override; ///< Load a saved game directly from the given file path. This will search the CharacterManager /// for a Character containing this save file, and set this Character current if one was found. /// Otherwise, a new Character will be created. void loadGame (const Character *character, const std::string &filepath) override; ///< Load a saved game file belonging to the given character. Character *getCurrentCharacter () override; ///< @note May return null. CharacterIterator characterBegin() override; ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned /// iterator. CharacterIterator characterEnd() override; void update(float duration); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/000077500000000000000000000000001445372753700203775ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw/mwworld/action.cpp000066400000000000000000000040011445372753700223530ustar00rootroot00000000000000#include "action.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" const MWWorld::Ptr& MWWorld::Action::getTarget() const { return mTarget; } void MWWorld::Action::setTarget(const MWWorld::Ptr& target) { mTarget = target; } MWWorld::Action::Action (bool keepSound, const Ptr& target) : mKeepSound (keepSound), mSoundOffset(0), mTarget (target) {} MWWorld::Action::~Action() {} void MWWorld::Action::execute (const Ptr& actor, bool noSound) { if(!mSoundId.empty() && !noSound) { MWSound::PlayMode envType = MWSound::PlayMode::Normal; // Action sounds should not have a distortion in GUI mode // example: take an item or drink a potion underwater if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWindowManager()->isGuiMode()) { envType = MWSound::PlayMode::NoEnv; } if(mKeepSound && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset ); else { bool local = mTarget.isEmpty() || !mTarget.isInCell(); // no usable target if(mKeepSound) MWBase::Environment::get().getSoundManager()->playSound3D( (local ? actor : mTarget).getRefData().getPosition().asVec3(), mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset ); else MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset ); } } executeImp (actor); } void MWWorld::Action::setSound (const std::string& id) { mSoundId = id; } void MWWorld::Action::setSoundOffset(float offset) { mSoundOffset=offset; } openmw-openmw-0.48.0/apps/openmw/mwworld/action.hpp000066400000000000000000000021631445372753700223670ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTION_H #define GAME_MWWORLD_ACTION_H #include #include "ptr.hpp" namespace MWWorld { /// \brief Abstract base for actions class Action { std::string mSoundId; bool mKeepSound; float mSoundOffset; Ptr mTarget; // not implemented Action (const Action& action); Action& operator= (const Action& action); virtual void executeImp (const Ptr& actor) = 0; protected: void setTarget(const Ptr&); public: const Ptr& getTarget() const; Action (bool keepSound = false, const Ptr& target = Ptr()); ///< \param keepSound Keep playing the sound even if the object the sound is played on is removed. virtual ~Action(); virtual bool isNullAction() { return false; } ///< Is running this action a no-op? (default false) void execute (const Ptr& actor, bool noSound = false); void setSound (const std::string& id); void setSoundOffset(float offset); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionalchemy.cpp000066400000000000000000000013071445372753700237240ustar00rootroot00000000000000#include "actionalchemy.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionAlchemy::ActionAlchemy(bool force) : Action (false) , mForce(force) { } void ActionAlchemy::executeImp (const Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; if(!mForce && MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); return; } MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionalchemy.hpp000066400000000000000000000004651445372753700237350ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONALCHEMY_H #define GAME_MWWORLD_ACTIONALCHEMY_H #include "action.hpp" namespace MWWorld { class ActionAlchemy : public Action { bool mForce; void executeImp (const Ptr& actor) override; public: ActionAlchemy(bool force=false); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionapply.cpp000066400000000000000000000017421445372753700234320ustar00rootroot00000000000000#include "actionapply.hpp" #include "class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/containerstore.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionApply::ActionApply (const Ptr& object, const std::string& id) : Action (false, object), mId (id) {} void ActionApply::executeImp (const Ptr& actor) { actor.getClass().consume(getTarget(), actor); } ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType) : Action (false, object), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) {} void ActionApplyWithSkill::executeImp (const Ptr& actor) { bool consumed = actor.getClass().consume(getTarget(), actor); if (consumed && mUsageType != -1 && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionapply.hpp000066400000000000000000000013121445372753700234300ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONAPPLY_H #define GAME_MWWORLD_ACTIONAPPLY_H #include #include "action.hpp" namespace MWWorld { class ActionApply : public Action { std::string mId; void executeImp (const Ptr& actor) override; public: ActionApply (const Ptr& object, const std::string& id); }; class ActionApplyWithSkill : public Action { std::string mId; int mSkillIndex; int mUsageType; void executeImp (const Ptr& actor) override; public: ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actiondoor.cpp000066400000000000000000000005461445372753700232510ustar00rootroot00000000000000#include "actiondoor.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" namespace MWWorld { ActionDoor::ActionDoor (const MWWorld::Ptr& object) : Action (false, object) { } void ActionDoor::executeImp (const MWWorld::Ptr& actor) { MWBase::Environment::get().getWorld()->activateDoor(getTarget()); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actiondoor.hpp000066400000000000000000000004761445372753700232600ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONDOOR_H #define GAME_MWWORLD_ACTIONDOOR_H #include "action.hpp" #include "ptr.hpp" namespace MWWorld { class ActionDoor : public Action { void executeImp (const MWWorld::Ptr& actor) override; public: ActionDoor (const Ptr& object); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actioneat.cpp000066400000000000000000000010041445372753700230450ustar00rootroot00000000000000#include "actioneat.hpp" #include #include "../mwworld/containerstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "class.hpp" namespace MWWorld { void ActionEat::executeImp (const Ptr& actor) { if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} } openmw-openmw-0.48.0/apps/openmw/mwworld/actioneat.hpp000066400000000000000000000004501445372753700230560ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONEAT_H #define GAME_MWWORLD_ACTIONEAT_H #include "action.hpp" namespace MWWorld { class ActionEat : public Action { void executeImp (const Ptr& actor) override; public: ActionEat (const MWWorld::Ptr& object); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionequip.cpp000066400000000000000000000071301445372753700234250ustar00rootroot00000000000000#include "actionequip.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include #include "inventorystore.hpp" #include "player.hpp" #include "class.hpp" namespace MWWorld { ActionEquip::ActionEquip (const MWWorld::Ptr& object, bool force) : Action (false, object) , mForce(force) { } void ActionEquip::executeImp (const Ptr& actor) { MWWorld::Ptr object = getTarget(); MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0) { if (actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}"); return; } if (!mForce) { std::pair result = object.getClass().canBeEquipped (object, actor); // display error message if the player tried to equip something if (!result.second.empty() && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(result.second); switch(result.first) { case 0: return; default: break; } } // slots that this item can be equipped in std::pair, bool> slots_ = getTarget().getClass().getEquipmentSlots(getTarget()); if (slots_.first.empty()) return; // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { if (*it == object) { break; } } if (it == invStore.end()) throw std::runtime_error("ActionEquip can't find item " + object.getCellRef().getRefId()); // equip the item in the first free slot std::vector::const_iterator slot=slots_.first.begin(); for (;slot!=slots_.first.end(); ++slot) { // if the item is equipped already, nothing to do if (invStore.getSlot(*slot) == it) return; if (invStore.getSlot(*slot) == invStore.end()) { // slot is not occupied invStore.equip(*slot, it, actor); break; } } // all slots are occupied -> cycle // move all slots one towards begin(), then equip the item in the slot that is now free if (slot == slots_.first.end()) { ContainerStoreIterator enchItem = invStore.getSelectedEnchantItem(); bool reEquip = false; for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) { invStore.unequipSlot(*slot, actor, false); if (slot + 1 != slots_.first.end()) { invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor); } else { invStore.equip(*slot, it, actor); } //Fix for issue of selected enchated item getting remmoved on cycle if (invStore.getSlot(*slot) == enchItem) { reEquip = true; } } if (reEquip) { invStore.setSelectedEnchantItem(enchItem); } } } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionequip.hpp000066400000000000000000000005431445372753700234330ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONEQUIP_H #define GAME_MWWORLD_ACTIONEQUIP_H #include "action.hpp" namespace MWWorld { class ActionEquip : public Action { bool mForce; void executeImp (const Ptr& actor) override; public: /// @param item to equip ActionEquip (const Ptr& object, bool force=false); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionharvest.cpp000066400000000000000000000067761445372753700237750ustar00rootroot00000000000000#include "actionharvest.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "class.hpp" #include "containerstore.hpp" namespace MWWorld { ActionHarvest::ActionHarvest (const MWWorld::Ptr& container) : Action (true, container) { setSound("Item Ingredient Up"); } void ActionHarvest::executeImp (const MWWorld::Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; MWWorld::Ptr target = getTarget(); MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); store.resolve(); MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); std::map takenMap; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (!it->getClass().showsInInventory(*it)) continue; int itemCount = it->getRefData().getCount(); // Note: it is important to check for crime before move an item from container. Otherwise owner check will not work // for a last item in the container - empty harvested containers are considered as "allowed to use". MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount); actorStore.add(*it, itemCount, actor); store.remove(*it, itemCount, getTarget()); takenMap[it->getClass().getName(*it)]+=itemCount; } // Spawn a messagebox (only for items added to player's inventory) if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) { std::ostringstream stream; int lineCount = 0; const static int maxLines = 10; for (auto & pair : takenMap) { std::string itemName = pair.first; int itemCount = pair.second; lineCount++; if (lineCount == maxLines) stream << "\n..."; else if (lineCount > maxLines) break; // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory std::string msgBox; if (itemCount == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage60}"); msgBox = Misc::StringUtils::format(msgBox, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage61}"); msgBox = Misc::StringUtils::format(msgBox, itemCount, itemName); } stream << msgBox; } std::string tooltip = stream.str(); // remove the first newline (easier this way) if (tooltip.size() > 0 && tooltip[0] == '\n') tooltip.erase(0, 1); if (tooltip.size() > 0) MWBase::Environment::get().getWindowManager()->messageBox(tooltip); } // Update animation object MWBase::Environment::get().getWorld()->disable(target); MWBase::Environment::get().getWorld()->enable(target); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionharvest.hpp000066400000000000000000000006471445372753700237710ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONHARVEST_H #define GAME_MWWORLD_ACTIONHARVEST_H #include "action.hpp" #include "ptr.hpp" namespace MWWorld { class ActionHarvest : public Action { void executeImp (const MWWorld::Ptr& actor) override; public: ActionHarvest (const Ptr& container); ///< \param container The Container the Player has activated. }; } #endif // ACTIONOPEN_H openmw-openmw-0.48.0/apps/openmw/mwworld/actionopen.cpp000066400000000000000000000015511445372753700232440ustar00rootroot00000000000000#include "actionopen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionOpen::ActionOpen (const MWWorld::Ptr& container) : Action (false, container) { } void ActionOpen::executeImp (const MWWorld::Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; if (actor != MWMechanics::getPlayer()) return; if (!MWBase::Environment::get().getMechanicsManager()->onOpen(getTarget())) return; MWMechanics::diseaseContact(actor, getTarget()); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, getTarget()); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionopen.hpp000066400000000000000000000006111445372753700232450ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONOPEN_H #define GAME_MWWORLD_ACTIONOPEN_H #include "action.hpp" namespace MWWorld { class ActionOpen : public Action { void executeImp (const MWWorld::Ptr& actor) override; public: ActionOpen (const Ptr& container); ///< \param container The Container the Player has activated. }; } #endif // ACTIONOPEN_H openmw-openmw-0.48.0/apps/openmw/mwworld/actionread.cpp000066400000000000000000000040311445372753700232120ustar00rootroot00000000000000#include "actionread.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "player.hpp" #include "class.hpp" #include "esmstore.hpp" namespace MWWorld { ActionRead::ActionRead (const MWWorld::Ptr& object) : Action (false, object) { } void ActionRead::executeImp (const MWWorld::Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; //Ensure we're not in combat if(MWMechanics::isPlayerInCombat() // Reading in combat is still allowed if the scroll/book is not in the player inventory yet // (since otherwise, there would be no way to pick it up) && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor) ) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}"); return; } LiveCellRef *ref = getTarget().get(); if (ref->mBase->mData.mIsScroll) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Scroll, getTarget()); else MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Book, getTarget()); MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); // Skill gain from books if (ref->mBase->mData.mSkillId >= 0 && ref->mBase->mData.mSkillId < ESM::Skill::Length && !npcStats.hasBeenUsed (ref->mBase->mId)) { MWWorld::LiveCellRef *playerRef = actor.get(); const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find ( playerRef->mBase->mClass ); npcStats.increaseSkill (ref->mBase->mData.mSkillId, *class_, true, true); npcStats.flagAsUsed (ref->mBase->mId); } } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionread.hpp000066400000000000000000000005511445372753700232220ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONREAD_H #define GAME_MWWORLD_ACTIONREAD_H #include "action.hpp" namespace MWWorld { class ActionRead : public Action { void executeImp (const MWWorld::Ptr& actor) override; public: /// @param book or scroll to read ActionRead (const Ptr& object); }; } #endif // ACTIONREAD_H openmw-openmw-0.48.0/apps/openmw/mwworld/actionrepair.cpp000066400000000000000000000013571445372753700235710ustar00rootroot00000000000000#include "actionrepair.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionRepair::ActionRepair(const Ptr& item, bool force) : Action (false, item) , mForce(force) { } void ActionRepair::executeImp (const Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; if(!mForce && MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); return; } MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Repair, getTarget()); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionrepair.hpp000066400000000000000000000005511445372753700235710ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONREPAIR_H #define GAME_MWWORLD_ACTIONREPAIR_H #include "action.hpp" namespace MWWorld { class ActionRepair : public Action { bool mForce; void executeImp (const Ptr& actor) override; public: /// @param item repair hammer ActionRepair(const Ptr& item, bool force=false); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionsoulgem.cpp000066400000000000000000000025051445372753700237560ustar00rootroot00000000000000#include "actionsoulgem.hpp" #include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" namespace MWWorld { ActionSoulgem::ActionSoulgem(const Ptr &object) : Action(false, object) { } void ActionSoulgem::executeImp(const Ptr &actor) { if (actor != MWMechanics::getPlayer()) return; if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); return; } const auto& target = getTarget(); const std::string targetSoul = target.getCellRef().getSoul(); if (targetSoul.empty()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage32}"); return; } if (!MWBase::Environment::get().getWorld()->getStore().get().search(targetSoul)) { Log(Debug::Warning) << "Soul '" << targetSoul << "' not found (item: '" << target.getCellRef().getRefId() << "')"; return; } MWBase::Environment::get().getWindowManager()->showSoulgemDialog(target); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionsoulgem.hpp000066400000000000000000000005351445372753700237640ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONSOULGEM_H #define GAME_MWWORLD_ACTIONSOULGEM_H #include "action.hpp" namespace MWWorld { class ActionSoulgem : public Action { void executeImp (const MWWorld::Ptr& actor) override; public: /// @param soulgem to use ActionSoulgem (const Ptr& object); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actiontake.cpp000066400000000000000000000024611445372753700232300ustar00rootroot00000000000000#include "actiontake.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwgui/inventorywindow.hpp" #include "class.hpp" #include "containerstore.hpp" namespace MWWorld { ActionTake::ActionTake (const MWWorld::Ptr& object) : Action (true, object) {} void ActionTake::executeImp (const Ptr& actor) { // When in GUI mode, we should use drag and drop if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) { MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); if (mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(getTarget()); return; } } MWBase::Environment::get().getMechanicsManager()->itemTaken( actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); MWWorld::Ptr newitem = *actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor); MWBase::Environment::get().getWorld()->deleteObject (getTarget()); setTarget(newitem); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actiontake.hpp000066400000000000000000000004541445372753700232350ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONTAKE_H #define GAME_MWWORLD_ACTIONTAKE_H #include "action.hpp" namespace MWWorld { class ActionTake : public Action { void executeImp (const Ptr& actor) override; public: ActionTake (const MWWorld::Ptr& object); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actiontalk.cpp000066400000000000000000000007101445372753700232320ustar00rootroot00000000000000#include "actiontalk.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionTalk::ActionTalk (const Ptr& actor) : Action (false, actor) {} void ActionTalk::executeImp (const Ptr& actor) { if (actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, getTarget()); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actiontalk.hpp000066400000000000000000000005431445372753700232430ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONTALK_H #define GAME_MWWORLD_ACTIONTALK_H #include "action.hpp" namespace MWWorld { class ActionTalk : public Action { void executeImp (const Ptr& actor) override; public: ActionTalk (const Ptr& actor); ///< \param actor The actor the player is talking to }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actionteleport.cpp000066400000000000000000000063521445372753700241450ustar00rootroot00000000000000#include "actionteleport.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellutils.hpp" #include "player.hpp" namespace MWWorld { ActionTeleport::ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers) : Action (true), mCellName (cellName), mPosition (position), mTeleportFollowers(teleportFollowers) { } void ActionTeleport::executeImp (const Ptr& actor) { if (mTeleportFollowers) { // Find any NPCs that are following the actor and teleport them with him std::set followers; getFollowers(actor, followers, mCellName.empty(), true); for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) teleport(*it); } teleport(actor); } void ActionTeleport::teleport(const Ptr &actor) { MWBase::World* world = MWBase::Environment::get().getWorld(); actor.getClass().getCreatureStats(actor).land(actor == world->getPlayerPtr()); if(actor == world->getPlayerPtr()) { world->getPlayer().setTeleported(true); if (mCellName.empty()) world->changeToExteriorCell (mPosition, true); else world->changeToInteriorCell (mCellName, mPosition, true); } else { if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(world->getPlayerPtr())) actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat(); else if (mCellName.empty()) { const osg::Vec2i index = positionToCellIndex(mPosition.pos[0], mPosition.pos[1]); world->moveObject(actor, world->getExterior(index.x(), index.y()), mPosition.asVec3(), true, true); } else world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3(), true, true); } } void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles) { std::set followers; MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); for(std::set::iterator it = followers.begin();it != followers.end();++it) { MWWorld::Ptr follower = *it; std::string script = follower.getClass().getScript(follower); if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor)) continue; if (!toExterior && !script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1 && follower.getCell()->getCell()->isExterior()) continue; if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800) continue; out.emplace(follower); } } } openmw-openmw-0.48.0/apps/openmw/mwworld/actionteleport.hpp000066400000000000000000000023771445372753700241550ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONTELEPORT_H #define GAME_MWWORLD_ACTIONTELEPORT_H #include #include #include #include "action.hpp" namespace MWWorld { class ActionTeleport : public Action { std::string mCellName; ESM::Position mPosition; bool mTeleportFollowers; /// Teleports this actor and also teleports anyone following that actor. void executeImp (const Ptr& actor) override; /// Teleports only the given actor (internal use). void teleport(const Ptr &actor); public: /// If cellName is empty, an exterior cell is assumed. /// @param teleportFollowers Whether to teleport any following actors of the target actor as well. ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output, /// e.g. so that the teleport action can calm them. static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles = false); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/actiontrap.cpp000066400000000000000000000025711445372753700232540ustar00rootroot00000000000000#include "actiontrap.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" namespace MWWorld { void ActionTrap::executeImp(const Ptr &actor) { osg::Vec3f actorPosition(actor.getRefData().getPosition().asVec3()); osg::Vec3f trapPosition(mTrapSource.getRefData().getPosition().asVec3()); float trapRange = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); // Note: can't just detonate the trap at the trapped object's location and use the blast // radius, because for most trap spells this is 1 foot, much less than the activation distance. // Using activation distance as the trap range. if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > trapRange) // player activated object outside range of trap { MWMechanics::CastSpell cast(mTrapSource, mTrapSource); cast.mHitPosition = trapPosition; cast.cast(mSpellId); } else // player activated object within range of trap, or NPC activated trap { MWMechanics::CastSpell cast(mTrapSource, actor); cast.mHitPosition = actorPosition; cast.cast(mSpellId); } mTrapSource.getCellRef().setTrap(""); } } openmw-openmw-0.48.0/apps/openmw/mwworld/actiontrap.hpp000066400000000000000000000010731445372753700232550ustar00rootroot00000000000000#ifndef GAME_MWWORLD_ACTIONTRAP_H #define GAME_MWWORLD_ACTIONTRAP_H #include #include "action.hpp" namespace MWWorld { class ActionTrap : public Action { std::string mSpellId; MWWorld::Ptr mTrapSource; void executeImp (const Ptr& actor) override; public: /// @param spellId /// @param trapSource ActionTrap (const std::string& spellId, const Ptr& trapSource) : Action(false, trapSource), mSpellId(spellId), mTrapSource(trapSource) {} }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellpreloader.cpp000066400000000000000000000364271445372753700237340ustar00rootroot00000000000000#include "cellpreloader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwrender/landmanager.hpp" #include "cellstore.hpp" #include "class.hpp" namespace { template bool contains(const std::vector& container, const Contained& contained, float tolerance) { for (const auto& pos : contained) { bool found = false; for (const auto& pos2 : container) { if ((pos.first-pos2.first).length2() < tolerance*tolerance && pos.second == pos2.second) { found = true; break; } } if (!found) return false; } return true; } } namespace MWWorld { struct ListModelsVisitor { ListModelsVisitor(std::vector& out) : mOut(out) { } virtual bool operator()(const MWWorld::Ptr& ptr) { ptr.getClass().getModelsToPreload(ptr, mOut); return true; } virtual ~ListModelsVisitor() = default; std::vector& mOut; }; /// Worker thread item: preload models in a cell. class PreloadItem : public SceneUtil::WorkItem { public: /// Constructor to be called from the main thread. PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, MWRender::LandManager* landManager, bool preloadInstances) : mIsExterior(cell->getCell()->isExterior()) , mX(cell->getCell()->getGridX()) , mY(cell->getCell()->getGridY()) , mSceneManager(sceneManager) , mBulletShapeManager(bulletShapeManager) , mKeyframeManager(keyframeManager) , mTerrain(terrain) , mLandManager(landManager) , mPreloadInstances(preloadInstances) , mAbort(false) { mTerrainView = mTerrain->createView(); ListModelsVisitor visitor (mMeshes); cell->forEach(visitor); } void abort() override { mAbort = true; } /// Preload work to be called from the worker thread. void doWork() override { if (mIsExterior) { try { mTerrain->cacheCell(mTerrainView.get(), mX, mY); mPreloadedObjects.insert(mLandManager->getLand(mX, mY)); } catch(std::exception&) { } } for (std::string& mesh: mMeshes) { if (mAbort) break; try { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { Misc::StringUtils::lowerCaseInPlace(mesh); if (mesh[slashpos+1] == 'x') { std::string kfname = mesh; if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) { kfname.replace(kfname.size()-4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) mPreloadedObjects.insert(mKeyframeManager->get(kfname)); } } } mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); if (mPreloadInstances) mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); else mPreloadedObjects.insert(mBulletShapeManager->getShape(mesh)); } catch (std::exception&) { // ignore error for now, would spam the log too much // error will be shown when visiting the cell } } } private: typedef std::vector MeshList; bool mIsExterior; int mX; int mY; MeshList mMeshes; Resource::SceneManager* mSceneManager; Resource::BulletShapeManager* mBulletShapeManager; Resource::KeyframeManager* mKeyframeManager; Terrain::World* mTerrain; MWRender::LandManager* mLandManager; bool mPreloadInstances; std::atomic mAbort; osg::ref_ptr mTerrainView; // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state std::set > mPreloadedObjects; }; class TerrainPreloadItem : public SceneUtil::WorkItem { public: TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) : mAbort(false) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) { } void doWork() override { for (unsigned int i=0; ireset(); mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mLoadingReporter); } mLoadingReporter.complete(); } void abort() override { mAbort = true; } void wait(Loading::Listener& listener) const { mLoadingReporter.wait(listener); } private: std::atomic mAbort; std::vector > mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; Loading::Reporter mLoadingReporter; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. class UpdateCacheItem : public SceneUtil::WorkItem { public: UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime) : mReferenceTime(referenceTime) , mResourceSystem(resourceSystem) { } void doWork() override { mResourceSystem->updateCache(mReferenceTime); } private: double mReferenceTime; Resource::ResourceSystem* mResourceSystem; }; CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) , mTerrain(terrain) , mLandManager(landManager) , mExpiryDelay(0.0) , mMinCacheSize(0) , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) , mLoadedTerrainTimestamp(0.0) { } CellPreloader::~CellPreloader() { if (mTerrainPreloadItem) { mTerrainPreloadItem->abort(); mTerrainPreloadItem->waitTillDone(); mTerrainPreloadItem = nullptr; } if (mUpdateCacheItem) { mUpdateCacheItem->waitTillDone(); mUpdateCacheItem = nullptr; } for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) it->second.mWorkItem->abort(); for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) it->second.mWorkItem->waitTillDone(); mPreloadCells.clear(); } void CellPreloader::preload(CellStore *cell, double timestamp) { if (!mWorkQueue) { Log(Debug::Error) << "Error: can't preload, no work queue set"; return; } if (cell->getState() == CellStore::State_Unloaded) { Log(Debug::Error) << "Error: can't preload objects for unloaded cell"; return; } PreloadMap::iterator found = mPreloadCells.find(cell); if (found != mPreloadCells.end()) { // already preloaded, nothing to do other than updating the timestamp found->second.mTimeStamp = timestamp; return; } while (mPreloadCells.size() >= mMaxCacheSize) { // throw out oldest cell to make room PreloadMap::iterator oldestCell = mPreloadCells.begin(); double oldestTimestamp = std::numeric_limits::max(); double threshold = 1.0; // seconds for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) { if (it->second.mTimeStamp < oldestTimestamp) { oldestTimestamp = it->second.mTimeStamp; oldestCell = it; } } if (oldestTimestamp + threshold < timestamp) { oldestCell->second.mWorkItem->abort(); mPreloadCells.erase(oldestCell); } else return; } osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); } void CellPreloader::notifyLoaded(CellStore *cell) { PreloadMap::iterator found = mPreloadCells.find(cell); if (found != mPreloadCells.end()) { if (found->second.mWorkItem) { found->second.mWorkItem->abort(); found->second.mWorkItem = nullptr; } mPreloadCells.erase(found); } } void CellPreloader::clear() { for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { if (it->second.mWorkItem) { it->second.mWorkItem->abort(); it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); } } void CellPreloader::updateCache(double timestamp) { for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { if (mPreloadCells.size() >= mMinCacheSize && it->second.mTimeStamp < timestamp - mExpiryDelay) { if (it->second.mWorkItem) { it->second.mWorkItem->abort(); it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); } else ++it; } if (timestamp - mLastResourceCacheUpdate > 1.0 && (!mUpdateCacheItem || mUpdateCacheItem->isDone())) { // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations mUpdateCacheItem = new UpdateCacheItem(mResourceSystem, timestamp); mWorkQueue->addWorkItem(mUpdateCacheItem, true); mLastResourceCacheUpdate = timestamp; } if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) { mLoadedTerrainPositions = mTerrainPreloadPositions; mLoadedTerrainTimestamp = timestamp; } } void CellPreloader::setExpiryDelay(double expiryDelay) { mExpiryDelay = expiryDelay; } void CellPreloader::setMinCacheSize(unsigned int num) { mMinCacheSize = num; } void CellPreloader::setMaxCacheSize(unsigned int num) { mMaxCacheSize = num; } void CellPreloader::setPreloadInstances(bool preload) { mPreloadInstances = preload; } unsigned int CellPreloader::getMaxCacheSize() const { return mMaxCacheSize; } void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) { mWorkQueue = workQueue; } bool CellPreloader::syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener) { if (!mTerrainPreloadItem) return true; else if (mTerrainPreloadItem->isDone()) { return true; } else { mTerrainPreloadItem->wait(listener); return true; } } void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { if (exceptPos && contains(mTerrainPreloadPositions, std::array {*exceptPos}, ESM::Land::REAL_SIZE)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); mTerrainPreloadItem->waitTillDone(); } setTerrainPreloadPositions(std::vector()); } void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (positions.empty()) { mTerrainPreloadPositions.clear(); mLoadedTerrainPositions.clear(); } else if (contains(mTerrainPreloadPositions, positions, 128.f)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { if (mTerrainViews.size() > positions.size()) mTerrainViews.resize(positions.size()); else if (mTerrainViews.size() < positions.size()) { for (unsigned int i=mTerrainViews.size(); icreateView()); } mTerrainPreloadPositions = positions; if (!positions.empty()) { mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); mWorkQueue->addWorkItem(mTerrainPreloadItem); } } } bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const { return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime && contains(mLoadedTerrainPositions, std::array {position}, ESM::Land::REAL_SIZE); } } openmw-openmw-0.48.0/apps/openmw/mwworld/cellpreloader.hpp000066400000000000000000000072301445372753700237270ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_CELLPRELOADER_H #define OPENMW_MWWORLD_CELLPRELOADER_H #include #include #include #include #include namespace Resource { class ResourceSystem; class BulletShapeManager; } namespace Terrain { class World; class View; } namespace MWRender { class LandManager; } namespace Loading { class Listener; } namespace MWWorld { class CellStore; class TerrainPreloadItem; class CellPreloader { public: CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager); ~CellPreloader(); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. void preload(MWWorld::CellStore* cell, double timestamp); void notifyLoaded(MWWorld::CellStore* cell); void clear(); /// Removes preloaded cells that have not had a preload request for a while. void updateCache(double timestamp); /// How long to keep a preloaded cell in cache after it's no longer requested. void setExpiryDelay(double expiryDelay); /// The minimum number of preloaded cells before unused cells get thrown out. void setMinCacheSize(unsigned int num); /// The maximum number of preloaded cells. void setMaxCacheSize(unsigned int num); /// Enables the creation of instances in the preloading thread. void setPreloadInstances(bool preload); unsigned int getMaxCacheSize() const; void setWorkQueue(osg::ref_ptr workQueue); typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); bool syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); bool isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const; private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; Terrain::World* mTerrain; MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; double mExpiryDelay; unsigned int mMinCacheSize; unsigned int mMaxCacheSize; bool mPreloadInstances; double mLastResourceCacheUpdate; struct PreloadEntry { PreloadEntry(double timestamp, osg::ref_ptr workItem) : mTimeStamp(timestamp) , mWorkItem(workItem) { } PreloadEntry() : mTimeStamp(0.0) { } double mTimeStamp; osg::ref_ptr mWorkItem; }; typedef std::map PreloadMap; // Cells that are currently being preloaded, or have already finished preloading PreloadMap mPreloadCells; std::vector > mTerrainViews; std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; std::vector mLoadedTerrainPositions; double mLoadedTerrainTimestamp; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellref.cpp000066400000000000000000000116631445372753700225260ustar00rootroot00000000000000#include "cellref.hpp" #include #include #include namespace MWWorld { const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) { if (!mCellRef.mRefNum.isSet()) { // Generated RefNums have negative mContentFile assert(lastAssignedRefNum.mContentFile < 0); lastAssignedRefNum.mIndex++; if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed { if (lastAssignedRefNum.mContentFile > std::numeric_limits::min()) lastAssignedRefNum.mContentFile--; else Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum"; } mCellRef.mRefNum = lastAssignedRefNum; mChanged = true; } return mCellRef.mRefNum; } void CellRef::unsetRefNum() { mCellRef.mRefNum.unset(); } void CellRef::setScale(float scale) { if (scale != mCellRef.mScale) { mChanged = true; mCellRef.mScale = scale; } } void CellRef::setPosition(const ESM::Position &position) { mChanged = true; mCellRef.mPos = position; } float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const { if (maxCharge == 0) { return 0; } else if (mCellRef.mEnchantmentCharge == -1) { return 1; } else { return mCellRef.mEnchantmentCharge / static_cast(maxCharge); } } void CellRef::setEnchantmentCharge(float charge) { if (charge != mCellRef.mEnchantmentCharge) { mChanged = true; mCellRef.mEnchantmentCharge = charge; } } void CellRef::setCharge(int charge) { if (charge != mCellRef.mChargeInt) { mChanged = true; mCellRef.mChargeInt = charge; } } void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder) { mCellRef.mChargeIntRemainder += std::abs(chargeRemainder); if (mCellRef.mChargeIntRemainder > 1.0f) { float newChargeRemainder = (mCellRef.mChargeIntRemainder - std::floor(mCellRef.mChargeIntRemainder)); if (mCellRef.mChargeInt <= static_cast(mCellRef.mChargeIntRemainder)) { mCellRef.mChargeInt = 0; } else { mCellRef.mChargeInt -= static_cast(mCellRef.mChargeIntRemainder); } mCellRef.mChargeIntRemainder = newChargeRemainder; } } void CellRef::setChargeFloat(float charge) { if (charge != mCellRef.mChargeFloat) { mChanged = true; mCellRef.mChargeFloat = charge; } } void CellRef::resetGlobalVariable() { if (!mCellRef.mGlobalVariable.empty()) { mChanged = true; mCellRef.mGlobalVariable.erase(); } } void CellRef::setFactionRank(int factionRank) { if (factionRank != mCellRef.mFactionRank) { mChanged = true; mCellRef.mFactionRank = factionRank; } } void CellRef::setOwner(const std::string &owner) { if (owner != mCellRef.mOwner) { mChanged = true; mCellRef.mOwner = owner; } } void CellRef::setSoul(const std::string &soul) { if (soul != mCellRef.mSoul) { mChanged = true; mCellRef.mSoul = soul; } } void CellRef::setFaction(const std::string &faction) { if (faction != mCellRef.mFaction) { mChanged = true; mCellRef.mFaction = faction; } } void CellRef::setLockLevel(int lockLevel) { if (lockLevel != mCellRef.mLockLevel) { mChanged = true; mCellRef.mLockLevel = lockLevel; } } void CellRef::lock(int lockLevel) { if(lockLevel != 0) setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive else setLockLevel(ESM::UnbreakableLock); // If zero, set to max lock level } void CellRef::unlock() { setLockLevel(-abs(mCellRef.mLockLevel)); //Makes lockLevel negative } void CellRef::setTrap(const std::string& trap) { if (trap != mCellRef.mTrap) { mChanged = true; mCellRef.mTrap = trap; } } void CellRef::setGoldValue(int value) { if (value != mCellRef.mGoldValue) { mChanged = true; mCellRef.mGoldValue = value; } } void CellRef::writeState(ESM::ObjectState &state) const { state.mRef = mCellRef; } } openmw-openmw-0.48.0/apps/openmw/mwworld/cellref.hpp000066400000000000000000000122301445372753700225220ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_CELLREF_H #define OPENMW_MWWORLD_CELLREF_H #include namespace ESM { struct ObjectState; } namespace MWWorld { /// \brief Encapsulated variant of ESM::CellRef with change tracking class CellRef { public: CellRef (const ESM::CellRef& ref) : mCellRef(ref) { mChanged = false; } // Note: Currently unused for items in containers const ESM::RefNum& getRefNum() const { return mCellRef.mRefNum; } // Returns RefNum. // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. const ESM::RefNum& getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum); // Set RefNum to its default state. void unsetRefNum(); /// Does the RefNum have a content file? bool hasContentFile() const { return mCellRef.mRefNum.hasContentFile(); } // Id of object being referenced const std::string& getRefId() const { return mCellRef.mRefID; } // For doors - true if this door teleports to somewhere else, false // if it should open through animation. bool getTeleport() const { return mCellRef.mTeleport; } // Teleport location for the door, if this is a teleporting door. const ESM::Position& getDoorDest() const { return mCellRef.mDoorDest; } // Destination cell for doors (optional) const std::string& getDestCell() const { return mCellRef.mDestCell; } // Scale applied to mesh float getScale() const { return mCellRef.mScale; } void setScale(float scale); // The *original* position and rotation as it was given in the Construction Set. // Current position and rotation of the object is stored in RefData. const ESM::Position& getPosition() const { return mCellRef.mPos; } void setPosition (const ESM::Position& position); // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float getEnchantmentCharge() const { return mCellRef.mEnchantmentCharge; } // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). float getNormalizedEnchantmentCharge(int maxCharge) const; void setEnchantmentCharge(float charge); // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. // If this returns int(-1) it means full health. int getCharge() const { return mCellRef.mChargeInt; } float getChargeFloat() const { return mCellRef.mChargeFloat; } // Implemented as union with int charge void setCharge(int charge); void setChargeFloat(float charge); void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1 // The NPC that owns this object (and will get angry if you steal it) const std::string& getOwner() const { return mCellRef.mOwner; } void setOwner(const std::string& owner); // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed // even if it has an Owner field. // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. const std::string& getGlobalVariable() const { return mCellRef.mGlobalVariable; } void resetGlobalVariable(); // ID of creature trapped in this soul gem const std::string& getSoul() const { return mCellRef.mSoul; } void setSoul(const std::string& soul); // The faction that owns this object (and will get angry if // you take it and are not a faction member) const std::string& getFaction() const { return mCellRef.mFaction; } void setFaction (const std::string& faction); // PC faction rank required to use the item. Sometimes is -1, which means "any rank". void setFactionRank(int factionRank); int getFactionRank() const { return mCellRef.mFactionRank; } // Lock level for doors and containers // Positive for a locked door. 0 for a door that was never locked. // For an unlocked door, it is set to -(previous locklevel) int getLockLevel() const { return mCellRef.mLockLevel; } void setLockLevel(int lockLevel); void lock(int lockLevel); void unlock(); // Key and trap ID names, if any const std::string& getKey() const { return mCellRef.mKey; } const std::string& getTrap() const { return mCellRef.mTrap; } void setTrap(const std::string& trap); // This is 5 for Gold_005 references, 100 for Gold_100 and so on. int getGoldValue() const { return mCellRef.mGoldValue; } void setGoldValue(int value); // Write the content of this CellRef into the given ObjectState void writeState (ESM::ObjectState& state) const; // Has this CellRef changed since it was originally loaded? bool hasChanged() const { return mChanged; } private: bool mChanged; ESM::CellRef mCellRef; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellreflist.hpp000066400000000000000000000026201445372753700234200ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLREFLIST_H #define GAME_MWWORLD_CELLREFLIST_H #include #include "livecellref.hpp" namespace MWWorld { /// \brief Collection of references of one type template struct CellRefList { typedef LiveCellRef LiveRef; typedef std::list List; List mList; /// Search for the given reference in the given reclist from /// ESMStore. Insert the reference into the list if a match is /// found. If not, throw an exception. /// Moved to cpp file, as we require a custom compare operator for it, /// and the build will fail with an ugly three-way cyclic header dependence /// so we need to pass the instantiation of the method to the linker, when /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); LiveRef &insert (const LiveRef &item) { mList.push_back(item); return mList.back(); } /// Remove all references with the given refNum from this list. void remove (const ESM::RefNum &refNum) { for (typename List::iterator it = mList.begin(); it != mList.end();) { if (*it == refNum) mList.erase(it++); else ++it; } } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cells.cpp000066400000000000000000000316061445372753700222130ustar00rootroot00000000000000#include "cells.hpp" #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "esmstore.hpp" #include "containerstore.hpp" #include "cellstore.hpp" namespace { template bool forEachInStore(const std::string& id, Visitor&& visitor, std::map& cellStore) { for(auto& cell : cellStore) { if(cell.second.getState() == MWWorld::CellStore::State_Unloaded) cell.second.preload(); if(cell.second.getState() == MWWorld::CellStore::State_Preloaded) { if(cell.second.hasId(id)) { cell.second.load(); } else continue; } bool cont = cell.second.forEach([&] (MWWorld::Ptr ptr) { if (ptr.getCellRef().getRefId() == id) { return visitor(ptr); } return true; }); if(!cont) return false; } return true; } struct PtrCollector { std::vector mPtrs; bool operator()(MWWorld::Ptr ptr) { mPtrs.emplace_back(ptr); return true; } }; } MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { if (cell->mData.mFlags & ESM::Cell::Interior) { std::string lowerName(Misc::StringUtils::lowerCase(cell->mName)); std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) result = mInteriors.emplace(std::move(lowerName), CellStore(cell, mStore, mReaders)).first; return &result->second; } else { std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY())); if (result==mExteriors.end()) result = mExteriors.emplace(std::make_pair(cell->getGridX(), cell->getGridY()), CellStore(cell, mStore, mReaders)).first; return &result->second; } } void MWWorld::Cells::clear() { mInteriors.clear(); mExteriors.clear(); std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)nullptr)); mIdCacheIndex = 0; } MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& cellStore) { Ptr ptr = getPtr (name, cellStore); if (!ptr.isEmpty() && ptr.isInCell()) { mIdCache[mIdCacheIndex].first = name; mIdCache[mIdCacheIndex].second = &cellStore; if (++mIdCacheIndex>=mIdCache.size()) mIdCacheIndex = 0; } return ptr; } void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const { if (cell.getState()!=CellStore::State_Loaded) cell.load (); ESM::CellState cellState; cell.saveState (cellState); writer.startRecord (ESM::REC_CSTA); cellState.mId.save (writer); cellState.save (writer); cell.writeFog(writer); cell.writeReferences (writer); writer.endRecord (ESM::REC_CSTA); } MWWorld::Cells::Cells (const MWWorld::ESMStore& store, ESM::ReadersCache& readers) : mStore(store) , mReaders(readers) , mIdCacheIndex(0) { int cacheSize = std::clamp(Settings::Manager::getInt("pointers cache size", "Cells"), 40, 1000); mIdCache = IdCache(cacheSize, std::pair ("", (CellStore*)nullptr)); } MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); if (result==mExteriors.end()) { const ESM::Cell *cell = mStore.get().search(x, y); if (!cell) { // Cell isn't predefined. Make one on the fly. ESM::Cell record; record.mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace; record.mCellId.mPaged = true; record.mCellId.mIndex.mX = x; record.mCellId.mIndex.mY = y; record.mData.mFlags = ESM::Cell::HasWater; record.mData.mX = x; record.mData.mY = y; record.mWater = 0; record.mMapColor = 0; cell = MWBase::Environment::get().getWorld()->createRecord (record); } result = mExteriors.emplace(std::make_pair(x, y), CellStore(cell, mStore, mReaders)).first; } if (result->second.getState()!=CellStore::State_Loaded) { result->second.load (); } return &result->second; } MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::string lowerName = Misc::StringUtils::lowerCase(name); std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.get().find(lowerName); result = mInteriors.emplace(std::move(lowerName), CellStore(cell, mStore, mReaders)).first; } if (result->second.getState()!=CellStore::State_Loaded) { result->second.load (); } return &result->second; } void MWWorld::Cells::rest (double hours) { for (auto &interior : mInteriors) { interior.second.rest(hours); } for (auto &exterior : mExteriors) { exterior.second.rest(hours); } } void MWWorld::Cells::recharge (float duration) { for (auto &interior : mInteriors) { interior.second.recharge(duration); } for (auto &exterior : mExteriors) { exterior.second.recharge(duration); } } MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id) { if (id.mPaged) return getExterior (id.mIndex.mX, id.mIndex.mY); return getInterior (id.mWorldspace); } MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, bool searchInContainers) { if (cell.getState()==CellStore::State_Unloaded) cell.preload (); if (cell.getState()==CellStore::State_Preloaded) { if (cell.hasId (name)) { cell.load (); } else return Ptr(); } Ptr ptr = cell.search (name); if (!ptr.isEmpty() && MWWorld::CellStore::isAccessible(ptr.getRefData(), ptr.getCellRef())) return ptr; if (searchInContainers) return cell.searchInContainer (name); return Ptr(); } MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) { // First check the cache for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter) if (iter->first==name && iter->second) { Ptr ptr = getPtr (name, *iter->second); if (!ptr.isEmpty()) return ptr; } // Then check cells that are already listed // Search in reverse, this is a workaround for an ambiguous chargen_plank reference in the vanilla game. // there is one at -22,16 and one at -2,-9, the latter should be used. for (std::map, CellStore>::reverse_iterator iter = mExteriors.rbegin(); iter!=mExteriors.rend(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); if (!ptr.isEmpty()) return ptr; } for (std::map::iterator iter = mInteriors.begin(); iter!=mInteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); if (!ptr.isEmpty()) return ptr; } // Now try the other cells const MWWorld::Store &cells = mStore.get(); MWWorld::Store::iterator iter; for (iter = cells.extBegin(); iter != cells.extEnd(); ++iter) { CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); if (!ptr.isEmpty()) return ptr; } for (iter = cells.intBegin(); iter != cells.intEnd(); ++iter) { CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); if (!ptr.isEmpty()) return ptr; } // giving up return Ptr(); } MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& id, const ESM::RefNum& refNum) { for (auto& pair : mInteriors) { Ptr ptr = getPtr(pair.second, id, refNum); if (!ptr.isEmpty()) return ptr; } for (auto& pair : mExteriors) { Ptr ptr = getPtr(pair.second, id, refNum); if (!ptr.isEmpty()) return ptr; } return Ptr(); } MWWorld::Ptr MWWorld::Cells::getPtr(CellStore& cellStore, const std::string& id, const ESM::RefNum& refNum) { if (cellStore.getState() == CellStore::State_Unloaded) cellStore.preload(); if (cellStore.getState() == CellStore::State_Preloaded) { if (cellStore.hasId(id)) cellStore.load(); else return Ptr(); } return cellStore.searchViaRefNum(refNum); } void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) { const MWWorld::Store &cells = mStore.get(); for (MWWorld::Store::iterator iter = cells.extBegin(); iter != cells.extEnd(); ++iter) { CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); if (!ptr.isEmpty()) out.push_back(ptr); } } void MWWorld::Cells::getInteriorPtrs(const std::string &name, std::vector &out) { const MWWorld::Store &cells = mStore.get(); for (MWWorld::Store::iterator iter = cells.intBegin(); iter != cells.intEnd(); ++iter) { CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); if (!ptr.isEmpty()) out.push_back(ptr); } } std::vector MWWorld::Cells::getAll(const std::string& id) { PtrCollector visitor; if(forEachInStore(id, visitor, mInteriors)) forEachInStore(id, visitor, mExteriors); return visitor.mPtrs; } int MWWorld::Cells::countSavedGameRecords() const { int count = 0; for (std::map::const_iterator iter (mInteriors.begin()); iter!=mInteriors.end(); ++iter) if (iter->second.hasState()) ++count; for (std::map, CellStore>::const_iterator iter (mExteriors.begin()); iter!=mExteriors.end(); ++iter) if (iter->second.hasState()) ++count; return count; } void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (std::map, CellStore>::iterator iter (mExteriors.begin()); iter!=mExteriors.end(); ++iter) if (iter->second.hasState()) { writeCell (writer, iter->second); progress.increaseProgress(); } for (std::map::iterator iter (mInteriors.begin()); iter!=mInteriors.end(); ++iter) if (iter->second.hasState()) { writeCell (writer, iter->second); progress.increaseProgress(); } } struct GetCellStoreCallback : public MWWorld::CellStore::GetCellStoreCallback { public: GetCellStoreCallback(MWWorld::Cells& cells) : mCells(cells) { } MWWorld::Cells& mCells; MWWorld::CellStore* getCellStore(const ESM::CellId& cellId) override { try { return mCells.getCell(cellId); } catch (...) { return nullptr; } } }; bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { if (type==ESM::REC_CSTA) { ESM::CellState state; state.mId.load (reader); CellStore *cellStore = nullptr; try { cellStore = getCell (state.mId); } catch (...) { // silently drop cells that don't exist anymore Log(Debug::Warning) << "Warning: Dropping state for cell " << state.mId.mWorldspace << " (cell no longer exists)"; reader.skipRecord(); return true; } state.load (reader); cellStore->loadState (state); if (state.mHasFogOfWar) cellStore->readFog(reader); if (cellStore->getState()!=CellStore::State_Loaded) cellStore->load (); GetCellStoreCallback callback(*this); cellStore->readReferences (reader, contentFileMap, &callback); return true; } return false; } openmw-openmw-0.48.0/apps/openmw/mwworld/cells.hpp000066400000000000000000000055501445372753700222170ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLS_H #define GAME_MWWORLD_CELLS_H #include #include #include #include "ptr.hpp" namespace ESM { class ESMReader; class ESMWriter; class ReadersCache; struct CellId; struct Cell; struct RefNum; } namespace Loading { class Listener; } namespace MWWorld { class ESMStore; /// \brief Cell container class Cells { typedef std::vector > IdCache; const MWWorld::ESMStore& mStore; ESM::ReadersCache& mReaders; mutable std::map mInteriors; mutable std::map, CellStore> mExteriors; IdCache mIdCache; std::size_t mIdCacheIndex; Cells (const Cells&); Cells& operator= (const Cells&); CellStore *getCellStore (const ESM::Cell *cell); Ptr getPtrAndCache (const std::string& name, CellStore& cellStore); Ptr getPtr(CellStore& cellStore, const std::string& id, const ESM::RefNum& refNum); void writeCell (ESM::ESMWriter& writer, CellStore& cell) const; public: void clear(); explicit Cells(const MWWorld::ESMStore& store, ESM::ReadersCache& reader); CellStore *getExterior (int x, int y); CellStore *getInterior (const std::string& name); CellStore *getCell (const ESM::CellId& id); Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false); ///< \param searchInContainers Only affect loaded cells. /// @note name must be lower case /// @note name must be lower case Ptr getPtr (const std::string& name); Ptr getPtr(const std::string& id, const ESM::RefNum& refNum); void rest (double hours); void recharge (float duration); /// Get all Ptrs referencing \a name in exterior cells /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. /// @note name must be lower case void getExteriorPtrs (const std::string& name, std::vector& out); /// Get all Ptrs referencing \a name in interior cells /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. /// @note name must be lower case void getInteriorPtrs (const std::string& name, std::vector& out); std::vector getAll(const std::string& id); int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellstore.cpp000066400000000000000000001255731445372753700231140ustar00rootroot00000000000000#include "cellstore.hpp" #include "magiceffects.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/recharge.hpp" #include "ptr.hpp" #include "esmloader.hpp" #include "esmstore.hpp" #include "class.hpp" #include "containerstore.hpp" namespace { template MWWorld::Ptr searchInContainerList (MWWorld::CellRefList& containerList, const std::string& id) { for (typename MWWorld::CellRefList::List::iterator iter (containerList.mList.begin()); iter!=containerList.mList.end(); ++iter) { MWWorld::Ptr container (&*iter, nullptr); if (container.getRefData().getCustomData() == nullptr) continue; MWWorld::Ptr ptr = container.getClass().getContainerStore (container).search (id); if (!ptr.isEmpty()) return ptr; } return MWWorld::Ptr(); } template MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, MWWorld::CellStore *cell, const std::map& toIgnore) { for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); iter!=actorList.mList.end(); ++iter) { MWWorld::Ptr actor (&*iter, cell); if (toIgnore.find(&*iter) != toIgnore.end()) continue; if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) return actor; } return MWWorld::Ptr(); } template void writeReferenceCollection (ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { if (!collection.mList.empty()) { // references for (typename MWWorld::CellRefList::List::const_iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) { // Reference that came from a content file and has not been changed -> ignore continue; } if (iter->mData.getCount()==0 && !iter->mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; } RecordType state; iter->save (state); // recordId currently unused writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); state.save (writer); } } } template void fixRestockingImpl(const T* base, RecordType& state) { // Workaround for old saves not containing negative quantities for(const auto& baseItem : base->mInventory.mList) { if(baseItem.mCount < 0) { for(auto& item : state.mInventory.mItems) { if(item.mCount > 0 && Misc::StringUtils::ciEqual(baseItem.mItem, item.mRef.mRefID)) item.mCount = -item.mCount; } } } } template void fixRestocking(const T* base, RecordType& state) {} template<> void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state) { fixRestockingImpl(base, state); } template<> void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state) { fixRestockingImpl(base, state); } template<> void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state) { fixRestockingImpl(base, state); } template void readReferenceCollection (ESM::ESMReader& reader, MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, MWWorld::CellStore* cellstore) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); RecordType state; state.mRef = cref; state.load(reader); // If the reference came from a content file, make sure this content file is loaded if (state.mRef.mRefNum.hasContentFile()) { std::map::const_iterator iter = contentFileMap.find (state.mRef.mRefNum.mContentFile); if (iter==contentFileMap.end()) return; // content file has been removed -> skip state.mRef.mRefNum.mContentFile = iter->second; } if (!MWWorld::LiveCellRef::checkState (state)) return; // not valid anymore with current content files -> skip const T *record = esmStore.get().search (state.mRef.mRefID); if (!record) return; if (state.mVersion < 15) fixRestocking(record, state); if (state.mVersion < 17) { if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } else if(state.mVersion < 20) { if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertStats(state.mCreatureStats); } if (state.mRef.mRefNum.hasContentFile()) { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefId() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); iter->load (state); const ESM::Position & oldpos = iter->mRef.getPosition(); const ESM::Position & newpos = iter->mData.getPosition(); const MWWorld::Ptr ptr(&*iter, cellstore); if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.asVec3()); if (!iter->mData.isEnabled()) { iter->mData.enable(); MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); } else MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(&*iter, cellstore)); return; } Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; return; } // new reference MWWorld::LiveCellRef ref (record); ref.load (state); collection.mList.push_back (ref); MWWorld::LiveCellRefBase* base = &collection.mList.back(); MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(base, cellstore)); } } namespace MWWorld { template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { const MWWorld::Store &store = esmStore.get(); if (const X *ptr = store.search (ref.mRefID)) { typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); LiveRef liveCellRef (ref, ptr); if (deleted) liveCellRef.mData.setDeletedByContentFile(true); if (iter != mList.end()) *iter = liveCellRef; else mList.push_back (liveCellRef); } else { Log(Debug::Warning) << "Warning: could not resolve cell reference '" << ref.mRefID << "'" << " (dropping reference)"; } } template bool operator==(const LiveCellRef& ref, int pRefnum) { return (ref.mRef.mRefnum == pRefnum); } Ptr CellStore::getCurrentPtr(LiveCellRefBase *ref) { MovedRefTracker::iterator found = mMovedToAnotherCell.find(ref); if (found != mMovedToAnotherCell.end()) return Ptr(ref, found->second); return Ptr(ref, this); } void CellStore::moveFrom(const Ptr &object, CellStore *from) { if (mState != State_Loaded) load(); mHasState = true; MovedRefTracker::iterator found = mMovedToAnotherCell.find(object.getBase()); if (found != mMovedToAnotherCell.end()) { // A cell we had previously moved an object to is returning it to us. assert (found->second == from); mMovedToAnotherCell.erase(found); } else { mMovedHere.insert(std::make_pair(object.getBase(), from)); } updateMergedRefs(); } MWWorld::Ptr CellStore::moveTo(const Ptr &object, CellStore *cellToMoveTo) { if (cellToMoveTo == this) throw std::runtime_error("moveTo: object is already in this cell"); // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise. if (mState != State_Loaded) throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); // Ensure that the object actually exists in the cell if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty()) throw std::runtime_error("moveTo: object is not in this cell"); MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(object.getBase(), cellToMoveTo)); MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); if (found != mMovedHere.end()) { // Special case - object didn't originate in this cell // Move it back to its original cell first CellStore* originalCell = found->second; assert (originalCell != this); originalCell->moveFrom(object, this); mMovedHere.erase(found); // Now that object is back to its rightful owner, we can move it if (cellToMoveTo != originalCell) { originalCell->moveTo(object, cellToMoveTo); } updateMergedRefs(); return MWWorld::Ptr(object.getBase(), cellToMoveTo); } cellToMoveTo->moveFrom(object, this); mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); updateMergedRefs(); return MWWorld::Ptr(object.getBase(), cellToMoveTo); } struct MergeVisitor { MergeVisitor(std::vector& mergeTo, const std::map& movedHere, const std::map& movedToAnotherCell) : mMergeTo(mergeTo) , mMovedHere(movedHere) , mMovedToAnotherCell(movedToAnotherCell) { } bool operator() (const MWWorld::Ptr& ptr) { if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end()) return true; mMergeTo.push_back(ptr.getBase()); return true; } void merge() { for (const auto & [base, _] : mMovedHere) mMergeTo.push_back(base); } private: std::vector& mMergeTo; const std::map& mMovedHere; const std::map& mMovedToAnotherCell; }; void CellStore::updateMergedRefs() { mMergedRefs.clear(); mRechargingItemsUpToDate = false; MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); forEachInternal(visitor); visitor.merge(); } bool CellStore::movedHere(const MWWorld::Ptr& ptr) const { if (ptr.isEmpty()) return false; if (mMovedHere.find(ptr.getBase()) != mMovedHere.end()) return true; return false; } CellStore::CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) : mStore(esmStore) , mReaders(readers) , mCell(cell) , mState(State_Unloaded) , mHasState(false) , mLastRespawn(0, 0) , mRechargingItemsUpToDate(false) { mWaterLevel = cell->mWater; } const ESM::Cell *CellStore::getCell() const { return mCell; } CellStore::State CellStore::getState() const { return mState; } const std::vector &CellStore::getPreloadedIds() const { return mIds; } bool CellStore::hasState() const { return mHasState; } bool CellStore::hasId (const std::string& id) const { if (mState==State_Unloaded) return false; if (mState==State_Preloaded) return std::binary_search (mIds.begin(), mIds.end(), id); return searchConst (id).isEmpty(); } template struct SearchVisitor { PtrType mFound; const std::string *mIdToFind; bool operator()(const PtrType& ptr) { if (ptr.getCellRef().getRefId() == *mIdToFind) { mFound = ptr; return false; } return true; } }; Ptr CellStore::search (const std::string& id) { SearchVisitor searchVisitor; searchVisitor.mIdToFind = &id; forEach(searchVisitor); return searchVisitor.mFound; } ConstPtr CellStore::searchConst (const std::string& id) const { SearchVisitor searchVisitor; searchVisitor.mIdToFind = &id; forEachConst(searchVisitor); return searchVisitor.mFound; } Ptr CellStore::searchViaActorId (int id) { if (Ptr ptr = ::searchViaActorId (mNpcs, id, this, mMovedToAnotherCell)) return ptr; if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell)) return ptr; for (const auto& [base, _] : mMovedHere) { MWWorld::Ptr actor (base, this); if (!actor.getClass().isActor()) continue; if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0) return actor; } return Ptr(); } class RefNumSearchVisitor { const ESM::RefNum& mRefNum; public: RefNumSearchVisitor(const ESM::RefNum& refNum) : mRefNum(refNum) {} Ptr mFound; bool operator()(const Ptr& ptr) { if (ptr.getCellRef().getRefNum() == mRefNum) { mFound = ptr; return false; } return true; } }; Ptr CellStore::searchViaRefNum (const ESM::RefNum& refNum) { RefNumSearchVisitor searchVisitor(refNum); forEach(searchVisitor); return searchVisitor.mFound; } float CellStore::getWaterLevel() const { if (isExterior()) return -1; return mWaterLevel; } void CellStore::setWaterLevel (float level) { mWaterLevel = level; mHasState = true; } std::size_t CellStore::count() const { return mMergedRefs.size(); } void CellStore::load () { if (mState!=State_Loaded) { if (mState==State_Preloaded) mIds.clear(); loadRefs (); mState = State_Loaded; } } void CellStore::preload () { if (mState==State_Unloaded) { listRefs (); mState = State_Preloaded; } } void CellStore::listRefs() { assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. const std::size_t index = static_cast(mCell->mContextList[i].index); const ESM::ReadersCache::BusyItem reader = mReaders.get(index); mCell->restore(*reader, i); ESM::CellRef ref; // Get each reference in turn ESM::MovedCellRef cMRef; cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (deleted || moved) continue; // Don't list reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { continue; } Misc::StringUtils::lowerCaseInPlace(ref.mRefID); mIds.push_back(std::move(ref.mRefID)); } } catch (std::exception& e) { Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what(); } } // List moved references, from separately tracked list. for (const auto& [ref, deleted]: mCell->mLeasedRefs) { if (!deleted) mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } std::sort (mIds.begin(), mIds.end()); } void CellStore::loadRefs() { assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. std::map refNumToID; // used to detect refID modifications // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. const std::size_t index = static_cast(mCell->mContextList[i].index); const ESM::ReadersCache::BusyItem reader = mReaders.get(index); mCell->restore(*reader, i); ESM::CellRef ref; ref.mRefNum.unset(); // Get each reference in turn ESM::MovedCellRef cMRef; cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { continue; } loadRef (ref, deleted, refNumToID); } } catch (std::exception& e) { Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what(); } } // Load moved references, from separately tracked list. for (const auto& leasedRef : mCell->mLeasedRefs) { ESM::CellRef &ref = const_cast(leasedRef.first); bool deleted = leasedRef.second; loadRef (ref, deleted, refNumToID); } updateMergedRefs(); } bool CellStore::isExterior() const { return mCell->isExterior(); } Ptr CellStore::searchInContainer (const std::string& id) { bool oldState = mHasState; mHasState = true; if (Ptr ptr = searchInContainerList (mContainers, id)) return ptr; if (Ptr ptr = searchInContainerList (mCreatures, id)) return ptr; if (Ptr ptr = searchInContainerList (mNpcs, id)) return ptr; mHasState = oldState; return Ptr(); } void CellStore::loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID) { Misc::StringUtils::lowerCaseInPlace (ref.mRefID); const MWWorld::ESMStore& store = mStore; std::map::iterator it = refNumToID.find(ref.mRefNum); if (it != refNumToID.end()) { if (it->second != ref.mRefID) { // refID was modified, make sure we don't end up with duplicated refs switch (store.find(it->second)) { case ESM::REC_ACTI: mActivators.remove(ref.mRefNum); break; case ESM::REC_ALCH: mPotions.remove(ref.mRefNum); break; case ESM::REC_APPA: mAppas.remove(ref.mRefNum); break; case ESM::REC_ARMO: mArmors.remove(ref.mRefNum); break; case ESM::REC_BOOK: mBooks.remove(ref.mRefNum); break; case ESM::REC_CLOT: mClothes.remove(ref.mRefNum); break; case ESM::REC_CONT: mContainers.remove(ref.mRefNum); break; case ESM::REC_CREA: mCreatures.remove(ref.mRefNum); break; case ESM::REC_DOOR: mDoors.remove(ref.mRefNum); break; case ESM::REC_INGR: mIngreds.remove(ref.mRefNum); break; case ESM::REC_LEVC: mCreatureLists.remove(ref.mRefNum); break; case ESM::REC_LEVI: mItemLists.remove(ref.mRefNum); break; case ESM::REC_LIGH: mLights.remove(ref.mRefNum); break; case ESM::REC_LOCK: mLockpicks.remove(ref.mRefNum); break; case ESM::REC_MISC: mMiscItems.remove(ref.mRefNum); break; case ESM::REC_NPC_: mNpcs.remove(ref.mRefNum); break; case ESM::REC_PROB: mProbes.remove(ref.mRefNum); break; case ESM::REC_REPA: mRepairs.remove(ref.mRefNum); break; case ESM::REC_STAT: mStatics.remove(ref.mRefNum); break; case ESM::REC_WEAP: mWeapons.remove(ref.mRefNum); break; case ESM::REC_BODY: mBodyParts.remove(ref.mRefNum); break; default: break; } } } switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; case 0: Log(Debug::Error) << "Cell reference '" + ref.mRefID + "' not found!"; return; default: Log(Debug::Error) << "Error: Ignoring reference '" << ref.mRefID << "' of unhandled type"; return; } refNumToID[ref.mRefNum] = ref.mRefID; } void CellStore::loadState (const ESM::CellState& state) { mHasState = true; if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) mWaterLevel = state.mWaterLevel; mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } void CellStore::saveState (ESM::CellState& state) const { state.mId = mCell->getCellId(); if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) state.mWaterLevel = mWaterLevel; state.mHasFogOfWar = (mFogState.get() ? 1 : 0); state.mLastRespawn = mLastRespawn.toEsm(); } void CellStore::writeFog(ESM::ESMWriter &writer) const { if (mFogState.get()) { mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior); } } void CellStore::readFog(ESM::ESMReader &reader) { mFogState = std::make_unique(); mFogState->load(reader); } void CellStore::writeReferences (ESM::ESMWriter& writer) const { writeReferenceCollection (writer, mActivators); writeReferenceCollection (writer, mPotions); writeReferenceCollection (writer, mAppas); writeReferenceCollection (writer, mArmors); writeReferenceCollection (writer, mBooks); writeReferenceCollection (writer, mClothes); writeReferenceCollection (writer, mContainers); writeReferenceCollection (writer, mCreatures); writeReferenceCollection (writer, mDoors); writeReferenceCollection (writer, mIngreds); writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); writeReferenceCollection (writer, mMiscItems); writeReferenceCollection (writer, mNpcs); writeReferenceCollection (writer, mProbes); writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); writeReferenceCollection (writer, mBodyParts); for (const auto& [base, store] : mMovedToAnotherCell) { ESM::RefNum refNum = base->mRef.getRefNum(); ESM::CellId movedTo = store->getCell()->getCellId(); refNum.save(writer, true, "MVRF"); movedTo.save(writer); } } void CellStore::readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) { mHasState = true; while (reader.isNextSub ("OBJE")) { unsigned int unused; reader.getHT (unused); // load the RefID first so we know what type of object it is ESM::CellRef cref; cref.loadId(reader, true); int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); if (type == 0) { Log(Debug::Warning) << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)"; // Skip until the next OBJE or MVRF while(reader.hasMoreSubs() && !reader.peekNextSub("OBJE") && !reader.peekNextSub("MVRF")) { reader.getSubName(); reader.skipHSub(); } continue; } switch (type) { case ESM::REC_ACTI: readReferenceCollection (reader, mActivators, cref, contentFileMap, this); break; case ESM::REC_ALCH: readReferenceCollection (reader, mPotions, cref, contentFileMap, this); break; case ESM::REC_APPA: readReferenceCollection (reader, mAppas, cref, contentFileMap, this); break; case ESM::REC_ARMO: readReferenceCollection (reader, mArmors, cref, contentFileMap, this); break; case ESM::REC_BOOK: readReferenceCollection (reader, mBooks, cref, contentFileMap, this); break; case ESM::REC_CLOT: readReferenceCollection (reader, mClothes, cref, contentFileMap, this); break; case ESM::REC_CONT: readReferenceCollection (reader, mContainers, cref, contentFileMap, this); break; case ESM::REC_CREA: readReferenceCollection (reader, mCreatures, cref, contentFileMap, this); break; case ESM::REC_DOOR: readReferenceCollection (reader, mDoors, cref, contentFileMap, this); break; case ESM::REC_INGR: readReferenceCollection (reader, mIngreds, cref, contentFileMap, this); break; case ESM::REC_LEVC: readReferenceCollection (reader, mCreatureLists, cref, contentFileMap, this); break; case ESM::REC_LEVI: readReferenceCollection (reader, mItemLists, cref, contentFileMap, this); break; case ESM::REC_LIGH: readReferenceCollection (reader, mLights, cref, contentFileMap, this); break; case ESM::REC_LOCK: readReferenceCollection (reader, mLockpicks, cref, contentFileMap, this); break; case ESM::REC_MISC: readReferenceCollection (reader, mMiscItems, cref, contentFileMap, this); break; case ESM::REC_NPC_: readReferenceCollection (reader, mNpcs, cref, contentFileMap, this); break; case ESM::REC_PROB: readReferenceCollection (reader, mProbes, cref, contentFileMap, this); break; case ESM::REC_REPA: readReferenceCollection (reader, mRepairs, cref, contentFileMap, this); break; case ESM::REC_STAT: readReferenceCollection (reader, mStatics, cref, contentFileMap, this); break; case ESM::REC_WEAP: readReferenceCollection (reader, mWeapons, cref, contentFileMap, this); break; case ESM::REC_BODY: readReferenceCollection (reader, mBodyParts, cref, contentFileMap, this); break; default: throw std::runtime_error ("unknown type in cell reference section"); } } // Do another update here to make sure objects referred to by MVRF tags can be found // This update is only needed for old saves that used the old copy&delete way of moving objects updateMergedRefs(); while (reader.isNextSub("MVRF")) { reader.cacheSubName(); ESM::RefNum refnum; ESM::CellId movedTo; refnum.load(reader, true, "MVRF"); movedTo.load(reader); if (refnum.hasContentFile()) { auto iter = contentFileMap.find(refnum.mContentFile); if (iter != contentFileMap.end()) refnum.mContentFile = iter->second; } // Search for the reference. It might no longer exist if its content file was removed. Ptr movedRef = searchViaRefNum(refnum); if (movedRef.isEmpty()) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; continue; } CellStore* otherCell = callback->getCellStore(movedTo); if (otherCell == nullptr) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId() << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. // Restore original coordinates: movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition()); continue; } if (otherCell == this) { // Should never happen unless someone's tampering with files. Log(Debug::Warning) << "Found invalid moved ref, ignoring"; continue; } moveTo(movedRef, otherCell); } } bool operator== (const CellStore& left, const CellStore& right) { return left.getCell()->getCellId()==right.getCell()->getCellId(); } bool operator!= (const CellStore& left, const CellStore& right) { return !(left==right); } void CellStore::setFog(std::unique_ptr&& fog) { mFogState = std::move(fog); } ESM::FogState* CellStore::getFog() const { return mFogState.get(); } void clearCorpse(const MWWorld::Ptr& ptr) { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->mValue.getFloat(); if (creatureStats.isDead() && creatureStats.isDeathAnimationFinished() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { MWBase::Environment::get().getWorld()->deleteObject(ptr); } } void CellStore::rest(double hours) { if (mState == State_Loaded) { for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } } } void CellStore::recharge(float duration) { if (duration <= 0) return; if (mState == State_Loaded) { for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } rechargeItems(duration); } } void CellStore::respawn() { if (mState == State_Loaded) { static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->mValue.getInteger(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); ptr.getClass().respawn(ptr); } } for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); clearCorpse(ptr); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); clearCorpse(ptr); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); // no need to clearCorpse, handled as part of mCreatures ptr.getClass().respawn(ptr); } } } void MWWorld::CellStore::rechargeItems(float duration) { if (!mRechargingItemsUpToDate) { updateRechargingItems(); mRechargingItemsUpToDate = true; } for (const auto& [item, charge] : mRechargingItems) { MWMechanics::rechargeItem(item, charge, duration); } } void MWWorld::CellStore::updateRechargingItems() { mRechargingItems.clear(); const auto update = [this](auto& list) { for (auto & item : list) { Ptr ptr = getCurrentPtr(&item); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { checkItem(ptr); } } }; update(mWeapons.mList); update(mArmors.mList); update(mClothes.mList); update(mBooks.mList); } void MWWorld::CellStore::checkItem(const Ptr& ptr) { if (ptr.getClass().getEnchantment(ptr).empty()) return; std::string enchantmentId = ptr.getClass().getEnchantment(ptr); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentId); if (!enchantment) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << ptr.getCellRef().getRefId(); return; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.emplace_back(ptr.getBase(), static_cast(enchantment->mData.mCharge)); } Ptr MWWorld::CellStore::getMovedActor(int actorId) const { for(const auto& [cellRef, cell] : mMovedToAnotherCell) { if(cellRef->mClass->isActor() && cellRef->mData.getCustomData()) { Ptr actor(cellRef, cell); if(actor.getClass().getCreatureStats(actor).getActorId() == actorId) return actor; } } return {}; } } openmw-openmw-0.48.0/apps/openmw/mwworld/cellstore.hpp000066400000000000000000000473641445372753700231220ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLSTORE_H #define GAME_MWWORLD_CELLSTORE_H #include #include #include #include #include #include #include "livecellref.hpp" #include "cellreflist.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "timestamp.hpp" #include "ptr.hpp" namespace ESM { class ReadersCache; struct Cell; struct CellState; struct CellId; struct RefNum; } namespace MWWorld { class ESMStore; /// \brief Mutable state of a cell class CellStore { public: enum State { State_Unloaded, State_Preloaded, State_Loaded }; private: const MWWorld::ESMStore& mStore; ESM::ReadersCache& mReaders; // Even though fog actually belongs to the player and not cells, // it makes sense to store it here since we need it once for each cell. // Note this is nullptr until the cell is explored to save some memory std::unique_ptr mFogState; const ESM::Cell *mCell; State mState; bool mHasState; std::vector mIds; float mWaterLevel; MWWorld::TimeStamp mLastRespawn; // List of refs owned by this cell CellRefList mActivators; CellRefList mPotions; CellRefList mAppas; CellRefList mArmors; CellRefList mBooks; CellRefList mClothes; CellRefList mContainers; CellRefList mCreatures; CellRefList mDoors; CellRefList mIngreds; CellRefList mCreatureLists; CellRefList mItemLists; CellRefList mLights; CellRefList mLockpicks; CellRefList mMiscItems; CellRefList mNpcs; CellRefList mProbes; CellRefList mRepairs; CellRefList mStatics; CellRefList mWeapons; CellRefList mBodyParts; typedef std::map MovedRefTracker; // References owned by a different cell that have been moved here. // MovedRefTracker mMovedHere; // References owned by this cell that have been moved to another cell. // MovedRefTracker mMovedToAnotherCell; // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell std::vector mMergedRefs; // Get the Ptr for the given ref which originated from this cell (possibly moved to another cell at this point). Ptr getCurrentPtr(MWWorld::LiveCellRefBase* ref); /// Moves object from the given cell to this cell. void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); /// Repopulate mMergedRefs. void updateMergedRefs(); // (item, max charge) typedef std::vector > TRechargingItems; TRechargingItems mRechargingItems; bool mRechargingItemsUpToDate; void updateRechargingItems(); void rechargeItems(float duration); void checkItem(const Ptr& ptr); // helper function for forEachInternal template bool forEachImp (Visitor& visitor, List& list) { for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { if (!isAccessible(iter->mData, iter->mRef)) continue; if (!visitor (MWWorld::Ptr(&*iter, this))) return false; } return true; } // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for. template bool forEachInternal (Visitor& visitor) { return forEachImp (visitor, mActivators) && forEachImp (visitor, mPotions) && forEachImp (visitor, mAppas) && forEachImp (visitor, mArmors) && forEachImp (visitor, mBooks) && forEachImp (visitor, mClothes) && forEachImp (visitor, mContainers) && forEachImp (visitor, mDoors) && forEachImp (visitor, mIngreds) && forEachImp (visitor, mItemLists) && forEachImp (visitor, mLights) && forEachImp (visitor, mLockpicks) && forEachImp (visitor, mMiscItems) && forEachImp (visitor, mProbes) && forEachImp (visitor, mRepairs) && forEachImp (visitor, mStatics) && forEachImp (visitor, mWeapons) && forEachImp (visitor, mBodyParts) && forEachImp (visitor, mCreatures) && forEachImp (visitor, mNpcs) && forEachImp (visitor, mCreatureLists); } /// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are /// defined at the bottom of this file. template CellRefList& get(); public: /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) { return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); } /// Moves object from this cell to the given cell. /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) /// @note throws exception if cellToMoveTo == this /// @return updated MWWorld::Ptr with the new CellStore pointer set. MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); void rest(double hours); void recharge(float duration); /// Make a copy of the given object and insert it into this cell. /// @note If you get a linker error here, this means the given type can not be inserted into a cell. /// The supported types are defined at the bottom of this file. template LiveCellRefBase* insert(const LiveCellRef* ref) { mHasState = true; CellRefList& list = get(); LiveCellRefBase* ret = &list.insert(*ref); updateMergedRefs(); return ret; } /// @param readerList The readers to use for loading of the cell on-demand. CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); const ESM::Cell *getCell() const; State getState() const; const std::vector& getPreloadedIds() const; ///< Get Ids of objects in this cell, only valid in State_Preloaded bool hasState() const; ///< Does this cell have state that needs to be stored in a saved game file? bool hasId (const std::string& id) const; ///< May return true for deleted IDs when in preload state. Will return false, if cell is /// unloaded. /// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the cell is loaded. Ptr search (const std::string& id); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Triggers CellStore hasState flag. ConstPtr searchConst (const std::string& id) const; ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Does not trigger CellStore hasState flag. Ptr searchViaActorId (int id); ///< Will return an empty Ptr if cell is not loaded. Ptr searchViaRefNum (const ESM::RefNum& refNum); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Triggers CellStore hasState flag. float getWaterLevel() const; bool movedHere(const MWWorld::Ptr& ptr) const; void setWaterLevel (float level); void setFog(std::unique_ptr&& fog); ///< \note Takes ownership of the pointer ESM::FogState* getFog () const; std::size_t count() const; ///< Return total number of references, including deleted ones. void load (); ///< Load references from content file. void preload (); ///< Build ID list from content file. /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Prefer using forEachConst when possible. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template bool forEach (Visitor&& visitor) { if (mState != State_Loaded) return false; if (mMergedRefs.empty()) return true; mHasState = true; for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) continue; if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) return false; } return true; } /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template bool forEachConst (Visitor&& visitor) const { if (mState != State_Loaded) return false; for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) continue; if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) return false; } return true; } /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template bool forEachType(Visitor& visitor) { if (mState != State_Loaded) return false; if (mMergedRefs.empty()) return true; mHasState = true; CellRefList& list = get(); for (typename CellRefList::List::iterator it (list.mList.begin()); it!=list.mList.end(); ++it) { LiveCellRefBase* base = &*it; if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) continue; if (!isAccessible(base->mData, base->mRef)) continue; if (!visitor(MWWorld::Ptr(base, this))) return false; } for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) { LiveCellRefBase* base = it->first; if (dynamic_cast*>(base)) if (!visitor(MWWorld::Ptr(base, this))) return false; } return true; } // NOTE: does not account for moved references // Should be phased out when we have const version of forEach inline const CellRefList& getReadOnlyDoors() const { return mDoors; } inline const CellRefList& getReadOnlyStatics() const { return mStatics; } bool isExterior() const; Ptr searchInContainer (const std::string& id); void loadState (const ESM::CellState& state); void saveState (ESM::CellState& state) const; void writeFog (ESM::ESMWriter& writer) const; void readFog (ESM::ESMReader& reader); void writeReferences (ESM::ESMWriter& writer) const; struct GetCellStoreCallback { public: ///@note must return nullptr if the cell is not found virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0; virtual ~GetCellStoreCallback() = default; }; /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved references) void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. Ptr getMovedActor(int actorId) const; private: /// Run through references and store IDs void listRefs(); void loadRefs(); void loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. }; template<> inline CellRefList& CellStore::get() { mHasState = true; return mActivators; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mPotions; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mAppas; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mArmors; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mBooks; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mClothes; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mContainers; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mCreatures; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mDoors; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mIngreds; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mCreatureLists; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mItemLists; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mLights; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mLockpicks; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mMiscItems; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mNpcs; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mProbes; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mRepairs; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mStatics; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mWeapons; } template<> inline CellRefList& CellStore::get() { mHasState = true; return mBodyParts; } bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellutils.hpp000066400000000000000000000006561445372753700231170ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_CELLUTILS_H #define OPENMW_MWWORLD_CELLUTILS_H #include #include #include namespace MWWorld { inline osg::Vec2i positionToCellIndex(float x, float y) { return { static_cast(std::floor(x / Constants::CellSizeInUnits)), static_cast(std::floor(y / Constants::CellSizeInUnits)) }; } } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/cellvisitors.hpp000066400000000000000000000010131445372753700236250ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CELLVISITORS_H #define GAME_MWWORLD_CELLVISITORS_H #include #include #include "ptr.hpp" namespace MWWorld { struct ListAndResetObjectsVisitor { std::vector mObjects; bool operator() (const MWWorld::Ptr& ptr) { if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); } mObjects.push_back (ptr); return true; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/class.cpp000066400000000000000000000360741445372753700222220ustar00rootroot00000000000000#include "class.hpp" #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "ptr.hpp" #include "nullaction.hpp" #include "failedaction.hpp" #include "actiontake.hpp" #include "containerstore.hpp" #include "../mwgui/tooltips.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWWorld { std::map& Class::getClasses() { static std::map values; return values; } void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } void Class::insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { } void Class::insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const {} bool Class::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const { return false; } void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const { throw std::runtime_error ("class does not represent an actor"); } bool Class::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return false; } int Class::getServices(const ConstPtr &actor) const { throw std::runtime_error ("class does not have services"); } MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have creature stats"); } MWMechanics::NpcStats& Class::getNpcStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have NPC stats"); } bool Class::hasItemHealth (const ConstPtr& ptr) const { return false; } int Class::getItemHealth(const ConstPtr &ptr) const { if (ptr.getCellRef().getCharge() == -1) return getItemMaxHealth(ptr); else return ptr.getCellRef().getCharge(); } float Class::getItemNormalizedHealth (const ConstPtr& ptr) const { if (getItemMaxHealth(ptr) == 0) { return 0.f; } else { return getItemHealth(ptr) / static_cast(getItemMaxHealth(ptr)); } } int Class::getItemMaxHealth (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have item health"); } void Class::hit(const Ptr& ptr, float attackStrength, int type) const { throw std::runtime_error("class cannot hit"); } void Class::block(const Ptr &ptr) const { throw std::runtime_error("class cannot block"); } void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const { throw std::runtime_error("class cannot be hit"); } std::unique_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return std::make_unique(); } std::unique_ptr Class::use (const Ptr& ptr, bool force) const { return std::make_unique(); } ContainerStore& Class::getContainerStore (const Ptr& ptr) const { throw std::runtime_error ("class does not have a container store"); } InventoryStore& Class::getInventoryStore (const Ptr& ptr) const { throw std::runtime_error ("class does not have an inventory store"); } bool Class::hasInventoryStore(const Ptr &ptr) const { return false; } bool Class::canLock(const ConstPtr &ptr) const { return false; } void Class::setRemainingUsageTime (const Ptr& ptr, float duration) const { throw std::runtime_error ("class does not support time-based uses"); } float Class::getRemainingUsageTime (const ConstPtr& ptr) const { return -1; } std::string Class::getScript (const ConstPtr& ptr) const { return ""; } float Class::getMaxSpeed (const Ptr& ptr) const { return 0; } float Class::getCurrentSpeed (const Ptr& ptr) const { return 0; } float Class::getJump (const Ptr& ptr) const { return 0; } int Class::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not support enchanting"); } MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const { throw std::runtime_error ("movement settings not supported by class"); } osg::Vec3f Class::getRotationVector (const Ptr& ptr) const { return osg::Vec3f (0, 0, 0); } std::pair, bool> Class::getEquipmentSlots (const ConstPtr& ptr) const { return std::make_pair (std::vector(), false); } int Class::getEquipmentSkill (const ConstPtr& ptr) const { return -1; } int Class::getValue (const ConstPtr& ptr) const { throw std::logic_error ("value not supported by this class"); } float Class::getCapacity (const MWWorld::Ptr& ptr) const { throw std::runtime_error ("capacity not supported by this class"); } float Class::getWeight(const ConstPtr &ptr) const { throw std::runtime_error ("weight not supported by this class"); } float Class::getEncumbrance (const MWWorld::Ptr& ptr) const { throw std::runtime_error ("encumbrance not supported by class"); } bool Class::isEssential (const MWWorld::ConstPtr& ptr) const { return false; } float Class::getArmorRating (const MWWorld::Ptr& ptr) const { throw std::runtime_error("Class does not support armor rating"); } const Class& Class::get (unsigned int key) { const auto& classes = getClasses(); auto iter = classes.find(key); if (iter == classes.end()) throw std::logic_error ("Class::get(): unknown class key: " + std::to_string(key)); return *iter->second; } bool Class::isPersistent(const ConstPtr &ptr) const { throw std::runtime_error ("class does not support persistence"); } void Class::registerClass(Class& instance) { getClasses().emplace(instance.getType(), &instance); } std::string Class::getUpSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an up sound"); } std::string Class::getDownSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an down sound"); } std::string Class::getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const { throw std::runtime_error("class does not support soundgen look up"); } std::string Class::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not have any inventory icon"); } MWGui::ToolTipInfo Class::getToolTipInfo (const ConstPtr& ptr, int count) const { throw std::runtime_error ("class does not have a tool tip"); } bool Class::showsInInventory (const ConstPtr& ptr) const { // NOTE: Don't show WerewolfRobe objects in the inventory, or allow them to be taken. // Vanilla likely uses a hack like this since there's no other way to prevent it from // being shown or taken. return (ptr.getCellRef().getRefId() != "werewolfrobe"); } bool Class::hasToolTip (const ConstPtr& ptr) const { return true; } std::string Class::getEnchantment (const ConstPtr& ptr) const { return ""; } void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const { } std::string Class::getModel(const MWWorld::ConstPtr &ptr) const { return ""; } bool Class::useAnim() const { return false; } void Class::getModelsToPreload(const Ptr &ptr, std::vector &models) const { std::string model = getModel(ptr); if (!model.empty()) models.push_back(model); } std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); } std::pair Class::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { return std::make_pair (1, ""); } void Class::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { } std::unique_ptr Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return std::make_unique(); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if(sound) action->setSound(sound->mId); return action; } std::unique_ptr action = std::make_unique(ptr); action->setSound(getUpSoundId(ptr)); return action; } MWWorld::Ptr Class::copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const { throw std::runtime_error("unable to copy class to cell"); } MWWorld::Ptr Class::copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference newPtr.getRefData().setCount(count); return newPtr; } MWWorld::Ptr Class::copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const { Ptr newPtr = copyToCell(ptr, cell, count); newPtr.getRefData().setPosition(pos); return newPtr; } bool Class::isBipedal(const ConstPtr &ptr) const { return false; } bool Class::canFly(const ConstPtr &ptr) const { return false; } bool Class::canSwim(const ConstPtr &ptr) const { return false; } bool Class::canWalk(const ConstPtr &ptr) const { return false; } bool Class::isPureWaterCreature(const ConstPtr& ptr) const { return canSwim(ptr) && !isBipedal(ptr) && !canFly(ptr) && !canWalk(ptr); } bool Class::isPureFlyingCreature(const ConstPtr& ptr) const { return canFly(ptr) && !isBipedal(ptr) && !canSwim(ptr) && !canWalk(ptr); } bool Class::isPureLandCreature(const Ptr& ptr) const { return canWalk(ptr) && !isBipedal(ptr) && !canFly(ptr) && !canSwim(ptr); } bool Class::isMobile(const MWWorld::Ptr& ptr) const { return canSwim(ptr) || canWalk(ptr) || canFly(ptr); } float Class::getSkill(const MWWorld::Ptr& ptr, int skill) const { throw std::runtime_error("class does not support skills"); } int Class::getBloodTexture (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support gore"); } void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} void Class::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {} int Class::getBaseGold(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support base gold"); } bool Class::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return false; } MWWorld::DoorState Class::getDoorState (const MWWorld::ConstPtr &ptr) const { throw std::runtime_error("this is not a door"); } void Class::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const { throw std::runtime_error("this is not a door"); } float Class::getNormalizedEncumbrance(const Ptr &ptr) const { float capacity = getCapacity(ptr); float encumbrance = getEncumbrance(ptr); if (encumbrance == 0) return 0.f; if (capacity == 0) return 1.f; return encumbrance / capacity; } std::string Class::getSound(const MWWorld::ConstPtr&) const { return std::string(); } int Class::getBaseFightRating(const ConstPtr &ptr) const { throw std::runtime_error("class does not support fight rating"); } std::string Class::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { return std::string(); } int Class::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { return -1; } float Class::getEffectiveArmorRating(const ConstPtr &armor, const Ptr &actor) const { throw std::runtime_error("class does not support armor ratings"); } osg::Vec4f Class::getEnchantmentColor(const MWWorld::ConstPtr& item) const { osg::Vec4f result(1,1,1,1); std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) return result; const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentName); if (!enchantment) return result; assert (enchantment->mEffects.mList.size()); const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().search( enchantment->mEffects.mList.front().mEffectID); if (!magicEffect) return result; result.x() = magicEffect->mData.mRed / 255.f; result.y() = magicEffect->mData.mGreen / 255.f; result.z() = magicEffect->mData.mBlue / 255.f; return result; } void Class::setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const { throw std::runtime_error ("class does not have creature stats"); } void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const { throw std::runtime_error ("class does not have an inventory store"); } float Class::getWalkSpeed(const Ptr& /*ptr*/) const { return 0; } float Class::getRunSpeed(const Ptr& /*ptr*/) const { return 0; } float Class::getSwimSpeed(const Ptr& /*ptr*/) const { return 0; } } openmw-openmw-0.48.0/apps/openmw/mwworld/class.hpp000066400000000000000000000416771445372753700222340ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CLASS_H #define GAME_MWWORLD_CLASS_H #include #include #include #include #include #include #include "ptr.hpp" #include "doorstate.hpp" #include "../mwmechanics/aisetting.hpp" namespace ESM { struct ObjectState; } namespace MWRender { class RenderingInterface; } namespace MWPhysics { class PhysicsSystem; } namespace MWMechanics { class NpcStats; struct Movement; class CreatureStats; } namespace MWGui { struct ToolTipInfo; } namespace ESM { struct Position; } namespace MWWorld { class ContainerStore; class InventoryStore; class CellStore; class Action; /// \brief Base class for referenceable esm records class Class { const unsigned mType; static std::map& getClasses(); protected: explicit Class(unsigned type) : mType(type) {} std::unique_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items virtual Ptr copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const; public: virtual ~Class() = default; Class (const Class&) = delete; Class& operator= (const Class&) = delete; unsigned int getType() const { return mType; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; virtual std::string getName (const ConstPtr& ptr) const = 0; ///< \return name or ID; can return an empty string. virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying virtual MWMechanics::CreatureStats& getCreatureStats (const Ptr& ptr) const; ///< Return creature stats or throw an exception, if class does not have creature stats /// (default implementation: throw an exception) virtual bool hasToolTip (const ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: true) virtual MWGui::ToolTipInfo getToolTipInfo (const ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual bool showsInInventory (const ConstPtr& ptr) const; ///< Return whether ptr shows in inventory views. /// Hidden items are not displayed and cannot be (re)moved by the user. /// \return True if shown, false if hidden. virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const; ///< Return NPC stats or throw an exception, if class does not have NPC stats /// (default implementation: throw an exception) virtual bool hasItemHealth (const ConstPtr& ptr) const; ///< \return Item health data available? (default implementation: false) virtual int getItemHealth (const ConstPtr& ptr) const; ///< Return current item health or throw an exception if class does not have item health virtual float getItemNormalizedHealth (const ConstPtr& ptr) const; ///< Return current item health re-scaled to maximum health virtual int getItemMaxHealth (const ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) virtual void hit(const Ptr& ptr, float attackStrength, int type=-1) const; ///< Execute a melee hit, using the current weapon. This will check the relevant skills /// of the given attacker, and whoever is hit. /// \param attackStrength how long the attack was charged for, a value in 0-1 range. /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType /// enums. ignored for creature attacks. /// (default implementation: throw an exception) virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the /// actor responsible for the attack, and \a successful specifies if the hit is /// successful or not. virtual void block (const Ptr& ptr) const; ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield /// (default implementation: throw an exception) virtual std::unique_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). virtual std::unique_ptr use (const Ptr& ptr, bool force=false) const; ///< Generate action for using via inventory menu (default implementation: return a /// null action). virtual ContainerStore& getContainerStore (const Ptr& ptr) const; ///< Return container store or throw an exception, if class does not have a /// container store (default implementation: throw an exception) virtual InventoryStore& getInventoryStore (const Ptr& ptr) const; ///< Return inventory store or throw an exception, if class does not have a /// inventory store (default implementation: throw an exception) virtual bool hasInventoryStore (const Ptr& ptr) const; ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) virtual bool canLock (const ConstPtr& ptr) const; virtual void setRemainingUsageTime (const Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object, such as an equippable light /// source. (default implementation: throw an exception) virtual float getRemainingUsageTime (const ConstPtr& ptr) const; ///< Returns the remaining duration of the object, such as an equippable light /// source. (default implementation: -1, i.e. infinite) virtual std::string getScript (const ConstPtr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty /// string). virtual float getWalkSpeed(const Ptr& ptr) const; virtual float getRunSpeed(const Ptr& ptr) const; virtual float getSwimSpeed(const Ptr& ptr) const; /// Return maximal movement speed for the current state. virtual float getMaxSpeed(const Ptr& ptr) const; /// Return current movement speed. virtual float getCurrentSpeed(const Ptr& ptr) const; virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; ///< Return desired movement. virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. virtual std::pair, bool> getEquipmentSlots (const ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? /// /// Default implementation: return (empty vector, false). virtual int getEquipmentSkill (const ConstPtr& ptr) const; /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. /// (default implementation: return -1) virtual int getValue (const ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. /// (default implementation: throws an exception) virtual float getCapacity (const MWWorld::Ptr& ptr) const; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. /// (default implementation: throws an exception) virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. /// (default implementation: throws an exception) virtual float getNormalizedEncumbrance (const MWWorld::Ptr& ptr) const; ///< Returns encumbrance re-scaled to capacity virtual bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const; ///< Consume an item, e. g. a potion. virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const; ///< Inform actor \a ptr that a skill use has succeeded. /// /// (default implementations: throws an exception) virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) /// /// (default implementation: return false) virtual std::string getUpSoundId (const ConstPtr& ptr) const; ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) virtual std::string getDownSoundId (const ConstPtr& ptr) const; ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) virtual std::string getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const; ///< Returns the sound ID for \a ptr of the given soundgen \a type. virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; ///< @return the number of enchantment points available for possible enchanting virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices virtual int getServices (const MWWorld::ConstPtr& actor) const; virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual bool useAnim() const; ///< Whether or not to use animated variant of model (default false) virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message virtual float getWeight (const MWWorld::ConstPtr& ptr) const; virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; virtual bool isKey (const MWWorld::ConstPtr& ptr) const { return false; } virtual bool isGold(const MWWorld::ConstPtr& ptr) const { return false; } virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; } ///< Return whether this class of object can be activated with telekinesis /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const; virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const; virtual bool isActivator() const { return false; } virtual bool isActor() const { return false; } virtual bool isNpc() const { return false; } virtual bool isDoor() const { return false; } virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; bool isPureWaterCreature(const MWWorld::ConstPtr& ptr) const; bool isPureFlyingCreature(const MWWorld::ConstPtr& ptr) const; bool isPureLandCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const; virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. static const Class& get (unsigned int key); ///< If there is no class for this \a key, an exception is thrown. static void registerClass(Class& instance); virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; virtual DoorState getDoorState (const MWWorld::ConstPtr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, DoorState state) const; virtual void respawn (const MWWorld::Ptr& ptr) const {} /// Returns sound id virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; virtual std::string getPrimaryFaction (const MWWorld::ConstPtr& ptr) const; virtual int getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; virtual void setBaseAISetting(const std::string& id, MWMechanics::AiSetting setting, int value) const; virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/containerstore.cpp000066400000000000000000001302441445372753700241460ustar00rootroot00000000000000#include "containerstore.hpp" #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/recharge.hpp" #include "manualref.hpp" #include "refdata.hpp" #include "class.hpp" #include "localscripts.hpp" #include "player.hpp" #include "esmstore.hpp" namespace { void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) { auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); for(const auto&& ptr : store) { const std::string& script = ptr.getClass().getScript(ptr); if(!script.empty()) { MWWorld::Ptr item = ptr; item.mCell = cell; scripts.add(script, item); } } } template float getTotalWeight (const MWWorld::CellRefList& cellRefList) { float sum = 0; for (const MWWorld::LiveCellRef& liveCellRef : cellRefList.mList) { if (const int count = liveCellRef.mData.getCount(); count > 0) sum += count * liveCellRef.mBase->mData.mWeight; } return sum; } template MWWorld::Ptr searchId (MWWorld::CellRefList& list, const std::string& id, MWWorld::ContainerStore *store) { store->resolve(); std::string id2 = Misc::StringUtils::lowerCase (id); for (MWWorld::LiveCellRef& liveCellRef : list.mList) { if (Misc::StringUtils::ciEqual(liveCellRef.mBase->mId, id2) && liveCellRef.mData.getCount()) { MWWorld::Ptr ptr(&liveCellRef, nullptr); ptr.setContainerStore (store); return ptr; } } return MWWorld::Ptr(); } } MWWorld::ResolutionListener::~ResolutionListener() { try { mStore.unresolve(); } catch(const std::exception& e) { Log(Debug::Error) << "Failed to clear temporary container contents: " << e.what(); } } template MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, const ESM::ObjectState& state) { if (!LiveCellRef::checkState (state)) return ContainerStoreIterator (this); // not valid anymore with current content files -> skip const T *record = MWBase::Environment::get().getWorld()->getStore(). get().search (state.mRef.mRefID); if (!record) return ContainerStoreIterator (this); LiveCellRef ref (record); ref.load (state); collection.mList.push_back (ref); return ContainerStoreIterator (this, --collection.mList.end()); } void MWWorld::ContainerStore::storeEquipmentState(const MWWorld::LiveCellRefBase &ref, int index, ESM::InventoryState &inventory) const { } void MWWorld::ContainerStore::readEquipmentState(const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState &inventory) { } template void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::ObjectState& state) const { ref.save (state); } template void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { for (const LiveCellRef& liveCellRef : collection.mList) { if (liveCellRef.mData.getCount() == 0) continue; ESM::ObjectState state; storeState(liveCellRef, state); if (equipable) storeEquipmentState(liveCellRef, index, inventory); inventory.mItems.push_back(std::move(state)); ++index; } } const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; MWWorld::ContainerStore::ContainerStore() : mListener(nullptr) , mRechargingItemsUpToDate(false) , mCachedWeight (0) , mWeightUpToDate (false) , mModified(false) , mResolved(false) , mSeed() , mPtr() {} MWWorld::ContainerStore::~ContainerStore() {} MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin (int mask) const { return ConstContainerStoreIterator (mask, this); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cend() const { return ConstContainerStoreIterator (this); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::begin (int mask) const { return cbegin(mask); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::end() const { return cend(); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin (int mask) { return ContainerStoreIterator (mask, this); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() { return ContainerStoreIterator (this); } int MWWorld::ContainerStore::count(std::string_view id) const { int total=0; for (const auto&& iter : *this) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) total += iter.getRefData().getCount(); return total; } MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const { return mListener; } void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* listener) { mListener = listener; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) { resolve(); if (ptr.getRefData().getCount() <= count) return end(); MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); const std::string script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); remove(ptr, ptr.getRefData().getCount()-count, container); return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) { resolve(); MWWorld::ContainerStoreIterator retval = end(); for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { if (item == *iter) { retval = iter; break; } } if (retval == end()) throw std::runtime_error("item is not from this container"); for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { if (stacks(*iter, item)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); item.getRefData().setCount(0); retval = iter; break; } } return retval; } bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) return false; // If it has an enchantment, don't stack when some of the charge is already used if (!ptr1.getClass().getEnchantment(ptr1).empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( ptr1.getClass().getEnchantment(ptr1)); float maxCharge = static_cast(enchantment->mData.mCharge); float enchantCharge1 = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); float enchantCharge2 = ptr2.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr2.getCellRef().getEnchantmentCharge(); if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) return false; } return ptr1 != ptr2 // an item never stacks onto itself && ptr1.getCellRef().getSoul() == ptr2.getCellRef().getSoul() && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) // Items with scripts never stack && cls1.getScript(ptr1).empty() && cls2.getScript(ptr2).empty() // item that is already partly used up never stacks && (!cls1.hasItemHealth(ptr1) || ( cls1.getItemHealth(ptr1) == cls1.getItemMaxHealth(ptr1) && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(std::string_view id, int count, const Ptr &actorPtr) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); return add(ref.getPtr(), count, actorPtr); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/, bool resolve) { Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::ContainerStoreIterator it = addImp(itemPtr, count, resolve); // The copy of the original item we just made MWWorld::Ptr item = *it; // we may have copied an item from the world, so reset a few things first item.getRefData().setBaseNode(nullptr); // Especially important, otherwise scripts on the item could think that it's actually in a cell ESM::Position pos; pos.rot[0] = 0; pos.rot[1] = 0; pos.rot[2] = 0; pos.pos[0] = 0; pos.pos[1] = 0; pos.pos[2] = 0; item.getCellRef().setPosition(pos); // We do not need to store owners for items in container stores - we do not use it anyway. item.getCellRef().setOwner(""); item.getCellRef().resetGlobalVariable(); item.getCellRef().setFaction(""); item.getCellRef().setFactionRank(-2); // must reset the RefNum on the copied item, so that the RefNum on the original item stays unique // maybe we should do this in the copy constructor instead? item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); if (!script.empty()) { if (actorPtr == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed item.mCell = nullptr; } else { // Set mCell to the cell of the container/actor, so that the scripts are removed properly when // the cell of the container/actor goes inactive item.mCell = actorPtr.getCell(); } item.mContainerStore = this; MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item); // Set OnPCAdd special variable, if it is declared // Make sure to do this *after* we have added the script to LocalScripts if (actorPtr == player) item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } // we should not fire event for InventoryStore yet - it has some custom logic if (mListener && !actorPtr.getClass().hasInventoryStore(actorPtr)) mListener->itemAdded(item, count); return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count, bool markModified) { if(markModified) resolve(); int type = getType(ptr); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) if(ptr.getClass().isGold(ptr)) { int realCount = count * ptr.getClass().getValue(ptr); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); return iter; } } MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); return addNewStack(ref.getPtr(), realCount); } // determine whether to stack or not for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { if (stacks(*iter, ptr)) { // stack iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); flagAsModified(); return iter; } } // if we got here, this means no stacking return addNewStack(ptr, count); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const ConstPtr& ptr, int count) { ContainerStoreIterator it = begin(); switch (getType(ptr)) { case Type_Potion: potions.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --potions.mList.end()); break; case Type_Apparatus: appas.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --appas.mList.end()); break; case Type_Armor: armors.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --armors.mList.end()); break; case Type_Book: books.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --books.mList.end()); break; case Type_Clothing: clothes.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --clothes.mList.end()); break; case Type_Ingredient: ingreds.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --ingreds.mList.end()); break; case Type_Light: lights.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lights.mList.end()); break; case Type_Lockpick: lockpicks.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lockpicks.mList.end()); break; case Type_Miscellaneous: miscItems.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --miscItems.mList.end()); break; case Type_Probe: probes.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --probes.mList.end()); break; case Type_Repair: repairs.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --repairs.mList.end()); break; case Type_Weapon: weapons.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --weapons.mList.end()); break; } it->getRefData().setCount(count); flagAsModified(); return it; } void MWWorld::ContainerStore::rechargeItems(float duration) { if (!mRechargingItemsUpToDate) { updateRechargingItems(); mRechargingItemsUpToDate = true; } for (auto& it : mRechargingItems) { if (!MWMechanics::rechargeItem(*it.first, it.second, duration)) continue; // attempt to restack when fully recharged if (it.first->getCellRef().getEnchantmentCharge() == it.second) it.first = restack(*it.first); } } void MWWorld::ContainerStore::updateRechargingItems() { mRechargingItems.clear(); for (ContainerStoreIterator it = begin(); it != end(); ++it) { const std::string& enchantmentId = it->getClass().getEnchantment(*it); if (!enchantmentId.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentId); if (!enchantment) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId(); continue; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.emplace_back(it, static_cast(enchantment->mData.mCharge)); } } } int MWWorld::ContainerStore::remove(std::string_view itemId, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst) { if(resolveFirst) resolve(); int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); flagAsModified(); // number of removed items return count - toRemove; } bool MWWorld::ContainerStore::hasVisibleItems() const { for (const auto&& iter : *this) { if (iter.getClass().showsInInventory(iter)) return true; } return false; } int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst) { assert(this == item.getContainerStore()); if(resolveFirst) resolve(); int toRemove = count; RefData& itemRef = item.getRefData(); if (itemRef.getCount() <= toRemove) { toRemove -= itemRef.getCount(); itemRef.setCount(0); } else { itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove)); toRemove = 0; } flagAsModified(); // we should not fire event for InventoryStore yet - it has some custom logic if (mListener && !actor.getClass().hasInventoryStore(actor)) mListener->itemRemoved(item, count - toRemove); // number of removed items return count - toRemove; } void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& prng) { for (const ESM::ContItem& iter : items.mList) { std::string id = Misc::StringUtils::lowerCase(iter.mItem); addInitialItem(id, owner, iter.mCount, &prng); } flagAsModified(); mResolved = true; } void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed) { mSeed = seed; for (const ESM::ContItem& iter : items.mList) { std::string id = Misc::StringUtils::lowerCase(iter.mItem); addInitialItem(id, owner, iter.mCount, nullptr); } flagAsModified(); mResolved = false; } void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { if (count == 0) return; //Don't restock with nothing. try { ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) { addInitialItemImp(ref.getPtr(), owner, count, prng, topLevel); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < std::abs(count); i++) addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, prng, topLevel); } } catch (const std::exception& e) { Log(Debug::Warning) << "Warning: MWWorld::ContainerStore::addInitialItem: " << e.what(); } } void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { if (ptr.getType()==ESM::ItemLevList::sRecordId) { if(!prng) return; const ESM::ItemLevList* levItemList = ptr.get()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i=0; i 0 ? 1 : -1, prng, true); return; } else { std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *prng); if (itemId.empty()) return; addInitialItem(itemId, owner, count, prng, false); } } else { ptr.getCellRef().setOwner(owner); addImp (ptr, count, false); } } void MWWorld::ContainerStore::clear() { for (auto&& iter : *this) iter.getRefData().setCount (0); flagAsModified(); mModified = true; } void MWWorld::ContainerStore::flagAsModified() { mWeightUpToDate = false; mRechargingItemsUpToDate = false; } bool MWWorld::ContainerStore::isResolved() const { return mResolved; } void MWWorld::ContainerStore::resolve() { if(!mResolved && !mPtr.isEmpty()) { for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{mSeed}; fill(mPtr.get()->mBase->mInventory, "", prng); addScripts(*this, mPtr.mCell); } mModified = true; } MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() { if(mModified) return {}; std::shared_ptr listener = mResolutionListener.lock(); if(!listener) { listener = std::make_shared(*this); mResolutionListener = listener; } if(!mResolved && !mPtr.isEmpty()) { for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{mSeed}; fill(mPtr.get()->mBase->mInventory, "", prng); addScripts(*this, mPtr.mCell); } return {listener}; } void MWWorld::ContainerStore::unresolve() { if (mModified) return; if (mResolved && !mPtr.isEmpty()) { for(const auto&& ptr : *this) ptr.getRefData().setCount(0); fillNonRandom(mPtr.get()->mBase->mInventory, "", mSeed); addScripts(*this, mPtr.mCell); mResolved = false; } } float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) { mCachedWeight = 0; mCachedWeight += getTotalWeight (potions); mCachedWeight += getTotalWeight (appas); mCachedWeight += getTotalWeight (armors); mCachedWeight += getTotalWeight (books); mCachedWeight += getTotalWeight (clothes); mCachedWeight += getTotalWeight (ingreds); mCachedWeight += getTotalWeight (lights); mCachedWeight += getTotalWeight (lockpicks); mCachedWeight += getTotalWeight (miscItems); mCachedWeight += getTotalWeight (probes); mCachedWeight += getTotalWeight (repairs); mCachedWeight += getTotalWeight (weapons); mWeightUpToDate = true; } return mCachedWeight; } int MWWorld::ContainerStore::getType (const ConstPtr& ptr) { if (ptr.isEmpty()) throw std::runtime_error ("can't put a non-existent object into a container"); if (ptr.getType()==ESM::Potion::sRecordId) return Type_Potion; if (ptr.getType()==ESM::Apparatus::sRecordId) return Type_Apparatus; if (ptr.getType()==ESM::Armor::sRecordId) return Type_Armor; if (ptr.getType()==ESM::Book::sRecordId) return Type_Book; if (ptr.getType()==ESM::Clothing::sRecordId) return Type_Clothing; if (ptr.getType()==ESM::Ingredient::sRecordId) return Type_Ingredient; if (ptr.getType()==ESM::Light::sRecordId) return Type_Light; if (ptr.getType()==ESM::Lockpick::sRecordId) return Type_Lockpick; if (ptr.getType()==ESM::Miscellaneous::sRecordId) return Type_Miscellaneous; if (ptr.getType()==ESM::Probe::sRecordId) return Type_Probe; if (ptr.getType()==ESM::Repair::sRecordId) return Type_Repair; if (ptr.getType()==ESM::Weapon::sRecordId) return Type_Weapon; throw std::runtime_error("Object '" + ptr.getCellRef().getRefId() + "' of type " + std::string(ptr.getTypeDescription()) + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) { MWWorld::Ptr item; int itemHealth = 1; for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found if (item.isEmpty() || (iterHealth > 0 && iterHealth < itemHealth) || (itemHealth <= 0 && iterHealth > 0)) { item = iter; itemHealth = iterHealth; } } } return item; } MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) { resolve(); { Ptr ptr = searchId (potions, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (appas, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (armors, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (books, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (clothes, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (ingreds, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (lights, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (lockpicks, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (miscItems, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (probes, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (repairs, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId (weapons, id, this); if (!ptr.isEmpty()) return ptr; } return Ptr(); } int MWWorld::ContainerStore::addItems(int count1, int count2) { int sum = std::abs(count1) + std::abs(count2); if(count1 < 0 || count2 < 0) return -sum; return sum; } int MWWorld::ContainerStore::subtractItems(int count1, int count2) { int sum = std::abs(count1) - std::abs(count2); if(count1 < 0 || count2 < 0) return -sum; return sum; } void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const { state.mItems.clear(); int index = 0; storeStates (potions, state, index); storeStates (appas, state, index); storeStates (armors, state, index, true); storeStates (books, state, index, true); // not equipable as such, but for selectedEnchantItem storeStates (clothes, state, index, true); storeStates (ingreds, state, index); storeStates (lockpicks, state, index, true); storeStates (miscItems, state, index); storeStates (probes, state, index, true); storeStates (repairs, state, index); storeStates (weapons, state, index, true); storeStates (lights, state, index, true); } void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) { clear(); mModified = true; mResolved = true; int index = 0; for (const ESM::ObjectState& state : inventory.mItems) { int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); int thisIndex = index++; switch (type) { case ESM::REC_ALCH: getState (potions, state); break; case ESM::REC_APPA: getState (appas, state); break; case ESM::REC_ARMO: readEquipmentState (getState (armors, state), thisIndex, inventory); break; case ESM::REC_BOOK: readEquipmentState (getState (books, state), thisIndex, inventory); break; // not equipable as such, but for selectedEnchantItem case ESM::REC_CLOT: readEquipmentState (getState (clothes, state), thisIndex, inventory); break; case ESM::REC_INGR: getState (ingreds, state); break; case ESM::REC_LOCK: readEquipmentState (getState (lockpicks, state), thisIndex, inventory); break; case ESM::REC_MISC: getState (miscItems, state); break; case ESM::REC_PROB: readEquipmentState (getState (probes, state), thisIndex, inventory); break; case ESM::REC_REPA: getState (repairs, state); break; case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; case 0: Log(Debug::Warning) << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)"; break; default: Log(Debug::Warning) << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID; break; } } } template template void MWWorld::ContainerStoreIteratorBase::copy (const ContainerStoreIteratorBase& src) { mType = src.mType; mMask = src.mMask; mContainer = src.mContainer; mPtr = src.mPtr; switch (src.mType) { case MWWorld::ContainerStore::Type_Potion: mPotion = src.mPotion; break; case MWWorld::ContainerStore::Type_Apparatus: mApparatus = src.mApparatus; break; case MWWorld::ContainerStore::Type_Armor: mArmor = src.mArmor; break; case MWWorld::ContainerStore::Type_Book: mBook = src.mBook; break; case MWWorld::ContainerStore::Type_Clothing: mClothing = src.mClothing; break; case MWWorld::ContainerStore::Type_Ingredient: mIngredient = src.mIngredient; break; case MWWorld::ContainerStore::Type_Light: mLight = src.mLight; break; case MWWorld::ContainerStore::Type_Lockpick: mLockpick = src.mLockpick; break; case MWWorld::ContainerStore::Type_Miscellaneous: mMiscellaneous = src.mMiscellaneous; break; case MWWorld::ContainerStore::Type_Probe: mProbe = src.mProbe; break; case MWWorld::ContainerStore::Type_Repair: mRepair = src.mRepair; break; case MWWorld::ContainerStore::Type_Weapon: mWeapon = src.mWeapon; break; case -1: break; default: assert(0); } } template void MWWorld::ContainerStoreIteratorBase::incType() { if (mType==0) mType = 1; else if (mType!=-1) { mType <<= 1; if (mType>ContainerStore::Type_Last) mType = -1; } } template void MWWorld::ContainerStoreIteratorBase::nextType() { while (mType!=-1) { incType(); if ((mType & mMask) && mType>0) if (resetIterator()) break; } } template bool MWWorld::ContainerStoreIteratorBase::resetIterator() { switch (mType) { case ContainerStore::Type_Potion: mPotion = mContainer->potions.mList.begin(); return mPotion!=mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: mApparatus = mContainer->appas.mList.begin(); return mApparatus!=mContainer->appas.mList.end(); case ContainerStore::Type_Armor: mArmor = mContainer->armors.mList.begin(); return mArmor!=mContainer->armors.mList.end(); case ContainerStore::Type_Book: mBook = mContainer->books.mList.begin(); return mBook!=mContainer->books.mList.end(); case ContainerStore::Type_Clothing: mClothing = mContainer->clothes.mList.begin(); return mClothing!=mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: mIngredient = mContainer->ingreds.mList.begin(); return mIngredient!=mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: mLight = mContainer->lights.mList.begin(); return mLight!=mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: mLockpick = mContainer->lockpicks.mList.begin(); return mLockpick!=mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: mMiscellaneous = mContainer->miscItems.mList.begin(); return mMiscellaneous!=mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: mProbe = mContainer->probes.mList.begin(); return mProbe!=mContainer->probes.mList.end(); case ContainerStore::Type_Repair: mRepair = mContainer->repairs.mList.begin(); return mRepair!=mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: mWeapon = mContainer->weapons.mList.begin(); return mWeapon!=mContainer->weapons.mList.end(); } return false; } template bool MWWorld::ContainerStoreIteratorBase::incIterator() { switch (mType) { case ContainerStore::Type_Potion: ++mPotion; return mPotion==mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: ++mApparatus; return mApparatus==mContainer->appas.mList.end(); case ContainerStore::Type_Armor: ++mArmor; return mArmor==mContainer->armors.mList.end(); case ContainerStore::Type_Book: ++mBook; return mBook==mContainer->books.mList.end(); case ContainerStore::Type_Clothing: ++mClothing; return mClothing==mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: ++mIngredient; return mIngredient==mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: ++mLight; return mLight==mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: ++mLockpick; return mLockpick==mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: ++mMiscellaneous; return mMiscellaneous==mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: ++mProbe; return mProbe==mContainer->probes.mList.end(); case ContainerStore::Type_Repair: ++mRepair; return mRepair==mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: ++mWeapon; return mWeapon==mContainer->weapons.mList.end(); } return true; } template template bool MWWorld::ContainerStoreIteratorBase::isEqual (const ContainerStoreIteratorBase& other) const { if (mContainer!=other.mContainer) return false; if (mType!=other.mType) return false; switch (mType) { case ContainerStore::Type_Potion: return mPotion==other.mPotion; case ContainerStore::Type_Apparatus: return mApparatus==other.mApparatus; case ContainerStore::Type_Armor: return mArmor==other.mArmor; case ContainerStore::Type_Book: return mBook==other.mBook; case ContainerStore::Type_Clothing: return mClothing==other.mClothing; case ContainerStore::Type_Ingredient: return mIngredient==other.mIngredient; case ContainerStore::Type_Light: return mLight==other.mLight; case ContainerStore::Type_Lockpick: return mLockpick==other.mLockpick; case ContainerStore::Type_Miscellaneous: return mMiscellaneous==other.mMiscellaneous; case ContainerStore::Type_Probe: return mProbe==other.mProbe; case ContainerStore::Type_Repair: return mRepair==other.mRepair; case ContainerStore::Type_Weapon: return mWeapon==other.mWeapon; case -1: return true; } return false; } template PtrType *MWWorld::ContainerStoreIteratorBase::operator->() const { mPtr = **this; return &mPtr; } template PtrType MWWorld::ContainerStoreIteratorBase::operator*() const { PtrType ptr; switch (mType) { case ContainerStore::Type_Potion: ptr = PtrType (&*mPotion, nullptr); break; case ContainerStore::Type_Apparatus: ptr = PtrType (&*mApparatus, nullptr); break; case ContainerStore::Type_Armor: ptr = PtrType (&*mArmor, nullptr); break; case ContainerStore::Type_Book: ptr = PtrType (&*mBook, nullptr); break; case ContainerStore::Type_Clothing: ptr = PtrType (&*mClothing, nullptr); break; case ContainerStore::Type_Ingredient: ptr = PtrType (&*mIngredient, nullptr); break; case ContainerStore::Type_Light: ptr = PtrType (&*mLight, nullptr); break; case ContainerStore::Type_Lockpick: ptr = PtrType (&*mLockpick, nullptr); break; case ContainerStore::Type_Miscellaneous: ptr = PtrType (&*mMiscellaneous, nullptr); break; case ContainerStore::Type_Probe: ptr = PtrType (&*mProbe, nullptr); break; case ContainerStore::Type_Repair: ptr = PtrType (&*mRepair, nullptr); break; case ContainerStore::Type_Weapon: ptr = PtrType (&*mWeapon, nullptr); break; } if (ptr.isEmpty()) throw std::runtime_error ("invalid iterator"); ptr.setContainerStore (mContainer); return ptr; } template MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator++() { do { if (incIterator()) nextType(); } while (mType!=-1 && !(**this).getRefData().getCount()); return *this; } template MWWorld::ContainerStoreIteratorBase MWWorld::ContainerStoreIteratorBase::operator++ (int) { ContainerStoreIteratorBase iter (*this); ++*this; return iter; } template MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator= (const ContainerStoreIteratorBase& rhs) { if (this!=&rhs) { copy(rhs); } return *this; } template int MWWorld::ContainerStoreIteratorBase::getType() const { return mType; } template const MWWorld::ContainerStore *MWWorld::ContainerStoreIteratorBase::getContainerStore() const { return mContainer; } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container) : mType (-1), mMask (0), mContainer (container) {} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (int mask, ContainerStoreType container) : mType (0), mMask (mask), mContainer (container) { nextType(); if (mType==-1 || (**this).getRefData().getCount()) return; ++*this; } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Potion), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mPotion(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Apparatus), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mApparatus(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Armor), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mArmor(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Book), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mBook(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Clothing), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mClothing(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Ingredient), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mIngredient(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Light), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLight(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Lockpick), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLockpick(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Miscellaneous), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mMiscellaneous(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Probe), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mProbe(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Repair), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mRepair(iterator){} template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Weapon), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mWeapon(iterator){} template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) { return left.isEqual (right); } template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) { return !(left==right); } template class MWWorld::ContainerStoreIteratorBase; template class MWWorld::ContainerStoreIteratorBase; template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); openmw-openmw-0.48.0/apps/openmw/mwworld/containerstore.hpp000066400000000000000000000373601445372753700241600ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CONTAINERSTORE_H #define GAME_MWWORLD_CONTAINERSTORE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ptr.hpp" #include "cellreflist.hpp" namespace ESM { struct InventoryList; struct InventoryState; } namespace MWClass { class Container; } namespace MWWorld { class ContainerStore; template class ContainerStoreIteratorBase; typedef ContainerStoreIteratorBase ContainerStoreIterator; typedef ContainerStoreIteratorBase ConstContainerStoreIterator; class ResolutionListener { ContainerStore& mStore; public: ResolutionListener(ContainerStore& store) : mStore(store) {} ~ResolutionListener(); }; class ResolutionHandle { std::shared_ptr mListener; public: ResolutionHandle(std::shared_ptr listener) : mListener(listener) {} ResolutionHandle() = default; }; class ContainerStoreListener { public: virtual void itemAdded(const ConstPtr& item, int count) {} virtual void itemRemoved(const ConstPtr& item, int count) {} virtual ~ContainerStoreListener() = default; }; class ContainerStore { public: static constexpr int Type_Potion = 0x0001; static constexpr int Type_Apparatus = 0x0002; static constexpr int Type_Armor = 0x0004; static constexpr int Type_Book = 0x0008; static constexpr int Type_Clothing = 0x0010; static constexpr int Type_Ingredient = 0x0020; static constexpr int Type_Light = 0x0040; static constexpr int Type_Lockpick = 0x0080; static constexpr int Type_Miscellaneous = 0x0100; static constexpr int Type_Probe = 0x0200; static constexpr int Type_Repair = 0x0400; static constexpr int Type_Weapon = 0x0800; static constexpr int Type_Last = Type_Weapon; static constexpr int Type_All = 0xffff; static const std::string sGoldId; protected: ContainerStoreListener* mListener; // (item, max charge) typedef std::vector > TRechargingItems; TRechargingItems mRechargingItems; bool mRechargingItemsUpToDate; private: MWWorld::CellRefList potions; MWWorld::CellRefList appas; MWWorld::CellRefList armors; MWWorld::CellRefList books; MWWorld::CellRefList clothes; MWWorld::CellRefList ingreds; MWWorld::CellRefList lights; MWWorld::CellRefList lockpicks; MWWorld::CellRefList miscItems; MWWorld::CellRefList probes; MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; mutable float mCachedWeight; mutable bool mWeightUpToDate; bool mModified; bool mResolved; unsigned int mSeed; MWWorld::Ptr mPtr; std::weak_ptr mResolutionListener; ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true); void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel=true); void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel=true); template ContainerStoreIterator getState (CellRefList& collection, const ESM::ObjectState& state); template void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; template void storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; void updateRechargingItems(); virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); public: ContainerStore(); virtual ~ContainerStore(); virtual std::unique_ptr clone() { return std::make_unique(*this); } ConstContainerStoreIterator cbegin (int mask = Type_All) const; ConstContainerStoreIterator cend() const; ConstContainerStoreIterator begin (int mask = Type_All) const; ConstContainerStoreIterator end() const; ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); bool hasVisibleItems() const; virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. /// /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. ContainerStoreIterator add(std::string_view id, int count, const Ptr& actorPtr); ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) int remove(std::string_view itemId, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true); ///< Remove \a count item(s) designated by \a itemId from this container. /// /// @return the number of items actually removed virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true); ///< Remove \a count item(s) designated by \a item from this inventory. /// /// @return the number of items actually removed void rechargeItems (float duration); ///< Restore charge on enchanted items. Note this should only be done for the player. ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1); ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count). /// /// @return an iterator to the new stack, or end() if no new stack was created. MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); ///< Attempt to re-stack an item in this container. /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. int count(std::string_view id) const; ///< @return How many items with refID \a id are in this container? ContainerStoreListener* getContListener() const; void setContListener(ContainerStoreListener* listener); protected: ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ///< Add the item to this container (do not try to stack it onto existing items) virtual void flagAsModified(); /// + and - operations that can deal with negative stacks /// Note that negativity is infectious static int addItems(int count1, int count2); static int subtractItems(int count1, int count2); public: virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; ///< @return true if the two specified objects can stack with each other void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed); ///< Insert items into *this. void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed); ///< Insert items into *this, excluding leveled items virtual void clear(); ///< Empty container. float getWeight() const; ///< Return total weight of the items contained in *this. static int getType (const ConstPtr& ptr); ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. Ptr findReplacement(const std::string& id); ///< Returns replacement for object with given id. Prefer used items (with low durability left). Ptr search (const std::string& id); virtual void writeState (ESM::InventoryState& state) const; virtual void readState (const ESM::InventoryState& state); bool isResolved() const; void resolve(); ResolutionHandle resolveTemporarily(); void unresolve(); friend class ContainerStoreIteratorBase; friend class ContainerStoreIteratorBase; friend class ResolutionListener; friend class MWClass::Container; }; template class ContainerStoreIteratorBase { template struct IsConvertible { static constexpr bool value = true; }; template struct IsConvertible { static constexpr bool value = false; }; template struct IteratorTrait { typedef typename MWWorld::CellRefList::List::iterator type; }; template struct IteratorTrait { typedef typename MWWorld::CellRefList::List::const_iterator type; }; template struct Iterator : IteratorTrait { }; template struct ContainerStoreTrait { typedef ContainerStore* type; }; template struct ContainerStoreTrait { typedef const ContainerStore* type; }; typedef typename ContainerStoreTrait::type ContainerStoreType; int mType; int mMask; ContainerStoreType mContainer; mutable PtrType mPtr; typename Iterator::type mPotion; typename Iterator::type mApparatus; typename Iterator::type mArmor; typename Iterator::type mBook; typename Iterator::type mClothing; typename Iterator::type mIngredient; typename Iterator::type mLight; typename Iterator::type mLockpick; typename Iterator::type mMiscellaneous; typename Iterator::type mProbe; typename Iterator::type mRepair; typename Iterator::type mWeapon; ContainerStoreIteratorBase (ContainerStoreType container); ///< End-iterator ContainerStoreIteratorBase (int mask, ContainerStoreType container); ///< Begin-iterator // construct iterator using a CellRefList iterator ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); template void copy (const ContainerStoreIteratorBase& src); void incType (); void nextType (); bool resetIterator (); ///< Reset iterator for selected type. /// /// \return Type not empty? bool incIterator (); ///< Increment iterator for selected type. /// /// \return reached the end? public: using iterator_category = std::forward_iterator_tag; using value_type = PtrType; using difference_type = std::ptrdiff_t; using pointer = PtrType*; using reference = PtrType&; template ContainerStoreIteratorBase (const ContainerStoreIteratorBase& other) { char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible::value ? 1 : -1]; ((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR); copy (other); } template bool isEqual(const ContainerStoreIteratorBase& other) const; PtrType *operator->() const; PtrType operator*() const; ContainerStoreIteratorBase& operator++ (); ContainerStoreIteratorBase operator++ (int); ContainerStoreIteratorBase& operator= (const ContainerStoreIteratorBase& rhs); ContainerStoreIteratorBase (const ContainerStoreIteratorBase& rhs) = default; int getType() const; const ContainerStore *getContainerStore() const; friend class ContainerStore; friend class ContainerStoreIteratorBase; friend class ContainerStoreIteratorBase; }; template bool operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/contentloader.hpp000066400000000000000000000006061445372753700237530ustar00rootroot00000000000000#ifndef CONTENTLOADER_HPP #define CONTENTLOADER_HPP #include namespace Loading { class Listener; } namespace MWWorld { struct ContentLoader { virtual ~ContentLoader() = default; virtual void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) = 0; }; } /* namespace MWWorld */ #endif /* CONTENTLOADER_HPP */ openmw-openmw-0.48.0/apps/openmw/mwworld/customdata.cpp000066400000000000000000000044261445372753700232550ustar00rootroot00000000000000#include "customdata.hpp" #include #include #include namespace MWWorld { MWClass::CreatureCustomData &CustomData::asCreatureCustomData() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; throw std::logic_error(error.str()); } const MWClass::CreatureCustomData &CustomData::asCreatureCustomData() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; throw std::logic_error(error.str()); } MWClass::NpcCustomData &CustomData::asNpcCustomData() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcCustomData"; throw std::logic_error(error.str()); } const MWClass::NpcCustomData &CustomData::asNpcCustomData() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcCustomData"; throw std::logic_error(error.str()); } MWClass::ContainerCustomData &CustomData::asContainerCustomData() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; throw std::logic_error(error.str()); } const MWClass::ContainerCustomData &CustomData::asContainerCustomData() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; throw std::logic_error(error.str()); } MWClass::DoorCustomData &CustomData::asDoorCustomData() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorCustomData"; throw std::logic_error(error.str()); } const MWClass::DoorCustomData &CustomData::asDoorCustomData() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorCustomData"; throw std::logic_error(error.str()); } MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; throw std::logic_error(error.str()); } const MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; throw std::logic_error(error.str()); } } openmw-openmw-0.48.0/apps/openmw/mwworld/customdata.hpp000066400000000000000000000031321445372753700232530ustar00rootroot00000000000000#ifndef GAME_MWWORLD_CUSTOMDATA_H #define GAME_MWWORLD_CUSTOMDATA_H #include namespace MWClass { class CreatureCustomData; class NpcCustomData; class ContainerCustomData; class DoorCustomData; class CreatureLevListCustomData; } namespace MWWorld { /// \brief Base class for the MW-class-specific part of RefData class CustomData { public: virtual ~CustomData() {} virtual std::unique_ptr clone() const = 0; // Fast version of dynamic_cast. Needs to be overridden in the respective class. virtual MWClass::CreatureCustomData& asCreatureCustomData(); virtual const MWClass::CreatureCustomData& asCreatureCustomData() const; virtual MWClass::NpcCustomData& asNpcCustomData(); virtual const MWClass::NpcCustomData& asNpcCustomData() const; virtual MWClass::ContainerCustomData& asContainerCustomData(); virtual const MWClass::ContainerCustomData& asContainerCustomData() const; virtual MWClass::DoorCustomData& asDoorCustomData(); virtual const MWClass::DoorCustomData& asDoorCustomData() const; virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; }; template struct TypedCustomData : CustomData { std::unique_ptr clone() const final { return std::make_unique(*static_cast(this)); } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/datetimemanager.cpp000066400000000000000000000127211445372753700242350ustar00rootroot00000000000000#include "datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "duration.hpp" #include "esmstore.hpp" #include "globals.hpp" #include "timestamp.hpp" namespace { static int getDaysPerMonth(int month) { switch (month) { case 0: return 31; case 1: return 28; case 2: return 31; case 3: return 30; case 4: return 31; case 5: return 30; case 6: return 31; case 7: return 31; case 8: return 30; case 9: return 31; case 10: return 30; case 11: return 31; } throw std::runtime_error ("month out of range"); } } namespace MWWorld { void DateTimeManager::setup(Globals& globalVariables) { mGameHour = globalVariables["gamehour"].getFloat(); mDaysPassed = globalVariables["dayspassed"].getInteger(); mDay = globalVariables["day"].getInteger(); mMonth = globalVariables["month"].getInteger(); mYear = globalVariables["year"].getInteger(); mTimeScale = globalVariables["timescale"].getFloat(); } void DateTimeManager::setHour(double hour) { if (hour < 0) hour = 0; const Duration duration = Duration::fromHours(hour); mGameHour = duration.getHours(); if (const int days = duration.getDays(); days > 0) setDay(days + mDay); } void DateTimeManager::setDay(int day) { if (day < 1) day = 1; int month = mMonth; while (true) { int days = getDaysPerMonth(month); if (day <= days) break; if (month < 11) { ++month; } else { month = 0; mYear++; } day -= days; } mDay = day; mMonth = month; } TimeStamp DateTimeManager::getTimeStamp() const { return TimeStamp(mGameHour, mDaysPassed); } float DateTimeManager::getTimeScaleFactor() const { return mTimeScale; } ESM::EpochTimeStamp DateTimeManager::getEpochTimeStamp() const { ESM::EpochTimeStamp timeStamp; timeStamp.mGameHour = mGameHour; timeStamp.mDay = mDay; timeStamp.mMonth = mMonth; timeStamp.mYear = mYear; return timeStamp; } void DateTimeManager::setMonth(int month) { if (month < 0) month = 0; int years = month / 12; month = month % 12; int days = getDaysPerMonth(month); if (mDay > days) mDay = days; mMonth = month; if (years > 0) mYear += years; } void DateTimeManager::advanceTime(double hours, Globals& globalVariables) { hours += mGameHour; setHour(hours); int days = static_cast(hours / 24); if (days > 0) mDaysPassed += days; globalVariables["gamehour"].setFloat(mGameHour); globalVariables["dayspassed"].setInteger(mDaysPassed); globalVariables["day"].setInteger(mDay); globalVariables["month"].setInteger(mMonth); globalVariables["year"].setInteger(mYear); } std::string DateTimeManager::getMonthName(int month) const { if (month == -1) month = mMonth; const int months = 12; if (month < 0 || month >= months) return std::string(); static const char *monthNames[months] = { "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" }; const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().get().find(monthNames[month]); return setting->mValue.getString(); } bool DateTimeManager::updateGlobalFloat(std::string_view name, float value) { if (name=="gamehour") { setHour(value); return true; } else if (name=="day") { setDay(static_cast(value)); return true; } else if (name=="month") { setMonth(static_cast(value)); return true; } else if (name=="year") { mYear = static_cast(value); } else if (name=="timescale") { mTimeScale = value; } else if (name=="dayspassed") { mDaysPassed = static_cast(value); } return false; } bool DateTimeManager::updateGlobalInt(std::string_view name, int value) { if (name=="gamehour") { setHour(static_cast(value)); return true; } else if (name=="day") { setDay(value); return true; } else if (name=="month") { setMonth(value); return true; } else if (name=="year") { mYear = value; } else if (name=="timescale") { mTimeScale = static_cast(value); } else if (name=="dayspassed") { mDaysPassed = value; } return false; } } openmw-openmw-0.48.0/apps/openmw/mwworld/datetimemanager.hpp000066400000000000000000000017021445372753700242370ustar00rootroot00000000000000#ifndef GAME_MWWORLD_DATETIMEMANAGER_H #define GAME_MWWORLD_DATETIMEMANAGER_H #include namespace ESM { struct EpochTimeStamp; } namespace MWWorld { class Globals; class TimeStamp; class DateTimeManager { int mDaysPassed = 0; int mDay = 0; int mMonth = 0; int mYear = 0; float mGameHour = 0.f; float mTimeScale = 0.f; void setHour(double hour); void setDay(int day); void setMonth(int month); public: std::string getMonthName(int month) const; TimeStamp getTimeStamp() const; ESM::EpochTimeStamp getEpochTimeStamp() const; float getTimeScaleFactor() const; void advanceTime(double hours, Globals& globalVariables); void setup(Globals& globalVariables); bool updateGlobalInt(std::string_view name, int value); bool updateGlobalFloat(std::string_view name, float value); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/doorstate.hpp000066400000000000000000000003031445372753700231100ustar00rootroot00000000000000#ifndef GAME_MWWORLD_DOORSTATE_H #define GAME_MWWORLD_DOORSTATE_H namespace MWWorld { enum class DoorState { Idle = 0, Opening = 1, Closing = 2, }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/duration.hpp000066400000000000000000000015451445372753700227420ustar00rootroot00000000000000#ifndef GAME_MWWORLD_DURATION_H #define GAME_MWWORLD_DURATION_H #include #include namespace MWWorld { inline const double maxFloatHour = static_cast(std::nextafter(24.0f, 0.0f)); class Duration { public: static Duration fromHours(double hours) { if (hours < 0) throw std::runtime_error("Negative hours is not supported Duration"); return Duration( static_cast(hours / 24), static_cast(std::min(std::fmod(hours, 24), maxFloatHour))); } int getDays() const { return mDays; } float getHours() const { return mHours; } private: int mDays; float mHours; explicit Duration(int days, float hours) : mDays(days) , mHours(hours) { } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/esmloader.cpp000066400000000000000000000032141445372753700230560ustar00rootroot00000000000000#include "esmloader.hpp" #include "esmstore.hpp" #include #include namespace MWWorld { EsmLoader::EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder) : mReaders(readers) , mStore(store) , mEncoder(encoder) , mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue { } void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) { const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); reader->setEncoder(mEncoder); reader->setIndex(index); reader->open(filepath.string()); reader->resolveParentFileIndices(mReaders); assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) if (i == static_cast(reader->getIndex())) throw std::runtime_error("File " + reader->getName() + " asks for parent file " + reader->getGameFiles()[i].name + ", but it is not available or has been loaded in the wrong order. " "Please run the launcher to fix this issue."); mStore.load(*reader, listener, mDialogue); if (!mMasterFileFormat.has_value() && (Misc::StringUtils::ciEndsWith(reader->getName(), ".esm") || Misc::StringUtils::ciEndsWith(reader->getName(), ".omwgame"))) mMasterFileFormat = reader->getFormat(); } } /* namespace MWWorld */ openmw-openmw-0.48.0/apps/openmw/mwworld/esmloader.hpp000066400000000000000000000015101445372753700230600ustar00rootroot00000000000000#ifndef ESMLOADER_HPP #define ESMLOADER_HPP #include #include "contentloader.hpp" namespace ToUTF8 { class Utf8Encoder; } namespace ESM { class ReadersCache; struct Dialogue; } namespace MWWorld { class ESMStore; struct EsmLoader : public ContentLoader { explicit EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder); std::optional getMasterFileFormat() const { return mMasterFileFormat; } void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override; private: ESM::ReadersCache& mReaders; MWWorld::ESMStore& mStore; ToUTF8::Utf8Encoder* mEncoder; ESM::Dialogue* mDialogue; std::optional mMasterFileFormat; }; } /* namespace MWWorld */ #endif // ESMLOADER_HPP openmw-openmw-0.48.0/apps/openmw/mwworld/esmstore.cpp000066400000000000000000000442601445372753700227520ustar00rootroot00000000000000#include "esmstore.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwmechanics/spelllist.hpp" namespace { struct Ref { ESM::RefNum mRefNum; std::size_t mRefID; Ref(ESM::RefNum refNum, std::size_t refID) : mRefNum(refNum), mRefID(refID) {} }; constexpr std::size_t deletedRefID = std::numeric_limits::max(); void readRefs(const ESM::Cell& cell, std::vector& refs, std::vector& refIDs, ESM::ReadersCache& readers) { // TODO: we have many similar copies of this code. for (size_t i = 0; i < cell.mContextList.size(); i++) { const std::size_t index = static_cast(cell.mContextList[i].index); const ESM::ReadersCache::BusyItem reader = readers.get(index); cell.restore(*reader, i); ESM::CellRef ref; ref.mRefNum.unset(); bool deleted = false; while (cell.getNextRef(*reader, ref, deleted)) { if(deleted) refs.emplace_back(ref.mRefNum, deletedRefID); else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end()) { refs.emplace_back(ref.mRefNum, refIDs.size()); refIDs.push_back(std::move(ref.mRefID)); } } } for(const auto& [value, deleted] : cell.mLeasedRefs) { if(deleted) refs.emplace_back(value.mRefNum, deletedRefID); else { refs.emplace_back(value.mRefNum, refIDs.size()); refIDs.push_back(value.mRefID); } } } const std::string& getDefaultClass(const MWWorld::Store& classes) { auto it = classes.begin(); if (it != classes.end()) return it->mId; throw std::runtime_error("List of NPC classes is empty!"); } std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found const std::string& defaultCls = getDefaultClass(classes); // Validate NPCs for non-existing class and faction. // We will replace invalid entries by fixed ones std::vector npcsToReplace; for (const auto& npcIter : npcs) { ESM::NPC npc = npcIter.second; bool changed = false; const std::string& npcFaction = npc.mFaction; if (!npcFaction.empty()) { const ESM::Faction *fact = factions.search(npcFaction); if (!fact) { Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; npc.mFaction.clear(); npc.mNpdt.mRank = 0; changed = true; } } const std::string& npcClass = npc.mClass; const ESM::Class *cls = classes.search(npcClass); if (!cls) { Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; npc.mClass = defaultCls; changed = true; } if (changed) npcsToReplace.push_back(npc); } return npcsToReplace; } // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no longer exists however. // So instead of removing the item altogether, we're only removing the script. template void removeMissingScripts(const MWWorld::Store& scripts, MapT& items) { for(auto& [id, item] : items) { if(!item.mScript.empty() && !scripts.search(item.mScript)) { item.mScript.clear(); Log(Debug::Verbose) << "Item '" << id << "' (" << item.mName << ") has nonexistent script '" << item.mScript << "', ignoring it."; } } } } namespace MWWorld { static bool isCacheableRecord(int id) { if (id == ESM::REC_ACTI || id == ESM::REC_ALCH || id == ESM::REC_APPA || id == ESM::REC_ARMO || id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA || id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI || id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ || id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP || id == ESM::REC_BODY) { return true; } return false; } void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialogue*& dialogue) { if (listener != nullptr) listener->setProgressRange(::EsmLoader::fileProgress); // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here so we can properly verify if valid plugin // indices are being passed to the LandTexture Store retrieval methods. mLandTextures.resize(esm.getIndex()+1); // Loop through all records while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); esm.getRecHeader(); if (esm.getRecordFlags() & ESM::FLAG_Ignored) { esm.skipRecord(); continue; } // Look up the record type. std::map::iterator it = mStores.find(n.toInt()); if (it == mStores.end()) { if (n.toInt() == ESM::REC_INFO) { if (dialogue) { dialogue->readInfo(esm, esm.getIndex() != 0); } else { Log(Debug::Error) << "Error: info record without dialog"; esm.skipRecord(); } } else if (n.toInt() == ESM::REC_MGEF) { mMagicEffects.load (esm); } else if (n.toInt() == ESM::REC_SKIL) { mSkills.load (esm); } else if (n.toInt() == ESM::REC_FILT || n.toInt() == ESM::REC_DBGP) { // ignore project file only records esm.skipRecord(); } else if (n.toInt() == ESM::REC_LUAL) { ESM::LuaScriptsCfg cfg; cfg.load(esm); cfg.adjustRefNums(esm); mLuaContent.push_back(std::move(cfg)); } else { throw std::runtime_error("Unknown record: " + n.toString()); } } else { RecordId id = it->second->load(esm); if (id.mIsDeleted) { it->second->eraseStatic(id.mId); continue; } if (n.toInt() == ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = nullptr; } } if (listener != nullptr) listener->setProgress(::EsmLoader::fileProgress * esm.getFileOffset() / esm.getFileSize()); } } ESM::LuaScriptsCfg ESMStore::getLuaScriptsCfg() const { ESM::LuaScriptsCfg cfg; for (const LuaContent& c : mLuaContent) { if (std::holds_alternative(c)) { // *.omwscripts are intentionally reloaded every time when `getLuaScriptsCfg` is called. // It is important for the `reloadlua` console command. try { auto file = boost::filesystem::ifstream(std::get(c)); std::string fileContent(std::istreambuf_iterator(file), {}); LuaUtil::parseOMWScripts(cfg, fileContent); } catch (std::exception& e) { Log(Debug::Error) << e.what(); } } else { const ESM::LuaScriptsCfg& addition = std::get(c); cfg.mScripts.insert(cfg.mScripts.end(), addition.mScripts.begin(), addition.mScripts.end()); } } return cfg; } void ESMStore::setUp() { mIds.clear(); std::map::iterator storeIt = mStores.begin(); for (; storeIt != mStores.end(); ++storeIt) { storeIt->second->setUp(); if (isCacheableRecord(storeIt->first)) { std::vector identifiers; storeIt->second->listIdentifier(identifiers); for (std::vector::const_iterator record = identifiers.begin(); record != identifiers.end(); ++record) mIds[*record] = storeIt->first; } } if (mStaticIds.empty()) for (const auto& [k, v] : mIds) mStaticIds.emplace(Misc::StringUtils::lowerCase(k), v); mSkills.setUp(); mMagicEffects.setUp(); mAttributes.setUp(); mDialogs.setUp(); } void ESMStore::validateRecords(ESM::ReadersCache& readers) { validate(); countAllCellRefs(readers); } void ESMStore::countAllCellRefs(ESM::ReadersCache& readers) { // TODO: We currently need to read entire files here again. // We should consider consolidating or deferring this reading. if(!mRefCount.empty()) return; std::vector refs; std::vector refIDs; for(auto it = mCells.intBegin(); it != mCells.intEnd(); ++it) readRefs(*it, refs, refIDs, readers); for(auto it = mCells.extBegin(); it != mCells.extEnd(); ++it) readRefs(*it, refs, refIDs, readers); const auto lessByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; }; std::stable_sort(refs.begin(), refs.end(), lessByRefNum); const auto equalByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum == r.mRefNum; }; const auto incrementRefCount = [&] (const Ref& value) { if (value.mRefID != deletedRefID) { std::string& refId = refIDs[value.mRefID]; // We manually lower case IDs here for the time being to improve performance. Misc::StringUtils::lowerCaseInPlace(refId); ++mRefCount[std::move(refId)]; } }; Misc::forEachUnique(refs.rbegin(), refs.rend(), equalByRefNum, incrementRefCount); } int ESMStore::getRefCount(const std::string& id) const { const std::string lowerId = Misc::StringUtils::lowerCase(id); auto it = mRefCount.find(lowerId); if(it == mRefCount.end()) return 0; return it->second; } void ESMStore::validate() { std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic); for (const ESM::NPC &npc : npcsToReplace) { mNpcs.eraseStatic(npc.mId); mNpcs.insertStatic(npc); } // Validate spell effects for invalid arguments std::vector spellsToReplace; for (ESM::Spell spell : mSpells) { if (spell.mEffects.mList.empty()) continue; bool changed = false; auto iter = spell.mEffects.mList.begin(); while (iter != spell.mEffects.mList.end()) { const ESM::MagicEffect* mgef = mMagicEffects.search(iter->mEffectID); if (!mgef) { Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " << iter->mEffectID << ") present. Dropping the effect."; iter = spell.mEffects.mList.erase(iter); changed = true; continue; } if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) { if (iter->mAttribute != -1) { iter->mAttribute = -1; Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has an attribute argument present. Dropping the argument."; changed = true; } } else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) { if (iter->mSkill != -1) { iter->mSkill = -1; Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has a skill argument present. Dropping the argument."; changed = true; } } else if (iter->mSkill != -1 || iter->mAttribute != -1) { iter->mSkill = -1; iter->mAttribute = -1; Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has argument(s) present. Dropping the argument(s)."; changed = true; } ++iter; } if (changed) spellsToReplace.emplace_back(spell); } for (const ESM::Spell &spell : spellsToReplace) { mSpells.eraseStatic(spell.mId); mSpells.insertStatic(spell); } } void ESMStore::validateDynamic() { std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic); for (const ESM::NPC &npc : npcsToReplace) mNpcs.insert(npc); removeMissingScripts(mScripts, mArmors.mDynamic); removeMissingScripts(mScripts, mBooks.mDynamic); removeMissingScripts(mScripts, mClothes.mDynamic); removeMissingScripts(mScripts, mWeapons.mDynamic); removeMissingObjects(mCreatureLists); removeMissingObjects(mItemLists); } // Leveled lists can be modified by scripts. This removes items that no longer exist (presumably because the plugin was removed) from modified lists template void ESMStore::removeMissingObjects(Store& store) { for(auto& entry : store.mDynamic) { auto first = std::remove_if(entry.second.mList.begin(), entry.second.mList.end(), [&] (const auto& item) { if(!find(item.mId)) { Log(Debug::Verbose) << "Leveled list '" << entry.first << "' has nonexistent object '" << item.mId << "', ignoring it."; return true; } return false; }); entry.second.mList.erase(first, entry.second.mList.end()); } } int ESMStore::countSavedGameRecords() const { return 1 // DYNA (dynamic name counter) +mPotions.getDynamicSize() +mArmors.getDynamicSize() +mBooks.getDynamicSize() +mClasses.getDynamicSize() +mClothes.getDynamicSize() +mEnchants.getDynamicSize() +mNpcs.getDynamicSize() +mSpells.getDynamicSize() +mWeapons.getDynamicSize() +mCreatureLists.getDynamicSize() +mItemLists.getDynamicSize() +mCreatures.getDynamicSize() +mContainers.getDynamicSize(); } void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { writer.startRecord(ESM::REC_DYNA); writer.startSubRecord("COUN"); writer.writeT(mDynamicCount); writer.endRecord("COUN"); writer.endRecord(ESM::REC_DYNA); mPotions.write (writer, progress); mArmors.write (writer, progress); mBooks.write (writer, progress); mClasses.write (writer, progress); mClothes.write (writer, progress); mEnchants.write (writer, progress); mSpells.write (writer, progress); mWeapons.write (writer, progress); mNpcs.write (writer, progress); mItemLists.write (writer, progress); mCreatureLists.write (writer, progress); mCreatures.write (writer, progress); mContainers.write (writer, progress); } bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type) { switch (type) { case ESM::REC_ALCH: case ESM::REC_ARMO: case ESM::REC_BOOK: case ESM::REC_CLAS: case ESM::REC_CLOT: case ESM::REC_ENCH: case ESM::REC_SPEL: case ESM::REC_WEAP: case ESM::REC_LEVI: case ESM::REC_LEVC: mStores[type]->read (reader); return true; case ESM::REC_NPC_: case ESM::REC_CREA: case ESM::REC_CONT: mStores[type]->read (reader, true); return true; case ESM::REC_DYNA: reader.getSubNameIs("COUN"); reader.getHT(mDynamicCount); return true; default: return false; } } void ESMStore::checkPlayer() { setUp(); const ESM::NPC *player = mNpcs.find ("player"); if (!mRaces.find (player->mRace) || !mClasses.find (player->mClass)) throw std::runtime_error ("Invalid player record (race or class unavailable"); } std::pair, bool> ESMStore::getSpellList(const std::string& id) const { auto result = mSpellListCache.find(id); std::shared_ptr ptr; if (result != mSpellListCache.end()) ptr = result->second.lock(); if (!ptr) { int type = find(id); ptr = std::make_shared(id, type); if (result != mSpellListCache.end()) result->second = ptr; else mSpellListCache.insert({id, ptr}); return {ptr, false}; } return {ptr, true}; } } // end namespace openmw-openmw-0.48.0/apps/openmw/mwworld/esmstore.hpp000066400000000000000000000400621445372753700227530ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H #include #include #include #include #include #include "store.hpp" namespace Loading { class Listener; } namespace MWMechanics { class SpellList; } namespace ESM { class ReadersCache; } namespace MWWorld { class ESMStore { Store mActivators; Store mPotions; Store mAppas; Store mArmors; Store mBodyParts; Store mBooks; Store mBirthSigns; Store mClasses; Store mClothes; Store mContainers; Store mCreatures; Store mDialogs; Store mDoors; Store mEnchants; Store mFactions; Store mGlobals; Store mIngreds; Store mCreatureLists; Store mItemLists; Store mLights; Store mLockpicks; Store mMiscItems; Store mNpcs; Store mProbes; Store mRaces; Store mRegions; Store mRepairs; Store mSoundGens; Store mSounds; Store mSpells; Store mStartScripts; Store mStatics; Store mWeapons; Store mGameSettings; Store mScripts; // Lists that need special rules Store mCells; Store mLands; Store mLandTextures; Store mPathgrids; Store mMagicEffects; Store mSkills; // Special entry which is hardcoded and not loaded from an ESM Store mAttributes; // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. using IDMap = std::unordered_map; IDMap mIds; std::unordered_map mStaticIds; std::unordered_map mRefCount; std::map mStores; unsigned int mDynamicCount; mutable std::unordered_map, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> mSpellListCache; /// Validate entries in store after setup void validate(); void countAllCellRefs(ESM::ReadersCache& readers); template void removeMissingObjects(Store& store); using LuaContent = std::variant< ESM::LuaScriptsCfg, // data from an omwaddon std::string>; // path to an omwscripts file std::vector mLuaContent; public: void addOMWScripts(std::string filePath) { mLuaContent.push_back(std::move(filePath)); } ESM::LuaScriptsCfg getLuaScriptsCfg() const; /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; iterator begin() const { return mStores.begin(); } iterator end() const { return mStores.end(); } /// Look up the given ID in 'all'. Returns 0 if not found. int find(const std::string &id) const { IDMap::const_iterator it = mIds.find(id); if (it == mIds.end()) { return 0; } return it->second; } int findStatic(const std::string &id) const { IDMap::const_iterator it = mStaticIds.find(id); if (it == mStaticIds.end()) { return 0; } return it->second; } ESMStore() : mDynamicCount(0) { mStores[ESM::REC_ACTI] = &mActivators; mStores[ESM::REC_ALCH] = &mPotions; mStores[ESM::REC_APPA] = &mAppas; mStores[ESM::REC_ARMO] = &mArmors; mStores[ESM::REC_BODY] = &mBodyParts; mStores[ESM::REC_BOOK] = &mBooks; mStores[ESM::REC_BSGN] = &mBirthSigns; mStores[ESM::REC_CELL] = &mCells; mStores[ESM::REC_CLAS] = &mClasses; mStores[ESM::REC_CLOT] = &mClothes; mStores[ESM::REC_CONT] = &mContainers; mStores[ESM::REC_CREA] = &mCreatures; mStores[ESM::REC_DIAL] = &mDialogs; mStores[ESM::REC_DOOR] = &mDoors; mStores[ESM::REC_ENCH] = &mEnchants; mStores[ESM::REC_FACT] = &mFactions; mStores[ESM::REC_GLOB] = &mGlobals; mStores[ESM::REC_GMST] = &mGameSettings; mStores[ESM::REC_INGR] = &mIngreds; mStores[ESM::REC_LAND] = &mLands; mStores[ESM::REC_LEVC] = &mCreatureLists; mStores[ESM::REC_LEVI] = &mItemLists; mStores[ESM::REC_LIGH] = &mLights; mStores[ESM::REC_LOCK] = &mLockpicks; mStores[ESM::REC_LTEX] = &mLandTextures; mStores[ESM::REC_MISC] = &mMiscItems; mStores[ESM::REC_NPC_] = &mNpcs; mStores[ESM::REC_PGRD] = &mPathgrids; mStores[ESM::REC_PROB] = &mProbes; mStores[ESM::REC_RACE] = &mRaces; mStores[ESM::REC_REGN] = &mRegions; mStores[ESM::REC_REPA] = &mRepairs; mStores[ESM::REC_SCPT] = &mScripts; mStores[ESM::REC_SNDG] = &mSoundGens; mStores[ESM::REC_SOUN] = &mSounds; mStores[ESM::REC_SPEL] = &mSpells; mStores[ESM::REC_SSCR] = &mStartScripts; mStores[ESM::REC_STAT] = &mStatics; mStores[ESM::REC_WEAP] = &mWeapons; mPathgrids.setCells(mCells); } void clearDynamic () { for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); movePlayerRecord(); } void movePlayerRecord () { auto player = mNpcs.find("player"); mNpcs.insert(*player); } /// Validate entries in store after loading a save void validateDynamic(); void load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); template const Store &get() const { throw std::runtime_error("Storage for this type not exist"); } /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) template const T *insert(const T &x) { const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); if (store.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); } T record = x; record.mId = id; T *ptr = store.insert(record); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { if (it->second == &store) { mIds[ptr->mId] = it->first; } } return ptr; } /// Insert a record with set ID, and allow it to override a pre-existing static record. template const T *overrideRecord(const T &x) { Store &store = const_cast &>(get()); T *ptr = store.insert(x); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { if (it->second == &store) { mIds[ptr->mId] = it->first; } } return ptr; } template const T *insertStatic(const T &x) { const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); if (store.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); } T record = x; T *ptr = store.insertStatic(record); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { if (it->second == &store) { mIds[ptr->mId] = it->first; } } return ptr; } // This method must be called once, after loading all master/plugin files. This can only be done // from the outside, so it must be public. void setUp(); void validateRecords(ESM::ReadersCache& readers); int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type); ///< \return Known type? // To be called when we are done with dynamic record loading void checkPlayer(); /// @return The number of instances defined in the base files. Excludes changes from the save file. int getRefCount(const std::string& id) const; /// Actors with the same ID share spells, abilities, etc. /// @return The shared spell list to use for this actor and whether or not it has already been initialized. std::pair, bool> getSpellList(const std::string& id) const; }; template <> inline const ESM::Cell *ESMStore::insert(const ESM::Cell &cell) { return mCells.insert(cell); } template <> inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) { const std::string id = "$dynamic" + std::to_string(mDynamicCount++); if (Misc::StringUtils::ciEqual(npc.mId, "player")) { return mNpcs.insert(npc); } else if (mNpcs.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); } ESM::NPC record = npc; record.mId = id; ESM::NPC *ptr = mNpcs.insert(record); mIds[ptr->mId] = ESM::REC_NPC_; return ptr; } template <> inline const Store &ESMStore::get() const { return mActivators; } template <> inline const Store &ESMStore::get() const { return mPotions; } template <> inline const Store &ESMStore::get() const { return mAppas; } template <> inline const Store &ESMStore::get() const { return mArmors; } template <> inline const Store &ESMStore::get() const { return mBodyParts; } template <> inline const Store &ESMStore::get() const { return mBooks; } template <> inline const Store &ESMStore::get() const { return mBirthSigns; } template <> inline const Store &ESMStore::get() const { return mClasses; } template <> inline const Store &ESMStore::get() const { return mClothes; } template <> inline const Store &ESMStore::get() const { return mContainers; } template <> inline const Store &ESMStore::get() const { return mCreatures; } template <> inline const Store &ESMStore::get() const { return mDialogs; } template <> inline const Store &ESMStore::get() const { return mDoors; } template <> inline const Store &ESMStore::get() const { return mEnchants; } template <> inline const Store &ESMStore::get() const { return mFactions; } template <> inline const Store &ESMStore::get() const { return mGlobals; } template <> inline const Store &ESMStore::get() const { return mIngreds; } template <> inline const Store &ESMStore::get() const { return mCreatureLists; } template <> inline const Store &ESMStore::get() const { return mItemLists; } template <> inline const Store &ESMStore::get() const { return mLights; } template <> inline const Store &ESMStore::get() const { return mLockpicks; } template <> inline const Store &ESMStore::get() const { return mMiscItems; } template <> inline const Store &ESMStore::get() const { return mNpcs; } template <> inline const Store &ESMStore::get() const { return mProbes; } template <> inline const Store &ESMStore::get() const { return mRaces; } template <> inline const Store &ESMStore::get() const { return mRegions; } template <> inline const Store &ESMStore::get() const { return mRepairs; } template <> inline const Store &ESMStore::get() const { return mSoundGens; } template <> inline const Store &ESMStore::get() const { return mSounds; } template <> inline const Store &ESMStore::get() const { return mSpells; } template <> inline const Store &ESMStore::get() const { return mStartScripts; } template <> inline const Store &ESMStore::get() const { return mStatics; } template <> inline const Store &ESMStore::get() const { return mWeapons; } template <> inline const Store &ESMStore::get() const { return mGameSettings; } template <> inline const Store &ESMStore::get() const { return mScripts; } template <> inline const Store &ESMStore::get() const { return mCells; } template <> inline const Store &ESMStore::get() const { return mLands; } template <> inline const Store &ESMStore::get() const { return mLandTextures; } template <> inline const Store &ESMStore::get() const { return mPathgrids; } template <> inline const Store &ESMStore::get() const { return mMagicEffects; } template <> inline const Store &ESMStore::get() const { return mSkills; } template <> inline const Store &ESMStore::get() const { return mAttributes; } } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/failedaction.cpp000066400000000000000000000007771445372753700235400ustar00rootroot00000000000000#include "failedaction.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { FailedAction::FailedAction(const std::string &msg, const Ptr& target) : Action(false, target), mMessage(msg) { } void FailedAction::executeImp(const Ptr &actor) { if(actor == MWMechanics::getPlayer() && !mMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(mMessage); } } openmw-openmw-0.48.0/apps/openmw/mwworld/failedaction.hpp000066400000000000000000000006021445372753700235300ustar00rootroot00000000000000#ifndef GAME_MWWORLD_FAILEDACTION_H #define GAME_MWWORLD_FAILEDACTION_H #include "action.hpp" #include "ptr.hpp" namespace MWWorld { class FailedAction : public Action { std::string mMessage; void executeImp(const Ptr &actor) override; public: FailedAction(const std::string &message = std::string(), const Ptr& target = Ptr()); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/globals.cpp000066400000000000000000000060611445372753700225310ustar00rootroot00000000000000#include "globals.hpp" #include #include #include #include #include "esmstore.hpp" namespace MWWorld { Globals::Collection::const_iterator Globals::find (std::string_view name) const { Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + std::string{name}); return iter; } Globals::Collection::iterator Globals::find (std::string_view name) { Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + std::string{name}); return iter; } void Globals::fill (const MWWorld::ESMStore& store) { mVariables.clear(); const MWWorld::Store& globals = store.get(); for (const ESM::Global& esmGlobal : globals) { mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (esmGlobal.mId), esmGlobal)); } } const ESM::Variant& Globals::operator[] (std::string_view name) const { return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (std::string_view name) { return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (std::string_view name) const { Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) return ' '; switch (iter->second.mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; case ESM::VT_Float: return 'f'; default: return ' '; } } int Globals::countSavedGameRecords() const { return mVariables.size(); } void Globals::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { writer.startRecord (ESM::REC_GLOB); iter->second.save (writer); writer.endRecord (ESM::REC_GLOB); } } bool Globals::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_GLOB) { ESM::Global global; bool isDeleted = false; // This readRecord() method is used when reading a saved game. // Deleted globals can't appear there, so isDeleted will be ignored here. global.load(reader, isDeleted); Misc::StringUtils::lowerCaseInPlace(global.mId); Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) iter->second = global; return true; } return false; } } openmw-openmw-0.48.0/apps/openmw/mwworld/globals.hpp000066400000000000000000000026001445372753700225310ustar00rootroot00000000000000#ifndef GAME_MWWORLD_GLOBALS_H #define GAME_MWWORLD_GLOBALS_H #include #include #include #include #include namespace ESM { class ESMWriter; class ESMReader; } namespace Loading { class Listener; } namespace MWWorld { class ESMStore; class Globals { private: typedef std::map Collection; Collection mVariables; // type, value Collection::const_iterator find (std::string_view name) const; Collection::iterator find (std::string_view name); public: const ESM::Variant& operator[] (std::string_view name) const; ESM::Variant& operator[] (std::string_view name); char getType (std::string_view name) const; ///< If there is no global variable with this name, ' ' is returned. void fill (const MWWorld::ESMStore& store); ///< Replace variables with variables from \a store with default values. int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type); ///< Records for variables that do not exist are dropped silently. /// /// \return Known type? }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/groundcoverstore.cpp000066400000000000000000000055601445372753700245230ustar00rootroot00000000000000#include "groundcoverstore.hpp" #include #include #include #include #include #include #include #include "store.hpp" namespace MWWorld { void GroundcoverStore::init(const Store& statics, const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { ::EsmLoader::Query query; query.mLoadStatics = true; query.mLoadCells = true; ESM::ReadersCache readers; const ::EsmLoader::EsmData content = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder, listener); const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); static constexpr std::string_view prefix = "grass\\"; for (const ESM::Static& stat : statics) { std::string id = Misc::StringUtils::lowerCase(stat.mId); std::string model = Misc::StringUtils::lowerCase(stat.mModel); std::replace(model.begin(), model.end(), '/', '\\'); if (model.compare(0, prefix.size(), prefix) != 0) continue; mMeshCache[id] = Misc::ResourceHelpers::correctMeshPath(model, vfs); } for (const ESM::Static& stat : content.mStatics) { std::string id = Misc::StringUtils::lowerCase(stat.mId); std::string model = Misc::StringUtils::lowerCase(stat.mModel); std::replace(model.begin(), model.end(), '/', '\\'); if (model.compare(0, prefix.size(), prefix) != 0) continue; mMeshCache[id] = Misc::ResourceHelpers::correctMeshPath(model, vfs); } for (const ESM::Cell& cell : content.mCells) { if (!cell.isExterior()) continue; auto cellIndex = std::make_pair(cell.getCellId().mIndex.mX, cell.getCellId().mIndex.mY); mCellContexts[cellIndex] = std::move(cell.mContextList); } } std::string GroundcoverStore::getGroundcoverModel(const std::string& id) const { std::string idLower = Misc::StringUtils::lowerCase(id); auto search = mMeshCache.find(idLower); if (search == mMeshCache.end()) return std::string(); return search->second; } void GroundcoverStore::initCell(ESM::Cell& cell, int cellX, int cellY) const { cell.blank(); auto searchCell = mCellContexts.find(std::make_pair(cellX, cellY)); if (searchCell != mCellContexts.end()) cell.mContextList = searchCell->second; } } openmw-openmw-0.48.0/apps/openmw/mwworld/groundcoverstore.hpp000066400000000000000000000020031445372753700245150ustar00rootroot00000000000000#ifndef GAME_MWWORLD_GROUNDCOVER_STORE_H #define GAME_MWWORLD_GROUNDCOVER_STORE_H #include #include #include namespace ESM { struct ESM_Context; struct Static; struct Cell; } namespace Loading { class Listener; } namespace Files { class Collections; } namespace ToUTF8 { class Utf8Encoder; } namespace MWWorld { template class Store; class GroundcoverStore { private: std::map mMeshCache; std::map, std::vector> mCellContexts; public: void init(const Store& statics, const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener); std::string getGroundcoverModel(const std::string& id) const; void initCell(ESM::Cell& cell, int cellX, int cellY) const; }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/inventorystore.cpp000066400000000000000000000621301445372753700242170ustar00rootroot00000000000000#include "inventorystore.hpp" #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellresistance.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" #include "esmstore.hpp" #include "class.hpp" void MWWorld::InventoryStore::copySlots (const InventoryStore& store) { // some const-trickery, required because of a flaw in the handling of MW-references and the // resulting workarounds for (std::vector::const_iterator iter ( const_cast (store).mSlots.begin()); iter!=const_cast (store).mSlots.end(); ++iter) { std::size_t distance = std::distance (const_cast (store).begin(), *iter); ContainerStoreIterator slot = begin(); std::advance (slot, distance); mSlots.push_back (slot); } // some const-trickery, required because of a flaw in the handling of MW-references and the // resulting workarounds std::size_t distance = std::distance (const_cast (store).begin(), const_cast (store).mSelectedEnchantItem); ContainerStoreIterator slot = begin(); std::advance (slot, distance); mSelectedEnchantItem = slot; } void MWWorld::InventoryStore::initSlots (TSlots& slots_) { for (int i=0; i (mSlots.size()); ++i) if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) { inventory.mEquipmentSlots[index] = i; } if (mSelectedEnchantItem.getType()!=-1 && mSelectedEnchantItem->getBase() == &ref) inventory.mSelectedEnchantItem = index; } void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIterator &iter, int index, const ESM::InventoryState &inventory) { if (index == inventory.mSelectedEnchantItem) mSelectedEnchantItem = iter; std::map::const_iterator found = inventory.mEquipmentSlots.find(index); if (found != inventory.mEquipmentSlots.end()) { if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots) throw std::runtime_error("Invalid slot index in inventory state"); // make sure the item can actually be equipped in this slot int slot = found->second; std::pair, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); if (!allowedSlots.first.size()) return; if (std::find(allowedSlots.first.begin(), allowedSlots.first.end(), slot) == allowedSlots.first.end()) slot = allowedSlots.first.front(); // unstack if required if (!allowedSlots.second && iter->getRefData().getCount() > 1) { int count = iter->getRefData().getCount(false); MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1); iter->getRefData().setCount(subtractItems(count, 1)); mSlots[slot] = newIter; } else mSlots[slot] = iter; } } MWWorld::InventoryStore::InventoryStore() : ContainerStore() , mInventoryListener(nullptr) , mUpdatesEnabled (true) , mFirstAutoEquip(true) , mSelectedEnchantItem(end()) { initSlots (mSlots); } MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) , mInventoryListener(store.mInventoryListener) , mUpdatesEnabled(store.mUpdatesEnabled) , mFirstAutoEquip(store.mFirstAutoEquip) , mSelectedEnchantItem(end()) { copySlots (store); } MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { if (this == &store) return *this; mListener = store.mListener; mInventoryListener = store.mInventoryListener; mFirstAutoEquip = store.mFirstAutoEquip; mRechargingItemsUpToDate = false; ContainerStore::operator= (store); mSlots.clear(); copySlots (store); return *this; } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip, bool resolve) { const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip, resolve); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) { auto type = itemPtr.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) autoEquip(actorPtr); } if (mListener) mListener->itemAdded(*retVal, count); return retVal; } void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor) { if (iterator == end()) throw std::runtime_error ("can't equip end() iterator, use unequip function instead"); if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); if (iterator.getContainerStore()!=this) throw std::runtime_error ("attempt to equip an item that is not in the inventory"); std::pair, bool> slots_; slots_ = iterator->getClass().getEquipmentSlots (*iterator); if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) throw std::runtime_error ("invalid slot"); if (mSlots[slot] != end()) unequipSlot(slot, actor); // unstack item pointed to by iterator if required if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { unstack(*iterator, actor); } mSlots[slot] = iterator; flagAsModified(); fireEquipmentChangedEvent(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) { mUpdatesEnabled = false; for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) unequipSlot(slot, actor); mUpdatesEnabled = true; fireEquipmentChangedEvent(actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) { return findSlot (slot); } MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) const { return findSlot (slot); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) const { if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); if (mSlots[slot]==end()) return mSlots[slot]; if (mSlots[slot]->getRefData().getCount()<1) { // Object has been deleted // This should no longer happen, since the new remove function will unequip first throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object"); } return mSlots[slot]; } void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots& slots_) { if (!actor.getClass().isNpc()) { // In original game creatures do not autoequip weapon, but we need it for weapon sheathing. // The only case when the difference is noticable - when this creature sells weapon. // So just disable weapon autoequipping for creatures which sells weapon. int services = actor.getClass().getServices(actor); bool sellsWeapon = services & (ESM::NPC::Weapon|ESM::NPC::MagicItems); if (sellsWeapon) return; } static const ESM::Skill::SkillEnum weaponSkills[] = { ESM::Skill::LongBlade, ESM::Skill::Axe, ESM::Skill::Spear, ESM::Skill::ShortBlade, ESM::Skill::Marksman, ESM::Skill::BluntWeapon }; const size_t weaponSkillsLength = sizeof(weaponSkills) / sizeof(weaponSkills[0]); bool weaponSkillVisited[weaponSkillsLength] = { false }; // give arrows/bolt with max damage by default int arrowMax = 0; int boltMax = 0; ContainerStoreIterator arrow(end()); ContainerStoreIterator bolt(end()); // rate ammo for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter) { const ESM::Weapon* esmWeapon = iter->get()->mBase; if (esmWeapon->mData.mType == ESM::Weapon::Arrow) { if (esmWeapon->mData.mChop[1] >= arrowMax) { arrowMax = esmWeapon->mData.mChop[1]; arrow = iter; } } else if (esmWeapon->mData.mType == ESM::Weapon::Bolt) { if (esmWeapon->mData.mChop[1] >= boltMax) { boltMax = esmWeapon->mData.mChop[1]; bolt = iter; } } } // rate weapon for (int i = 0; i < static_cast(weaponSkillsLength); ++i) { float max = 0; int maxWeaponSkill = -1; for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { float skillValue = actor.getClass().getSkill(actor, static_cast(weaponSkills[j])); if (skillValue > max && !weaponSkillVisited[j]) { max = skillValue; maxWeaponSkill = j; } } if (maxWeaponSkill == -1) break; max = 0; ContainerStoreIterator weapon(end()); for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter) { const ESM::Weapon* esmWeapon = iter->get()->mBase; if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo) continue; if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill]) { if (esmWeapon->mData.mChop[1] >= max) { max = esmWeapon->mData.mChop[1]; weapon = iter; } if (esmWeapon->mData.mSlash[1] >= max) { max = esmWeapon->mData.mSlash[1]; weapon = iter; } if (esmWeapon->mData.mThrust[1] >= max) { max = esmWeapon->mData.mThrust[1]; weapon = iter; } } } if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; const MWWorld::LiveCellRef *ref = weapon->get(); int type = ref->mBase->mData.mType; int ammotype = MWMechanics::getWeaponType(type)->mAmmoType; if (ammotype == ESM::Weapon::Arrow) { if (arrow == end()) hasAmmo = false; else slots_[Slot_Ammunition] = arrow; } else if (ammotype == ESM::Weapon::Bolt) { if (bolt == end()) hasAmmo = false; else slots_[Slot_Ammunition] = bolt; } if (hasAmmo) { std::pair, bool> itemsSlots = weapon->getClass().getEquipmentSlots (*weapon); if (!itemsSlots.first.empty()) { if (!itemsSlots.second) { if (weapon->getRefData().getCount() > 1) { unstack(*weapon, actor); } } int slot = itemsSlots.first.front(); slots_[slot] = weapon; if (ammotype == ESM::Weapon::None) slots_[Slot_Ammunition] = end(); } break; } } weaponSkillVisited[maxWeaponSkill] = true; } } void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& slots_) { // Only NPCs can wear armor for now. // For creatures we equip only shields. if (!actor.getClass().isNpc()) { autoEquipShield(actor, slots_); return; } const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter) { Ptr test = *iter; switch(test.getClass().canBeEquipped (test, actor).first) { case 0: continue; default: break; } if (iter.getType() == ContainerStore::Type_Armor && test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f)) { continue; } std::pair, bool> itemsSlots = iter->getClass().getEquipmentSlots (*iter); // checking if current item pointed by iter can be equipped for (int slot : itemsSlots.first) { // if true then it means slot is equipped already // check if slot may require swapping if current item is more valuable if (slots_.at (slot)!=end()) { Ptr old = *slots_.at (slot); if (iter.getType() == ContainerStore::Type_Armor) { if (old.getType() == ESM::Armor::sRecordId) { if (old.get()->mBase->mData.mType < test.get()->mBase->mData.mType) continue; if (old.get()->mBase->mData.mType == test.get()->mBase->mData.mType) { if (old.getClass().getEffectiveArmorRating(old, actor) >= test.getClass().getEffectiveArmorRating(test, actor)) // old armor had better armor rating continue; } } // suitable armor should replace already equipped clothing } else if (iter.getType() == ContainerStore::Type_Clothing) { // if left ring is equipped if (slot == Slot_LeftRing) { // if there is a place for right ring dont swap it if (slots_.at(Slot_RightRing) == end()) { continue; } else // if right ring is equipped too { Ptr rightRing = *slots_.at(Slot_RightRing); // we want to swap cheaper ring only if both are equipped if (old.getClass().getValue (old) >= rightRing.getClass().getValue (rightRing)) continue; } } if (old.getType() == ESM::Clothing::sRecordId) { // check value if (old.getClass().getValue (old) >= test.getClass().getValue (test)) // old clothing was more valuable continue; } else // suitable clothing should NOT replace already equipped armor continue; } } if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped { // unstack item pointed to by iterator if required if (iter->getRefData().getCount() > 1) { unstack(*iter, actor); } } // if we are here it means item can be equipped or swapped slots_[slot] = iter; break; } } } void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_) { for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter) { if (iter->get()->mBase->mData.mType != ESM::Armor::Shield) continue; if (iter->getClass().canBeEquipped(*iter, actor).first != 1) continue; std::pair, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter); int slot = shieldSlots.first[0]; const ContainerStoreIterator& shield = slots_[slot]; if (shield != end() && shield.getType() == Type_Armor && shield->get()->mBase->mData.mType == ESM::Armor::Shield) { if (shield->getClass().getItemHealth(*shield) >= iter->getClass().getItemHealth(*iter)) continue; } slots_[slot] = iter; } } void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { TSlots slots_; initSlots (slots_); // Disable model update during auto-equip mUpdatesEnabled = false; // Autoequip clothing, armor and weapons. // Equipping lights is handled in Actors::updateEquippedLight based on environment light. // Note: creatures ignore equipment armor rating and only equip shields // Use custom logic for them - select shield based on its health instead of armor rating autoEquipWeapon(actor, slots_); autoEquipArmor(actor, slots_); bool changed = false; for (std::size_t i=0; igetClass().getEquipmentSlots(**iter).second; if (!stackWhenEquipped) return false; } } return true; } void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator) { mSelectedEnchantItem = iterator; } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem() { return mSelectedEnchantItem; } int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolve) { int retCount = ContainerStore::remove(item, count, actor, equipReplacement, resolve); bool wasEquipped = false; if (!item.getRefData().getCount()) { for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) { if (mSlots[slot] == end()) continue; if (*mSlots[slot] == item) { unequipSlot(slot, actor); wasEquipped = true; break; } } } // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves, and not if the RemoveItem script command // was used (equipReplacement is false) if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf()) { auto type = item.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) autoEquip(actor); } if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) { mSelectedEnchantItem = end(); } if (mListener) mListener->itemRemoved(item, retCount); return retCount; } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool applyUpdates) { if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); ContainerStoreIterator it = mSlots[slot]; if (it != end()) { ContainerStoreIterator retval = it; // empty this slot mSlots[slot] = end(); if (it->getRefData().getCount()) { retval = restack(*it); if (actor == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared const std::string& script = it->getClass().getScript(*it); if (script != "") (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); } if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) { mSelectedEnchantItem = end(); } } if (applyUpdates) { fireEquipmentChangedEvent(actor); } return retval; } return it; } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor) { for (int slot=0; slot item.getRefData().getCount()) throw std::runtime_error ("attempt to unequip more items than equipped"); if (count == item.getRefData().getCount()) return unequipItem(item, actor); // Move items to an existing stack if possible, otherwise split count items out into a new stack. // Moving counts manually here, since ContainerStore's restack can't target unequipped stacks. for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { if (stacks(*iter, item) && !isEquipped(*iter)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count)); return iter; } } return unstack(item, actor, item.getRefData().getCount() - count); } MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const { return mInventoryListener; } void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor) { mInventoryListener = listener; } void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) { if (!mUpdatesEnabled) return; if (mInventoryListener) mInventoryListener->equipmentChanged(); // if player, update inventory window /* if (actor == MWMechanics::getPlayer()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } */ } void MWWorld::InventoryStore::clear() { mSlots.clear(); initSlots (mSlots); ContainerStore::clear(); } bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) { for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) { if (getSlot(i) != end() && *getSlot(i) == item) return true; } return false; } bool MWWorld::InventoryStore::isFirstEquip() { bool first = mFirstAutoEquip; mFirstAutoEquip = false; return first; } openmw-openmw-0.48.0/apps/openmw/mwworld/inventorystore.hpp000066400000000000000000000165101445372753700242250ustar00rootroot00000000000000#ifndef GAME_MWWORLD_INVENTORYSTORE_H #define GAME_MWWORLD_INVENTORYSTORE_H #include "containerstore.hpp" #include "../mwmechanics/magiceffects.hpp" namespace ESM { struct MagicEffect; } namespace MWMechanics { class NpcStats; } namespace MWWorld { class InventoryStoreListener { public: /** * Fired when items are equipped or unequipped */ virtual void equipmentChanged () {} virtual ~InventoryStoreListener() = default; }; ///< \brief Variant of the ContainerStore for NPCs class InventoryStore : public ContainerStore { public: static constexpr int Slot_Helmet = 0; static constexpr int Slot_Cuirass = 1; static constexpr int Slot_Greaves = 2; static constexpr int Slot_LeftPauldron = 3; static constexpr int Slot_RightPauldron = 4; static constexpr int Slot_LeftGauntlet = 5; static constexpr int Slot_RightGauntlet = 6; static constexpr int Slot_Boots = 7; static constexpr int Slot_Shirt = 8; static constexpr int Slot_Pants = 9; static constexpr int Slot_Skirt = 10; static constexpr int Slot_Robe = 11; static constexpr int Slot_LeftRing = 12; static constexpr int Slot_RightRing = 13; static constexpr int Slot_Amulet = 14; static constexpr int Slot_Belt = 15; static constexpr int Slot_CarriedRight = 16; static constexpr int Slot_CarriedLeft = 17; static constexpr int Slot_Ammunition = 18; static constexpr int Slots = 19; static constexpr int Slot_NoSlot = -1; private: InventoryStoreListener* mInventoryListener; // Enables updates of magic effects and actor model whenever items are equipped or unequipped. // This is disabled during autoequip to avoid excessive updates bool mUpdatesEnabled; bool mFirstAutoEquip; typedef std::vector TSlots; TSlots mSlots; void autoEquipWeapon(const MWWorld::Ptr& actor, TSlots& slots_); void autoEquipArmor(const MWWorld::Ptr& actor, TSlots& slots_); void autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_); // selected magic item (for using enchantments of type "Cast once" or "Cast when used") ContainerStoreIterator mSelectedEnchantItem; void copySlots (const InventoryStore& store); void initSlots (TSlots& slots_); void fireEquipmentChangedEvent(const Ptr& actor); void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; ContainerStoreIterator findSlot (int slot) const; public: InventoryStore(); InventoryStore (const InventoryStore& store); InventoryStore& operator= (const InventoryStore& store); std::unique_ptr clone() override { return std::make_unique(*this); } ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override; ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). /// /// \note The item pointed to is not required to exist beyond this function call. /// /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \warning \a iterator can not be an end()-iterator, use unequip function instead bool isEquipped(const MWWorld::ConstPtr& item); ///< Utility function, returns true if the given item is equipped in any slot void setSelectedEnchantItem(const ContainerStoreIterator& iterator); ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") /// \note to unset the selected item, call this method with end() iterator ContainerStoreIterator getSelectedEnchantItem(); ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used") /// \note if no item selected, return end() iterator ContainerStoreIterator getSlot (int slot); ConstContainerStoreIterator getSlot(int slot) const; ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor); void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const override; ///< @return true if the two specified objects can stack with each other using ContainerStore::remove; int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true) override; ///< Remove \a count item(s) designated by \a item from this inventory. /// /// @return the number of items actually removed ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ///< Unequip an item identified by its Ptr. An exception is thrown /// if the item is not currently equipped. /// /// @return an iterator to the item that was previously in the slot /// (it can be re-stacked so its count may be different than when it /// was equipped). ContainerStoreIterator unequipItemQuantity(const Ptr& item, const Ptr& actor, int count); ///< Unequip a specific quantity of an item identified by its Ptr. /// An exception is thrown if the item is not currently equipped, /// if count <= 0, or if count > the item stack size. /// /// @return an iterator to the unequipped items that were previously /// in the slot (they can be re-stacked so its count may be different /// than the requested count). void setInvListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener InventoryStoreListener* getInvListener() const; void clear() override; ///< Empty container. bool isFirstEquip(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/livecellref.cpp000066400000000000000000000055511445372753700234050ustar00rootroot00000000000000#include "livecellref.hpp" #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/luamanager.hpp" #include "ptr.hpp" #include "class.hpp" #include "esmstore.hpp" MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef &cref) : mClass(&Class::get(type)), mRef(cref), mData(cref) { } void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) { mRef = state.mRef; mData = RefData (state, mData.isDeletedByContentFile()); Ptr ptr (this); if (state.mHasLocals) { std::string scriptId = mClass->getScript (ptr); // Make sure we still have a script. It could have been coming from a content file that is no longer active. if (!scriptId.empty()) { if (const ESM::Script* script = MWBase::Environment::get().getWorld()->getStore().get().search (scriptId)) { try { mData.setLocals (*script); mData.getLocals().read (state.mLocals, scriptId); } catch (const std::exception& exception) { Log(Debug::Error) << "Error: failed to load state for local script " << scriptId << " because an exception has been thrown: " << exception.what(); } } } } mClass->readAdditionalState (ptr, state); if (!mRef.getSoul().empty() && !MWBase::Environment::get().getWorld()->getStore().get().search(mRef.getSoul())) { Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; mRef.setSoul(std::string()); } MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); } void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const { mRef.writeState(state); ConstPtr ptr (this); mData.write (state, mClass->getScript (ptr)); MWBase::Environment::get().getLuaManager()->saveLocalScripts(Ptr(const_cast(this)), state.mLuaScripts); mClass->writeAdditionalState (ptr, state); } bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) { return true; } unsigned int MWWorld::LiveCellRefBase::getType() const { return mClass->getType(); } namespace MWWorld { std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) { std::stringstream message; message << "Bad LiveCellRef cast to " << recordType << " from "; if (value != nullptr) message << value->getTypeDescription(); else message << "an empty object"; return message.str(); } } openmw-openmw-0.48.0/apps/openmw/mwworld/livecellref.hpp000066400000000000000000000115161445372753700234100ustar00rootroot00000000000000#ifndef GAME_MWWORLD_LIVECELLREF_H #define GAME_MWWORLD_LIVECELLREF_H #include "cellref.hpp" #include "refdata.hpp" #include namespace ESM { struct ObjectState; } namespace MWWorld { class Ptr; class ESMStore; class Class; template struct LiveCellRef; /// Used to create pointers to hold any type of LiveCellRef<> object. struct LiveCellRefBase { const Class *mClass; /** Information about this instance, such as 3D location and rotation * and individual type-dependent data. */ MWWorld::CellRef mRef; /** runtime-data */ RefData mData; LiveCellRefBase(unsigned int type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } virtual void load (const ESM::ObjectState& state) = 0; ///< Load state into a LiveCellRef, that has already been initialised with base and class. /// /// \attention Must not be called with an invalid \a state. virtual void save (ESM::ObjectState& state) const = 0; ///< Save LiveCellRef state into \a state. virtual std::string_view getTypeDescription() const = 0; unsigned int getType() const; ///< @see MWWorld::Class::getType template static const LiveCellRef* dynamicCast(const LiveCellRefBase* value); template static LiveCellRef* dynamicCast(LiveCellRefBase* value); protected: void loadImp (const ESM::ObjectState& state); ///< Load state into a LiveCellRef, that has already been initialised with base and /// class. /// /// \attention Must not be called with an invalid \a state. void saveImp (ESM::ObjectState& state) const; ///< Save LiveCellRef state into \a state. static bool checkStateImp (const ESM::ObjectState& state); ///< Check if state is valid and report errors. /// /// \return Valid? /// /// \note Does not check if the RefId exists. }; inline bool operator== (const LiveCellRefBase& cellRef, const ESM::RefNum refNum) { return cellRef.mRef.getRefNum()==refNum; } std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType); template const LiveCellRef* LiveCellRefBase::dynamicCast(const LiveCellRefBase* value) { if (const LiveCellRef* ref = dynamic_cast*>(value)) return ref; throw std::runtime_error(makeDynamicCastErrorMessage(value, T::getRecordType())); } template LiveCellRef* LiveCellRefBase::dynamicCast(LiveCellRefBase* value) { if (LiveCellRef* ref = dynamic_cast*>(value)) return ref; throw std::runtime_error(makeDynamicCastErrorMessage(value, T::getRecordType())); } /// A reference to one object (of any type) in a cell. /// /// Constructing this with a CellRef instance in the constructor means that /// in practice (where D is RefData) the possibly mutable data is copied /// across to mData. If later adding data (such as position) to CellRef /// this would have to be manually copied across. template struct LiveCellRef : public LiveCellRefBase { LiveCellRef(const ESM::CellRef& cref, const X* b = nullptr) : LiveCellRefBase(X::sRecordId, cref), mBase(b) {} LiveCellRef(const X* b = nullptr) : LiveCellRefBase(X::sRecordId), mBase(b) {} // The object that this instance is based on. const X* mBase; void load (const ESM::ObjectState& state) override; ///< Load state into a LiveCellRef, that has already been initialised with base and class. /// /// \attention Must not be called with an invalid \a state. void save (ESM::ObjectState& state) const override; ///< Save LiveCellRef state into \a state. std::string_view getTypeDescription() const override { return X::getRecordType(); } static bool checkState (const ESM::ObjectState& state); ///< Check if state is valid and report errors. /// /// \return Valid? /// /// \note Does not check if the RefId exists. }; template void LiveCellRef::load (const ESM::ObjectState& state) { loadImp (state); } template void LiveCellRef::save (ESM::ObjectState& state) const { saveImp (state); } template bool LiveCellRef::checkState (const ESM::ObjectState& state) { return checkStateImp (state); } } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/localscripts.cpp000066400000000000000000000114031445372753700236040ustar00rootroot00000000000000#include "localscripts.hpp" #include #include "esmstore.hpp" #include "cellstore.hpp" #include "class.hpp" #include "containerstore.hpp" namespace { struct AddScriptsVisitor { AddScriptsVisitor(MWWorld::LocalScripts& scripts) : mScripts(scripts) { } MWWorld::LocalScripts& mScripts; bool operator()(const MWWorld::Ptr& ptr) { if (ptr.getRefData().isDeleted()) return true; std::string script = ptr.getClass().getScript(ptr); if (!script.empty()) mScripts.add(script, ptr); return true; } }; struct AddContainerItemScriptsVisitor { AddContainerItemScriptsVisitor(MWWorld::LocalScripts& scripts) : mScripts(scripts) { } MWWorld::LocalScripts& mScripts; bool operator()(const MWWorld::Ptr& containerPtr) { // Ignore containers without generated content if (containerPtr.getType() == ESM::Container::sRecordId && containerPtr.getRefData().getCustomData() == nullptr) return true; MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { std::string script = it->getClass().getScript(*it); if(script != "") { MWWorld::Ptr item = *it; item.mCell = containerPtr.getCell(); mScripts.add (script, item); } } return true; } }; } MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) { mIter = mScripts.end(); } void MWWorld::LocalScripts::startIteration() { mIter = mScripts.begin(); } bool MWWorld::LocalScripts::getNext(std::pair& script) { if (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; script = *iter; return true; } return false; } void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) { if (const ESM::Script *script = mStore.get().search (scriptName)) { try { ptr.getRefData().setLocals (*script); for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) if (iter->second==ptr) { Log(Debug::Warning) << "Error: tried to add local script twice for " << ptr.getCellRef().getRefId(); remove(ptr); break; } mScripts.emplace_back (scriptName, ptr); } catch (const std::exception& exception) { Log(Debug::Error) << "failed to add local script " << scriptName << " because an exception has been thrown: " << exception.what(); } } else Log(Debug::Warning) << "failed to add local script " << scriptName << " because the script does not exist."; } void MWWorld::LocalScripts::addCell (CellStore *cell) { AddScriptsVisitor addScriptsVisitor(*this); cell->forEach(addScriptsVisitor); AddContainerItemScriptsVisitor addContainerItemScriptsVisitor(*this); cell->forEachType(addContainerItemScriptsVisitor); cell->forEachType(addContainerItemScriptsVisitor); cell->forEachType(addContainerItemScriptsVisitor); } void MWWorld::LocalScripts::clear() { mScripts.clear(); } void MWWorld::LocalScripts::clearCell (CellStore *cell) { std::list >::iterator iter = mScripts.begin(); while (iter!=mScripts.end()) { if (iter->second.mCell==cell) { if (iter==mIter) ++mIter; mScripts.erase (iter++); } else ++iter; } } void MWWorld::LocalScripts::remove (RefData *ref) { for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) if (&(iter->second.getRefData()) == ref) { if (iter==mIter) ++mIter; mScripts.erase (iter); break; } } void MWWorld::LocalScripts::remove (const Ptr& ptr) { for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) if (iter->second==ptr) { if (iter==mIter) ++mIter; mScripts.erase (iter); break; } } openmw-openmw-0.48.0/apps/openmw/mwworld/localscripts.hpp000066400000000000000000000026211445372753700236130ustar00rootroot00000000000000#ifndef GAME_MWWORLD_LOCALSCRIPTS_H #define GAME_MWWORLD_LOCALSCRIPTS_H #include #include #include "ptr.hpp" namespace MWWorld { class ESMStore; class CellStore; class RefData; /// \brief List of active local scripts class LocalScripts { std::list > mScripts; std::list >::iterator mIter; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); void startIteration(); ///< Set the iterator to the begin of the script list. bool getNext(std::pair& script); ///< Get next local script /// @return Did we get a script? void add (const std::string& scriptName, const Ptr& ptr); ///< Add script to collection of active local scripts. void addCell (CellStore *cell); ///< Add all local scripts in a cell. void clear(); ///< Clear active local scripts collection. void clearCell (CellStore *cell); ///< Remove all scripts belonging to \a cell. void remove (RefData *ref); void remove (const Ptr& ptr); ///< Remove script for given reference (ignored if reference does not have a script listed). }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/magiceffects.cpp000066400000000000000000000233671445372753700235360ustar00rootroot00000000000000#include "magiceffects.hpp" #include "esmstore.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/magiceffects.hpp" namespace { template void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName) { const T* item = MWBase::Environment::get().getWorld()->getStore().get().search(id); if(item) { enchantment = item->mEnchant; itemName = item->mName; } } } namespace MWWorld { void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) { const auto& store = MWBase::Environment::get().getWorld()->getStore(); // Convert corprus to format 10 for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) { const ESM::Spell* spell = store.get().search(id); if (!spell) continue; ESM::CreatureStats::CorprusStats stats; stats.mNextWorsening = oldStats.mNextWorsening; for (int i=0; imEffects.mList) { if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; } creatureStats.mCorprusSpells[id] = stats; } // Convert to format 17 for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) { const ESM::Spell* spell = store.get().search(id); if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) continue; ESM::ActiveSpells::ActiveSpellParams params; params.mId = id; params.mDisplayName = spell->mName; params.mItem.unset(); params.mCasterActorId = creatureStats.mActorId; if(spell->mData.mType == ESM::Spell::ST_Ability) params.mType = ESM::ActiveSpells::Type_Ability; else params.mType = ESM::ActiveSpells::Type_Permanent; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); int effectIndex = 0; for(const auto& enam : spell->mEffects.mList) { if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) { ESM::ActiveEffect effect; effect.mEffectId = enam.mEffectID; effect.mArg = MWMechanics::EffectKey(enam).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; effect.mEffectIndex = effectIndex; auto rand = oldParams.mEffectRands.find(effectIndex); if(rand != oldParams.mEffectRands.end()) { float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; // Prevent recalculation of resistances and don't reflect or absorb the effect effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; } else { effect.mMagnitude = 0.f; effect.mMinMagnitude = enam.mMagnMin; effect.mMaxMagnitude = enam.mMagnMax; effect.mFlags = ESM::ActiveEffect::Flag_None; } params.mEffects.emplace_back(effect); } effectIndex++; } creatureStats.mActiveSpells.mSpells.emplace_back(params); } std::multimap equippedItems; for(std::size_t i = 0; i < inventory.mItems.size(); ++i) { const ESM::ObjectState& item = inventory.mItems[i]; auto slot = inventory.mEquipmentSlots.find(i); if(slot != inventory.mEquipmentSlots.end()) equippedItems.emplace(item.mRef.mRefID, slot->second); } for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) { std::string eId; std::string name; switch(store.find(id)) { case ESM::REC_ARMO: getEnchantedItem(id, eId, name); break; case ESM::REC_CLOT: getEnchantedItem(id, eId, name); break; case ESM::REC_WEAP: getEnchantedItem(id, eId, name); break; } if(eId.empty()) continue; const ESM::Enchantment* enchantment = store.get().search(eId); if(!enchantment) continue; ESM::ActiveSpells::ActiveSpellParams params; params.mId = id; params.mDisplayName = name; params.mCasterActorId = creatureStats.mActorId; params.mType = ESM::ActiveSpells::Type_Enchantment; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) { const auto& enam = enchantment->mEffects.mList[effectIndex]; auto [random, multiplier] = oldMagnitudes[effectIndex]; float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; magnitude *= multiplier; if(magnitude <= 0) continue; ESM::ActiveEffect effect; effect.mEffectId = enam.mEffectID; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; effect.mArg = MWMechanics::EffectKey(enam).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; effect.mEffectIndex = static_cast(effectIndex); // Prevent recalculation of resistances and don't reflect or absorb the effect effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; params.mEffects.emplace_back(effect); } auto [begin, end] = equippedItems.equal_range(id); for(auto it = begin; it != end; ++it) { params.mItem = { static_cast(it->second), 0 }; creatureStats.mActiveSpells.mSpells.emplace_back(params); } } for(const auto& spell : creatureStats.mCorprusSpells) { auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; }); if(it != creatureStats.mActiveSpells.mSpells.end()) { it->mNextWorsening = spell.second.mNextWorsening; int worsenings = 0; for(int i = 0; i < ESM::Attribute::Length; ++i) worsenings = std::max(spell.second.mWorsenings[i], worsenings); it->mWorsenings = worsenings; } } for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) { if(actorId == -1) continue; for(auto& params : creatureStats.mActiveSpells.mSpells) { if(params.mId == key.mSourceId) { bool found = false; for(auto& effect : params.mEffects) { if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) { effect.mArg = actorId; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; found = true; break; } } if(found) break; } } } // Reset modifiers that were previously recalculated each frame for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) creatureStats.mAttributes[i].mMod = 0.f; for(std::size_t i = 0; i < 3; ++i) { auto& dynamic = creatureStats.mDynamic[i]; dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; dynamic.mMod = 0.f; } for(std::size_t i = 0; i < 4; ++i) creatureStats.mAiSettings[i].mMod = 0.f; if(npcStats) { for(std::size_t i = 0; i < ESM::Skill::Length; ++i) npcStats->mSkills[i].mMod = 0.f; } } // Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 version or not void convertStats(ESM::CreatureStats& creatureStats) { for(std::size_t i = 0; i < 3; ++i) creatureStats.mDynamic[i].mMod = 0.f; for(std::size_t i = 0; i < 4; ++i) creatureStats.mAiSettings[i].mMod = 0.f; } } openmw-openmw-0.48.0/apps/openmw/mwworld/magiceffects.hpp000066400000000000000000000006171445372753700235340ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_MAGICEFFECTS_H #define OPENMW_MWWORLD_MAGICEFFECTS_H namespace ESM { struct CreatureStats; struct InventoryState; struct NpcStats; } namespace MWWorld { void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); void convertStats(ESM::CreatureStats& creatureStats); } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/manualref.cpp000066400000000000000000000055141445372753700230620ustar00rootroot00000000000000#include "manualref.hpp" #include "esmstore.hpp" namespace { template void create(const MWWorld::Store& list, const std::string& name, std::any& refValue, MWWorld::Ptr& ptrValue) { const T* base = list.find(name); ESM::CellRef cellRef; cellRef.blank(); cellRef.mRefID = name; MWWorld::LiveCellRef ref(cellRef, base); refValue = ref; ptrValue = MWWorld::Ptr(&std::any_cast&>(refValue), nullptr); } } MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, std::string_view name, const int count) { std::string lowerName = Misc::StringUtils::lowerCase(name); switch (store.find(lowerName)) { case ESM::REC_ACTI: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_ALCH: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_APPA: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_ARMO: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_BOOK: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_CLOT: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_CONT: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_CREA: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_DOOR: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_INGR: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_LEVC: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_LEVI: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_LIGH: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_LOCK: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_MISC: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_NPC_: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_PROB: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_REPA: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_STAT: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_WEAP: create(store.get(), lowerName, mRef, mPtr); break; case ESM::REC_BODY: create(store.get(), lowerName, mRef, mPtr); break; case 0: throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown ID)"); default: throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown type)"); } mPtr.getRefData().setCount(count); } openmw-openmw-0.48.0/apps/openmw/mwworld/manualref.hpp000066400000000000000000000010751445372753700230650ustar00rootroot00000000000000#ifndef GAME_MWWORLD_MANUALREF_H #define GAME_MWWORLD_MANUALREF_H #include #include "ptr.hpp" namespace MWWorld { /// \brief Manually constructed live cell ref class ManualRef { std::any mRef; Ptr mPtr; ManualRef (const ManualRef&); ManualRef& operator= (const ManualRef&); public: ManualRef(const MWWorld::ESMStore& store, std::string_view name, const int count = 1); const Ptr& getPtr() const { return mPtr; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/nullaction.hpp000066400000000000000000000005041445372753700232570ustar00rootroot00000000000000#ifndef GAME_MWWORLD_NULLACTION_H #define GAME_MWWORLD_NULLACTION_H #include "action.hpp" namespace MWWorld { /// \brief Action: do nothing class NullAction : public Action { void executeImp (const Ptr& actor) override {} bool isNullAction() override { return true; } }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/player.cpp000066400000000000000000000443461445372753700224120ustar00rootroot00000000000000#include "player.hpp" #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/magiceffects.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "cellstore.hpp" #include "class.hpp" #include "ptr.hpp" namespace MWWorld { Player::Player (const ESM::NPC *player) : mCellStore(nullptr), mLastKnownExteriorPosition(0,0,0), mMarkedPosition(ESM::Position()), mMarkedCell(nullptr), mAutoMove(false), mForwardBackward(0), mTeleported(false), mCurrentCrimeId(-1), mPaidCrimeId(-1), mJumping(false) { ESM::CellRef cellRef; cellRef.blank(); cellRef.mRefID = "player"; mPlayer = LiveCellRef(cellRef, player); ESM::Position playerPos = mPlayer.mData.getPosition(); playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0; mPlayer.mData.setPosition(playerPos); } void Player::saveStats() { MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); for (int i=0; i& gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::DynamicStat health = creatureStats.getDynamic(0); creatureStats.setHealth(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat()); for (int i=0; i& gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::DynamicStat health = creatureStats.getDynamic(0); creatureStats.setHealth(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat()); for(size_t i = 0;i < ESM::Attribute::Length;++i) { // Oh, Bethesda. It's "Intelligence". std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : ESM::Attribute::sAttributeNames[i]); MWMechanics::AttributeValue value = npcStats.getAttribute(i); value.setModifier(gmst.find(name)->mValue.getFloat() - value.getModified()); npcStats.setAttribute(i, value); } for(size_t i = 0;i < ESM::Skill::Length;i++) { // Acrobatics is set separately for some reason. if(i == ESM::Skill::Acrobatics) continue; // "Mercantile"! >_< std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : ESM::Skill::sSkillNames[i]); MWMechanics::SkillValue& value = npcStats.getSkill(i); value.setModifier(gmst.find(name)->mValue.getFloat() - value.getModified()); } } void Player::set(const ESM::NPC *player) { mPlayer.mBase = player; } void Player::setCell (MWWorld::CellStore *cellStore) { mCellStore = cellStore; } MWWorld::Ptr Player::getPlayer() { MWWorld::Ptr ptr (&mPlayer, mCellStore); return ptr; } MWWorld::ConstPtr Player::getConstPlayer() const { MWWorld::ConstPtr ptr (&mPlayer, mCellStore); return ptr; } void Player::setBirthSign (const std::string &sign) { mSign = sign; } const std::string& Player::getBirthSign() const { return mSign; } void Player::setDrawState (MWMechanics::DrawState state) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getNpcStats(ptr).setDrawState (state); } bool Player::getAutoMove() const { return mAutoMove; } void Player::setAutoMove (bool enable) { MWWorld::Ptr ptr = getPlayer(); mAutoMove = enable; int value = mForwardBackward; if (mAutoMove) value = 1; ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; } void Player::setLeftRight (float value) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getMovementSettings(ptr).mPosition[0] = value; } void Player::setForwardBackward (float value) { MWWorld::Ptr ptr = getPlayer(); mForwardBackward = value; if (mAutoMove) value = 1; ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; } void Player::setUpDown(int value) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getMovementSettings(ptr).mPosition[2] = static_cast(value); } void Player::setRunState(bool run) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, run); } void Player::setSneak(bool sneak) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak); } void Player::yaw(float yaw) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getMovementSettings(ptr).mRotation[2] += yaw; } void Player::pitch(float pitch) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getMovementSettings(ptr).mRotation[0] += pitch; } void Player::roll(float roll) { MWWorld::Ptr ptr = getPlayer(); ptr.getClass().getMovementSettings(ptr).mRotation[1] += roll; } MWMechanics::DrawState Player::getDrawState() { MWWorld::Ptr ptr = getPlayer(); return ptr.getClass().getNpcStats(ptr).getDrawState(); } void Player::activate() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; MWWorld::Ptr player = getPlayer(); const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && playerStats.isParalyzed()) || playerStats.getKnockedDown() || playerStats.isDead()) return; MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject(); if (toActivate.isEmpty()) return; if (!toActivate.getClass().hasToolTip(toActivate)) return; MWBase::Environment::get().getWorld()->activate(toActivate, player); } bool Player::wasTeleported() const { return mTeleported; } void Player::setTeleported(bool teleported) { mTeleported = teleported; } void Player::setAttackingOrSpell(bool attackingOrSpell) { getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell); } void Player::setJumping(bool jumping) { mJumping = jumping; } bool Player::getJumping() const { return mJumping; } bool Player::isInCombat() { return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } bool Player::enemiesNearby() { return MWBase::Environment::get().getMechanicsManager()->getEnemiesNearby(getPlayer()).size() != 0; } void Player::markPosition(CellStore *markedCell, const ESM::Position& markedPosition) { mMarkedCell = markedCell; mMarkedPosition = markedPosition; } void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position &markedPosition) const { markedCell = mMarkedCell; if (mMarkedCell) markedPosition = mMarkedPosition; } void Player::clear() { mCellStore = nullptr; mSign.clear(); mMarkedCell = nullptr; mAutoMove = false; mForwardBackward = 0; mTeleported = false; mJumping = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; mPreviousItems.clear(); mLastKnownExteriorPosition = osg::Vec3f(0,0,0); for (int i=0; igetCell()->getCellId(); player.mCurrentCrimeId = mCurrentCrimeId; player.mPaidCrimeId = mPaidCrimeId; player.mBirthsign = mSign; player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x(); player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y(); player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z(); if (mMarkedCell) { player.mHasMark = true; player.mMarkedPosition = mMarkedPosition; player.mMarkedCell = mMarkedCell->getCell()->getCellId(); } else player.mHasMark = false; for (int i=0; iapplyWerewolfAcrobatics(getPlayer()); } } getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); MWBase::World& world = *MWBase::Environment::get().getWorld(); try { mCellStore = world.getCell (player.mCellId); } catch (...) { Log(Debug::Warning) << "Warning: Player cell '" << player.mCellId.mWorldspace << "' no longer exists"; // Cell no longer exists. The loader will have to choose a default cell. mCellStore = nullptr; } if (!player.mBirthsign.empty()) { const ESM::BirthSign* sign = world.getStore().get().search (player.mBirthsign); if (!sign) throw std::runtime_error ("invalid player state record (birthsign does not exist)"); } mCurrentCrimeId = player.mCurrentCrimeId; mPaidCrimeId = player.mPaidCrimeId; mSign = player.mBirthsign; mLastKnownExteriorPosition.x() = player.mLastKnownExteriorPosition[0]; mLastKnownExteriorPosition.y() = player.mLastKnownExteriorPosition[1]; mLastKnownExteriorPosition.z() = player.mLastKnownExteriorPosition[2]; if (player.mHasMark && !player.mMarkedCell.mPaged) { // interior cell -> need to check if it exists (exterior cell will be // generated on the fly) if (!world.getStore().get().search (player.mMarkedCell.mWorldspace)) player.mHasMark = false; // drop mark silently } if (player.mHasMark) { mMarkedPosition = player.mMarkedPosition; mMarkedCell = world.getCell (player.mMarkedCell); } else { mMarkedCell = nullptr; } mForwardBackward = 0; mTeleported = false; mPreviousItems = player.mPreviousItems; return true; } return false; } int Player::getNewCrimeId() { return ++mCurrentCrimeId; } void Player::recordCrimeId() { mPaidCrimeId = mCurrentCrimeId; } int Player::getCrimeId() const { return mPaidCrimeId; } void Player::setPreviousItem(const std::string& boundItemId, const std::string& previousItemId) { mPreviousItems[boundItemId] = previousItemId; } std::string Player::getPreviousItem(const std::string& boundItemId) { return mPreviousItems[boundItemId]; } void Player::erasePreviousItem(const std::string& boundItemId) { mPreviousItems.erase(boundItemId); } void Player::setSelectedSpell(const std::string& spellId) { Ptr player = getPlayer(); InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); int castChance = int(MWMechanics::getSpellSuccessChance(spellId, player)); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, castChance); MWBase::Environment::get().getWindowManager()->updateSpellWindow(); } void Player::update() { auto player = getPlayer(); const auto world = MWBase::Environment::get().getWorld(); const auto rendering = world->getRenderingManager(); auto& store = world->getStore(); auto& playerClass = player.getClass(); const auto windowMgr = MWBase::Environment::get().getWindowManager(); if (player.getCell()->isExterior()) { ESM::Position pos = player.getRefData().getPosition(); setLastKnownExteriorPosition(pos.asVec3()); } bool isWerewolf = playerClass.getNpcStats(player).isWerewolf(); bool isFirstPerson = world->isFirstPerson(); if (isWerewolf && isFirstPerson) { float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); if (werewolfFov != 0) rendering->overrideFieldOfView(werewolfFov); windowMgr->setWerewolfOverlay(true); } else { rendering->resetFieldOfView(); windowMgr->setWerewolfOverlay(false); } // Sink the camera while sneaking bool sneaking = playerClass.getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool swimming = world->isSwimming(player); bool flying = world->isFlying(player); static const float i1stPersonSneakDelta = store.get().find("i1stPersonSneakDelta")->mValue.getFloat(); if (sneaking && !swimming && !flying) rendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else rendering->getCamera()->setSneakOffset(0.f); int blind = 0; const auto& magicEffects = playerClass.getCreatureStats(player).getMagicEffects(); if (!world->getGodModeState()) blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); windowMgr->setBlindness(std::clamp(blind, 0, 100)); int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); rendering->setNightEyeFactor(std::min(1.f, (nightEye / 100.f))); } } openmw-openmw-0.48.0/apps/openmw/mwworld/player.hpp000066400000000000000000000102641445372753700224070ustar00rootroot00000000000000#ifndef GAME_MWWORLD_PLAYER_H #define GAME_MWWORLD_PLAYER_H #include #include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" #include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/stat.hpp" #include #include #include namespace ESM { class ESMWriter; class ESMReader; } namespace Loading { class Listener; } namespace MWWorld { class CellStore; class ConstPtr; /// \brief NPC object representing the player and additional player data class Player { LiveCellRef mPlayer; MWWorld::CellStore *mCellStore; std::string mSign; osg::Vec3f mLastKnownExteriorPosition; ESM::Position mMarkedPosition; // If no position was marked, this is nullptr CellStore* mMarkedCell; bool mAutoMove; float mForwardBackward; bool mTeleported; int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; // Saved stats prior to becoming a werewolf float mSaveSkills[ESM::Skill::Length]; float mSaveAttributes[ESM::Attribute::Length]; bool mJumping; public: Player(const ESM::NPC *player); void saveStats(); void restoreStats(); void setWerewolfStats(); // For mark/recall magic effects void markPosition (CellStore* markedCell, const ESM::Position& markedPosition); void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const; /// Interiors can not always be mapped to a world position. However /// world position is still required for divine / almsivi magic effects /// and the player arrow on the global map. void setLastKnownExteriorPosition (const osg::Vec3f& position) { mLastKnownExteriorPosition = position; } osg::Vec3f getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } void set (const ESM::NPC *player); void setCell (MWWorld::CellStore *cellStore); MWWorld::Ptr getPlayer(); MWWorld::ConstPtr getConstPlayer() const; void setBirthSign(const std::string &sign); const std::string &getBirthSign() const; void setDrawState (MWMechanics::DrawState state); MWMechanics::DrawState getDrawState(); /// \todo constness /// Activate the object under the crosshair, if any void activate(); bool getAutoMove() const; void setAutoMove (bool enable); void setLeftRight (float value); void setForwardBackward (float value); void setUpDown(int value); void setRunState(bool run); void setSneak(bool sneak); void yaw(float yaw); void pitch(float pitch); void roll(float roll); bool wasTeleported() const; void setTeleported(bool teleported); void setAttackingOrSpell(bool attackingOrSpell); void setJumping(bool jumping); bool getJumping() const; ///Checks all nearby actors to see if anyone has an aipackage against you bool isInCombat(); bool enemiesNearby(); void clear(); void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type); int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 int getCrimeId() const; // get the last paid crime id void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId); std::string getPreviousItem(const std::string& boundItemId); void erasePreviousItem(const std::string& boundItemId); void setSelectedSpell(const std::string& spellId); void update(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/projectilemanager.cpp000066400000000000000000000752431445372753700246110ustar00rootroot00000000000000#include "projectilemanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aipackage.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/util.hpp" #include "../mwsound/sound.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/projectile.hpp" namespace { ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::set& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::EffectList* effects; if (const ESM::Spell* spell = esmStore.get().search(id)) // check if it's a spell { sourceName = spell->mName; effects = &spell->mEffects; } else // check if it's an enchanted item { MWWorld::ManualRef ref(esmStore, id); MWWorld::Ptr ptr = ref.getPtr(); const ESM::Enchantment* ench = esmStore.get().find(ptr.getClass().getEnchantment(ptr)); sourceName = ptr.getClass().getName(ptr); effects = &ench->mEffects; } int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; for (std::vector::const_iterator iter (effects->mList.begin()); iter!=effects->mList.end(); ++iter) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); // Speed of multi-effect projectiles should be the average of the constituent effects, // based on observation of the original engine. speed += magicEffect->mData.mSpeed; count++; if (iter->mRange != ESM::RT_Target) continue; if (magicEffect->mBolt.empty()) projectileIDs.emplace_back("VFX_DefaultBolt"); else projectileIDs.push_back(magicEffect->mBolt); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; if (!magicEffect->mBoltSound.empty()) sounds.emplace(magicEffect->mBoltSound); else sounds.emplace(schools[magicEffect->mData.mSchool] + " bolt"); projectileEffects.mList.push_back(*iter); } if (count != 0) speed /= count; // the particle texture is only used if there is only one projectile if (projectileEffects.mList.size() == 1) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effects->mList.begin()->mEffectID); texture = magicEffect->mParticle; } if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size()); std::vector::iterator it; it = projectileIDs.begin(); it = projectileIDs.insert(it, ID); } return projectileEffects; } osg::Vec4 getMagicBoltLightDiffuseColor(const ESM::EffectList& effects) { // Calculate combined light diffuse color from magical effects osg::Vec4 lightDiffuseColor; float lightDiffuseRed = 0.0f; float lightDiffuseGreen = 0.0f; float lightDiffuseBlue = 0.0f; for (std::vector::const_iterator iter(effects.mList.begin()); iter != effects.mList.end(); ++iter) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( iter->mEffectID); lightDiffuseRed += (static_cast(magicEffect->mData.mRed) / 255.f); lightDiffuseGreen += (static_cast(magicEffect->mData.mGreen) / 255.f); lightDiffuseBlue += (static_cast(magicEffect->mData.mBlue) / 255.f); } int numberOfEffects = effects.mList.size(); lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects , lightDiffuseGreen / numberOfEffects , lightDiffuseBlue / numberOfEffects , 1.0f); return lightDiffuseColor; } } namespace MWWorld { ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) : mParent(parent) , mResourceSystem(resourceSystem) , mRendering(rendering) , mPhysics(physics) , mCleanupTimer(0.0f) { } /// Rotates an osg::PositionAttitudeTransform over time. class RotateCallback : public SceneUtil::NodeCallback { public: RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) : mAxis(axis) , mRotateSpeed(rotateSpeed) { } void operator()(osg::PositionAttitudeTransform* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); node->setAttitude(orient); traverse(node, nv); } private: osg::Vec3f mAxis; float mRotateSpeed; }; void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; state.mNode->setNodeMask(MWRender::Mask_Effect); state.mNode->setPosition(pos); state.mNode->setAttitude(orient); osg::Group* attachTo = state.mNode; if (rotate) { osg::ref_ptr rotateNode (new osg::PositionAttitudeTransform); rotateNode->addUpdateCallback(new RotateCallback()); state.mNode->addChild(rotateNode); attachTo = rotateNode; } osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); if (state.mIdMagic.size() > 1) { const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { std::ostringstream nodeName; nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter; const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); SceneUtil::FindByNameVisitor findVisitor(nodeName.str()); attachTo->accept(findVisitor); if (findVisitor.mFoundNode) mResourceSystem->getSceneManager()->getInstance( Misc::ResourceHelpers::correctMeshPath(weapon->mModel, vfs), findVisitor.mFoundNode); } } if (createLight) { osg::ref_ptr projectileLight(new osg::Light); projectileLight->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); projectileLight->setDiffuse(lightDiffuseColor); projectileLight->setSpecular(osg::Vec4(0.0f, 0.0f, 0.0f, 0.0f)); projectileLight->setConstantAttenuation(0.f); projectileLight->setLinearAttenuation(0.1f); projectileLight->setQuadraticAttenuation(0.f); projectileLight->setPosition(osg::Vec4(pos, 1.0)); SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource; projectileLightSource->setNodeMask(MWRender::Mask_Lighting); projectileLightSource->setRadius(66.f); state.mNode->addChild(projectileLightSource); projectileLightSource->setLight(projectileLight); } state.mNode->addCullCallback(new SceneUtil::LightListCallback); mParent->addChild(state.mNode); state.mEffectAnimationTime = std::make_shared(); SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); } void ProjectileManager::update(State& state, float duration) { state.mEffectAnimationTime->addTime(duration); } void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection, int slot) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) { // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; osg::Quat orient; if (caster.getClass().isActor()) orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); else orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); MagicBoltState state; state.mSpellId = spellId; state.mCasterHandle = caster; state.mSlot = slot; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else state.mActorId = -1; std::string texture; state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); // Non-projectile should have been removed by getMagicBoltData if (state.mEffects.mList.empty()) return; if (!caster.getClass().isActor() && fallbackDirection.length2() <= 0) { Log(Debug::Warning) << "Unable to launch magic bolt (direction to target is empty)"; return; } MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); auto model = ptr.getClass().getModel(ptr); createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) { MWBase::Sound *sound = sndMgr->playSound3D(pos, soundid, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape if (state.mIdMagic.size() > 1) { model = Misc::ResourceHelpers::correctMeshPath( MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic[1])->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); } state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } void ProjectileManager::launchProjectile(const Ptr& actor, const ConstPtr& projectile, const osg::Vec3f &pos, const osg::Quat &orient, const Ptr& bow, float speed, float attackStrength) { ProjectileState state; state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mBowId = bow.getCellRef().getRefId(); state.mVelocity = orient * osg::Vec3f(0,1,0) * speed; state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; int type = projectile.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); const auto model = ptr.getClass().getModel(ptr); createModel(state, model, pos, orient, false, false, osg::Vec4(0,0,0,0)); if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); state.mToDelete = false; mProjectiles.push_back(state); } void ProjectileManager::updateCasters() { for (auto& state : mProjectiles) mPhysics->setCaster(state.mProjectileId, state.getCaster()); for (auto& state : mMagicBolts) { // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back. // TODO: should object-type caster be restored from savegame? if (state.mActorId == -1) continue; auto caster = state.getCaster(); if (caster.isEmpty()) { Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId; cleanupMagicBolt(state); continue; } mPhysics->setCaster(state.mProjectileId, caster); } } void ProjectileManager::update(float dt) { periodicCleanup(dt); moveProjectiles(dt); moveMagicBolts(dt); } void ProjectileManager::periodicCleanup(float dt) { mCleanupTimer -= dt; if (mCleanupTimer <= 0.0f) { mCleanupTimer = 2.0f; auto isCleanable = [](const ProjectileManager::State& state) -> bool { const float farawayThreshold = 72000.0f; osg::Vec3 playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; }; for (auto& projectileState : mProjectiles) { if (isCleanable(projectileState)) cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { if (isCleanable(magicBoltState)) cleanupMagicBolt(magicBoltState); } } } void ProjectileManager::moveMagicBolts(float duration) { static const bool normaliseRaceSpeed = Settings::Manager::getBool("normalise race speed", "Game"); for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); if (!projectile->isActive()) continue; // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. MWWorld::Ptr caster = magicBoltState.getCaster(); if (!caster.isEmpty() && caster.getClass().isActor()) { if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) { cleanupMagicBolt(magicBoltState); continue; } } const auto& store = MWBase::Environment::get().getWorld()->getStore(); osg::Quat orient = magicBoltState.mNode->getAttitude(); static float fTargetSpellMaxSpeed = store.get().find("fTargetSpellMaxSpeed")->mValue.getFloat(); float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; if (!normaliseRaceSpeed && !caster.isEmpty() && caster.getClass().isNpc()) { const auto npc = caster.get()->mBase; const auto race = store.get().find(npc->mRace); speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; } osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); projectile->setVelocity(direction * speed); update(magicBoltState, duration); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); } } void ProjectileManager::moveProjectiles(float duration) { for (auto& projectileState : mProjectiles) { if (projectileState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; projectile->setVelocity(projectileState.mVelocity); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. if (!projectileState.mThrown) { osg::Quat orient; orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity); projectileState.mNode->setAttitude(orient); } update(projectileState, duration); MWWorld::Ptr caster = projectileState.getCaster(); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); } } void ProjectileManager::processHits() { for (auto& projectileState : mProjectiles) { if (projectileState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); const auto pos = projectile->getSimulationPosition(); projectileState.mNode->setPosition(pos); if (projectile->isActive()) continue; const auto target = projectile->getTarget(); auto caster = projectileState.getCaster(); assert(target != caster); if (caster.isEmpty()) caster = target; // Try to get a Ptr to the bow that was used. It might no longer exist. MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) { MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } if (projectile->getHitWater()) mRendering->emitWaterRipple(pos); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); projectileState.mToDelete = true; } const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); const auto pos = projectile->getSimulationPosition(); magicBoltState.mNode->setPosition(pos); for (const auto& sound : magicBoltState.mSounds) sound->setPosition(pos); if (projectile->isActive()) continue; const auto target = projectile->getTarget(); const auto caster = magicBoltState.getCaster(); assert(target != caster); MWMechanics::CastSpell cast(caster, target); cast.mHitPosition = pos; cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; cast.mSlot = magicBoltState.mSlot; // Grab original effect list so the indices are correct const ESM::EffectList* effects; if (const ESM::Spell* spell = esmStore.get().search(magicBoltState.mSpellId)) effects = &spell->mEffects; else { MWWorld::ManualRef ref(esmStore, magicBoltState.mSpellId); const MWWorld::Ptr& ptr = ref.getPtr(); effects = &esmStore.get().find(ptr.getClass().getEnchantment(ptr))->mEffects; } cast.inflict(target, caster, *effects, ESM::RT_Target); magicBoltState.mToDelete = true; } for (auto& projectileState : mProjectiles) { if (projectileState.mToDelete) cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) cleanupMagicBolt(magicBoltState); } mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.end()); mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), mMagicBolts.end()); } void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); state.mToDelete = true; } void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); state.mToDelete = true; for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) { MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); } } void ProjectileManager::clear() { for (auto& mProjectile : mProjectiles) cleanupProjectile(mProjectile); mProjectiles.clear(); for (auto& mMagicBolt : mMagicBolts) cleanupMagicBolt(mMagicBolt); mMagicBolts.clear(); } void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const { for (std::vector::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) { writer.startRecord(ESM::REC_PROJ); ESM::ProjectileState state; state.mId = it->mIdArrow; state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; state.mBowId = it->mBowId; state.mVelocity = it->mVelocity; state.mAttackStrength = it->mAttackStrength; state.save(writer); writer.endRecord(ESM::REC_PROJ); } for (std::vector::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) { writer.startRecord(ESM::REC_MPRJ); ESM::MagicBoltState state; state.mId = it->mIdMagic.at(0); state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; state.mSlot = it->mSlot; state.mSpellId = it->mSpellId; state.mSpeed = it->mSpeed; state.save(writer); writer.endRecord(ESM::REC_MPRJ); } } bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_PROJ) { ESM::ProjectileState esm; esm.load(reader); ProjectileState state; state.mActorId = esm.mActorId; state.mBowId = esm.mBowId; state.mVelocity = esm.mVelocity; state.mIdArrow = esm.mId; state.mAttackStrength = esm.mAttackStrength; state.mToDelete = false; std::string model; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); } catch(...) { return true; } createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, osg::Vec4(0,0,0,0)); mProjectiles.push_back(state); return true; } if (type == ESM::REC_MPRJ) { ESM::MagicBoltState esm; esm.load(reader); MagicBoltState state; state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mToDelete = false; state.mSlot = esm.mSlot; std::string texture; try { state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); } catch(...) { Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)"; return true; } state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as // projectile effects, so we can't calculate it from the save // file's effect list, which is already trimmed of non-projectile // effects. We need to use the stored value. std::string model; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); } catch(...) { return true; } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) { MWBase::Sound *sound = sndMgr->playSound3D(esm.mPosition, soundid, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } mMagicBolts.push_back(state); return true; } return false; } int ProjectileManager::countSavedGameRecords() const { return mMagicBolts.size() + mProjectiles.size(); } MWWorld::Ptr ProjectileManager::State::getCaster() { if (!mCasterHandle.isEmpty()) return mCasterHandle; return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); } } openmw-openmw-0.48.0/apps/openmw/mwworld/projectilemanager.hpp000066400000000000000000000077671445372753700246240ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_PROJECTILEMANAGER_H #define OPENMW_MWWORLD_PROJECTILEMANAGER_H #include #include #include #include #include "../mwbase/soundmanager.hpp" #include "ptr.hpp" namespace MWPhysics { class PhysicsSystem; } namespace Loading { class Listener; } namespace osg { class Group; class Quat; } namespace Resource { class ResourceSystem; } namespace MWRender { class EffectAnimationTime; class RenderingManager; } namespace MWWorld { class ProjectileManager { public: ProjectileManager (osg::Group* parent, Resource::ResourceSystem* resourceSystem, MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot); void launchProjectile (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile, const osg::Vec3f& pos, const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength); void updateCasters(); void update(float dt); void processHits(); /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, uint32_t type); int countSavedGameRecords() const; private: osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; MWRender::RenderingManager* mRendering; MWPhysics::PhysicsSystem* mPhysics; float mCleanupTimer; struct State { osg::ref_ptr mNode; std::shared_ptr mEffectAnimationTime; int mActorId; int mProjectileId; // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. MWWorld::Ptr mCasterHandle; MWWorld::Ptr getCaster(); // MW-ids of a magic projectile std::vector mIdMagic; // MW-id of an arrow projectile std::string mIdArrow; bool mToDelete; }; struct MagicBoltState : public State { std::string mSpellId; // Name of item to display as effect source in magic menu (in case we casted an enchantment) std::string mSourceName; ESM::EffectList mEffects; float mSpeed; int mSlot; std::vector mSounds; std::set mSoundIds; }; struct ProjectileState : public State { // RefID of the bow or crossbow the actor was using when this projectile was fired (may be empty) std::string mBowId; osg::Vec3f mVelocity; float mAttackStrength; bool mThrown; }; std::vector mMagicBolts; std::vector mProjectiles; void cleanupProjectile(ProjectileState& state); void cleanupMagicBolt(MagicBoltState& state); void periodicCleanup(float dt); void moveProjectiles(float dt); void moveMagicBolts(float dt); void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); void operator=(const ProjectileManager&); ProjectileManager(const ProjectileManager&); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/ptr.hpp000066400000000000000000000111171445372753700217160ustar00rootroot00000000000000#ifndef GAME_MWWORLD_PTR_H #define GAME_MWWORLD_PTR_H #include #include #include #include #include "livecellref.hpp" namespace MWWorld { class ContainerStore; class CellStore; struct LiveCellRefBase; /// \brief Pointer to a LiveCellRef /// @note PtrBase is never used directly and needed only to define Ptr and ConstPtr template class TypeTransform> class PtrBase { public: typedef TypeTransform LiveCellRefBaseType; typedef TypeTransform CellStoreType; typedef TypeTransform ContainerStoreType; LiveCellRefBaseType *mRef; CellStoreType *mCell; ContainerStoreType *mContainerStore; bool isEmpty() const { return mRef == nullptr; } // Returns a 32-bit id of the ESM record this object is based on. // Specific values of ids are defined in ESM::RecNameInts. // Note 1: ids are not sequential. E.g. for a creature `getType` returns 0x41455243. // Note 2: Life is not easy and full of surprises. For example // prison marker reuses ESM::Door record. Player is ESM::NPC. unsigned int getType() const { if(mRef != nullptr) return mRef->getType(); throw std::runtime_error("Can't get type name from an empty object."); } std::string_view getTypeDescription() const { return mRef ? mRef->getTypeDescription() : "nullptr"; } const Class& getClass() const { if(mRef != nullptr) return *(mRef->mClass); throw std::runtime_error("Cannot get class of an empty object"); } template auto* get() const { return LiveCellRefBase::dynamicCast(mRef); } LiveCellRefBaseType *getBase() const { if (!mRef) throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); return mRef; } TypeTransform& getCellRef() const { assert(mRef); return mRef->mRef; } TypeTransform& getRefData() const { assert(mRef); return mRef->mData; } CellStoreType *getCell() const { assert(mCell); return mCell; } bool isInCell() const { return (mContainerStore == nullptr) && (mCell != nullptr); } void setContainerStore (ContainerStoreType *store) ///< Must not be called on references that are in a cell. { assert (store); assert (!mCell); mContainerStore = store; } ContainerStoreType *getContainerStore() const ///< May return a 0-pointer, if reference is not in a container. { return mContainerStore; } operator const void *() const ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty { return mRef; } protected: PtrBase(LiveCellRefBaseType *liveCellRef, CellStoreType *cell, ContainerStoreType* containerStore) : mRef(liveCellRef), mCell(cell), mContainerStore(containerStore) {} }; /// @note It is possible to get mutable values from const Ptr. So if a function accepts const Ptr&, the object is still mutable. /// To make it really const the argument should be const ConstPtr&. class Ptr : public PtrBase { public: Ptr(LiveCellRefBase *liveCellRef=nullptr, CellStoreType *cell=nullptr) : PtrBase(liveCellRef, cell, nullptr) {} }; /// @note The difference between Ptr and ConstPtr is that the second one adds const to the underlying pointers. /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. class ConstPtr : public PtrBase { public: ConstPtr(const Ptr& ptr) : PtrBase(ptr.mRef, ptr.mCell, ptr.mContainerStore) {} ConstPtr(const LiveCellRefBase *liveCellRef=nullptr, const CellStoreType *cell=nullptr) : PtrBase(liveCellRef, cell, nullptr) {} }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/refdata.cpp000066400000000000000000000162061445372753700225160ustar00rootroot00000000000000#include "refdata.hpp" #include #include "customdata.hpp" #include "cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwlua/localscripts.hpp" namespace { enum RefDataFlags { Flag_SuppressActivate = 1, // If set, activation will be suppressed and redirected to the OnActivate flag, which can then be handled by a script. Flag_OnActivate = 2, Flag_ActivationBuffered = 4 }; } namespace MWWorld { void RefData::setLuaScripts(std::shared_ptr&& scripts) { mChanged = true; mLuaScripts = std::move(scripts); } void RefData::copy (const RefData& refData) { mBaseNode = refData.mBaseNode; mLocals = refData.mLocals; mEnabled = refData.mEnabled; mCount = refData.mCount; mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; mFlags = refData.mFlags; mPhysicsPostponed = refData.mPhysicsPostponed; mAnimationState = refData.mAnimationState; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : nullptr; mLuaScripts = refData.mLuaScripts; } void RefData::cleanup() { mBaseNode = nullptr; mCustomData = nullptr; mLuaScripts = nullptr; } RefData::RefData() : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { mPosition.pos[i] = 0; mPosition.rot[i] = 0; } } RefData::RefData (const ESM::CellRef& cellRef) : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mPosition (cellRef.mPos), mCustomData (nullptr), mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr), mDeletedByContentFile(deletedByContentFile), mEnabled (objectState.mEnabled != 0), mPhysicsPostponed(false), mCount (objectState.mCount), mPosition (objectState.mPosition), mAnimationState(objectState.mAnimationState), mCustomData (nullptr), mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. // This occurred when removing the animated containers mod, and the fix in MCP is to reset UseEnabled to true on loading a game." mFlags &= (~Flag_SuppressActivate); } RefData::RefData (const RefData& refData) : mBaseNode(nullptr), mCustomData (nullptr) { try { copy (refData); mFlags &= ~(Flag_SuppressActivate|Flag_OnActivate|Flag_ActivationBuffered); } catch (...) { cleanup(); throw; } } void RefData::write (ESM::ObjectState& objectState, const std::string& scriptId) const { objectState.mHasLocals = mLocals.write (objectState.mLocals, scriptId); objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; objectState.mFlags = mFlags; objectState.mAnimationState = mAnimationState; } RefData& RefData::operator= (const RefData& refData) { try { cleanup(); copy (refData); } catch (...) { cleanup(); throw; } return *this; } RefData::~RefData() { try { cleanup(); } catch (...) {} } RefData::RefData(RefData&& other) noexcept = default; RefData& RefData::operator=(RefData&& other) noexcept = default; void RefData::setBaseNode(SceneUtil::PositionAttitudeTransform *base) { mBaseNode = base; } SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() { return mBaseNode; } const SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() const { return mBaseNode; } int RefData::getCount(bool absolute) const { if(absolute) return std::abs(mCount); return mCount; } void RefData::setLocals (const ESM::Script& script) { if (mLocals.configure (script) && !mLocals.isEmpty()) mChanged = true; } void RefData::setCount (int count) { if(count == 0) MWBase::Environment::get().getWorld()->removeRefScript(this); mChanged = true; mCount = count; } void RefData::setDeletedByContentFile(bool deleted) { mDeletedByContentFile = deleted; } bool RefData::isDeleted() const { return mDeletedByContentFile || mCount == 0; } bool RefData::isDeletedByContentFile() const { return mDeletedByContentFile; } MWScript::Locals& RefData::getLocals() { return mLocals; } bool RefData::isEnabled() const { return mEnabled; } void RefData::enable() { if (!mEnabled) { mChanged = true; mEnabled = true; } } void RefData::disable() { if (mEnabled) { mChanged = true; mEnabled = false; } } void RefData::setPosition(const ESM::Position& pos) { mChanged = true; mPosition = pos; } const ESM::Position& RefData::getPosition() const { return mPosition; } void RefData::setCustomData(std::unique_ptr&& value) noexcept { mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed mCustomData = std::move(value); } CustomData *RefData::getCustomData() { return mCustomData.get(); } const CustomData *RefData::getCustomData() const { return mCustomData.get(); } bool RefData::hasChanged() const { return mChanged || !mAnimationState.empty(); } bool RefData::activateByScript() { bool ret = (mFlags & Flag_ActivationBuffered); mFlags &= ~(Flag_SuppressActivate|Flag_OnActivate); return ret; } bool RefData::activate() { if (mFlags & Flag_SuppressActivate) { mFlags |= Flag_OnActivate|Flag_ActivationBuffered; return false; } else { return true; } } bool RefData::onActivate() { bool ret = mFlags & Flag_OnActivate; mFlags |= Flag_SuppressActivate; mFlags &= (~Flag_OnActivate); return ret; } const ESM::AnimationState& RefData::getAnimationState() const { return mAnimationState; } ESM::AnimationState& RefData::getAnimationState() { return mAnimationState; } } openmw-openmw-0.48.0/apps/openmw/mwworld/refdata.hpp000066400000000000000000000113661445372753700225250ustar00rootroot00000000000000#ifndef GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H #include #include #include "../mwscript/locals.hpp" #include "../mwworld/customdata.hpp" #include #include namespace SceneUtil { class PositionAttitudeTransform; } namespace ESM { class Script; class CellRef; struct ObjectState; } namespace MWLua { class LocalScripts; } namespace MWWorld { class CustomData; class RefData { SceneUtil::PositionAttitudeTransform* mBaseNode; MWScript::Locals mLocals; std::shared_ptr mLuaScripts; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. bool mDeletedByContentFile:1; bool mEnabled:1; public: bool mPhysicsPostponed:1; private: /// 0: deleted int mCount; ESM::Position mPosition; ESM::AnimationState mAnimationState; std::unique_ptr mCustomData; void copy (const RefData& refData); void cleanup(); bool mChanged; unsigned int mFlags; public: RefData(); /// @param cellRef Used to copy constant data such as position into this class where it can /// be altered without affecting the original data. This makes it possible /// to reset the position as the original data is still held in the CellRef RefData (const ESM::CellRef& cellRef); RefData (const ESM::ObjectState& objectState, bool deletedByContentFile); ///< Ignores local variables and custom data (not enough context available here to /// perform these operations). RefData (const RefData& refData); RefData (RefData&& other) noexcept; ~RefData(); void write (ESM::ObjectState& objectState, const std::string& scriptId = "") const; ///< Ignores custom data (not enough context available here to /// perform this operations). RefData& operator= (const RefData& refData); RefData& operator= (RefData&& other) noexcept; /// Return base node (can be a null pointer). SceneUtil::PositionAttitudeTransform* getBaseNode(); /// Return base node (can be a null pointer). const SceneUtil::PositionAttitudeTransform* getBaseNode() const; /// Set base node (can be a null pointer). void setBaseNode (SceneUtil::PositionAttitudeTransform* base); int getCount(bool absolute = true) const; void setLocals (const ESM::Script& script); MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); } void setLuaScripts(std::shared_ptr&&); void setCount (int count); ///< Set object count (an object pile is a simple object with a count >1). /// /// \warning Do not call setCount() to add or remove objects from a /// container or an actor's inventory. Call ContainerStore::add() or /// ContainerStore::remove() instead. /// This flag is only used for content stack loading and will not be stored in the savegame. /// If the object was deleted by gameplay, then use setCount(0) instead. void setDeletedByContentFile(bool deleted); /// Returns true if the object was either deleted by the content file or by gameplay. bool isDeleted() const; /// Returns true if the object was deleted by a content file. bool isDeletedByContentFile() const; MWScript::Locals& getLocals(); bool isEnabled() const; void enable(); void disable(); void setPosition (const ESM::Position& pos); const ESM::Position& getPosition() const; void setCustomData(std::unique_ptr&& value) noexcept; ///< Set custom data (potentially replacing old custom data). The ownership of \a data is /// transferred to this. CustomData *getCustomData(); ///< May return a 0-pointer. The ownership of the return data object is not transferred. const CustomData *getCustomData() const; bool activate(); bool onActivate(); bool activateByScript(); bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? const ESM::AnimationState& getAnimationState() const; ESM::AnimationState& getAnimationState(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/registeredclass.hpp000066400000000000000000000007541445372753700243010ustar00rootroot00000000000000#ifndef GAME_MWWORLD_REGISTEREDCLASS_H #define GAME_MWWORLD_REGISTEREDCLASS_H #include "class.hpp" namespace MWWorld { template class RegisteredClass : public Base { public: static void registerSelf() { static Derived instance; Base::registerClass(instance); } protected: explicit RegisteredClass(unsigned type) : Base(type) {} }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/scene.cpp000066400000000000000000001360121445372753700222030ustar00rootroot00000000000000#include "scene.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/actor.hpp" #include "../mwphysics/object.hpp" #include "../mwphysics/heightfield.hpp" #include "player.hpp" #include "localscripts.hpp" #include "esmstore.hpp" #include "class.hpp" #include "cellvisitors.hpp" #include "cellstore.hpp" #include "cellpreloader.hpp" #include "worldimp.hpp" #include "cellutils.hpp" namespace { using MWWorld::RotationOrder; osg::Quat makeActorOsgQuat(const ESM::Position& position) { return osg::Quat(position.rot[2], osg::Vec3(0, 0, -1)); } osg::Quat makeInversedOrderObjectOsgQuat(const ESM::Position& position) { const float xr = position.rot[0]; const float yr = position.rot[1]; const float zr = position.rot[2]; return osg::Quat(xr, osg::Vec3(-1, 0, 0)) * osg::Quat(yr, osg::Vec3(0, -1, 0)) * osg::Quat(zr, osg::Vec3(0, 0, -1)); } osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr) { const auto pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos); } osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr) { const auto pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos); } osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) { if (order == RotationOrder::inverse) return makeInverseNodeRotation(ptr); return makeDirectNodeRotation(ptr); } void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const osg::Quat &rotation) { if (ptr.getRefData().getBaseNode()) rendering.rotateObject(ptr, rotation); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) { if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) return {}; bool useAnim = ptr.getClass().useAnim(); std::string model = ptr.getClass().getModel(ptr); if (useAnim) model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); return model; } void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { Log(Debug::Warning) << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice"; return; } std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeDirectNodeRotation(ptr); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); if (ptr.getClass().useAnim()) MWBase::Environment::get().getMechanicsManager()->add(ptr); if (ptr.getClass().isActor()) rendering.addWaterRippleEmitter(ptr); // Restore effect particles world.applyLoopingParticles(ptr); if (!model.empty()) ptr.getClass().insertObject(ptr, model, rotation, physics); MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) { if (const auto object = physics.getObject(ptr)) { const DetourNavigator::ObjectTransform objectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}; if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { btVector3 aabbMin; btVector3 aabbMax; object->getShapeInstance()->mCollisionShape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto center = (aabbMax + aabbMin) * 0.5f; const auto distanceFromDoor = world.getMaxActivationDistance() * 0.5f; const auto toPoint = aabbMax.x() - aabbMin.x() < aabbMax.y() - aabbMin.y() ? btVector3(distanceFromDoor, 0, 0) : btVector3(0, distanceFromDoor, 0); const auto transform = object->getTransform(); const btTransform closedDoorTransform( Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin() ); const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint)); const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint)); const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; navigator.addObject( DetourNavigator::ObjectId(object), DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd), transform ); } else if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) { navigator.addObject( DetourNavigator::ObjectId(object), DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform), object->getTransform() ); } } else if (physics.getActor(ptr)) { navigator.addAgent(world.getPathfindingAgentBounds(ptr)); } } struct InsertVisitor { MWWorld::CellStore& mCell; Loading::Listener* mLoadingListener; std::vector mToInsert; InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener); bool operator() (const MWWorld::Ptr& ptr); template void insert(AddObject&& addObject); }; InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener) : mCell(cell), mLoadingListener(loadingListener) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) { // do not insert directly as we can't modify the cell from within the visitation // CreatureLevList::insertObjectRendering may spawn a new creature mToInsert.push_back(ptr); return true; } template void InsertVisitor::insert(AddObject&& addObject) { for (MWWorld::Ptr& ptr : mToInsert) { if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { addObject(ptr); } catch (const std::exception& e) { std::string error ("failed to render '" + ptr.getCellRef().getRefId() + "': "); Log(Debug::Error) << error + e.what(); } } if (mLoadingListener != nullptr) mLoadingListener->increaseProgress(1); } } int getCellPositionDistanceToOrigin(const std::pair& cellPosition) { return std::abs(cellPosition.first) + std::abs(cellPosition.second); } bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) { for (auto *cell : collection) { assert(cell->getCell()->isExterior()); if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) return true; } return false; } bool removeFromSorted(const ESM::RefNum& refNum, std::vector& pagedRefs) { const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum); if (it == pagedRefs.end() || *it != refNum) return false; pagedRefs.erase(it); return true; } } namespace MWWorld { void Scene::removeFromPagedRefs(const Ptr &ptr) { const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs)) { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); reloadTerrain(); } } void Scene::updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics) { mRendering.moveObject(ptr, pos); if (movePhysics) { mPhysics->updatePosition(ptr); } } void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { const auto rot = makeNodeRotation(ptr, order); setNodeRotation(ptr, mRendering, rot); mPhysics->updateRotation(ptr, rot); } void Scene::updateObjectScale(const Ptr &ptr) { float scale = ptr.getCellRef().getScale(); osg::Vec3f scaleVec (scale, scale, scale); ptr.getClass().adjustScale(ptr, scaleVec, true); mRendering.scaleObject(ptr, scaleVec); mPhysics->updateScale(ptr); } void Scene::update(float duration) { if (mChangeCellGridRequest.has_value()) { changeCellGrid(mChangeCellGridRequest->mPosition, mChangeCellGridRequest->mCell.x(), mChangeCellGridRequest->mCell.y(), mChangeCellGridRequest->mChangeEvent); mChangeCellGridRequest.reset(); } mPreloader->updateCache(mRendering.getReferenceTime()); preloadCells(duration); } void Scene::unloadCell(CellStore* cell) { if (mActiveCells.find(cell) == mActiveCells.end()) return; Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; cell->forEach(visitor); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) { if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) mNavigator.removeObject(DetourNavigator::ObjectId(object)); mPhysics->remove(ptr); ptr.mRef->mData.mPhysicsPostponed = false; } else if (mPhysics->getActor(ptr)) { mNavigator.removeAgent(mWorld.getPathfindingAgentBounds(ptr)); mRendering.removeActorPath(ptr); mPhysics->remove(ptr); } MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); } const auto cellX = cell->getCell()->getGridX(); const auto cellY = cell->getCell()->getGridY(); if (cell->getCell()->isExterior()) { if (mPhysics->getHeightField(cellX, cellY) != nullptr) mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY)); mPhysics->removeHeightField(cellX, cellY); } if (cell->getCell()->hasWater()) mNavigator.removeWater(osg::Vec2i(cellX, cellY)); if (const auto pathgrid = mWorld.getStore().get().search(*cell->getCell())) mNavigator.removePathgrid(*pathgrid); mNavigator.update(mWorld.getPlayerPtr().getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (cell); mRendering.removeCell(cell); MWBase::Environment::get().getWindowManager()->removeCell(cell); mWorld.getLocalScripts().clearCell (cell); MWBase::Environment::get().getSoundManager()->stopSound (cell); mActiveCells.erase(cell); // Clean up any effects that may have been spawned while unloading all cells if(mActiveCells.empty()) mRendering.notifyWorldSpaceChanged(); } void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position) { using DetourNavigator::HeightfieldShape; assert(mActiveCells.find(cell) == mActiveCells.end()); mActiveCells.insert(cell); Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); if (cell->getCell()->isExterior()) { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; const int verts = ESM::Land::LAND_SIZE; const int worldsize = ESM::Land::REAL_SIZE; if (data) { mPhysics->addHeightField(data->mHeights, cellX, cellY, worldsize, verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { static std::vector defaultHeight; defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { const osg::Vec2i cellPosition(cellX, cellY); const btVector3& origin = heightField->getCollisionObject()->getWorldTransform().getOrigin(); const osg::Vec3f shift(origin.x(), origin.y(), origin.z()); const HeightfieldShape shape = [&] () -> HeightfieldShape { if (data == nullptr) { return DetourNavigator::HeightfieldPlane {static_cast(ESM::Land::DEFAULT_HEIGHT)}; } else { DetourNavigator::HeightfieldSurface heights; heights.mHeights = data->mHeights; heights.mSize = static_cast(ESM::Land::LAND_SIZE); heights.mMinHeight = data->mMinHeight; heights.mMaxHeight = data->mMaxHeight; return heights; } } (); mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape); } } if (const auto pathgrid = mWorld.getStore().get().search(*cell->getCell())) mNavigator.addPathgrid(*cell->getCell(), *pathgrid); // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice mWorld.getLocalScripts().addCell (cell); if (respawn) cell->respawn(); insertCell(*cell, loadingListener); mRendering.addCell(cell); MWBase::Environment::get().getWindowManager()->addCell(cell); bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); float waterLevel = cell->getWaterLevel(); mRendering.setWaterEnabled(waterEnabled); if (waterEnabled) { mPhysics->enableWater(waterLevel); mRendering.setWaterHeight(waterLevel); if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel); } else { mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), waterLevel); } } else mPhysics->disableWater(); mNavigator.update(position); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); mPreloader->notifyLoaded(cell); } void Scene::clear() { for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cell = *iter++; unloadCell (cell); } assert(mActiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); } osg::Vec4i Scene::gridCenterToBounds(const osg::Vec2i& centerCell) const { return osg::Vec4i(centerCell.x()-mHalfGridSize,centerCell.y()-mHalfGridSize,centerCell.x()+mHalfGridSize+1,centerCell.y()+mHalfGridSize+1); } osg::Vec2i Scene::getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i* currentGridCenter) const { if (currentGridCenter) { float centerX, centerY; mWorld.indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true); float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold if (distance <= maxDistance) return *currentGridCenter; } return positionToCellIndex(pos.x(), pos.y()); } void Scene::playerMoved(const osg::Vec3f &pos) { if (mCurrentCell == nullptr) return; mNavigator.updatePlayerPosition(pos); if (!mCurrentCell->isExterior()) return; osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); if (newCell != mCurrentGridCenter) requestChangeCellGrid(pos, newCell); } void Scene::requestChangeCellGrid(const osg::Vec3f &position, const osg::Vec2i& cell, bool changeEvent) { mChangeCellGridRequest = ChangeCellGridRequest {position, cell, changeEvent}; } void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { for (auto iter = mActiveCells.begin(); iter != mActiveCells.end(); ) { auto* cell = *iter++; if (cell->getCell()->isExterior()) { const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); if (dx > mHalfGridSize || dy > mHalfGridSize) unloadCell(cell); } else unloadCell (cell); } mNavigator.updateBounds(pos); mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); if (mRendering.pagingUnlockCache()) mPreloader->abortTerrainPreloadExcept(nullptr); if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime())) preloadTerrain(pos, true); mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; const auto cellsToLoad = [&] (CellStoreCollection& collection, int range) -> std::vector> { std::vector> cellsPositionsToLoad; for (int x = playerCellX - range; x <= playerCellX + range; ++x) { for (int y = playerCellY - range; y <= playerCellY + range; ++y) { if (!isCellInCollection(x, y, collection)) { refsToLoad += mWorld.getExterior(x, y)->count(); cellsPositionsToLoad.emplace_back(x, y); } } } return cellsPositionsToLoad; }; addPostponedPhysicsObjects(); auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); loadingListener->setProgressRange(refsToLoad); const auto getDistanceToPlayerCell = [&] (const std::pair& cellPosition) { return std::abs(cellPosition.first - playerCellX) + std::abs(cellPosition.second - playerCellY); }; const auto getCellPositionPriority = [&] (const std::pair& cellPosition) { return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition)); }; std::sort(cellsPositionsToLoad.begin(), cellsPositionsToLoad.end(), [&] (const std::pair& lhs, const std::pair& rhs) { return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); for (const auto& [x,y] : cellsPositionsToLoad) { if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = mWorld.getExterior(x, y); loadCell(cell, loadingListener, changeEvent, pos); } } CellStore* current = mWorld.getExterior(playerCellX, playerCellY); MWBase::Environment::get().getWindowManager()->changeCell(current); if (changeEvent) mCellChanged = true; mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); } void Scene::addPostponedPhysicsObjects() { for(const auto& cell : mActiveCells) { cell->forEach([&](const MWWorld::Ptr& ptr) { if(ptr.mRef->mData.mPhysicsPostponed) { ptr.mRef->mData.mPhysicsPostponed = false; if (ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) { std::string model = getModel(ptr, MWBase::Environment::get().getResourceSystem()->getVFS()); if (!model.empty()) { const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); ptr.getClass().insertObjectPhysics(ptr, model, rotation, *mPhysics); } } } return true; }); } } void Scene::testExteriorCells() { // Note: temporary disable ICO to decrease memory usage mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr); mRendering.getResourceSystem()->setExpiryDelay(1.f); const MWWorld::Store &cells = mWorld.getStore().get(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); loadingListener->setProgressRange(cells.getExtSize()); MWWorld::Store::iterator it = cells.extBegin(); int i = 1; for (; it != cells.extEnd(); ++it) { loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = mWorld.getExterior(it->mData.mX, it->mData.mY); loadCell(cell, nullptr, false, osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits); auto iter = mActiveCells.begin(); while (iter != mActiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { unloadCell(*iter); break; } ++iter; } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); loadingListener->increaseProgress (1); i++; } mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation()); mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); } void Scene::testInteriorCells() { // Note: temporary disable ICO to decrease memory usage mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr); mRendering.getResourceSystem()->setExpiryDelay(1.f); const MWWorld::Store &cells = mWorld.getStore().get(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); loadingListener->setProgressRange(cells.getIntSize()); int i = 1; MWWorld::Store::iterator it = cells.intBegin(); for (; it != cells.intEnd(); ++it) { loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = mWorld.getInterior(it->mName); ESM::Position position; mWorld.findInteriorPosition(it->mName, position); loadCell(cell, nullptr, false, position.asVec3()); auto iter = mActiveCells.begin(); while (iter != mActiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); if (it->mName == (*iter)->getCell()->mName) { unloadCell(*iter); break; } ++iter; } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); loadingListener->increaseProgress (1); i++; } mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation()); mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) { mCurrentCell = cell; mRendering.enableTerrain(cell->isExterior()); MWWorld::Ptr old = mWorld.getPlayerPtr(); mWorld.getPlayer().setCell(cell); MWWorld::Ptr player = mWorld.getPlayerPtr(); mRendering.updatePlayerPtr(player); // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, // we validate and correct this. Only run once, during initial cell load. if (old.mCell == cell) mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); if (adjustPlayerPos) { mWorld.moveObject(player, pos.asVec3()); mWorld.rotateObject(player, pos.asRotationVec3()); player.getClass().adjustPosition(player, true); } MWBase::Environment::get().getMechanicsManager()->updateCell(old, player); MWBase::Environment::get().getWindowManager()->watchActor(player); mPhysics->updatePtr(old, player); mWorld.adjustSky(); mLastPlayerPos = player.getRefData().getPosition().asVec3(); } Scene::Scene(MWWorld::World& world, MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) : mCurrentCell (nullptr), mCellChanged (false) , mWorld(world), mPhysics(physics), mRendering(rendering), mNavigator(navigator) , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) , mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells")) , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) , mPredictionTime(Settings::Manager::getFloat("prediction time", "Cells")) { mPreloader = std::make_unique(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()); mPreloader->setWorkQueue(mRendering.getWorkQueue()); rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells")); mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells")); mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells")); mPreloader->setPreloadInstances(Settings::Manager::getBool("preload instances", "Cells")); } Scene::~Scene() { for (const osg::ref_ptr& v : mWorkItems) v->abort(); for (const osg::ref_ptr& v : mWorkItems) v->waitTillDone(); } bool Scene::hasCellChanged() const { return mCellChanged; } const Scene::CellStoreCollection& Scene::getActiveCells() const { return mActiveCells; } void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { CellStore *cell = mWorld.getInterior(cellName); bool useFading = (mCurrentCell != nullptr); if (useFading) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); std::string loadingInteriorText = "#{sLoadingMessage2}"; loadingListener->setLabel(loadingInteriorText); Loading::ScopedLoad load(loadingListener); if(mCurrentCell != nullptr && *mCurrentCell == *cell) { mWorld.moveObject(mWorld.getPlayerPtr(), position.asVec3()); mWorld.rotateObject(mWorld.getPlayerPtr(), position.asRotationVec3()); if (adjustPlayerPos) mWorld.getPlayerPtr().getClass().adjustPosition(mWorld.getPlayerPtr(), true); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } Log(Debug::Info) << "Changing to interior"; // unload for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cellToUnload = *iter++; unloadCell(cellToUnload); } assert(mActiveCells.empty()); loadingListener->setProgressRange(cell->count()); mNavigator.updateBounds(position.asVec3()); // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent, position.asVec3()); changePlayerCell(cell, position, adjustPlayerPos); // adjust fog mRendering.configureFog(mCurrentCell->getCell()); // Sky system mWorld.adjustSky(); if (changeEvent) mCellChanged = true; if (useFading) MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { const osg::Vec2i cellIndex = positionToCellIndex(position.pos[0], position.pos[1]); if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); changeCellGrid(position.asVec3(), cellIndex.x(), cellIndex.y(), changeEvent); CellStore* current = mWorld.getExterior(cellIndex.x(), cellIndex.y()); changePlayerCell(current, position, adjustPlayerPos); if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(true); } CellStore* Scene::getCurrentCell () { return mCurrentCell; } void Scene::markCellAsUnchanged() { mCellChanged = false; } void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener) { InsertVisitor insertVisitor(cell, loadingListener); cell.forEach (insertVisitor); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); }); } void Scene::addObjectToScene (const Ptr& ptr) { try { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); addObject(ptr, mWorld, *mPhysics, mNavigator); mWorld.scaleObject(ptr, ptr.getCellRef().getScale()); if (mCurrentCell != nullptr) { const auto player = mWorld.getPlayerPtr(); mNavigator.update(player.getRefData().getPosition().asVec3()); } } catch (std::exception& e) { Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what(); } } void Scene::removeObjectFromScene (const Ptr& ptr, bool keepActive) { MWBase::Environment::get().getMechanicsManager()->remove (ptr, keepActive); // You'd expect the sounds attached to the object to be stopped here // because the object is nowhere to be heard, but in Morrowind, they're not. // They're still stopped when the cell is unloaded // or if the player moves away far from the object's position. // Todd Howard, Who art in Bethesda, hallowed be Thy name. MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); if (const auto object = mPhysics->getObject(ptr)) { if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) mNavigator.removeObject(DetourNavigator::ObjectId(object)); if (mCurrentCell != nullptr) { const auto player = mWorld.getPlayerPtr(); mNavigator.update(player.getRefData().getPosition().asVec3()); } } else if (mPhysics->getActor(ptr)) { mNavigator.removeAgent(mWorld.getPathfindingAgentBounds(ptr)); } mPhysics->remove(ptr); mRendering.removeObject (ptr); if (ptr.getClass().isActor()) mRendering.removeWaterRippleEmitter(ptr); ptr.getRefData().setBaseNode(nullptr); } bool Scene::isCellActive(const CellStore &cell) { CellStoreCollection::iterator active = mActiveCells.begin(); while (active != mActiveCells.end()) { if (**active == cell) { return true; } ++active; } return false; } Ptr Scene::searchPtrViaActorId (int actorId) { for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); iter!=mActiveCells.end(); ++iter) if (Ptr ptr = (*iter)->searchViaActorId (actorId)) return ptr; return Ptr(); } class PreloadMeshItem : public SceneUtil::WorkItem { public: PreloadMeshItem(const std::string& mesh, Resource::SceneManager* sceneManager) : mMesh(mesh), mSceneManager(sceneManager) { } void doWork() override { if (mAborted) return; try { mSceneManager->getTemplate(mMesh); } catch (std::exception&) { } } void abort() override { mAborted = true; } private: std::string mMesh; Resource::SceneManager* mSceneManager; std::atomic_bool mAborted {false}; }; void Scene::preload(const std::string &mesh, bool useAnim) { std::string mesh_ = mesh; if (useAnim) mesh_ = Misc::ResourceHelpers::correctActorModelPath(mesh_, mRendering.getResourceSystem()->getVFS()); if (!mRendering.getResourceSystem()->getSceneManager()->checkLoaded(mesh_, mRendering.getReferenceTime())) { osg::ref_ptr item(new PreloadMeshItem(mesh_, mRendering.getResourceSystem()->getSceneManager())); mRendering.getWorkQueue()->addWorkItem(item); const auto isDone = [] (const osg::ref_ptr& v) { return v->isDone(); }; mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end()); mWorkItems.emplace_back(std::move(item)); } } void Scene::preloadCells(float dt) { if (dt<=1e-06) return; std::vector exteriorPositions; const MWWorld::ConstPtr player = mWorld.getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f moved = playerPos - mLastPlayerPos; osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; if (mCurrentCell->isExterior()) exteriorPositions.emplace_back(predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter))); mLastPlayerPos = playerPos; if (mPreloadEnabled) { if (mPreloadDoors) preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions); if (mPreloadExteriorGrid) preloadExteriorGrid(playerPos, predictedPos); if (mPreloadFastTravel) preloadFastTravelDestinations(playerPos, predictedPos, exteriorPositions); } mPreloader->setTerrainPreloadPositions(exteriorPositions); } void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) { std::vector teleportDoors; for (const MWWorld::CellStore* cellStore : mActiveCells) { typedef MWWorld::CellRefList::List DoorList; const DoorList &doors = cellStore->getReadOnlyDoors().mList; for (auto& door : doors) { if (!door.mRef.getTeleport()) { continue; } teleportDoors.emplace_back(&door, cellStore); } } for (const MWWorld::ConstPtr& door : teleportDoors) { float sqrDistToPlayer = (playerPos - door.getRefData().getPosition().asVec3()).length2(); sqrDistToPlayer = std::min(sqrDistToPlayer, (predictedPos - door.getRefData().getPosition().asVec3()).length2()); if (sqrDistToPlayer < mPreloadDistance*mPreloadDistance) { try { if (!door.getCellRef().getDestCell().empty()) preloadCell(mWorld.getInterior(door.getCellRef().getDestCell())); else { osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3(); const osg::Vec2i cellIndex = positionToCellIndex(pos.x(), pos.y()); preloadCell(mWorld.getExterior(cellIndex.x(), cellIndex.y()), true); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } catch (std::exception&) { // ignore error for now, would spam the log too much } } } } void Scene::preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos) { if (!mWorld.isCellExterior()) return; int halfGridSizePlusOne = mHalfGridSize + 1; int cellX,cellY; cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y(); float centerX, centerY; mWorld.indexToPosition(cellX, cellY, centerX, centerY, true); for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx) { for (int dy = -halfGridSizePlusOne; dy <= halfGridSizePlusOne; ++dy) { if (dy != halfGridSizePlusOne && dy != -halfGridSizePlusOne && dx != halfGridSizePlusOne && dx != -halfGridSizePlusOne) continue; // only care about the outer (not yet loaded) part of the grid float thisCellCenterX, thisCellCenterY; mWorld.indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true); float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); dist = std::min(dist,std::max(std::abs(thisCellCenterX - predictedPos.x()), std::abs(thisCellCenterY - predictedPos.y()))); float loadDist = Constants::CellSizeInUnits / 2 + Constants::CellSizeInUnits - mCellLoadingThreshold + mPreloadDistance; if (dist < loadDist) preloadCell(mWorld.getExterior(cellX+dx, cellY+dy)); } } } void Scene::preloadCell(CellStore *cell, bool preloadSurrounding) { if (preloadSurrounding && cell->isExterior()) { int x = cell->getCell()->getGridX(); int y = cell->getCell()->getGridY(); unsigned int numpreloaded = 0; for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx) { for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) { mPreloader->preload(mWorld.getExterior(x+dx, y+dy), mRendering.getReferenceTime()); if (++numpreloaded >= mPreloader->getMaxCacheSize()) break; } } } else mPreloader->preload(cell, mRendering.getReferenceTime()); } void Scene::preloadTerrain(const osg::Vec3f &pos, bool sync) { std::vector vec; vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); if (!sync) return; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); loadingListener->setLabel("#{sLoadingMessage4}"); while (!mPreloader->syncTerrainLoad(vec, mRendering.getReferenceTime(), *loadingListener)) {} } void Scene::reloadTerrain() { mPreloader->setTerrainPreloadPositions(std::vector()); } struct ListFastTravelDestinationsVisitor { ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) : mPreloadDist(preloadDist) , mPlayerPos(playerPos) { } bool operator()(const MWWorld::Ptr& ptr) { if ((ptr.getRefData().getPosition().asVec3() - mPlayerPos).length2() > mPreloadDist * mPreloadDist) return true; if (ptr.getClass().isNpc()) { const std::vector& transport = ptr.get()->mBase->mTransport.mList; mList.insert(mList.begin(), transport.begin(), transport.end()); } else { const std::vector& transport = ptr.get()->mBase->mTransport.mList; mList.insert(mList.begin(), transport.begin(), transport.end()); } return true; } float mPreloadDist; osg::Vec3f mPlayerPos; std::vector mList; }; void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time { const MWWorld::ConstPtr player = mWorld.getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); for (MWWorld::CellStore* cellStore : mActiveCells) { cellStore->forEachType(listVisitor); cellStore->forEachType(listVisitor); } for (ESM::Transport::Dest& dest : listVisitor.mList) { if (!dest.mCellName.empty()) preloadCell(mWorld.getInterior(dest.mCellName)); else { osg::Vec3f pos = dest.mPos.asVec3(); const osg::Vec2i cellIndex = positionToCellIndex(pos.x(), pos.y()); preloadCell(mWorld.getExterior(cellIndex.x(), cellIndex.y()), true); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } } } openmw-openmw-0.48.0/apps/openmw/mwworld/scene.hpp000066400000000000000000000132271445372753700222120ustar00rootroot00000000000000#ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H #include #include #include #include "ptr.hpp" #include "globals.hpp" #include #include #include #include #include #include namespace osg { class Vec3f; } namespace ESM { struct Position; } namespace Files { class Collections; } namespace Loading { class Listener; } namespace DetourNavigator { struct Navigator; } namespace MWRender { class SkyManager; class RenderingManager; } namespace MWPhysics { class PhysicsSystem; } namespace SceneUtil { class WorkItem; } namespace MWWorld { class Player; class CellStore; class CellPreloader; class World; enum class RotationOrder { direct, inverse }; class Scene { public: using CellStoreCollection = std::set; private: struct ChangeCellGridRequest { osg::Vec3f mPosition; osg::Vec2i mCell; bool mChangeEvent; }; CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; bool mCellChanged; MWWorld::World& mWorld; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; DetourNavigator::Navigator& mNavigator; std::unique_ptr mPreloader; float mCellLoadingThreshold; float mPreloadDistance; bool mPreloadEnabled; bool mPreloadExteriorGrid; bool mPreloadDoors; bool mPreloadFastTravel; float mPredictionTime; static const int mHalfGridSize = Constants::CellGridRadius; osg::Vec3f mLastPlayerPos; std::vector mPagedRefs; std::vector> mWorkItems; std::optional mChangeCellGridRequest; void insertCell(CellStore &cell, Loading::Listener* loadingListener); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center void changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent = true); void requestChangeCellGrid(const osg::Vec3f &position, const osg::Vec2i& cell, bool changeEvent = true); typedef std::pair PositionCellGrid; void preloadCells(float dt); void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; void unloadCell(CellStore* cell); void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position); public: Scene(MWWorld::World& world, MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator); ~Scene(); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); void playerMoved (const osg::Vec3f& pos); void changePlayerCell(CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); CellStore *getCurrentCell(); const CellStoreCollection& getActiveCells () const; bool hasCellChanged() const; ///< Has the set of active cells changed, since the last frame? void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to interior cell. /// @param changeEvent Set cellChanged flag? void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to exterior cell. /// @param changeEvent Set cellChanged flag? void clear(); ///< Change into a void void markCellAsUnchanged(); void update(float duration); void addObjectToScene (const Ptr& ptr); ///< Add an object that already exists in the world model to the scene. void removeObjectFromScene (const Ptr& ptr, bool keepActive = false); ///< Remove an object from the scene, but not from the world model. void addPostponedPhysicsObjects(); void removeFromPagedRefs(const Ptr &ptr); void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); void updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics); bool isCellActive(const CellStore &cell); Ptr searchPtrViaActorId (int actorId); void preload(const std::string& mesh, bool useAnim=false); void testExteriorCells(); void testInteriorCells(); }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/spellcaststate.hpp000066400000000000000000000003511445372753700241420ustar00rootroot00000000000000#ifndef GAME_MWWORLD_SPELLCASTSTATE_H #define GAME_MWWORLD_SPELLCASTSTATE_H namespace MWWorld { enum class SpellCastState { Success = 0, InsufficientMagicka = 1, PowerAlreadyUsed = 2 }; } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/store.cpp000066400000000000000000001126341445372753700222460ustar00rootroot00000000000000#include "store.hpp" #include #include #include #include #include #include #include #include namespace MWWorld { RecordId::RecordId(const std::string &id, bool isDeleted) : mId(id), mIsDeleted(isDeleted) {} template IndexedStore::IndexedStore() { } template typename IndexedStore::iterator IndexedStore::begin() const { return mStatic.begin(); } template typename IndexedStore::iterator IndexedStore::end() const { return mStatic.end(); } template void IndexedStore::load(ESM::ESMReader &esm) { T record; bool isDeleted = false; record.load(esm, isDeleted); auto idx = record.mIndex; mStatic.insert_or_assign(idx, std::move(record)); } template int IndexedStore::getSize() const { return mStatic.size(); } template void IndexedStore::setUp() { } template const T *IndexedStore::search(int index) const { typename Static::const_iterator it = mStatic.find(index); if (it != mStatic.end()) return &(it->second); return nullptr; } template const T *IndexedStore::find(int index) const { const T *ptr = search(index); if (ptr == nullptr) { std::stringstream msg; msg << T::getRecordType() << " with index " << index << " not found"; throw std::runtime_error(msg.str()); } return ptr; } // Need to instantiate these before they're used template class IndexedStore; template class IndexedStore; template Store::Store() { } template Store::Store(const Store& orig) : mStatic(orig.mStatic) { } template void Store::clearDynamic() { // remove the dynamic part of mShared assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); mDynamic.clear(); } template const T *Store::search(const std::string &id) const { typename Dynamic::const_iterator dit = mDynamic.find(id); if (dit != mDynamic.end()) return &dit->second; typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } template const T *Store::searchStatic(const std::string &id) const { typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } template bool Store::isDynamic(const std::string &id) const { typename Dynamic::const_iterator dit = mDynamic.find(id); return (dit != mDynamic.end()); } template const T *Store::searchRandom(const std::string &id, Misc::Rng::Generator& prng) const { std::vector results; std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), [&id](const T* item) { return Misc::StringUtils::ciCompareLen(id, item->mId, id.size()) == 0; }); if(!results.empty()) return results[Misc::Rng::rollDice(results.size(), prng)]; return nullptr; } template const T *Store::find(const std::string &id) const { const T *ptr = search(id); if (ptr == nullptr) { std::stringstream msg; msg << T::getRecordType() << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; } template RecordId Store::load(ESM::ESMReader &esm) { T record; bool isDeleted = false; record.load(esm, isDeleted); Misc::StringUtils::lowerCaseInPlace(record.mId); // TODO: remove this line once we have ported our remaining code base to lowercase on lookup std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) mShared.push_back(&inserted.first->second); return RecordId(record.mId, isDeleted); } template void Store::setUp() { } template typename Store::iterator Store::begin() const { return mShared.begin(); } template typename Store::iterator Store::end() const { return mShared.end(); } template size_t Store::getSize() const { return mShared.size(); } template int Store::getDynamicSize() const { return mDynamic.size(); } template void Store::listIdentifier(std::vector &list) const { list.reserve(list.size() + getSize()); typename std::vector::const_iterator it = mShared.begin(); for (; it != mShared.end(); ++it) { list.push_back((*it)->mId); } } template T *Store::insert(const T &item, bool overrideOnly) { if(overrideOnly) { auto it = mStatic.find(item.mId); if(it == mStatic.end()) return nullptr; } std::pair result = mDynamic.insert_or_assign(item.mId, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template T *Store::insertStatic(const T &item) { std::pair result = mStatic.insert_or_assign(item.mId, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template bool Store::eraseStatic(const std::string &id) { typename Static::iterator it = mStatic.find(id); if (it != mStatic.end()) { // delete from the static part of mShared typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); while (sharedIter != mShared.end() && sharedIter != end) { if(Misc::StringUtils::ciEqual((*sharedIter)->mId, id)) { mShared.erase(sharedIter); break; } ++sharedIter; } mStatic.erase(it); } return true; } template bool Store::erase(const std::string &id) { if (!mDynamic.erase(id)) return false; // have to reinit the whole shared part assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); for (auto it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); } return true; } template bool Store::erase(const T &item) { return erase(item.mId); } template void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); ++iter) { writer.startRecord (T::sRecordId); iter->second.save (writer); writer.endRecord (T::sRecordId); } } template RecordId Store::read(ESM::ESMReader& reader, bool overrideOnly) { T record; bool isDeleted = false; record.load (reader, isDeleted); insert (record, overrideOnly); return RecordId(record.mId, isDeleted); } // LandTexture //========================================================================= Store::Store() { } const ESM::LandTexture *Store::search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; if (index >= ltexl.size()) return nullptr; return <exl[index]; } const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); if (ptr == nullptr) { const std::string msg = "Land texture with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); } return ptr; } size_t Store::getSize() const { return mStatic.size(); } size_t Store::getSize(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].size(); } RecordId Store::load(ESM::ESMReader &esm) { ESM::LandTexture lt; bool isDeleted = false; lt.load(esm, isDeleted); // Replace texture for records with given ID and index from all plugins. for (unsigned int i=0; i(search(lt.mIndex, i)); if (tex) { if (Misc::StringUtils::ciEqual(tex->mId, lt.mId)) tex->mTexture = lt.mTexture; } } LandTextureList <exl = mStatic.back(); if(lt.mIndex + 1 > (int)ltexl.size()) ltexl.resize(lt.mIndex+1); // Store it auto idx = lt.mIndex; ltexl[idx] = std::move(lt); return RecordId(ltexl[idx].mId, isDeleted); } Store::iterator Store::begin(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].begin(); } Store::iterator Store::end(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].end(); } // Land //========================================================================= Store::~Store() { } size_t Store::getSize() const { return mStatic.size(); } Store::iterator Store::begin() const { return iterator(mStatic.begin()); } Store::iterator Store::end() const { return iterator(mStatic.end()); } const ESM::Land *Store::search(int x, int y) const { std::pair comp(x,y); if (auto it = mStatic.find(comp); it != mStatic.end() && it->mX == x && it->mY == y) return &*it; return nullptr; } const ESM::Land *Store::find(int x, int y) const { const ESM::Land *ptr = search(x, y); if (ptr == nullptr) { const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); } return ptr; } RecordId Store::load(ESM::ESMReader &esm) { ESM::Land land; bool isDeleted = false; land.load(esm, isDeleted); // Same area defined in multiple plugins? -> last plugin wins auto it = mStatic.lower_bound(land); if (it != mStatic.end() && (std::tie(it->mX, it->mY) == std::tie(land.mX, land.mY))) { auto nh = mStatic.extract(it); nh.value() = std::move(land); mStatic.insert(std::move(nh)); } else mStatic.insert(it, std::move(land)); return RecordId("", isDeleted); } void Store::setUp() { // The land is static for given game session, there is no need to refresh it every load. if (mBuilt) return; mBuilt = true; } // Cell //========================================================================= const ESM::Cell *Store::search(const ESM::Cell &cell) const { if (cell.isExterior()) { return search(cell.getGridX(), cell.getGridY()); } return search(cell.mName); } // this method *must* be called right after esm3.loadCell() void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { ESM::CellRef ref; ESM::MovedCellRef cMRef; bool deleted = false; bool moved = false; ESM::ESM_Context ctx = esm.getContext(); // Handling MovedCellRefs, there is no way to do it inside loadcell // TODO: verify above comment // // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. while (ESM::Cell::getNextRef(esm, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyMoved)) { if (!moved) continue; ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); // Add data required to make reference appear in the correct cell. // We should not need to test for duplicates, as this part of the code is pre-cell merge. cell->mMovedRefs.push_back(cMRef); // But there may be duplicates here! ESM::CellRefTracker::iterator iter = std::find_if(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(ref.mRefNum)); if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.emplace_back(std::move(ref), deleted); else *iter = std::make_pair(std::move(ref), deleted); cMRef.mRefNum.mIndex = 0; } esm.restoreContext(ctx); } const ESM::Cell *Store::search(const std::string &id) const { DynamicInt::const_iterator it = mInt.find(id); if (it != mInt.end()) { return &(it->second); } DynamicInt::const_iterator dit = mDynamicInt.find(id); if (dit != mDynamicInt.end()) { return &dit->second; } return nullptr; } const ESM::Cell *Store::search(int x, int y) const { std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); if (it != mExt.end()) return &(it->second); DynamicExt::const_iterator dit = mDynamicExt.find(key); if (dit != mDynamicExt.end()) return &dit->second; return nullptr; } const ESM::Cell *Store::searchStatic(int x, int y) const { DynamicExt::const_iterator it = mExt.find(std::make_pair(x,y)); if (it != mExt.end()) return &(it->second); return nullptr; } const ESM::Cell *Store::searchOrCreate(int x, int y) { std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); if (it != mExt.end()) return &(it->second); DynamicExt::const_iterator dit = mDynamicExt.find(key); if (dit != mDynamicExt.end()) return &dit->second; ESM::Cell newCell; newCell.mData.mX = x; newCell.mData.mY = y; newCell.mData.mFlags = ESM::Cell::HasWater; newCell.mAmbi.mAmbient = 0; newCell.mAmbi.mSunlight = 0; newCell.mAmbi.mFog = 0; newCell.mAmbi.mFogDensity = 0; newCell.mCellId.mPaged = true; newCell.mCellId.mIndex.mX = x; newCell.mCellId.mIndex.mY = y; return &mExt.insert(std::make_pair(key, newCell)).first->second; } const ESM::Cell *Store::find(const std::string &id) const { const ESM::Cell *ptr = search(id); if (ptr == nullptr) { const std::string msg = "Cell '" + id + "' not found"; throw std::runtime_error(msg); } return ptr; } const ESM::Cell *Store::find(int x, int y) const { const ESM::Cell *ptr = search(x, y); if (ptr == nullptr) { const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); } return ptr; } void Store::clearDynamic() { setUp(); } void Store::setUp() { mSharedInt.clear(); mSharedInt.reserve(mInt.size()); for (auto & [_, cell] : mInt) mSharedInt.push_back(&cell); mSharedExt.clear(); mSharedExt.reserve(mExt.size()); for (auto & [_, cell] : mExt) mSharedExt.push_back(&cell); } RecordId Store::load(ESM::ESMReader &esm) { // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, // and we merge all this data into one Cell object. However, we can't simply search for the cell id, // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they // are not available until both cells have been loaded at least partially! // All cells have a name record, even nameless exterior cells. ESM::Cell cell; bool isDeleted = false; // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with cell.loadNameAndData(esm, isDeleted); if(cell.mData.mFlags & ESM::Cell::Interior) { // Store interior cell by name, try to merge with existing parent data. ESM::Cell *oldcell = const_cast(search(cell.mName)); if (oldcell) { // merge new cell into old cell // push the new references on the list of references to manage (saveContext = true) oldcell->mData = cell.mData; oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) oldcell->loadCell(esm, true); } else { // spawn a new cell cell.loadCell(esm, true); mInt[cell.mName] = cell; } } else { // Store exterior cells by grid position, try to merge with existing parent data. ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); if (oldcell) { // merge new cell into old cell oldcell->mData = cell.mData; oldcell->mName = cell.mName; oldcell->loadCell(esm, false); // handle moved ref (MVRF) subrecords handleMovedCellRefs (esm, &cell); // push the new references on the list of references to manage oldcell->postLoad(esm); // merge lists of leased references, use newer data in case of conflict for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); if (itold != oldcell->mMovedRefs.end()) { if (it->mTarget[0] != itold->mTarget[0] || it->mTarget[1] != itold->mTarget[1]) { ESM::Cell *wipecell = const_cast(search(itold->mTarget[0], itold->mTarget[1])); ESM::CellRefTracker::iterator it_lease = std::find_if(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(it->mRefNum)); if (it_lease != wipecell->mLeasedRefs.end()) wipecell->mLeasedRefs.erase(it_lease); else Log(Debug::Error) << "Error: can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs"; } *itold = *it; } else oldcell->mMovedRefs.push_back(*it); } // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a // reference to this cell, so the list for the new cell should be empty. The list for oldcell, // however, could have leased refs in it and so should be kept. } else { // spawn a new cell cell.loadCell(esm, false); // handle moved ref (MVRF) subrecords handleMovedCellRefs (esm, &cell); // push the new references on the list of references to manage cell.postLoad(esm); mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; } } return RecordId(cell.mName, isDeleted); } Store::iterator Store::intBegin() const { return iterator(mSharedInt.begin()); } Store::iterator Store::intEnd() const { return iterator(mSharedInt.end()); } Store::iterator Store::extBegin() const { return iterator(mSharedExt.begin()); } Store::iterator Store::extEnd() const { return iterator(mSharedExt.end()); } const ESM::Cell *Store::searchExtByName(const std::string &id) const { const ESM::Cell *cell = nullptr; for (const ESM::Cell *sharedCell : mSharedExt) { if (Misc::StringUtils::ciEqual(sharedCell->mName, id)) { if (cell == nullptr || (sharedCell->mData.mX > cell->mData.mX) || (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { cell = sharedCell; } } } return cell; } const ESM::Cell *Store::searchExtByRegion(const std::string &id) const { const ESM::Cell *cell = nullptr; for (const ESM::Cell *sharedCell : mSharedExt) { if (Misc::StringUtils::ciEqual(sharedCell->mRegion, id)) { if (cell == nullptr || (sharedCell->mData.mX > cell->mData.mX) || (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { cell = sharedCell; } } } return cell; } size_t Store::getSize() const { return mSharedInt.size() + mSharedExt.size(); } size_t Store::getExtSize() const { return mSharedExt.size(); } size_t Store::getIntSize() const { return mSharedInt.size(); } void Store::listIdentifier(std::vector &list) const { list.reserve(list.size() + mSharedInt.size()); for (const ESM::Cell *sharedCell : mSharedInt) { list.push_back(sharedCell->mName); } } ESM::Cell *Store::insert(const ESM::Cell &cell) { if (search(cell) != nullptr) { const std::string cellType = (cell.isExterior()) ? "exterior" : "interior"; throw std::runtime_error("Failed to create " + cellType + " cell"); } if (cell.isExterior()) { std::pair key(cell.getGridX(), cell.getGridY()); // duplicate insertions are avoided by search(ESM::Cell &) DynamicExt::iterator result = mDynamicExt.emplace(key, cell).first; mSharedExt.push_back(&result->second); return &result->second; } else { // duplicate insertions are avoided by search(ESM::Cell &) DynamicInt::iterator result = mDynamicInt.emplace(cell.mName, cell).first; mSharedInt.push_back(&result->second); return &result->second; } } bool Store::erase(const ESM::Cell &cell) { if (cell.isExterior()) { return erase(cell.getGridX(), cell.getGridY()); } return erase(cell.mName); } bool Store::erase(const std::string &id) { DynamicInt::iterator it = mDynamicInt.find(id); if (it == mDynamicInt.end()) { return false; } mDynamicInt.erase(it); mSharedInt.erase( mSharedInt.begin() + mSharedInt.size(), mSharedInt.end() ); for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { mSharedInt.push_back(&it->second); } return true; } bool Store::erase(int x, int y) { std::pair key(x, y); DynamicExt::iterator it = mDynamicExt.find(key); if (it == mDynamicExt.end()) { return false; } mDynamicExt.erase(it); mSharedExt.erase( mSharedExt.begin() + mSharedExt.size(), mSharedExt.end() ); for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { mSharedExt.push_back(&it->second); } return true; } // Pathgrid //========================================================================= Store::Store() : mCells(nullptr) { } void Store::setCells(Store& cells) { mCells = &cells; } RecordId Store::load(ESM::ESMReader &esm) { ESM::Pathgrid pathgrid; bool isDeleted = false; pathgrid.load(esm, isDeleted); // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. // A proper fix should be made for future versions of the file format. bool interior = pathgrid.mData.mX == 0 && pathgrid.mData.mY == 0 && mCells->search(pathgrid.mCell) != nullptr; // deal with mods that have empty pathgrid records (Issue #6209) // we assume that these records are empty on purpose (i.e. to remove old pathgrid on an updated cell) if (isDeleted || pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) { if (interior) { Interior::iterator it = mInt.find(pathgrid.mCell); if (it != mInt.end()) mInt.erase(it); } else { Exterior::iterator it = mExt.find(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY)); if (it != mExt.end()) mExt.erase(it); } return RecordId("", isDeleted); } // Try to overwrite existing record if (interior) { std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); if (!ret.second) ret.first->second = pathgrid; } else { std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); if (!ret.second) ret.first->second = pathgrid; } return RecordId("", isDeleted); } size_t Store::getSize() const { return mInt.size() + mExt.size(); } void Store::setUp() { } const ESM::Pathgrid *Store::search(int x, int y) const { Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); if (it != mExt.end()) return &(it->second); return nullptr; } const ESM::Pathgrid *Store::search(const std::string& name) const { Interior::const_iterator it = mInt.find(name); if (it != mInt.end()) return &(it->second); return nullptr; } const ESM::Pathgrid *Store::find(int x, int y) const { const ESM::Pathgrid* pathgrid = search(x,y); if (!pathgrid) { const std::string msg = "Pathgrid in cell '" + std::to_string(x) + " " + std::to_string(y) + "' not found"; throw std::runtime_error(msg); } return pathgrid; } const ESM::Pathgrid* Store::find(const std::string& name) const { const ESM::Pathgrid* pathgrid = search(name); if (!pathgrid) { const std::string msg = "Pathgrid in cell '" + name + "' not found"; throw std::runtime_error(msg); } return pathgrid; } const ESM::Pathgrid *Store::search(const ESM::Cell &cell) const { if (!(cell.mData.mFlags & ESM::Cell::Interior)) return search(cell.mData.mX, cell.mData.mY); else return search(cell.mName); } const ESM::Pathgrid *Store::find(const ESM::Cell &cell) const { if (!(cell.mData.mFlags & ESM::Cell::Interior)) return find(cell.mData.mX, cell.mData.mY); else return find(cell.mName); } // Skill //========================================================================= Store::Store() { } // Magic effect //========================================================================= Store::Store() { } // Attribute //========================================================================= Store::Store() { mStatic.reserve(ESM::Attribute::Length); } const ESM::Attribute *Store::search(size_t index) const { if (index >= mStatic.size()) { return nullptr; } return &mStatic[index]; } const ESM::Attribute *Store::find(size_t index) const { const ESM::Attribute *ptr = search(index); if (ptr == nullptr) { const std::string msg = "Attribute with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); } return ptr; } void Store::setUp() { for (int i = 0; i < ESM::Attribute::Length; ++i) { ESM::Attribute newAttribute; newAttribute.mId = ESM::Attribute::sAttributeIds[i]; newAttribute.mName = ESM::Attribute::sGmstAttributeIds[i]; newAttribute.mDescription = ESM::Attribute::sGmstAttributeDescIds[i]; mStatic.push_back(newAttribute); } } size_t Store::getSize() const { return mStatic.size(); } Store::iterator Store::begin() const { return mStatic.begin(); } Store::iterator Store::end() const { return mStatic.end(); } // Dialogue //========================================================================= Store::Store() : mKeywordSearchModFlag(true) { } void Store::setUp() { // DialInfos marked as deleted are kept during the loading phase, so that the linked list // structure is kept intact for inserting further INFOs. Delete them now that loading is done. for (auto & [_, dial] : mStatic) dial.clearDeletedInfos(); mShared.clear(); mShared.reserve(mStatic.size()); for (auto & [_, dial] : mStatic) mShared.push_back(&dial); // TODO: verify and document this inconsistent behaviour // TODO: if we require this behaviour, maybe we should move it to the place that requires it std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); mKeywordSearchModFlag = true; } const ESM::Dialogue *Store::search(const std::string &id) const { typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } const ESM::Dialogue *Store::find(const std::string &id) const { const ESM::Dialogue *ptr = search(id); if (ptr == nullptr) { std::stringstream msg; msg << ESM::Dialogue::getRecordType() << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; } typename Store::iterator Store::begin() const { return mShared.begin(); } typename Store::iterator Store::end() const { return mShared.end(); } size_t Store::getSize() const { return mShared.size(); } inline RecordId Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; bool isDeleted = false; dialogue.loadId(esm); Static::iterator found = mStatic.find(dialogue.mId); if (found == mStatic.end()) { dialogue.loadData(esm, isDeleted); mStatic.emplace(dialogue.mId, dialogue); } else { found->second.loadData(esm, isDeleted); dialogue.mId = found->second.mId; } mKeywordSearchModFlag = true; return RecordId(dialogue.mId, isDeleted); } bool Store::eraseStatic(const std::string &id) { if (mStatic.erase(id)) mKeywordSearchModFlag = true; return true; } void Store::listIdentifier(std::vector& list) const { list.reserve(list.size() + getSize()); for (const auto& dialogue : mShared) list.push_back(dialogue->mId); } const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() const { if (mKeywordSearchModFlag) { mKeywordSearch.clear(); std::vector keywordList; keywordList.reserve(getSize()); for (const auto& it : *this) keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); sort(keywordList.begin(), keywordList.end()); for (const auto& it : keywordList) mKeywordSearch.seed(it, 0 /*unused*/); mKeywordSearchModFlag = false; } return mKeywordSearch; } } template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; openmw-openmw-0.48.0/apps/openmw/mwworld/store.hpp000066400000000000000000000342711445372753700222530ustar00rootroot00000000000000#ifndef OPENMW_MWWORLD_STORE_H #define OPENMW_MWWORLD_STORE_H #include #include #include #include #include #include #include #include #include #include "../mwdialogue/keywordsearch.hpp" namespace ESM { struct Land; } namespace Loading { class Listener; } namespace MWWorld { struct RecordId { std::string mId; bool mIsDeleted; RecordId(const std::string &id = "", bool isDeleted = false); }; class StoreBase { public: virtual ~StoreBase() {} virtual void setUp() {} /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string IDs. virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} virtual RecordId read (ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); } ///< Read into dynamic storage }; template class IndexedStore { protected: typedef typename std::map Static; Static mStatic; public: typedef typename std::map::const_iterator iterator; IndexedStore(); iterator begin() const; iterator end() const; void load(ESM::ESMReader &esm); int getSize() const; void setUp(); const T *search(int index) const; // calls `search` and throws an exception if not found const T *find(int index) const; }; template > class SharedIterator { typedef typename Container::const_iterator Iter; Iter mIter; public: SharedIterator() {} SharedIterator(const SharedIterator &orig) : mIter(orig.mIter) {} SharedIterator(const Iter &iter) : mIter(iter) {} SharedIterator& operator=(const SharedIterator&) = default; SharedIterator &operator++() { ++mIter; return *this; } SharedIterator operator++(int) { SharedIterator iter = *this; ++mIter; return iter; } SharedIterator &operator+=(int advance) { mIter += advance; return *this; } SharedIterator &operator--() { --mIter; return *this; } SharedIterator operator--(int) { SharedIterator iter = *this; --mIter; return iter; } bool operator==(const SharedIterator &x) const { return mIter == x.mIter; } bool operator!=(const SharedIterator &x) const { return !(*this == x); } const T &operator*() const { return **mIter; } const T *operator->() const { return &(**mIter); } }; class ESMStore; template class Store : public StoreBase { typedef std::unordered_map Static; Static mStatic; /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) std::vector mShared; typedef std::unordered_map Dynamic; Dynamic mDynamic; friend class ESMStore; public: Store(); Store(const Store &orig); typedef SharedIterator iterator; // setUp needs to be called again after void clearDynamic() override; void setUp() override; const T *search(const std::string &id) const; const T *searchStatic(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? */ bool isDynamic(const std::string &id) const; /** Returns a random record that starts with the named ID, or nullptr if not found. */ const T *searchRandom(const std::string &id, Misc::Rng::Generator& prng) const; // calls `search` and throws an exception if not found const T *find(const std::string &id) const; iterator begin() const; iterator end() const; size_t getSize() const override; int getDynamicSize() const override; /// @note The record identifiers are listed in the order that the records were defined by the content files. void listIdentifier(std::vector &list) const override; T *insert(const T &item, bool overrideOnly = false); T *insertStatic(const T &item); bool eraseStatic(const std::string &id) override; bool erase(const std::string &id); bool erase(const T &item); RecordId load(ESM::ESMReader &esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; }; template <> class Store : public StoreBase { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; public: Store(); typedef std::vector::const_iterator iterator; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased const ESM::LandTexture *search(size_t index, size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const; void resize(size_t num) { mStatic.resize(num); } size_t getSize() const override; size_t getSize(size_t plugin) const; RecordId load(ESM::ESMReader &esm) override; iterator begin(size_t plugin) const; iterator end(size_t plugin) const; }; template <> class Store : public StoreBase { struct SpatialComparator { using is_transparent = void; bool operator()(const ESM::Land& x, const ESM::Land& y) const { return std::tie(x.mX, x.mY) < std::tie(y.mX, y.mY); } bool operator()(const ESM::Land& x, const std::pair& y) const { return std::tie(x.mX, x.mY) < std::tie(y.first, y.second); } bool operator()(const std::pair& x, const ESM::Land& y) const { return std::tie(x.first, x.second) < std::tie(y.mX, y.mY); } }; using Statics = std::set; Statics mStatic; public: typedef typename Statics::iterator iterator; virtual ~Store(); size_t getSize() const override; iterator begin() const; iterator end() const; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::Land can never be modified or inserted/erased const ESM::Land *search(int x, int y) const; const ESM::Land *find(int x, int y) const; RecordId load(ESM::ESMReader &esm) override; void setUp() override; private: bool mBuilt = false; }; template <> class Store : public StoreBase { struct DynamicExtCmp { bool operator()(const std::pair &left, const std::pair &right) const { if (left.first == right.first && left.second == right.second) return false; if (left.first == right.first) return left.second > right.second; // Exterior cells are listed in descending, row-major order, // this is a workaround for an ambiguous chargen_plank reference in the vanilla game. // there is one at -22,16 and one at -2,-9, the latter should be used. return left.first > right.first; } }; typedef std::unordered_map DynamicInt; typedef std::map, ESM::Cell, DynamicExtCmp> DynamicExt; DynamicInt mInt; DynamicExt mExt; std::vector mSharedInt; std::vector mSharedExt; DynamicInt mDynamicInt; DynamicExt mDynamicExt; const ESM::Cell *search(const ESM::Cell &cell) const; void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: typedef SharedIterator iterator; const ESM::Cell *search(const std::string &id) const; const ESM::Cell *search(int x, int y) const; const ESM::Cell *searchStatic(int x, int y) const; const ESM::Cell *searchOrCreate(int x, int y); const ESM::Cell *find(const std::string &id) const; const ESM::Cell *find(int x, int y) const; void clearDynamic() override; void setUp() override; RecordId load(ESM::ESMReader &esm) override; iterator intBegin() const; iterator intEnd() const; iterator extBegin() const; iterator extEnd() const; // Return the northernmost cell in the easternmost column. const ESM::Cell *searchExtByName(const std::string &id) const; // Return the northernmost cell in the easternmost column. const ESM::Cell *searchExtByRegion(const std::string &id) const; size_t getSize() const override; size_t getExtSize() const; size_t getIntSize() const; void listIdentifier(std::vector &list) const override; ESM::Cell *insert(const ESM::Cell &cell); bool erase(const ESM::Cell &cell); bool erase(const std::string &id); bool erase(int x, int y); }; template <> class Store : public StoreBase { private: typedef std::unordered_map Interior; typedef std::map, ESM::Pathgrid> Exterior; Interior mInt; Exterior mExt; Store* mCells; public: Store(); void setCells(Store& cells); RecordId load(ESM::ESMReader &esm) override; size_t getSize() const override; void setUp() override; const ESM::Pathgrid *search(int x, int y) const; const ESM::Pathgrid *search(const std::string& name) const; const ESM::Pathgrid *find(int x, int y) const; const ESM::Pathgrid* find(const std::string& name) const; const ESM::Pathgrid *search(const ESM::Cell &cell) const; const ESM::Pathgrid *find(const ESM::Cell &cell) const; }; template <> class Store : public IndexedStore { public: Store(); }; template <> class Store : public IndexedStore { public: Store(); }; template <> class Store : public IndexedStore { std::vector mStatic; public: typedef std::vector::const_iterator iterator; Store(); const ESM::Attribute *search(size_t index) const; // calls `search` and throws an exception if not found const ESM::Attribute *find(size_t index) const; void setUp(); size_t getSize() const; iterator begin() const; iterator end() const; }; template <> class Store : public StoreBase { std::map mStatic; public: typedef std::map::const_iterator iterator; Store(); const ESM::WeaponType *search(const int id) const; // calls `search` and throws an exception if not found const ESM::WeaponType *find(const int id) const; RecordId load(ESM::ESMReader &esm) override { return RecordId({}, false); } ESM::WeaponType* insert(const ESM::WeaponType &weaponType); void setUp() override; size_t getSize() const override; iterator begin() const; iterator end() const; }; template <> class Store : public StoreBase { typedef std::unordered_map Static; Static mStatic; /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. std::vector mShared; mutable bool mKeywordSearchModFlag; mutable MWDialogue::KeywordSearch mKeywordSearch; public: Store(); typedef SharedIterator iterator; void setUp() override; const ESM::Dialogue *search(const std::string &id) const; const ESM::Dialogue *find(const std::string &id) const; iterator begin() const; iterator end() const; size_t getSize() const override; bool eraseStatic(const std::string &id) override; RecordId load(ESM::ESMReader &esm) override; void listIdentifier(std::vector &list) const override; const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; } //end namespace #endif openmw-openmw-0.48.0/apps/openmw/mwworld/timestamp.cpp000066400000000000000000000052521445372753700231120ustar00rootroot00000000000000#include "timestamp.hpp" #include #include #include #include #include "duration.hpp" namespace MWWorld { TimeStamp::TimeStamp (float hour, int day) : mHour (hour), mDay (day) { if (hour < 0 || hour >= 24) throw std::runtime_error("invalid time stamp hour: " + std::to_string(hour)); } float TimeStamp::getHour() const { return mHour; } int TimeStamp::getDay() const { return mDay; } TimeStamp& TimeStamp::operator+= (double hours) { if (hours<0) throw std::runtime_error ("can't move time stamp backwards in time"); const Duration duration = Duration::fromHours(mHour + hours); mHour = duration.getHours(); mDay += duration.getDays(); return *this; } bool operator== (const TimeStamp& left, const TimeStamp& right) { return left.getHour()==right.getHour() && left.getDay()==right.getDay(); } bool operator!= (const TimeStamp& left, const TimeStamp& right) { return !(left==right); } bool operator< (const TimeStamp& left, const TimeStamp& right) { if (left.getDay()right.getDay()) return false; return left.getHour() (const TimeStamp& left, const TimeStamp& right) { return !(left<=right); } bool operator>= (const TimeStamp& left, const TimeStamp& right) { return !(left=0 explicit TimeStamp (const ESM::TimeStamp& esm); ESM::TimeStamp toEsm () const; float getHour() const; int getDay() const; TimeStamp& operator+= (double hours); ///< \param hours >=0 }; bool operator== (const TimeStamp& left, const TimeStamp& right); bool operator!= (const TimeStamp& left, const TimeStamp& right); bool operator< (const TimeStamp& left, const TimeStamp& right); bool operator<= (const TimeStamp& left, const TimeStamp& right); bool operator> (const TimeStamp& left, const TimeStamp& right); bool operator>= (const TimeStamp& left, const TimeStamp& right); TimeStamp operator+ (const TimeStamp& stamp, double hours); TimeStamp operator+ (double hours, const TimeStamp& stamp); double operator- (const TimeStamp& left, const TimeStamp& right); ///< Returns the difference between \a left and \a right in in-game hours. } #endif openmw-openmw-0.48.0/apps/openmw/mwworld/weather.cpp000066400000000000000000001564631445372753700225610ustar00rootroot00000000000000#include "weather.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwsound/sound.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/sky.hpp" #include "player.hpp" #include "esmstore.hpp" #include "cellstore.hpp" #include namespace MWWorld { namespace { static const int invalidWeatherID = -1; // linear interpolate between x and y based on factor. float lerp (float x, float y, float factor) { return x * (1-factor) + y * factor; } // linear interpolate between x and y based on factor. osg::Vec4f lerp (const osg::Vec4f& x, const osg::Vec4f& y, float factor) { return x * (1-factor) + y * factor; } osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; osg::Vec3f redMountainPos = osg::Vec3f(25000.f, 70000.f, 0.f); stormDirection = (playerPos - redMountainPos); stormDirection.normalize(); } return stormDirection; } } template T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const { WeatherSetting setting = timeSettings.getSetting(prefix); float preSunriseTime = setting.mPreSunriseTime; float postSunriseTime = setting.mPostSunriseTime; float preSunsetTime = setting.mPreSunsetTime; float postSunsetTime = setting.mPostSunsetTime; // night if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) return mNightValue; // sunrise else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; if (gameHour <= middle) { // fade in float advance = middle - gameHour; float factor = 0.f; if (duration > 0) factor = advance / duration * 2; return lerp(mSunriseValue, mNightValue, factor); } else { // fade out float advance = gameHour - middle; float factor = 1.f; if (duration > 0) factor = advance / duration * 2; return lerp(mSunriseValue, mDayValue, factor); } } // day else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) return mDayValue; // sunset else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) { float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; if (gameHour <= middle) { // fade in float advance = middle - gameHour; float factor = 0.f; if (duration > 0) factor = advance / duration * 2; return lerp(mSunsetValue, mDayValue, factor); } else { // fade out float advance = gameHour - middle; float factor = 1.f; if (duration > 0) factor = advance / duration * 2; return lerp(mSunsetValue, mNightValue, factor); } } // shut up compiler return T(); } template class MWWorld::TimeOfDayInterpolator; template class MWWorld::TimeOfDayInterpolator; osg::Vec3f Weather::defaultDirection() { static const osg::Vec3f direction = osg::Vec3f(0.f, 1.f, 0.f); return direction; } Weather::Weather(const std::string& name, float stormWindSpeed, float rainSpeed, float dlFactor, float dlOffset, const std::string& particleEffect) : mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture")) , mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"), Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"), Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"), Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color")) , mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"), Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"), Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"), Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color")) , mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"), Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"), Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"), Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color")) , mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"), Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"), Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"), Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color")) , mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth")) , mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) , mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed")) , mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed")) , mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View")) , mIsStorm(mWindSpeed > stormWindSpeed) , mRainSpeed(rainSpeed) , mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed")) , mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops")) , mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter")) , mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold")) , mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min")) , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) , mParticleEffect(particleEffect) , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") , mStormDirection(Weather::defaultDirection()) , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) , mThunderSoundID() , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) , mFlashBrightness(0.0f) { mDL.FogFactor = dlFactor; mDL.FogOffset = dlOffset; mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0"); mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1"); mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2"); mThunderSoundID[3] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3"); // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time. if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect { mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID"); if (mAmbientLoopSoundID.empty()) // default to "rain" if not set mAmbientLoopSoundID = "rain"; } else mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID"); if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) mAmbientLoopSoundID.clear(); } float Weather::transitionDelta() const { // Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the // measurement is in real time, not in-game time. return mTransitionDelta; } float Weather::cloudBlendFactor(const float transitionRatio) const { // Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next. return transitionRatio / mCloudsMaximumPercent; } float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused) { // When paused, the flash brightness remains the same and no new strikes can occur. if(!isPaused) { // Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold. if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f) { flashDecrement(elapsedSeconds); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if(Misc::Rng::rollProbability(prng) <= thunderChance(transitionRatio, elapsedSeconds)) { lightningAndThunder(); } } else { mFlashBrightness = 0.0f; } } return mFlashBrightness; } inline void Weather::flashDecrement(const float elapsedSeconds) { // The Flash Decrement is measured in whole units per second. This means that if the flash brightness was // currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum). float decrement = mFlashDecrement * elapsedSeconds; mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement; } inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const { // This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes // per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of // Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to // scaled based on how far past it is past the Thunder Threshold. float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold); return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor; } inline void Weather::lightningAndThunder(void) { // Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects. // They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance // was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0. // TODO: Determine the distribution of each distance to see if it's evenly weighted. auto& prng = MWBase::Environment::get().getWorld()->getPrng(); unsigned int distance = Misc::Rng::rollDice(4, prng); // Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0. mFlashBrightness += 1 - (distance * 0.25f); MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0); } RegionWeather::RegionWeather(const ESM::Region& region) : mWeather(invalidWeatherID) , mChances() { mChances.reserve(10); mChances.push_back(region.mData.mClear); mChances.push_back(region.mData.mCloudy); mChances.push_back(region.mData.mFoggy); mChances.push_back(region.mData.mOvercast); mChances.push_back(region.mData.mRain); mChances.push_back(region.mData.mThunder); mChances.push_back(region.mData.mAsh); mChances.push_back(region.mData.mBlight); mChances.push_back(region.mData.mSnow); mChances.push_back(region.mData.mBlizzard); } RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) : mWeather(state.mWeather) , mChances(state.mChances) { } RegionWeather::operator ESM::RegionWeatherState() const { ESM::RegionWeatherState state = { mWeather, mChances }; return state; } void RegionWeather::setChances(const std::vector& chances) { if(mChances.size() < chances.size()) { mChances.reserve(chances.size()); } int i = 0; for(char chance : chances) { mChances[i] = chance; i++; } // Regional weather no longer supports the current type, select a new weather pattern. if((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) { chooseNewWeather(); } } void RegionWeather::setWeather(int weatherID) { mWeather = weatherID; } int RegionWeather::getWeather() { // If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value. // Note that the region weather will be expired periodically when the weather update timer expires. if(mWeather == invalidWeatherID) { chooseNewWeather(); } return mWeather; } void RegionWeather::chooseNewWeather() { // All probabilities must add to 100 (responsibility of the user). // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 // and 70% will be greater than 30 (in theory). auto& prng = MWBase::Environment::get().getWorld()->getPrng(); int chance = Misc::Rng::rollDice(100, prng) + 1; // 1..100 int sum = 0; int i = 0; for(; static_cast(i) < mChances.size(); ++i) { sum += mChances[i]; if(chance <= sum) { mWeather = i; return; } } // if we hit this path then the chances don't add to 100, choose a default weather instead mWeather = 0; } MoonModel::MoonModel(const std::string& name) : mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start")) , mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish")) , mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start")) , mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish")) , mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset")) , mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed")) , mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment")) , mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle")) , mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle")) , mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle")) { // Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably // complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering. mSpeed = std::min(mSpeed, 180.0f / 23.0f); } MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const { float rotationFromHorizon = angle(gameTime); MWRender::MoonState state = { rotationFromHorizon, mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices. phase(gameTime), shadowBlend(rotationFromHorizon), earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour()) }; return state; } inline float MoonModel::angle(const TimeStamp& gameTime) const { // Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the // opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise. // When calculating the angle of the moon, several cases have to be taken into account: // 1. Moon rises and then sets in one day. // 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24). // 3. Moon sets and then rises in one day. float moonRiseHourToday = moonRiseHour(gameTime.getDay()); float moonRiseAngleToday = 0; if(gameTime.getHour() < moonRiseHourToday) { float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); if(moonRiseHourYesterday < 24) { float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); if(moonRiseAngleYesterday < 180) { // The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today. moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday; } } } else { moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); } if(moonRiseAngleToday >= 180) { // The moon set today, reset the angle to the horizon. moonRiseAngleToday = 0; } return moonRiseAngleToday; } inline float MoonModel::moonRiseHour(unsigned int daysPassed) const { // This arises from the start date of 16 Last Seed, 427 // TODO: Find an alternate formula that doesn't rely on this day being fixed. static const unsigned int startDay = 16; // This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning // that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed. // Note that we don't modulo after adding the latest daily increment because other calculations need to // know if doing so would cause the moon rise to be postponed until the next day (which happens when // the moon rise hour is >= 24 in Morrowind). return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f); } inline float MoonModel::rotation(float hours) const { // 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph. // Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure // of whole rotations that could be completed in a day. return 15.0f * mSpeed * hours; } MWRender::MoonState::Phase MoonModel::phase(const TimeStamp& gameTime) const { // Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle. // If the moon didn't rise yet today, use yesterday's moon phase. if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) return static_cast((gameTime.getDay() / 3) % 8); else return static_cast(((gameTime.getDay() + 1) / 3) % 8); } inline float MoonModel::shadowBlend(float angle) const { // The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk // that is roughly the color of the sky, to a textured surface. // Depending on the current angle, the following values describe the ratio between the textured moon // and the solid disk: // 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1 // 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured) // 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0 // 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk) float fadeAngle = mFadeStartAngle - mFadeEndAngle; float fadeEndAngle2 = 180.0f - mFadeEndAngle; float fadeStartAngle2 = 180.0f - mFadeStartAngle; if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle)) return (angle - mFadeEndAngle) / fadeAngle; else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2)) return 1.0f; else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2)) return (fadeEndAngle2 - angle) / fadeAngle; else return 0.0f; } inline float MoonModel::hourlyAlpha(float gameHour) const { // The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon // appears and disappears. // Depending on the current hour, the following values describe how transparent the moon is. // 1. From Fade Out Start to Fade Out Finish: 1..0 // 2. From Fade Out Finish to Fade In Start: 0 (transparent) // 3. From Fade In Start to Fade In Finish: 0..1 // 4. From Fade In Finish to Fade Out Start: 1 (solid) if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish)) return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart); else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart)) return 0.0f; else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish)) return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart); else return 1.0f; } inline float MoonModel::earlyMoonShadowAlpha(float angle) const { // The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle. // Depending on the current angle, the following values describe how transparent the moon is. // 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1 // 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid) // 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0 // 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent) float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle; float fadeEndAngle2 = 180.0f - mFadeEndAngle; float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle; if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle)) return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle; else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2)) return 1.0f; else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2)) return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle; else return 0.0f; } WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, MWWorld::ESMStore& store) : mStore(store) , mRendering(rendering) , mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time")) , mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time")) , mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration")) , mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration")) , mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time")) , mNightFade(0, 0, 0, 1) , mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes")) , mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity")) , mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"), Fallback::Map::getFloat("Water_UnderwaterDayFog"), Fallback::Map::getFloat("Water_UnderwaterSunsetFog"), Fallback::Map::getFloat("Water_UnderwaterNightFog")) , mWeatherSettings() , mMasser("Masser") , mSecunda("Secunda") , mWindSpeed(0.f) , mCurrentWindSpeed(0.f) , mNextWindSpeed(0.f) , mIsStorm(false) , mPrecipitation(false) , mStormDirection(Weather::defaultDirection()) , mCurrentRegion() , mTimePassed(0) , mFastForward(false) , mWeatherUpdateTime(mHoursBetweenWeatherChanges) , mTransitionFactor(0) , mNightDayMode(Default) , mCurrentWeather(0) , mNextWeather(0) , mQueuedWeather(0) , mRegions() , mResult() , mAmbientSound(nullptr) , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; mTimeSettings.mNightEnd = mSunriseTime; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; mTimeSettings.mDayEnd = mSunsetTime; mTimeSettings.addSetting("Sky"); mTimeSettings.addSetting("Ambient"); mTimeSettings.addSetting("Fog"); mTimeSettings.addSetting("Sun"); // Morrowind handles stars settings differently for other ones mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start"); mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish"); mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration"); WeatherSetting starSetting = { mTimeSettings.mStarsPreSunriseFinish, mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, mTimeSettings.mStarsPostSunsetStart, mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart }; mTimeSettings.mSunriseTransitions["Stars"] = starSetting; mWeatherSettings.reserve(10); // These distant land fog factor and offset values are the defaults MGE XE provides. Should be // provided by settings somewhere? addWeather("Clear", 1.0f, 0.0f); // 0 addWeather("Cloudy", 0.9f, 0.0f); // 1 addWeather("Foggy", 0.2f, 30.0f); // 2 addWeather("Overcast", 0.7f, 0.0f); // 3 addWeather("Rain", 0.5f, 10.0f); // 4 addWeather("Thunderstorm", 0.5f, 20.0f); // 5 addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 Store::iterator it = store.get().begin(); for(; it != store.get().end(); ++it) { std::string regionID = Misc::StringUtils::lowerCase(it->mId); mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); } forceWeather(0); } WeatherManager::~WeatherManager() { stopSounds(); } void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID) { // In Morrowind, this seems to have the following behavior, when applied to the current region: // - When there is no transition in progress, start transitioning to the new weather. // - If there is a transition in progress, queue up the transition and process it when the current one completes. // - If there is a transition in progress, and a queued transition, overwrite the queued transition. // - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used, // meaning that if there was no transition in progress, only the last ChangeWeather will be processed. // If the region isn't current, Morrowind will store the new weather for the region in question. if(weatherID < mWeatherSettings.size()) { std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); std::map::iterator it = mRegions.find(lowerCaseRegionID); if(it != mRegions.end()) { it->second.setWeather(weatherID); regionalWeatherChanged(it->first, it->second); } } } void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) { // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. // In Morrowind, this seems to have the following behavior when applied to the current region: // - If the region supports the current weather, no change in current weather occurs. // - If the region no longer supports the current weather, and there is no transition in progress, begin to // transition to a new supported weather type. // - If the region no longer supports the current weather, and there is a transition in progress, queue a // transition to a new supported weather type. std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); std::map::iterator it = mRegions.find(lowerCaseRegionID); if(it != mRegions.end()) { it->second.setChances(chances); regionalWeatherChanged(it->first, it->second); } } void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior) { // If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to // be changed immediately, and any transitions for the previous region discarded. { std::map::iterator it = mRegions.find(playerRegion); if(it != mRegions.end() && playerRegion != mCurrentRegion) { mCurrentRegion = playerRegion; forceWeather(it->second.getWeather()); } } } float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) { float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); if (currentSpeed == 0.f) currentSpeed = targetSpeed; float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); float updatedSpeed = (Misc::Rng::rollClosedProbability(prng) - 0.5f) * multiplier * targetSpeed + currentSpeed; if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) currentSpeed = updatedSpeed; return currentSpeed; } void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); if(!paused || mFastForward) { // Add new transitions when either the player's current external region changes. std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); if(updateWeatherTime() || updateWeatherRegion(playerRegion)) { std::map::iterator it = mRegions.find(mCurrentRegion); if(it != mRegions.end()) { addWeatherTransition(it->second.getWeather()); } } updateWeatherTransitions(duration); } bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; if (isExterior && !isDay) mNightDayMode = ExteriorNight; else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) mNightDayMode = InteriorDay; else mNightDayMode = Default; if(!isExterior) { mRendering.setSkyEnabled(false); stopSounds(); mWindSpeed = 0.f; mCurrentWindSpeed = 0.f; mNextWindSpeed = 0.f; return; } calculateWeatherResult(time.getHour(), duration, paused); if (!paused) { mWindSpeed = mResult.mWindSpeed; mCurrentWindSpeed = mResult.mCurrentWindSpeed; mNextWindSpeed = mResult.mNextWindSpeed; } mIsStorm = mResult.mIsStorm; // For some reason Ash Storm is not considered as a precipitation weather in game mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) && mResult.mParticleEffect != "meshes\\ashcloud.nif"; mStormDirection = calculateStormDirection(mResult.mParticleEffect); mRendering.getSkyManager()->setStormParticleDirection(mStormDirection); // disable sun during night if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime) mRendering.getSkyManager()->sunDisable(); else mRendering.getSkyManager()->sunEnable(); // Update the sun direction. Run it east to west at a fixed angle from overhead. // The sun's speed at day and night may differ, since mSunriseTime and mNightStart // mark when the sun is level with the horizon. { // Shift times into a 24-hour window beginning at mSunriseTime... float adjustedHour = time.getHour(); float adjustedNightStart = mTimeSettings.mNightStart; if ( time.getHour() < mSunriseTime ) adjustedHour += 24.f; if ( mTimeSettings.mNightStart < mSunriseTime ) adjustedNightStart += 24.f; const bool is_night = adjustedHour >= adjustedNightStart; const float dayDuration = adjustedNightStart - mSunriseTime; const float nightDuration = 24.f - dayDuration; double theta; if ( !is_night ) { theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; } else { theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( static_cast(cos(theta)), -0.268f, // approx tan( -15 degrees ) static_cast(sin(theta))); mRendering.setSunDirection( final * -1 ); mRendering.setNight(is_night); } float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; float glareFade = 1.f; if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) glareFade = 0.f; else if (time.getHour() < peakHour) glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); else glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset/100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); // Play sounds if (mPlayingSoundID != mResult.mAmbientLoopSoundID) { stopSounds(); if (!mResult.mAmbientLoopSoundID.empty()) mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound( mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop ); mPlayingSoundID = mResult.mAmbientLoopSoundID; } else if (mAmbientSound) mAmbientSound->setVolume(mResult.mAmbientSoundVolume); } void WeatherManager::stopSounds() { if (mAmbientSound) MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); mAmbientSound = nullptr; mPlayingSoundID.clear(); } float WeatherManager::getWindSpeed() const { return mWindSpeed; } bool WeatherManager::isInStorm() const { return mIsStorm; } osg::Vec3f WeatherManager::getStormDirection() const { return mStormDirection; } void WeatherManager::advanceTime(double hours, bool incremental) { // In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are // immediately applied, regardless of whatever transition time might have been remaining. mTimePassed += hours; mFastForward = !incremental ? true : mFastForward; } NightDayMode WeatherManager::getNightDayMode() const { return mNightDayMode; } bool WeatherManager::useTorches(float hour) const { bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; return isDark && !mPrecipitation; } void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) { ESM::WeatherState state; state.mCurrentRegion = mCurrentRegion; state.mTimePassed = mTimePassed; state.mFastForward = mFastForward; state.mWeatherUpdateTime = mWeatherUpdateTime; state.mTransitionFactor = mTransitionFactor; state.mCurrentWeather = mCurrentWeather; state.mNextWeather = mNextWeather; state.mQueuedWeather = mQueuedWeather; std::map::iterator it = mRegions.begin(); for(; it != mRegions.end(); ++it) { state.mRegions.insert(std::make_pair(it->first, it->second)); } writer.startRecord(ESM::REC_WTHR); state.save(writer); writer.endRecord(ESM::REC_WTHR); } bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if(ESM::REC_WTHR == type) { static const int oldestCompatibleSaveFormat = 2; if(reader.getFormat() < oldestCompatibleSaveFormat) { // Weather state isn't really all that important, so to preserve older save games, we'll just discard the // older weather records, rather than fail to handle the record. reader.skipRecord(); } else { ESM::WeatherState state; state.load(reader); mCurrentRegion.swap(state.mCurrentRegion); mTimePassed = state.mTimePassed; mFastForward = state.mFastForward; mWeatherUpdateTime = state.mWeatherUpdateTime; mTransitionFactor = state.mTransitionFactor; mCurrentWeather = state.mCurrentWeather; mNextWeather = state.mNextWeather; mQueuedWeather = state.mQueuedWeather; mRegions.clear(); importRegions(); for(std::map::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it) { std::map::iterator found = mRegions.find(it->first); if (found != mRegions.end()) { found->second = RegionWeather(it->second); } } } return true; } return false; } void WeatherManager::clear() { stopSounds(); mCurrentRegion.clear(); mTimePassed = 0.0f; mWeatherUpdateTime = 0.0f; forceWeather(0); mRegions.clear(); importRegions(); } inline void WeatherManager::addWeather(const std::string& name, float dlFactor, float dlOffset, const std::string& particleEffect) { static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); mWeatherSettings.push_back(weather); } inline void WeatherManager::importRegions() { for(const ESM::Region& region : mStore.get()) { std::string regionID = Misc::StringUtils::lowerCase(region.mId); mRegions.insert(std::make_pair(regionID, RegionWeather(region))); } } inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) { // If the region is current, then add a weather transition for it. MWWorld::ConstPtr player = MWMechanics::getPlayer(); if(player.isInCell()) { if(Misc::StringUtils::ciEqual(regionID, mCurrentRegion)) { addWeatherTransition(region.getWeather()); } } } inline bool WeatherManager::updateWeatherTime() { mWeatherUpdateTime -= mTimePassed; mTimePassed = 0.0f; if(mWeatherUpdateTime <= 0.0f) { // Expire all regional weather, so that any call to getWeather() will return a new weather ID. std::map::iterator it = mRegions.begin(); for(; it != mRegions.end(); ++it) { it->second.setWeather(invalidWeatherID); } mWeatherUpdateTime += mHoursBetweenWeatherChanges; return true; } return false; } inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion) { if(!playerRegion.empty() && playerRegion != mCurrentRegion) { mCurrentRegion = playerRegion; return true; } return false; } inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds) { // When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last // weather type set, regardless of the remaining transition time. if(!mFastForward && inTransition()) { const float delta = mWeatherSettings[mNextWeather].transitionDelta(); mTransitionFactor -= elapsedRealSeconds * delta; if(mTransitionFactor <= 0.0f) { mCurrentWeather = mNextWeather; mNextWeather = mQueuedWeather; mQueuedWeather = invalidWeatherID; // We may have begun processing the queued transition, so we need to apply the remaining time towards it. if(inTransition()) { const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); const float remainingSeconds = -(mTransitionFactor / delta); mTransitionFactor = 1.0f - (remainingSeconds * newDelta); } else { mTransitionFactor = 0.0f; } } } else { if(mQueuedWeather != invalidWeatherID) { mCurrentWeather = mQueuedWeather; } else if(mNextWeather != invalidWeatherID) { mCurrentWeather = mNextWeather; } mNextWeather = invalidWeatherID; mQueuedWeather = invalidWeatherID; mFastForward = false; } } inline void WeatherManager::forceWeather(const int weatherID) { mTransitionFactor = 0.0f; mCurrentWeather = weatherID; mNextWeather = invalidWeatherID; mQueuedWeather = invalidWeatherID; } inline bool WeatherManager::inTransition() { return mNextWeather != invalidWeatherID; } inline void WeatherManager::addWeatherTransition(const int weatherID) { // In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if // no transition is in progress, otherwise it queues it to be transitioned. assert(weatherID >= 0 && static_cast(weatherID) < mWeatherSettings.size()); if(!inTransition() && (weatherID != mCurrentWeather)) { mNextWeather = weatherID; mTransitionFactor = 1.0f; } else if(inTransition() && (weatherID != mNextWeather)) { mQueuedWeather = weatherID; } } inline void WeatherManager::calculateWeatherResult(const float gameHour, const float elapsedSeconds, const bool isPaused) { float flash = 0.0f; if(!inTransition()) { calculateResult(mCurrentWeather, gameHour); flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused); } else { calculateTransitionResult(1 - mTransitionFactor, gameHour); float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor, elapsedSeconds, isPaused); float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor, elapsedSeconds, isPaused); flash = currentFlash + nextFlash; } osg::Vec4f flashColor(flash, flash, flash, 0.0f); mResult.mFogColor += flashColor; mResult.mAmbientColor += flashColor; mResult.mSunColor += flashColor; } inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) { const Weather& current = mWeatherSettings[weatherID]; mResult.mCloudTexture = current.mCloudTexture; mResult.mCloudBlendFactor = 0; mResult.mNextWindSpeed = 0; mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mPrecipitationAlpha = 1.f; mResult.mIsStorm = current.mIsStorm; mResult.mRainSpeed = current.mRainSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); mResult.mDLFogFactor = current.mDL.FogFactor; mResult.mDLFogOffset = current.mDL.FogOffset; WeatherSetting setting = mTimeSettings.getSetting("Sun"); float preSunsetTime = setting.mPreSunsetTime; if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) { float factor = 1.f; if (preSunsetTime > 0) factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; factor = std::min(1.f, factor); mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline // would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped. // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); for (int i=0; i<3; ++i) mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); } else mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); if (gameHour >= mTimeSettings.mDayEnd) { // sunset float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); fade = fade*fade; mResult.mSunDiscColor.a() = 1.f - fade; } else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) { // sunrise mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; } else mResult.mSunDiscColor.a() = 1; mResult.mStormDirection = calculateStormDirection(mResult.mParticleEffect); } inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) { calculateResult(mCurrentWeather, gameHour); const MWRender::WeatherResult current = mResult; calculateResult(mNextWeather, gameHour); const MWRender::WeatherResult other = mResult; mResult.mStormDirection = current.mStormDirection; mResult.mNextStormDirection = other.mStormDirection; mResult.mCloudTexture = current.mCloudTexture; mResult.mNextCloudTexture = other.mCloudTexture; mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(factor); mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor); mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor); mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); mResult.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor); mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor); mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; float threshold = mWeatherSettings[mNextWeather].mRainThreshold; if (threshold <= 0.f) threshold = 0.5f; if(factor < threshold) { mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; mResult.mRainSpeed = current.mRainSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; mResult.mAmbientSoundVolume = 1.f - factor / threshold; mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; } else { mResult.mIsStorm = other.mIsStorm; mResult.mParticleEffect = other.mParticleEffect; mResult.mRainEffect = other.mRainEffect; mResult.mRainSpeed = other.mRainSpeed; mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; mResult.mRainDiameter = other.mRainDiameter; mResult.mRainMinHeight = other.mRainMinHeight; mResult.mRainMaxHeight = other.mRainMaxHeight; mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; } } } openmw-openmw-0.48.0/apps/openmw/mwworld/weather.hpp000066400000000000000000000271621445372753700225570ustar00rootroot00000000000000#ifndef GAME_MWWORLD_WEATHER_H #define GAME_MWWORLD_WEATHER_H #include #include #include #include #include #include "../mwbase/soundmanager.hpp" #include "../mwrender/sky.hpp" namespace ESM { struct Region; struct RegionWeatherState; class ESMWriter; class ESMReader; } namespace MWRender { class RenderingManager; } namespace Loading { class Listener; } namespace Fallback { class Map; } namespace MWWorld { class TimeStamp; enum NightDayMode { Default = 0, ExteriorNight = 1, InteriorDay = 2 }; struct WeatherSetting { float mPreSunriseTime; float mPostSunriseTime; float mPreSunsetTime; float mPostSunsetTime; }; struct TimeOfDaySettings { float mNightStart; float mNightEnd; float mDayStart; float mDayEnd; std::map mSunriseTransitions; float mStarsPostSunsetStart; float mStarsPreSunriseFinish; float mStarsFadingDuration; WeatherSetting getSetting(const std::string& type) const { std::map::const_iterator it = mSunriseTransitions.find(type); if (it != mSunriseTransitions.end()) { return it->second; } else { return { 1.f, 1.f, 1.f, 1.f }; } } void addSetting(const std::string& type) { WeatherSetting setting = { Fallback::Map::getFloat("Weather_" + type + "_Pre-Sunrise_Time"), Fallback::Map::getFloat("Weather_" + type + "_Post-Sunrise_Time"), Fallback::Map::getFloat("Weather_" + type + "_Pre-Sunset_Time"), Fallback::Map::getFloat("Weather_" + type + "_Post-Sunset_Time") }; mSunriseTransitions[type] = setting; } }; /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. /// The template value could be a floating point number, or a color. template class TimeOfDayInterpolator { public: TimeOfDayInterpolator(const T& sunrise, const T& day, const T& sunset, const T& night) : mSunriseValue(sunrise), mDayValue(day), mSunsetValue(sunset), mNightValue(night) { } T getValue (const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const; private: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; }; /// Defines a single weather setting (according to INI) class Weather { public: static osg::Vec3f defaultDirection(); Weather(const std::string& name, float stormWindSpeed, float rainSpeed, float dlFactor, float dlOffset, const std::string& particleEffect); std::string mCloudTexture; // Sky (atmosphere) color TimeOfDayInterpolator mSkyColor; // Fog color TimeOfDayInterpolator mFogColor; // Ambient lighting color TimeOfDayInterpolator mAmbientColor; // Sun (directional) lighting color TimeOfDayInterpolator mSunColor; // Fog depth/density TimeOfDayInterpolator mLandFogDepth; // Color modulation for the sun itself during sunset osg::Vec4f mSunDiscSunsetColor; // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) float mWindSpeed; // Cloud animation speed multiplier float mCloudSpeed; // Value between 0 and 1, defines the strength of the sun glare effect. // Also appears to modify how visible the sun, moons, and stars are for various weather effects. float mGlareView; // Fog factor and offset used with distant land rendering. struct { float FogFactor; float FogOffset; } mDL; // Sound effect // This is used for Blight, Ashstorm and Blizzard (Bloodmoon) std::string mAmbientLoopSoundID; // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation) // - Slower movement when walking against the storm (fStromWalkMult) bool mIsStorm; // How fast does rain travel down? // In Morrowind.ini this is set globally, but we may want to change it per weather later. float mRainSpeed; // How often does a new rain mesh spawn? float mRainEntranceSpeed; // Maximum count of rain particles int mRainMaxRaindrops; // Radius of rain effect float mRainDiameter; // Transition threshold to spawn rain float mRainThreshold; // Height of rain particles spawn float mRainMinHeight; float mRainMaxHeight; std::string mParticleEffect; std::string mRainEffect; osg::Vec3f mStormDirection; // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. float transitionDelta() const; float cloudBlendFactor(const float transitionRatio) const; float calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused); private: float mTransitionDelta; float mCloudsMaximumPercent; // Note: In MW, only thunderstorms support these attributes, but in the interest of making weather more // flexible, these settings are imported for all weather types. Only thunderstorms will normally have any // non-zero values. float mThunderFrequency; float mThunderThreshold; std::string mThunderSoundID[4]; float mFlashDecrement; float mFlashBrightness; void flashDecrement(const float elapsedSeconds); float thunderChance(const float transitionRatio, const float elapsedSeconds) const; void lightningAndThunder(void); }; /// A class for storing a region's weather. class RegionWeather { public: explicit RegionWeather(const ESM::Region& region); explicit RegionWeather(const ESM::RegionWeatherState& state); operator ESM::RegionWeatherState() const; void setChances(const std::vector& chances); void setWeather(int weatherID); int getWeather(); private: int mWeather; std::vector mChances; void chooseNewWeather(); }; /// A class that acts as a model for the moons. class MoonModel { public: MoonModel(const std::string& name); MWRender::MoonState calculateState(const TimeStamp& gameTime) const; private: float mFadeInStart; float mFadeInFinish; float mFadeOutStart; float mFadeOutFinish; float mAxisOffset; float mSpeed; float mDailyIncrement; float mFadeStartAngle; float mFadeEndAngle; float mMoonShadowEarlyFadeAngle; float angle(const TimeStamp& gameTime) const; float moonRiseHour(unsigned int daysPassed) const; float rotation(float hours) const; MWRender::MoonState::Phase phase(const TimeStamp& gameTime) const; float shadowBlend(float angle) const; float hourlyAlpha(float gameHour) const; float earlyMoonShadowAlpha(float angle) const; }; /// Interface for weather settings class WeatherManager { public: // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time WeatherManager(MWRender::RenderingManager& rendering, MWWorld::ESMStore& store); ~WeatherManager(); /** * Change the weather in the specified region * @param region that should be changed * @param ID of the weather setting to shift to */ void changeWeather(const std::string& regionID, const unsigned int weatherID); void modRegion(const std::string& regionID, const std::vector& chances); void playerTeleported(const std::string& playerRegion, bool isExterior); /** * Per-frame update * @param duration * @param paused */ void update(float duration, bool paused, const TimeStamp& time, bool isExterior); void stopSounds(); float getWindSpeed() const; NightDayMode getNightDayMode() const; /// Are we in an ash or blight storm? bool isInStorm() const; osg::Vec3f getStormDirection() const; void advanceTime(double hours, bool incremental); int getWeatherID() const { return mCurrentWeather; } int getNextWeatherID() const { return mNextWeather; } float getTransitionFactor() const { return mTransitionFactor; } bool useTorches(float hour) const; void write(ESM::ESMWriter& writer, Loading::Listener& progress); bool readRecord(ESM::ESMReader& reader, uint32_t type); void clear(); private: MWWorld::ESMStore& mStore; MWRender::RenderingManager& mRendering; float mSunriseTime; float mSunsetTime; float mSunriseDuration; float mSunsetDuration; float mSunPreSunsetTime; TimeOfDaySettings mTimeSettings; // fading of night skydome TimeOfDayInterpolator mNightFade; float mHoursBetweenWeatherChanges; float mRainSpeed; // underwater fog not really related to weather, but we handle it here because it's convenient TimeOfDayInterpolator mUnderwaterFog; std::vector mWeatherSettings; MoonModel mMasser; MoonModel mSecunda; float mWindSpeed; float mCurrentWindSpeed; float mNextWindSpeed; bool mIsStorm; bool mPrecipitation; osg::Vec3f mStormDirection; std::string mCurrentRegion; float mTimePassed; bool mFastForward; float mWeatherUpdateTime; float mTransitionFactor; NightDayMode mNightDayMode; int mCurrentWeather; int mNextWeather; int mQueuedWeather; std::map mRegions; MWRender::WeatherResult mResult; MWBase::Sound *mAmbientSound; std::string mPlayingSoundID; void addWeather(const std::string& name, float dlFactor, float dlOffset, const std::string& particleEffect = ""); void importRegions(); void regionalWeatherChanged(const std::string& regionID, RegionWeather& region); bool updateWeatherTime(); bool updateWeatherRegion(const std::string& playerRegion); void updateWeatherTransitions(const float elapsedRealSeconds); void forceWeather(const int weatherID); bool inTransition(); void addWeatherTransition(const int weatherID); void calculateWeatherResult(const float gameHour, const float elapsedSeconds, const bool isPaused); void calculateResult(const int weatherID, const float gameHour); void calculateTransitionResult(const float factor, const float gameHour); float calculateWindSpeed(int weatherId, float currentSpeed); }; } #endif // GAME_MWWORLD_WEATHER_H openmw-openmw-0.48.0/apps/openmw/mwworld/worldimp.cpp000066400000000000000000004357271445372753700227620ustar00rootroot00000000000000#include "worldimp.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/luamanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors #include "../mwmechanics/summoning.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/npcanimation.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "../mwrender/vismask.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwscript/globalscripts.hpp" #include "../mwclass/door.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/actor.hpp" #include "../mwphysics/collisiontype.hpp" #include "../mwphysics/object.hpp" #include "../mwphysics/constants.hpp" #include "datetimemanager.hpp" #include "player.hpp" #include "manualref.hpp" #include "cellstore.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" #include "actionteleport.hpp" #include "projectilemanager.hpp" #include "weather.hpp" #include "contentloader.hpp" #include "esmloader.hpp" #include "cellutils.hpp" namespace MWWorld { struct GameContentLoader : public ContentLoader { void addLoader(std::string&& extension, ContentLoader& loader) { mLoaders.emplace(std::move(extension), &loader); } void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override { const auto it = mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string())); if (it != mLoaders.end()) { const std::string filename = filepath.filename().string(); Log(Debug::Info) << "Loading content file " << filename; if (listener != nullptr) listener->setLabel(MyGUI::TextIterator::toTagsString(filename)); it->second->load(filepath, index, listener); } else { std::string msg("Cannot load file: "); msg += filepath.string(); throw std::runtime_error(msg.c_str()); } } private: std::map mLoaders; }; struct OMWScriptsLoader : public ContentLoader { ESMStore& mStore; OMWScriptsLoader(ESMStore& store) : mStore(store) {} void load(const boost::filesystem::path& filepath, int& /*index*/, Loading::Listener* /*listener*/) override { mStore.addOMWScripts(filepath.string()); } }; void World::adjustSky() { if (mSky && (isCellExterior() || isCellQuasiExterior())) { updateSkyDate(); mRendering->setSkyEnabled(true); } else mRendering->setSkyEnabled(false); } World::World ( osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) : mResourceSystem(resourceSystem), mLocalScripts(mStore), mCells(mStore, mReaders), mSky(true), mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mDefaultHalfExtents(Settings::Manager::getVector3("default actor pathfind half extents", "Game")), mDefaultActorCollisionShapeType(DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game"))), mShouldUpdateNavigator(false), mActivationDistanceOverride (activationDistanceOverride), mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); loadContentFiles(fileCollections, contentFiles, encoder, listener); loadGroundcoverFiles(fileCollections, groundcoverFiles, encoder, listener); listener->loadingOff(); mCurrentDate = std::make_unique(); fillGlobalVariables(); mStore.setUp(); mStore.validateRecords(mReaders); mStore.movePlayerRecord(); mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); mPhysics = std::make_unique(resourceSystem, rootNode); if (Settings::Manager::getBool("enable", "Navigator")) { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath); } else { mNavigator = DetourNavigator::makeNavigatorStub(); } mRendering = std::make_unique(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator, mGroundcoverStore, unrefQueue); mProjectileManager = std::make_unique(mRendering->getLightRoot()->asGroup(), resourceSystem, mRendering.get(), mPhysics.get()); mRendering->preloadCommonAssets(); mWeatherManager = std::make_unique(*mRendering, mStore); mWorldScene = std::make_unique(*this, *mRendering.get(), mPhysics.get(), *mNavigator); } void World::fillGlobalVariables() { mGlobalVariables.fill (mStore); mCurrentDate->setup(mGlobalVariables); } void World::startNewGame (bool bypass) { mGoToJail = false; mLevitationEnabled = true; mTeleportEnabled = true; mGodMode = false; mScriptsEnabled = true; mSky = true; // Rebuild player setupPlayer(); renderPlayer(); mRendering->getCamera()->reset(); // we don't want old weather to persist on a new game // Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost. mWeatherManager.reset(); mWeatherManager = std::make_unique(*mRendering.get(), mStore); if (!bypass) { // set new game mark mGlobalVariables["chargenstate"].setInteger (1); } else mGlobalVariables["chargenstate"].setInteger (-1); if (bypass && !mStartCell.empty()) { ESM::Position pos; if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos, true); adjustPosition(getPlayerPtr(), false); } else { findInteriorPosition (mStartCell, pos); changeToInteriorCell (mStartCell, pos, true); } } else { for (int i=0; i<5; ++i) MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); if (!getPlayerPtr().isInCell()) { ESM::Position pos; const int cellSize = Constants::CellSizeInUnits; pos.pos[0] = cellSize/2; pos.pos[1] = cellSize/2; pos.pos[2] = 0; pos.rot[0] = 0; pos.rot[1] = 0; pos.rot[2] = 0; mWorldScene->changeToExteriorCell(pos, true); } } if (!bypass) { const std::string& video = Fallback::Map::getString("Movies_New_Game"); if (!video.empty()) MWBase::Environment::get().getWindowManager()->playVideo(video, true); } // enable collision if (!mPhysics->toggleCollisionMode()) mPhysics->toggleCollisionMode(); MWBase::Environment::get().getWindowManager()->updatePlayer(); mCurrentDate->setup(mGlobalVariables); // Initial seed. mPrng.seed(mRandomSeed); } void World::clear() { mWeatherManager->clear(); mRendering->clear(); mProjectileManager->clear(); mLocalScripts.clear(); mWorldScene->clear(); mStore.clearDynamic(); if (mPlayer) { mPlayer->clear(); mPlayer->setCell(nullptr); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); } mCells.clear(); mDoorStates.clear(); mGoToJail = false; mTeleportEnabled = true; mLevitationEnabled = true; mPlayerTraveling = false; mPlayerInJail = false; fillGlobalVariables(); } int World::countSavedGameRecords() const { return mCells.countSavedGameRecords() +mStore.countSavedGameRecords() +mGlobalVariables.countSavedGameRecords() +mProjectileManager->countSavedGameRecords() +1 // player record +1 // weather record +1 // actorId counter +1 // levitation/teleport enabled state +1 // camera +1; // random state. } int World::countSavedGameCells() const { return mCells.countSavedGameRecords(); } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { writer.startRecord(ESM::REC_RAND); writer.writeHNOString("RAND", Misc::Rng::serialize(mPrng)); writer.endRecord(ESM::REC_RAND); // Active cells could have a dirty fog of war, sync it to the CellStore first for (CellStore* cellstore : mWorldScene->getActiveCells()) { MWBase::Environment::get().getWindowManager()->writeFog(cellstore); } MWMechanics::CreatureStats::writeActorIdCounter(writer); mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that // references to custom made records will be recognized mPlayer->write (writer, progress); mCells.write (writer, progress); mGlobalVariables.write (writer, progress); mWeatherManager->write (writer, progress); mProjectileManager->write (writer, progress); writer.startRecord(ESM::REC_ENAB); writer.writeHNT("TELE", mTeleportEnabled); writer.writeHNT("LEVT", mLevitationEnabled); writer.endRecord(ESM::REC_ENAB); writer.startRecord(ESM::REC_CAM_); writer.writeHNT("FIRS", isFirstPerson()); writer.endRecord(ESM::REC_CAM_); } void World::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { switch (type) { case ESM::REC_ACTC: MWMechanics::CreatureStats::readActorIdCounter(reader); return; case ESM::REC_ENAB: reader.getHNT(mTeleportEnabled, "TELE"); reader.getHNT(mLevitationEnabled, "LEVT"); return; case ESM::REC_RAND: { auto data = reader.getHNOString("RAND"); Misc::Rng::deserialize(data, mPrng); } break; case ESM::REC_PLAY: mStore.checkPlayer(); mPlayer->readRecord(reader, type); if (getPlayerPtr().isInCell()) { if (getPlayerPtr().getCell()->isExterior()) mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); mWorldScene->preloadCell(getPlayerPtr().getCell(), true); } break; default: if (!mStore.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) && !mWeatherManager->readRecord (reader, type) && !mCells.readRecord (reader, type, contentFileMap) && !mProjectileManager->readRecord (reader, type) ) { throw std::runtime_error ("unknown record in saved game"); } break; } } void World::ensureNeededRecords() { std::map gmst; // Companion (tribunal) gmst["sCompanionShare"] = ESM::Variant("Companion Share"); gmst["sCompanionWarningMessage"] = ESM::Variant("Warning message"); gmst["sCompanionWarningButtonOne"] = ESM::Variant("Button 1"); gmst["sCompanionWarningButtonTwo"] = ESM::Variant("Button 2"); gmst["sProfitValue"] = ESM::Variant("Profit Value"); gmst["sTeleportDisabled"] = ESM::Variant("Teleport disabled"); gmst["sLevitateDisabled"] = ESM::Variant("Levitate disabled"); // Missing in unpatched MW 1.0 gmst["sDifficulty"] = ESM::Variant("Difficulty"); gmst["fDifficultyMult"] = ESM::Variant(5.f); gmst["sAuto_Run"] = ESM::Variant("Auto Run"); gmst["sServiceRefusal"] = ESM::Variant("Service Refusal"); gmst["sNeedOneSkill"] = ESM::Variant("Need one skill"); gmst["sNeedTwoSkills"] = ESM::Variant("Need two skills"); gmst["sEasy"] = ESM::Variant("Easy"); gmst["sHard"] = ESM::Variant("Hard"); gmst["sDeleteNote"] = ESM::Variant("Delete Note"); gmst["sEditNote"] = ESM::Variant("Edit Note"); gmst["sAdmireSuccess"] = ESM::Variant("Admire Success"); gmst["sAdmireFail"] = ESM::Variant("Admire Fail"); gmst["sIntimidateSuccess"] = ESM::Variant("Intimidate Success"); gmst["sIntimidateFail"] = ESM::Variant("Intimidate Fail"); gmst["sTauntSuccess"] = ESM::Variant("Taunt Success"); gmst["sTauntFail"] = ESM::Variant("Taunt Fail"); gmst["sBribeSuccess"] = ESM::Variant("Bribe Success"); gmst["sBribeFail"] = ESM::Variant("Bribe Fail"); gmst["fNPCHealthBarTime"] = ESM::Variant(5.f); gmst["fNPCHealthBarFade"] = ESM::Variant(1.f); gmst["fFleeDistance"] = ESM::Variant(3000.f); gmst["sMaxSale"] = ESM::Variant("Max Sale"); gmst["sAnd"] = ESM::Variant("and"); // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.3f); gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(2.f); gmst["iWerewolfFightMod"] = ESM::Variant(100); gmst["iWereWolfFleeMod"] = ESM::Variant(100); gmst["iWereWolfLevelToAttack"] = ESM::Variant(20); gmst["iWereWolfBounty"] = ESM::Variant(1000); gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); for (const auto ¶ms : gmst) { if (!mStore.get().search(params.first)) { ESM::GameSetting record; record.mId = params.first; record.mValue = params.second; record.mRecordFlags = 0; mStore.insertStatic(record); } } std::map globals; // vanilla Morrowind does not define dayspassed. globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :( globals["werewolfclawmult"] = ESM::Variant(25.f); globals["pcknownwerewolf"] = ESM::Variant(0); // following should exist in all versions of MW, but not necessarily in TCs globals["gamehour"] = ESM::Variant(0.f); globals["timescale"] = ESM::Variant(30.f); globals["day"] = ESM::Variant(1); globals["month"] = ESM::Variant(1); globals["year"] = ESM::Variant(1); globals["pcrace"] = ESM::Variant(0); globals["pchascrimegold"] = ESM::Variant(0); globals["pchasgolddiscount"] = ESM::Variant(0); globals["crimegolddiscount"] = ESM::Variant(0); globals["crimegoldturnin"] = ESM::Variant(0); globals["pchasturnin"] = ESM::Variant(0); for (const auto ¶ms : globals) { if (!mStore.get().search(params.first)) { ESM::Global record; record.mId = params.first; record.mValue = params.second; record.mRecordFlags = 0; mStore.insertStatic(record); } } std::map statics; // Total conversions from SureAI lack marker records statics["divinemarker"] = "marker_divine.nif"; statics["doormarker"] = "marker_arrow.nif"; statics["northmarker"] = "marker_north.nif"; statics["templemarker"] = "marker_temple.nif"; statics["travelmarker"] = "marker_travel.nif"; for (const auto ¶ms : statics) { if (!mStore.get().search(params.first)) { ESM::Static record; record.mId = params.first; record.mModel = params.second; record.mRecordFlags = 0; mStore.insertStatic(record); } } std::map doors; doors["prisonmarker"] = "marker_prison.nif"; for (const auto ¶ms : doors) { if (!mStore.get().search(params.first)) { ESM::Door record; record.mId = params.first; record.mModel = params.second; record.mRecordFlags = 0; mStore.insertStatic(record); } } } World::~World() { // Must be cleared before mRendering is destroyed mProjectileManager->clear(); } void World::setRandomSeed(uint32_t seed) { mRandomSeed = seed; } const ESM::Cell* World::getExterior(const std::string& cellName) const { // first try named cells const ESM::Cell *cell = mStore.get().searchExtByName (cellName); if (cell) return cell; // treat "Wilderness" like an empty string static const std::string defaultName = mStore.get().find("sDefaultCellname")->mValue.getString(); if (Misc::StringUtils::ciEqual(cellName, defaultName)) { cell = mStore.get().searchExtByName(""); if (cell) return cell; } // didn't work -> now check for regions for (const ESM::Region ®ion : mStore.get()) { if (Misc::StringUtils::ciEqual(cellName, region.mName)) { return mStore.get().searchExtByRegion(region.mId); } } return nullptr; } CellStore *World::getExterior (int x, int y) { return mCells.getExterior (x, y); } CellStore *World::getInterior (const std::string& name) { return mCells.getInterior (name); } CellStore *World::getCell (const ESM::CellId& id) { if (id.mPaged) return getExterior (id.mIndex.mX, id.mIndex.mY); else return getInterior (id.mWorldspace); } bool World::isCellActive(CellStore* cell) const { return mWorldScene->getActiveCells().count(cell) > 0; } void World::testExteriorCells() { mWorldScene->testExteriorCells(); } void World::testInteriorCells() { mWorldScene->testInteriorCells(); } void World::useDeathCamera() { mRendering->getCamera()->setMode(MWRender::Camera::Mode::ThirdPerson); } MWWorld::Player& World::getPlayer() { return *mPlayer; } const MWWorld::ESMStore& World::getStore() const { return mStore; } LocalScripts& World::getLocalScripts() { return mLocalScripts; } bool World::hasCellChanged() const { return mWorldScene->hasCellChanged(); } void World::setGlobalInt(std::string_view name, int value) { bool dateUpdated = mCurrentDate->updateGlobalInt(name, value); if (dateUpdated) updateSkyDate(); mGlobalVariables[name].setInteger (value); } void World::setGlobalFloat(std::string_view name, float value) { bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value); if (dateUpdated) updateSkyDate(); mGlobalVariables[name].setFloat(value); } int World::getGlobalInt(std::string_view name) const { return mGlobalVariables[name].getInteger(); } float World::getGlobalFloat(std::string_view name) const { return mGlobalVariables[name].getFloat(); } char World::getGlobalVariableType (std::string_view name) const { return mGlobalVariables.getType (name); } std::string World::getMonthName (int month) const { return mCurrentDate->getMonthName(month); } std::string World::getCellName (const MWWorld::CellStore *cell) const { if (!cell) cell = mWorldScene->getCurrentCell(); return getCellName(cell->getCell()); } std::string World::getCellName(const ESM::Cell* cell) const { if (cell) { if (!cell->isExterior() || !cell->mName.empty()) return cell->mName; if (const ESM::Region* region = mStore.get().search (cell->mRegion)) return region->mName; } return mStore.get().find ("sDefaultCellname")->mValue.getString(); } void World::removeRefScript (MWWorld::RefData *ref) { mLocalScripts.remove (ref); } Ptr World::searchPtr (std::string_view name, bool activeOnly, bool searchInContainers) { Ptr ret; // the player is always in an active cell. if (name=="player") { return mPlayer->getPlayer(); } std::string lowerCaseName = Misc::StringUtils::lowerCase(name); for (CellStore* cellstore : mWorldScene->getActiveCells()) { // TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in) Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false); if (!ptr.isEmpty()) return ptr; } if (!activeOnly) { ret = mCells.getPtr (lowerCaseName); if (!ret.isEmpty()) return ret; } if (searchInContainers) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { Ptr ptr = cellstore->searchInContainer(lowerCaseName); if (!ptr.isEmpty()) return ptr; } } Ptr ptr = mPlayer->getPlayer().getClass() .getContainerStore(mPlayer->getPlayer()).search(lowerCaseName); return ptr; } Ptr World::getPtr (std::string_view name, bool activeOnly) { Ptr ret = searchPtr(name, activeOnly); if (!ret.isEmpty()) return ret; std::string error = "failed to find an instance of object '" + std::string(name) + "'"; if (activeOnly) error += " in active cells"; throw std::runtime_error(error); } Ptr World::searchPtrViaActorId (int actorId) { // The player is not registered in any CellStore so must be checked manually if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId()) return getPlayerPtr(); // Now search cells return mWorldScene->searchPtrViaActorId (actorId); } Ptr World::searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum) { return mCells.getPtr (id, refNum); } struct FindContainerVisitor { ConstPtr mContainedPtr; Ptr mResult; FindContainerVisitor(const ConstPtr& containedPtr) : mContainedPtr(containedPtr) {} bool operator() (const Ptr& ptr) { if (mContainedPtr.getContainerStore() == &ptr.getClass().getContainerStore(ptr)) { mResult = ptr; return false; } return true; } }; Ptr World::findContainer(const ConstPtr& ptr) { if (ptr.isInCell()) return Ptr(); Ptr player = getPlayerPtr(); if (ptr.getContainerStore() == &player.getClass().getContainerStore(player)) return player; for (CellStore* cellstore : mWorldScene->getActiveCells()) { FindContainerVisitor visitor(ptr); cellstore->forEachType(visitor); if (visitor.mResult.isEmpty()) cellstore->forEachType(visitor); if (visitor.mResult.isEmpty()) cellstore->forEachType(visitor); if (!visitor.mResult.isEmpty()) return visitor.mResult; } return Ptr(); } void World::addContainerScripts(const Ptr& reference, CellStore * cell) { if( reference.getType()==ESM::Container::sRecordId || reference.getType()==ESM::NPC::sRecordId || reference.getType()==ESM::Creature::sRecordId) { MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { std::string script = it->getClass().getScript(*it); if(script != "") { MWWorld::Ptr item = *it; item.mCell = cell; mLocalScripts.add (script, item); } } } } void World::enable (const Ptr& reference) { MWBase::Environment::get().getLuaManager()->registerObject(reference); if (!reference.isInCell()) return; if (!reference.getRefData().isEnabled()) { reference.getRefData().enable(); if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) mWorldScene->addObjectToScene (reference); if (reference.getCellRef().getRefNum().hasContentFile()) { int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); if (mRendering->pagingEnableObject(type, reference, true)) mWorldScene->reloadTerrain(); } } } void World::removeContainerScripts(const Ptr& reference) { if( reference.getType()==ESM::Container::sRecordId || reference.getType()==ESM::NPC::sRecordId || reference.getType()==ESM::Creature::sRecordId) { MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { std::string script = it->getClass().getScript(*it); if(script != "") { MWWorld::Ptr item = *it; mLocalScripts.remove (item); } } } } void World::disable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) return; // disable is a no-op for items in containers if (!reference.isInCell()) return; if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); MWBase::Environment::get().getLuaManager()->deregisterObject(reference); reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) { int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId())); if (mRendering->pagingEnableObject(type, reference, false)) mWorldScene->reloadTerrain(); } if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) { mWorldScene->removeObjectFromScene (reference); mWorldScene->addPostponedPhysicsObjects(); } } void World::advanceTime (double hours, bool incremental) { if (!incremental) { // When we fast-forward time, we should recharge magic items // in all loaded cells, using game world time float duration = hours * 3600; const float timeScaleFactor = getTimeScaleFactor(); if (timeScaleFactor != 0.0f) duration /= timeScaleFactor; rechargeItems(duration, false); } mWeatherManager->advanceTime (hours, incremental); mCurrentDate->advanceTime(hours, mGlobalVariables); updateSkyDate(); if (!incremental) { mRendering->notifyWorldSpaceChanged(); mProjectileManager->clear(); mDiscardMovements = true; } } float World::getTimeScaleFactor() const { return mCurrentDate->getTimeScaleFactor(); } void World::setSimulationTimeScale(float scale) { mSimulationTimeScale = std::max(0.f, scale); MWBase::Environment::get().getSoundManager()->setSimulationTimeScale(mSimulationTimeScale); } TimeStamp World::getTimeStamp() const { return mCurrentDate->getTimeStamp(); } ESM::EpochTimeStamp World::getEpochTimeStamp() const { return mCurrentDate->getEpochTimeStamp(); } bool World::toggleSky() { mSky = !mSky; mRendering->setSkyEnabled(mSky); return mSky; } int World::getMasserPhase() const { return mRendering->skyGetMasserPhase(); } int World::getSecundaPhase() const { return mRendering->skyGetSecundaPhase(); } void World::setMoonColour (bool red) { mRendering->skySetMoonColour (red); } void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); mDiscardMovements = true; if (changeEvent && !Misc::StringUtils::ciEqual(mCurrentWorldSpace, cellName)) { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); mCurrentWorldSpace = cellName; } removeContainerScripts(getPlayerPtr()); mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); mRendering->getCamera()->instantTransition(); } void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { mPhysics->clearQueuedMovement(); mDiscardMovements = true; if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace) { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); } removeContainerScripts(getPlayerPtr()); mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); mRendering->getCamera()->instantTransition(); } void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { if (!changeEvent) mCurrentWorldSpace = cellId.mWorldspace; if (cellId.mPaged) changeToExteriorCell (position, adjustPlayerPos, changeEvent); else changeToInteriorCell (cellId.mWorldspace, position, adjustPlayerPos, changeEvent); mCurrentDate->setup(mGlobalVariables); } void World::markCellAsUnchanged() { return mWorldScene->markCellAsUnchanged(); } float World::getMaxActivationDistance() const { if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); static const int iMaxActivateDist = mStore.get().find("iMaxActivateDist")->mValue.getInteger(); return static_cast(iMaxActivateDist); } MWWorld::Ptr World::getFacedObject() { MWWorld::Ptr facedObject; if (MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager()->isConsoleMode()) facedObject = getFacedObject(getMaxActivationDistance() * 50, false); else { float activationDistance = getActivationDistancePlusTelekinesis(); facedObject = getFacedObject(activationDistance, true); if (!facedObject.isEmpty() && !facedObject.getClass().allowTelekinesis(facedObject) && mDistanceToFacedObject > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode()) return nullptr; } return facedObject; } float World::getDistanceToFacedObject() { return mDistanceToFacedObject; } osg::Matrixf World::getActorHeadTransform(const MWWorld::ConstPtr& actor) const { const MWRender::Animation *anim = mRendering->getAnimation(actor); if(anim) { const osg::Node *node = anim->getNode("Head"); if(!node) node = anim->getNode("Bip01 Head"); if(node) { osg::NodePathList nodepaths = node->getParentalNodePaths(); if(!nodepaths.empty()) return osg::computeLocalToWorld(nodepaths[0]); } } return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); } std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) { const ESM::Position &posdata = ptr.getRefData().getPosition(); osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); // the origin of hitbox is an actor's front, not center distance += halfExtents.y(); // special cased for better aiming with the camera // if we do not hit anything, will use the default approach as fallback if (ptr == getPlayerPtr()) { osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(!result.first.isEmpty()) return std::make_pair(result.first, result.second); } osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); // general case, compatible with all types of different creatures // note: we intentionally do *not* use the collision box offset here, this is required to make // some flying creatures work that have their collision box offset in the air pos.z() += halfExtents.z(); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(result.first.isEmpty()) return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); return std::make_pair(result.first, result.second); } void World::deleteObject (const Ptr& ptr) { if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr) { if (ptr == getPlayerPtr()) throw std::runtime_error("can not delete player object"); ptr.getRefData().setCount(0); if (ptr.isInCell() && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() && ptr.getRefData().isEnabled()) { mWorldScene->removeObjectFromScene (ptr); mLocalScripts.remove (ptr); removeContainerScripts (ptr); } } } void World::undeleteObject(const Ptr& ptr) { if (!ptr.getCellRef().hasContentFile()) return; if (ptr.getRefData().isDeleted()) { ptr.getRefData().setCount(1); if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() && ptr.getRefData().isEnabled()) { mWorldScene->addObjectToScene(ptr); std::string script = ptr.getClass().getScript(ptr); if (!script.empty()) mLocalScripts.add(script, ptr); addContainerScripts(ptr, ptr.getCell()); } } } MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics, bool keepActive) { ESM::Position pos = ptr.getRefData().getPosition(); std::memcpy(pos.pos, &position, sizeof(osg::Vec3f)); ptr.getRefData().setPosition(pos); CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || (currCell && mWorldScene->isCellActive(*currCell)); MWWorld::Ptr newPtr = ptr; if (!isPlayer && !currCell) throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: current cell is nullptr"); if (!newCell) throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: new cell is nullptr"); if (currCell != newCell) { removeContainerScripts(ptr); if (isPlayer) { if (!newCell->isExterior()) { changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos, false); removeContainerScripts(getPlayerPtr()); } else { if (mWorldScene->isCellActive(*newCell)) mWorldScene->changePlayerCell(newCell, pos, false); else mWorldScene->changeToExteriorCell(pos, false); } addContainerScripts (getPlayerPtr(), newCell); newPtr = getPlayerPtr(); } else { bool currCellActive = mWorldScene->isCellActive(*currCell); bool newCellActive = mWorldScene->isCellActive(*newCell); if (!currCellActive && newCellActive) { newPtr = currCell->moveTo(ptr, newCell); if(newPtr.getRefData().isEnabled()) mWorldScene->addObjectToScene(newPtr); std::string script = newPtr.getClass().getScript(newPtr); if (!script.empty()) { mLocalScripts.add(script, newPtr); } addContainerScripts(newPtr, newCell); } else if (!newCellActive && currCellActive) { mWorldScene->removeObjectFromScene(ptr, keepActive); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; newPtr = currCell->moveTo(ptr, newCell); newPtr.getRefData().setBaseNode(nullptr); } else if (!currCellActive && !newCellActive) newPtr = currCell->moveTo(ptr, newCell); else // both cells active { newPtr = currCell->moveTo(ptr, newCell); mRendering->updatePtr(ptr, newPtr); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr); mPhysics->updatePtr(ptr, newPtr); MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); mechMgr->updateCell(ptr, newPtr); std::string script = ptr.getClass().getScript(ptr); if (!script.empty()) { mLocalScripts.remove(ptr); removeContainerScripts (ptr); mLocalScripts.add(script, newPtr); addContainerScripts (newPtr, newCell); } } } MWBase::Environment::get().getWindowManager()->updateConsoleObjectPtr(ptr, newPtr); MWBase::Environment::get().getScriptManager()->getGlobalScripts().updatePtrs(ptr, newPtr); } if (haveToMove && newPtr.getRefData().getBaseNode()) { mWorldScene->updateObjectPosition(newPtr, position, movePhysics); if (movePhysics) { if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } } if (isPlayer) mWorldScene->playerMoved(position); else { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(newPtr); } return newPtr; } MWWorld::Ptr World::moveObject(const Ptr& ptr, const osg::Vec3f& position, bool movePhysics, bool moveToActive) { const osg::Vec2i index = positionToCellIndex(position.x(), position.y()); CellStore* cell = ptr.getCell(); CellStore* newCell = getExterior(index.x(), index.y()); bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) cell = newCell; return moveObject(ptr, cell, position, movePhysics); } MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive) { auto* actor = mPhysics->getActor(ptr); osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; if (actor) actor->adjustPosition(vec); if (ptr.getClass().isActor()) return moveObject(ptr, newpos, false, moveToActive && ptr != getPlayerPtr()); return moveObject(ptr, newpos); } void World::scaleObject (const Ptr& ptr, float scale, bool force) { if (!force && scale == ptr.getCellRef().getScale()) return; if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingAgentBounds(ptr)); ptr.getCellRef().setScale(scale); mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); if(ptr.getRefData().getBaseNode() != nullptr) mWorldScene->updateObjectScale(ptr); if (mPhysics->getActor(ptr)) mNavigator->addAgent(getPathfindingAgentBounds(ptr)); else if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } void World::rotateObject(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) { ESM::Position pos = ptr.getRefData().getPosition(); float *objRot = pos.rot; if (flags & MWBase::RotationFlag_adjust) { objRot[0] += rot.x(); objRot[1] += rot.y(); objRot[2] += rot.z(); } else { objRot[0] = rot.x(); objRot[1] = rot.y(); objRot[2] = rot.z(); } if(ptr.getClass().isActor()) { /* HACK? Actors shouldn't really be rotating around X (or Y), but * currently it's done so for rotating the camera, which needs * clamping. */ objRot[0] = std::clamp(objRot[0], -osg::PI_2, osg::PI_2); objRot[1] = Misc::normalizeAngle(objRot[1]); objRot[2] = Misc::normalizeAngle(objRot[2]); } ptr.getRefData().setPosition(pos); mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); if(ptr.getRefData().getBaseNode() != nullptr) { const auto order = flags & MWBase::RotationFlag_inverseOrder ? RotationOrder::inverse : RotationOrder::direct; mWorldScene->updateObjectRotation(ptr, order); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } } void World::adjustPosition(const Ptr &ptr, bool force) { if (ptr.isEmpty()) { Log(Debug::Warning) << "Unable to adjust position for empty object"; return; } osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); if(!ptr.getRefData().getBaseNode()) { // will be adjusted when Ptr's cell becomes active return; } if (!ptr.isInCell()) { Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell"; return; } const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); pos.z() = std::min(pos.z(), traced.z()); } moveObject(ptr, ptr.getCell(), pos); } void World::fixPosition() { const MWWorld::Ptr actor = getPlayerPtr(); const float distance = 128.f; ESM::Position esmPos = actor.getRefData().getPosition(); osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1)); osg::Vec3f pos (esmPos.asVec3()); int direction = 0; int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; osg::Vec3f targetPos = pos; for (int i=0; i<4; ++i) { direction = fallbackDirections[i]; if (direction == 0) targetPos = pos + (orientation * osg::Vec3f(0,1,0)) * distance; else if(direction == 1) targetPos = pos - (orientation * osg::Vec3f(0,1,0)) * distance; else if(direction == 2) targetPos = pos - (orientation * osg::Vec3f(1,0,0)) * distance; else if(direction == 3) targetPos = pos + (orientation * osg::Vec3f(1,0,0)) * distance; // destination is free if (!castRay(pos.x(), pos.y(), pos.z(), targetPos.x(), targetPos.y(), targetPos.z())) break; } targetPos.z() += distance / 2.f; // move up a bit to get out from geometry, will snap down later osg::Vec3f traced = mPhysics->traceDown(actor, targetPos, Constants::CellSizeInUnits); if (traced != pos) { esmPos.pos[0] = traced.x(); esmPos.pos[1] = traced.y(); esmPos.pos[2] = traced.z(); MWWorld::ActionTeleport(actor.getCell()->isExterior() ? "" : actor.getCell()->getCell()->mName, esmPos, false).execute(actor); } } void World::rotateWorldObject (const Ptr& ptr, const osg::Quat& rotate) { if(ptr.getRefData().getBaseNode() != nullptr) { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); mPhysics->updateRotation(ptr, rotate); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(*object); } } MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) { ESM::Position ipos = referenceObject.getRefData().getPosition(); osg::Vec3f pos(ipos.asVec3()); osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1)); int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; osg::Vec3f spawnPoint = pos; for (int i=0; i<4; ++i) { direction = fallbackDirections[i]; if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance; else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance; else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance; else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance; if (!ptr.getClass().isActor()) break; // check if spawn point is safe, fall back to another direction if not spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), pos.x(), pos.y(), pos.z() + 20)) { // safe break; } } ipos.pos[0] = spawnPoint.x(); ipos.pos[1] = spawnPoint.y(); ipos.pos[2] = spawnPoint.z(); if (referenceObject.getClass().isActor()) { ipos.rot[0] = 0; ipos.rot[1] = 0; } MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); adjustPosition(placed, true); // snap to ground return placed; } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = Constants::CellSizeInUnits; x = static_cast(cellSize * cellX); y = static_cast(cellSize * cellY); if (centre) { x += cellSize/2; y += cellSize/2; } } void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) { mPhysics->queueObjectMovement(ptr, velocity); } void World::updateAnimatedCollisionShape(const Ptr &ptr) { mPhysics->updateAnimatedCollisionShape(ptr); } void World::doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { processDoors(duration); mProjectileManager->update(duration); mPhysics->stepSimulation(duration, mDiscardMovements, frameStart, frameNumber, stats); mProjectileManager->processHits(); mDiscardMovements = false; mPhysics->moveActors(); } void World::updateNavigator() { mPhysics->forEachAnimatedObject([&] (const auto& pair) { const auto [object, changed] = pair; if (changed) updateNavigatorObject(*object); }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) updateNavigatorObject(*object); auto player = getPlayerPtr(); if (mShouldUpdateNavigator && player.getCell() != nullptr) { mNavigator->update(player.getRefData().getPosition().asVec3()); mShouldUpdateNavigator = false; } } void World::updateNavigatorObject(const MWPhysics::Object& object) { if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None) return; const MWWorld::Ptr ptr = object.getPtr(); const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(), DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}); mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) || mShouldUpdateNavigator; } const MWPhysics::RayCastingInterface* World::getRayCasting() const { return mPhysics.get(); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) { int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door; bool result = castRay(x1, y1, z1, x2, y2, z2, mask); return result; } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) { osg::Vec3f a(x1,y1,z1); osg::Vec3f b(x2,y2,z2); MWPhysics::RayCastingResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector(), mask); return result.mHit; } bool World::castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) { return mPhysics->castRay(from, to, ignore, std::vector(), mask).mHit; } bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration) { const ESM::Position& objPos = door.getRefData().getPosition(); auto oldRot = objPos.asRotationVec3(); auto newRot = oldRot; float minRot = door.getCellRef().getPosition().rot[2]; float maxRot = minRot + osg::DegreesToRadians(90.f); float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1); float targetRot = std::clamp(oldRot.z() + diff, minRot, maxRot); newRot.z() = targetRot; rotateObject(door, newRot, MWBase::RotationFlag_none); bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot; /// \todo should use convexSweepTest here bool collisionWithActor = false; for (auto& [ptr, point, normal] : mPhysics->getCollisionsPoints(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor)) { if (ptr.getClass().isActor()) { auto localPoint = objPos.asVec3() - point; osg::Vec3f direction = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * localPoint - localPoint; direction.normalize(); mPhysics->reportCollision(Misc::Convert::toBullet(point), Misc::Convert::toBullet(normal)); if (direction * normal < 0) // door is turning away from actor continue; collisionWithActor = true; // Collided with actor, ask actor to try to avoid door if(ptr != getPlayerPtr() ) { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(door),ptr); } // we need to undo the rotation reached = false; } } // Cancel door closing sound if collision with actor is detected if (collisionWithActor) { const ESM::Door* ref = door.get()->mBase; if (state == MWWorld::DoorState::Opening) { const std::string& openSound = ref->mOpenSound; if (!openSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, openSound)) MWBase::Environment::get().getSoundManager()->stopSound3D(door, openSound); } else if (state == MWWorld::DoorState::Closing) { const std::string& closeSound = ref->mCloseSound; if (!closeSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, closeSound)) MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound); } rotateObject(door, oldRot, MWBase::RotationFlag_none); } return reached; } void World::processDoors(float duration) { auto it = mDoorStates.begin(); while (it != mDoorStates.end()) { if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) { // The door is no longer in an active cell, or it was disabled. // Erase from mDoorStates, since we no longer need to move it. // Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates. mDoorStates.erase(it++); } else { bool reached = rotateDoor(it->first, it->second, duration); if (reached) { // Mark as non-moving it->first.getClass().setDoorState(it->first, MWWorld::DoorState::Idle); mDoorStates.erase(it++); } else ++it; } } } void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) { MWPhysics::Actor *physicActor = mPhysics->getActor(ptr); if (physicActor && physicActor->getCollisionMode() != internal) { physicActor->enableCollisionMode(internal); physicActor->enableCollisionBody(external); } } bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr) { MWPhysics::Actor *physicActor = mPhysics->getActor(ptr); return physicActor && physicActor->getCollisionMode(); } bool World::toggleCollisionMode() { if (mPhysics->toggleCollisionMode()) { adjustPosition(getPlayerPtr(), true); return true; } return false; } bool World::toggleRenderMode (MWRender::RenderMode mode) { switch (mode) { case MWRender::Render_CollisionDebug: return mPhysics->toggleDebugRendering(); default: return mRendering->toggleRenderMode(mode); } } const ESM::Potion *World::createRecord (const ESM::Potion& record) { return mStore.insert(record); } const ESM::Class *World::createRecord (const ESM::Class& record) { return mStore.insert(record); } const ESM::Spell *World::createRecord (const ESM::Spell& record) { return mStore.insert(record); } const ESM::Cell *World::createRecord (const ESM::Cell& record) { return mStore.insert(record); } const ESM::CreatureLevList *World::createOverrideRecord(const ESM::CreatureLevList &record) { return mStore.overrideRecord(record); } const ESM::ItemLevList *World::createOverrideRecord(const ESM::ItemLevList &record) { return mStore.overrideRecord(record); } const ESM::Creature *World::createOverrideRecord(const ESM::Creature &record) { return mStore.overrideRecord(record); } const ESM::NPC *World::createOverrideRecord(const ESM::NPC &record) { return mStore.overrideRecord(record); } const ESM::Container *World::createOverrideRecord(const ESM::Container &record) { return mStore.overrideRecord(record); } const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; if (Misc::StringUtils::ciEqual(record.mId, "player")) { const ESM::NPC *player = mPlayer->getPlayer().get()->mBase; update = record.isMale() != player->isMale() || !Misc::StringUtils::ciEqual(record.mRace, player->mRace) || !Misc::StringUtils::ciEqual(record.mHead, player->mHead) || !Misc::StringUtils::ciEqual(record.mHair, player->mHair); } const ESM::NPC *ret = mStore.insert(record); if (update) { renderPlayer(); } return ret; } const ESM::Armor *World::createRecord (const ESM::Armor& record) { return mStore.insert(record); } const ESM::Weapon *World::createRecord (const ESM::Weapon& record) { return mStore.insert(record); } const ESM::Clothing *World::createRecord (const ESM::Clothing& record) { return mStore.insert(record); } const ESM::Enchantment *World::createRecord (const ESM::Enchantment& record) { return mStore.insert(record); } const ESM::Book *World::createRecord (const ESM::Book& record) { return mStore.insert(record); } void World::update (float duration, bool paused) { if (mGoToJail && !paused) goToJail(); // Reset "traveling" flag - there was a frame to detect traveling. mPlayerTraveling = false; // The same thing for "in jail" flag: reset it if: // 1. Player was in jail // 2. Jailing window was closed if (mPlayerInJail && !mGoToJail && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail)) mPlayerInJail = false; updateWeather(duration, paused); if (!paused) { updateNavigator(); } mPlayer->update(); mPhysics->debugDraw(); mWorldScene->update(duration); mRendering->update(duration, paused); updateSoundListener(); mSpellPreloadTimer -= duration; if (mSpellPreloadTimer <= 0.f) { mSpellPreloadTimer = 0.1f; preloadSpells(); } } void World::updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { if (!paused) { doPhysics (duration, frameStart, frameNumber, stats); } else { // zero the async stats if we are paused stats.setAttribute(frameNumber, "physicsworker_time_begin", 0); stats.setAttribute(frameNumber, "physicsworker_time_taken", 0); stats.setAttribute(frameNumber, "physicsworker_time_end", 0); } } void World::preloadSpells() { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); if (!selectedSpell.empty()) { const ESM::Spell* spell = mStore.get().search(selectedSpell); if (spell) preloadEffects(&spell->mEffects); } const MWWorld::Ptr& selectedEnchantItem = MWBase::Environment::get().getWindowManager()->getSelectedEnchantItem(); if (!selectedEnchantItem.isEmpty()) { std::string enchantId = selectedEnchantItem.getClass().getEnchantment(selectedEnchantItem); if (!enchantId.empty()) { const ESM::Enchantment* ench = mStore.get().search(enchantId); if (ench) preloadEffects(&ench->mEffects); } } const MWWorld::Ptr& selectedWeapon = MWBase::Environment::get().getWindowManager()->getSelectedWeapon(); if (!selectedWeapon.isEmpty()) { std::string enchantId = selectedWeapon.getClass().getEnchantment(selectedWeapon); if (!enchantId.empty()) { const ESM::Enchantment* ench = mStore.get().search(enchantId); if (ench && ench->mData.mType == ESM::Enchantment::WhenStrikes) preloadEffects(&ench->mEffects); } } } void World::updateSoundListener() { osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); osg::Vec3f listenerPos; if (isFirstPerson()) listenerPos = cameraPosition; else listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z()); osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) * osg::Quat(refpos.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0,0,-1)); osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0); osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1); bool underwater = isUnderwater(getPlayerPtr().getCell(), cameraPosition); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } void World::updateWindowManager () { try { // inform the GUI about focused object MWWorld::Ptr object = getFacedObject (); // retrieve object dimensions so we know where to place the floating label if (!object.isEmpty ()) { osg::BoundingBox bb = mPhysics->getBoundingBox(object); if (!bb.valid() && object.getRefData().getBaseNode()) { osg::ComputeBoundsVisitor computeBoundsVisitor; computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); object.getRefData().getBaseNode()->accept(computeBoundsVisitor); bb = computeBoundsVisitor.getBoundingBox(); } osg::Vec4f screenBounds = mRendering->getScreenBounds(bb); MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); } MWBase::Environment::get().getWindowManager()->setFocusObject(object); } catch (std::exception& e) { Log(Debug::Error) << "Error updating window manager: " << e.what(); } } MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer) { const float camDist = mRendering->getCamera()->getCameraDistance(); maxDistance += camDist; MWWorld::Ptr facedObject; MWRender::RenderingManager::RayResult rayToObject; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); rayToObject = mRendering->castCameraToViewportRay(x, y, maxDistance, ignorePlayer); } else rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer); facedObject = rayToObject.mHitObject; if (facedObject.isEmpty() && rayToObject.mHitRefnum.isSet()) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum); if (!facedObject.isEmpty()) break; } } if (rayToObject.mHit) mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist; else mDistanceToFacedObject = -1; return facedObject; } bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, bool ignorePlayer, bool ignoreActors) { MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); res.mHit = rayRes.mHit; res.mHitPos = rayRes.mHitPointWorld; res.mHitNormal = rayRes.mHitNormalWorld; res.mHitObject = rayRes.mHitObject; if (res.mHitObject.isEmpty() && rayRes.mHitRefnum.isSet()) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { res.mHitObject = cellstore->searchViaRefNum(rayRes.mHitRefnum); if (!res.mHitObject.isEmpty()) break; } } return res.mHit; } bool World::isCellExterior() const { const CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { return currentCell->getCell()->isExterior(); } return false; } bool World::isCellQuasiExterior() const { const CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { if (!(currentCell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) return false; else return true; } return false; } int World::getCurrentWeather() const { return mWeatherManager->getWeatherID(); } int World::getNextWeather() const { return mWeatherManager->getNextWeatherID(); } float World::getWeatherTransition() const { return mWeatherManager->getTransitionFactor(); } unsigned int World::getNightDayMode() const { return mWeatherManager->getNightDayMode(); } void World::changeWeather(const std::string& region, const unsigned int id) { mWeatherManager->changeWeather(region, id); } void World::modRegion(const std::string ®ionid, const std::vector &chances) { mWeatherManager->modRegion(regionid, chances); } osg::Vec2f World::getNorthVector (const CellStore* cell) { MWWorld::ConstPtr northmarker = cell->searchConst("northmarker"); if (northmarker.isEmpty()) return osg::Vec2f(0, 1); osg::Quat orient (-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0,0,1)); osg::Vec3f dir = orient * osg::Vec3f(0,1,0); osg::Vec2f d (dir.x(), dir.y()); return d; } struct GetDoorMarkerVisitor { std::vector& mOut; bool operator()(const MWWorld::Ptr& ptr) { MWWorld::LiveCellRef& ref = *static_cast* >(ptr.getBase()); if (!ref.mData.isEnabled() || ref.mData.isDeleted()) return true; if (ref.mRef.getTeleport()) { World::DoorMarker newMarker; newMarker.name = MWClass::Door::getDestination(ref); ESM::CellId cellid; if (!ref.mRef.getDestCell().empty()) { cellid.mWorldspace = ref.mRef.getDestCell(); cellid.mPaged = false; cellid.mIndex.mX = 0; cellid.mIndex.mY = 0; } else { cellid.mPaged = true; const osg::Vec2i index = positionToCellIndex(ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1]); cellid.mIndex.mX = index.x(); cellid.mIndex.mY = index.y(); } newMarker.dest = cellid; ESM::Position pos = ref.mData.getPosition (); newMarker.x = pos.pos[0]; newMarker.y = pos.pos[1]; mOut.push_back(newMarker); } return true; } }; void World::getDoorMarkers (CellStore* cell, std::vector& out) { GetDoorMarkerVisitor visitor {out}; cell->forEachType(visitor); } void World::setWaterHeight(const float height) { mPhysics->setWaterHeight(height); mRendering->setWaterHeight(height); } bool World::toggleWater() { return mRendering->toggleRenderMode(MWRender::Render_Water); } bool World::toggleWorld() { return mRendering->toggleRenderMode(MWRender::Render_Scene); } bool World::toggleBorders() { return mRendering->toggleBorders(); } void World::PCDropped (const Ptr& item) { std::string script = item.getClass().getScript(item); // Set OnPCDrop Variable on item's script, if it has a script with that variable declared if(script != "") item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } MWWorld::Ptr World::placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) { const float maxDist = 200.f; MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true); CellStore* cell = getPlayerPtr().getCell(); ESM::Position pos = getPlayerPtr().getRefData().getPosition(); if (result.mHit) { pos.pos[0] = result.mHitPointWorld.x(); pos.pos[1] = result.mHitPointWorld.y(); pos.pos[2] = result.mHitPointWorld.z(); } // We want only the Z part of the player's rotation pos.rot[0] = 0; pos.rot[1] = 0; // copy the object and set its count Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); // only the player place items in the world, so no need to check actor PCDropped(dropped); return dropped; } bool World::canPlaceObject(float cursorX, float cursorY) { const float maxDist = 200.f; MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true); if (result.mHit) { // check if the wanted position is on a flat surface, and not e.g. against a vertical wall if (std::acos((result.mHitNormalWorld/result.mHitNormalWorld.length()) * osg::Vec3f(0,0,1)) >= osg::DegreesToRadians(30.f)) return false; return true; } else return false; } Ptr World::copyObjectToCell(const ConstPtr &object, CellStore* cell, ESM::Position pos, int count, bool adjustPos) { if (!cell) throw std::runtime_error("copyObjectToCell(): cannot copy object to null cell"); if (cell->isExterior()) { const osg::Vec2i index = positionToCellIndex(pos.pos[0], pos.pos[1]); cell = mCells.getExterior(index.x(), index.y()); } MWWorld::Ptr dropped = object.getClass().copyToCell(object, *cell, pos, count); // Reset some position values that could be uninitialized if this item came from a container dropped.getCellRef().setPosition(pos); dropped.getCellRef().unsetRefNum(); if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); } std::string script = dropped.getClass().getScript(dropped); if (!script.empty()) { mLocalScripts.add(script, dropped); } addContainerScripts(dropped, cell); } if (!object.getClass().isActor() && adjustPos && dropped.getRefData().getBaseNode()) { // Adjust position so the location we wanted ends up in the middle of the object bounding box osg::ComputeBoundsVisitor computeBounds; computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem); dropped.getRefData().getBaseNode()->accept(computeBounds); osg::BoundingBox bounds = computeBounds.getBoundingBox(); if (bounds.valid()) { bounds.set(bounds._min - pos.asVec3(), bounds._max - pos.asVec3()); osg::Vec3f adjust ( (bounds.xMin() + bounds.xMax()) / 2, (bounds.yMin() + bounds.yMax()) / 2, bounds.zMin() ); pos.pos[0] -= adjust.x(); pos.pos[1] -= adjust.y(); pos.pos[2] -= adjust.z(); moveObject(dropped, pos.asVec3()); } } return dropped; } MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const ConstPtr& object, int amount) { MWWorld::CellStore* cell = actor.getCell(); ESM::Position pos = actor.getRefData().getPosition(); // We want only the Z part of the actor's rotation pos.rot[0] = 0; pos.rot[1] = 0; osg::Vec3f orig = pos.asVec3(); orig.z() += 20; osg::Vec3f dir (0, 0, -1); float len = 1000000.0; MWRender::RenderingManager::RayResult result = mRendering->castRay(orig, orig+dir*len, true, true); if (result.mHit) pos.pos[2] = result.mHitPointWorld.z(); // copy the object and set its count Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); return dropped; } void World::processChangedSettings(const Settings::CategorySettingVector& settings) { mRendering->processChangedSettings(settings); } bool World::isFlying(const MWWorld::Ptr &ptr) const { if(!ptr.getClass().isActor()) return false; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); if (stats.isDead()) return false; const bool isPlayer = ptr == getPlayerConstPtr(); if (!(isPlayer && mGodMode) && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0) return false; if (ptr.getClass().canFly(ptr)) return true; if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && isLevitationEnabled()) return true; const MWPhysics::Actor* actor = mPhysics->getActor(ptr); if(!actor) return true; return false; } bool World::isSlowFalling(const MWWorld::Ptr &ptr) const { if(!ptr.getClass().isActor()) return false; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).getMagnitude() > 0) return true; return false; } bool World::isSubmerged(const MWWorld::ConstPtr &object) const { return isUnderwater(object, 1.0f/mSwimHeightScale); } bool World::isSwimming(const MWWorld::ConstPtr &object) const { return isUnderwater(object, mSwimHeightScale); } bool World::isWading(const MWWorld::ConstPtr &object) const { const float kneeDeep = 0.25f; return isUnderwater(object, kneeDeep); } bool World::isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const { osg::Vec3f pos (object.getRefData().getPosition().asVec3()); pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z(); const CellStore *currCell = object.isInCell() ? object.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup return isUnderwater(currCell, pos); } bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const { if (!cell) return false; if (!(cell->getCell()->hasWater())) { return false; } return pos.z() < cell->getWaterLevel(); } bool World::isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const { const MWWorld::CellStore* cell = target.getCell(); if (!cell->getCell()->hasWater()) return true; float waterlevel = cell->getWaterLevel(); // SwimHeightScale affects the upper z position an actor can swim to // while in water. Based on observation from the original engine, // the upper z position you get with a +1 SwimHeightScale is the depth // limit for being able to cast water walking on an underwater target. if (isUnderwater(target, mSwimHeightScale + 1) || (isUnderwater(cell, target.getRefData().getPosition().asVec3()) && !mPhysics->canMoveToWaterSurface(target, waterlevel))) return false; // not castable if too deep or if not enough room to move actor to surface else return true; } bool World::isOnGround(const MWWorld::Ptr &ptr) const { return mPhysics->isOnGround(ptr); } void World::togglePOV(bool force) { mRendering->getCamera()->toggleViewMode(force); } bool World::isFirstPerson() const { return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::FirstPerson; } bool World::isPreviewModeEnabled() const { return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview; } bool World::toggleVanityMode(bool enable) { return mRendering->getCamera()->toggleVanityMode(enable); } void World::disableDeferredPreviewRotation() { mRendering->getCamera()->disableDeferredPreviewRotation(); } void World::applyDeferredPreviewRotationToPlayer(float dt) { mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt); } MWRender::Camera* World::getCamera() { return mRendering->getCamera(); } bool World::vanityRotateCamera(float * rot) { auto* camera = mRendering->getCamera(); if(!camera->isVanityOrPreviewModeEnabled()) return false; camera->setPitch(camera->getPitch() + rot[0]); camera->setYaw(camera->getYaw() + rot[2]); return true; } void World::saveLoaded() { mStore.validateDynamic(); } void World::setupPlayer() { const ESM::NPC *player = mStore.get().find("player"); if (!mPlayer) mPlayer = std::make_unique(player); else { // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); mNavigator->removeAgent(getPathfindingAgentBounds(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(getPlayerPtr()); mPlayer->set(player); } Ptr ptr = mPlayer->getPlayer(); mRendering->setupPlayer(ptr); MWBase::Environment::get().getLuaManager()->setupPlayer(ptr); } void World::renderPlayer() { MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); MWWorld::Ptr player = getPlayerPtr(); mRendering->renderPlayer(player); MWRender::NpcAnimation* anim = static_cast(mRendering->getAnimation(player)); player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setContListener(anim); scaleObject(player, player.getCellRef().getScale(), true); // apply race height rotateObject(player, osg::Vec3f(), MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr()); std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr()); model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); mPhysics->remove(getPlayerPtr()); mPhysics->addActor(getPlayerPtr(), model); applyLoopingParticles(player); mNavigator->addAgent(getPathfindingAgentBounds(getPlayerConstPtr())); } World::RestPermitted World::canRest () const { CellStore *currentCell = mWorldScene->getCurrentCell(); Ptr player = mPlayer->getPlayer(); RefData &refdata = player.getRefData(); osg::Vec3f playerPos(refdata.getPosition().asVec3()); const MWPhysics::Actor* actor = mPhysics->getActor(player); if (!actor) throw std::runtime_error("can't find player"); if(mPlayer->enemiesNearby()) return Rest_EnemiesAreNearby; if (isUnderwater(currentCell, playerPos) || isWalkingOnWater(player)) return Rest_PlayerIsUnderwater; float fallHeight = player.getClass().getCreatureStats(player).getFallHeight(); float epsilon = 1e-4; if ((actor->getCollisionMode() && (!mPhysics->isOnSolidGround(player) || fallHeight >= epsilon)) || isFlying(player)) return Rest_PlayerIsInAir; if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf()) return Rest_OnlyWaiting; return Rest_Allowed; } MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr) { auto* animation = mRendering->getAnimation(ptr); if(!animation) { mWorldScene->removeFromPagedRefs(ptr); animation = mRendering->getAnimation(ptr); if(animation) mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); } return animation; } const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const { return mRendering->getAnimation(ptr); } void World::screenshot(osg::Image* image, int w, int h) { mRendering->screenshot(image, w, h); } bool World::screenshot360(osg::Image* image) { return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) { auto state = door.getClass().getDoorState(door); switch (state) { case MWWorld::DoorState::Idle: if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2]) state = MWWorld::DoorState::Opening; // if closed, then open else state = MWWorld::DoorState::Closing; // if open, then close break; case MWWorld::DoorState::Closing: state = MWWorld::DoorState::Opening; // if closing, then open break; case MWWorld::DoorState::Opening: default: state = MWWorld::DoorState::Closing; // if opening, then close break; } door.getClass().setDoorState(door, state); mDoorStates[door] = state; } void World::activateDoor(const Ptr &door, MWWorld::DoorState state) { door.getClass().setDoorState(door, state); mDoorStates[door] = state; if (state == MWWorld::DoorState::Idle) { mDoorStates.erase(door); rotateDoor(door, state, 1); } } bool World::getPlayerStandingOn (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorStandingOn(player, object); } bool World::getActorStandingOn (const MWWorld::ConstPtr& object) { std::vector actors; mPhysics->getActorsStandingOn(object, actors); return !actors.empty(); } void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) { mPhysics->getActorsStandingOn(object, actors); } bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorCollidingWith(player, object); } bool World::getActorCollidingWith (const MWWorld::ConstPtr& object) { std::vector actors; mPhysics->getActorsCollidingWith(object, actors); return !actors.empty(); } void World::hurtStandingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; std::vector actors; mPhysics->getActorsStandingOn(object, actors); for (const Ptr &actor : actors) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; mPhysics->markAsNonSolid (object); if (actor == getPlayerPtr() && mGodMode) continue; MWMechanics::DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage")) MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f); } } } void World::hurtCollidingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; std::vector actors; mPhysics->getActorsCollidingWith(object, actors); for (const Ptr &actor : actors) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; mPhysics->markAsNonSolid (object); if (actor == getPlayerPtr() && mGodMode) continue; MWMechanics::DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage")) MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f); } } } float World::getWindSpeed() { if (isCellExterior() || isCellQuasiExterior()) return mWeatherManager->getWindSpeed(); else return 0.f; } bool World::isInStorm() const { if (isCellExterior() || isCellQuasiExterior()) return mWeatherManager->isInStorm(); else return false; } osg::Vec3f World::getStormDirection() const { if (isCellExterior() || isCellQuasiExterior()) return mWeatherManager->getStormDirection(); else return osg::Vec3f(0,1,0); } struct GetContainersOwnedByVisitor { GetContainersOwnedByVisitor(const MWWorld::ConstPtr& owner, std::vector& out) : mOwner(owner) , mOut(out) { } MWWorld::ConstPtr mOwner; std::vector& mOut; bool operator()(const MWWorld::Ptr& ptr) { if (ptr.getRefData().isDeleted()) return true; // vanilla Morrowind does not allow to sell items from containers with zero capacity if (ptr.getClass().getCapacity(ptr) <= 0.f) return true; if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId())) mOut.push_back(ptr); return true; } }; void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector& out) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { GetContainersOwnedByVisitor visitor (owner, out); cellstore->forEachType(visitor); } } void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { cellstore->forEach([&] (const auto& ptr) { if (ptr.getRefData().getBaseNode() && Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) out.push_back(ptr); return true; }); } } bool World::getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor) { if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled if (!targetActor.getRefData().getBaseNode() || !actor.getRefData().getBaseNode()) return false; // not in active cell return mPhysics->getLineOfSight(actor, targetActor); } float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater) { osg::Vec3f to (dir); to.normalize(); to = from + (to * maxDist); int collisionTypes = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; if (includeWater) { collisionTypes |= MWPhysics::CollisionType_Water; } MWPhysics::RayCastingResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector(), collisionTypes); if (!result.mHit) return maxDist; else return (result.mHitPos - from).length(); } void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { MWPhysics::Actor *physicActor = mPhysics->getActor(actor); if (physicActor) physicActor->enableCollisionBody(enable); } bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) { pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; MWWorld::CellStore *cellStore = getInterior(name); if (!cellStore) return false; std::vector sortedDoors; for (const MWWorld::LiveCellRef& door : cellStore->getReadOnlyDoors().mList) { if (!door.mRef.getTeleport()) continue; sortedDoors.push_back(&door.mRef); } // Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent std::sort(sortedDoors.begin(), sortedDoors.end(), [] (const MWWorld::CellRef *lhs, const MWWorld::CellRef *rhs) { if (lhs->getRefId() != rhs->getRefId()) return lhs->getRefId() < rhs->getRefId(); return lhs->getDestCell() < rhs->getDestCell(); }); for (const MWWorld::CellRef* door : sortedDoors) { MWWorld::CellStore *source = nullptr; // door to exterior if (door->getDestCell().empty()) { ESM::Position doorDest = door->getDoorDest(); const osg::Vec2i index = positionToCellIndex(doorDest.pos[0], doorDest.pos[1]); source = getExterior(index.x(), index.y()); } // door to interior else { source = getInterior(door->getDestCell()); } if (source) { // Find door leading to our current teleport door // and use its destination to position inside cell. for (const MWWorld::LiveCellRef& destDoor : source->getReadOnlyDoors().mList) { if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell())) { /// \note Using _any_ door pointed to the interior, /// not the one pointed to current door. pos = destDoor.mRef.getDoorDest(); pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; return true; } } } } // Fall back to the first static location. const MWWorld::CellRefList::List &statics = cellStore->getReadOnlyStatics().mList; if (!statics.empty()) { pos = statics.begin()->mRef.getPosition(); pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; return true; } return false; } bool World::findExteriorPosition(const std::string &name, ESM::Position &pos) { pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; const ESM::Cell *ext = getExterior(name); if (!ext && name.find(',') != std::string::npos) { try { int x = std::stoi(name.substr(0, name.find(','))); int y = std::stoi(name.substr(name.find(',')+1)); ext = getExterior(x, y)->getCell(); } catch (const std::invalid_argument&) { // This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates } catch (const std::out_of_range&) { throw std::runtime_error("Cell coordinates out of range."); } } if (ext) { int x = ext->getGridX(); int y = ext->getGridY(); indexToPosition(x, y, pos.pos[0], pos.pos[1], true); // Note: Z pos will be adjusted by adjustPosition later pos.pos[2] = 0; return true; } return false; } void World::enableTeleporting(bool enable) { mTeleportEnabled = enable; } bool World::isTeleportingEnabled() const { return mTeleportEnabled; } void World::enableLevitation(bool enable) { mLevitationEnabled = enable; } bool World::isLevitationEnabled() const { return mLevitationEnabled; } void World::reattachPlayerCamera() { mRendering->rebuildPtr(getPlayerPtr()); } bool World::getGodModeState() const { return mGodMode; } bool World::toggleGodMode() { mGodMode = !mGodMode; return mGodMode; } bool World::toggleScripts() { mScriptsEnabled = !mScriptsEnabled; return mScriptsEnabled; } bool World::getScriptsEnabled() const { return mScriptsEnabled; } void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { GameContentLoader gameContentLoader; EsmLoader esmLoader(mStore, mReaders, encoder); gameContentLoader.addLoader(".esm", esmLoader); gameContentLoader.addLoader(".esp", esmLoader); gameContentLoader.addLoader(".omwgame", esmLoader); gameContentLoader.addLoader(".omwaddon", esmLoader); gameContentLoader.addLoader(".project", esmLoader); OMWScriptsLoader omwScriptsLoader(mStore); gameContentLoader.addLoader(".omwscripts", omwScriptsLoader); int idx = 0; for (const std::string &file : content) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { gameContentLoader.load(col.getPath(file), idx, listener); } else { std::string message = "Failed loading " + file + ": the content file does not exist"; throw std::runtime_error(message); } idx++; } if (const auto v = esmLoader.getMasterFileFormat(); v.has_value() && *v == 0) ensureNeededRecords(); // Insert records that may not be present in all versions of master files. } void World::loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { if (!Settings::Manager::getBool("enabled", "Groundcover")) return; Log(Debug::Info) << "Loading groundcover:"; mGroundcoverStore.init(mStore.get(), fileCollections, groundcoverFiles, encoder, listener); } MWWorld::SpellCastState World::startSpellCast(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); std::string message; MWWorld::SpellCastState result = MWWorld::SpellCastState::Success; bool isPlayer = (actor == getPlayerPtr()); std::string selectedSpell = stats.getSpells().getSelectedSpell(); if (!selectedSpell.empty()) { const ESM::Spell* spell = mStore.get().find(selectedSpell); int spellCost = MWMechanics::calcSpellCost(*spell); // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); if (spellCost > 0 && magicka.getCurrent() < spellCost && !godmode) { message = "#{sMagicInsufficientSP}"; result = MWWorld::SpellCastState::InsufficientMagicka; } // If this is a power, check if it was already used in the last 24h if (result == MWWorld::SpellCastState::Success && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell)) { message = "#{sPowerAlreadyUsed}"; result = MWWorld::SpellCastState::PowerAlreadyUsed; } if (result == MWWorld::SpellCastState::Success && !godmode) { // Reduce mana magicka.setCurrent(magicka.getCurrent() - spellCost); stats.setMagicka(magicka); // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) static const float fFatigueSpellBase = mStore.get().find("fFatigueSpellBase")->mValue.getFloat(); static const float fFatigueSpellMult = mStore.get().find("fFatigueSpellMult")->mValue.getFloat(); MWMechanics::DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = actor.getClass().getNormalizedEncumbrance(actor); float fatigueLoss = spellCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); } } if (isPlayer && result != MWWorld::SpellCastState::Success) MWBase::Environment::get().getWindowManager()->messageBox(message); return result; } void World::castSpell(const Ptr &actor, bool manualSpell) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) stats.getAiSequence().getCombatTargets(targetActors); const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); // for player we can take faced object first MWWorld::Ptr target; if (actor == MWMechanics::getPlayer()) target = getFacedObject(); // if the faced object can not be activated, do not use it if (!target.isEmpty() && !target.getClass().hasToolTip(target)) target = nullptr; if (target.isEmpty()) { // For scripted spells we should not use hit contact if (manualSpell) { if (actor != MWMechanics::getPlayer()) { for (const auto& package : stats.getAiSequence()) { if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { target = package->getTarget(); break; } } } } else { // For actor targets, we want to use hit contact with bounding boxes. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. // For object targets, we want the detailed shapes (rendering raycast). // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. std::pair result1 = getHitContact(actor, fCombatDistance, targetActors); // Get the target to use for "on touch" effects, using the facing direction from Head node osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); osg::Vec3f direction = orient * osg::Vec3f(0,1,0); float distance = getMaxActivationDistance(); osg::Vec3f dest = origin + direction * distance; MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); float dist1 = std::numeric_limits::max(); float dist2 = std::numeric_limits::max(); if (!result1.first.isEmpty() && result1.first.getClass().isActor()) dist1 = (origin - result1.second).length(); if (result2.mHit) dist2 = (origin - result2.mHitPointWorld).length(); if (!result1.first.isEmpty() && result1.first.getClass().isActor()) { target = result1.first; hitPosition = result1.second; if (dist1 > getMaxActivationDistance()) target = nullptr; } else if (result2.mHit) { target = result2.mHitObject; hitPosition = result2.mHitPointWorld; if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().hasToolTip(target)) target = nullptr; } } } std::string selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target, false, manualSpell); cast.mHitPosition = hitPosition; if (!selectedSpell.empty()) { const ESM::Spell* spell = mStore.get().find(selectedSpell); cast.cast(spell); } else if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); if (inv.getSelectedEnchantItem() != inv.end()) { const auto& itemPtr = *inv.getSelectedEnchantItem(); auto [slots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); int slot = 0; for(std::size_t i = 0; i < slots.size(); ++i) { if(inv.getSlot(slots[i]) == inv.getSelectedEnchantItem()) { slot = slots[i]; break; } } cast.cast(itemPtr, slot); } } } void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) { // An initial position of projectile can be outside shooter's collision box, so any object between shooter and launch position will be ignored. // To avoid this issue, we should check for impact immediately before launch the projectile. // So we cast a 1-yard-length ray from shooter to launch position and check if there are collisions in this area. // TODO: as a better solutuon we should handle projectiles during physics update, not during world update. const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f; // Early out if the launch position is underwater bool underwater = isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); mRendering->emitWaterRipple(worldPos); return; } // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); // Check for impact, if yes, handle hit, if not, launch projectile MWPhysics::RayCastingResult result = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); if (result.mHit) MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); else mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) { mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, slot); } void World::updateProjectilesCasters() { mProjectileManager->updateCasters(); } void World::applyLoopingParticles(const MWWorld::Ptr& ptr) const { const MWWorld::Class &cls = ptr.getClass(); if (cls.isActor()) { std::set playing; for(const auto& params : cls.getCreatureStats(ptr).getActiveSpells()) { for(const auto& effect : params.getEffects()) { if(playing.insert(effect.mEffectId).second) { const auto magicEffect = getStore().get().find(effect.mEffectId); if(magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) MWMechanics::playEffects(ptr, *magicEffect, false); } } } } } const std::vector& World::getContentFiles() const { return mContentFiles; } void World::breakInvisibility(const Ptr &actor) { actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(actor, ESM::MagicEffect::Invisibility); // Normally updated once per frame, but here it is kinda important to do it right away. MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); } bool World::useTorches() const { // If we are in exterior, check the weather manager. // In interiors there are no precipitations and sun, so check the ambient // Looks like pseudo-exteriors considered as interiors in this case MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); if (cell->isExterior()) { float hour = getTimeStamp().getHour(); return mWeatherManager->useTorches(hour); } else { uint32_t ambient = cell->getCell()->mAmbi.mAmbient; int ambientTotal = (ambient & 0xff) + ((ambient>>8) & 0xff) + ((ambient>>16) & 0xff); return !(cell->getCell()->mData.mFlags & ESM::Cell::NoSleep) && ambientTotal <= 201; } } bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) return false; // Search for a 'nearest' exterior, counting each cell between the starting // cell and the exterior as a distance of 1. Will fail for isolated interiors. std::set< std::string >checkedCells; std::set< std::string >currentCells; std::set< std::string >nextCells; nextCells.insert( cell->getCell()->mName ); while ( !nextCells.empty() ) { currentCells = nextCells; nextCells.clear(); for (const std::string ¤tCell : currentCells) { MWWorld::CellStore *next = getInterior(currentCell); if ( !next ) continue; // Check if any door in the cell leads to an exterior directly for (const MWWorld::LiveCellRef& ref : next->getReadOnlyDoors().mList) { if (!ref.mRef.getTeleport()) continue; if (ref.mRef.getDestCell().empty()) { ESM::Position pos = ref.mRef.getDoorDest(); result = pos.asVec3(); return true; } else { std::string dest = ref.mRef.getDestCell(); if ( !checkedCells.count(dest) && !currentCells.count(dest) ) nextCells.insert(dest); } } checkedCells.insert(currentCell); } } // No luck :( return false; } MWWorld::ConstPtr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) { if ( ptr.getCell()->isExterior() ) { return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id); } // Search for a 'nearest' marker, counting each cell between the starting // cell and the exterior as a distance of 1. If an exterior is found, jump // to the nearest exterior marker, without further interior searching. std::set< std::string >checkedCells; std::set< std::string >currentCells; std::set< std::string >nextCells; MWWorld::ConstPtr closestMarker; nextCells.insert( ptr.getCell()->getCell()->mName ); while ( !nextCells.empty() ) { currentCells = nextCells; nextCells.clear(); for (const std::string &cell : currentCells) { MWWorld::CellStore *next = getInterior(cell); checkedCells.insert(cell); if ( !next ) continue; closestMarker = next->searchConst( id ); if ( !closestMarker.isEmpty() ) { return closestMarker; } // Check if any door in the cell leads to an exterior directly for (const MWWorld::LiveCellRef& ref : next->getReadOnlyDoors().mList) { if (!ref.mRef.getTeleport()) continue; if (ref.mRef.getDestCell().empty()) { osg::Vec3f worldPos = ref.mRef.getDoorDest().asVec3(); return getClosestMarkerFromExteriorPosition(worldPos, id); } else { std::string dest = ref.mRef.getDestCell(); if ( !checkedCells.count(dest) && !currentCells.count(dest) ) nextCells.insert(dest); } } } } return MWWorld::Ptr(); } MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) { MWWorld::ConstPtr closestMarker; float closestDistance = std::numeric_limits::max(); std::vector markers; mCells.getExteriorPtrs(id, markers); for (const Ptr& marker : markers) { osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3(); float distance = (worldPos - markerPos).length2(); if (distance < closestDistance) { closestDistance = distance; closestMarker = marker; } } return closestMarker; } void World::rest(double hours) { mCells.rest(hours); } void World::rechargeItems(double duration, bool activeOnly) { MWWorld::Ptr player = getPlayerPtr(); player.getClass().getInventoryStore(player).rechargeItems(duration); if (activeOnly) { for (auto &cell : mWorldScene->getActiveCells()) { cell->recharge(duration); } } else mCells.recharge(duration); } void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) { MWWorld::ConstPtr closestMarker = getClosestMarker( ptr, id ); if ( closestMarker.isEmpty() ) { Log(Debug::Warning) << "Failed to teleport: no closest marker found"; return; } std::string cellName; if ( !closestMarker.mCell->isExterior() ) cellName = closestMarker.mCell->getCell()->mName; MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); action.execute(ptr); } void World::updateWeather(float duration, bool paused) { bool isExterior = isCellExterior() || isCellQuasiExterior(); if (mPlayer->wasTeleported()) { mPlayer->setTeleported(false); const std::string playerRegion = Misc::StringUtils::lowerCase(getPlayerPtr().getCell()->getCell()->mRegion); mWeatherManager->playerTeleported(playerRegion, isExterior); } const TimeStamp time = getTimeStamp(); mWeatherManager->update(duration, paused, time, isExterior); } struct AddDetectedReferenceVisitor { std::vector& mOut; Ptr mDetector; float mSquaredDist; World::DetectionType mType; const MWWorld::ESMStore& mStore; bool operator() (const MWWorld::Ptr& ptr) { if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist) return true; if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted()) return true; // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers) bool isContainer = ptr.getClass().getType() == ESM::Container::sRecordId; if (mType != World::Detect_Creature && (ptr.getClass().isActor() || isContainer)) { // but ignore containers without resolved content if (isContainer && ptr.getRefData().getCustomData() == nullptr) { for(const auto& containerItem : ptr.get()->mBase->mInventory.mList) { if(containerItem.mCount) { try { ManualRef ref(mStore, containerItem.mItem, containerItem.mCount); if(needToAdd(ref.getPtr(), mDetector)) { mOut.push_back(ptr); return true; } } catch (const std::exception&) { // Ignore invalid item id } } } return true; } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); { for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (needToAdd(*it, mDetector)) { mOut.push_back(ptr); return true; } } } } if (needToAdd(ptr, mDetector)) mOut.push_back(ptr); return true; } bool needToAdd (const MWWorld::Ptr& ptr, const MWWorld::Ptr& detector) { if (mType == World::Detect_Creature) { // If in werewolf form, this detects only NPCs, otherwise only creatures if (detector.getClass().isNpc() && detector.getClass().getNpcStats(detector).isWerewolf()) { if (ptr.getClass().getType() != ESM::NPC::sRecordId) return false; } else if (ptr.getClass().getType() != ESM::Creature::sRecordId) return false; if (ptr.getClass().getCreatureStats(ptr).isDead()) return false; } if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr)) return false; if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty()) return false; return true; } }; void World::listDetectedReferences(const Ptr &ptr, std::vector &out, DetectionType type) { const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float dist=0; if (type == World::Detect_Creature) dist = effects.get(ESM::MagicEffect::DetectAnimal).getMagnitude(); else if (type == World::Detect_Key) dist = effects.get(ESM::MagicEffect::DetectKey).getMagnitude(); else if (type == World::Detect_Enchantment) dist = effects.get(ESM::MagicEffect::DetectEnchantment).getMagnitude(); if (!dist) return; dist = feetToGameUnits(dist); AddDetectedReferenceVisitor visitor {out, ptr, dist * dist, type, mStore}; for (CellStore* cellStore : mWorldScene->getActiveCells()) { cellStore->forEach(visitor); } } float World::feetToGameUnits(float feet) { // Original engine rounds size upward static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); return feet * unitsPerFoot; } float World::getActivationDistancePlusTelekinesis() { float telekinesisRangeBonus = mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() .get(ESM::MagicEffect::Telekinesis).getMagnitude(); telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; return activationDistance; } MWWorld::Ptr World::getPlayerPtr() { return mPlayer->getPlayer(); } MWWorld::ConstPtr World::getPlayerConstPtr() const { return mPlayer->getConstPlayer(); } void World::updateDialogueGlobals() { MWWorld::Ptr player = getPlayerPtr(); int bounty = player.getClass().getNpcStats(player).getBounty(); int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId); static float fCrimeGoldDiscountMult = mStore.get().find("fCrimeGoldDiscountMult")->mValue.getFloat(); static float fCrimeGoldTurnInMult = mStore.get().find("fCrimeGoldTurnInMult")->mValue.getFloat(); int discount = static_cast(bounty * fCrimeGoldDiscountMult); int turnIn = static_cast(bounty * fCrimeGoldTurnInMult); if (bounty > 0) { discount = std::max(1, discount); turnIn = std::max(1, turnIn); } mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0); mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0); mGlobalVariables["crimegolddiscount"].setInteger(discount); mGlobalVariables["crimegoldturnin"].setInteger(turnIn); mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0); } void World::confiscateStolenItems(const Ptr &ptr) { MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); if ( prisonMarker.isEmpty() ) { Log(Debug::Warning) << "Failed to confiscate items: no closest prison marker found."; return; } std::string prisonName = prisonMarker.getCellRef().getDestCell(); if ( prisonName.empty() ) { Log(Debug::Warning) << "Failed to confiscate items: prison marker not linked to prison interior"; return; } MWWorld::CellStore *prison = getInterior( prisonName ); if ( !prison ) { Log(Debug::Warning) << "Failed to confiscate items: failed to load cell " << prisonName; return; } MWWorld::Ptr closestChest = prison->search( "stolen_goods" ); if (!closestChest.isEmpty()) //Found a close chest { MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest); } else Log(Debug::Warning) << "Failed to confiscate items: no stolen_goods container found"; } void World::goToJail() { const MWWorld::Ptr player = getPlayerPtr(); if (!mGoToJail) { // Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first) mGoToJail = true; mPlayerInJail = true; int bounty = player.getClass().getNpcStats(player).getBounty(); player.getClass().getNpcStats(player).setBounty(0); mPlayer->recordCrimeId(); confiscateStolenItems(player); static int iDaysinPrisonMod = mStore.get().find("iDaysinPrisonMod")->mValue.getInteger(); mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod); return; } else { if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player)) { mPlayer->setAttackingOrSpell(false); } mPlayer->setDrawState(MWMechanics::DrawState::Nothing); mGoToJail = false; MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); MWBase::Environment::get().getWindowManager()->goToJail(mDaysInPrison); } } bool World::isPlayerInJail() const { return mPlayerInJail; } void World::setPlayerTraveling(bool traveling) { mPlayerTraveling = traveling; } bool World::isPlayerTraveling() const { return mPlayerTraveling; } float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const { return mRendering->getTerrainHeightAt(worldPos); } osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const { if (!object.getClass().isActor()) return mRendering->getHalfExtents(object); // Handle actors separately because of bodyparts if (rendering) return mPhysics->getRenderingHalfExtents(object); else return mPhysics->getHalfExtents(object); } std::string World::exportSceneGraph(const Ptr &ptr) { std::string file = mUserDataPath + "/openmw.osgt"; if (!ptr.isEmpty()) { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); } mRendering->exportSceneGraph(ptr, file, "Ascii"); return file; } void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = mStore.get().find(creatureList); static int iNumberCreatures = mStore.get().find("iNumberCreatures")->mValue.getInteger(); int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures, mPrng); // [1, iNumberCreatures] for (int i=0; igetVFS()); mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, const bool fromProjectile, int slot) { std::map > toApply; int index = -1; for (const ESM::ENAMstruct& effectInfo : effects.mList) { ++index; const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mEffectID); if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor if (fromProjectile && effectInfo.mArea <= 0) continue; // Don't play explosion for projectiles with 0-area effects if (!fromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore)) continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from the projectile enchantment // Spawn the explosion orb effect const ESM::Static* areaStatic; if (!effect->mArea.empty()) areaStatic = mStore.get().find (effect->mArea); else areaStatic = mStore.get().find ("VFX_DefaultArea"); std::string texture = effect->mParticle; if (effectInfo.mArea <= 0) { if (effectInfo.mRange == ESM::RT_Target) mRendering->spawnEffect( Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, mResourceSystem->getVFS()), texture, origin, 1.0f); continue; } else mRendering->spawnEffect( Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, mResourceSystem->getVFS()), texture, origin, static_cast(effectInfo.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mAreaSound.empty()) sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f); else sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f); } // Get the actors in range of the effect std::vector objects; MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(static_cast(effectInfo.mArea)), objects); for (const Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. if (affected.getClass().isActor() && !isActorCollisionEnabled(affected)) continue; auto& list = toApply[affected]; while (list.size() < static_cast(index)) { // Insert dummy effects to preserve indices auto& dummy = list.emplace_back(effectInfo); dummy.mRange = ESM::RT_Self; assert(dummy.mRange != rangeType); } list.push_back(effectInfo); } } // Now apply the appropriate effects to each actor in range for (auto& applyPair : toApply) { MWWorld::Ptr source = caster; // Vanilla-compatible behaviour of never applying the spell to the caster // (could be changed by mods later) if (applyPair.first == caster) continue; if (applyPair.first == ignore) continue; if (source.isEmpty()) source = applyPair.first; MWMechanics::CastSpell cast(source, applyPair.first); cast.mHitPosition = origin; cast.mId = id; cast.mSourceName = sourceName; cast.mSlot = slot; ESM::EffectList effectsToApply; effectsToApply.mList = applyPair.second; cast.inflict(applyPair.first, caster, effectsToApply, rangeType, true); } } void World::activate(const Ptr &object, const Ptr &actor) { breakInvisibility(actor); if (object.getRefData().activate()) { MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); std::unique_ptr action = object.getClass().activate(object, actor); action->execute (actor); } } struct ResetActorsVisitor { World& mWorld; bool operator() (const Ptr& ptr) { if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile()) { if (ptr.getCell()->movedHere(ptr)) return true; const ESM::Position& origPos = ptr.getCellRef().getPosition(); mWorld.moveObject(ptr, origPos.asVec3()); mWorld.rotateObject(ptr, origPos.asRotationVec3()); ptr.getClass().adjustPosition(ptr, true); } return true; } }; void World::resetActors() { for (CellStore* cellstore : mWorldScene->getActiveCells()) { ResetActorsVisitor visitor {*this}; cellstore->forEach(visitor); } } bool World::isWalkingOnWater(const ConstPtr &actor) const { const MWPhysics::Actor* physicActor = mPhysics->getActor(actor); if (physicActor && physicActor->isWalkingOnWater()) return true; return false; } osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target, bool isRangedCombat) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); float heightRatio = isRangedCombat ? 2.f * Constants::TorsoHeight : 1.f; weaponPos.z() += mPhysics->getHalfExtents(actor).z() * heightRatio; osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target) { osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); weaponPos.z() += halfExtents.z(); return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); } void preload(MWWorld::Scene* scene, const ESMStore& store, const std::string& obj) { if (obj.empty()) return; try { MWWorld::ManualRef ref(store, obj); std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); if (!model.empty()) scene->preload(model, ref.getPtr().getClass().useAnim()); } catch(std::exception&) { } } void World::preloadEffects(const ESM::EffectList *effectList) { for (const ESM::ENAMstruct& effectInfo : effectList->mList) { const ESM::MagicEffect *effect = mStore.get().find(effectInfo.mEffectID); if (MWMechanics::isSummoningEffect(effectInfo.mEffectID)) { preload(mWorldScene.get(), mStore, "VFX_Summon_Start"); preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID)); } preload(mWorldScene.get(), mStore, effect->mCasting); preload(mWorldScene.get(), mStore, effect->mHit); if (effectInfo.mArea > 0) preload(mWorldScene.get(), mStore, effect->mArea); if (effectInfo.mRange == ESM::RT_Target) preload(mWorldScene.get(), mStore, effect->mBolt); } } DetourNavigator::Navigator* World::getNavigator() const { return mNavigator.get(); } void World::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const { mRendering->updateActorPath(actor, path, agentBounds, start, end); } void World::removeActorPath(const MWWorld::ConstPtr& actor) const { mRendering->removeActorPath(actor); } void World::setNavMeshNumberToRender(const std::size_t value) { mRendering->setNavMeshNumber(value); } DetourNavigator::AgentBounds World::getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const { const MWPhysics::Actor* physicsActor = mPhysics->getActor(actor); if (physicsActor == nullptr || !actor.isInCell() || actor.getCell()->isExterior()) return DetourNavigator::AgentBounds {mDefaultActorCollisionShapeType, mDefaultHalfExtents}; else return DetourNavigator::AgentBounds {physicsActor->getCollisionShapeType(), physicsActor->getHalfExtents()}; } bool World::hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const { const auto object = mPhysics->getObject(door); if (!object) return false; btVector3 aabbMin; btVector3 aabbMax; object->getShapeInstance()->mCollisionShape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto toLocal = object->getTransform().inverse(); const auto localFrom = toLocal(Misc::Convert::toBullet(position)); const auto localTo = toLocal(Misc::Convert::toBullet(destination)); btScalar hitDistance = 1; btVector3 hitNormal; return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal); } bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const Misc::Span& ignore, std::vector* occupyingActors) const { return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors); } void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mNavigator->reportStats(frameNumber, stats); mPhysics->reportStats(frameNumber, stats); } void World::updateSkyDate() { ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp(); mRendering->skySetDate(currentDate.mDay, currentDate.mMonth); } std::vector World::getAll(const std::string& id) { return mCells.getAll(id); } Misc::Rng::Generator& World::getPrng() { return mPrng; } MWRender::PostProcessor* World::getPostProcessor() { return mRendering->getPostProcessor(); } void World::setActorActive(const MWWorld::Ptr& ptr, bool value) { if (MWPhysics::Actor* const actor = mPhysics->getActor(ptr)) actor->setActive(value); } } openmw-openmw-0.48.0/apps/openmw/mwworld/worldimp.hpp000066400000000000000000001040641445372753700227520ustar00rootroot00000000000000#ifndef GAME_MWWORLD_WORLDIMP_H #define GAME_MWWORLD_WORLDIMP_H #include #include #include #include #include #include "../mwbase/world.hpp" #include "ptr.hpp" #include "scene.hpp" #include "esmstore.hpp" #include "cells.hpp" #include "localscripts.hpp" #include "timestamp.hpp" #include "globals.hpp" #include "contentloader.hpp" #include "groundcoverstore.hpp" namespace osg { class Group; class Stats; } namespace osgViewer { class Viewer; } namespace Resource { class ResourceSystem; } namespace SceneUtil { class WorkQueue; class UnrefQueue; } namespace ESM { struct Position; } namespace Files { class Collections; } namespace MWRender { class SkyManager; class Animation; class Camera; class PostProcessor; } namespace ToUTF8 { class Utf8Encoder; } namespace MWPhysics { class Object; } namespace MWWorld { class DateTimeManager; class WeatherManager; class Player; class ProjectileManager; /// \brief The game world and its visual representation class World final: public MWBase::World { private: Resource::ResourceSystem* mResourceSystem; ESM::ReadersCache mReaders; MWWorld::ESMStore mStore; GroundcoverStore mGroundcoverStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; Misc::Rng::Generator mPrng; Cells mCells; std::string mCurrentWorldSpace; std::unique_ptr mPlayer; std::unique_ptr mPhysics; std::unique_ptr mNavigator; std::unique_ptr mRendering; std::unique_ptr mWorldScene; std::unique_ptr mWeatherManager; std::unique_ptr mCurrentDate; std::unique_ptr mProjectileManager; bool mSky; bool mGodMode; bool mScriptsEnabled; bool mDiscardMovements; std::vector mContentFiles; std::string mUserDataPath; osg::Vec3f mDefaultHalfExtents; DetourNavigator::CollisionShapeType mDefaultActorCollisionShapeType; bool mShouldUpdateNavigator; int mActivationDistanceOverride; std::string mStartCell; float mSwimHeightScale; float mDistanceToFacedObject; bool mTeleportEnabled; bool mLevitationEnabled; bool mGoToJail; int mDaysInPrison; bool mPlayerTraveling; bool mPlayerInJail; float mSpellPreloadTimer; std::map mDoorStates; ///< only holds doors that are currently moving. 1 = opening, 2 = closing uint32_t mRandomSeed{}; float mSimulationTimeScale = 1.0; // not implemented World (const World&); World& operator= (const World&); void updateWeather(float duration, bool paused = false); Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); void preloadSpells(); MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); void PCDropped (const Ptr& item); bool rotateDoor(const Ptr door, DoorState state, float duration); void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. void doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); ///< Run physics simulation and modify \a world accordingly. void updateNavigator(); void updateNavigatorObject(const MWPhysics::Object& object); void ensureNeededRecords(); void fillGlobalVariables(); void updateSkyDate(); void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener); void loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); MWWorld::ConstPtr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); MWWorld::ConstPtr getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ); public: // FIXME void addContainerScripts(const Ptr& reference, CellStore* cell) override; void removeContainerScripts(const Ptr& reference) override; World ( osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); virtual ~World(); void setRandomSeed(uint32_t seed) override; void startNewGame (bool bypass) override; ///< \param bypass Bypass regular game start. void clear() override; int countSavedGameRecords() const override; int countSavedGameCells() const override; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; void readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) override; CellStore *getExterior (int x, int y) override; CellStore *getInterior (const std::string& name) override; CellStore *getCell (const ESM::CellId& id) override; bool isCellActive(CellStore* cell) const override; void testExteriorCells() override; void testInteriorCells() override; //switch to POV before showing player's death animation void useDeathCamera() override; void setWaterHeight(const float height) override; void rotateWorldObject (const MWWorld::Ptr& ptr, const osg::Quat& rotate) override; bool toggleWater() override; bool toggleWorld() override; bool toggleBorders() override; void adjustSky(); Player& getPlayer() override; MWWorld::Ptr getPlayerPtr() override; MWWorld::ConstPtr getPlayerConstPtr() const override; const MWWorld::ESMStore& getStore() const override; LocalScripts& getLocalScripts() override; bool hasCellChanged() const override; ///< Has the set of active cells changed, since the last frame? bool isCellExterior() const override; bool isCellQuasiExterior() const override; osg::Vec2f getNorthVector (const CellStore* cell) override; ///< get north vector for given interior cell void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) override; ///< get a list of teleport door markers for a given cell, to be displayed on the local map void setGlobalInt(std::string_view name, int value) override; ///< Set value independently from real type. void setGlobalFloat(std::string_view name, float value) override; ///< Set value independently from real type. int getGlobalInt(std::string_view name) const override; ///< Get value independently from real type. float getGlobalFloat(std::string_view name) const override; ///< Get value independently from real type. char getGlobalVariableType(std::string_view name) const override; ///< Return ' ', if there is no global variable with this name. std::string getCellName (const MWWorld::CellStore *cell = nullptr) const override; ///< Return name of the cell. /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. std::string getCellName(const ESM::Cell* cell) const override; void removeRefScript (MWWorld::RefData *ref) override; //< Remove the script attached to ref from mLocalScripts Ptr getPtr (std::string_view name, bool activeOnly) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. Ptr searchPtr (std::string_view name, bool activeOnly, bool searchInContainers = false) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do not search inactive cells. Ptr searchPtrViaActorId (int actorId) override; ///< Search is limited to the active cells. Ptr searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum) override; MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) override; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. void adjustPosition (const Ptr& ptr, bool force) override; ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying void fixPosition () override; ///< Attempt to fix position so that the player is not stuck inside the geometry. void enable (const Ptr& ptr) override; void disable (const Ptr& ptr) override; void advanceTime (double hours, bool incremental = false) override; ///< Advance in-game time. std::string getMonthName (int month = -1) const override; ///< Return name of month (-1: current month) TimeStamp getTimeStamp() const override; ///< Return current in-game time and number of day since new game start. ESM::EpochTimeStamp getEpochTimeStamp() const override; ///< Return current in-game date and time. bool toggleSky() override; ///< \return Resulting mode void changeWeather (const std::string& region, const unsigned int id) override; int getCurrentWeather() const override; int getNextWeather() const override; float getWeatherTransition() const override; unsigned int getNightDayMode() const override; int getMasserPhase() const override; int getSecundaPhase() const override; void setMoonColour (bool red) override; void modRegion(const std::string ®ionid, const std::vector &chances) override; float getTimeScaleFactor() const override; float getSimulationTimeScale() const override { return mSimulationTimeScale; } void setSimulationTimeScale(float scale) override; void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) override; ///< Move to interior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) override; ///< Move to exterior cell. ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) override; ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes const ESM::Cell *getExterior (const std::string& cellName) const override; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. void markCellAsUnchanged() override; MWWorld::Ptr getFacedObject() override; ///< Return pointer to the object the player is looking at, if it is within activation range float getDistanceToFacedObject() override; /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node as a basis. std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) override; /// @note No-op for items in containers. Use ContainerStore::removeItem instead. void deleteObject (const Ptr& ptr) override; void undeleteObject (const Ptr& ptr) override; MWWorld::Ptr moveObject (const Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) override; ///< @return an updated Ptr MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale, bool force = false) override; /// World rotates object, uses radians /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This /// could be considered a bug, but is needed for MW compatibility. /// \param adjust indicates rotation should be set or adjusted void rotateObject (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override; MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) override; ///< Place an object. Makes a copy of the Ptr. MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) override; ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). float getMaxActivationDistance() const override; void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const override; ///< Convert cell numbers to position. void queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. void updateAnimatedCollisionShape(const Ptr &ptr) override; const MWPhysics::RayCastingInterface* getRayCasting() const override; bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) override; ///< cast a Ray and return true if there is an object in the ray path. bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, bool ignorePlayer, bool ignoreActors) override; void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. ///< \return Resulting mode bool toggleRenderMode (MWRender::RenderMode mode) override; ///< Toggle a render mode. ///< \return Resulting mode const ESM::Potion *createRecord (const ESM::Potion& record) override; ///< Create a new record (of type potion) in the ESM store. /// \return pointer to created record const ESM::Spell *createRecord (const ESM::Spell& record) override; ///< Create a new record (of type spell) in the ESM store. /// \return pointer to created record const ESM::Class *createRecord (const ESM::Class& record) override; ///< Create a new record (of type class) in the ESM store. /// \return pointer to created record const ESM::Cell *createRecord (const ESM::Cell& record) override; ///< Create a new record (of type cell) in the ESM store. /// \return pointer to created record const ESM::NPC *createRecord(const ESM::NPC &record) override; ///< Create a new record (of type npc) in the ESM store. /// \return pointer to created record const ESM::Armor *createRecord (const ESM::Armor& record) override; ///< Create a new record (of type armor) in the ESM store. /// \return pointer to created record const ESM::Weapon *createRecord (const ESM::Weapon& record) override; ///< Create a new record (of type weapon) in the ESM store. /// \return pointer to created record const ESM::Clothing *createRecord (const ESM::Clothing& record) override; ///< Create a new record (of type clothing) in the ESM store. /// \return pointer to created record const ESM::Enchantment *createRecord (const ESM::Enchantment& record) override; ///< Create a new record (of type enchantment) in the ESM store. /// \return pointer to created record const ESM::Book *createRecord (const ESM::Book& record) override; ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record const ESM::Creature *createOverrideRecord (const ESM::Creature& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record const ESM::NPC *createOverrideRecord (const ESM::NPC& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record const ESM::Container *createOverrideRecord (const ESM::Container& record) override; ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record void update(float duration, bool paused); void updatePhysics(float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void updateWindowManager(); MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) override; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) override; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object /// @param number of objects to place bool canPlaceObject(float cursorX, float cursorY) override; ///< @return true if it is possible to place on object at specified cursor location void processChangedSettings(const Settings::CategorySettingVector& settings) override; bool isFlying(const MWWorld::Ptr &ptr) const override; bool isSlowFalling(const MWWorld::Ptr &ptr) const override; ///Is the head of the creature underwater? bool isSubmerged(const MWWorld::ConstPtr &object) const override; bool isSwimming(const MWWorld::ConstPtr &object) const override; bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const override; bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const override; bool isWading(const MWWorld::ConstPtr &object) const override; bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const override; bool isOnGround(const MWWorld::Ptr &ptr) const override; osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const override; void togglePOV(bool force = false) override; bool isFirstPerson() const override; bool isPreviewModeEnabled() const override; bool toggleVanityMode(bool enable) override; MWRender::Camera* getCamera() override; bool vanityRotateCamera(float * rot) override; void applyDeferredPreviewRotationToPlayer(float dt) override; void disableDeferredPreviewRotation() override; void saveLoaded() override; void setupPlayer() override; void renderPlayer() override; /// open or close a non-teleport door (depending on current state) void activateDoor(const MWWorld::Ptr& door) override; /// update movement state of a non-teleport door as specified /// @param state see MWClass::setDoorState /// @note throws an exception when invoked on a teleport door void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) override; void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) override; ///< get a list of actors standing on \a object bool getPlayerStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if the player is standing on \a object bool getActorStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is standing on \a object bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) override; ///< @return true if the player is colliding with \a object bool getActorCollidingWith (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is colliding with \a object void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) override; ///< Apply a health difference to any actors standing on \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) override; ///< Apply a health difference to any actors colliding with \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. float getWindSpeed() override; void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) override; ///< get all containers in active cells owned by this Npc void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) override; ///< get all items in active cells owned by this Npc bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) override; ///< get Line of Sight (morrowind stupid implementation) float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) override; void enableActorCollision(const MWWorld::Ptr& actor, bool enable) override; RestPermitted canRest() const override; ///< check if the player is allowed to rest void rest(double hours) override; void rechargeItems(double duration, bool activeOnly); /// \todo Probably shouldn't be here MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const override; void reattachPlayerCamera() override; /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise bool findExteriorPosition(const std::string &name, ESM::Position &pos) override; /// Find position in interior cell near door entrance /// \return false if interior with given name not exists, true otherwise bool findInteriorPosition(const std::string &name, ESM::Position &pos) override; /// Enables or disables use of teleport spell effects (recall, intervention, etc). void enableTeleporting(bool enable) override; /// Returns true if teleport spell effects are allowed. bool isTeleportingEnabled() const override; /// Enables or disables use of levitation spell effect. void enableLevitation(bool enable) override; /// Returns true if levitation spell effect is allowed. bool isLevitationEnabled() const override; bool getGodModeState() const override; bool toggleGodMode() override; bool toggleScripts() override; bool getScriptsEnabled() const override; /** * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. * @param actor * @return Success or the failure condition. */ MWWorld::SpellCastState startSpellCast (const MWWorld::Ptr& actor) override; /** * @brief Cast the actual spell, should be called mid-animation * @param actor */ void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override; void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) override; void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void updateProjectilesCasters() override; void applyLoopingParticles(const MWWorld::Ptr& ptr) const override; const std::vector& getContentFiles() const override; void breakInvisibility (const MWWorld::Ptr& actor) override; // Allow NPCs to use torches? bool useTorches() const override; bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case void teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) override; /// List all references (filtered by \a type) detected by \a ptr. The range /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. /// @note This also works for references in containers. void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, DetectionType type) override; /// Update the value of some globals according to the world state, which may be used by dialogue entries. /// This should be called when initiating a dialogue. void updateDialogueGlobals() override; /// Moves all stolen items from \a ptr to the closest evidence chest. void confiscateStolenItems(const MWWorld::Ptr& ptr) override; void goToJail () override; /// Spawn a random creature from a levelled list next to the player void spawnRandomCreature(const std::string& creatureList) override; /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, const bool fromProjectile=false, int slot = 0) override; void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; /// @see MWWorld::WeatherManager::isInStorm bool isInStorm() const override; /// @see MWWorld::WeatherManager::getStormDirection osg::Vec3f getStormDirection() const override; /// Resets all actors in the current active cells to their original location within that cell. void resetActors() override; bool isWalkingOnWater (const MWWorld::ConstPtr& actor) const override; /// Return a vector aiming the actor's weapon towards a target. /// @note The length of the vector is the distance between actor and target. osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) override; /// Return the distance between actor's weapon and target's collision box. float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; bool isPlayerInJail() const override; void setPlayerTraveling(bool traveling) override; bool isPlayerTraveling() const override; /// Return terrain height at \a worldPos position. float getTerrainHeightAt(const osg::Vec3f& worldPos) const override; /// Return physical or rendering half extents of the given actor. osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const override; /// Export scene graph to a file and return the filename. /// \param ptr object to export scene graph for (if empty, export entire scene graph) std::string exportSceneGraph(const MWWorld::Ptr& ptr) override; /// Preload VFX associated with this effect list void preloadEffects(const ESM::EffectList* effectList) override; DetourNavigator::Navigator* getNavigator() const override; void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const override; void removeActorPath(const MWWorld::ConstPtr& actor) const override; void setNavMeshNumberToRender(const std::size_t value) override; DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const override; bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const Misc::Span& ignore, std::vector* occupyingActors) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; std::vector getAll(const std::string& id) override; Misc::Rng::Generator& getPrng() override; MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } MWRender::PostProcessor* getPostProcessor() override; void setActorActive(const MWWorld::Ptr& ptr, bool value) override; }; } #endif openmw-openmw-0.48.0/apps/openmw/options.cpp000066400000000000000000000125671445372753700211160ustar00rootroot00000000000000#include "options.hpp" #include #include #include namespace { namespace bpo = boost::program_options; typedef std::vector StringsVector; } namespace OpenMW { bpo::options_description makeOptionsDescription() { bpo::options_description desc("Syntax: openmw \nAllowed options"); Files::ConfigurationManager::addCommonOptions(desc); desc.add_options() ("help", "print help message") ("version", "print version information and quit") ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), "set resources directory") ("start", bpo::value()->default_value(""), "set initial cell") ("content", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") ("groundcover", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") ("script-all", bpo::value()->implicit_value(true) ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") ("script-all-dialogue", bpo::value()->implicit_value(true) ->default_value(false), "compile all dialogue scripts at startup") ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") ("script-run", bpo::value()->default_value(""), "select a file containing a list of console commands that is executed on startup") ("script-warn", bpo::value()->implicit_value (1) ->default_value (1), "handling of warnings when compiling scripts\n" "\t0 - ignore warning\n" "\t1 - show warning but consider script as correctly compiled anyway\n" "\t2 - treat warnings as errors") ("script-blacklist", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") ("load-savegame", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") ("skip-menu", bpo::value()->implicit_value(true) ->default_value(false), "skip main menu on game startup") ("new-game", bpo::value()->implicit_value(true) ->default_value(false), "run new game sequence (ignored if skip-menu=0)") ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") ("encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") ->multitoken()->composing(), "fallback values") ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") ("export-fonts", bpo::value()->implicit_value(true) ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") ("random-seed", bpo::value () ->default_value(Misc::Rng::generateDefaultSeed()), "seed value for random number generator") ; return desc; } } openmw-openmw-0.48.0/apps/openmw/options.hpp000066400000000000000000000003361445372753700211120ustar00rootroot00000000000000#ifndef APPS_OPENMW_OPTIONS_H #define APPS_OPENMW_OPTIONS_H #include namespace OpenMW { boost::program_options::options_description makeOptionsDescription(); } #endif openmw-openmw-0.48.0/apps/openmw_test_suite/000077500000000000000000000000001445372753700211545ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/CMakeLists.txt000066400000000000000000000066721445372753700237270ustar00rootroot00000000000000if (OPENMW_USE_SYSTEM_GOOGLETEST) find_package(GTest 1.10 REQUIRED) find_package(GMock 1.10 REQUIRED) endif() include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES testing_util.hpp ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp ../openmw/mwworld/timestamp.cpp mwworld/test_store.cpp mwworld/testduration.cpp mwworld/testtimestamp.cpp mwdialogue/test_keywordsearch.cpp mwscript/test_scripts.cpp esm/test_fixed_string.cpp esm/variant.cpp lua/test_lua.cpp lua/test_scriptscontainer.cpp lua/test_utilpackage.cpp lua/test_serialization.cpp lua/test_configuration.cpp lua/test_l10n.cpp lua/test_storage.cpp lua/test_async.cpp lua/test_ui_content.cpp misc/test_stringops.cpp misc/test_endianness.cpp misc/test_resourcehelpers.cpp misc/progressreporter.cpp misc/compression.cpp nifloader/testbulletnifloader.cpp detournavigator/navigator.cpp detournavigator/settingsutils.cpp detournavigator/recastmeshbuilder.cpp detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp detournavigator/navmeshdb.cpp detournavigator/serialization.cpp detournavigator/asyncnavmeshupdater.cpp serialization/binaryreader.cpp serialization/binarywriter.cpp serialization/sizeaccumulator.cpp serialization/integration.cpp settings/parser.cpp settings/shadermanager.cpp shader/parsedefines.cpp shader/parsefors.cpp shader/parselinks.cpp shader/shadermanager.cpp ../openmw/options.cpp openmw/options.cpp sqlite3/db.cpp sqlite3/request.cpp sqlite3/statement.cpp sqlite3/transaction.cpp esmloader/load.cpp esmloader/esmdata.cpp esmloader/record.cpp files/hash.cpp toutf8/toutf8.cpp esm4/includes.cpp fx/lexer.cpp fx/technique.cpp esm3/readerscache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) target_link_libraries(openmw_test_suite GTest::GTest GMock::GMock components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) endif() if (BUILD_WITH_CODE_COVERAGE) add_definitions(--coverage) target_link_libraries(openmw_test_suite gcov) endif() file(DOWNLOAD https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6 ) target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data" OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(openmw_test_suite PRIVATE ) endif() openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/000077500000000000000000000000001445372753700243715ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp000066400000000000000000000373351445372753700311740ustar00rootroot00000000000000#include "settings.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; using namespace DetourNavigator::Tests; void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager, const osg::Vec2i cellPosition = osg::Vec2i(0, 0)) { const int cellSize = 8192; recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); } void addObject(const btBoxShape& shape, TileCachedRecastMeshManager& recastMeshManager) { const ObjectId id(&shape); osg::ref_ptr bulletShape(new Resource::BulletShape); bulletShape->mFileName = "test.nif"; bulletShape->mFileHash = "test_hash"; ObjectTransform objectTransform; std::fill(std::begin(objectTransform.mPosition.pos), std::end(objectTransform.mPosition.pos), 0.1f); std::fill(std::begin(objectTransform.mPosition.rot), std::end(objectTransform.mPosition.rot), 0.2f); objectTransform.mScale = 3.14f; const CollisionShape collisionShape( osg::ref_ptr(new Resource::BulletShapeInstance(bulletShape)), shape, objectTransform ); recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {}); } struct DetourNavigatorAsyncNavMeshUpdaterTest : Test { Settings mSettings = makeSettings(); TileCachedRecastMeshManager mRecastMeshManager {mSettings.mRecast}; OffMeshConnectionsManager mOffMeshConnectionsManager {mSettings.mRecast}; const AgentBounds mAgentBounds {CollisionShapeType::Aabb, {29, 29, 66}}; const TilePosition mPlayerTile {0, 0}; const std::string mWorldspace = "sys::default"; const btBoxShape mBox {btVector3(100, 100, 20)}; Loading::Listener mListener; }; TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate) { AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr}; updater.wait(mListener, WaitConditionType::allJobsDone); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate) { AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); updater.wait(mListener, WaitConditionType::requiredTilesPresent); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mHitCount, 0); } updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mHitCount, 1); } } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTiles {{TilePosition {0, 0}, ChangeType::update}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mHitCount, 0); } updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mHitCount, 0); } } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const TilePosition tilePosition {0, 0}; const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); ShapeId nextShapeId {1}; const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, mAgentBounds, *recastMesh, objects)); ASSERT_TRUE(tile.has_value()); EXPECT_EQ(tile->mTileId, 1); EXPECT_EQ(tile->mVersion, navMeshFormatVersion); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_tiles) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); mSettings.mWriteToNavMeshDb = false; AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const TilePosition tilePosition {0, 0}; const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); ShapeId nextShapeId {1}; const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, mAgentBounds, *recastMesh, objects)); ASSERT_FALSE(tile.has_value()); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_shapes) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); mSettings.mWriteToNavMeshDb = false; AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const TilePosition tilePosition {0, 0}; const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); const auto objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); }); EXPECT_FALSE(objects.has_value()); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); mSettings.mMaxNavMeshTilesCacheSize = 0; AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique(":memory:", std::numeric_limits::max())); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mHitCount, 0); ASSERT_TRUE(stats.mDb.has_value()); ASSERT_EQ(stats.mDb->mGetTileCount, 1); ASSERT_EQ(stats.mDbGetTileHits, 0); } updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); { const auto stats = updater.getStats(); EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mHitCount, 0); ASSERT_TRUE(stats.mDb.has_value()); EXPECT_EQ(stats.mDb->mGetTileCount, 2); EXPECT_EQ(stats.mDbGetTileHits, 1); } } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}}; updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd); updater.wait(mListener, WaitConditionType::allJobsDone); ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); const std::map changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}}; const TilePosition playerTile(100, 100); updater.post(mAgentBounds, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove); updater.wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); } TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_stop_writing_to_db_when_size_limit_is_reached) { mRecastMeshManager.setWorldspace(mWorldspace); for (int x = -1; x <= 1; ++x) for (int y = -1; y <= 1; ++y) addHeightFieldPlane(mRecastMeshManager, osg::Vec2i(x, y)); addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:", 4097); NavMeshDb* const dbPtr = db.get(); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); std::map changedTiles; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) changedTiles.emplace(TilePosition {x, y}, ChangeType::add); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); updater.stop(); const std::set present { TilePosition(-2, 0), TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(-1, 1), TilePosition(0, -2), TilePosition(0, -1), TilePosition(0, 0), TilePosition(0, 1), TilePosition(0, 2), TilePosition(1, -1), TilePosition(1, 0), }; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) { const TilePosition tilePosition(x, y); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); const std::optional> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); }); if (!objects.has_value()) continue; EXPECT_EQ(dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, mAgentBounds, *recastMesh, *objects)).has_value(), present.find(tilePosition) != present.end()) << tilePosition.x() << " " << tilePosition.y() << " present=" << (present.find(tilePosition) != present.end()); } } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/generate.hpp000066400000000000000000000027711445372753700267030ustar00rootroot00000000000000#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H #define OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H #include #include #include #include namespace DetourNavigator { namespace Tests { template inline auto generateValue(T& value, Random& random) -> std::enable_if_t= 2> { using Distribution = std::conditional_t< std::is_floating_point_v, std::uniform_real_distribution, std::uniform_int_distribution >; Distribution distribution(std::numeric_limits::min(), std::numeric_limits::max()); value = distribution(random); } template inline auto generateValue(T& value, Random& random) -> std::enable_if_t { unsigned short v; generateValue(v, random); value = static_cast(v % 256); } template inline void generateValue(unsigned char& value, Random& random) { unsigned short v; generateValue(v, random); value = static_cast(v % 256); } template inline void generateRange(I begin, I end, Random& random) { std::for_each(begin, end, [&] (auto& v) { generateValue(v, random); }); } } } #endif openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/gettilespositions.cpp000066400000000000000000000067231445372753700306750ustar00rootroot00000000000000#include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; struct CollectTilesPositions { std::vector& mTilesPositions; void operator ()(const TilePosition& value) { mTilesPositions.push_back(value); } }; struct DetourNavigatorGetTilesPositionsTest : Test { RecastSettings mSettings; std::vector mTilesPositions; CollectTilesPositions mCollect {mTilesPositions}; DetourNavigatorGetTilesPositionsTest() { mSettings.mBorderSize = 0; mSettings.mCellSize = 0.5; mSettings.mRecastScaleFactor = 1; mSettings.mTileSize = 64; } }; TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) { getTilesPositions(makeTilesPositionsRange(osg::Vec2f(2, 2), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) { getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) { getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { getTilesPositions(makeTilesPositionsRange(osg::Vec2f(-31, -31), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0) )); } TEST_F(DetourNavigatorGetTilesPositionsTest, border_size_should_extend_tile_bounds) { mSettings.mBorderSize = 1; getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31.5, 31.5), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(-1, 1), TilePosition(0, -1), TilePosition(0, 0), TilePosition(0, 1), TilePosition(1, -1), TilePosition(1, 0), TilePosition(1, 1) )); } TEST_F(DetourNavigatorGetTilesPositionsTest, should_apply_recast_scale_factor) { mSettings.mRecastScaleFactor = 0.5; getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/navigator.cpp000066400000000000000000001741541445372753700271030ustar00rootroot00000000000000#include "operators.hpp" #include "settings.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MATCHER_P3(Vec3fEq, x, y, z, "") { return std::abs(arg.x() - x) < 1e-3 && std::abs(arg.y() - y) < 1e-3 && std::abs(arg.z() - z) < 1e-3; } namespace { using namespace testing; using namespace DetourNavigator; using namespace DetourNavigator::Tests; struct DetourNavigatorNavigatorTest : Test { Settings mSettings = makeSettings(); std::unique_ptr mNavigator; const osg::Vec3f mPlayerPosition; const std::string mWorldspace; const AgentBounds mAgentBounds {CollisionShapeType::Aabb, {29, 29, 66}}; osg::Vec3f mStart; osg::Vec3f mEnd; std::deque mPath; std::back_insert_iterator> mOut; float mStepSize; AreaCosts mAreaCosts; Loading::Listener mListener; const osg::Vec2i mCellPosition {0, 0}; const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const float mEndTolerance = 0; const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; const ObjectTransform mObjectTransform {ESM::Position {{256, 256, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorNavigatorTest() : mPlayerPosition(256, 256, 0) , mWorldspace("sys::default") , mStart(52, 460, 1) , mEnd(460, 52, 1) , mOut(mPath) , mStepSize(28.333332061767578125f) { mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); } }; template std::unique_ptr makeSquareHeightfieldTerrainShape(const std::array& values, btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false) { const int width = static_cast(std::sqrt(size)); const btScalar min = *std::min_element(values.begin(), values.end()); const btScalar max = *std::max_element(values.begin(), values.end()); const btScalar greater = std::max(std::abs(min), std::abs(max)); return std::make_unique(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges); } template HeightfieldSurface makeSquareHeightfieldSurface(const std::array& values) { const auto [min, max] = std::minmax_element(values.begin(), values.end()); const float greater = std::max(std::abs(*min), std::abs(*max)); HeightfieldSurface surface; surface.mHeights = values.data(); surface.mMinHeight = -greater; surface.mMaxHeight = greater; surface.mSize = static_cast(std::sqrt(size)); return surface; } template osg::ref_ptr makeBulletShapeInstance(std::unique_ptr&& shape) { osg::ref_ptr bulletShape(new Resource::BulletShape); bulletShape->mCollisionShape.reset(std::move(shape).release()); return new Resource::BulletShapeInstance(bulletShape); } template class CollisionShapeInstance { public: CollisionShapeInstance(std::unique_ptr&& shape) : mInstance(makeBulletShapeInstance(std::move(shape))) {} T& shape() { return static_cast(*mInstance->mCollisionShape); } const osg::ref_ptr& instance() const { return mInstance; } private: osg::ref_ptr mInstance; }; btVector3 getHeightfieldShift(const osg::Vec2i& cellPosition, int cellSize, float minHeight, float maxHeight) { return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.x(), cellSize, minHeight, maxHeight); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentBounds); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) { mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds); mNavigator->removeAgent(mAgentBounds); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) { constexpr std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, add_object_should_change_navmesh) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), Vec3fEq(185.2996063232421875, 207.54925537109375, -19.575878143310546875), Vec3fEq(206.6449737548828125, 188.917236328125, -20.3511219024658203125), Vec3fEq(227.9903564453125, 170.28521728515625, -22.9776935577392578125), Vec3fEq(253.4362640380859375, 157.8239593505859375, -31.1692962646484375), Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), Vec3fEq(304.328094482421875, 132.9014739990234375, -22.219127655029296875), Vec3fEq(329.774017333984375, 120.44022369384765625, -13.2701435089111328125), Vec3fEq(355.219940185546875, 107.97898101806640625, -5.330339908599853515625), Vec3fEq(380.665863037109375, 95.51773834228515625, -3.5501649379730224609375), Vec3fEq(406.111785888671875, 83.05649566650390625, -1.76998889446258544921875), Vec3fEq(431.557708740234375, 70.5952606201171875, 0.01018683053553104400634765625), Vec3fEq(457.003662109375, 58.134021759033203125, 1.79036080837249755859375), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), Vec3fEq(185.2996063232421875, 207.54925537109375, -19.575878143310546875), Vec3fEq(206.6449737548828125, 188.917236328125, -20.3511219024658203125), Vec3fEq(227.9903564453125, 170.28521728515625, -22.9776935577392578125), Vec3fEq(253.4362640380859375, 157.8239593505859375, -31.1692962646484375), Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), Vec3fEq(304.328094482421875, 132.9014739990234375, -22.219127655029296875), Vec3fEq(329.774017333984375, 120.44022369384765625, -13.2701435089111328125), Vec3fEq(355.219940185546875, 107.97898101806640625, -5.330339908599853515625), Vec3fEq(380.665863037109375, 95.51773834228515625, -3.5501649379730224609375), Vec3fEq(406.111785888671875, 83.05649566650390625, -1.76998889446258544921875), Vec3fEq(431.557708740234375, 70.5952606201171875, 0.01018683053553104400634765625), Vec3fEq(457.003662109375, 58.134021759033203125, 1.79036080837249755859375), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_objects_should_use_higher) { const std::array heightfieldData1 {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; CollisionShapeInstance heightfield1(makeSquareHeightfieldTerrainShape(heightfieldData1)); heightfield1.shape().setLocalScaling(btVector3(128, 128, 1)); const std::array heightfieldData2 {{ -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; CollisionShapeInstance heightfield2(makeSquareHeightfieldTerrainShape(heightfieldData2)); heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentBounds); mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.903246104717254638671875), Vec3fEq(96.73604583740234375, 419.93060302734375, -3.8064472675323486328125), Vec3fEq(116.770751953125, 399.89593505859375, -6.709649562835693359375), Vec3fEq(136.8054351806640625, 379.861236572265625, -9.33333873748779296875), Vec3fEq(156.840118408203125, 359.826568603515625, -9.33333873748779296875), Vec3fEq(176.8748016357421875, 339.7918701171875, -9.33333873748779296875), Vec3fEq(196.90948486328125, 319.757171630859375, -9.33333873748779296875), Vec3fEq(216.944183349609375, 299.722503662109375, -9.33333873748779296875), Vec3fEq(236.9788665771484375, 279.68780517578125, -9.33333873748779296875), Vec3fEq(257.0135498046875, 259.65313720703125, -9.33333873748779296875), Vec3fEq(277.048248291015625, 239.618438720703125, -9.33333873748779296875), Vec3fEq(297.082916259765625, 219.583740234375, -9.33333873748779296875), Vec3fEq(317.11761474609375, 199.549041748046875, -9.33333873748779296875), Vec3fEq(337.15228271484375, 179.5143585205078125, -9.33333873748779296875), Vec3fEq(357.186981201171875, 159.47967529296875, -9.33333873748779296875), Vec3fEq(377.221649169921875, 139.4449920654296875, -9.33333873748779296875), Vec3fEq(397.25634765625, 119.41030120849609375, -6.891522884368896484375), Vec3fEq(417.291046142578125, 99.3756103515625, -4.053897380828857421875), Vec3fEq(437.325714111328125, 79.340911865234375, -1.21627247333526611328125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.621352672576904296875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed) { const std::array heightfieldData1 {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(heightfieldData1); const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); const std::array heightfieldData2 {{ -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); mNavigator->addAgent(mAgentBounds); EXPECT_TRUE(mNavigator->addHeightfield(mCellPosition, cellSize1, surface1)); EXPECT_FALSE(mNavigator->addHeightfield(mCellPosition, cellSize2, surface2)); } TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) { osg::ref_ptr bulletShape(new Resource::BulletShape); std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; std::unique_ptr shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); shapePtr->setLocalScaling(btVector3(128, 128, 1)); bulletShape->mCollisionShape.reset(shapePtr.release()); std::array heightfieldDataAvoid {{ -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; std::unique_ptr shapeAvoidPtr = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); shapeAvoidPtr->setLocalScaling(btVector3(128, 128, 1)); bulletShape->mAvoidCollisionShape.reset(shapeAvoidPtr.release()); osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentBounds); mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance, mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(69.013885498046875, 434.49853515625, -0.74384129047393798828125), Vec3fEq(81.36110687255859375, 408.997100830078125, -3.4876689910888671875), Vec3fEq(93.7083282470703125, 383.495635986328125, -6.2314929962158203125), Vec3fEq(106.0555419921875, 357.99420166015625, -8.97531890869140625), Vec3fEq(118.40276336669921875, 332.49273681640625, -11.7191448211669921875), Vec3fEq(130.7499847412109375, 306.991302490234375, -14.4629726409912109375), Vec3fEq(143.0972137451171875, 281.4898681640625, -17.206798553466796875), Vec3fEq(155.4444122314453125, 255.9884033203125, -19.9506206512451171875), Vec3fEq(167.7916412353515625, 230.4869537353515625, -19.91887664794921875), Vec3fEq(189.053619384765625, 211.75982666015625, -20.1138629913330078125), Vec3fEq(210.3155975341796875, 193.032684326171875, -20.3088512420654296875), Vec3fEq(231.577606201171875, 174.3055419921875, -20.503841400146484375), Vec3fEq(252.839599609375, 155.5784149169921875, -19.9803981781005859375), Vec3fEq(278.407989501953125, 143.3704071044921875, -17.2675113677978515625), Vec3fEq(303.976348876953125, 131.16241455078125, -14.55462360382080078125), Vec3fEq(329.54473876953125, 118.9544219970703125, -11.84173583984375), Vec3fEq(355.11309814453125, 106.74642181396484375, -9.12884807586669921875), Vec3fEq(380.681488037109375, 94.538421630859375, -6.4159603118896484375), Vec3fEq(406.249847412109375, 82.33042144775390625, -3.7030735015869140625), Vec3fEq(431.8182373046875, 70.1224365234375, -0.990187108516693115234375), Vec3fEq(457.38665771484375, 57.9144439697265625, 1.72269880771636962890625), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water_with_only_swim_flag) { std::array heightfieldData {{ -50, -50, -50, -50, 0, -50, -100, -150, -100, -50, -50, -150, -200, -150, -100, -50, -100, -150, -100, -100, 0, -50, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addWater(mCellPosition, cellSize, 300); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 256; mStart.z() = 300; mEnd.x() = 256; mEnd.z() = 300; EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(256, 460, 185.33331298828125), Vec3fEq(256, 431.666656494140625, 185.33331298828125), Vec3fEq(256, 403.33331298828125, 185.33331298828125), Vec3fEq(256, 375, 185.33331298828125), Vec3fEq(256, 346.666656494140625, 185.33331298828125), Vec3fEq(256, 318.33331298828125, 185.33331298828125), Vec3fEq(256, 290, 185.33331298828125), Vec3fEq(256, 261.666656494140625, 185.33331298828125), Vec3fEq(256, 233.3333282470703125, 185.33331298828125), Vec3fEq(256, 205, 185.33331298828125), Vec3fEq(256, 176.6666717529296875, 185.33331298828125), Vec3fEq(256, 148.3333282470703125, 185.33331298828125), Vec3fEq(256, 120, 185.33331298828125), Vec3fEq(256, 91.6666717529296875, 185.33331298828125), Vec3fEq(255.999969482421875, 63.33333587646484375, 185.33331298828125), Vec3fEq(255.999969482421875, 56.66666412353515625, 185.33331298828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_swim_and_walk_flags) { std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, 0, -100, -100, -100, -100, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -150, -200, -150, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 256; mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(256, 460, -129.4098663330078125), Vec3fEq(256, 431.666656494140625, -129.6970062255859375), Vec3fEq(256, 403.33331298828125, -129.6970062255859375), Vec3fEq(256, 375, -129.4439239501953125), Vec3fEq(256, 346.666656494140625, -129.02587890625), Vec3fEq(256, 318.33331298828125, -128.6078338623046875), Vec3fEq(256, 290, -128.1021728515625), Vec3fEq(256, 261.666656494140625, -126.46875), Vec3fEq(256, 233.3333282470703125, -119.4891357421875), Vec3fEq(256, 205, -110.62021636962890625), Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), Vec3fEq(256, 120, -75.29378509521484375), Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size_and_swim_and_walk_flags) { std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, 0, -100, -100, -100, -100, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -150, -200, -150, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addWater(mCellPosition, std::numeric_limits::max(), -25); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 256; mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(256, 460, -129.4098663330078125), Vec3fEq(256, 431.666656494140625, -129.6970062255859375), Vec3fEq(256, 403.33331298828125, -129.6970062255859375), Vec3fEq(256, 375, -129.4439239501953125), Vec3fEq(256, 346.666656494140625, -129.02587890625), Vec3fEq(256, 318.33331298828125, -128.6078338623046875), Vec3fEq(256, 290, -128.1021728515625), Vec3fEq(256, 261.666656494140625, -126.46875), Vec3fEq(256, 233.3333282470703125, -119.4891357421875), Vec3fEq(256, 205, -110.62021636962890625), Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), Vec3fEq(256, 120, -75.29378509521484375), Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_ground_when_ground_cross_water_with_only_walk_flag) { std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, 0, -100, -100, -100, -100, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -150, -200, -150, -100, 0, 0, -100, -150, -150, -150, -100, 0, 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 256; mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(256, 460, -129.4098663330078125), Vec3fEq(256, 431.666656494140625, -129.6970062255859375), Vec3fEq(256, 403.33331298828125, -129.6970062255859375), Vec3fEq(256, 375, -129.4439239501953125), Vec3fEq(256, 346.666656494140625, -129.02587890625), Vec3fEq(256, 318.33331298828125, -128.6078338623046875), Vec3fEq(256, 290, -128.1021728515625), Vec3fEq(256, 261.666656494140625, -126.46875), Vec3fEq(256, 233.3333282470703125, -119.4891357421875), Vec3fEq(256, 205, -110.62021636962890625), Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), Vec3fEq(256, 120, -75.29378509521484375), Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_object_remove_and_update_then_find_path_should_return_path) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; CollisionShapeInstance heightfield(makeSquareHeightfieldTerrainShape(heightfieldData)); heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentBounds); mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->removeObject(ObjectId(&heightfield.shape())); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->removeHeightfield(mCellPosition); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_then_find_random_point_around_circle_should_return_position) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, -25, 0, -25, -1000, -1000, -100, -100, 0, -25, -1000, -1000, -100, -100, 0, -25, -100, -100, -100, -100, 0, -25, -100, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); Misc::Rng::init(42); const auto result = findRandomPointAroundCircle(*mNavigator, mAgentBounds, mStart, 100.0, Flag_walk, []() { return Misc::Rng::rollClosedProbability(); }); ASSERT_THAT(result, Optional(Vec3fEq(70.35845947265625, 335.592041015625, -2.6667339801788330078125))) << (result ? *result : osg::Vec3f()); const auto distance = (*result - mStart).length(); EXPECT_FLOAT_EQ(distance, 125.80865478515625) << distance; } TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) { mSettings.mAsyncNavMeshUpdaterThreads = 2; mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); std::vector> boxes; std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10)); mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1)); mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), Vec3fEq(185.2996063232421875, 207.54925537109375, -20.3612518310546875), Vec3fEq(206.6449737548828125, 188.917236328125, -20.578319549560546875), Vec3fEq(227.9903564453125, 170.28521728515625, -26.291717529296875), Vec3fEq(253.4362640380859375, 157.8239593505859375, -34.784488677978515625), Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), Vec3fEq(304.328094482421875, 132.9014739990234375, -25.72176361083984375), Vec3fEq(329.774017333984375, 120.44022369384765625, -21.1904010772705078125), Vec3fEq(355.219940185546875, 107.97898101806640625, -16.6590404510498046875), Vec3fEq(380.665863037109375, 95.51773834228515625, -12.127681732177734375), Vec3fEq(406.111785888671875, 83.05649566650390625, -7.5963191986083984375), Vec3fEq(431.557708740234375, 70.5952606201171875, -3.0649592876434326171875), Vec3fEq(457.003662109375, 58.134021759033203125, 1.4664003849029541015625), Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change) { std::vector> shapes; std::generate_n(std::back_inserter(shapes), 100, [] { return std::make_unique(btVector3(64, 64, 64)); }); mNavigator->addAgent(mAgentBounds); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto start = std::chrono::steady_clock::now(); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto duration = std::chrono::steady_clock::now() - start; EXPECT_GT(duration, mSettings.mMinUpdateInterval) << std::chrono::duration_cast>(duration).count() << " ms"; } TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const osg::Vec3f start(57, 460, 1); const osg::Vec3f end(460, 57, 1); const auto result = raycast(*mNavigator, mAgentBounds, start, end, Flag_walk); ASSERT_THAT(result, Optional(Vec3fEq(end.x(), end.y(), 1.95257937908172607421875))) << (result ? *result : osg::Vec3f()); } TEST_F(DetourNavigatorNavigatorTest, update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(288, 288, 400); CollisionShapeInstance borderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const Version expectedVersion {1, 4}; const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); ASSERT_EQ(navMeshes.begin()->second->lockConst()->getVersion(), expectedVersion); for (int n = 0; n < 10; ++n) { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } ASSERT_EQ(navMeshes.size(), 1); ASSERT_EQ(navMeshes.begin()->second->lockConst()->getVersion(), expectedVersion); } TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield) { const HeightfieldPlane plane {100}; const int cellSize = mHeightfieldTileSize * 4; mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, plane); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, 101.99999237060546875), Vec3fEq(76.70135498046875, 439.965301513671875, 101.99999237060546875), Vec3fEq(96.73604583740234375, 419.93060302734375, 101.99999237060546875), Vec3fEq(116.770751953125, 399.89593505859375, 101.99999237060546875), Vec3fEq(136.8054351806640625, 379.861236572265625, 101.99999237060546875), Vec3fEq(156.840118408203125, 359.826568603515625, 101.99999237060546875), Vec3fEq(176.8748016357421875, 339.7918701171875, 101.99999237060546875), Vec3fEq(196.90948486328125, 319.757171630859375, 101.99999237060546875), Vec3fEq(216.944183349609375, 299.722503662109375, 101.99999237060546875), Vec3fEq(236.9788665771484375, 279.68780517578125, 101.99999237060546875), Vec3fEq(257.0135498046875, 259.65313720703125, 101.99999237060546875), Vec3fEq(277.048248291015625, 239.618438720703125, 101.99999237060546875), Vec3fEq(297.082916259765625, 219.583740234375, 101.99999237060546875), Vec3fEq(317.11761474609375, 199.549041748046875, 101.99999237060546875), Vec3fEq(337.15228271484375, 179.5143585205078125, 101.99999237060546875), Vec3fEq(357.186981201171875, 159.47967529296875, 101.99999237060546875), Vec3fEq(377.221649169921875, 139.4449920654296875, 101.99999237060546875), Vec3fEq(397.25634765625, 119.41030120849609375, 101.99999237060546875), Vec3fEq(417.291046142578125, 99.3756103515625, 101.99999237060546875), Vec3fEq(437.325714111328125, 79.340911865234375, 101.99999237060546875), Vec3fEq(457.360443115234375, 59.3062286376953125, 101.99999237060546875), Vec3fEq(460, 56.66666412353515625, 101.99999237060546875) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(200, 200, 1000))); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::PartialPath); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66664886474609375, 460, -2.5371043682098388671875), Vec3fEq(76.42063140869140625, 439.6884765625, -2.9134314060211181640625), Vec3fEq(96.17461395263671875, 419.376953125, -4.50826549530029296875), Vec3fEq(115.9285888671875, 399.0654296875, -6.1030979156494140625), Vec3fEq(135.6825714111328125, 378.753936767578125, -7.697928905487060546875), Vec3fEq(155.436553955078125, 358.442413330078125, -20.9574985504150390625), Vec3fEq(175.190521240234375, 338.130889892578125, -35.907512664794921875), Vec3fEq(194.9445037841796875, 317.8193359375, -50.85752105712890625), Vec3fEq(214.698486328125, 297.5078125, -65.807525634765625), Vec3fEq(222.0001068115234375, 290.000091552734375, -71.333465576171875) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) { const std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(100, 100, 1000))); mNavigator->addAgent(mAgentBounds); mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); const float endTolerance = 1000.0f; EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(56.66666412353515625, 460, -2.5371043682098388671875), Vec3fEq(71.5649566650390625, 435.899810791015625, -5.817593097686767578125), Vec3fEq(86.46324920654296875, 411.79962158203125, -9.66499996185302734375), Vec3fEq(101.36154937744140625, 387.699462890625, -13.512401580810546875), Vec3fEq(116.2598419189453125, 363.599273681640625, -17.359806060791015625), Vec3fEq(131.1581268310546875, 339.499114990234375, -21.2072086334228515625), Vec3fEq(146.056427001953125, 315.39892578125, -25.0546112060546875), Vec3fEq(160.9547271728515625, 291.298736572265625, -28.9020137786865234375), Vec3fEq(175.8530120849609375, 267.198577880859375, -32.749416351318359375), Vec3fEq(190.751312255859375, 243.098388671875, -33.819454193115234375), Vec3fEq(205.64959716796875, 218.9982147216796875, -31.020172119140625), Vec3fEq(220.5478973388671875, 194.898040771484375, -26.844608306884765625), Vec3fEq(235.446197509765625, 170.7978668212890625, -26.785541534423828125), Vec3fEq(250.3444671630859375, 146.6976776123046875, -26.7264766693115234375), Vec3fEq(265.242767333984375, 122.59751129150390625, -20.59339141845703125), Vec3fEq(280.141021728515625, 98.4973297119140625, -14.040531158447265625), Vec3fEq(295.039306640625, 74.39715576171875, -7.48766994476318359375), Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, only_one_water_per_cell_is_allowed) { const int cellSize1 = 100; const float level1 = 1; const int cellSize2 = 200; const float level2 = 2; mNavigator->addAgent(mAgentBounds); EXPECT_TRUE(mNavigator->addWater(mCellPosition, cellSize1, level1)); EXPECT_FALSE(mNavigator->addWater(mCellPosition, cellSize2, level2)); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/navmeshdb.cpp000066400000000000000000000164671445372753700270620ustar00rootroot00000000000000#include "generate.hpp" #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; using namespace DetourNavigator::Tests; struct Tile { std::string mWorldspace; TilePosition mTilePosition; std::vector mInput; std::vector mData; }; struct DetourNavigatorNavMeshDbTest : Test { NavMeshDb mDb {":memory:", std::numeric_limits::max()}; std::minstd_rand mRandom; std::vector generateData() { std::vector data(32); generateRange(data.begin(), data.end(), mRandom); return data; } Tile insertTile(TileId tileId, TileVersion version) { std::string worldspace = "sys::default"; const TilePosition tilePosition {3, 4}; std::vector input = generateData(); std::vector data = generateData(); EXPECT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); return {std::move(worldspace), tilePosition, std::move(input), std::move(data)}; } }; TEST_F(DetourNavigatorNavMeshDbTest, get_max_tile_id_for_empty_db_should_return_zero) { EXPECT_EQ(mDb.getMaxTileId(), TileId {0}); } TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_be_found_by_key) { const TileId tileId {146}; const TileVersion version {1}; const auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); const auto result = mDb.findTile(worldspace, tilePosition, input); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->mTileId, tileId); EXPECT_EQ(result->mVersion, version); } TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_change_max_tile_id) { insertTile(TileId {53}, TileVersion {1}); EXPECT_EQ(mDb.getMaxTileId(), TileId {53}); } TEST_F(DetourNavigatorNavMeshDbTest, updated_tile_should_change_data) { const TileId tileId {13}; const TileVersion version {1}; auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); generateRange(data.begin(), data.end(), mRandom); ASSERT_EQ(mDb.updateTile(tileId, version, data), 1); const auto row = mDb.getTileData(worldspace, tilePosition, input); ASSERT_TRUE(row.has_value()); EXPECT_EQ(row->mTileId, tileId); EXPECT_EQ(row->mVersion, version); ASSERT_FALSE(row->mData.empty()); EXPECT_EQ(row->mData, data); } TEST_F(DetourNavigatorNavMeshDbTest, on_inserted_duplicate_should_throw_exception) { const TileId tileId {53}; const TileVersion version {1}; const std::string worldspace = "sys::default"; const TilePosition tilePosition {3, 4}; const std::vector input = generateData(); const std::vector data = generateData(); ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); } TEST_F(DetourNavigatorNavMeshDbTest, inserted_duplicate_leaves_db_in_correct_state) { const TileId tileId {53}; const TileVersion version {1}; const std::string worldspace = "sys::default"; const TilePosition tilePosition {3, 4}; const std::vector input = generateData(); const std::vector data = generateData(); ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); EXPECT_NO_THROW(insertTile(TileId {54}, version)); } TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_at_should_remove_all_tiles_with_given_worldspace_and_position) { const TileVersion version {1}; const std::string worldspace = "sys::default"; const TilePosition tilePosition {3, 4}; const std::vector input1 = generateData(); const std::vector input2 = generateData(); const std::vector data = generateData(); ASSERT_EQ(mDb.insertTile(TileId {53}, worldspace, tilePosition, version, input1, data), 1); ASSERT_EQ(mDb.insertTile(TileId {54}, worldspace, tilePosition, version, input2, data), 1); ASSERT_EQ(mDb.deleteTilesAt(worldspace, tilePosition), 2); EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, input1).has_value()); EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, input2).has_value()); } TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_at_except_should_leave_tile_with_given_id) { const TileId leftTileId {53}; const TileId removedTileId {54}; const TileVersion version {1}; const std::string worldspace = "sys::default"; const TilePosition tilePosition {3, 4}; const std::vector leftInput = generateData(); const std::vector removedInput = generateData(); const std::vector data = generateData(); ASSERT_EQ(mDb.insertTile(leftTileId, worldspace, tilePosition, version, leftInput, data), 1); ASSERT_EQ(mDb.insertTile(removedTileId, worldspace, tilePosition, version, removedInput, data), 1); ASSERT_EQ(mDb.deleteTilesAtExcept(worldspace, tilePosition, leftTileId), 1); const auto left = mDb.findTile(worldspace, tilePosition, leftInput); ASSERT_TRUE(left.has_value()); EXPECT_EQ(left->mTileId, leftTileId); EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, removedInput).has_value()); } TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_outside_range_should_leave_tiles_inside_given_rectangle) { TileId tileId {1}; const TileVersion version {1}; const std::string worldspace = "sys::default"; const std::vector input = generateData(); const std::vector data = generateData(); for (int x = -2; x <= 2; ++x) { for (int y = -2; y <= 2; ++y) { ASSERT_EQ(mDb.insertTile(tileId, worldspace, TilePosition {x, y}, version, input, data), 1); ++tileId; } } const TilesPositionsRange range {TilePosition {-1, -1}, TilePosition {2, 2}}; ASSERT_EQ(mDb.deleteTilesOutsideRange(worldspace, range), 16); for (int x = -2; x <= 2; ++x) for (int y = -2; y <= 2; ++y) ASSERT_EQ(mDb.findTile(worldspace, TilePosition {x, y}, input).has_value(), -1 <= x && x <= 1 && -1 <= y && y <= 1) << "x=" << x << " y=" << y; } TEST_F(DetourNavigatorNavMeshDbTest, should_support_file_size_limit) { mDb = NavMeshDb(":memory:", 4096); const auto f = [&] { for (std::int64_t i = 1; i <= 100; ++i) insertTile(TileId {i}, TileVersion {1}); }; EXPECT_THROW(f(), std::runtime_error); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp000066400000000000000000000435221445372753700305710ustar00rootroot00000000000000#include "operators.hpp" #include "generate.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; using namespace DetourNavigator::Tests; template void generateRecastArray(T*& values, int size, Random& random) { values = static_cast(permRecastAlloc(size * sizeof(T))); generateRange(values, values + static_cast(size), random); } template void generate(rcPolyMesh& value, int size, Random& random) { value.nverts = size; value.maxpolys = size; value.nvp = size; value.npolys = size; rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr()); rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr()); generateValue(value.cs, random); generateValue(value.ch, random); generateValue(value.borderSize, random); generateValue(value.maxEdgeError, random); generateRecastArray(value.verts, getVertsLength(value), random); generateRecastArray(value.polys, getPolysLength(value), random); generateRecastArray(value.regs, getRegsLength(value), random); generateRecastArray(value.flags, getFlagsLength(value), random); generateRecastArray(value.areas, getAreasLength(value), random); } template void generate(rcPolyMeshDetail& value, int size, Random& random) { value.nmeshes = size; value.nverts = size; value.ntris = size; generateRecastArray(value.meshes, getMeshesLength(value), random); generateRecastArray(value.verts, getVertsLength(value), random); generateRecastArray(value.tris, getTrisLength(value), random); } template void generate(PreparedNavMeshData& value, int size, Random& random) { generateValue(value.mUserId, random); generateValue(value.mCellHeight, random); generateValue(value.mCellSize, random); generate(value.mPolyMesh, size, random); generate(value.mPolyMeshDetail, size, random); } std::unique_ptr makePeparedNavMeshData(int size) { std::minstd_rand random; auto result = std::make_unique(); generate(*result, size, random); return result; } std::unique_ptr clone(const PreparedNavMeshData& value) { return std::make_unique(value); } Mesh makeMesh() { std::vector indices {{0, 1, 2}}; std::vector vertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; std::vector areaTypes {1, AreaType_ground}; return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); } struct DetourNavigatorNavMeshTilesCacheTest : Test { const AgentBounds mAgentBounds {CollisionShapeType::Aabb, {1, 2, 3}}; const TilePosition mTilePosition {0, 0}; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; const Mesh mMesh {makeMesh()}; const std::vector mWater {}; const std::vector mHeightfields {}; const std::vector mFlatHeightfields {}; const std::vector mSources {}; const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields, mSources}; std::unique_ptr mPreparedNavMeshData {makePeparedNavMeshData(3)}; const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(CellWater); const std::size_t mPreparedNavMeshDataSize = sizeof(*mPreparedNavMeshData) + getSize(*mPreparedNavMeshData); }; TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) { const std::size_t maxSize = 0; NavMeshTilesCache cache(maxSize); EXPECT_FALSE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value) { const std::size_t maxSize = 0; NavMeshTilesCache cache(maxSize); EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData))); EXPECT_NE(mPreparedNavMeshData, nullptr); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) { const std::size_t maxSize = mRecastMeshSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const auto copy = clone(*mPreparedNavMeshData); ASSERT_EQ(*mPreparedNavMeshData, *copy); const auto result = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), *copy); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_return_cached_element) { const std::size_t maxSize = 2 * (mRecastMeshSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); auto copy = clone(*mPreparedNavMeshData); const auto sameCopy = clone(*mPreparedNavMeshData); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_EQ(mPreparedNavMeshData, nullptr); const auto result = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(copy)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), *sameCopy); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) { const std::size_t maxSize = mRecastMeshSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const auto copy = clone(*mPreparedNavMeshData); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); const auto result = cache.get(mAgentBounds, mTilePosition, mRecastMesh); ASSERT_TRUE(result); EXPECT_EQ(result.get(), *copy); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value) { const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const AgentBounds absentAgentBounds {CollisionShapeType::Aabb, {1, 1, 1}}; cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.get(absentAgentBounds, mTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value) { const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const TilePosition unexistentTilePosition {1, 1}; cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.get(mAgentBounds, unexistentTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value) { const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh unexistentRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.get(mAgentBounds, mTilePosition, unexistentRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) { const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto copy = clone(*anotherPreparedNavMeshData); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); const auto result = cache.set(mAgentBounds, mTilePosition, anotherRecastMesh, std::move(anotherPreparedNavMeshData)); ASSERT_TRUE(result); EXPECT_EQ(result.get(), *copy); EXPECT_FALSE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) { const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto value = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, anotherRecastMesh, std::move(anotherPreparedNavMeshData))); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) { const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); const auto copy = clone(*mPreparedNavMeshData); const std::vector leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh leastRecentlySetRecastMesh(mGeneration, mRevision, mMesh, leastRecentlySetWater, mHeightfields, mFlatHeightfields, mSources); auto leastRecentlySetData = makePeparedNavMeshData(3); const std::vector mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh mostRecentlySetRecastMesh(mGeneration, mRevision, mMesh, mostRecentlySetWater, mHeightfields, mFlatHeightfields, mSources); auto mostRecentlySetData = makePeparedNavMeshData(3); ASSERT_TRUE(cache.set(mAgentBounds, mTilePosition, leastRecentlySetRecastMesh, std::move(leastRecentlySetData))); ASSERT_TRUE(cache.set(mAgentBounds, mTilePosition, mostRecentlySetRecastMesh, std::move(mostRecentlySetData))); const auto result = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_EQ(result.get(), *copy); EXPECT_FALSE(cache.get(mAgentBounds, mTilePosition, leastRecentlySetRecastMesh)); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mostRecentlySetRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) { const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh leastRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, leastRecentlyUsedWater, mHeightfields, mFlatHeightfields, mSources); auto leastRecentlyUsedData = makePeparedNavMeshData(3); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); const std::vector mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh mostRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, mostRecentlyUsedWater, mHeightfields, mFlatHeightfields, mSources); auto mostRecentlyUsedData = makePeparedNavMeshData(3); const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData); cache.set(mAgentBounds, mTilePosition, leastRecentlyUsedRecastMesh, std::move(leastRecentlyUsedData)); cache.set(mAgentBounds, mTilePosition, mostRecentlyUsedRecastMesh, std::move(mostRecentlyUsedData)); { const auto value = cache.get(mAgentBounds, mTilePosition, leastRecentlyUsedRecastMesh); ASSERT_TRUE(value); ASSERT_EQ(value.get(), *leastRecentlyUsedCopy); } { const auto value = cache.get(mAgentBounds, mTilePosition, mostRecentlyUsedRecastMesh); ASSERT_TRUE(value); ASSERT_EQ(value.get(), *mostRecentlyUsedCopy); } const auto copy = clone(*mPreparedNavMeshData); const auto result = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_EQ(result.get(), *copy); EXPECT_FALSE(cache.get(mAgentBounds, mTilePosition, leastRecentlyUsedRecastMesh)); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mostRecentlyUsedRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size) { const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, tooLargeRecastMesh, std::move(tooLargeData))); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items) { const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); const std::vector anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, anotherWater, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const std::vector tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, tooLargeWater, mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); const auto value = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); ASSERT_TRUE(value); ASSERT_TRUE(cache.set(mAgentBounds, mTilePosition, anotherRecastMesh, std::move(anotherData))); EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, tooLargeRecastMesh, std::move(tooLargeData))); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, anotherRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) { const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const auto firstCopy = cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); ASSERT_TRUE(firstCopy); { const auto secondCopy = cache.get(mAgentBounds, mTilePosition, mRecastMesh); ASSERT_TRUE(secondCopy); } EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, anotherRecastMesh, std::move(anotherData))); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) { const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); cache.set(mAgentBounds, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); const auto firstCopy = cache.get(mAgentBounds, mTilePosition, mRecastMesh); ASSERT_TRUE(firstCopy); { const auto secondCopy = cache.get(mAgentBounds, mTilePosition, mRecastMesh); ASSERT_TRUE(secondCopy); } EXPECT_FALSE(cache.set(mAgentBounds, mTilePosition, anotherRecastMesh, std::move(anotherData))); EXPECT_TRUE(cache.get(mAgentBounds, mTilePosition, mRecastMesh)); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/operators.hpp000066400000000000000000000046001445372753700271200ustar00rootroot00000000000000#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H #define OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H #include #include #include #include #include #include namespace { template struct Wrapper { const T& mValue; }; template inline testing::Message& writeRange(testing::Message& message, const Range& range, std::size_t newLine) { message << "{"; std::size_t i = 0; for (const auto& v : range) { if (i++ % newLine == 0) message << "\n"; message << Wrapper::type> {v} << ", "; } return message << "\n}"; } } namespace testing { template <> inline testing::Message& Message::operator <<(const osg::Vec3f& value) { return (*this) << "Vec3fEq(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.z() << ')'; } template <> inline testing::Message& Message::operator <<(const Wrapper& value) { return (*this) << value.mValue; } template <> inline testing::Message& Message::operator <<(const Wrapper& value) { return (*this) << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue; } template <> inline testing::Message& Message::operator <<(const Wrapper& value) { return (*this) << value.mValue; } template <> inline testing::Message& Message::operator <<(const std::deque& value) { return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { return writeRange(*this, value, 3); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { return writeRange(*this, value, 3); } } #endif openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp000066400000000000000000000614441445372753700306130ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { static inline bool operator ==(const Water& lhs, const Water& rhs) { const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); }; return tie(lhs) == tie(rhs); } static inline bool operator ==(const CellWater& lhs, const CellWater& rhs) { const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); }; return tie(lhs) == tie(rhs); } static inline bool operator==(const Heightfield& lhs, const Heightfield& rhs) { return makeTuple(lhs) == makeTuple(rhs); } static inline bool operator==(const FlatHeightfield& lhs, const FlatHeightfield& rhs) { const auto tie = [] (const FlatHeightfield& v) { return std::tie(v.mCellPosition, v.mCellSize, v.mHeight); }; return tie(lhs) == tie(rhs); } } namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorRecastMeshBuilderTest : Test { TileBounds mBounds; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; const osg::ref_ptr mSource {nullptr}; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorRecastMeshBuilderTest() { mBounds.mMin = osg::Vec2f(-std::numeric_limits::max() * std::numeric_limits::epsilon(), -std::numeric_limits::max() * std::numeric_limits::epsilon()); mBounds.mMax = osg::Vec2f(std::numeric_limits::max() * std::numeric_limits::epsilon(), std::numeric_limits::max() * std::numeric_limits::epsilon()); } }; TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty) { RecastMeshBuilder builder(mBounds); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector()); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector()); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector()); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, -1, 1, 0, 1, -1, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_bhv_triangle_mesh_shape) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ 0, 0, 3, 0, 4, 3, 2, 0, 3, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_terrian_shape) { const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({0, 1, 2, 2, 1, 3})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles) { btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, -2, -1, -1, 2, -1, 1, -2, -1, 1, 2, 1, -1, -2, 1, -1, 2, 1, 1, -2, 1, 1, 2, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({ 0, 1, 5, 0, 2, 3, 0, 4, 6, 1, 3, 7, 2, 6, 7, 3, 1, 0, 4, 5, 7, 5, 4, 0, 6, 2, 0, 7, 3, 2, 7, 5, 1, 7, 6, 4, })) << recastMesh->getMesh().getIndices(); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector(12, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_compound_shape) { btTriangleMesh mesh1; mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape triangle1(&mesh1, true); btBoxShape box(btVector3(1, 1, 2)); btTriangleMesh mesh2; mesh2.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape triangle2(&mesh2, true); btCompoundShape shape; shape.addChildShape(btTransform::getIdentity(), &triangle1); shape.addChildShape(btTransform::getIdentity(), &box); shape.addChildShape(btTransform::getIdentity(), &triangle2); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, -2, -1, -1, 0, -1, -1, 2, -1, 1, -2, -1, 1, 0, -1, 1, 2, 1, -1, -2, 1, -1, 0, 1, -1, 2, 1, 1, -2, 1, 1, 0, 1, 1, 2, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({ 0, 2, 8, 0, 3, 5, 0, 6, 9, 2, 5, 11, 3, 9, 11, 5, 2, 0, 6, 8, 11, 7, 4, 1, 7, 4, 10, 8, 6, 0, 9, 3, 0, 11, 5, 3, 11, 8, 2, 11, 9, 6, })) << recastMesh->getMesh().getIndices(); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector(14, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape triangle(&mesh, true); btCompoundShape shape; shape.addChildShape(btTransform::getIdentity(), &triangle); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ 0, 0, 3, 0, 4, 3, 2, 0, 3, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape_with_transformed_bhv_triangle_shape) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape triangle(&mesh, true); btCompoundShape shape; shape.addChildShape(btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), &triangle); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ 1, 2, 12, 1, 10, 12, 3, 2, 12, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_bhv_triangle_shape_should_not_filter_by_bounds) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -3, -3, 0, -3, -2, 0, -2, -3, 0, -1, -1, 0, -1, 1, 0, 1, -1, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0, 5, 4, 3})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector(2, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_bhv_triangle_shape_should_filter_by_bounds) { mBounds.mMin = osg::Vec2f(-3, -3); mBounds.mMax = osg::Vec2f(-2, -2); btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -3, -3, 0, -3, -2, 0, -2, -3, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds) { mBounds.mMin = osg::Vec2f(-5, -5); mBounds.mMax = osg::Vec2f(5, -2); btTriangleMesh mesh; mesh.addTriangle(btVector3(0, -1, -1), btVector3(0, -1, -1), btVector3(0, 1, -1)); mesh.addTriangle(btVector3(0, -3, -3), btVector3(0, -3, -2), btVector3(0, -2, -3)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5f), std::vector({ 0, -4.24264049530029296875f, 4.44089209850062616169452667236328125e-16f, 0, -3.535533905029296875f, -0.707106769084930419921875f, 0, -3.535533905029296875f, 0.707106769084930419921875f, }))) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({1, 2, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_y_bhv_triangle_shape_should_filter_by_bounds) { mBounds.mMin = osg::Vec2f(-5, -5); mBounds.mMax = osg::Vec2f(-3, 5); btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, 0, -1), btVector3(-1, 0, 1), btVector3(1, 0, -1)); mesh.addTriangle(btVector3(-3, 0, -3), btVector3(-3, 0, -2), btVector3(-2, 0, -3)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5f), std::vector({ -4.24264049530029296875f, 0, 4.44089209850062616169452667236328125e-16f, -3.535533905029296875f, 0, -0.707106769084930419921875f, -3.535533905029296875f, 0, 0.707106769084930419921875f, }))) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({1, 2, 0})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_z_bhv_triangle_shape_should_filter_by_bounds) { mBounds.mMin = osg::Vec2f(-5, -5); mBounds.mMax = osg::Vec2f(-1, -1); btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape), btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5f), std::vector({ -1.41421353816986083984375f, -1.1102230246251565404236316680908203125e-16f, 0, 1.1102230246251565404236316680908203125e-16f, -1.41421353816986083984375f, 0, 1.41421353816986083984375f, 1.1102230246251565404236316680908203125e-16f, 0, }))) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 0, 1})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, flags_values_should_be_corresponding_to_added_objects) { btTriangleMesh mesh1; mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape1(&mesh1, true); btTriangleMesh mesh2; mesh2.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape2(&mesh2, true); RecastMeshBuilder builder(mBounds); builder.addObject( static_cast(shape1), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform ); builder.addObject( static_cast(shape2), btTransform::getIdentity(), AreaType_null, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -3, -3, 0, -3, -2, 0, -2, -3, 0, -1, -1, 0, -1, 1, 0, 1, -1, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0, 5, 4, 3})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_null, AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it) { RecastMeshBuilder builder(mBounds); builder.addWater(osg::Vec2i(1, 2), Water {1000, 300.0f}); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getWater(), std::vector({ CellWater {osg::Vec2i(1, 2), Water {1000, 300.0f}} })); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape_with_duplicated_vertices) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0, })) << recastMesh->getMesh().getVertices(); EXPECT_EQ(recastMesh->getMesh().getIndices(), std::vector({2, 1, 0, 2, 1, 3})); EXPECT_EQ(recastMesh->getMesh().getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_flat_heightfield_should_add_intersection) { const osg::Vec2i cellPosition(0, 0); const int cellSize = 1000; const float height = 10; mBounds.mMin = osg::Vec2f(100, 100); RecastMeshBuilder builder(mBounds); builder.addHeightfield(cellPosition, cellSize, height); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getFlatHeightfields(), std::vector({ FlatHeightfield {cellPosition, cellSize, height}, })); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_inside_tile) { constexpr std::size_t size = 3; constexpr std::array heights {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, }}; const osg::Vec2i cellPosition(0, 0); const int cellSize = 1000; const float minHeight = 0; const float maxHeight = 8; RecastMeshBuilder builder(mBounds); builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); Heightfield expected; expected.mCellPosition = cellPosition; expected.mCellSize = cellSize; expected.mLength = size; expected.mMinHeight = minHeight; expected.mMaxHeight = maxHeight; expected.mHeights = { 0, 1, 2, 3, 4, 5, 6, 7, 8, }; expected.mOriginalSize = 3; expected.mMinX = 0; expected.mMinY = 0; EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_to_shifted_cell_inside_tile) { constexpr std::size_t size = 3; constexpr std::array heights {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, }}; const osg::Vec2i cellPosition(1, 2); const int cellSize = 1000; const float minHeight = 0; const float maxHeight = 8; RecastMeshBuilder builder(maxCellTileBounds(cellPosition, cellSize)); builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); Heightfield expected; expected.mCellPosition = cellPosition; expected.mCellSize = cellSize; expected.mLength = size; expected.mMinHeight = minHeight; expected.mMaxHeight = maxHeight; expected.mHeights = { 0, 1, 2, 3, 4, 5, 6, 7, 8, }; expected.mOriginalSize = 3; expected.mMinX = 0; expected.mMinY = 0; EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_should_add_intersection) { constexpr std::size_t size = 3; constexpr std::array heights {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, }}; const osg::Vec2i cellPosition(0, 0); const int cellSize = 1000; const float minHeight = 0; const float maxHeight = 8; mBounds.mMin = osg::Vec2f(750, 750); RecastMeshBuilder builder(mBounds); builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); Heightfield expected; expected.mCellPosition = cellPosition; expected.mCellSize = cellSize; expected.mLength = 2; expected.mMinHeight = 0; expected.mMaxHeight = 8; expected.mHeights = { 4, 5, 7, 8, }; expected.mOriginalSize = 3; expected.mMinX = 1; expected.mMinY = 1; EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp000066400000000000000000000067351445372753700304350ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; const ObjectTransform mObjectTransform {ESM::Position {{1, 2, 3}, {1, 2, 3}}, 0.5f}; CollisionShape mBoxShape {nullptr, mBoxShapeImpl, mObjectTransform}; btCompoundShape mCompoundShapeImpl {true}; CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl, mObjectTransform}; btTransform mTransform {Misc::Convert::makeBulletTransform(mObjectTransform.mPosition)}; DetourNavigatorRecastMeshObjectTest() { mCompoundShapeImpl.addChildShape(mTransform, std::addressof(mBoxShapeImpl)); } }; TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) { const RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShapeImpl)); EXPECT_EQ(object.getTransform(), mTransform); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_same_transform_for_not_compound_shape_should_return_false) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_transform_should_return_true) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); EXPECT_TRUE(object.update(btTransform::getIdentity(), AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_flags_should_return_true) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); EXPECT_TRUE(object.update(mTransform, AreaType_null)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_not_changed_child_transform_should_return_false) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); object.update(mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_changed_local_scaling_should_return_true) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); mBoxShapeImpl.setLocalScaling(btVector3(2, 2, 2)); EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/settings.hpp000066400000000000000000000036311445372753700267450ustar00rootroot00000000000000#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H #include #include #include namespace DetourNavigator { namespace Tests { inline Settings makeSettings() { Settings result; result.mEnableWriteRecastMeshToFile = false; result.mEnableWriteNavMeshToFile = false; result.mEnableRecastMeshFileNameRevision = false; result.mEnableNavMeshFileNameRevision = false; result.mRecast.mBorderSize = 16; result.mRecast.mCellHeight = 0.2f; result.mRecast.mCellSize = 0.2f; result.mRecast.mDetailSampleDist = 6; result.mRecast.mDetailSampleMaxError = 1; result.mRecast.mMaxClimb = 34; result.mRecast.mMaxSimplificationError = 1.3f; result.mRecast.mMaxSlope = 49; result.mRecast.mRecastScaleFactor = 0.017647058823529415f; result.mRecast.mSwimHeightScale = 0.89999997615814208984375f; result.mRecast.mMaxEdgeLen = 12; result.mDetour.mMaxNavMeshQueryNodes = 2048; result.mRecast.mMaxVertsPerPoly = 6; result.mRecast.mRegionMergeArea = 400; result.mRecast.mRegionMinArea = 64; result.mRecast.mTileSize = 64; result.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); result.mAsyncNavMeshUpdaterThreads = 1; result.mMaxNavMeshTilesCacheSize = 1024 * 1024; result.mDetour.mMaxPolygonPathSize = 1024; result.mDetour.mMaxSmoothPathSize = 1024; result.mDetour.mMaxPolys = 4096; result.mMaxTilesNumber = 512; result.mMinUpdateInterval = std::chrono::milliseconds(50); result.mWriteToNavMeshDb = true; return result; } } } #endif openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/settingsutils.cpp000066400000000000000000000040701445372753700300170ustar00rootroot00000000000000#include "operators.hpp" #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorGetTilePositionTest : Test { RecastSettings mSettings; DetourNavigatorGetTilePositionTest() { mSettings.mCellSize = 0.5; mSettings.mTileSize = 64; } }; TEST_F(DetourNavigatorGetTilePositionTest, for_zero_coordinates_should_return_zero_tile_position) { EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(0, 0, 0)), TilePosition(0, 0)); } TEST_F(DetourNavigatorGetTilePositionTest, tile_size_should_be_multiplied_by_cell_size) { EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 0, 0)), TilePosition(1, 0)); } TEST_F(DetourNavigatorGetTilePositionTest, tile_position_calculates_by_floor) { EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(31, 0, 0)), TilePosition(0, 0)); } TEST_F(DetourNavigatorGetTilePositionTest, tile_position_depends_on_x_and_z_coordinates) { EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 64, 128)), TilePosition(1, 4)); } TEST_F(DetourNavigatorGetTilePositionTest, tile_position_works_for_negative_coordinates) { EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(-31, 0, -32)), TilePosition(-1, -1)); } struct DetourNavigatorMakeTileBoundsTest : Test { RecastSettings mSettings; DetourNavigatorMakeTileBoundsTest() { mSettings.mCellSize = 0.5; mSettings.mTileSize = 64; } }; TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_depend_on_tile_size_and_cell_size) { EXPECT_EQ(makeTileBounds(mSettings, TilePosition(0, 0)), (TileBounds {osg::Vec2f(0, 0), osg::Vec2f(32, 32)})); } TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_are_multiplied_by_tile_position) { EXPECT_EQ(makeTileBounds(mSettings, TilePosition(1, 2)), (TileBounds {osg::Vec2f(32, 64), osg::Vec2f(64, 96)})); } } openmw-openmw-0.48.0/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp000066400000000000000000000552611445372753700326050ustar00rootroot00000000000000#include "operators.hpp" #include #include #include #include #include namespace { using namespace testing; using namespace DetourNavigator; struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { RecastSettings mSettings; std::vector mAddedTiles; std::vector> mChangedTiles; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; const osg::ref_ptr mShape = new Resource::BulletShape; const osg::ref_ptr mInstance = new Resource::BulletShapeInstance(mShape); DetourNavigatorTileCachedRecastMeshManagerTest() { mSettings.mBorderSize = 16; mSettings.mCellSize = 0.2f; mSettings.mRecastScaleFactor = 0.017647058823529415f; mSettings.mTileSize = 64; } void onAddedTile(const TilePosition& tilePosition) { mAddedTiles.push_back(tilePosition); } void onChangedTile(const TilePosition& tilePosition, ChangeType changeType) { mChangedTiles.emplace_back(tilePosition, changeType); } }; TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) { TileCachedRecastMeshManager manager(mSettings); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) { const TileCachedRecastMeshManager manager(mSettings); EXPECT_EQ(manager.getRevision(), 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none) { TileCachedRecastMeshManager manager(mSettings); std::size_t calls = 0; manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; }); EXPECT_EQ(calls, 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_added_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); TileBounds bounds; bounds.mMin = osg::Vec2f(182, 182); bounds.mMax = osg::Vec2f(1000, 1000); manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onAddedTile(v); }); EXPECT_THAT(mAddedTiles, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); TileBounds bounds; bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMax = osg::Vec2f(1000, 1000); manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& ... v) { onChangedTile(v ...); })); EXPECT_THAT(mChangedTiles, ElementsAre( std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(-1, 0), ChangeType::add), std::pair(TilePosition(0, -1), ChangeType::update), std::pair(TilePosition(0, 0), ChangeType::update), std::pair(TilePosition(1, -1), ChangeType::remove), std::pair(TilePosition(1, 0), ChangeType::remove) )); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& ... v) { onChangedTile(v ...); })); EXPECT_THAT(mChangedTiles, IsEmpty()); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); TileBounds bounds; bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMax = osg::Vec2f(1000, 1000); manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) { TileCachedRecastMeshManager manager(mSettings); const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_existing_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeAddRevision = manager.getRevision(); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_moved_object_should_return_incremented_value) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_existing_object_should_return_incremented_value) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_absent_object_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&manager)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_return_true) { TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; EXPECT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) { TileCachedRecastMeshManager manager(mSettings); EXPECT_EQ(manager.removeWater(osg::Vec2i(0, 0)), std::nullopt); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_return_removed_water) { TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); const auto result = manager.removeWater(cellPosition); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->mCellSize, cellSize); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles) { TileCachedRecastMeshManager manager(mSettings); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); manager.setWorldspace("other"); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_return_changed_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); TileBounds bounds; bounds.mMin = osg::Vec2f(182, 0); bounds.mMax = osg::Vec2f(1000, 1000); manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMax = osg::Vec2f(0, -182); EXPECT_THAT(manager.setBounds(bounds), ElementsAre( std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(0, 0), ChangeType::remove) )); } } openmw-openmw-0.48.0/apps/openmw_test_suite/esm/000077500000000000000000000000001445372753700217405ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/esm/test_fixed_string.cpp000066400000000000000000000076361445372753700262040ustar00rootroot00000000000000#include #include "components/esm/esmcommon.hpp" #include "components/esm/defs.hpp" TEST(EsmFixedString, operator__eq_ne) { { SCOPED_TRACE("asdc == asdc"); constexpr ESM::NAME name("asdc"); char s[4] = {'a', 's', 'd', 'c'}; std::string ss(s, 4); EXPECT_TRUE(name == s); EXPECT_TRUE(name == ss.c_str()); EXPECT_TRUE(name == ss); } { SCOPED_TRACE("asdc == asdcx"); constexpr ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', 'x'}; std::string ss(s, 5); EXPECT_TRUE(name != s); EXPECT_TRUE(name != ss.c_str()); EXPECT_TRUE(name != ss); } { SCOPED_TRACE("asdc == asdc[NULL]"); const ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', '\0'}; std::string ss(s, 5); EXPECT_TRUE(name == s); EXPECT_TRUE(name == ss.c_str()); EXPECT_TRUE(name == ss); } } TEST(EsmFixedString, operator__eq_ne_const) { { SCOPED_TRACE("asdc == asdc (const)"); constexpr ESM::NAME name("asdc"); const char s[4] = { 'a', 's', 'd', 'c' }; std::string ss(s, 4); EXPECT_TRUE(name == s); EXPECT_TRUE(name == ss.c_str()); EXPECT_TRUE(name == ss); } { SCOPED_TRACE("asdc == asdcx (const)"); constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', 'x' }; std::string ss(s, 5); EXPECT_TRUE(name != s); EXPECT_TRUE(name != ss.c_str()); EXPECT_TRUE(name != ss); } { SCOPED_TRACE("asdc == asdc[NULL] (const)"); constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', '\0' }; std::string ss(s, 5); EXPECT_TRUE(name == s); EXPECT_TRUE(name == ss.c_str()); EXPECT_TRUE(name == ss); } } TEST(EsmFixedString, empty_strings) { { SCOPED_TRACE("4 bytes"); ESM::NAME empty = ESM::NAME(); EXPECT_TRUE(empty == ""); EXPECT_TRUE(empty == static_cast(0)); EXPECT_TRUE(empty != "some string"); EXPECT_TRUE(empty != static_cast(42)); } { SCOPED_TRACE("32 bytes"); ESM::NAME32 empty = ESM::NAME32(); EXPECT_TRUE(empty == ""); EXPECT_TRUE(empty != "some string"); } } TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4) { ESM::NAME value; value = static_cast(0xFFFFFFFFu); value.assign(std::string(1, 'a')); EXPECT_EQ(value, static_cast('a')) << value.toInt(); } TEST(EsmFixedString, assign_should_only_truncate_for_4) { ESM::NAME value; value.assign(std::string(5, 'a')); EXPECT_EQ(value, std::string(4, 'a')); } TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero) { ESM::FixedString<17> value; value.assign(std::string(20, 'a')); EXPECT_EQ(value, std::string(16, 'a')); } TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_32) { ESM::NAME32 value; value.assign(std::string(33, 'a')); EXPECT_EQ(value, std::string(31, 'a')); } TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_64) { ESM::NAME64 value; value.assign(std::string(65, 'a')); EXPECT_EQ(value, std::string(63, 'a')); } TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) { ESM::NAME value; value = static_cast(0xFEDCBA98u); EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); } TEST(EsmFixedString, construction_from_uint32_is_supported) { constexpr ESM::NAME value(0xFEDCBA98u); EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); } TEST(EsmFixedString, construction_from_RecNameInts_is_supported) { constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI); EXPECT_EQ(value, static_cast(ESM::RecNameInts::REC_ACTI)) << value.toInt(); } openmw-openmw-0.48.0/apps/openmw_test_suite/esm/variant.cpp000066400000000000000000000376761445372753700241330ustar00rootroot00000000000000#include #include #include #include #include #include #include namespace { using namespace testing; using namespace ESM; Variant makeVariant(VarType type) { Variant v; v.setType(type); return v; } Variant makeVariant(VarType type, int value) { Variant v; v.setType(type); v.setInteger(value); return v; } TEST(ESMVariantTest, move_constructed_should_have_data) { Variant a(int{42}); const Variant b(std::move(a)); ASSERT_EQ(b.getInteger(), 42); } TEST(ESMVariantTest, copy_constructed_is_equal_to_source) { const Variant a(int{42}); const Variant b(a); ASSERT_EQ(a, b); } TEST(ESMVariantTest, copy_constructed_does_not_share_data_with_source) { const Variant a(int{42}); Variant b(a); b.setInteger(13); ASSERT_EQ(a.getInteger(), 42); ASSERT_EQ(b.getInteger(), 13); } TEST(ESMVariantTest, move_assigned_should_have_data) { Variant b; { Variant a(int{42}); b = std::move(a); } ASSERT_EQ(b.getInteger(), 42); } TEST(ESMVariantTest, copy_assigned_is_equal_to_source) { const Variant a(int{42}); Variant b; b = a; ASSERT_EQ(a, b); } TEST(ESMVariantTest, not_equal_is_negation_of_equal) { const Variant a(int{42}); Variant b; b = a; ASSERT_TRUE(!(a != b)); } TEST(ESMVariantTest, different_types_are_not_equal) { ASSERT_NE(Variant(int{42}), Variant(float{2.7f})); } struct ESMVariantWriteToOStreamTest : TestWithParam> {}; TEST_P(ESMVariantWriteToOStreamTest, should_write) { const auto [variant, result] = GetParam(); std::ostringstream s; s << variant; ASSERT_EQ(s.str(), result); } INSTANTIATE_TEST_SUITE_P(VariantAsString, ESMVariantWriteToOStreamTest, Values( std::make_tuple(Variant(), "variant none"), std::make_tuple(Variant(int{42}), "variant long: 42"), std::make_tuple(Variant(float{2.7f}), "variant float: 2.7"), std::make_tuple(Variant(std::string("foo")), "variant string: \"foo\""), std::make_tuple(makeVariant(VT_Unknown), "variant unknown"), std::make_tuple(makeVariant(VT_Short, 42), "variant short: 42"), std::make_tuple(makeVariant(VT_Int, 42), "variant int: 42") )); struct ESMVariantGetTypeTest : Test {}; TEST(ESMVariantGetTypeTest, default_constructed_should_return_none) { ASSERT_EQ(Variant().getType(), VT_None); } TEST(ESMVariantGetTypeTest, for_constructed_from_int_should_return_long) { ASSERT_EQ(Variant(int{}).getType(), VT_Long); } TEST(ESMVariantGetTypeTest, for_constructed_from_float_should_return_float) { ASSERT_EQ(Variant(float{}).getType(), VT_Float); } TEST(ESMVariantGetTypeTest, for_constructed_from_lvalue_string_should_return_string) { const std::string string; ASSERT_EQ(Variant(string).getType(), VT_String); } TEST(ESMVariantGetTypeTest, for_constructed_from_rvalue_string_should_return_string) { ASSERT_EQ(Variant(std::string{}).getType(), VT_String); } struct ESMVariantGetIntegerTest : Test {}; TEST(ESMVariantGetIntegerTest, for_default_constructed_should_throw_exception) { ASSERT_THROW(Variant().getInteger(), std::runtime_error); } TEST(ESMVariantGetIntegerTest, for_constructed_from_int_should_return_same_value) { const Variant variant(int{42}); ASSERT_EQ(variant.getInteger(), 42); } TEST(ESMVariantGetIntegerTest, for_constructed_from_float_should_return_casted_to_int) { const Variant variant(float{2.7}); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantGetIntegerTest, for_constructed_from_string_should_throw_exception) { const Variant variant(std::string("foo")); ASSERT_THROW(variant.getInteger(), std::runtime_error); } TEST(ESMVariantGetFloatTest, for_default_constructed_should_throw_exception) { ASSERT_THROW(Variant().getFloat(), std::runtime_error); } TEST(ESMVariantGetFloatTest, for_constructed_from_int_should_return_casted_to_float) { const Variant variant(int{42}); ASSERT_EQ(variant.getFloat(), 42); } TEST(ESMVariantGetFloatTest, for_constructed_from_float_should_return_same_value) { const Variant variant(float{2.7f}); ASSERT_EQ(variant.getFloat(), 2.7f); } TEST(ESMVariantGetFloatTest, for_constructed_from_string_should_throw_exception) { const Variant variant(std::string("foo")); ASSERT_THROW(variant.getFloat(), std::runtime_error); } TEST(ESMVariantGetStringTest, for_default_constructed_should_throw_exception) { ASSERT_THROW(Variant().getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_int_should_throw_exception) { const Variant variant(int{42}); ASSERT_THROW(variant.getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_float_should_throw_exception) { const Variant variant(float{2.7}); ASSERT_THROW(variant.getString(), std::bad_variant_access); } TEST(ESMVariantGetStringTest, for_constructed_from_string_should_return_same_value) { const Variant variant(std::string("foo")); ASSERT_EQ(variant.getString(), "foo"); } TEST(ESMVariantSetTypeTest, for_unknown_should_reset_data) { Variant variant(int{42}); variant.setType(VT_Unknown); ASSERT_THROW(variant.getInteger(), std::runtime_error); } TEST(ESMVariantSetTypeTest, for_none_should_reset_data) { Variant variant(int{42}); variant.setType(VT_None); ASSERT_THROW(variant.getInteger(), std::runtime_error); } TEST(ESMVariantSetTypeTest, for_same_type_should_not_change_value) { Variant variant(int{42}); variant.setType(VT_Long); ASSERT_EQ(variant.getInteger(), 42); } TEST(ESMVariantSetTypeTest, for_float_replaced_by_int_should_cast_float_to_int) { Variant variant(float{2.7f}); variant.setType(VT_Int); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetTypeTest, for_string_replaced_by_int_should_set_default_initialized_data) { Variant variant(std::string("foo")); variant.setType(VT_Int); ASSERT_EQ(variant.getInteger(), 0); } TEST(ESMVariantSetTypeTest, for_default_constructed_replaced_by_float_should_set_default_initialized_value) { Variant variant; variant.setType(VT_Float); ASSERT_EQ(variant.getInteger(), 0.0f); } TEST(ESMVariantSetTypeTest, for_float_replaced_by_short_should_cast_data_to_int) { Variant variant(float{2.7f}); variant.setType(VT_Short); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetTypeTest, for_float_replaced_by_long_should_cast_data_to_int) { Variant variant(float{2.7f}); variant.setType(VT_Long); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetTypeTest, for_int_replaced_by_float_should_cast_data_to_float) { Variant variant(int{42}); variant.setType(VT_Float); ASSERT_EQ(variant.getFloat(), 42.0f); } TEST(ESMVariantSetTypeTest, for_int_replaced_by_string_should_set_default_initialized_data) { Variant variant(int{42}); variant.setType(VT_String); ASSERT_EQ(variant.getString(), ""); } TEST(ESMVariantSetIntegerTest, for_default_constructed_should_throw_exception) { Variant variant; ASSERT_THROW(variant.setInteger(42), std::runtime_error); } TEST(ESMVariantSetIntegerTest, for_unknown_should_throw_exception) { Variant variant; variant.setType(VT_Unknown); ASSERT_THROW(variant.setInteger(42), std::runtime_error); } TEST(ESMVariantSetIntegerTest, for_default_int_should_change_value) { Variant variant(int{13}); variant.setInteger(42); ASSERT_EQ(variant.getInteger(), 42); } TEST(ESMVariantSetIntegerTest, for_int_should_change_value) { Variant variant; variant.setType(VT_Int); variant.setInteger(42); ASSERT_EQ(variant.getInteger(), 42); } TEST(ESMVariantSetIntegerTest, for_short_should_change_value) { Variant variant; variant.setType(VT_Short); variant.setInteger(42); ASSERT_EQ(variant.getInteger(), 42); } TEST(ESMVariantSetIntegerTest, for_float_should_change_value) { Variant variant(float{2.7f}); variant.setInteger(42); ASSERT_EQ(variant.getFloat(), 42.0f); } TEST(ESMVariantSetIntegerTest, for_string_should_throw_exception) { Variant variant(std::string{}); ASSERT_THROW(variant.setInteger(42), std::runtime_error); } TEST(ESMVariantSetFloatTest, for_default_constructed_should_throw_exception) { Variant variant; ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); } TEST(ESMVariantSetFloatTest, for_unknown_should_throw_exception) { Variant variant; variant.setType(VT_Unknown); ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); } TEST(ESMVariantSetFloatTest, for_default_int_should_change_value) { Variant variant(int{13}); variant.setFloat(2.7f); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetFloatTest, for_int_should_change_value) { Variant variant; variant.setType(VT_Int); variant.setFloat(2.7f); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetFloatTest, for_short_should_change_value) { Variant variant; variant.setType(VT_Short); variant.setFloat(2.7f); ASSERT_EQ(variant.getInteger(), 2); } TEST(ESMVariantSetFloatTest, for_float_should_change_value) { Variant variant(float{2.7f}); variant.setFloat(3.14f); ASSERT_EQ(variant.getFloat(), 3.14f); } TEST(ESMVariantSetFloatTest, for_string_should_throw_exception) { Variant variant(std::string{}); ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); } TEST(ESMVariantSetStringTest, for_default_constructed_should_throw_exception) { Variant variant; ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_unknown_should_throw_exception) { Variant variant; variant.setType(VT_Unknown); ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_default_int_should_throw_exception) { Variant variant(int{13}); ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_int_should_throw_exception) { Variant variant; variant.setType(VT_Int); ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_short_should_throw_exception) { Variant variant; variant.setType(VT_Short); ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_float_should_throw_exception) { Variant variant(float{2.7f}); ASSERT_THROW(variant.setString("foo"), std::bad_variant_access); } TEST(ESMVariantSetStringTest, for_string_should_change_value) { Variant variant(std::string("foo")); variant.setString("bar"); ASSERT_EQ(variant.getString(), "bar"); } struct WriteToESMTestCase { Variant mVariant; Variant::Format mFormat; std::size_t mDataSize {}; }; std::string write(const Variant& variant, const Variant::Format format) { std::ostringstream out; ESMWriter writer; writer.save(out); variant.write(writer, format); writer.close(); return out.str(); } Variant read(const Variant::Format format, const std::string& data) { Variant result; ESMReader reader; reader.open(std::make_unique(data), ""); result.read(reader, format); return result; } Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize) { const std::string data = write(variant, format); EXPECT_EQ(data.size(), dataSize); return read(format, data); } struct ESMVariantToESMTest : TestWithParam {}; TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) { const auto param = GetParam(); const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); ASSERT_EQ(param.mVariant, result); } INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, Values( WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324}, WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345}, WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345}, WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336}, WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336}, WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345}, WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334}, WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336}, WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336} )); struct ESMVariantToESMNoneTest : TestWithParam {}; TEST_P(ESMVariantToESMNoneTest, deserialized_is_none) { const auto param = GetParam(); const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize); ASSERT_EQ(Variant(), result); } INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, Values( WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336}, WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335}, WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336} )); struct ESMVariantWriteToESMFailTest : TestWithParam {}; TEST_P(ESMVariantWriteToESMFailTest, write_is_not_supported) { const auto param = GetParam(); std::ostringstream out; ESMWriter writer; writer.save(out); ASSERT_THROW(param.mVariant.write(writer, param.mFormat), std::runtime_error); } INSTANTIATE_TEST_SUITE_P(VariantAndFormat, ESMVariantWriteToESMFailTest, Values( WriteToESMTestCase {Variant(), Variant::Format_Global}, WriteToESMTestCase {Variant(), Variant::Format_Info}, WriteToESMTestCase {Variant(), Variant::Format_Local}, WriteToESMTestCase {Variant(int{42}), Variant::Format_Gmst}, WriteToESMTestCase {Variant(int{42}), Variant::Format_Info}, WriteToESMTestCase {Variant(int{42}), Variant::Format_Local}, WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Global}, WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Info}, WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Local}, WriteToESMTestCase {makeVariant(VT_Unknown), Variant::Format_Global}, WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Global}, WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Gmst}, WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Info} )); } openmw-openmw-0.48.0/apps/openmw_test_suite/esm3/000077500000000000000000000000001445372753700220235ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/esm3/readerscache.cpp000066400000000000000000000062601445372753700251440ustar00rootroot00000000000000#include #include #include #include #ifndef OPENMW_DATA_DIR #error "OPENMW_DATA_DIR is not defined" #endif namespace { using namespace testing; using namespace ESM; TEST(ESM3ReadersCache, onAttemptToRequestTheSameReaderTwiceShouldThrowException) { ReadersCache readers(1); const ReadersCache::BusyItem reader = readers.get(0); EXPECT_THROW(readers.get(0), std::logic_error); } TEST(ESM3ReadersCache, shouldAllowToHaveBusyItemsMoreThanCapacity) { ReadersCache readers(1); const ReadersCache::BusyItem reader0 = readers.get(0); const ReadersCache::BusyItem reader1 = readers.get(1); } TEST(ESM3ReadersCache, shouldKeepClosedReleasedClosedItem) { ReadersCache readers(1); readers.get(0); const ReadersCache::BusyItem reader = readers.get(0); EXPECT_FALSE(reader->isOpen()); } struct ESM3ReadersCacheWithContentFile : Test { static constexpr std::size_t sInitialOffset = 324; static constexpr std::size_t sSkip = 100; const Files::PathContainer mDataDirs {{std::string(OPENMW_DATA_DIR)}}; const Files::Collections mFileCollections {mDataDirs, true}; const std::string mContentFile = "template.omwgame"; const std::string mContentFilePath = mFileCollections.getCollection(".omwgame").getPath(mContentFile).string(); }; TEST_F(ESM3ReadersCacheWithContentFile, shouldKeepOpenReleasedOpenReader) { ReadersCache readers(1); { const ReadersCache::BusyItem reader = readers.get(0); reader->open(mContentFilePath); ASSERT_TRUE(reader->isOpen()); ASSERT_EQ(reader->getFileOffset(), sInitialOffset); ASSERT_GT(reader->getFileSize(), sInitialOffset + sSkip); reader->skip(sSkip); ASSERT_EQ(reader->getFileOffset(), sInitialOffset + sSkip); } { const ReadersCache::BusyItem reader = readers.get(0); EXPECT_TRUE(reader->isOpen()); EXPECT_EQ(reader->getName(), mContentFilePath); EXPECT_EQ(reader->getFileOffset(), sInitialOffset + sSkip); } } TEST_F(ESM3ReadersCacheWithContentFile, shouldCloseFreeReaderWhenReachingCapacityLimit) { ReadersCache readers(1); { const ReadersCache::BusyItem reader = readers.get(0); reader->open(mContentFilePath); ASSERT_TRUE(reader->isOpen()); ASSERT_EQ(reader->getFileOffset(), sInitialOffset); ASSERT_GT(reader->getFileSize(), sInitialOffset + sSkip); reader->skip(sSkip); ASSERT_EQ(reader->getFileOffset(), sInitialOffset + sSkip); } { const ReadersCache::BusyItem reader = readers.get(1); reader->open(mContentFilePath); ASSERT_TRUE(reader->isOpen()); } { const ReadersCache::BusyItem reader = readers.get(0); EXPECT_TRUE(reader->isOpen()); EXPECT_EQ(reader->getFileOffset(), sInitialOffset); } } } openmw-openmw-0.48.0/apps/openmw_test_suite/esm4/000077500000000000000000000000001445372753700220245ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/esm4/includes.cpp000066400000000000000000000067341445372753700243500ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include openmw-openmw-0.48.0/apps/openmw_test_suite/esmloader/000077500000000000000000000000001445372753700231275ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/esmloader/esmdata.cpp000066400000000000000000000076521445372753700252630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace EsmLoader; struct Params { std::string mRefId; ESM::RecNameInts mType; std::string mResult; std::function mPushBack; }; struct EsmLoaderGetModelTest : TestWithParam {}; TEST_P(EsmLoaderGetModelTest, shouldReturnFoundModelName) { EsmData data; GetParam().mPushBack(data); EXPECT_EQ(EsmLoader::getModel(data, GetParam().mRefId, GetParam().mType), GetParam().mResult); } void pushBack(ESM::Activator&& value, EsmData& esmData) { esmData.mActivators.push_back(std::move(value)); } void pushBack(ESM::Container&& value, EsmData& esmData) { esmData.mContainers.push_back(std::move(value)); } void pushBack(ESM::Door&& value, EsmData& esmData) { esmData.mDoors.push_back(std::move(value)); } void pushBack(ESM::Static&& value, EsmData& esmData) { esmData.mStatics.push_back(std::move(value)); } template struct PushBack { std::string mId; std::string mModel; void operator()(EsmData& esmData) const { T value; value.mId = mId; value.mModel = mModel; pushBack(std::move(value), esmData); } }; const std::array params = { Params {"acti_ref_id", ESM::REC_ACTI, "acti_model", PushBack {"acti_ref_id", "acti_model"}}, Params {"cont_ref_id", ESM::REC_CONT, "cont_model", PushBack {"cont_ref_id", "cont_model"}}, Params {"door_ref_id", ESM::REC_DOOR, "door_model", PushBack {"door_ref_id", "door_model"}}, Params {"static_ref_id", ESM::REC_STAT, "static_model", PushBack {"static_ref_id", "static_model"}}, Params {"acti_ref_id_a", ESM::REC_ACTI, "", PushBack {"acti_ref_id_z", "acti_model"}}, Params {"cont_ref_id_a", ESM::REC_CONT, "", PushBack {"cont_ref_id_z", "cont_model"}}, Params {"door_ref_id_a", ESM::REC_DOOR, "", PushBack {"door_ref_id_z", "door_model"}}, Params {"static_ref_id_a", ESM::REC_STAT, "", PushBack {"static_ref_id_z", "static_model"}}, Params {"acti_ref_id_z", ESM::REC_ACTI, "", PushBack {"acti_ref_id_a", "acti_model"}}, Params {"cont_ref_id_z", ESM::REC_CONT, "", PushBack {"cont_ref_id_a", "cont_model"}}, Params {"door_ref_id_z", ESM::REC_DOOR, "", PushBack {"door_ref_id_a", "door_model"}}, Params {"static_ref_id_z", ESM::REC_STAT, "", PushBack {"static_ref_id_a", "static_model"}}, Params {"ref_id", ESM::REC_STAT, "", [] (EsmData&) {}}, Params {"ref_id", ESM::REC_BOOK, "", [] (EsmData&) {}}, }; INSTANTIATE_TEST_SUITE_P(Params, EsmLoaderGetModelTest, ValuesIn(params)); TEST(EsmLoaderGetGameSettingTest, shouldReturnFoundValue) { std::vector settings; ESM::GameSetting setting; setting.mId = "setting"; setting.mValue = ESM::Variant(42); settings.push_back(setting); EXPECT_EQ(EsmLoader::getGameSetting(settings, "setting"), ESM::Variant(42)); } TEST(EsmLoaderGetGameSettingTest, shouldThrowExceptionWhenNotFound) { const std::vector settings; EXPECT_THROW(EsmLoader::getGameSetting(settings, "setting"), std::runtime_error); } } openmw-openmw-0.48.0/apps/openmw_test_suite/esmloader/load.cpp000066400000000000000000000121501445372753700245510ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef OPENMW_DATA_DIR #error "OPENMW_DATA_DIR is not defined" #endif namespace { using namespace testing; using namespace EsmLoader; struct EsmLoaderTest : Test { const Files::PathContainer mDataDirs {{std::string(OPENMW_DATA_DIR)}}; const Files::Collections mFileCollections {mDataDirs, true}; const std::vector mContentFiles {{"template.omwgame"}}; }; TEST_F(EsmLoaderTest, loadEsmDataShouldSupportOmwgame) { Query query; query.mLoadActivators = true; query.mLoadCells = true; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = true; query.mLoadLands = true; query.mLoadStatics = true; ESM::ReadersCache readers; ToUTF8::Utf8Encoder* const encoder = nullptr; const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mCells.size(), 1); EXPECT_EQ(esmData.mContainers.size(), 0); EXPECT_EQ(esmData.mDoors.size(), 0); EXPECT_EQ(esmData.mGameSettings.size(), 1521); EXPECT_EQ(esmData.mLands.size(), 1); EXPECT_EQ(esmData.mStatics.size(), 2); } TEST_F(EsmLoaderTest, shouldIgnoreCellsWhenQueryLoadCellsIsFalse) { Query query; query.mLoadActivators = true; query.mLoadCells = false; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = true; query.mLoadLands = true; query.mLoadStatics = true; ESM::ReadersCache readers; ToUTF8::Utf8Encoder* const encoder = nullptr; const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mCells.size(), 0); EXPECT_EQ(esmData.mContainers.size(), 0); EXPECT_EQ(esmData.mDoors.size(), 0); EXPECT_EQ(esmData.mGameSettings.size(), 1521); EXPECT_EQ(esmData.mLands.size(), 1); EXPECT_EQ(esmData.mStatics.size(), 2); } TEST_F(EsmLoaderTest, shouldIgnoreCellsGameSettingsWhenQueryLoadGameSettingsIsFalse) { Query query; query.mLoadActivators = true; query.mLoadCells = true; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = false; query.mLoadLands = true; query.mLoadStatics = true; ESM::ReadersCache readers; ToUTF8::Utf8Encoder* const encoder = nullptr; const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mCells.size(), 1); EXPECT_EQ(esmData.mContainers.size(), 0); EXPECT_EQ(esmData.mDoors.size(), 0); EXPECT_EQ(esmData.mGameSettings.size(), 0); EXPECT_EQ(esmData.mLands.size(), 1); EXPECT_EQ(esmData.mStatics.size(), 2); } TEST_F(EsmLoaderTest, shouldIgnoreAllWithDefaultQuery) { const Query query; ESM::ReadersCache readers; ToUTF8::Utf8Encoder* const encoder = nullptr; const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mCells.size(), 0); EXPECT_EQ(esmData.mContainers.size(), 0); EXPECT_EQ(esmData.mDoors.size(), 0); EXPECT_EQ(esmData.mGameSettings.size(), 0); EXPECT_EQ(esmData.mLands.size(), 0); EXPECT_EQ(esmData.mStatics.size(), 0); } TEST_F(EsmLoaderTest, loadEsmDataShouldSkipUnsupportedFormats) { Query query; query.mLoadActivators = true; query.mLoadCells = true; query.mLoadContainers = true; query.mLoadDoors = true; query.mLoadGameSettings = true; query.mLoadLands = true; query.mLoadStatics = true; const std::vector contentFiles {{"script.omwscripts"}}; ESM::ReadersCache readers; ToUTF8::Utf8Encoder* const encoder = nullptr; const EsmData esmData = loadEsmData(query, contentFiles, mFileCollections, readers, encoder); EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mCells.size(), 0); EXPECT_EQ(esmData.mContainers.size(), 0); EXPECT_EQ(esmData.mDoors.size(), 0); EXPECT_EQ(esmData.mGameSettings.size(), 0); EXPECT_EQ(esmData.mLands.size(), 0); EXPECT_EQ(esmData.mStatics.size(), 0); } } openmw-openmw-0.48.0/apps/openmw_test_suite/esmloader/record.cpp000066400000000000000000000046721445372753700251220ustar00rootroot00000000000000#include #include #include #include #include namespace { using namespace testing; using namespace EsmLoader; struct Value { int mKey; int mValue; }; auto tie(const Value& v) { return std::tie(v.mKey, v.mValue); } bool operator==(const Value& l, const Value& r) { return tie(l) == tie(r); } std::ostream& operator<<(std::ostream& s, const Value& v) { return s << "Value {" << v.mKey << ", " << v.mValue << "}"; } Record present(const Value& v) { return Record(false, v); } Record deleted(const Value& v) { return Record(true, v); } struct Params { Records mRecords; std::vector mResult; }; struct EsmLoaderPrepareRecordTest : TestWithParam {}; TEST_P(EsmLoaderPrepareRecordTest, prepareRecords) { auto records = GetParam().mRecords; const auto getKey = [&] (const Record& v) { return v.mValue.mKey; }; EXPECT_THAT(prepareRecords(records, getKey), ElementsAreArray(GetParam().mResult)); } const std::array params = { Params {{}, {}}, Params { {present(Value {1, 1})}, {Value {1, 1}} }, Params { {deleted(Value {1, 1})}, {} }, Params { {present(Value {1, 1}), present(Value {2, 2})}, {Value {1, 1}, Value {2, 2}} }, Params { {present(Value {2, 2}), present(Value {1, 1})}, {Value {1, 1}, Value {2, 2}} }, Params { {present(Value {1, 1}), present(Value {1, 2})}, {Value {1, 2}} }, Params { {present(Value {1, 2}), present(Value {1, 1})}, {Value {1, 1}} }, Params { {present(Value {1, 1}), deleted(Value {1, 2})}, {} }, Params { {deleted(Value {1, 1}), present(Value {1, 2})}, {Value {1, 2}} }, Params { {present(Value {1, 2}), deleted(Value {1, 1})}, {} }, Params { {deleted(Value {1, 2}), present(Value {1, 1})}, {Value {1, 1}} }, }; INSTANTIATE_TEST_SUITE_P(Params, EsmLoaderPrepareRecordTest, ValuesIn(params)); } openmw-openmw-0.48.0/apps/openmw_test_suite/files/000077500000000000000000000000001445372753700222565ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/files/hash.cpp000066400000000000000000000050271445372753700237110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace TestingOpenMW; using namespace Files; struct Params { std::size_t mSize; std::array mHash; }; struct FilesGetHash : TestWithParam {}; TEST(FilesGetHash, shouldClearErrors) { const std::string fileName = temporaryFilePath("fileName"); std::string content; std::fill_n(std::back_inserter(content), 1, 'a'); std::istringstream stream(content); stream.exceptions(std::ios::failbit | std::ios::badbit); EXPECT_THAT(getHash(fileName, stream), ElementsAre(9607679276477937801ull, 16624257681780017498ull)); } TEST_P(FilesGetHash, shouldReturnHashForStringStream) { const std::string fileName = temporaryFilePath("fileName"); std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::istringstream stream(content); EXPECT_EQ(getHash(fileName, stream), GetParam().mHash); } TEST_P(FilesGetHash, shouldReturnHashForConstrainedFileStream) { std::string fileName(UnitTest::GetInstance()->current_test_info()->name()); std::replace(fileName.begin(), fileName.end(), '/', '_'); std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); fileName = outputFilePath(fileName); boost::filesystem::fstream(fileName, boost::filesystem::fstream::out | boost::filesystem::fstream::binary) .write(content.data(), static_cast(content.size())); const auto stream = Files::openConstrainedFileStream(fileName, 0, content.size()); EXPECT_EQ(getHash(fileName, *stream), GetParam().mHash); } INSTANTIATE_TEST_SUITE_P(Params, FilesGetHash, Values( Params {0, {0, 0}}, Params {1, {9607679276477937801ull, 16624257681780017498ull}}, Params {128, {15287858148353394424ull, 16818615825966581310ull}}, Params {1000, {11018119256083894017ull, 6631144854802791578ull}}, Params {4096, {11972283295181039100ull, 16027670129106775155ull}}, Params {4097, {16717956291025443060ull, 12856404199748778153ull}}, Params {5000, {15775925571142117787ull, 10322955217889622896ull}} )); } openmw-openmw-0.48.0/apps/openmw_test_suite/fx/000077500000000000000000000000001445372753700215715ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/fx/lexer.cpp000066400000000000000000000136131445372753700234200ustar00rootroot00000000000000#include #include namespace { using namespace testing; using namespace fx::Lexer; struct LexerTest : Test {}; struct LexerSingleTokenTest : Test { template void test() { const std::string content = std::string(Token::repr); Lexer lexer(content); EXPECT_TRUE(std::holds_alternative(lexer.next())); } }; TEST_F(LexerSingleTokenTest, single_token_shared) { test(); } TEST_F(LexerSingleTokenTest, single_token_technique) { test(); } TEST_F(LexerSingleTokenTest, single_token_render_target) { test(); } TEST_F(LexerSingleTokenTest, single_token_vertex) { test(); } TEST_F(LexerSingleTokenTest, single_token_fragment) { test(); } TEST_F(LexerSingleTokenTest, single_token_compute) { test(); } TEST_F(LexerSingleTokenTest, single_token_sampler_1d) { test(); } TEST_F(LexerSingleTokenTest, single_token_sampler_2d) { test(); } TEST_F(LexerSingleTokenTest, single_token_sampler_3d) { test(); } TEST_F(LexerSingleTokenTest, single_token_true) { test(); } TEST_F(LexerSingleTokenTest, single_token_false) { test(); } TEST_F(LexerSingleTokenTest, single_token_vec2) { test(); } TEST_F(LexerSingleTokenTest, single_token_vec3) { test(); } TEST_F(LexerSingleTokenTest, single_token_vec4) { test(); } TEST(LexerTest, peek_whitespace_only_content_should_be_eof) { Lexer lexer(R"( )"); EXPECT_TRUE(std::holds_alternative(lexer.peek())); } TEST(LexerTest, float_with_no_prefixed_digits) { Lexer lexer(R"( 0.123; )"); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); } TEST(LexerTest, float_with_alpha_prefix) { Lexer lexer(R"( abc.123; )"); EXPECT_TRUE(std::holds_alternative(lexer.next())); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); } TEST(LexerTest, float_with_numeric_prefix) { Lexer lexer(R"( 123.123; )"); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); EXPECT_FLOAT_EQ(std::get(token).value, 123.123f); } TEST(LexerTest, int_should_not_be_float) { Lexer lexer(R"( 123 )"); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); EXPECT_EQ(std::get(token).value, 123); } TEST(LexerTest, simple_string) { Lexer lexer(R"( "test string" )"); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); std::string parsed = std::string(std::get(token).value); EXPECT_EQ("test string", parsed); } TEST(LexerTest, fail_on_unterminated_double_quotes) { Lexer lexer(R"( "unterminated string' )"); EXPECT_THROW(lexer.next(), LexerException); } TEST(LexerTest, multiline_strings_with_single_quotes) { Lexer lexer(R"( "string that is on multiple with 'single quotes' and correctly terminated!" )"); auto token = lexer.next(); EXPECT_TRUE(std::holds_alternative(token)); } TEST(LexerTest, fail_on_unterminated_double_quotes_with_multiline_strings) { Lexer lexer(R"( "string that is on multiple with 'single quotes' and but is unterminated :( )"); EXPECT_THROW(lexer.next(), LexerException); } TEST(LexerTest, jump_with_single_nested_bracket) { const std::string content = R"( #version 120 void main() { return 0; }})"; const std::string expected = content.substr(0, content.size() - 1); Lexer lexer(content); auto block = lexer.jump(); EXPECT_NE(block, std::nullopt); EXPECT_EQ(expected, std::string(block.value())); } TEST(LexerTest, jump_with_single_line_comments_and_mismatching_brackets) { const std::string content = R"( #version 120 void main() { // } return 0; }})"; const std::string expected = content.substr(0, content.size() - 1); Lexer lexer(content); auto block = lexer.jump(); EXPECT_NE(block, std::nullopt); EXPECT_EQ(expected, std::string(block.value())); } TEST(LexerTest, jump_with_multi_line_comments_and_mismatching_brackets) { const std::string content = R"( #version 120 void main() { /* } */ return 0; }})"; const std::string expected = content.substr(0, content.size() - 1); Lexer lexer(content); auto block = lexer.jump(); EXPECT_NE(block, std::nullopt); EXPECT_EQ(expected, std::string(block.value())); } TEST(LexerTest, immediate_closed_blocks) { Lexer lexer(R"(block{})"); EXPECT_TRUE(std::holds_alternative(lexer.next())); EXPECT_TRUE(std::holds_alternative(lexer.next())); auto block = lexer.jump(); EXPECT_TRUE(block.has_value()); EXPECT_TRUE(block.value().empty()); EXPECT_TRUE(std::holds_alternative(lexer.next())); } } openmw-openmw-0.48.0/apps/openmw_test_suite/fx/technique.cpp000066400000000000000000000150361445372753700242670ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include #include #include "../testing_util.hpp" namespace { TestingOpenMW::VFSTestFile technique_properties(R"( fragment main {} vertex main {} technique { passes = main; version = "0.1a"; description = "description"; author = "author"; glsl_version = 330; glsl_profile = "compatability"; glsl_extensions = GL_EXT_gpu_shader4, GL_ARB_uniform_buffer_object; flags = disable_sunglare; hdr = true; } )"); TestingOpenMW::VFSTestFile rendertarget_properties{R"( render_target rendertarget { width_ratio = 0.5; height_ratio = 0.5; internal_format = r16f; source_type = float; source_format = red; mipmaps = true; wrap_s = clamp_to_edge; wrap_t = repeat; min_filter = linear; mag_filter = nearest; } fragment downsample2x(target=rendertarget) { omw_In vec2 omw_TexCoord; void main() { omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; } } fragment main { } technique { passes = downsample2x, main; } )"}; TestingOpenMW::VFSTestFile uniform_properties{R"( uniform_vec4 uVec4 { default = vec4(0,0,0,0); min = vec4(0,1,0,0); max = vec4(0,0,1,0); step = 0.5; header = "header"; static = true; description = "description"; } fragment main { } technique { passes = main; } )"}; TestingOpenMW::VFSTestFile missing_sampler_source{R"( sampler_1d mysampler1d { } fragment main { } technique { passes = main; } )"}; TestingOpenMW::VFSTestFile repeated_shared_block{R"( shared { float myfloat = 1.0; } shared {} fragment main { } technique { passes = main; } )"}; using namespace testing; using namespace fx; struct TechniqueTest : Test { std::unique_ptr mVFS; Resource::ImageManager mImageManager; std::unique_ptr mTechnique; TechniqueTest() : mVFS(TestingOpenMW::createTestVFS({ {"shaders/technique_properties.omwfx", &technique_properties}, {"shaders/rendertarget_properties.omwfx", &rendertarget_properties}, {"shaders/uniform_properties.omwfx", &uniform_properties}, {"shaders/missing_sampler_source.omwfx", &missing_sampler_source}, {"shaders/repeated_shared_block.omwfx", &repeated_shared_block}, })) , mImageManager(mVFS.get()) { Settings::Manager::setBool("radial fog", "Fog", true); Settings::Manager::setBool("exponential fog", "Fog", false); Settings::Manager::setBool("stereo enabled", "Stereo", false); } void compile(const std::string& name) { mTechnique = std::make_unique(*mVFS.get(), mImageManager, name, 1, 1, true, true); mTechnique->compile(); } }; TEST_F(TechniqueTest, technique_properties) { std::unordered_set targetExtensions = { "GL_EXT_gpu_shader4", "GL_ARB_uniform_buffer_object" }; compile("technique_properties"); EXPECT_EQ(mTechnique->getVersion(), "0.1a"); EXPECT_EQ(mTechnique->getDescription(), "description"); EXPECT_EQ(mTechnique->getAuthor(), "author"); EXPECT_EQ(mTechnique->getGLSLVersion(), 330); EXPECT_EQ(mTechnique->getGLSLProfile(), "compatability"); EXPECT_EQ(mTechnique->getGLSLExtensions(), targetExtensions); EXPECT_EQ(mTechnique->getFlags(), Technique::Flag_Disable_SunGlare); EXPECT_EQ(mTechnique->getHDR(), true); EXPECT_EQ(mTechnique->getPasses().size(), 1); EXPECT_EQ(mTechnique->getPasses().front()->getName(), "main"); } TEST_F(TechniqueTest, rendertarget_properties) { compile("rendertarget_properties"); EXPECT_EQ(mTechnique->getRenderTargetsMap().size(), 1); const std::string_view name = mTechnique->getRenderTargetsMap().begin()->first; auto& rt = mTechnique->getRenderTargetsMap().begin()->second; auto& texture = rt.mTarget; EXPECT_EQ(name, "rendertarget"); EXPECT_EQ(rt.mMipMap, true); EXPECT_EQ(rt.mSize.mWidthRatio, 0.5f); EXPECT_EQ(rt.mSize.mHeightRatio, 0.5f); EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_S), osg::Texture::CLAMP_TO_EDGE); EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_T), osg::Texture::REPEAT); EXPECT_EQ(texture->getFilter(osg::Texture::MIN_FILTER), osg::Texture::LINEAR); EXPECT_EQ(texture->getFilter(osg::Texture::MAG_FILTER), osg::Texture::NEAREST); EXPECT_EQ(texture->getSourceType(), static_cast(GL_FLOAT)); EXPECT_EQ(texture->getSourceFormat(), static_cast(GL_RED)); EXPECT_EQ(texture->getInternalFormat(), static_cast(GL_R16F)); EXPECT_EQ(mTechnique->getPasses().size(), 2); EXPECT_EQ(mTechnique->getPasses()[0]->getTarget(), "rendertarget"); } TEST_F(TechniqueTest, uniform_properties) { compile("uniform_properties"); EXPECT_EQ(mTechnique->getUniformMap().size(), 1); const auto& uniform = mTechnique->getUniformMap().front(); EXPECT_TRUE(uniform->mStatic); EXPECT_DOUBLE_EQ(uniform->mStep, 0.5); EXPECT_EQ(uniform->getDefault(), osg::Vec4f(0,0,0,0)); EXPECT_EQ(uniform->getMin(), osg::Vec4f(0,1,0,0)); EXPECT_EQ(uniform->getMax(), osg::Vec4f(0,0,1,0)); EXPECT_EQ(uniform->mHeader, "header"); EXPECT_EQ(uniform->mDescription, "description"); EXPECT_EQ(uniform->mName, "uVec4"); } TEST_F(TechniqueTest, fail_with_missing_source_for_sampler) { internal::CaptureStdout(); compile("missing_sampler_source"); std::string output = internal::GetCapturedStdout(); Log(Debug::Error) << output; EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename")); } TEST_F(TechniqueTest, fail_with_repeated_shared_block) { internal::CaptureStdout(); compile("repeated_shared_block"); std::string output = internal::GetCapturedStdout(); Log(Debug::Error) << output; EXPECT_THAT(output, HasSubstr("repeated 'shared' block")); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/000077500000000000000000000000001445372753700217355ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_async.cpp000066400000000000000000000031271445372753700246200ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace TestingOpenMW; struct LuaCoroutineCallbackTest : Test { void SetUp() override { mLua.open_libraries(sol::lib::coroutine); mLua["callback"] = [&](sol::protected_function fn) -> LuaUtil::Callback { sol::table hiddenData(mLua, sol::create); hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = sol::table(mLua, sol::create); return LuaUtil::Callback{ std::move(fn), hiddenData }; }; mLua["pass"] = [this](LuaUtil::Callback callback) { mCb = callback; }; } sol::state mLua; LuaUtil::Callback mCb; }; TEST_F(LuaCoroutineCallbackTest, CoroutineCallbacks) { internal::CaptureStdout(); mLua.safe_script(R"X( local s = 'test' coroutine.wrap(function() pass(callback(function(v) print(s) end)) end)() )X"); mLua.collect_garbage(); mCb.call(); EXPECT_THAT(internal::GetCapturedStdout(), "test\n"); } TEST_F(LuaCoroutineCallbackTest, ErrorInCoroutineCallbacks) { mLua.safe_script(R"X( coroutine.wrap(function() pass(callback(function() error('COROUTINE CALLBACK') end)) end)() )X"); mLua.collect_garbage(); EXPECT_ERROR(mCb.call(), "COROUTINE CALLBACK"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_configuration.cpp000066400000000000000000000275751445372753700263670ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include #include #include #include #include "../testing_util.hpp" namespace { using testing::ElementsAre; using testing::Pair; std::vector> asVector(const LuaUtil::ScriptIdsWithInitializationData& d) { std::vector> res; for (const auto& [k, v] : d) res.emplace_back(k, std::string(v)); return res; } TEST(LuaConfigurationTest, ValidOMWScripts) { ESM::LuaScriptsCfg cfg; LuaUtil::parseOMWScripts(cfg, R"X( # Lines starting with '#' are comments GLOBAL: my_mod/#some_global_script.lua # Script that will be automatically attached to the player PLAYER :my_mod/player.lua CUSTOM : my_mod/some_other_script.lua NPC , CREATURE PLAYER : my_mod/some_other_script.lua)X"); LuaUtil::parseOMWScripts(cfg, ":my_mod/player.LUA \r\nCREATURE,CUSTOM: my_mod/creature.lua\r\n"); ASSERT_EQ(cfg.mScripts.size(), 6); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[0]), "GLOBAL : my_mod/#some_global_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[1]), "PLAYER : my_mod/player.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[2]), "CUSTOM : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[3]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[4]), ": my_mod/player.LUA"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CUSTOM CREATURE : my_mod/creature.lua"); LuaUtil::ScriptsConfiguration conf; conf.init(std::move(cfg)); ASSERT_EQ(conf.size(), 4); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "GLOBAL : my_mod/#some_global_script.lua"); // cfg.mScripts[1] is overridden by cfg.mScripts[4] // cfg.mScripts[2] is overridden by cfg.mScripts[3] EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[2]), ": my_mod/player.LUA"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[3]), "CUSTOM CREATURE : my_mod/creature.lua"); EXPECT_THAT(asVector(conf.getGlobalConf()), ElementsAre(Pair(0, ""))); EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(1, ""))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "something", ESM::RefNum())), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "something", ESM::RefNum())), ElementsAre(Pair(1, ""))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "something", ESM::RefNum())), ElementsAre(Pair(1, ""), Pair(3, ""))); // Check that initialization cleans old data cfg = ESM::LuaScriptsCfg(); conf.init(std::move(cfg)); EXPECT_EQ(conf.size(), 0); } TEST(LuaConfigurationTest, InvalidOMWScripts) { ESM::LuaScriptsCfg cfg; EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL: something"), "Lua script should have suffix '.lua', got: GLOBAL: something"); EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "something.lua"), "No flags found in: something.lua"); cfg.mScripts.clear(); EXPECT_NO_THROW(LuaUtil::parseOMWScripts(cfg, "GLOBAL, PLAYER: something.lua")); LuaUtil::ScriptsConfiguration conf; EXPECT_ERROR(conf.init(std::move(cfg)), "Global script can not have local flags"); } TEST(LuaConfigurationTest, ConfInit) { ESM::LuaScriptsCfg cfg; ESM::LuaScriptCfg& script1 = cfg.mScripts.emplace_back(); script1.mScriptPath = "Script1.lua"; script1.mInitializationData = "data1"; script1.mFlags = ESM::LuaScriptCfg::sPlayer; script1.mTypes.push_back(ESM::REC_CREA); script1.mRecords.push_back({true, "record1", "dataRecord1"}); script1.mRefs.push_back({true, 2, 3, ""}); script1.mRefs.push_back({true, 2, 4, ""}); ESM::LuaScriptCfg& script2 = cfg.mScripts.emplace_back(); script2.mScriptPath = "Script2.lua"; script2.mFlags = ESM::LuaScriptCfg::sCustom; script2.mTypes.push_back(ESM::REC_CONT); ESM::LuaScriptCfg& script1Extra = cfg.mScripts.emplace_back(); script1Extra.mScriptPath = "script1.LUA"; script1Extra.mFlags = ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sMerge; script1Extra.mTypes.push_back(ESM::REC_NPC_); script1Extra.mRecords.push_back({false, "rat", ""}); script1Extra.mRecords.push_back({true, "record2", ""}); script1Extra.mRefs.push_back({true, 3, 5, "dataRef35"}); script1Extra.mRefs.push_back({false, 2, 3, ""}); LuaUtil::ScriptsConfiguration conf; conf.init(cfg); ASSERT_EQ(conf.size(), 2); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "CUSTOM PLAYER CREATURE NPC : Script1.lua ; data 5 bytes ; 3 records ; 4 objects"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "CUSTOM CONTAINER : Script2.lua"); EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "something", ESM::RefNum())), ElementsAre(Pair(1, ""))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "guar", ESM::RefNum())), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, "rat", ESM::RefNum())), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, "record1", ESM::RefNum())), ElementsAre(Pair(0, "dataRecord1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, "record2", ESM::RefNum())), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {1, 1})), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {2, 3})), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, "record3", {3, 5})), ElementsAre(Pair(0, "dataRef35"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, "record4", {2, 4})), ElementsAre(Pair(0, "data1"), Pair(1, ""))); ESM::LuaScriptCfg& script3 = cfg.mScripts.emplace_back(); script3.mScriptPath = "script1.lua"; script3.mFlags = ESM::LuaScriptCfg::sGlobal; EXPECT_ERROR(conf.init(cfg), "Flags mismatch for script1.lua"); } TEST(LuaConfigurationTest, Serialization) { sol::state lua; LuaUtil::BasicSerializer serializer; ESM::ESMWriter writer; writer.setAuthor(""); writer.setDescription(""); writer.setRecordCount(1); writer.setFormat(ESM::Header::CurrentFormat); writer.setVersion(); writer.addMaster("morrowind.esm", 0); ESM::LuaScriptsCfg cfg; std::string luaData; { sol::table data(lua, sol::create); data["number"] = 5; data["string"] = "some value"; data["fargoth"] = ESM::RefNum{128964, 1}; luaData = LuaUtil::serialize(data, &serializer); } { ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back(); script.mScriptPath = "test_global.lua"; script.mFlags = ESM::LuaScriptCfg::sGlobal; script.mInitializationData = luaData; } { ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back(); script.mScriptPath = "test_local.lua"; script.mFlags = ESM::LuaScriptCfg::sMerge; script.mTypes.push_back(ESM::REC_DOOR); script.mTypes.push_back(ESM::REC_MISC); script.mRecords.push_back({true, "rat", luaData}); script.mRecords.push_back({false, "chargendoorjournal", ""}); script.mRefs.push_back({true, 128964, 1, ""}); script.mRefs.push_back({true, 128962, 1, luaData}); } std::stringstream stream; writer.save(stream); writer.startRecord(ESM::REC_LUAL); cfg.save(writer); writer.endRecord(ESM::REC_LUAL); writer.close(); std::string serializedOMWAddon = stream.str(); { // Save for manual testing. boost::filesystem::ofstream f(TestingOpenMW::outputFilePath("lua_conf_test.omwaddon"), boost::filesystem::fstream::binary); f << serializedOMWAddon; f.close(); } ESM::ESMReader reader; reader.open(std::make_unique(serializedOMWAddon), "lua_conf_test.omwaddon"); ASSERT_EQ(reader.getRecordCount(), 1); ASSERT_EQ(reader.getRecName().toInt(), ESM::REC_LUAL); reader.getRecHeader(); ESM::LuaScriptsCfg loadedCfg; loadedCfg.load(reader); ASSERT_EQ(loadedCfg.mScripts.size(), cfg.mScripts.size()); for (size_t i = 0; i < cfg.mScripts.size(); ++i) { EXPECT_EQ(loadedCfg.mScripts[i].mScriptPath, cfg.mScripts[i].mScriptPath); EXPECT_EQ(loadedCfg.mScripts[i].mFlags, cfg.mScripts[i].mFlags); EXPECT_EQ(loadedCfg.mScripts[i].mInitializationData, cfg.mScripts[i].mInitializationData); ASSERT_EQ(loadedCfg.mScripts[i].mTypes.size(), cfg.mScripts[i].mTypes.size()); for (size_t j = 0; j < cfg.mScripts[i].mTypes.size(); ++j) EXPECT_EQ(loadedCfg.mScripts[i].mTypes[j], cfg.mScripts[i].mTypes[j]); ASSERT_EQ(loadedCfg.mScripts[i].mRecords.size(), cfg.mScripts[i].mRecords.size()); for (size_t j = 0; j < cfg.mScripts[i].mRecords.size(); ++j) { EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mAttach, cfg.mScripts[i].mRecords[j].mAttach); EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mRecordId, cfg.mScripts[i].mRecords[j].mRecordId); EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mInitializationData, cfg.mScripts[i].mRecords[j].mInitializationData); } ASSERT_EQ(loadedCfg.mScripts[i].mRefs.size(), cfg.mScripts[i].mRefs.size()); for (size_t j = 0; j < cfg.mScripts[i].mRefs.size(); ++j) { EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mAttach, cfg.mScripts[i].mRefs[j].mAttach); EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mRefnumIndex, cfg.mScripts[i].mRefs[j].mRefnumIndex); EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mRefnumContentFile, cfg.mScripts[i].mRefs[j].mRefnumContentFile); EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mInitializationData, cfg.mScripts[i].mRefs[j].mInitializationData); } } { ESM::ReadersCache readers(4); readers.get(0)->openRaw(std::make_unique("dummyData"), "a.omwaddon"); readers.get(1)->openRaw(std::make_unique("dummyData"), "b.omwaddon"); readers.get(2)->openRaw(std::make_unique("dummyData"), "Morrowind.esm"); readers.get(3)->openRaw(std::make_unique("dummyData"), "c.omwaddon"); reader.setIndex(3); reader.resolveParentFileIndices(readers); } loadedCfg.adjustRefNums(reader); EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumIndex, cfg.mScripts[1].mRefs[0].mRefnumIndex); EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumContentFile, 2); { sol::table data = LuaUtil::deserialize(lua.lua_state(), loadedCfg.mScripts[1].mRefs[1].mInitializationData, &serializer); ESM::RefNum adjustedRef = data["fargoth"].get(); EXPECT_EQ(adjustedRef.mIndex, 128964u); EXPECT_EQ(adjustedRef.mContentFile, 2); } } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_l10n.cpp000066400000000000000000000163651445372753700242650ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace TestingOpenMW; template T get(sol::state& lua, const std::string& luaCode) { return lua.safe_script("return " + luaCode).get(); } VFSTestFile invalidScript("not a script"); VFSTestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); VFSTestFile emptyScript(""); VFSTestFile test1En(R"X( good_morning: "Good morning." you_have_arrows: |- {count, plural, =0{You have no arrows.} one{You have one arrow.} other{You have {count} arrows.} } pc_must_come: |- {PCGender, select, male {He is} female {She is} other {They are} } coming with us. quest_completion: "The quest is {done, number, percent} complete." ordinal: "You came in {num, ordinal} place." spellout: "There {num, plural, one{is {num, spellout} thing} other{are {num, spellout} things}}." duration: "It took {num, duration}" numbers: "{int} and {double, number, integer} are integers, but {double} is a double" rounding: "{value, number, :: .00}" )X"); VFSTestFile test1De(R"X( good_morning: "Guten Morgen." you_have_arrows: |- {count, plural, one{Du hast ein Pfeil.} other{Du hast {count} Pfeile.} } "Hello {name}!": "Hallo {name}!" )X"); VFSTestFile test1EnUS(R"X( currency: "You have {money, number, currency}" )X"); VFSTestFile test2En(R"X( good_morning: "Morning!" you_have_arrows: "Arrows count: {count}" )X"); struct LuaL10nTest : Test { std::unique_ptr mVFS = createTestVFS({ {"l10n/Test1/en.yaml", &test1En}, {"l10n/Test1/en_US.yaml", &test1EnUS}, {"l10n/Test1/de.yaml", &test1De}, {"l10n/Test2/en.yaml", &test2En}, {"l10n/Test3/en.yaml", &test1En}, {"l10n/Test3/de.yaml", &test1De}, }); LuaUtil::ScriptsConfiguration mCfg; }; TEST_F(LuaL10nTest, L10n) { internal::CaptureStdout(); LuaUtil::LuaState lua{mVFS.get(), &mCfg}; sol::state& l = lua.sol(); LuaUtil::L10nManager l10n(mVFS.get(), &lua); l10n.init(); l10n.setPreferredLocales({"de", "en"}); EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n"); internal::CaptureStdout(); l["t1"] = l10n.getContext("Test1"); EXPECT_THAT(internal::GetCapturedStdout(), "Fallback locale: en\n" "Language file \"l10n/Test1/de.yaml\" is enabled\n" "Language file \"l10n/Test1/en.yaml\" is enabled\n"); internal::CaptureStdout(); l["t2"] = l10n.getContext("Test2"); { std::string output = internal::GetCapturedStdout(); EXPECT_THAT(output, HasSubstr("Language file \"l10n/Test2/en.yaml\" is enabled")); } EXPECT_EQ(get(l, "t1('good_morning')"), "Guten Morgen."); EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "Du hast ein Pfeil."); EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "Du hast 5 Pfeile."); EXPECT_EQ(get(l, "t1('Hello {name}!', {name='World'})"), "Hallo World!"); EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); internal::CaptureStdout(); l10n.setPreferredLocales({"en", "de"}); EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: en de\n" "Language file \"l10n/Test1/en.yaml\" is enabled\n" "Language file \"l10n/Test1/de.yaml\" is enabled\n" "Language file \"l10n/Test2/en.yaml\" is enabled\n"); EXPECT_EQ(get(l, "t1('good_morning')"), "Good morning."); EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "You have one arrow."); EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "You have 5 arrows."); EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"male\"})"), "He is coming with us."); EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"female\"})"), "She is coming with us."); EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"blah\"})"), "They are coming with us."); EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"other\"})"), "They are coming with us."); EXPECT_EQ(get(l, "t1('quest_completion', {done=0.1})"), "The quest is 10% complete."); EXPECT_EQ(get(l, "t1('quest_completion', {done=1})"), "The quest is 100% complete."); EXPECT_EQ(get(l, "t1('ordinal', {num=1})"), "You came in 1st place."); EXPECT_EQ(get(l, "t1('ordinal', {num=100})"), "You came in 100th place."); EXPECT_EQ(get(l, "t1('spellout', {num=1})"), "There is one thing."); EXPECT_EQ(get(l, "t1('spellout', {num=100})"), "There are one hundred things."); EXPECT_EQ(get(l, "t1('duration', {num=100})"), "It took 1:40"); EXPECT_EQ(get(l, "t1('numbers', {int=123, double=123.456})"), "123 and 123 are integers, but 123.456 is a double"); EXPECT_EQ(get(l, "t1('rounding', {value=123.456789})"), "123.46"); // Check that failed messages display the key instead of an empty string EXPECT_EQ(get(l, "t1('{mismatched_braces')"), "{mismatched_braces"); EXPECT_EQ(get(l, "t1('{unknown_arg}')"), "{unknown_arg}"); EXPECT_EQ(get(l, "t1('{num, integer}', {num=1})"), "{num, integer}"); // Doesn't give a valid currency symbol with `en`. Not that openmw is designed for real world currency. l10n.setPreferredLocales({"en-US", "de"}); EXPECT_EQ(get(l, "t1('currency', {money=10000.10})"), "You have $10,000.10"); // Note: Not defined in English localisation file, so we fall back to the German before falling back to the key EXPECT_EQ(get(l, "t1('Hello {name}!', {name='World'})"), "Hallo World!"); EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); // Test that locales with variants and country codes fall back to more generic locales internal::CaptureStdout(); l10n.setPreferredLocales({"en-GB-oed", "de"}); EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: en_GB_OED de\n" "Language file \"l10n/Test1/en.yaml\" is enabled\n" "Language file \"l10n/Test1/de.yaml\" is enabled\n" "Language file \"l10n/Test2/en.yaml\" is enabled\n"); EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); // Test setting fallback language l["t3"] = l10n.getContext("Test3", "de"); l10n.setPreferredLocales({"en"}); EXPECT_EQ(get(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_lua.cpp000066400000000000000000000162541445372753700242710ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include "../testing_util.hpp" namespace { using namespace testing; TestingOpenMW::VFSTestFile counterFile(R"X( x = 42 return { get = function() return x end, inc = function(v) x = x + v end } )X"); TestingOpenMW::VFSTestFile invalidScriptFile("Invalid script"); TestingOpenMW::VFSTestFile testsFile(R"X( return { -- should work sin = function(x) return math.sin(x) end, requireMathSin = function(x) return require('math').sin(x) end, useCounter = function() local counter = require('aaa.counter') counter.inc(1) return counter.get() end, callRawset = function() t = {a = 1, b = 2} rawset(t, 'b', 3) return t.b end, print = print, -- should throw an error incorrectRequire = function() require('counter') end, modifySystemLib = function() math.sin = 5 end, modifySystemLib2 = function() math.__index.sin = 5 end, rawsetSystemLib = function() rawset(math, 'sin', 5) end, callLoadstring = function() loadstring('print(1)') end, setSqr = function() require('sqrlib').sqr = math.sin end, setOmwName = function() require('openmw').name = 'abc' end, -- should work if API is registered sqr = function(x) return require('sqrlib').sqr(x) end, apiName = function() return require('test.api').name end } )X"); struct LuaStateTest : Test { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ {"aaa/counter.lua", &counterFile}, {"bbb/tests.lua", &testsFile}, {"invalid.lua", &invalidScriptFile} }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{mVFS.get(), &mCfg}; }; TEST_F(LuaStateTest, Sandbox) { sol::table script1 = mLua.runInNewSandbox("aaa/counter.lua"); EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 42); LuaUtil::call(script1["inc"], 3); EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); sol::table script2 = mLua.runInNewSandbox("aaa/counter.lua"); EXPECT_EQ(LuaUtil::call(script2["get"]).get(), 42); LuaUtil::call(script2["inc"], 1); EXPECT_EQ(LuaUtil::call(script2["get"]).get(), 43); EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); } TEST_F(LuaStateTest, ToString) { EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), 3.14)), "3.14"); EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), true)), "true"); EXPECT_EQ(LuaUtil::toString(sol::nil), "nil"); EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), "something")), "\"something\""); } TEST_F(LuaStateTest, Cast) { EXPECT_EQ(LuaUtil::cast(sol::make_object(mLua.sol(), 3.14)), 3); EXPECT_ERROR( LuaUtil::cast(sol::make_object(mLua.sol(), "3.14")), "Value \"\"3.14\"\" can not be casted to int"); EXPECT_ERROR(LuaUtil::cast(sol::make_object(mLua.sol(), sol::nil)), "Value \"nil\" can not be casted to string"); EXPECT_ERROR(LuaUtil::cast(sol::make_object(mLua.sol(), sol::nil)), "Value \"nil\" can not be casted to string"); EXPECT_ERROR(LuaUtil::cast(sol::make_object(mLua.sol(), sol::nil)), "Value \"nil\" can not be casted to sol::table"); EXPECT_ERROR(LuaUtil::cast(sol::make_object(mLua.sol(), "3.14")), "Value \"\"3.14\"\" can not be casted to sol::function"); EXPECT_ERROR(LuaUtil::cast(sol::make_object(mLua.sol(), "3.14")), "Value \"\"3.14\"\" can not be casted to sol::function"); } TEST_F(LuaStateTest, ErrorHandling) { EXPECT_ERROR(mLua.runInNewSandbox("invalid.lua"), "[string \"invalid.lua\"]:1:"); } TEST_F(LuaStateTest, CustomRequire) { sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); EXPECT_FLOAT_EQ(LuaUtil::call(script["sin"], 1).get(), -LuaUtil::call(script["requireMathSin"], -1).get()); EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 43); EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 44); { sol::table script2 = mLua.runInNewSandbox("bbb/tests.lua"); EXPECT_EQ(LuaUtil::call(script2["useCounter"]).get(), 43); } EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 45); EXPECT_ERROR(LuaUtil::call(script["incorrectRequire"]), "module not found: counter"); } TEST_F(LuaStateTest, ReadOnly) { sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); // rawset itself is allowed EXPECT_EQ(LuaUtil::call(script["callRawset"]).get(), 3); // but read-only object can not be modified even with rawset EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib2"]), "a nil value"); EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script); } TEST_F(LuaStateTest, Print) { { sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); testing::internal::CaptureStdout(); LuaUtil::call(script["print"], 1, 2, 3); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "[bbb/tests.lua]:\t1\t2\t3\n"); } { sol::table script = mLua.runInNewSandbox("bbb/tests.lua", "prefix"); testing::internal::CaptureStdout(); LuaUtil::call(script["print"]); // print with no arguments std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "prefix[bbb/tests.lua]:\n"); } } TEST_F(LuaStateTest, UnsafeFunction) { sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); EXPECT_ERROR(LuaUtil::call(script["callLoadstring"]), "a nil value"); } TEST_F(LuaStateTest, ProvideAPI) { LuaUtil::LuaState lua(mVFS.get(), &mCfg); sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1")); sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2")); sol::table script1 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api1}}); lua.addCommonPackage( "sqrlib", lua.sol().create_table_with("sqr", [](int x) { return x * x; })); sol::table script2 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api2}}); EXPECT_ERROR(LuaUtil::call(script1["sqr"], 3), "module not found: sqrlib"); EXPECT_EQ(LuaUtil::call(script2["sqr"], 3).get(), 9); EXPECT_EQ(LuaUtil::call(script1["apiName"]).get(), "api1"); EXPECT_EQ(LuaUtil::call(script2["apiName"]).get(), "api2"); } TEST_F(LuaStateTest, GetLuaVersion) { EXPECT_THAT(LuaUtil::getLuaVersion(), HasSubstr("Lua")); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_scriptscontainer.cpp000066400000000000000000000425101445372753700270740ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace TestingOpenMW; VFSTestFile invalidScript("not a script"); VFSTestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); VFSTestFile emptyScript(""); VFSTestFile testScript(R"X( return { engineHandlers = { onUpdate = function(dt) print(' update ' .. tostring(dt)) end, onLoad = function() print('load') end, }, eventHandlers = { Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) end, Event2 = function(eventData) print(' event2 ' .. tostring(eventData.x)) end, Print = function() print('print') end } } )X"); VFSTestFile stopEventScript(R"X( return { eventHandlers = { Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) return eventData.x >= 1 end } } )X"); VFSTestFile loadSaveScript(R"X( x = 0 y = 0 return { engineHandlers = { onSave = function(state) return {x = x, y = y} end, onLoad = function(state) x, y = state.x, state.y end }, eventHandlers = { Set = function(eventData) eventData.n = eventData.n - 1 if eventData.n == 0 then x, y = eventData.x, eventData.y end end, Print = function() print(x, y) end } } )X"); VFSTestFile interfaceScript(R"X( return { interfaceName = "TestInterface", interface = { fn = function(x) print('FN', x) end, value = 3.5 }, } )X"); VFSTestFile overrideInterfaceScript(R"X( local old = nil local interface = { fn = function(x) print('NEW FN', x) old.fn(x) end, value, } return { interfaceName = "TestInterface", interface = interface, engineHandlers = { onInit = function() print('init') end, onLoad = function() print('load') end, onInterfaceOverride = function(oldInterface) print('override') old = oldInterface interface.value = oldInterface.value + 1 end }, } )X"); VFSTestFile useInterfaceScript(R"X( local interfaces = require('openmw.interfaces') return { engineHandlers = { onUpdate = function() interfaces.TestInterface.fn(interfaces.TestInterface.value) end, }, } )X"); struct LuaScriptsContainerTest : Test { std::unique_ptr mVFS = createTestVFS({ {"invalid.lua", &invalidScript}, {"incorrect.lua", &incorrectScript}, {"empty.lua", &emptyScript}, {"test1.lua", &testScript}, {"test2.lua", &testScript}, {"stopEvent.lua", &stopEventScript}, {"loadSave1.lua", &loadSaveScript}, {"loadSave2.lua", &loadSaveScript}, {"testInterface.lua", &interfaceScript}, {"overrideInterface.lua", &overrideInterfaceScript}, {"useInterface.lua", &useInterfaceScript}, }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{mVFS.get(), &mCfg}; LuaScriptsContainerTest() { ESM::LuaScriptsCfg cfg; LuaUtil::parseOMWScripts(cfg, R"X( CUSTOM: invalid.lua CUSTOM: incorrect.lua CUSTOM: empty.lua CUSTOM: test1.lua CUSTOM: stopEvent.lua CUSTOM: test2.lua NPC: loadSave1.lua CUSTOM, NPC: loadSave2.lua CUSTOM, PLAYER: testInterface.lua CUSTOM, PLAYER: overrideInterface.lua CUSTOM, PLAYER: useInterface.lua )X"); mCfg.init(std::move(cfg)); } }; TEST_F(LuaScriptsContainerTest, VerifyStructure) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); { testing::internal::CaptureStdout(); EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("invalid.lua"))); std::string output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, HasSubstr("Can't start Test[invalid.lua]")); } { testing::internal::CaptureStdout(); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("incorrect.lua"))); std::string output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, HasSubstr("Not supported handler 'incorrectHandler' in Test[incorrect.lua]")); EXPECT_THAT(output, HasSubstr("Not supported section 'incorrectSection' in Test[incorrect.lua]")); } { testing::internal::CaptureStdout(); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("empty.lua"))); EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("empty.lua"))); // already present EXPECT_EQ(internal::GetCapturedStdout(), ""); } } TEST_F(LuaScriptsContainerTest, CallHandler) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); testing::internal::CaptureStdout(); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n" "Test[test2.lua]:\t update 1.5\n"); } TEST_F(LuaScriptsContainerTest, CallEvent) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); std::string X0 = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); std::string X1 = LuaUtil::serialize(mLua.sol().create_table_with("x", 1.5)); { testing::internal::CaptureStdout(); scripts.receiveEvent("SomeEvent", X1); EXPECT_EQ(internal::GetCapturedStdout(), "Test has received event 'SomeEvent', but there are no handlers for this event\n"); } { testing::internal::CaptureStdout(); scripts.receiveEvent("Event1", X1); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test2.lua]:\t event1 1.5\n" "Test[stopEvent.lua]:\t event1 1.5\n" "Test[test1.lua]:\t event1 1.5\n"); } { testing::internal::CaptureStdout(); scripts.receiveEvent("Event2", X1); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test2.lua]:\t event2 1.5\n" "Test[test1.lua]:\t event2 1.5\n"); } { testing::internal::CaptureStdout(); scripts.receiveEvent("Event1", X0); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test2.lua]:\t event1 0.5\n" "Test[stopEvent.lua]:\t event1 0.5\n"); } { testing::internal::CaptureStdout(); scripts.receiveEvent("Event2", X0); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test2.lua]:\t event2 0.5\n" "Test[test1.lua]:\t event2 0.5\n"); } } TEST_F(LuaScriptsContainerTest, RemoveScript) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); std::string X = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); { testing::internal::CaptureStdout(); scripts.update(1.5f); scripts.receiveEvent("Event1", X); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n" "Test[test2.lua]:\t update 1.5\n" "Test[test2.lua]:\t event1 0.5\n" "Test[stopEvent.lua]:\t event1 0.5\n"); } { testing::internal::CaptureStdout(); int stopEventScriptId = *mCfg.findId("stopEvent.lua"); EXPECT_TRUE(scripts.hasScript(stopEventScriptId)); scripts.removeScript(stopEventScriptId); EXPECT_FALSE(scripts.hasScript(stopEventScriptId)); scripts.update(1.5f); scripts.receiveEvent("Event1", X); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n" "Test[test2.lua]:\t update 1.5\n" "Test[test2.lua]:\t event1 0.5\n" "Test[test1.lua]:\t event1 0.5\n"); } { testing::internal::CaptureStdout(); scripts.removeScript(*mCfg.findId("test1.lua")); scripts.update(1.5f); scripts.receiveEvent("Event1", X); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test2.lua]:\t update 1.5\n" "Test[test2.lua]:\t event1 0.5\n"); } } TEST_F(LuaScriptsContainerTest, AutoStart) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); scripts.setAutoStartConf(mCfg.getPlayerConf()); testing::internal::CaptureStdout(); scripts.addAutoStartedScripts(); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), "Test[overrideInterface.lua]:\toverride\n" "Test[overrideInterface.lua]:\tinit\n" "Test[overrideInterface.lua]:\tNEW FN\t4.5\n" "Test[testInterface.lua]:\tFN\t4.5\n"); } TEST_F(LuaScriptsContainerTest, Interface) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); scripts.setAutoStartConf(mCfg.getLocalConf(ESM::REC_CREA, "", ESM::RefNum())); int addIfaceId = *mCfg.findId("testInterface.lua"); int overrideIfaceId = *mCfg.findId("overrideInterface.lua"); int useIfaceId = *mCfg.findId("useInterface.lua"); testing::internal::CaptureStdout(); scripts.addAutoStartedScripts(); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), ""); testing::internal::CaptureStdout(); EXPECT_TRUE(scripts.addCustomScript(addIfaceId)); EXPECT_TRUE(scripts.addCustomScript(overrideIfaceId)); EXPECT_TRUE(scripts.addCustomScript(useIfaceId)); scripts.update(1.5f); scripts.removeScript(overrideIfaceId); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), "Test[overrideInterface.lua]:\toverride\n" "Test[overrideInterface.lua]:\tinit\n" "Test[overrideInterface.lua]:\tNEW FN\t4.5\n" "Test[testInterface.lua]:\tFN\t4.5\n" "Test[testInterface.lua]:\tFN\t3.5\n"); } TEST_F(LuaScriptsContainerTest, LoadSave) { LuaUtil::ScriptsContainer scripts1(&mLua, "Test"); LuaUtil::ScriptsContainer scripts2(&mLua, "Test"); LuaUtil::ScriptsContainer scripts3(&mLua, "Test"); scripts1.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, "", ESM::RefNum())); scripts2.setAutoStartConf(mCfg.getLocalConf(ESM::REC_NPC_, "", ESM::RefNum())); scripts3.setAutoStartConf(mCfg.getPlayerConf()); scripts1.addAutoStartedScripts(); EXPECT_TRUE(scripts1.addCustomScript(*mCfg.findId("test1.lua"))); scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with( "n", 1, "x", 0.5, "y", 3.5))); scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with( "n", 2, "x", 2.5, "y", 1.5))); ESM::LuaScripts data; scripts1.save(data); { testing::internal::CaptureStdout(); scripts2.load(data); scripts2.receiveEvent("Print", ""); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\tload\n" "Test[loadSave2.lua]:\t0.5\t3.5\n" "Test[loadSave1.lua]:\t2.5\t1.5\n" "Test[test1.lua]:\tprint\n"); EXPECT_FALSE(scripts2.hasScript(*mCfg.findId("testInterface.lua"))); } { testing::internal::CaptureStdout(); scripts3.load(data); scripts3.receiveEvent("Print", ""); EXPECT_EQ(internal::GetCapturedStdout(), "Ignoring Test[loadSave1.lua]; this script is not allowed here\n" "Test[test1.lua]:\tload\n" "Test[overrideInterface.lua]:\toverride\n" "Test[overrideInterface.lua]:\tinit\n" "Test[loadSave2.lua]:\t0.5\t3.5\n" "Test[test1.lua]:\tprint\n"); EXPECT_TRUE(scripts3.hasScript(*mCfg.findId("testInterface.lua"))); } } TEST_F(LuaScriptsContainerTest, Timers) { using TimerType = LuaUtil::ScriptsContainer::TimerType; LuaUtil::ScriptsContainer scripts(&mLua, "Test"); int test1Id = *mCfg.findId("test1.lua"); int test2Id = *mCfg.findId("test2.lua"); testing::internal::CaptureStdout(); EXPECT_TRUE(scripts.addCustomScript(test1Id)); EXPECT_TRUE(scripts.addCustomScript(test2Id)); EXPECT_EQ(internal::GetCapturedStdout(), ""); int counter1 = 0, counter2 = 0, counter3 = 0, counter4 = 0; sol::function fn1 = sol::make_object(mLua.sol(), [&]() { counter1++; }); sol::function fn2 = sol::make_object(mLua.sol(), [&]() { counter2++; }); sol::function fn3 = sol::make_object(mLua.sol(), [&](int d) { counter3 += d; }); sol::function fn4 = sol::make_object(mLua.sol(), [&](int d) { counter4 += d; }); scripts.registerTimerCallback(test1Id, "A", fn3); scripts.registerTimerCallback(test1Id, "B", fn4); scripts.registerTimerCallback(test2Id, "B", fn3); scripts.registerTimerCallback(test2Id, "A", fn4); scripts.processTimers(1, 2); scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 10, test1Id, "B", sol::make_object(mLua.sol(), 3)); scripts.setupSerializableTimer(TimerType::GAME_TIME, 10, test2Id, "B", sol::make_object(mLua.sol(), 4)); scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 5, test1Id, "A", sol::make_object(mLua.sol(), 1)); scripts.setupSerializableTimer(TimerType::GAME_TIME, 5, test2Id, "A", sol::make_object(mLua.sol(), 2)); scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "A", sol::make_object(mLua.sol(), 10)); scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "B", sol::make_object(mLua.sol(), 20)); scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 10, test2Id, fn2); scripts.setupUnsavableTimer(TimerType::GAME_TIME, 10, test1Id, fn2); scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 5, test2Id, fn1); scripts.setupUnsavableTimer(TimerType::GAME_TIME, 5, test1Id, fn1); scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 15, test2Id, fn1); EXPECT_EQ(counter1, 0); EXPECT_EQ(counter3, 0); scripts.processTimers(6, 4); EXPECT_EQ(counter1, 1); EXPECT_EQ(counter3, 1); EXPECT_EQ(counter4, 0); scripts.processTimers(6, 8); EXPECT_EQ(counter1, 2); EXPECT_EQ(counter2, 0); EXPECT_EQ(counter3, 1); EXPECT_EQ(counter4, 2); scripts.processTimers(11, 12); EXPECT_EQ(counter1, 2); EXPECT_EQ(counter2, 2); EXPECT_EQ(counter3, 5); EXPECT_EQ(counter4, 5); testing::internal::CaptureStdout(); ESM::LuaScripts data; scripts.save(data); scripts.load(data); scripts.registerTimerCallback(test1Id, "B", fn4); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\tload\nTest[test2.lua]:\tload\n"); testing::internal::CaptureStdout(); scripts.processTimers(20, 20); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua] callTimer failed: Callback 'A' doesn't exist\n"); EXPECT_EQ(counter1, 2); EXPECT_EQ(counter2, 2); EXPECT_EQ(counter3, 5); EXPECT_EQ(counter4, 25); } TEST_F(LuaScriptsContainerTest, CallbackWrapper) { LuaUtil::Callback callback{mLua.sol()["print"], mLua.newTable()}; callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptDebugNameKey] = "some_script.lua"; callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptsContainer::ScriptId{nullptr, 0}; testing::internal::CaptureStdout(); callback.call(1.5); EXPECT_EQ(internal::GetCapturedStdout(), "1.5\n"); testing::internal::CaptureStdout(); callback.call(1.5, 2.5); EXPECT_EQ(internal::GetCapturedStdout(), "1.5\t2.5\n"); testing::internal::CaptureStdout(); callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = sol::nil; callback.call(1.5, 2.5); EXPECT_EQ(internal::GetCapturedStdout(), "Ignored callback to the removed script some_script.lua\n"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_serialization.cpp000066400000000000000000000250371445372753700263640ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include #include #include #include #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; TEST(LuaSerializationTest, Nil) { sol::state lua; EXPECT_EQ(LuaUtil::serialize(sol::nil), ""); EXPECT_EQ(LuaUtil::deserialize(lua, ""), sol::nil); } TEST(LuaSerializationTest, Number) { sol::state lua; std::string serialized = LuaUtil::serialize(sol::make_object(lua, 3.14)); EXPECT_EQ(serialized.size(), 10); // version, type, 8 bytes value sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_DOUBLE_EQ(value.as(), 3.14); } TEST(LuaSerializationTest, Boolean) { sol::state lua; { std::string serialized = LuaUtil::serialize(sol::make_object(lua, true)); EXPECT_EQ(serialized.size(), 3); // version, type, 1 byte value sol::object value = LuaUtil::deserialize(lua, serialized); EXPECT_FALSE(value.is()); ASSERT_TRUE(value.is()); EXPECT_TRUE(value.as()); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, false)); EXPECT_EQ(serialized.size(), 3); // version, type, 1 byte value sol::object value = LuaUtil::deserialize(lua, serialized); EXPECT_FALSE(value.is()); ASSERT_TRUE(value.is()); EXPECT_FALSE(value.as()); } } TEST(LuaSerializationTest, String) { sol::state lua; std::string_view emptyString = ""; std::string_view shortString = "abc"; std::string_view longString = "It is a string with more than 32 characters..........................."; { std::string serialized = LuaUtil::serialize(sol::make_object(lua, emptyString)); EXPECT_EQ(serialized.size(), 2); // version, type sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), emptyString); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, shortString)); EXPECT_EQ(serialized.size(), 2 + shortString.size()); // version, type, str data sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), shortString); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, longString)); EXPECT_EQ(serialized.size(), 6 + longString.size()); // version, type, size, str data sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), longString); } } TEST(LuaSerializationTest, Vector) { sol::state lua; osg::Vec2f vec2(1, 2); osg::Vec3f vec3(1, 2, 3); osg::Vec4f vec4(1, 2, 3, 4); { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec2)); EXPECT_EQ(serialized.size(), 18); // version, type, 2x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec2); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec3)); EXPECT_EQ(serialized.size(), 26); // version, type, 3x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec3); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec4)); EXPECT_EQ(serialized.size(), 34); // version, type, 4x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec4); } } TEST(LuaSerializationTest, Color) { sol::state lua; Misc::Color color(1, 1, 1, 1); { std::string serialized = LuaUtil::serialize(sol::make_object(lua, color)); EXPECT_EQ(serialized.size(), 18); // version, type, 4x float sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), color); } } TEST(LuaSerializationTest, Transform) { sol::state lua; osg::Matrixf matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); LuaUtil::TransformM transM = LuaUtil::asTransform(matrix); osg::Quat quat(1, 2, 3, 4); LuaUtil::TransformQ transQ = LuaUtil::asTransform(quat); { std::string serialized = LuaUtil::serialize(sol::make_object(lua, transM)); EXPECT_EQ(serialized.size(), 130); // version, type, 16x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as().mM, transM.mM); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, transQ)); EXPECT_EQ(serialized.size(), 34); // version, type, 4x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as().mQ, transQ.mQ); } } TEST(LuaSerializationTest, Table) { sol::state lua; sol::table table(lua, sol::create); table["aa"] = 1; table["ab"] = true; table["nested"] = sol::table(lua, sol::create); table["nested"]["aa"] = 2; table["nested"]["bb"] = "something"; table["nested"][5] = -0.5; table["nested_empty"] = sol::table(lua, sol::create); table[1] = osg::Vec2f(1, 2); table[2] = osg::Vec2f(2, 1); std::string serialized = LuaUtil::serialize(table); EXPECT_EQ(serialized.size(), 139); sol::table res_table = LuaUtil::deserialize(lua, serialized); sol::table res_readonly_table = LuaUtil::deserialize(lua, serialized, nullptr, true); for (auto t : {res_table, res_readonly_table}) { EXPECT_EQ(t.get("aa"), 1); EXPECT_EQ(t.get("ab"), true); EXPECT_EQ(t.get("nested").get("aa"), 2); EXPECT_EQ(t.get("nested").get("bb"), "something"); EXPECT_DOUBLE_EQ(t.get("nested").get(5), -0.5); EXPECT_EQ(t.get(1), osg::Vec2f(1, 2)); EXPECT_EQ(t.get(2), osg::Vec2f(2, 1)); } lua["t"] = res_table; lua["ro_t"] = res_readonly_table; EXPECT_NO_THROW(lua.safe_script("t.x = 5")); EXPECT_NO_THROW(lua.safe_script("t.nested.x = 5")); EXPECT_ERROR(lua.safe_script("ro_t.x = 5"), "userdata value"); EXPECT_ERROR(lua.safe_script("ro_t.nested.x = 5"), "userdata value"); } struct TestStruct1 { double a, b; }; struct TestStruct2 { int a, b; }; class TestSerializer final : public LuaUtil::UserdataSerializer { bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override { if (data.is()) { TestStruct1 t = data.as(); t.a = Misc::toLittleEndian(t.a); t.b = Misc::toLittleEndian(t.b); append(out, "ts1", &t, sizeof(t)); return true; } if (data.is()) { TestStruct2 t = data.as(); t.a = Misc::toLittleEndian(t.a); t.b = Misc::toLittleEndian(t.b); append(out, "test_struct2", &t, sizeof(t)); return true; } return false; } bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override { if (typeName == "ts1") { if (sizeof(TestStruct1) != binaryData.size()) throw std::runtime_error("Incorrect binaryData.size() for TestStruct1: " + std::to_string(binaryData.size())); TestStruct1 t; std::memcpy(&t, binaryData.data(), sizeof(t)); t.a = Misc::fromLittleEndian(t.a); t.b = Misc::fromLittleEndian(t.b); sol::stack::push(lua, t); return true; } if (typeName == "test_struct2") { if (sizeof(TestStruct2) != binaryData.size()) throw std::runtime_error("Incorrect binaryData.size() for TestStruct2: " + std::to_string(binaryData.size())); TestStruct2 t; std::memcpy(&t, binaryData.data(), sizeof(t)); t.a = Misc::fromLittleEndian(t.a); t.b = Misc::fromLittleEndian(t.b); sol::stack::push(lua, t); return true; } return false; } }; TEST(LuaSerializationTest, UserdataSerializer) { sol::state lua; sol::table table(lua, sol::create); table["x"] = TestStruct1{1.5, 2.5}; table["y"] = TestStruct2{4, 3}; TestSerializer serializer; EXPECT_ERROR(LuaUtil::serialize(table), "Value is not serializable."); std::string serialized = LuaUtil::serialize(table, &serializer); EXPECT_ERROR(LuaUtil::deserialize(lua, serialized), "Unknown type in serialized data:"); sol::table res = LuaUtil::deserialize(lua, serialized, &serializer); TestStruct1 rx = res.get("x"); TestStruct2 ry = res.get("y"); EXPECT_EQ(rx.a, 1.5); EXPECT_EQ(rx.b, 2.5); EXPECT_EQ(ry.a, 4); EXPECT_EQ(ry.b, 3); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_storage.cpp000066400000000000000000000105371445372753700251520ustar00rootroot00000000000000#include #include #include #include #include #include namespace { using namespace testing; template T get(sol::state& lua, std::string luaCode) { return lua.safe_script("return " + luaCode).get(); } TEST(LuaUtilStorageTest, Basic) { sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); std::vector callbackCalls; sol::table callbackHiddenData(mLua, sol::create); callbackHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptsContainer::ScriptId{}; sol::table callback(mLua, sol::create); callback[1] = [&](const std::string& section, const sol::optional& key) { if (key) callbackCalls.push_back(section + "_" + *key); else callbackCalls.push_back(section + "_*"); }; callback[2] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); mLua["ro"]["subscribe"](mLua["ro"], callback); mLua.safe_script("mutable:set('x', 5)"); EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); EXPECT_EQ(get(mLua, "ro:get('x')"), 5); EXPECT_THROW(mLua.safe_script("ro:set('y', 3)"), std::exception); mLua.safe_script("t1 = mutable:asTable()"); mLua.safe_script("t2 = ro:asTable()"); EXPECT_EQ(get(mLua, "t1.x"), 5); EXPECT_EQ(get(mLua, "t2.x"), 5); mLua.safe_script("mutable:reset()"); EXPECT_TRUE(get(mLua, "ro:get('x') == nil")); mLua.safe_script("mutable:reset({x=4, y=7})"); EXPECT_EQ(get(mLua, "ro:get('x')"), 4); EXPECT_EQ(get(mLua, "ro:get('y')"), 7); EXPECT_THAT(callbackCalls, ::testing::ElementsAre("test_x", "test_*", "test_*")); } TEST(LuaUtilStorageTest, Table) { sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); mLua.safe_script("mutable:set('x', { y = 'abc', z = 7 })"); EXPECT_EQ(get(mLua, "mutable:get('x').z"), 7); EXPECT_THROW(mLua.safe_script("mutable:get('x').z = 3"), std::exception); EXPECT_NO_THROW(mLua.safe_script("mutable:getCopy('x').z = 3")); EXPECT_EQ(get(mLua, "mutable:get('x').z"), 7); EXPECT_EQ(get(mLua, "ro:get('x').z"), 7); EXPECT_EQ(get(mLua, "ro:get('x').y"), "abc"); } TEST(LuaUtilStorageTest, Saving) { sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); mLua["permanent"] = storage.getMutableSection("permanent"); mLua["temporary"] = storage.getMutableSection("temporary"); mLua.safe_script("temporary:removeOnExit()"); mLua.safe_script("permanent:set('x', 1)"); mLua.safe_script("temporary:set('y', 2)"); std::string tmpFile = (boost::filesystem::temp_directory_path() / "test_storage.bin").string(); storage.save(tmpFile); EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); EXPECT_EQ(get(mLua, "temporary:get('y')"), 2); storage.clearTemporaryAndRemoveCallbacks(); mLua["permanent"] = storage.getMutableSection("permanent"); mLua["temporary"] = storage.getMutableSection("temporary"); EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); EXPECT_TRUE(get(mLua, "temporary:get('y') == nil")); mLua.safe_script("permanent:set('x', 3)"); mLua.safe_script("permanent:set('z', 4)"); LuaUtil::LuaStorage storage2(mLua); storage2.load(tmpFile); mLua["permanent"] = storage2.getMutableSection("permanent"); mLua["temporary"] = storage2.getMutableSection("temporary"); EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); EXPECT_TRUE(get(mLua, "permanent:get('z') == nil")); EXPECT_TRUE(get(mLua, "temporary:get('y') == nil")); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_ui_content.cpp000066400000000000000000000110231445372753700256440ustar00rootroot00000000000000#include #include #include #include namespace { using namespace testing; struct LuaUiContentTest : Test { LuaUtil::LuaState mLuaState{ nullptr, nullptr }; sol::protected_function mNew; LuaUiContentTest() { mLuaState.addInternalLibSearchPath("resources/lua_libs"); mNew = LuaUi::loadContentConstructor(&mLuaState); } LuaUi::ContentView makeContent(sol::table source) { auto result = mNew.call(source); if (result.get_type() != sol::type::table) throw std::logic_error("Expected table"); return LuaUi::ContentView(result.get()); } sol::table makeTable() { return sol::table(mLuaState.sol(), sol::create); } sol::table makeTable(std::string name) { auto result = makeTable(); result["name"] = name; return result; } }; TEST_F(LuaUiContentTest, ProtectedMetatable) { mLuaState.sol()["makeContent"] = mNew; mLuaState.sol()["M"] = makeContent(makeTable()).getMetatable(); std::string testScript = R"( assert(not pcall(function() setmetatable(makeContent{}, {}) end), 'Metatable is not protected') assert(getmetatable(makeContent{}) == false, 'Metatable is not protected') )"; EXPECT_NO_THROW(mLuaState.sol().safe_script(testScript)); } TEST_F(LuaUiContentTest, Create) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); LuaUi::ContentView content = makeContent(table); EXPECT_EQ(content.size(), 3); } TEST_F(LuaUiContentTest, Insert) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); LuaUi::ContentView content = makeContent(table); content.insert(2, makeTable("inserted")); EXPECT_EQ(content.size(), 4); auto inserted = content.at("inserted"); auto index = content.indexOf(inserted); EXPECT_TRUE(index.has_value()); EXPECT_EQ(index.value(), 2); } TEST_F(LuaUiContentTest, MakeHole) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); LuaUi::ContentView content = makeContent(table); sol::table t = makeTable(); EXPECT_ANY_THROW(content.assign(3, t)); } TEST_F(LuaUiContentTest, WrongType) { auto table = makeTable(); table.add(makeTable()); table.add("a"); table.add(makeTable()); EXPECT_ANY_THROW(makeContent(table)); } TEST_F(LuaUiContentTest, NameAccess) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable("a")); LuaUi::ContentView content = makeContent(table); EXPECT_NO_THROW(content.at("a")); content.remove("a"); EXPECT_EQ(content.size(), 1); content.assign(content.size(), makeTable("b")); content.assign("b", makeTable()); EXPECT_ANY_THROW(content.at("b")); EXPECT_EQ(content.size(), 2); content.assign(content.size(), makeTable("c")); content.assign(content.size(), makeTable("c")); content.remove("c"); EXPECT_ANY_THROW(content.at("c")); } TEST_F(LuaUiContentTest, IndexOf) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); LuaUi::ContentView content = makeContent(table); auto child = makeTable(); content.assign(2, child); EXPECT_EQ(content.indexOf(child).value(), 2); EXPECT_TRUE(!content.indexOf(makeTable()).has_value()); } TEST_F(LuaUiContentTest, BoundsChecks) { auto table = makeTable(); LuaUi::ContentView content = makeContent(table); EXPECT_ANY_THROW(content.at(0)); EXPECT_EQ(content.size(), 0); content.assign(content.size(), makeTable()); EXPECT_EQ(content.size(), 1); content.assign(content.size(), makeTable()); EXPECT_EQ(content.size(), 2); content.assign(content.size(), makeTable()); EXPECT_EQ(content.size(), 3); EXPECT_ANY_THROW(content.at(3)); EXPECT_ANY_THROW(content.remove(3)); content.remove(2); EXPECT_EQ(content.size(), 2); EXPECT_ANY_THROW(content.at(2)); } } openmw-openmw-0.48.0/apps/openmw_test_suite/lua/test_utilpackage.cpp000066400000000000000000000256611445372753700260030ustar00rootroot00000000000000#include "gmock/gmock.h" #include #include #include #include "../testing_util.hpp" namespace { using namespace testing; template T get(sol::state& lua, const std::string& luaCode) { return lua.safe_script("return " + luaCode).get(); } std::string getAsString(sol::state& lua, std::string luaCode) { return LuaUtil::toString(lua.safe_script("return " + luaCode)); } TEST(LuaUtilPackageTest, Vector2) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(3, 4)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 3); EXPECT_FLOAT_EQ(get(lua, "v.y"), 4); EXPECT_EQ(get(lua, "tostring(v)"), "(3, 4)"); EXPECT_FLOAT_EQ(get(lua, "v:length()"), 5); EXPECT_FLOAT_EQ(get(lua, "v:length2()"), 25); EXPECT_FALSE(get(lua, "util.vector2(1, 2) == util.vector2(1, 3)")); EXPECT_TRUE(get(lua, "util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)")); EXPECT_TRUE(get(lua, "util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)")); EXPECT_TRUE(get(lua, "util.vector2(1, 2) == util.vector2(2, 4) / 2")); EXPECT_TRUE(get(lua, "util.vector2(1, 2) * 2 == util.vector2(2, 4)")); EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2) * v"), 17); EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2):dot(v)"), 17); EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault lua.safe_script("v2, len = v:normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 5); EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); lua.safe_script("ediv0 = util.vector2(1, 0):ediv(util.vector2(0, 0))"); EXPECT_TRUE(get(lua, "ediv0.x == math.huge and ediv0.y ~= ediv0.y")); EXPECT_TRUE(get(lua, "util.vector2(1, 2):emul(util.vector2(3, 4)) == util.vector2(3, 8)")); EXPECT_TRUE(get(lua, "util.vector2(4, 6):ediv(util.vector2(2, 3)) == util.vector2(2, 2)")); } TEST(LuaUtilPackageTest, Vector3) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector3(5, 12, 13)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13)"); EXPECT_EQ(getAsString(lua, "v"), "(5, 12, 13)"); EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length()"), 5); EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length2()"), 25); EXPECT_FALSE(get(lua, "util.vector3(1, 2, 3) == util.vector3(1, 3, 2)")); EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)")); EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)")); EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2")); EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)")); EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1) * v"), 5*3 + 12*2 + 13*1); EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1):dot(v)"), 5*3 + 12*2 + 13*1); EXPECT_TRUE(get(lua, "util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)")); EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata"); lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 5); EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); lua.safe_script("ediv0 = util.vector3(1, 1, 1):ediv(util.vector3(0, 0, 0))"); EXPECT_TRUE(get(lua, "ediv0.z == math.huge")); EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3):emul(util.vector3(3, 4, 5)) == util.vector3(3, 8, 15)")); EXPECT_TRUE(get(lua, "util.vector3(4, 6, 8):ediv(util.vector3(2, 3, 4)) == util.vector3(2, 2, 2)")); } TEST(LuaUtilPackageTest, Vector4) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector4(5, 12, 13, 15)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); EXPECT_FLOAT_EQ(get(lua, "v.w"), 15); EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13, 15)"); EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length()"), 5); EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length2()"), 25); EXPECT_FALSE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(1, 3, 2, 4)")); EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) + util.vector4(2, 5, 1, 2) == util.vector4(3, 7, 4, 6)")); EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) - util.vector4(2, 5, 1, 7) == -util.vector4(1, 3, -2, 3)")); EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(2, 4, 6, 8) / 2")); EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) * 2 == util.vector4(2, 4, 6, 8)")); EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4) * v"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4):dot(v)"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); lua.safe_script("v2, len = util.vector4(3, 0, 0, 4):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 5); EXPECT_TRUE(get(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)")); lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); lua.safe_script("ediv0 = util.vector4(1, 1, 1, -1):ediv(util.vector4(0, 0, 0, 0))"); EXPECT_TRUE(get(lua, "ediv0.w == -math.huge")); EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4):emul(util.vector4(3, 4, 5, 6)) == util.vector4(3, 8, 15, 24)")); EXPECT_TRUE(get(lua, "util.vector4(4, 6, 8, 9):ediv(util.vector4(2, 3, 4, 3)) == util.vector4(2, 2, 2, 3)")); } TEST(LuaUtilPackageTest, Color) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)"); EXPECT_EQ(get(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)"); lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)"); EXPECT_EQ(get(lua, "tostring(blue)"), "(0, 1, 0, 1)"); lua.safe_script("red = util.color.hex('ff0000')"); EXPECT_EQ(get(lua, "tostring(red)"), "(1, 0, 0, 1)"); lua.safe_script("green = util.color.hex('00FF00')"); EXPECT_EQ(get(lua, "tostring(green)"), "(0, 1, 0, 1)"); lua.safe_script("darkRed = util.color.hex('a01112')"); EXPECT_EQ(get(lua, "darkRed:asHex()"), "a01112"); EXPECT_TRUE(get(lua, "green:asRgba() == util.vector4(0, 1, 0, 1)")); EXPECT_TRUE(get(lua, "red:asRgb() == util.vector3(1, 0, 0)")); } TEST(LuaUtilPackageTest, Transform) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua["T"] = lua["util"]["transform"]; lua["v"] = lua["util"]["vector3"]; EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index"); EXPECT_EQ(getAsString(lua, "T.identity * v(3, 4, 5)"), "(3, 4, 5)"); EXPECT_EQ(getAsString(lua, "T.move(1, 2, 3) * v(3, 4, 5)"), "(4, 6, 8)"); EXPECT_EQ(getAsString(lua, "T.scale(1, -2, 3) * v(3, 4, 5)"), "(3, -8, 15)"); EXPECT_EQ(getAsString(lua, "T.scale(v(1, 2, 3)) * v(3, 4, 5)"), "(3, 8, 15)"); lua.safe_script("moveAndScale = T.move(v(1, 2, 3)) * T.scale(0.5, 1, 0.5) * T.move(10, 20, 30)"); EXPECT_EQ(getAsString(lua, "moveAndScale * v(0, 0, 0)"), "(6, 22, 18)"); EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); EXPECT_THAT(getAsString(lua, "moveAndScale"), AllOf(StartsWith("TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) "), EndsWith(" }"))); EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); lua.safe_script("rx = T.rotateX(-math.pi / 2)"); lua.safe_script("ry = T.rotateY(-math.pi / 2)"); lua.safe_script("rz = T.rotateZ(-math.pi / 2)"); EXPECT_LT(get(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6); EXPECT_LT(get(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6); EXPECT_LT(get(lua, "(rz * v(1, 2, 3) - v(-2, 1, 3)):length()"), 1e-6); lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(math.pi / 4)"); EXPECT_THAT(getAsString(lua, "rot"), HasSubstr("TransformQ")); EXPECT_LT(get(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6); EXPECT_LT(get(lua, "(rot * rot:inverse() * v(1, 0, 0) - v(1, 0, 0)):length()"), 1e-6); lua.safe_script("rz_move_rx = rz * T.move(0, 3, 0) * rx"); EXPECT_LT(get(lua, "(rz_move_rx * v(1, 2, 3) - v(0, 1, 2)):length()"), 1e-6); EXPECT_LT(get(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6); } TEST(LuaUtilPackageTest, UtilityFunctions) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); EXPECT_FLOAT_EQ(get(lua, "v.x"), -0.5f); EXPECT_FLOAT_EQ(get(lua, "v.y"), 0.86602539f); EXPECT_FLOAT_EQ(get(lua, "util.normalizeAngle(math.pi * 10 + 0.1)"), 0.1f); EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1f); EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5f); lua.safe_script("t = util.makeReadOnly({x = 1})"); EXPECT_FLOAT_EQ(get(lua, "t.x"), 1); EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/misc/000077500000000000000000000000001445372753700221075ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/misc/compression.cpp000066400000000000000000000015461445372753700251620ustar00rootroot00000000000000#include #include #include #include #include namespace { using namespace testing; using namespace Misc; TEST(MiscCompressionTest, compressShouldAddPrefixWithDataSize) { const std::vector data(1234); const std::vector compressed = compress(data); int size = 0; std::memcpy(&size, compressed.data(), sizeof(size)); EXPECT_EQ(size, data.size()); } TEST(MiscCompressionTest, decompressIsInverseToCompress) { const std::vector data(1024); const std::vector compressed = compress(data); EXPECT_LT(compressed.size(), data.size()); const std::vector decompressed = decompress(compressed); EXPECT_EQ(decompressed, data); } } openmw-openmw-0.48.0/apps/openmw_test_suite/misc/progressreporter.cpp000066400000000000000000000021001445372753700262330ustar00rootroot00000000000000#include #include #include #include namespace { using namespace testing; using namespace Misc; struct ReportMock { MOCK_METHOD(void, call, (std::size_t, std::size_t), ()); }; struct Report { StrictMock* mImpl; void operator()(std::size_t provided, std::size_t expected) { mImpl->call(provided, expected); } }; TEST(MiscProgressReporterTest, shouldCallReportWhenPassedInterval) { StrictMock report; EXPECT_CALL(report, call(13, 42)).WillOnce(Return()); ProgressReporter reporter(std::chrono::steady_clock::duration(0), Report {&report}); reporter(13, 42); } TEST(MiscProgressReporterTest, shouldNotCallReportWhenIntervalIsNotPassed) { StrictMock report; EXPECT_CALL(report, call(13, 42)).Times(0); ProgressReporter reporter(std::chrono::seconds(1000), Report {&report}); reporter(13, 42); } } openmw-openmw-0.48.0/apps/openmw_test_suite/misc/test_endianness.cpp000066400000000000000000000060501445372753700260020ustar00rootroot00000000000000#include #include "components/misc/endianness.hpp" struct EndiannessTest : public ::testing::Test {}; TEST_F(EndiannessTest, test_swap_endianness_inplace1) { uint8_t zero=0x00; uint8_t ff=0xFF; uint8_t fortytwo=0x42; uint8_t half=128; Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x00); Misc::swapEndiannessInplace(ff); EXPECT_EQ(ff, 0xFF); Misc::swapEndiannessInplace(fortytwo); EXPECT_EQ(fortytwo, 0x42); Misc::swapEndiannessInplace(half); EXPECT_EQ(half, 128); } TEST_F(EndiannessTest, test_swap_endianness_inplace2) { uint16_t zero = 0x0000; uint16_t ffff = 0xFFFF; uint16_t n12 = 0x0102; uint16_t fortytwo = 0x0042; Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000u); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000u); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFu); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFu); Misc::swapEndiannessInplace(n12); EXPECT_EQ(n12, 0x0201u); Misc::swapEndiannessInplace(n12); EXPECT_EQ(n12, 0x0102u); Misc::swapEndiannessInplace(fortytwo); EXPECT_EQ(fortytwo, 0x4200u); Misc::swapEndiannessInplace(fortytwo); EXPECT_EQ(fortytwo, 0x0042u); } TEST_F(EndiannessTest, test_swap_endianness_inplace4) { uint32_t zero = 0x00000000; uint32_t n1234 = 0x01020304; uint32_t ffff = 0xFFFFFFFF; Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x00000000u); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x00000000u); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x04030201u); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x01020304u); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFFFFFu); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFFFFFFu); } TEST_F(EndiannessTest, test_swap_endianness_inplace8) { uint64_t zero = 0x0000'0000'0000'0000; uint64_t n1234 = 0x0102'0304'0506'0708; uint64_t ffff = 0xFFFF'FFFF'FFFF'FFFF; Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000'0000'0000'0000u); Misc::swapEndiannessInplace(zero); EXPECT_EQ(zero, 0x0000'0000'0000'0000u); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFFu); Misc::swapEndiannessInplace(ffff); EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFFu); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x0807'0605'0403'0201u); Misc::swapEndiannessInplace(n1234); EXPECT_EQ(n1234, 0x0102'0304'0506'0708u); } TEST_F(EndiannessTest, test_swap_endianness_inplace_float) { const uint32_t original = 0x4023d70a; const uint32_t expected = 0x0ad72340; float number; memcpy(&number, &original, sizeof(original)); Misc::swapEndiannessInplace(number); EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected))); } TEST_F(EndiannessTest, test_swap_endianness_inplace_double) { const uint64_t original = 0x040047ae147ae147ul; const uint64_t expected = 0x47e17a14ae470004ul; double number; memcpy(&number, &original, sizeof(original)); Misc::swapEndiannessInplace(number); EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected)) ); } openmw-openmw-0.48.0/apps/openmw_test_suite/misc/test_resourcehelpers.cpp000066400000000000000000000047661445372753700271010ustar00rootroot00000000000000#include #include "components/misc/resourcehelpers.hpp" #include "../testing_util.hpp" namespace { using namespace Misc::ResourceHelpers; TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ {"sound/bar.wav", nullptr} }); EXPECT_EQ(correctSoundPath("sound/bar.wav", mVFS.get()), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ {"sound/foo.mp3", nullptr} }); EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ }); EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); } TEST(CorrectSoundPath, correct_path_normalize_paths) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ }); EXPECT_EQ(correctSoundPath("sound\\foo.wav", mVFS.get()), "sound/foo.mp3"); EXPECT_EQ(correctSoundPath("SOUND\\foo.WAV", mVFS.get()), "SOUND/foo.mp3"); } namespace { std::string checkChangeExtensionToDds(std::string path) { changeExtensionToDds(path); return path; } } TEST(ChangeExtensionToDds, original_extension_with_same_size_as_dds) { EXPECT_EQ(checkChangeExtensionToDds("texture/bar.tga"), "texture/bar.dds"); } TEST(ChangeExtensionToDds, original_extension_greater_than_dds) { EXPECT_EQ(checkChangeExtensionToDds("texture/bar.jpeg"), "texture/bar.dds"); } TEST(ChangeExtensionToDds, original_extension_smaller_than_dds) { EXPECT_EQ(checkChangeExtensionToDds("texture/bar.xx"), "texture/bar.dds"); } TEST(ChangeExtensionToDds, does_not_change_dds_extension) { std::string path = "texture/bar.dds"; EXPECT_FALSE(changeExtensionToDds(path)); } TEST(ChangeExtensionToDds, does_not_change_when_no_extension) { std::string path = "texture/bar"; EXPECT_FALSE(changeExtensionToDds(path)); } TEST(ChangeExtensionToDds, change_when_there_is_an_extension) { std::string path = "texture/bar.jpeg"; EXPECT_TRUE(changeExtensionToDds(path)); } } openmw-openmw-0.48.0/apps/openmw_test_suite/misc/test_stringops.cpp000066400000000000000000000122751445372753700257110ustar00rootroot00000000000000#include #include "components/misc/stringops.hpp" #include "components/misc/algorithm.hpp" #include #include #include struct PartialBinarySearchTest : public ::testing::Test { protected: std::vector mDataVec; void SetUp() override { const char* data[] = { "Head", "Chest", "Tri Head", "Tri Chest", "Bip01", "Tri Bip01" }; mDataVec = std::vector(data, data+sizeof(data)/sizeof(data[0])); std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess); } bool matches(const std::string& keyword) { return Misc::partialBinarySearch(mDataVec.begin(), mDataVec.end(), keyword) != mDataVec.end(); } }; TEST_F(PartialBinarySearchTest, partial_binary_search_test) { EXPECT_TRUE( matches("Head 01") ); EXPECT_TRUE( matches("Head") ); EXPECT_TRUE( matches("Tri Head 01") ); EXPECT_TRUE( matches("Tri Head") ); EXPECT_TRUE( matches("tri head") ); EXPECT_TRUE( matches("Tri bip01") ); EXPECT_TRUE( matches("bip01") ); EXPECT_TRUE( matches("bip01 head") ); EXPECT_TRUE( matches("Bip01 L Hand") ); EXPECT_TRUE( matches("BIp01 r Clavicle") ); EXPECT_TRUE( matches("Bip01 SpiNe1") ); EXPECT_FALSE( matches("") ); EXPECT_FALSE( matches("Node Bip01") ); EXPECT_FALSE( matches("Hea") ); EXPECT_FALSE( matches(" Head") ); EXPECT_FALSE( matches("Tri Head") ); } TEST_F (PartialBinarySearchTest, ci_test) { EXPECT_TRUE (Misc::StringUtils::lowerCase("ASD") == "asd"); // test to make sure system locale is not used std::string unicode1 = "\u04151 \u0418"; // CYRILLIC CAPITAL LETTER IE, CYRILLIC CAPITAL LETTER I EXPECT_TRUE( Misc::StringUtils::lowerCase(unicode1) == unicode1 ); } namespace { using ::Misc::StringUtils; using namespace ::testing; template struct MiscStringUtilsCiEqualEmptyTest : Test {}; TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest); TYPED_TEST_P(MiscStringUtilsCiEqualEmptyTest, empty_strings_should_be_equal) { EXPECT_TRUE(StringUtils::ciEqual(typename TypeParam::first_type {}, typename TypeParam::second_type {})); } REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest, empty_strings_should_be_equal ); using EmptyStringTypePairsTypes = Types< std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair >; INSTANTIATE_TYPED_TEST_SUITE_P(EmptyStringTypePairs, MiscStringUtilsCiEqualEmptyTest, EmptyStringTypePairsTypes); template struct MiscStringUtilsCiEqualNotEmptyTest : Test {}; TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest); using RawValue = const char[4]; constexpr RawValue foo = "f0#"; constexpr RawValue fooUpper = "F0#"; constexpr RawValue bar = "bar"; template using Value = std::conditional_t, RawValue&, T>; TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_should_be_equal) { const Value a {foo}; const Value b {foo}; EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; } TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_with_different_case_sensetivity_should_be_equal) { const Value a {foo}; const Value b {fooUpper}; EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; } TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, different_strings_content_should_not_be_equal) { const Value a {foo}; const Value b {bar}; EXPECT_FALSE(StringUtils::ciEqual(a, b)) << a << "\n" << b; } REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_should_be_equal, same_strings_with_different_case_sensetivity_should_be_equal, different_strings_content_should_not_be_equal ); using NotEmptyStringTypePairsTypes = Types< std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair >; INSTANTIATE_TYPED_TEST_SUITE_P(NotEmptyStringTypePairs, MiscStringUtilsCiEqualNotEmptyTest, NotEmptyStringTypePairsTypes); TEST(MiscStringUtilsCiEqualTest, string_with_different_length_should_not_be_equal) { EXPECT_FALSE(StringUtils::ciEqual(std::string("a"), std::string("aa"))); } } openmw-openmw-0.48.0/apps/openmw_test_suite/mwdialogue/000077500000000000000000000000001445372753700233115ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp000066400000000000000000000122171445372753700277310ustar00rootroot00000000000000#include #include "apps/openmw/mwdialogue/keywordsearch.hpp" struct KeywordSearchTest : public ::testing::Test { protected: void SetUp() override { } void TearDown() override { } }; TEST_F(KeywordSearchTest, keyword_test_conflict_resolution) { // test to make sure the longest keyword in a chain of conflicting keywords gets chosen MWDialogue::KeywordSearch search; search.seed("foo bar", 0); search.seed("bar lock", 0); search.seed("lock switch", 0); std::string text = "foo bar lock switch"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); // Should contain: "foo bar", "lock switch" EXPECT_EQ (matches.size() , 2); EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "foo bar"); EXPECT_EQ (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) , "lock switch"); } TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) { MWDialogue::KeywordSearch search; search.seed("the dwemer", 0); search.seed("dwemer language", 0); std::string text = "the dwemer language"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ (matches.size() , 1); EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "dwemer language"); } TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) { // testing that the longest keyword is chosen, rather than maximizing the // amount of highlighted characters by highlighting the first and last keyword MWDialogue::KeywordSearch search; search.seed("foo bar", 0); search.seed("bar lock", 0); search.seed("lock so", 0); std::string text = "foo bar lock so"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ (matches.size() , 1); EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "bar lock"); } TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) { // We make sure that the search works well even if the character is not ASCII MWDialogue::KeywordSearch search; search.seed("états", 0); search.seed("ïrradiés", 0); search.seed("ça nous déçois", 0); search.seed("ois", 0); std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés et ça nous déçois"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ (matches.size() , 3); EXPECT_EQ (std::string( matches[0].mBeg, matches[0].mEnd) , "états"); EXPECT_EQ (std::string( matches[1].mBeg, matches[1].mEnd) , "ïrradiés"); EXPECT_EQ (std::string( matches[2].mBeg, matches[2].mEnd) , "ça nous déçois"); } TEST_F(KeywordSearchTest, keyword_test_non_alpha_non_whitespace_word_begin) { // We make sure that the search works well even if the separator is not a whitespace MWDialogue::KeywordSearch search; search.seed("Report to caius cosades", 0); std::string text = "I was told to \"Report to caius cosades\""; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ(matches.size(), 1); EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Report to caius cosades"); } TEST_F(KeywordSearchTest, keyword_test_russian_non_ascii_before) { // We make sure that the search works well even if the separator is not a whitespace with russian chars MWDialogue::KeywordSearch search; search.seed("Доложить Каю Косадесу", 0); std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели «Доложить Каю Косадесу»? О чем вы говорите?"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ(matches.size(), 1); EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); } TEST_F(KeywordSearchTest, keyword_test_russian_ascii_before) { // We make sure that the search works well even if the separator is not a whitespace with russian chars MWDialogue::KeywordSearch search; search.seed("Доложить Каю Косадесу", 0); std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели 'Доложить Каю Косадесу'? О чем вы говорите?"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); EXPECT_EQ(matches.size(), 1); EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); } openmw-openmw-0.48.0/apps/openmw_test_suite/mwscript/000077500000000000000000000000001445372753700230245ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/mwscript/test_scripts.cpp000066400000000000000000000456671445372753700263000ustar00rootroot00000000000000#include #include #include "test_utils.hpp" namespace { struct MWScriptTest : public ::testing::Test { MWScriptTest() : mErrorHandler(), mParser(mErrorHandler, mCompilerContext) {} std::optional compile(const std::string& scriptBody, bool shouldFail = false) { mParser.reset(); mErrorHandler.reset(); std::istringstream input(scriptBody); Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); scanner.scan(mParser); if(mErrorHandler.isGood()) { std::vector code; mParser.getCode(code); return CompiledScript(code, mParser.getLocals()); } else if(!shouldFail) logErrors(); return {}; } void logErrors() { for(const auto& [error, loc] : mErrorHandler.getErrors()) { std::cout << error; if(loc.mLine) std::cout << " at line" << loc.mLine << " column " << loc.mColumn << " (" << loc.mLiteral << ")"; std::cout << "\n"; } } void registerExtensions() { Compiler::registerExtensions(mExtensions); mCompilerContext.setExtensions(&mExtensions); } void run(const CompiledScript& script, TestInterpreterContext& context) { mInterpreter.run(&script.mByteCode[0], static_cast(script.mByteCode.size()), context); } template void installOpcode(int code, TArgs&& ...args) { mInterpreter.installSegment5(code, std::forward(args)...); } protected: void SetUp() override { Interpreter::installOpcodes(mInterpreter); } void TearDown() override {} private: TestErrorHandler mErrorHandler; TestCompilerContext mCompilerContext; Compiler::FileParser mParser; Compiler::Extensions mExtensions; Interpreter::Interpreter mInterpreter; }; const std::string sScript1 = R"mwscript(Begin basic_logic ; Comment short one short two set one to two if ( one == two ) set one to 1 elseif ( two == 1 ) set one to 2 else set one to 3 endif while ( one < two ) set one to ( one + 1 ) endwhile End)mwscript"; const std::string sScript2 = R"mwscript(Begin addtopic AddTopic "OpenMW Unit Test" End)mwscript"; const std::string sScript3 = R"mwscript(Begin math short a short b short c short d short e set b to ( a + 1 ) set c to ( a - 1 ) set d to ( b * c ) set e to ( d / a ) End)mwscript"; // https://forum.openmw.org/viewtopic.php?f=6&t=2262 const std::string sScript4 = R"mwscript(Begin scripting_once_again player -> addSpell "fire_bite", 645 PositionCell "Rabenfels, Taverne" 4480.000 3968.000 15820.000 0 End)mwscript"; const std::string sIssue587 = R"mwscript(Begin stalresetScript End stalreset Script)mwscript"; const std::string sIssue677 = R"mwscript(Begin _ase_dtree_dtree-owls End)mwscript"; const std::string sIssue685 = R"mwscript(Begin issue685 Choice: "Sicher. Hier, nehmt." 1 "Nein, ich denke nicht. Tut mir Leid." 2 StartScript GetPCGold End)mwscript"; const std::string sIssue694 = R"mwscript(Begin issue694 float timer if ( timer < .1 ) endif End)mwscript"; const std::string sIssue1062 = R"mwscript(Begin issue1026 short end End)mwscript"; const std::string sIssue1430 = R"mwscript(Begin issue1430 short var If ( menumode == 1 ) Player->AddItem "fur_boots", 1 Player->Equip "iron battle axe", 1 player->addspell "fire bite", 645 player->additem "ring_keley", 1, endif End)mwscript"; const std::string sIssue1593 = R"mwscript(Begin changeWater_-550_400 End)mwscript"; const std::string sIssue1730 = R"mwscript(Begin 4LOM_Corprusarium_Guards End)mwscript"; const std::string sIssue1767 = R"mwscript(Begin issue1767 player->GetPcRank "temple" End)mwscript"; const std::string sIssue2185 = R"mwscript(Begin issue2185 short a short b short eq short gte short lte short ne set eq to 0 if ( a == b ) set eq to ( eq + 1 ) endif if ( a = = b ) set eq to ( eq + 1 ) endif set gte to 0 if ( a >= b ) set gte to ( gte + 1 ) endif if ( a > = b ) set gte to ( gte + 1 ) endif set lte to 0 if ( a <= b ) set lte to ( lte + 1 ) endif if ( a < = b ) set lte to ( lte + 1 ) endif set ne to 0 if ( a != b ) set ne to ( ne + 1 ) endif if ( a ! = b ) set ne to ( ne + 1 ) endif End)mwscript"; const std::string sIssue2206 = R"mwscript(Begin issue2206 Choice ."Sklavin kaufen." 1 "Lebt wohl." 2 Choice Choice "Insister pour qu’il vous réponde." 6 "Le prier de vous accorder un peu de son temps." 6 " Le menacer de révéler qu'il prélève sa part sur les bénéfices de la mine d’ébonite." 7 End)mwscript"; const std::string sIssue2207 = R"mwscript(Begin issue2207 PositionCell -35 –473 -248 0 "Skaal-Dorf, Die Große Halle" End)mwscript"; const std::string sIssue2794 = R"mwscript(Begin issue2794 if ( player->"getlevel" == 1 ) ; do something endif End)mwscript"; const std::string sIssue2830 = R"mwscript(Begin issue2830 AddItem "if" 1 AddItem "endif" 1 GetItemCount "begin" End)mwscript"; const std::string sIssue2991 = R"mwscript(Begin issue2991 MessageBox "OnActivate" messagebox "messagebox" messagebox "if" messagebox "tcl" End)mwscript"; const std::string sIssue3006 = R"mwscript(Begin issue3006 short a if ( a == 1 ) set a to 2 else set a to 3 endif End)mwscript"; const std::string sIssue3725 = R"mwscript(Begin issue3725 onactivate if onactivate ; do something endif End)mwscript"; const std::string sIssue3744 = R"mwscript(Begin issue3744 short a short b short c set c to 0 if ( a => b ) set c to ( c + 1 ) endif if ( a =< b ) set c to ( c + 1 ) endif if ( a = b ) set c to ( c + 1 ) endif if ( a == b ) set c to ( c + 1 ) endif End)mwscript"; const std::string sIssue3836 = R"mwscript(Begin issue3836 MessageBox " Membership Level: %.0f Account Balance: %.0f Your Gold: %.0f Interest Rate: %.3f Service Charge Rate: %.3f Total Service Charges: %.0f Total Interest Earned: %.0f " Membership BankAccount YourGold InterestRate ServiceRate TotalServiceCharges TotalInterestEarned End)mwscript"; const std::string sIssue3846 = R"mwscript(Begin issue3846 Addtopic -spells... Addtopic -magicka... End)mwscript"; const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 End)mwscript"; const std::string sIssue4451 = R"mwscript(Begin, GlassDisplayScript ;[Script body] End, GlassDisplayScript)mwscript"; const std::string sIssue4597 = R"mwscript(Begin issue4597 short a short b short c short d set c to 0 set d to 0 if ( a <> b ) set c to ( c + 1 ) endif if ( a << b ) set c to ( c + 1 ) endif if ( a < b ) set c to ( c + 1 ) endif if ( a >< b ) set d to ( d + 1 ) endif if ( a >> b ) set d to ( d + 1 ) endif if ( a > b ) set d to ( d + 1 ) endif End)mwscript"; const std::string sIssue4598 = R"mwscript(Begin issue4598 StartScript kal_S_Pub_Jejubãr_Faraminos End)mwscript"; const std::string sIssue4803 = R"mwscript( -- +-Begin issue4803 End)mwscript"; const std::string sIssue4867 = R"mwscript(Begin issue4867 float PcMagickaMult : The gameplay setting fPcBaseMagickaMult - 1.0000 End)mwscript"; const std::string sIssue4888 = R"mwscript(Begin issue4888 if (player->GameHour == 10) set player->GameHour to 20 endif End)mwscript"; const std::string sIssue5087 = R"mwscript(Begin Begin player->sethealth 0 stopscript Begin End Begin)mwscript"; const std::string sIssue5097 = R"mwscript(Begin issue5097 setscale "0.3" End)mwscript"; const std::string sIssue5345 = R"mwscript(Begin issue5345 StartScript DN_MinionDrain_s" End)mwscript"; const std::string sIssue6066 = R"mwscript(Begin issue6066 addtopic "return" End)mwscript"; const std::string sIssue6282 = R"mwscript(Begin 11AA_LauraScript7.5 End)mwscript"; const std::string sIssue6363 = R"mwscript(Begin issue6363 short 1 if ( "1" == 1 ) PositionCell 0 1 2 3 4 5 "Morrowland" endif set 1 to 42 End)mwscript"; const std::string sIssue6380 = R"mwscript(,Begin,issue6380, ,short,a ,set,a,to,,,,(a,+1) messagebox,"this is a %g",a ,End,)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) { EXPECT_THROW(compile("this is not a valid script", true), Compiler::SourceException); } TEST_F(MWScriptTest, mwscript_test_compilation) { EXPECT_FALSE(!compile(sScript1)); } TEST_F(MWScriptTest, mwscript_test_no_extensions) { EXPECT_THROW(compile(sScript2, true), Compiler::SourceException); } TEST_F(MWScriptTest, mwscript_test_function) { registerExtensions(); bool failed = true; if(const auto script = compile(sScript2)) { class AddTopic : public Interpreter::Opcode0 { bool& mFailed; public: AddTopic(bool& failed) : mFailed(failed) {} void execute(Interpreter::Runtime& runtime) { const auto topic = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); mFailed = false; EXPECT_EQ(topic, "OpenMW Unit Test"); } }; installOpcode(Compiler::Dialogue::opcodeAddTopic, failed); TestInterpreterContext context; run(*script, context); } if(failed) { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_math) { if(const auto script = compile(sScript3)) { struct Algorithm { int a; int b; int c; int d; int e; void run(int input) { a = input; b = a + 1; c = a - 1; d = b * c; e = d / a; } void test(const TestInterpreterContext& context) const { EXPECT_EQ(a, context.getLocalShort(0)); EXPECT_EQ(b, context.getLocalShort(1)); EXPECT_EQ(c, context.getLocalShort(2)); EXPECT_EQ(d, context.getLocalShort(3)); EXPECT_EQ(e, context.getLocalShort(4)); } } algorithm; TestInterpreterContext context; for(int i = 1; i < 1000; ++i) { context.setLocalShort(0, i); run(*script, context); algorithm.run(i); algorithm.test(context); } } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_forum_thread) { registerExtensions(); EXPECT_FALSE(!compile(sScript4)); } TEST_F(MWScriptTest, mwscript_test_587) { EXPECT_FALSE(!compile(sIssue587)); } TEST_F(MWScriptTest, mwscript_test_677) { EXPECT_FALSE(!compile(sIssue677)); } TEST_F(MWScriptTest, mwscript_test_685) { registerExtensions(); EXPECT_FALSE(!compile(sIssue685)); } TEST_F(MWScriptTest, mwscript_test_694) { EXPECT_FALSE(!compile(sIssue694)); } TEST_F(MWScriptTest, mwscript_test_1062) { if(const auto script = compile(sIssue1062)) { EXPECT_EQ(script->mLocals.getIndex("end"), 0); } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_1430) { registerExtensions(); EXPECT_FALSE(!compile(sIssue1430)); } TEST_F(MWScriptTest, mwscript_test_1593) { EXPECT_FALSE(!compile(sIssue1593)); } TEST_F(MWScriptTest, mwscript_test_1730) { EXPECT_FALSE(!compile(sIssue1730)); } TEST_F(MWScriptTest, mwscript_test_1767) { registerExtensions(); EXPECT_FALSE(!compile(sIssue1767)); } TEST_F(MWScriptTest, mwscript_test_2185) { if(const auto script = compile(sIssue2185)) { TestInterpreterContext context; for(int a = 0; a < 100; ++a) { for(int b = 0; b < 100; ++b) { context.setLocalShort(0, a); context.setLocalShort(1, b); run(*script, context); EXPECT_EQ(context.getLocalShort(2), a == b ? 2 : 0); EXPECT_EQ(context.getLocalShort(3), a >= b ? 2 : 0); EXPECT_EQ(context.getLocalShort(4), a <= b ? 2 : 0); EXPECT_EQ(context.getLocalShort(5), a != b ? 2 : 0); } } } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_2206) { registerExtensions(); EXPECT_FALSE(!compile(sIssue2206)); } TEST_F(MWScriptTest, mwscript_test_2207) { registerExtensions(); EXPECT_FALSE(!compile(sIssue2207)); } TEST_F(MWScriptTest, mwscript_test_2794) { registerExtensions(); EXPECT_FALSE(!compile(sIssue2794)); } TEST_F(MWScriptTest, mwscript_test_2830) { registerExtensions(); EXPECT_FALSE(!compile(sIssue2830)); } TEST_F(MWScriptTest, mwscript_test_2991) { registerExtensions(); EXPECT_FALSE(!compile(sIssue2991)); } TEST_F(MWScriptTest, mwscript_test_3006) { if(const auto script = compile(sIssue3006)) { TestInterpreterContext context; context.setLocalShort(0, 0); run(*script, context); EXPECT_EQ(context.getLocalShort(0), 0); context.setLocalShort(0, 1); run(*script, context); EXPECT_EQ(context.getLocalShort(0), 2); } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_3725) { registerExtensions(); EXPECT_FALSE(!compile(sIssue3725)); } TEST_F(MWScriptTest, mwscript_test_3744) { if(const auto script = compile(sIssue3744)) { TestInterpreterContext context; for(int a = 0; a < 100; ++a) { for(int b = 0; b < 100; ++b) { context.setLocalShort(0, a); context.setLocalShort(1, b); run(*script, context); EXPECT_EQ(context.getLocalShort(2), a == b ? 4 : 0); } } } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_3836) { registerExtensions(); EXPECT_FALSE(!compile(sIssue3836)); } TEST_F(MWScriptTest, mwscript_test_3846) { registerExtensions(); if(const auto script = compile(sIssue3846)) { std::vector topics = { "-spells...", "-magicka..." }; class AddTopic : public Interpreter::Opcode0 { std::vector& mTopics; public: AddTopic(std::vector& topics) : mTopics(topics) {} void execute(Interpreter::Runtime& runtime) { const auto topic = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); EXPECT_EQ(topic, mTopics[0]); mTopics.erase(mTopics.begin()); } }; installOpcode(Compiler::Dialogue::opcodeAddTopic, topics); TestInterpreterContext context; run(*script, context); EXPECT_TRUE(topics.empty()); } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_4061) { EXPECT_FALSE(!compile(sIssue4061)); } TEST_F(MWScriptTest, mwscript_test_4451) { EXPECT_FALSE(!compile(sIssue4451)); } TEST_F(MWScriptTest, mwscript_test_4597) { if(const auto script = compile(sIssue4597)) { TestInterpreterContext context; for(int a = 0; a < 100; ++a) { for(int b = 0; b < 100; ++b) { context.setLocalShort(0, a); context.setLocalShort(1, b); run(*script, context); EXPECT_EQ(context.getLocalShort(2), a < b ? 3 : 0); EXPECT_EQ(context.getLocalShort(3), a > b ? 3 : 0); } } } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_4598) { registerExtensions(); EXPECT_FALSE(!compile(sIssue4598)); } TEST_F(MWScriptTest, mwscript_test_4803) { EXPECT_FALSE(!compile(sIssue4803)); } TEST_F(MWScriptTest, mwscript_test_4867) { EXPECT_FALSE(!compile(sIssue4867)); } TEST_F(MWScriptTest, mwscript_test_4888) { EXPECT_FALSE(!compile(sIssue4888)); } TEST_F(MWScriptTest, mwscript_test_5087) { registerExtensions(); EXPECT_FALSE(!compile(sIssue5087)); } TEST_F(MWScriptTest, mwscript_test_5097) { registerExtensions(); EXPECT_FALSE(!compile(sIssue5097)); } TEST_F(MWScriptTest, mwscript_test_5345) { registerExtensions(); EXPECT_FALSE(!compile(sIssue5345)); } TEST_F(MWScriptTest, mwscript_test_6066) { registerExtensions(); EXPECT_FALSE(!compile(sIssue6066)); } TEST_F(MWScriptTest, mwscript_test_6282) { EXPECT_FALSE(!compile(sIssue6282)); } TEST_F(MWScriptTest, mwscript_test_6363) { registerExtensions(); if(const auto script = compile(sIssue6363)) { class PositionCell : public Interpreter::Opcode0 { bool& mRan; public: PositionCell(bool& ran) : mRan(ran) {} void execute(Interpreter::Runtime& runtime) { mRan = true; } }; bool ran = false; installOpcode(Compiler::Transformation::opcodePositionCell, ran); TestInterpreterContext context; context.setLocalShort(0, 0); run(*script, context); EXPECT_FALSE(ran); ran = false; context.setLocalShort(0, 1); run(*script, context); EXPECT_TRUE(ran); } else { FAIL(); } } TEST_F(MWScriptTest, mwscript_test_6380) { EXPECT_FALSE(!compile(sIssue6380)); } }openmw-openmw-0.48.0/apps/openmw_test_suite/mwscript/test_utils.hpp000066400000000000000000000205551445372753700257430ustar00rootroot00000000000000#ifndef MWSCRIPT_TESTING_UTIL_H #define MWSCRIPT_TESTING_UTIL_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { class TestCompilerContext : public Compiler::Context { public: bool canDeclareLocals() const override { return true; } char getGlobalType(const std::string& name) const override { return ' '; } std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } bool isId(const std::string& name) const override { return Misc::StringUtils::ciEqual(name, "player"); } }; class TestErrorHandler : public Compiler::ErrorHandler { std::vector> mErrors; void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override { if(type == Compiler::ErrorHandler::ErrorMessage) mErrors.emplace_back(message, loc); } void report(const std::string& message, Compiler::ErrorHandler::Type type) override { report(message, {}, type); } public: void reset() override { Compiler::ErrorHandler::reset(); mErrors.clear(); } const std::vector>& getErrors() const { return mErrors; } }; class LocalVariables { std::vector mShorts; std::vector mLongs; std::vector mFloats; template T getLocal(std::size_t index, const std::vector& vector) const { if(index < vector.size()) return vector[index]; return {}; } template void setLocal(T value, std::size_t index, std::vector& vector) { if(index >= vector.size()) vector.resize(index + 1); vector[index] = value; } public: void clear() { mShorts.clear(); mLongs.clear(); mFloats.clear(); } int getShort(std::size_t index) const { return getLocal(index, mShorts); }; int getLong(std::size_t index) const { return getLocal(index, mLongs); }; float getFloat(std::size_t index) const { return getLocal(index, mFloats); }; void setShort(std::size_t index, int value) { setLocal(value, index, mShorts); }; void setLong(std::size_t index, int value) { setLocal(value, index, mLongs); }; void setFloat(std::size_t index, float value) { setLocal(value, index, mFloats); }; }; class GlobalVariables { std::map> mShorts; std::map> mLongs; std::map> mFloats; template T getGlobal(std::string_view name, const std::map>& map) const { auto it = map.find(name); if(it != map.end()) return it->second; return {}; } public: void clear() { mShorts.clear(); mLongs.clear(); mFloats.clear(); } int getShort(std::string_view name) const { return getGlobal(name, mShorts); }; int getLong(std::string_view name) const { return getGlobal(name, mLongs); }; float getFloat(std::string_view name) const { return getGlobal(name, mFloats); }; void setShort(std::string_view name, int value) { mShorts[std::string(name)] = value; }; void setLong(std::string_view name, int value) { mLongs[std::string(name)] = value; }; void setFloat(std::string_view name, float value) { mFloats[std::string(name)] = value; }; }; class TestInterpreterContext : public Interpreter::Context { LocalVariables mLocals; std::map> mMembers; public: std::string getTarget() const override { return {}; }; int getLocalShort(int index) const override { return mLocals.getShort(index); }; int getLocalLong(int index) const override { return mLocals.getLong(index); }; float getLocalFloat(int index) const override { return mLocals.getFloat(index); }; void setLocalShort(int index, int value) override { mLocals.setShort(index, value); }; void setLocalLong(int index, int value) override { mLocals.setLong(index, value); }; void setLocalFloat(int index, float value) override { mLocals.setFloat(index, value); }; void messageBox(const std::string& message, const std::vector& buttons) override {}; void report(const std::string& message) override {}; int getGlobalShort(std::string_view name) const override { return {}; }; int getGlobalLong(std::string_view name) const override { return {}; }; float getGlobalFloat(std::string_view name) const override { return {}; }; void setGlobalShort(std::string_view name, int value) override {}; void setGlobalLong(std::string_view name, int value) override {}; void setGlobalFloat(std::string_view name, float value) override {}; std::vector getGlobals() const override { return {}; }; char getGlobalType(std::string_view name) const override { return ' '; }; std::string getActionBinding(std::string_view action) const override { return {}; }; std::string getActorName() const override { return {}; }; std::string getNPCRace() const override { return {}; }; std::string getNPCClass() const override { return {}; }; std::string getNPCFaction() const override { return {}; }; std::string getNPCRank() const override { return {}; }; std::string getPCName() const override { return {}; }; std::string getPCRace() const override { return {}; }; std::string getPCClass() const override { return {}; }; std::string getPCRank() const override { return {}; }; std::string getPCNextRank() const override { return {}; }; int getPCBounty() const override { return {}; }; std::string getCurrentCellName() const override { return {}; }; int getMemberShort(std::string_view id, std::string_view name, bool global) const override { auto it = mMembers.find(id); if(it != mMembers.end()) return it->second.getShort(name); return {}; }; int getMemberLong(std::string_view id, std::string_view name, bool global) const override { auto it = mMembers.find(id); if(it != mMembers.end()) return it->second.getLong(name); return {}; }; float getMemberFloat(std::string_view id, std::string_view name, bool global) const override { auto it = mMembers.find(id); if(it != mMembers.end()) return it->second.getFloat(name); return {}; }; void setMemberShort(std::string_view id, std::string_view name, int value, bool global) override { mMembers[std::string(id)].setShort(name, value); }; void setMemberLong(std::string_view id, std::string_view name, int value, bool global) override { mMembers[std::string(id)].setLong(name, value); }; void setMemberFloat(std::string_view id, std::string_view name, float value, bool global) override { mMembers[std::string(id)].setFloat(name, value); }; }; struct CompiledScript { std::vector mByteCode; Compiler::Locals mLocals; CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} }; } #endifopenmw-openmw-0.48.0/apps/openmw_test_suite/mwworld/000077500000000000000000000000001445372753700226475ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/mwworld/test_store.cpp000066400000000000000000000237231445372753700255550ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwmechanics/spelllist.hpp" #include "../testing_util.hpp" namespace MWMechanics { SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} } static Loading::Listener dummyListener; /// Base class for tests of ESMStore that rely on external content files to produce the test results struct ContentFileTest : public ::testing::Test { protected: void SetUp() override { readContentFiles(); // load the content files int index=0; ESM::Dialogue* dialogue = nullptr; for (const auto & mContentFile : mContentFiles) { ESM::ESMReader lEsm; lEsm.setEncoder(nullptr); lEsm.setIndex(index); lEsm.open(mContentFile.string()); mEsmStore.load(lEsm, &dummyListener, dialogue); ++index; } mEsmStore.setUp(); } void TearDown() override { } // read absolute path to content files from openmw.cfg void readContentFiles() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Allowed options"); desc.add_options() ("data", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing()) ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); Files::ConfigurationManager::addCommonOptions(desc); mConfigurationManager.readConfiguration(variables, desc, true); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = asPathContainer(variables["data"].as()); } Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataLocal.push_back(local); mConfigurationManager.filterOutNonExistingPaths(dataDirs); mConfigurationManager.filterOutNonExistingPaths(dataLocal); if (!dataLocal.empty()) dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); Files::Collections collections (dataDirs, true); std::vector contentFiles = variables["content"].as>(); for (auto & contentFile : contentFiles) { if (!Misc::StringUtils::ciEndsWith(contentFile, ".omwscripts")) mContentFiles.push_back(collections.getPath(contentFile)); } } protected: Files::ConfigurationManager mConfigurationManager; MWWorld::ESMStore mEsmStore; std::vector mContentFiles; }; /// Print results of the dialogue merging process, i.e. the resulting linked list. TEST_F(ContentFileTest, dialogue_merging_test) { if (mContentFiles.empty()) { std::cout << "No content files found, skipping test" << std::endl; return; } const std::string file = TestingOpenMW::outputFilePath("test_dialogue_merging.txt"); boost::filesystem::ofstream stream; stream.open(file); const MWWorld::Store& dialStore = mEsmStore.get(); for (const auto & dial : dialStore) { stream << "Dialogue: " << dial.mId << std::endl; for (const auto & info : dial.mInfo) { stream << info.mId << std::endl; } stream << std::endl; } std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; } // Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell) #define RUN_TEST_FOR_TYPES(func, arg1, arg2) \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); template void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) { const MWWorld::Store& store = esmStore.get(); outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; for (typename MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) { const T& record = *it; outStream << record.mId << std::endl; } outStream << std::endl; } /// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records /// Also used to test the iteration order of records TEST_F(ContentFileTest, content_diagnostics_test) { if (mContentFiles.empty()) { std::cout << "No content files found, skipping test" << std::endl; return; } const std::string file = TestingOpenMW::outputFilePath("test_content_diagnostics.txt"); boost::filesystem::ofstream stream; stream.open(file); RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream); std::cout << "diagnostics_test successful, results printed to " << file << std::endl; } // TODO: /// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on /// - even incorrect rounding modes can completely change the resulting spell lists. /* TEST_F(ContentFileTest, autocalc_test) { if (mContentFiles.empty()) { std::cout << "No content files found, skipping test" << std::endl; return; } } */ /// Base class for tests of ESMStore that do not rely on external content files struct StoreTest : public ::testing::Test { protected: MWWorld::ESMStore mEsmStore; }; /// Create an ESM file in-memory containing the specified record. /// @param deleted Write record with deleted flag? template std::unique_ptr getEsmFile(T record, bool deleted) { ESM::ESMWriter writer; auto stream = std::make_unique(); writer.setFormat(0); writer.save(*stream); writer.startRecord(T::sRecordId); record.save(writer, deleted); writer.endRecord(T::sRecordId); return stream; } /// Tests deletion of records. TEST_F(StoreTest, delete_test) { const std::string recordId = "foobar"; typedef ESM::Apparatus RecordType; RecordType record; record.blank(); record.mId = recordId; ESM::ESMReader reader; ESM::Dialogue* dialogue = nullptr; // master file inserts a record reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); // now a plugin deletes it reader.open(getEsmFile(record, true), "filename"); mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 0); // now another plugin inserts it again // expected behaviour is the record to reappear rather than staying deleted reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); } /// Tests overwriting of records. TEST_F(StoreTest, overwrite_test) { const std::string recordId = "foobar"; const std::string recordIdUpper = "Foobar"; typedef ESM::Apparatus RecordType; RecordType record; record.blank(); record.mId = recordId; ESM::ESMReader reader; ESM::Dialogue* dialogue = nullptr; // master file inserts a record reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); // now a plugin overwrites it with changed data record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it record.mModel = "the_new_model"; reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); // verify that changes were actually applied const RecordType* overwrittenRec = mEsmStore.get().search(recordId); ASSERT_TRUE (overwrittenRec != nullptr); ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); } openmw-openmw-0.48.0/apps/openmw_test_suite/mwworld/testduration.cpp000066400000000000000000000044461445372753700261100ustar00rootroot00000000000000#include #include #include "apps/openmw/mwworld/duration.hpp" namespace MWWorld { namespace { TEST(MWWorldDurationTest, fromHoursShouldProduceZeroDaysAndHoursFor0) { const Duration duration = Duration::fromHours(0); EXPECT_EQ(duration.getDays(), 0); EXPECT_EQ(duration.getHours(), 0); } TEST(MWWorldDurationTest, fromHoursShouldProduceOneDayAndZeroHoursFor24) { const Duration duration = Duration::fromHours(24); EXPECT_EQ(duration.getDays(), 1); EXPECT_EQ(duration.getHours(), 0); } TEST(MWWorldDurationTest, fromHoursShouldProduceOneDayAndRemainderHoursFor42) { const Duration duration = Duration::fromHours(42); EXPECT_EQ(duration.getDays(), 1); EXPECT_EQ(duration.getHours(), 18); } TEST(MWWorldDurationTest, fromHoursShouldProduceZeroDaysAndZeroHoursForMinDouble) { const Duration duration = Duration::fromHours(std::numeric_limits::min()); EXPECT_EQ(duration.getDays(), 0); EXPECT_EQ(duration.getHours(), 0); } TEST(MWWorldDurationTest, fromHoursShouldProduceZeroDaysAndSomeHoursForMinFloat) { const Duration duration = Duration::fromHours(std::numeric_limits::min()); EXPECT_EQ(duration.getDays(), 0); EXPECT_GT(duration.getHours(), 0); EXPECT_FLOAT_EQ(duration.getHours(), std::numeric_limits::min()); } TEST(MWWorldDurationTest, fromHoursShouldProduceZeroDaysAndRemainderHoursForValueJustBelow24InDoublePrecision) { const Duration duration = Duration::fromHours(std::nextafter(24.0, 0.0)); EXPECT_EQ(duration.getDays(), 0); EXPECT_LT(duration.getHours(), 24); EXPECT_FLOAT_EQ(duration.getHours(), 24); } TEST(MWWorldDurationTest, fromHoursShouldProduceZeroDaysAndRemainderHoursForValueJustBelow24InFloatPrecision) { const Duration duration = Duration::fromHours(std::nextafter(24.0f, 0.0f)); EXPECT_EQ(duration.getDays(), 0); EXPECT_LT(duration.getHours(), 24); EXPECT_FLOAT_EQ(duration.getHours(), 24); } } } openmw-openmw-0.48.0/apps/openmw_test_suite/mwworld/testtimestamp.cpp000066400000000000000000000041051445372753700262560ustar00rootroot00000000000000#include #include #include "apps/openmw/mwworld/timestamp.hpp" namespace MWWorld { namespace { TEST(MWWorldTimeStampTest, operatorPlusShouldNotChangeTimeStampForZero) { TimeStamp timeStamp; timeStamp += 0; EXPECT_EQ(timeStamp.getDay(), 0); EXPECT_EQ(timeStamp.getHour(), 0); } TEST(MWWorldTimeStampTest, operatorPlusShouldProperlyHandleDoubleValuesCloseTo24) { TimeStamp timeStamp; timeStamp += std::nextafter(24.0, 0.0); EXPECT_EQ(timeStamp.getDay(), 0); EXPECT_LT(timeStamp.getHour(), 24); EXPECT_FLOAT_EQ(timeStamp.getHour(), 24); } TEST(MWWorldTimeStampTest, operatorPlusShouldProperlyHandleFloatValuesCloseTo24) { TimeStamp timeStamp; timeStamp += std::nextafter(24.0f, 0.0f); EXPECT_EQ(timeStamp.getDay(), 0); EXPECT_LT(timeStamp.getHour(), 24); EXPECT_FLOAT_EQ(timeStamp.getHour(), 24); } TEST(MWWorldTimeStampTest, operatorPlusShouldAddDaysForEach24Hours) { TimeStamp timeStamp; timeStamp += 24.0 * 42; EXPECT_EQ(timeStamp.getDay(), 42); EXPECT_EQ(timeStamp.getHour(), 0); } TEST(MWWorldTimeStampTest, operatorPlusShouldAddDaysForEach24HoursAndSetRemainderToHours) { TimeStamp timeStamp; timeStamp += 24.0 * 42 + 13.0; EXPECT_EQ(timeStamp.getDay(), 42); EXPECT_EQ(timeStamp.getHour(), 13); } TEST(MWWorldTimeStampTest, operatorPlusShouldAccumulateExistingValue) { TimeStamp timeStamp(13, 42); timeStamp += 24.0 * 2 + 17.0; EXPECT_EQ(timeStamp.getDay(), 45); EXPECT_EQ(timeStamp.getHour(), 6); } TEST(MWWorldTimeStampTest, operatorPlusShouldThrowExceptionForNegativeValue) { TimeStamp timeStamp(13, 42); EXPECT_THROW(timeStamp += -1, std::runtime_error); } } } openmw-openmw-0.48.0/apps/openmw_test_suite/nifloader/000077500000000000000000000000001445372753700231175ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp000066400000000000000000001654321445372753700277110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include namespace { template bool compareObjects(const T* lhs, const T* rhs) { return (!lhs && !rhs) || (lhs && rhs && *lhs == *rhs); } std::vector getTriangles(const btBvhTriangleMeshShape& shape) { std::vector result; auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) result.push_back(triangle[i]); }); btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); shape.processAllTriangles(&callback, aabbMin, aabbMax); return result; } bool isNear(btScalar lhs, btScalar rhs) { return std::abs(lhs - rhs) <= 1e-5; } bool isNear(const btVector3& lhs, const btVector3& rhs) { return std::equal( static_cast(lhs), static_cast(lhs) + 3, static_cast(rhs), [] (btScalar lhs, btScalar rhs) { return isNear(lhs, rhs); } ); } bool isNear(const btMatrix3x3& lhs, const btMatrix3x3& rhs) { for (int i = 0; i < 3; ++i) if (!isNear(lhs[i], rhs[i])) return false; return true; } bool isNear(const btTransform& lhs, const btTransform& rhs) { return isNear(lhs.getOrigin(), rhs.getOrigin()) && isNear(lhs.getBasis(), rhs.getBasis()); } struct WriteVec3f { osg::Vec3f mValue; friend std::ostream& operator <<(std::ostream& stream, const WriteVec3f& value) { return stream << "osg::Vec3f {" << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.x() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.y() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.z() << "}"; } }; } static std::ostream& operator <<(std::ostream& stream, const btVector3& value) { return stream << "btVector3 {" << std::setprecision(std::numeric_limits::max_exponent10) << value.getX() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.getY() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.getZ() << "}"; } static std::ostream& operator <<(std::ostream& stream, const btMatrix3x3& value) { stream << "btMatrix3x3 {"; for (int i = 0; i < 3; ++i) stream << value.getRow(i) << ", "; return stream << "}"; } static std::ostream& operator <<(std::ostream& stream, const btTransform& value) { return stream << "btTransform {" << value.getBasis() << ", " << value.getOrigin() << "}"; } static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value); static std::ostream& operator <<(std::ostream& stream, const btCompoundShape& value) { stream << "btCompoundShape {" << value.getLocalScaling() << ", "; stream << "{"; for (int i = 0; i < value.getNumChildShapes(); ++i) stream << value.getChildShape(i) << ", "; stream << "},"; stream << "{"; for (int i = 0; i < value.getNumChildShapes(); ++i) stream << value.getChildTransform(i) << ", "; stream << "}"; return stream << "}"; } static std::ostream& operator <<(std::ostream& stream, const btBoxShape& value) { return stream << "btBoxShape {" << value.getLocalScaling() << ", " << value.getHalfExtentsWithoutMargin() << "}"; } namespace Resource { static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& value) { stream << "Resource::TriangleMeshShape {" << value.getLocalScaling() << ", " << value.usesQuantizedAabbCompression() << ", " << value.getOwnsBvh() << ", {"; auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) stream << triangle[i] << ", "; }); btVector3 aabbMin; btVector3 aabbMax; value.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); value.processAllTriangles(&callback, aabbMin, aabbMax); return stream << "}}"; } static bool operator ==(const CollisionBox& l, const CollisionBox& r) { const auto tie = [] (const CollisionBox& v) { return std::tie(v.mExtents, v.mCenter); }; return tie(l) == tie(r); } static std::ostream& operator <<(std::ostream& stream, const CollisionBox& value) { return stream << "CollisionBox {" << WriteVec3f {value.mExtents} << ", " << WriteVec3f {value.mCenter} << "}"; } } static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) { switch (value.getShapeType()) { case COMPOUND_SHAPE_PROXYTYPE: return stream << static_cast(value); case BOX_SHAPE_PROXYTYPE: return stream << static_cast(value); case TRIANGLE_MESH_SHAPE_PROXYTYPE: if (const auto casted = dynamic_cast(&value)) return stream << *casted; break; } return stream << "btCollisionShape {" << value.getShapeType() << "}"; } static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value) { return value ? stream << "&" << *value : stream << "nullptr"; } namespace std { static std::ostream& operator <<(std::ostream& stream, const map& value) { stream << "std::map {"; for (const auto& v : value) stream << "{" << v.first << ", " << v.second << "},"; return stream << "}"; } } namespace Resource { static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) { return compareObjects(lhs.mCollisionShape.get(), rhs.mCollisionShape.get()) && compareObjects(lhs.mAvoidCollisionShape.get(), rhs.mAvoidCollisionShape.get()) && lhs.mCollisionBox == rhs.mCollisionBox && lhs.mVisualCollisionType == rhs.mVisualCollisionType && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } static std::ostream& operator <<(std::ostream& stream, Resource::VisualCollisionType value) { switch (value) { case Resource::VisualCollisionType::None: return stream << "Resource::VisualCollisionType::None"; case Resource::VisualCollisionType::Default: return stream << "Resource::VisualCollisionType::Default"; case Resource::VisualCollisionType::Camera: return stream << "Resource::VisualCollisionType::Camera"; } return stream << static_cast>(value); } static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) { return stream << "Resource::BulletShape {" << value.mCollisionShape.get() << ", " << value.mAvoidCollisionShape.get() << ", " << value.mCollisionBox << ", " << value.mAnimatedShapes<< ", " << value.mVisualCollisionType << "}"; } } static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs); static bool operator ==(const btCompoundShape& lhs, const btCompoundShape& rhs) { if (lhs.getNumChildShapes() != rhs.getNumChildShapes() || lhs.getLocalScaling() != rhs.getLocalScaling()) return false; for (int i = 0; i < lhs.getNumChildShapes(); ++i) { if (!compareObjects(lhs.getChildShape(i), rhs.getChildShape(i)) || !isNear(lhs.getChildTransform(i), rhs.getChildTransform(i))) return false; } return true; } static bool operator ==(const btBoxShape& lhs, const btBoxShape& rhs) { return isNear(lhs.getLocalScaling(), rhs.getLocalScaling()) && lhs.getHalfExtentsWithoutMargin() == rhs.getHalfExtentsWithoutMargin(); } static bool operator ==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMeshShape& rhs) { return isNear(lhs.getLocalScaling(), rhs.getLocalScaling()) && lhs.usesQuantizedAabbCompression() == rhs.usesQuantizedAabbCompression() && lhs.getOwnsBvh() == rhs.getOwnsBvh() && getTriangles(lhs) == getTriangles(rhs); } static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs) { if (lhs.getShapeType() != rhs.getShapeType()) return false; switch (lhs.getShapeType()) { case COMPOUND_SHAPE_PROXYTYPE: return static_cast(lhs) == static_cast(rhs); case BOX_SHAPE_PROXYTYPE: return static_cast(lhs) == static_cast(rhs); case TRIANGLE_MESH_SHAPE_PROXYTYPE: if (const auto lhsCasted = dynamic_cast(&lhs)) if (const auto rhsCasted = dynamic_cast(&rhs)) return *lhsCasted == *rhsCasted; return false; } return false; } namespace { using namespace testing; using NifBullet::BulletNifLoader; void init(Nif::Transformation& value) { value = Nif::Transformation::getIdentity(); } void init(Nif::Extra& value) { value.next = Nif::ExtraPtr(nullptr); } void init(Nif::Named& value) { value.extra = Nif::ExtraPtr(nullptr); value.extralist = Nif::ExtraList(); value.controller = Nif::ControllerPtr(nullptr); } void init(Nif::Node& value) { init(static_cast(value)); value.flags = 0; init(value.trafo); value.hasBounds = false; value.parents.push_back(nullptr); value.isBone = false; } void init(Nif::NiGeometry& value) { init(static_cast(value)); value.data = Nif::NiGeometryDataPtr(nullptr); value.skin = Nif::NiSkinInstancePtr(nullptr); } void init(Nif::NiTriShape& value) { init(static_cast(value)); value.recType = Nif::RC_NiTriShape; } void init(Nif::NiTriStrips& value) { init(static_cast(value)); value.recType = Nif::RC_NiTriStrips; } void init(Nif::NiSkinInstance& value) { value.data = Nif::NiSkinDataPtr(nullptr); value.root = Nif::NodePtr(nullptr); } void init(Nif::Controller& value) { value.next = Nif::ControllerPtr(nullptr); value.flags = 0; value.frequency = 0; value.phase = 0; value.timeStart = 0; value.timeStop = 0; value.target = Nif::NamedPtr(nullptr); } void copy(const btTransform& src, Nif::Transformation& dst) { dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); for (int row = 0; row < 3; ++row) for (int column = 0; column < 3; ++column) dst.rotation.mValues[column][row] = src.getBasis().getRow(row)[column]; } struct NifFileMock : Nif::File { MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override)); MOCK_METHOD(std::size_t, numRecords, (), (const, override)); MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override)); MOCK_METHOD(std::size_t, numRoots, (), (const, override)); MOCK_METHOD(std::string, getString, (uint32_t), (const, override)); MOCK_METHOD(void, setUseSkinning, (bool), (override)); MOCK_METHOD(bool, getUseSkinning, (), (const, override)); MOCK_METHOD(std::string, getFilename, (), (const, override)); MOCK_METHOD(std::string, getHash, (), (const, override)); MOCK_METHOD(unsigned int, getVersion, (), (const, override)); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); }; struct RecordMock : Nif::Record { MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override)); }; struct TestBulletNifLoader : Test { BulletNifLoader mLoader; const StrictMock mNifFile; Nif::Node mNode; Nif::Node mNode2; Nif::NiNode mNiNode; Nif::NiNode mNiNode2; Nif::NiNode mNiNode3; Nif::NiTriShapeData mNiTriShapeData; Nif::NiTriShape mNiTriShape; Nif::NiTriShapeData mNiTriShapeData2; Nif::NiTriShape mNiTriShape2; Nif::NiTriStripsData mNiTriStripsData; Nif::NiTriStrips mNiTriStrips; Nif::NiSkinInstance mNiSkinInstance; Nif::NiStringExtraData mNiStringExtraData; Nif::NiStringExtraData mNiStringExtraData2; Nif::Controller mController; btTransform mTransform {btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3)}; btTransform mResultTransform { btMatrix3x3( 1, 0, 0, 0, 0.82417738437652587890625, 0.56633174419403076171875, 0, -0.56633174419403076171875, 0.82417738437652587890625 ), btVector3(1, 2, 3) }; btTransform mResultTransform2 { btMatrix3x3( 1, 0, 0, 0, 0.7951543331146240234375, 0.606407105922698974609375, 0, -0.606407105922698974609375, 0.7951543331146240234375 ), btVector3(4, 8, 12) }; const std::string mHash = "hash"; TestBulletNifLoader() { init(mNode); init(mNode2); init(mNiNode); init(mNiNode2); init(mNiNode3); init(mNiTriShape); init(mNiTriShape2); init(mNiTriStrips); init(mNiSkinInstance); init(mNiStringExtraData); init(mNiStringExtraData2); init(mController); mNiTriShapeData.recType = Nif::RC_NiTriShapeData; mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; mNiTriShapeData.triangles = {0, 1, 2}; mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; mNiTriShapeData2.triangles = {0, 1, 2}; mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); mNiTriStripsData.recType = Nif::RC_NiTriStripsData; mNiTriStripsData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0)}; mNiTriStripsData.strips = {{0, 1, 2, 3}}; mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); EXPECT_CALL(mNifFile, getHash()).WillOnce(Return(mHash)); } }; TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); EXPECT_EQ(result->mFileName, "test.nif"); EXPECT_EQ(result->mFileHash, mHash); } TEST_F(TestBulletNifLoader, should_ignore_nullptr_root) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(nullptr)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_default_root_nif_node_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_default_root_collision_node_nif_node_should_return_default) { mNode.recType = Nif::RC_RootCollisionNode; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_default_root_nif_node_and_filename_starting_with_x_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) { mNode.hasBounds = true; mNode.flags |= Nif::Node::Flag_BBoxCollision; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) { mNode.hasBounds = true; mNode.flags |= Nif::Node::Flag_BBoxCollision; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) { mNode.hasBounds = true; mNode.flags |= Nif::Node::Flag_BBoxCollision; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNiNode.bounds.box.extents = osg::Vec3f(4, 5, 6); mNiNode.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) { mNode.hasBounds = true; mNode.flags |= Nif::Node::Flag_BBoxCollision; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode.parents.push_back(&mNiNode); mNode2.hasBounds = true; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { mNode.hasBounds = true; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode.parents.push_back(&mNiNode); mNode2.hasBounds = true; mNode2.flags |= Nif::Node::Flag_BBoxCollision; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6); std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { mNode.hasBounds = true; mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_root_node_should_return_shape_with_triangle_mesh_shape) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) { mNiTriShape.hasBounds = true; mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNiTriShape.bounds.box.extents = osg::Vec3f(1, 2, 3); mNiTriShape.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) { mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode2.parents.push_back(&mNiNode); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiTriShape.parents.push_back(&mNiNode2); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { mNiTriShape.parents.push_back(&mNiNode); mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) })); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) { mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform, mesh.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform2, mesh.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_shape_with_compound_shape) { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2), })); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); mesh2->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform, mesh.release()); shape->addChildShape(mResultTransform, mesh2.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_controller_should_return_shape_with_compound_shape) { mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parents.push_back(&mNiNode); mNiTriShape.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform2, mesh.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_where_one_with_controller_should_return_shape_with_compound_shape) { mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parents.push_back(&mNiNode); mNiTriShape2.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2), })); mNiNode.trafo.scale = 4; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(4, 8, 12), btVector3(16, 8, 12), btVector3(16, 18.5309906005859375, 6.246893405914306640625)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(1, 1, 1)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform2, mesh2.release()); shape->addChildShape(btTransform::getIdentity(), mesh.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) { mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriShape2)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr compound(new btCompoundShape); compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), false)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { auto data = static_cast(mNiTriShape.data.getPtr()); data->triangles.clear(); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.string = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); expected.mVisualCollisionType = Resource::VisualCollisionType::Camera; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.string = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); expected.mVisualCollisionType = Resource::VisualCollisionType::Camera; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); expected.mVisualCollisionType = Resource::VisualCollisionType::Default; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); expected.mVisualCollisionType = Resource::VisualCollisionType::Default; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_empty_root_collision_node_without_nc_should_return_shape_with_cameraonly_collision) { Nif::NiTriShape niTriShape; Nif::NiNode emptyCollisionNode; init(niTriShape); init(emptyCollisionNode); niTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); niTriShape.parents.push_back(&mNiNode); emptyCollisionNode.recType = Nif::RC_RootCollisionNode; emptyCollisionNode.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector( {Nif::NodePtr(&niTriShape), Nif::NodePtr(&emptyCollisionNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); expected.mVisualCollisionType = Resource::VisualCollisionType::Camera; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) { mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode2); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; mNiNode2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_ignore_tri_shape_data_with_mismatching_data_rec_type) { mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_tri_strips_root_node_should_return_shape_with_triangle_mesh_shape) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); triangles->addTriangle(btVector3(1, 0, 0), btVector3(0, 1, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_mismatching_data_rec_type) { mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_empty_strips) { mNiTriStripsData.strips.clear(); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_static_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriStripsData.strips.front() = {0, 1}; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; mNiTriStripsData.strips.front() = {0, 1}; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriStripsData.strips.front() = {0, 1}; mNiTriStrips.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriStrips)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) { mNiTriStripsData.strips.front() = {0, 1}; mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriStrips)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } TEST_F(TestBulletNifLoader, should_handle_node_with_multiple_parents) { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 4; mNiTriShape.parents = {&mNiNode, &mNiNode2}; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 2; mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.trafo.scale = 3; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiNode2)); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); std::unique_ptr triangles1(new btTriangleMesh(false)); triangles1->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh1(new Resource::TriangleMeshShape(triangles1.release(), true)); mesh1->setLocalScaling(btVector3(8, 8, 8)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); const btTransform transform1 { btMatrix3x3( 1, 0, 0, 0, 0.8004512795493964327775415767973754555, 0.59939782204119995689950428641168400645, 0, -0.59939782204119995689950428641168400645, 0.8004512795493964327775415767973754555 ), btVector3(2, 4, 6) }; const btTransform transform2 { btMatrix3x3( 1, 0, 0, 0, 0.79515431915808965079861536651151254773, 0.60640713116208888600056070572463795543, 0, -0.60640713116208888600056070572463795543, 0.79515431915808965079861536651151254773 ), btVector3(3, 6, 9) }; shape->addChildShape(transform1, mesh1.release()); shape->addChildShape(transform2, mesh2.release()); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); } } openmw-openmw-0.48.0/apps/openmw_test_suite/openmw/000077500000000000000000000000001445372753700224615ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/openmw/options.cpp000066400000000000000000000443461445372753700246730ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace OpenMW; namespace bpo = boost::program_options; template std::string makeString(const T (&range)[size]) { static_assert(size > 0); return std::string(std::begin(range), std::end(range) - 1); } template std::vector generateSupportedCharacters(Args&& ... args) { std::vector result; (result.emplace_back(makeString(args)) , ...); for (int i = 1; i <= std::numeric_limits::max(); ++i) if (i != '&' && i != '"' && i != ' ' && i != '\n') result.push_back(std::string(1, i)); return result; } MATCHER_P(IsPath, v, "") { return arg.string() == v; } template void parseArgs(const T& arguments, bpo::variables_map& variables, bpo::options_description& description) { Files::parseArgs(static_cast(arguments.size()), arguments.data(), variables, description); } TEST(OpenMWOptionsFromArguments, should_support_equality_to_separate_flag_and_value) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame=save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_octothorpe) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "my#save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save.omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_compose_data) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--data", "1", "--data", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--data", "1", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "1.omwsave", "--load-savegame", "2.omwsave"}; bpo::variables_map variables; EXPECT_THROW(parseArgs(arguments, variables, description), std::exception); } struct OpenMWOptionsFromArgumentsStrings : TestWithParam {}; TEST_P(OpenMWOptionsFromArgumentsStrings, should_support_paths_with_certain_characters_in_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; const std::string pathArgument = "\"" + path + "\""; const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()}; bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( SupportedCharacters, OpenMWOptionsFromArgumentsStrings, ValuesIn(generateSupportedCharacters(u8"👍", u8"Ъ", u8"Ǽ", "\n")) ); TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame=""save".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="my save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_octothorpe) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=save#.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save#.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=save@.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save@.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame=save".omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_lots_going_on) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="one &"two"three".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); } TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_even_more_going_on) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="one &"two"three ".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); } TEST(OpenMWOptionsFromConfig, should_ignore_commented_option) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_ignore_whitespace_prefixed_commented_option) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(" \t#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_support_whitespace_around_option) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(" load-savegame = save.omwsave "); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=1.omwsave\nload-savegame=2.omwsave"); bpo::variables_map variables; EXPECT_THROW(Files::parseConfig(stream, variables, description), std::exception); } TEST(OpenMWOptionsFromConfig, should_support_multi_component_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } TEST(OpenMWOptionsFromConfig, should_compose_data) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("data=1\ndata=2"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="save&".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame="save.omwsave&&")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream("load-savegame=save&.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } struct OpenMWOptionsFromConfigStrings : TestWithParam {}; TEST_P(OpenMWOptionsFromConfigStrings, should_support_paths_with_certain_characters_in_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; std::istringstream stream("load-savegame=\"" + path + "\""); bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( SupportedCharacters, OpenMWOptionsFromConfigStrings, ValuesIn(generateSupportedCharacters(u8"👍", u8"Ъ", u8"Ǽ")) ); } openmw-openmw-0.48.0/apps/openmw_test_suite/openmw_test_suite.cpp000066400000000000000000000005741445372753700254430ustar00rootroot00000000000000#include #ifdef WIN32 //we cannot use GTEST_API_ before main if we're building standalone exe application, //and we're linking GoogleTest / GoogleMock as DLLs and not linking gtest_main / gmock_main int main(int argc, char **argv) { #else GTEST_API_ int main(int argc, char **argv) { #endif testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/000077500000000000000000000000001445372753700240315ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/binaryreader.cpp000066400000000000000000000067631445372753700272200ustar00rootroot00000000000000#include "format.hpp" #include #include #include #include #include #include namespace { using namespace testing; using namespace Serialization; using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue) { std::uint32_t value = 42; std::vector data(sizeof(value)); std::memcpy(data.data(), &value, sizeof(value)); BinaryReader binaryReader(data.data(), data.data() + data.size()); std::uint32_t result = 0; const TestFormat format; binaryReader(format, result); EXPECT_EQ(result, 42u); } TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeRangeValue) { const std::size_t count = 3; std::vector data(sizeof(std::size_t) + count * sizeof(std::uint32_t)); std::memcpy(data.data(), &count, sizeof(count)); const std::uint32_t value1 = 960900021; std::memcpy(data.data() + sizeof(count), &value1, sizeof(std::uint32_t)); const std::uint32_t value2 = 1235496234; std::memcpy(data.data() + sizeof(count) + sizeof(std::uint32_t), &value2, sizeof(std::uint32_t)); const std::uint32_t value3 = 2342038092; std::memcpy(data.data() + sizeof(count) + 2 * sizeof(std::uint32_t), &value3, sizeof(std::uint32_t)); BinaryReader binaryReader(data.data(), data.data() + data.size()); std::size_t resultCount = 0; const TestFormat format; binaryReader(format, resultCount); std::vector result(resultCount); binaryReader(format, result.data(), result.size()); EXPECT_THAT(result, ElementsAre(value1, value2, value3)); } TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeShouldThrowException) { std::vector data(3); BinaryReader binaryReader(data.data(), data.data() + data.size()); std::uint32_t result = 0; const TestFormat format; EXPECT_THROW(binaryReader(format, result), std::runtime_error); } TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeRangeShouldThrowException) { std::vector data(7); BinaryReader binaryReader(data.data(), data.data() + data.size()); std::vector values(2); const TestFormat format; EXPECT_THROW(binaryReader(format, values.data(), values.size()), std::runtime_error); } TEST(DetourNavigatorSerializationBinaryReaderTest, shouldSetPointerToCurrentBufferPosition) { std::vector data(8); BinaryReader binaryReader(data.data(), data.data() + data.size()); const std::byte* ptr = nullptr; const TestFormat format; binaryReader(format, ptr); EXPECT_EQ(ptr, data.data()); } TEST(DetourNavigatorSerializationBinaryReaderTest, shouldNotAdvanceAfterPointer) { std::vector data(8); BinaryReader binaryReader(data.data(), data.data() + data.size()); const std::byte* ptr1 = nullptr; const std::byte* ptr2 = nullptr; const TestFormat format; binaryReader(format, ptr1); binaryReader(format, ptr2); EXPECT_EQ(ptr1, data.data()); EXPECT_EQ(ptr2, data.data()); } } openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/binarywriter.cpp000066400000000000000000000042141445372753700272570ustar00rootroot00000000000000#include "format.hpp" #include #include #include #include #include #include namespace { using namespace testing; using namespace Serialization; using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue) { std::vector result(4); BinaryWriter binaryWriter(result.data(), result.data() + result.size()); const TestFormat format; binaryWriter(format, std::uint32_t(42)); EXPECT_THAT(result, ElementsAre(std::byte(42), std::byte(0), std::byte(0), std::byte(0))); } TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeRangeValue) { std::vector result(8); BinaryWriter binaryWriter(result.data(), result.data() + result.size()); std::vector values({42, 13}); const TestFormat format; binaryWriter(format, values.data(), values.size()); constexpr std::array expected { std::byte(42), std::byte(0), std::byte(0), std::byte(0), std::byte(13), std::byte(0), std::byte(0), std::byte(0), }; EXPECT_THAT(result, ElementsAreArray(expected)); } TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeShouldThrowException) { std::vector result(3); BinaryWriter binaryWriter(result.data(), result.data() + result.size()); const TestFormat format; EXPECT_THROW(binaryWriter(format, std::uint32_t(42)), std::runtime_error); } TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeRangeShouldThrowException) { std::vector result(7); BinaryWriter binaryWriter(result.data(), result.data() + result.size()); std::vector values({42, 13}); const TestFormat format; EXPECT_THROW(binaryWriter(format, values.data(), values.size()), std::runtime_error); } } openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/format.hpp000066400000000000000000000044141445372753700260350ustar00rootroot00000000000000#ifndef OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H #define OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H #include #include #include #include namespace SerializationTesting { struct Pod { int mInt = 42; double mDouble = 3.14; friend bool operator==(const Pod& l, const Pod& r) { const auto tuple = [] (const Pod& v) { return std::tuple(v.mInt, v.mDouble); }; return tuple(l) == tuple(r); } }; enum Enum : std::int32_t { A, B, C, }; struct Composite { short mFloatArray[3] = {0}; std::vector mIntVector; std::vector mEnumVector; std::vector mPodVector; std::size_t mPodDataSize = 0; std::vector mPodBuffer; std::size_t mCharDataSize = 0; std::vector mCharBuffer; }; template struct TestFormat : Serialization::Format> { using Serialization::Format>::operator(); template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, Pod>> { visitor(*this, value.mInt); visitor(*this, value.mDouble); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, Composite>> { visitor(*this, value.mFloatArray); visitor(*this, value.mIntVector); visitor(*this, value.mEnumVector); visitor(*this, value.mPodVector); visitor(*this, value.mPodDataSize); if constexpr (mode == Serialization::Mode::Read) value.mPodBuffer.resize(value.mPodDataSize); visitor(*this, value.mPodBuffer.data(), value.mPodDataSize); visitor(*this, value.mCharDataSize); if constexpr (mode == Serialization::Mode::Read) value.mCharBuffer.resize(value.mCharDataSize); visitor(*this, value.mCharBuffer.data(), value.mCharDataSize); } }; } #endif openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/integration.cpp000066400000000000000000000042051445372753700270610ustar00rootroot00000000000000#include "format.hpp" #include #include #include #include #include #include namespace { using namespace testing; using namespace Serialization; using namespace SerializationTesting; struct DetourNavigatorSerializationIntegrationTest : Test { Composite mComposite; DetourNavigatorSerializationIntegrationTest() { mComposite.mIntVector = {4, 5, 6}; mComposite.mEnumVector = {Enum::A, Enum::B, Enum::C}; mComposite.mPodVector = {Pod {4, 23.87}, Pod {5, -31.76}, Pod {6, 65.12}}; mComposite.mPodBuffer = {Pod {7, 456.123}, Pod {8, -628.346}}; mComposite.mPodDataSize = mComposite.mPodBuffer.size(); std::string charData = "serialization"; mComposite.mCharBuffer = {charData.begin(), charData.end()}; mComposite.mCharDataSize = charData.size(); } }; TEST_F(DetourNavigatorSerializationIntegrationTest, sizeAccumulatorShouldSupportCustomSerializer) { SizeAccumulator sizeAccumulator; TestFormat{}(sizeAccumulator, mComposite); EXPECT_EQ(sizeAccumulator.value(), 143); } TEST_F(DetourNavigatorSerializationIntegrationTest, binaryReaderShouldDeserializeDataWrittenByBinaryWriter) { std::vector data(143); TestFormat{}(BinaryWriter(data.data(), data.data() + data.size()), mComposite); Composite result; TestFormat{}(BinaryReader(data.data(), data.data() + data.size()), result); EXPECT_EQ(result.mIntVector, mComposite.mIntVector); EXPECT_EQ(result.mEnumVector, mComposite.mEnumVector); EXPECT_EQ(result.mPodVector, mComposite.mPodVector); EXPECT_EQ(result.mPodDataSize, mComposite.mPodDataSize); EXPECT_EQ(result.mPodBuffer, mComposite.mPodBuffer); EXPECT_EQ(result.mCharDataSize, mComposite.mCharDataSize); EXPECT_EQ(result.mCharBuffer, mComposite.mCharBuffer); } } openmw-openmw-0.48.0/apps/openmw_test_suite/serialization/sizeaccumulator.cpp000066400000000000000000000023261445372753700277520ustar00rootroot00000000000000#include "format.hpp" #include #include #include #include #include #include namespace { using namespace testing; using namespace Serialization; using namespace SerializationTesting; TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType) { SizeAccumulator sizeAccumulator; constexpr std::monostate format; sizeAccumulator(format, std::uint32_t()); EXPECT_EQ(sizeAccumulator.value(), 4); } TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticTypeRange) { SizeAccumulator sizeAccumulator; const std::uint64_t* const data = nullptr; const std::size_t count = 3; const std::monostate format; sizeAccumulator(format, data, count); EXPECT_EQ(sizeAccumulator.value(), 24); } TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldSupportCustomSerializer) { SizeAccumulator sizeAccumulator; const TestFormat format; sizeAccumulator(format, Pod {}); EXPECT_EQ(sizeAccumulator.value(), 12); } } openmw-openmw-0.48.0/apps/openmw_test_suite/settings/000077500000000000000000000000001445372753700230145ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/settings/parser.cpp000066400000000000000000000267621445372753700250310ustar00rootroot00000000000000#include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace Settings; struct SettingsFileParserTest : Test { SettingsFileParser mLoader; SettingsFileParser mSaver; template void withSettingsFile( const std::string& content, F&& f) { std::string path = TestingOpenMW::outputFilePath( std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".cfg"); { boost::filesystem::ofstream stream(path); stream << content; stream.close(); } f(path); } }; TEST_F(SettingsFileParserTest, load_empty_file) { const std::string content; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap()); }); } TEST_F(SettingsFileParserTest, file_with_single_empty_section) { const std::string content = "[Section]\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap()); }); } TEST_F(SettingsFileParserTest, file_with_single_section_and_key) { const std::string content = "[Section]\n" "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), "value"} })); }); } TEST_F(SettingsFileParserTest, file_with_single_section_and_key_and_line_comments) { const std::string content = "# foo\n" "[Section]\n" "# bar\n" "key = value\n" "# baz\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), "value"} })); }); } TEST_F(SettingsFileParserTest, file_with_single_section_and_key_file_and_inline_section_comment) { const std::string content = "[Section] # foo\n" "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_single_section_and_key_and_inline_key_comment) { const std::string content = "[Section]\n" "key = value # foo\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), "value # foo"} })); }); } TEST_F(SettingsFileParserTest, file_with_single_section_and_key_and_whitespaces) { const std::string content = " [ Section ] \n" " key = value \n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), "value"} })); }); } TEST_F(SettingsFileParserTest, file_with_quoted_string_value) { const std::string content = "[Section]\n" R"(key = "value")" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), R"("value")"} })); }); } TEST_F(SettingsFileParserTest, file_with_quoted_string_value_and_eol) { const std::string content = "[Section]\n" R"(key = "value"\n)" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), R"("value"\n)"} })); }); } TEST_F(SettingsFileParserTest, file_with_empty_value) { const std::string content = "[Section]\n" "key =\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), ""} })); }); } TEST_F(SettingsFileParserTest, file_with_empty_key) { const std::string content = "[Section]\n" "=\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", ""), ""} })); }); } TEST_F(SettingsFileParserTest, file_with_multiple_keys) { const std::string content = "[Section]\n" "key1 = value1\n" "key2 = value2\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key1"), "value1"}, {CategorySetting("Section", "key2"), "value2"}, })); }); } TEST_F(SettingsFileParserTest, file_with_multiple_sections) { const std::string content = "[Section1]\n" "key1 = value1\n" "[Section2]\n" "key2 = value2\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section1", "key1"), "value1"}, {CategorySetting("Section2", "key2"), "value2"}, })); }); } TEST_F(SettingsFileParserTest, file_with_multiple_sections_and_keys) { const std::string content = "[Section1]\n" "key1 = value1\n" "key2 = value2\n" "[Section2]\n" "key3 = value3\n" "key4 = value4\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section1", "key1"), "value1"}, {CategorySetting("Section1", "key2"), "value2"}, {CategorySetting("Section2", "key3"), "value3"}, {CategorySetting("Section2", "key4"), "value4"}, })); }); } TEST_F(SettingsFileParserTest, file_with_repeated_sections) { const std::string content = "[Section]\n" "key1 = value1\n" "[Section]\n" "key2 = value2\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key1"), "value1"}, {CategorySetting("Section", "key2"), "value2"}, })); }); } TEST_F(SettingsFileParserTest, file_with_repeated_keys) { const std::string content = "[Section]\n" "key = value\n" "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_with_repeated_keys_in_differrent_sections) { const std::string content = "[Section1]\n" "key = value\n" "[Section2]\n" "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section1", "key"), "value"}, {CategorySetting("Section2", "key"), "value"}, })); }); } TEST_F(SettingsFileParserTest, file_with_unterminated_section) { const std::string content = "[Section" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_with_single_empty_section_name) { const std::string content = "[]\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap()); }); } TEST_F(SettingsFileParserTest, file_with_key_and_without_section) { const std::string content = "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_with_key_in_empty_name_section) { const std::string content = "[]" "key = value\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_with_unterminated_key) { const std::string content = "[Section]\n" "key\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); }); } TEST_F(SettingsFileParserTest, file_with_empty_lines) { const std::string content = "\n" "[Section]\n" "\n" "key = value\n" "\n" ; withSettingsFile(content, [this] (const auto& path) { CategorySettingValueMap map; mLoader.loadSettingsFile(path, map); EXPECT_EQ(map, CategorySettingValueMap({ {CategorySetting("Section", "key"), "value"} })); }); } } openmw-openmw-0.48.0/apps/openmw_test_suite/settings/shadermanager.cpp000066400000000000000000000041631445372753700263250ustar00rootroot00000000000000#include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace Settings; struct ShaderSettingsTest : Test { template void withSettingsFile( const std::string& content, F&& f) { std::string path = TestingOpenMW::outputFilePath( std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".yaml"); { boost::filesystem::ofstream stream; stream.open(path); stream << content; stream.close(); } f(path); } }; TEST_F(ShaderSettingsTest, fail_to_fetch_then_set_and_succeed) { const std::string content = R"YAML( config: shader: vec3_uniform: [1.0, 2.0] )YAML"; withSettingsFile(content, [] (const auto& path) { EXPECT_TRUE(ShaderManager::get().load(path)); EXPECT_FALSE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); EXPECT_TRUE(ShaderManager::get().setValue("shader", "vec3_uniform", osg::Vec3f(1, 2, 3))); EXPECT_TRUE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); EXPECT_EQ(ShaderManager::get().getValue("shader", "vec3_uniform").value(), osg::Vec3f(1, 2, 3)); EXPECT_TRUE(ShaderManager::get().save()); }); } TEST_F(ShaderSettingsTest, fail_to_load_file_then_fail_to_set_and_get) { const std::string content = R"YAML( config: shader: uniform: 12.0 >Defeated by a sideways carrot )YAML"; withSettingsFile(content, [] (const auto& path) { EXPECT_FALSE(ShaderManager::get().load(path)); EXPECT_FALSE(ShaderManager::get().setValue("shader", "uniform", 12.0)); EXPECT_FALSE(ShaderManager::get().getValue("shader", "uniform").has_value()); EXPECT_FALSE(ShaderManager::get().save()); }); } } openmw-openmw-0.48.0/apps/openmw_test_suite/shader/000077500000000000000000000000001445372753700224225ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/shader/parsedefines.cpp000066400000000000000000000146221445372753700256030ustar00rootroot00000000000000#include #include #include namespace { using namespace testing; using namespace Shader; using DefineMap = ShaderManager::DefineMap; struct ShaderParseDefinesTest : Test { std::string mSource; const std::string mName = "shader"; DefineMap mDefines; DefineMap mGlobalDefines; }; TEST_F(ShaderParseDefinesTest, empty_should_succeed) { ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, ""); } TEST_F(ShaderParseDefinesTest, should_fail_for_absent_define) { mSource = "@foo\n"; ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "@foo\n"); } TEST_F(ShaderParseDefinesTest, should_replace_by_existing_define) { mDefines["foo"] = "42"; mSource = "@foo\n"; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42\n"); } TEST_F(ShaderParseDefinesTest, should_replace_by_existing_global_define) { mGlobalDefines["foo"] = "42"; mSource = "@foo\n"; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42\n"); } TEST_F(ShaderParseDefinesTest, should_prefer_define_over_global_define) { mDefines["foo"] = "13"; mGlobalDefines["foo"] = "42"; mSource = "@foo\n"; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "13\n"); } namespace SupportedTerminals { struct ShaderParseDefinesTest : ::ShaderParseDefinesTest, WithParamInterface {}; TEST_P(ShaderParseDefinesTest, support_defines_terminated_by) { mDefines["foo"] = "13"; mSource = "@foo" + std::string(1, GetParam()); ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "13" + std::string(1, GetParam())); } INSTANTIATE_TEST_SUITE_P( SupportedTerminals, ShaderParseDefinesTest, Values(' ', '\n', '\r', '(', ')', '[', ']', '.', ';', ',') ); } TEST_F(ShaderParseDefinesTest, should_not_support_define_ending_with_source) { mDefines["foo"] = "42"; mSource = "@foo"; ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "@foo"); } TEST_F(ShaderParseDefinesTest, should_replace_all_matched_values) { mDefines["foo"] = "42"; mSource = "@foo @foo "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42 42 "); } TEST_F(ShaderParseDefinesTest, should_support_define_with_empty_name) { mDefines[""] = "42"; mSource = "@ "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42 "); } TEST_F(ShaderParseDefinesTest, should_replace_all_found_defines) { mDefines["foo"] = "42"; mDefines["bar"] = "13"; mDefines["baz"] = "55"; mSource = "@foo @bar "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42 13 "); } TEST_F(ShaderParseDefinesTest, should_fail_on_foreach_without_endforeach) { mSource = "@foreach "; ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach "); } TEST_F(ShaderParseDefinesTest, should_fail_on_endforeach_without_foreach) { mSource = "@endforeach "; ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$endforeach "); } TEST_F(ShaderParseDefinesTest, should_replace_at_sign_by_dollar_for_foreach_endforeach) { mSource = "@foreach @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach $endforeach "); } TEST_F(ShaderParseDefinesTest, should_succeed_on_unmatched_nested_foreach) { mSource = "@foreach @foreach @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach $foreach $endforeach "); } TEST_F(ShaderParseDefinesTest, should_fail_on_unmatched_nested_endforeach) { mSource = "@foreach @endforeach @endforeach "; ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach $endforeach $endforeach "); } TEST_F(ShaderParseDefinesTest, should_support_nested_foreach) { mSource = "@foreach @foreach @endforeach @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach $foreach $endforeach $endforeach "); } TEST_F(ShaderParseDefinesTest, should_support_foreach_variable) { mSource = "@foreach foo @foo @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach foo $foo $endforeach "); } TEST_F(ShaderParseDefinesTest, should_not_replace_foreach_variable_by_define) { mDefines["foo"] = "42"; mSource = "@foreach foo @foo @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach foo $foo $endforeach "); } TEST_F(ShaderParseDefinesTest, should_support_nested_foreach_with_variable) { mSource = "@foreach foo @foo @foreach bar @bar @endforeach @endforeach "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "$foreach foo $foo $foreach bar $bar $endforeach $endforeach "); } TEST_F(ShaderParseDefinesTest, should_not_support_single_line_comments_for_defines) { mDefines["foo"] = "42"; mSource = "@foo // @foo\n"; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "42 // 42\n"); } TEST_F(ShaderParseDefinesTest, should_not_support_multiline_comments_for_defines) { mDefines["foo"] = "42"; mSource = "/* @foo */ @foo "; ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); EXPECT_EQ(mSource, "/* 42 */ 42 "); } } openmw-openmw-0.48.0/apps/openmw_test_suite/shader/parsefors.cpp000066400000000000000000000060211445372753700251310ustar00rootroot00000000000000#include #include #include #include namespace { using namespace testing; using namespace Shader; using DefineMap = ShaderManager::DefineMap; struct ShaderParseForsTest : Test { std::string mSource; const std::string mName = "shader"; }; static bool parseFors(std::string& source, const std::string& templateName) { std::vector dummy; return parseDirectives(source, dummy, {}, {}, templateName); } TEST_F(ShaderParseForsTest, empty_should_succeed) { ASSERT_TRUE(parseFors(mSource, mName)); EXPECT_EQ(mSource, ""); } TEST_F(ShaderParseForsTest, should_fail_for_single_escape_symbol) { mSource = "$"; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$"); } TEST_F(ShaderParseForsTest, should_fail_on_first_found_escaped_not_foreach) { mSource = "$foo "; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$foo "); } TEST_F(ShaderParseForsTest, should_fail_on_absent_foreach_variable) { mSource = "$foreach "; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$foreach "); } TEST_F(ShaderParseForsTest, should_fail_on_unmatched_after_variable) { mSource = "$foreach foo "; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$foreach foo "); } TEST_F(ShaderParseForsTest, should_fail_on_absent_newline_after_foreach_list) { mSource = "$foreach foo 1,2,3 "; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$foreach foo 1,2,3 "); } TEST_F(ShaderParseForsTest, should_fail_on_absent_endforeach_after_newline) { mSource = "$foreach foo 1,2,3\n"; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "$foreach foo 1,2,3\n"); } TEST_F(ShaderParseForsTest, should_replace_complete_foreach_by_line_number) { mSource = "$foreach foo 1,2,3\n$endforeach"; ASSERT_TRUE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "\n#line 3"); } TEST_F(ShaderParseForsTest, should_replace_loop_variable) { mSource = "$foreach foo 1,2,3\n$foo\n$endforeach"; ASSERT_TRUE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "1\n2\n3\n\n#line 4"); } TEST_F(ShaderParseForsTest, should_count_line_number_from_existing) { mSource = "$foreach foo 1,2,3\n#line 10\n$foo\n$endforeach"; ASSERT_TRUE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "#line 10\n1\n#line 10\n2\n#line 10\n3\n\n#line 12"); } TEST_F(ShaderParseForsTest, should_not_support_nested_loops) { mSource = "$foreach foo 1,2\n$foo\n$foreach bar 1,2\n$bar\n$endforeach\n$endforeach"; ASSERT_FALSE(parseFors(mSource, mName)); EXPECT_EQ(mSource, "1\n1\n2\n$foreach bar 1,2\n1\n\n#line 6\n2\n2\n$foreach bar 1,2\n2\n\n#line 6\n\n#line 7"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/shader/parselinks.cpp000066400000000000000000000050301445372753700252770ustar00rootroot00000000000000#include #include #include #include namespace { using namespace testing; using namespace Shader; using DefineMap = ShaderManager::DefineMap; struct ShaderParseLinksTest : Test { std::string mSource; std::vector mLinkTargets; ShaderManager::DefineMap mDefines; const std::string mName = "my_shader.glsl"; bool parseLinks() { return parseDirectives(mSource, mLinkTargets, mDefines, {}, mName); } }; TEST_F(ShaderParseLinksTest, empty_should_succeed) { ASSERT_TRUE(parseLinks()); EXPECT_EQ(mSource, ""); EXPECT_TRUE(mLinkTargets.empty()); } TEST_F(ShaderParseLinksTest, should_fail_for_single_escape_symbol) { mSource = "$"; ASSERT_FALSE(parseLinks()); EXPECT_EQ(mSource, "$"); EXPECT_TRUE(mLinkTargets.empty()); } TEST_F(ShaderParseLinksTest, should_fail_on_first_found_escaped_not_valid_directive) { mSource = "$foo "; ASSERT_FALSE(parseLinks()); EXPECT_EQ(mSource, "$foo "); EXPECT_TRUE(mLinkTargets.empty()); } TEST_F(ShaderParseLinksTest, should_fail_on_absent_link_target) { mSource = "$link "; ASSERT_FALSE(parseLinks()); EXPECT_EQ(mSource, "$link "); EXPECT_TRUE(mLinkTargets.empty()); } TEST_F(ShaderParseLinksTest, should_not_require_newline) { mSource = "$link \"foo.glsl\""; ASSERT_TRUE(parseLinks()); EXPECT_EQ(mSource, ""); ASSERT_EQ(mLinkTargets.size(), 1); EXPECT_EQ(mLinkTargets[0], "foo.glsl"); } TEST_F(ShaderParseLinksTest, should_require_quotes) { mSource = "$link foo.glsl"; ASSERT_FALSE(parseLinks()); EXPECT_EQ(mSource, "$link foo.glsl"); EXPECT_EQ(mLinkTargets.size(), 0); } TEST_F(ShaderParseLinksTest, should_be_replaced_with_empty_line) { mSource = "$link \"foo.glsl\"\nbar"; ASSERT_TRUE(parseLinks()); EXPECT_EQ(mSource, "\nbar"); ASSERT_EQ(mLinkTargets.size(), 1); EXPECT_EQ(mLinkTargets[0], "foo.glsl"); } TEST_F(ShaderParseLinksTest, should_only_accept_on_true_condition) { mSource = R"glsl( $link "foo.glsl" if 1 $link "bar.glsl" if 0 )glsl"; ASSERT_TRUE(parseLinks()); EXPECT_EQ(mSource, R"glsl( )glsl"); ASSERT_EQ(mLinkTargets.size(), 1); EXPECT_EQ(mLinkTargets[0], "foo.glsl"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/shader/shadermanager.cpp000066400000000000000000000174061445372753700257370ustar00rootroot00000000000000#include #include #include #include "../testing_util.hpp" namespace { using namespace testing; using namespace Shader; struct ShaderManagerTest : Test { ShaderManager mManager; ShaderManager::DefineMap mDefines; ShaderManagerTest() { mManager.setShaderPath("."); } template void withShaderFile(const std::string& content, F&& f) { withShaderFile("", content, std::forward(f)); } template void withShaderFile(const std::string& suffix, const std::string& content, F&& f) { std::string path = TestingOpenMW::outputFilePath( std::string(UnitTest::GetInstance()->current_test_info()->name()) + suffix + ".glsl"); { boost::filesystem::ofstream stream(path); stream << content; stream.close(); } f(path); } }; TEST_F(ShaderManagerTest, get_shader_with_empty_content_should_succeed) { const std::string content; withShaderFile(content, [this] (const std::string& templateName) { EXPECT_TRUE(mManager.getShader(templateName, {}, osg::Shader::VERTEX)); }); } TEST_F(ShaderManagerTest, get_shader_should_not_change_source_without_template_parameters) { const std::string content = "#version 120\n" "void main() {}\n"; withShaderFile(content, [&] (const std::string& templateName) { const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); ASSERT_TRUE(shader); EXPECT_EQ(shader->getShaderSource(), content); }); } TEST_F(ShaderManagerTest, get_shader_should_replace_includes_with_content) { const std::string content0 = "void foo() {}\n"; withShaderFile("_0", content0, [&] (const std::string& templateName0) { const std::string content1 = "#include \"" + templateName0 + "\"\n" "void bar() { foo() }\n"; withShaderFile("_1", content1, [&] (const std::string& templateName1) { const std::string content2 = "#version 120\n" "#include \"" + templateName1 + "\"\n" "void main() { bar() }\n"; withShaderFile(content2, [&] (const std::string& templateName2) { const auto shader = mManager.getShader(templateName2, mDefines, osg::Shader::VERTEX); ASSERT_TRUE(shader); const std::string expected = "#version 120\n" "#line 0 1\n" "#line 0 2\n" "void foo() {}\n" "\n" "#line 0 0\n" "\n" "void bar() { foo() }\n" "\n" "#line 1 0\n" "\n" "void main() { bar() }\n"; EXPECT_EQ(shader->getShaderSource(), expected); }); }); }); } TEST_F(ShaderManagerTest, get_shader_should_replace_defines) { const std::string content = "#version 120\n" "#define FLAG @flag\n" "void main() {}\n" ; withShaderFile(content, [&] (const std::string& templateName) { mDefines["flag"] = "1"; const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); ASSERT_TRUE(shader); const std::string expected = "#version 120\n" "#define FLAG 1\n" "void main() {}\n"; EXPECT_EQ(shader->getShaderSource(), expected); }); } TEST_F(ShaderManagerTest, get_shader_should_expand_loop) { const std::string content = "#version 120\n" "@foreach index @list\n" " varying vec4 foo@index;\n" "@endforeach\n" "void main() {}\n" ; withShaderFile(content, [&] (const std::string& templateName) { mDefines["list"] = "1,2,3"; const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); ASSERT_TRUE(shader); const std::string expected = "#version 120\n" " varying vec4 foo1;\n" " varying vec4 foo2;\n" " varying vec4 foo3;\n" "\n" "#line 5\n" "void main() {}\n"; EXPECT_EQ(shader->getShaderSource(), expected); }); } TEST_F(ShaderManagerTest, get_shader_should_replace_loops_with_conditions) { const std::string content = "#version 120\n" "@foreach index @list\n" " varying vec4 foo@index;\n" "@endforeach\n" "void main()\n" "{\n" "#ifdef BAR\n" "@foreach index @list\n" " foo@index = vec4(1.0);\n" "@endforeach\n" "#elif BAZ\n" "@foreach index @list\n" " foo@index = vec4(2.0);\n" "@endforeach\n" "#else\n" "@foreach index @list\n" " foo@index = vec4(3.0);\n" "@endforeach\n" "#endif\n" "}\n" ; withShaderFile(content, [&] (const std::string& templateName) { mDefines["list"] = "1,2,3"; const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); ASSERT_TRUE(shader); const std::string expected = "#version 120\n" " varying vec4 foo1;\n" " varying vec4 foo2;\n" " varying vec4 foo3;\n" "\n" "#line 5\n" "void main()\n" "{\n" "#ifdef BAR\n" " foo1 = vec4(1.0);\n" " foo2 = vec4(1.0);\n" " foo3 = vec4(1.0);\n" "\n" "#line 11\n" "#elif BAZ\n" "#line 12\n" " foo1 = vec4(2.0);\n" " foo2 = vec4(2.0);\n" " foo3 = vec4(2.0);\n" "\n" "#line 15\n" "#else\n" "#line 16\n" " foo1 = vec4(3.0);\n" " foo2 = vec4(3.0);\n" " foo3 = vec4(3.0);\n" "\n" "#line 19\n" "#endif\n" "#line 20\n" "}\n"; EXPECT_EQ(shader->getShaderSource(), expected); }); } TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameters_in_single_line_comments) { const std::string content = "#version 120\n" "// #define FLAG @flag\n" "void main() {}\n" ; withShaderFile(content, [&] (const std::string& templateName) { EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX)); }); } TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameter_in_multi_line_comments) { const std::string content = "#version 120\n" "/* #define FLAG @flag */\n" "void main() {}\n" ; withShaderFile(content, [&] (const std::string& templateName) { EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX)); }); } } openmw-openmw-0.48.0/apps/openmw_test_suite/sqlite3/000077500000000000000000000000001445372753700225405ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/sqlite3/db.cpp000066400000000000000000000005051445372753700236310ustar00rootroot00000000000000#include #include namespace { using namespace testing; using namespace Sqlite3; TEST(Sqlite3DbTest, makeDbShouldCreateInMemoryDbWithSchema) { const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); EXPECT_NE(db, nullptr); } } openmw-openmw-0.48.0/apps/openmw_test_suite/sqlite3/request.cpp000066400000000000000000000225051445372753700247400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace Sqlite3; template struct InsertInt { static std::string_view text() noexcept { return "INSERT INTO ints (value) VALUES (:value)"; } static void bind(sqlite3& db, sqlite3_stmt& statement, T value) { bindParameter(db, statement, ":value", value); } }; struct InsertReal { static std::string_view text() noexcept { return "INSERT INTO reals (value) VALUES (:value)"; } static void bind(sqlite3& db, sqlite3_stmt& statement, double value) { bindParameter(db, statement, ":value", value); } }; struct InsertText { static std::string_view text() noexcept { return "INSERT INTO texts (value) VALUES (:value)"; } static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view value) { bindParameter(db, statement, ":value", value); } }; struct InsertBlob { static std::string_view text() noexcept { return "INSERT INTO blobs (value) VALUES (:value)"; } static void bind(sqlite3& db, sqlite3_stmt& statement, const std::vector& value) { bindParameter(db, statement, ":value", value); } }; struct GetAll { std::string mQuery; explicit GetAll(const std::string& table) : mQuery("SELECT value FROM " + table + " ORDER BY value") {} std::string_view text() noexcept { return mQuery; } static void bind(sqlite3&, sqlite3_stmt&) {} }; template struct GetExact { std::string mQuery; explicit GetExact(const std::string& table) : mQuery("SELECT value FROM " + table + " WHERE value = :value") {} std::string_view text() noexcept { return mQuery; } static void bind(sqlite3& db, sqlite3_stmt& statement, const T& value) { bindParameter(db, statement, ":value", value); } }; struct GetInt64 { static std::string_view text() noexcept { return "SELECT value FROM ints WHERE value = :value"; } static void bind(sqlite3& db, sqlite3_stmt& statement, std::int64_t value) { bindParameter(db, statement, ":value", value); } }; struct GetNull { static std::string_view text() noexcept { return "SELECT NULL"; } static void bind(sqlite3&, sqlite3_stmt&) {} }; struct Int { int mValue = 0; Int() = default; explicit Int(int value) : mValue(value) {} Int& operator=(int value) { mValue = value; return *this; } friend bool operator==(const Int& l, const Int& r) { return l.mValue == r.mValue; } }; constexpr const char schema[] = R"( CREATE TABLE ints ( value INTEGER ); CREATE TABLE reals ( value REAL ); CREATE TABLE texts ( value TEXT ); CREATE TABLE blobs ( value BLOB ); )"; struct Sqlite3RequestTest : Test { const Db mDb = makeDb(":memory:", schema); }; TEST_F(Sqlite3RequestTest, executeShouldSupportInt) { Statement insert(*mDb, InsertInt {}); EXPECT_EQ(execute(*mDb, insert, 13), 1); EXPECT_EQ(execute(*mDb, insert, 42), 1); Statement select(*mDb, GetAll("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(13), std::tuple(42))); } TEST_F(Sqlite3RequestTest, executeShouldSupportInt64) { Statement insert(*mDb, InsertInt {}); const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetAll("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } TEST_F(Sqlite3RequestTest, executeShouldSupportReal) { Statement insert(*mDb, InsertReal {}); EXPECT_EQ(execute(*mDb, insert, 3.14), 1); Statement select(*mDb, GetAll("reals")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(3.14))); } TEST_F(Sqlite3RequestTest, executeShouldSupportText) { Statement insert(*mDb, InsertText {}); const std::string text = "foo"; EXPECT_EQ(execute(*mDb, insert, text), 1); Statement select(*mDb, GetAll("texts")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(text))); } TEST_F(Sqlite3RequestTest, executeShouldSupportBlob) { Statement insert(*mDb, InsertBlob {}); const std::vector blob({std::byte(42), std::byte(13)}); EXPECT_EQ(execute(*mDb, insert, blob), 1); Statement select(*mDb, GetAll("blobs")); std::vector>> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(blob))); } TEST_F(Sqlite3RequestTest, requestShouldSupportInt) { Statement insert(*mDb, InsertInt {}); const int value = 42; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } TEST_F(Sqlite3RequestTest, requestShouldSupportInt64) { Statement insert(*mDb, InsertInt {}); const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } TEST_F(Sqlite3RequestTest, requestShouldSupportReal) { Statement insert(*mDb, InsertReal {}); const double value = 3.14; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("reals")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } TEST_F(Sqlite3RequestTest, requestShouldSupportText) { Statement insert(*mDb, InsertText {}); const std::string text = "foo"; EXPECT_EQ(execute(*mDb, insert, text), 1); Statement select(*mDb, GetExact("texts")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), text); EXPECT_THAT(result, ElementsAre(std::tuple(text))); } TEST_F(Sqlite3RequestTest, requestShouldSupportBlob) { Statement insert(*mDb, InsertBlob {}); const std::vector blob({std::byte(42), std::byte(13)}); EXPECT_EQ(execute(*mDb, insert, blob), 1); Statement select(*mDb, GetExact>("blobs")); std::vector>> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), blob); EXPECT_THAT(result, ElementsAre(std::tuple(blob))); } TEST_F(Sqlite3RequestTest, requestResultShouldSupportNull) { Statement select(*mDb, GetNull {}); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(nullptr))); } TEST_F(Sqlite3RequestTest, requestResultShouldSupportConstructibleFromInt) { Statement insert(*mDb, InsertInt {}); const int value = 42; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(Int(value)))); } TEST_F(Sqlite3RequestTest, requestShouldLimitOutput) { Statement insert(*mDb, InsertInt {}); EXPECT_EQ(execute(*mDb, insert, 13), 1); EXPECT_EQ(execute(*mDb, insert, 42), 1); Statement select(*mDb, GetAll("ints")); std::vector> result; request(*mDb, select, std::back_inserter(result), 1); EXPECT_THAT(result, ElementsAre(std::tuple(13))); } } openmw-openmw-0.48.0/apps/openmw_test_suite/sqlite3/statement.cpp000066400000000000000000000013051445372753700252470ustar00rootroot00000000000000#include #include #include namespace { using namespace testing; using namespace Sqlite3; struct Query { static std::string_view text() noexcept { return "SELECT 1"; } static void bind(sqlite3&, sqlite3_stmt&) {} }; TEST(Sqlite3StatementTest, makeStatementShouldCreateStatementWithPreparedQuery) { const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); const Statement statement(*db, Query {}); EXPECT_FALSE(statement.mNeedReset); EXPECT_NE(statement.mHandle, nullptr); EXPECT_EQ(statement.mQuery.text(), "SELECT 1"); } } openmw-openmw-0.48.0/apps/openmw_test_suite/sqlite3/transaction.cpp000066400000000000000000000033341445372753700255740ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include namespace { using namespace testing; using namespace Sqlite3; struct InsertId { static std::string_view text() noexcept { return "INSERT INTO test (id) VALUES (42)"; } static void bind(sqlite3&, sqlite3_stmt&) {} }; struct GetIds { static std::string_view text() noexcept { return "SELECT id FROM test"; } static void bind(sqlite3&, sqlite3_stmt&) {} }; struct Sqlite3TransactionTest : Test { const Db mDb = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); void insertId() const { Statement insertId(*mDb, InsertId {}); EXPECT_EQ(execute(*mDb, insertId), 1); } std::vector> getIds() const { Statement getIds(*mDb, GetIds {}); std::vector> result; request(*mDb, getIds, std::back_inserter(result), std::numeric_limits::max()); return result; } }; TEST_F(Sqlite3TransactionTest, shouldRollbackOnDestruction) { { const Transaction transaction(*mDb); insertId(); } EXPECT_THAT(getIds(), IsEmpty()); } TEST_F(Sqlite3TransactionTest, commitShouldCommitTransaction) { { Transaction transaction(*mDb); insertId(); transaction.commit(); } EXPECT_THAT(getIds(), ElementsAre(std::tuple(42))); } } openmw-openmw-0.48.0/apps/openmw_test_suite/testing_util.hpp000066400000000000000000000041471445372753700244050ustar00rootroot00000000000000#ifndef TESTING_UTIL_H #define TESTING_UTIL_H #include #include #include #include #include namespace TestingOpenMW { inline std::string outputFilePath(const std::string name) { boost::filesystem::path dir("tests_output"); boost::filesystem::create_directory(dir); return (dir / name).string(); } inline std::string temporaryFilePath(const std::string name) { return (boost::filesystem::temp_directory_path() / name).string(); } class VFSTestFile : public VFS::File { public: explicit VFSTestFile(std::string content) : mContent(std::move(content)) {} Files::IStreamPtr open() override { return std::make_unique(mContent, std::ios_base::in); } std::string getPath() override { return "TestFile"; } private: const std::string mContent; }; struct VFSTestData : public VFS::Archive { std::map mFiles; VFSTestData(std::map files) : mFiles(std::move(files)) {} void listResources(std::map& out, char (*normalize_function) (char)) override { out = mFiles; } bool contains(const std::string& file, char (*normalize_function) (char)) const override { return mFiles.count(file) != 0; } std::string getDescription() const override { return "TestData"; } }; inline std::unique_ptr createTestVFS(std::map files) { auto vfs = std::make_unique(true); vfs->addArchive(std::make_unique(std::move(files))); vfs->buildIndex(); return vfs; } #define EXPECT_ERROR(X, ERR_SUBSTR) try { X; FAIL() << "Expected error"; } \ catch (std::exception& e) { EXPECT_THAT(e.what(), ::testing::HasSubstr(ERR_SUBSTR)); } } #endif // TESTING_UTIL_H openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/000077500000000000000000000000001445372753700224055ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/data/000077500000000000000000000000001445372753700233165ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/data/french-utf8.txt000066400000000000000000000001531445372753700262070ustar00rootroot00000000000000Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger.openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/data/french-win1252.txt000066400000000000000000000001501445372753700264250ustar00rootroot00000000000000Vous lui donnez le gteau sans protester avant daller chercher tous vos amis et de revenir vous venger.openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/data/russian-utf8.txt000066400000000000000000000003311445372753700264240ustar00rootroot00000000000000Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам?openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/data/russian-win1251.txt000066400000000000000000000001701445372753700266450ustar00rootroot00000000000000 , , ?openmw-openmw-0.48.0/apps/openmw_test_suite/toutf8/toutf8.cpp000066400000000000000000000133451445372753700243500ustar00rootroot00000000000000#include #include #include #ifndef OPENMW_TEST_SUITE_SOURCE_DIR #define OPENMW_TEST_SUITE_SOURCE_DIR "" #endif namespace { using namespace testing; using namespace ToUTF8; struct Params { FromType mLegacyEncoding; std::string mLegacyEncodingFileName; std::string mUtf8FileName; }; std::string readContent(const std::string& fileName) { boost::filesystem::ifstream file; file.exceptions(boost::filesystem::fstream::failbit | boost::filesystem::fstream::badbit); file.open(std::string(OPENMW_TEST_SUITE_SOURCE_DIR) + "/toutf8/data/" + fileName); std::stringstream buffer; buffer << file.rdbuf(); return buffer.str(); } struct Utf8EncoderTest : TestWithParam {}; TEST(Utf8EncoderTest, getUtf8ShouldReturnEmptyAsIs) { Utf8Encoder encoder(FromType::CP437); EXPECT_EQ(encoder.getUtf8(std::string_view()), std::string_view()); } TEST(Utf8EncoderTest, getUtf8ShouldReturnAsciiOnlyAsIs) { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) input.push_back(c); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result.data(), input.data()); EXPECT_EQ(result.size(), input.size()); } TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilZero) { const std::string input("a\0b"); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result, "a"); } TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForAscii) { const std::string input("abc"); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); EXPECT_EQ(result, "ab"); } TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForNonAscii) { const std::string input("a\x92" "b"); Utf8Encoder encoder(FromType::WINDOWS_1252); const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); EXPECT_EQ(result, "a\xE2\x80\x99"); } TEST_P(Utf8EncoderTest, getUtf8ShouldConvertFromLegacyEncodingToUtf8) { const std::string input(readContent(GetParam().mLegacyEncodingFileName)); const std::string expected(readContent(GetParam().mUtf8FileName)); Utf8Encoder encoder(GetParam().mLegacyEncoding); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result, expected); } TEST(Utf8EncoderTest, getLegacyEncShouldReturnEmptyAsIs) { Utf8Encoder encoder(FromType::CP437); EXPECT_EQ(encoder.getLegacyEnc(std::string_view()), std::string_view()); } TEST(Utf8EncoderTest, getLegacyEncShouldReturnAsciiOnlyAsIs) { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) input.push_back(c); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result.data(), input.data()); EXPECT_EQ(result.size(), input.size()); } TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilZero) { const std::string input("a\0b"); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result, "a"); } TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilEndOfInputForAscii) { const std::string input("abc"); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 2)); EXPECT_EQ(result, "ab"); } TEST(Utf8EncoderTest, getLegacyEncShouldStripIncompleteCharacters) { const std::string input("a\xc3\xa2\xe2\x80\x99"); Utf8Encoder encoder(FromType::WINDOWS_1252); const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 5)); EXPECT_EQ(result, "a\xe2"); } TEST_P(Utf8EncoderTest, getLegacyEncShouldConvertFromUtf8ToLegacyEncoding) { const std::string input(readContent(GetParam().mUtf8FileName)); const std::string expected(readContent(GetParam().mLegacyEncodingFileName)); Utf8Encoder encoder(GetParam().mLegacyEncoding); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result, expected); } INSTANTIATE_TEST_SUITE_P(Files, Utf8EncoderTest, Values( Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"}, Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"} )); TEST(StatelessUtf8EncoderTest, shouldCleanupBuffer) { std::string buffer; StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::UseGrowFactor, buffer); const std::string shortString("short\x92"); ASSERT_GT(buffer.size(), shortString.size()); const std::string_view shortUtf8 = encoder.getUtf8(shortString, BufferAllocationPolicy::UseGrowFactor, buffer); ASSERT_GE(buffer.size(), shortUtf8.size()); EXPECT_EQ(buffer[shortUtf8.size()], '\0') << buffer; } TEST(StatelessUtf8EncoderTest, withFitToRequiredSizeShouldResizeBuffer) { std::string buffer; StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); const std::string_view utf8 = encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::FitToRequiredSize, buffer); EXPECT_EQ(buffer.size(), utf8.size()); } } openmw-openmw-0.48.0/apps/wizard/000077500000000000000000000000001445372753700166775ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/wizard/CMakeLists.txt000066400000000000000000000055571445372753700214530ustar00rootroot00000000000000set(WIZARD componentselectionpage.cpp conclusionpage.cpp existinginstallationpage.cpp importpage.cpp inisettings.cpp installationtargetpage.cpp intropage.cpp languageselectionpage.cpp main.cpp mainwizard.cpp methodselectionpage.cpp utils/componentlistwidget.cpp ) if(WIN32) list(APPEND WIZARD ${CMAKE_SOURCE_DIR}/files/windows/openmw-wizard.rc) endif() set(WIZARD_HEADER componentselectionpage.hpp conclusionpage.hpp existinginstallationpage.hpp importpage.hpp inisettings.hpp installationtargetpage.hpp intropage.hpp languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp utils/componentlistwidget.hpp ) set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/languageselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui ) if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) source_group(wizard FILES ${WIZARD} ${WIZARD_HEADER}) set(QT_USE_QTGUI 1) # Set some platform specific settings if(WIN32) set(GUI_TYPE WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if (OPENMW_USE_UNSHIELD) include_directories(${LIBUNSHIELD_INCLUDE_DIRS}) endif() openmw_add_executable(openmw-wizard ${GUI_TYPE} ${WIZARD} ${WIZARD_HEADER} ${RCC_SRCS} ${UI_HDRS} ) target_link_libraries(openmw-wizard components_qt ) target_link_libraries(openmw-wizard Qt5::Widgets Qt5::Core) if (OPENMW_USE_UNSHIELD) target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES}) endif() if(DPKG_PROGRAM) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION games COMPONENT openmw-wizard) endif() if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-wizard gcov) endif() # Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream if (CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(openmw-wizard dl Xt) endif() if (WIN32) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION ".") endif(WIN32) if(USE_QT) set_property(TARGET openmw-wizard PROPERTY AUTOMOC ON) endif(USE_QT) openmw-openmw-0.48.0/apps/wizard/componentselectionpage.cpp000066400000000000000000000137741445372753700241640ustar00rootroot00000000000000#include "componentselectionpage.hpp" #include #include #include #include "mainwizard.hpp" Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); setCommitPage(true); setButtonText(QWizard::CommitButton, tr("&Install")); registerField(QLatin1String("installation.components"), componentsList); connect(componentsList, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(updateButton(QListWidgetItem *))); } void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem*) { if (field(QLatin1String("installation.retailDisc")).toBool() == true) return; // Morrowind is always checked here bool unchecked = true; for (int i =0; i < componentsList->count(); ++i) { QListWidgetItem *item = componentsList->item(i); if (!item) continue; if (item->checkState() == Qt::Checked) { unchecked = false; } } if (unchecked) { setCommitPage(false); setButtonText(QWizard::NextButton, tr("&Skip")); } else { setCommitPage(true); } } void Wizard::ComponentSelectionPage::initializePage() { componentsList->clear(); QString path(field(QLatin1String("installation.path")).toString()); QListWidgetItem *morrowindItem = new QListWidgetItem(QLatin1String("Morrowind")); QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); QListWidgetItem *bloodmoonItem = new QListWidgetItem(QLatin1String("Bloodmoon")); if (field(QLatin1String("installation.retailDisc")).toBool() == true) { morrowindItem->setFlags((morrowindItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); componentsList->addItem(morrowindItem); tribunalItem->setFlags(tribunalItem->flags() | Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); componentsList->addItem(tribunalItem); bloodmoonItem->setFlags(bloodmoonItem->flags() | Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); componentsList->addItem(bloodmoonItem); } else { if (mWizard->mInstallations[path].hasMorrowind) { morrowindItem->setText(tr("Morrowind\t\t(installed)")); morrowindItem->setFlags((morrowindItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { morrowindItem->setText(tr("Morrowind")); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); } componentsList->addItem(morrowindItem); if (mWizard->mInstallations[path].hasTribunal) { tribunalItem->setText(tr("Tribunal\t\t(installed)")); tribunalItem->setFlags((tribunalItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { tribunalItem->setText(tr("Tribunal")); tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); } componentsList->addItem(tribunalItem); if (mWizard->mInstallations[path].hasBloodmoon) { bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); bloodmoonItem->setFlags((bloodmoonItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { bloodmoonItem->setText(tr("Bloodmoon")); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); } componentsList->addItem(bloodmoonItem); } } bool Wizard::ComponentSelectionPage::validatePage() { QStringList components(field(QLatin1String("installation.components")).toStringList()); QString path(field(QLatin1String("installation.path")).toString()); // qDebug() << components << path << mWizard->mInstallations[path]; if (field(QLatin1String("installation.retailDisc")).toBool() == false) { if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) { if (mWizard->mInstallations[path].hasBloodmoon) { QMessageBox msgBox; msgBox.setWindowTitle(tr("About to install Tribunal after Bloodmoon")); msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("

You are about to install Tribunal

\

Bloodmoon is already installed on your computer.

\

However, it is recommended that you install Tribunal before Bloodmoon.

\

Would you like to re-install Bloodmoon?

")); QAbstractButton *reinstallButton = msgBox.addButton(tr("Re-install &Bloodmoon"), QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() == reinstallButton) { // Force reinstallation mWizard->mInstallations[path].hasBloodmoon = false; QList items = componentsList->findItems(QLatin1String("Bloodmoon"), Qt::MatchStartsWith); for (QListWidgetItem *item : items) { item->setText(QLatin1String("Bloodmoon")); item->setCheckState(Qt::Checked); } return true; } } } } return true; } int Wizard::ComponentSelectionPage::nextId() const { #ifdef OPENMW_USE_UNSHIELD if (isCommitPage()) { return MainWizard::Page_Installation; } else { return MainWizard::Page_Import; } #else return MainWizard::Page_Import; #endif } openmw-openmw-0.48.0/apps/wizard/componentselectionpage.hpp000066400000000000000000000011551445372753700241570ustar00rootroot00000000000000#ifndef COMPONENTSELECTIONPAGE_HPP #define COMPONENTSELECTIONPAGE_HPP #include "ui_componentselectionpage.h" namespace Wizard { class MainWizard; class ComponentSelectionPage : public QWizardPage, private Ui::ComponentSelectionPage { Q_OBJECT public: ComponentSelectionPage(QWidget *parent); int nextId() const override; bool validatePage() override; private slots: void updateButton(QListWidgetItem *item); private: MainWizard *mWizard; protected: void initializePage() override; }; } #endif // COMPONENTSELECTIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/conclusionpage.cpp000066400000000000000000000037351445372753700224240ustar00rootroot00000000000000#include "conclusionpage.hpp" #include #include "mainwizard.hpp" Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } void Wizard::ConclusionPage::initializePage() { // Write the path to openmw.cfg if (field(QLatin1String("installation.retailDisc")).toBool() == true) { QString path(field(QLatin1String("installation.path")).toString()); mWizard->addInstallation(path); } if (!mWizard->mError) { if ((field(QLatin1String("installation.retailDisc")).toBool() == true) || (field(QLatin1String("installation.import-settings")).toBool() == true)) { qDebug() << "IMPORT SETTINGS"; mWizard->runSettingsImporter(); } } if (!mWizard->mError) { if (field(QLatin1String("installation.retailDisc")).toBool() == true) { textLabel->setText(tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\

Click Finish to close the Wizard.

")); } else { textLabel->setText(tr("

The OpenMW Wizard successfully modified your existing Morrowind installation.

\

Click Finish to close the Wizard.

")); } } else { textLabel->setText(tr("

The OpenMW Wizard failed to install Morrowind on your computer.

\

Please report any bugs you might have encountered to our \ bug tracker.
Make sure to include the installation log.


")); } } int Wizard::ConclusionPage::nextId() const { return -1; } openmw-openmw-0.48.0/apps/wizard/conclusionpage.hpp000066400000000000000000000007111445372753700224200ustar00rootroot00000000000000#ifndef CONCLUSIONPAGE_HPP #define CONCLUSIONPAGE_HPP #include "ui_conclusionpage.h" namespace Wizard { class MainWizard; class ConclusionPage : public QWizardPage, private Ui::ConclusionPage { Q_OBJECT public: ConclusionPage(QWidget *parent); int nextId() const override; private: MainWizard *mWizard; protected: void initializePage() override; }; } #endif // CONCLUSIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/existinginstallationpage.cpp000066400000000000000000000145311445372753700245200ustar00rootroot00000000000000#include "existinginstallationpage.hpp" #include #include #include #include #include "mainwizard.hpp" Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); // Add a placeholder item to the list of installations QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); emptyItem->setFlags(Qt::NoItemFlags); installationsList->insertItem(0, emptyItem); } void Wizard::ExistingInstallationPage::initializePage() { // Add the available installation paths QStringList paths(mWizard->mInstallations.keys()); // Hide the default item if there are installations to choose from installationsList->item(0)->setHidden(!paths.isEmpty()); for (const QString &path : paths) { if (installationsList->findItems(path, Qt::MatchExactly).isEmpty()) { QListWidgetItem *item = new QListWidgetItem(path); installationsList->addItem(item); } } connect(installationsList, SIGNAL(currentTextChanged(QString)), this, SLOT(textChanged(QString))); connect(installationsList,SIGNAL(itemSelectionChanged()), this, SIGNAL(completeChanged())); } bool Wizard::ExistingInstallationPage::validatePage() { // See if Morrowind.ini is detected, if not, ask the user // It can be missing entirely // Or failed to be detected due to the target being a symlink QString path(field(QLatin1String("installation.path")).toString()); QFile file(mWizard->mInstallations[path].iniPath); if (!file.exists()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind configuration")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ The Wizard needs to update settings in this file.

\ Press \"Browse...\" to specify the location manually.
")); QAbstractButton *browseButton2 = msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); msgBox.exec(); QString iniFile; if (msgBox.clickedButton() == browseButton2) { iniFile = QFileDialog::getOpenFileName( this, QObject::tr("Select configuration file"), QDir::currentPath(), QString(tr("Morrowind configuration file (*.ini)"))); } if (iniFile.isEmpty()) { return false; // Cancel was clicked; } // A proper Morrowind.ini was selected, set it QFileInfo info(iniFile); mWizard->mInstallations[path].iniPath = info.absoluteFilePath(); } return true; } void Wizard::ExistingInstallationPage::on_browseButton_clicked() { QString selectedFile = QFileDialog::getOpenFileName( this, tr("Select Morrowind.esm (located in Data Files)"), QDir::currentPath(), QString(tr("Morrowind master file (Morrowind.esm)")), nullptr, QFileDialog::DontResolveSymlinks); if (selectedFile.isEmpty()) return; QFileInfo info(selectedFile); if (!info.exists()) return; if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind files")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(QObject::tr( "Morrowind.bsa is missing!
\ Make sure your Morrowind installation is complete." )); msgBox.exec(); return; } if (!versionIsOK(info.absolutePath())) { return; } QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); if (items.isEmpty()) { // Path is not yet in the list, add it mWizard->addInstallation(path); // Hide the default item installationsList->item(0)->setHidden(true); QListWidgetItem *item = new QListWidgetItem(path); installationsList->addItem(item); installationsList->setCurrentItem(item); // Select it too } else { installationsList->setCurrentItem(items.first()); } // Update the button emit completeChanged(); } void Wizard::ExistingInstallationPage::textChanged(const QString &text) { // Set the installation path manually, as registerField doesn't work // Because it doesn't accept two widgets operating on a single field if (!text.isEmpty()) mWizard->setField(QLatin1String("installation.path"), text); } bool Wizard::ExistingInstallationPage::isComplete() const { if (installationsList->selectionModel()->hasSelection()) { return true; } else { return false; } } int Wizard::ExistingInstallationPage::nextId() const { return MainWizard::Page_LanguageSelection; } bool Wizard::ExistingInstallationPage::versionIsOK(QString directory_name) { QDir directory = QDir(directory_name); QFileInfoList infoList = directory.entryInfoList(QStringList(QString("Morrowind.bsa"))); if (infoList.size() == 1) { qint64 actualFileSize = infoList.at(0).size(); const qint64 expectedFileSize = 310459500; // Size of Morrowind.bsa in Steam and GOG editions. if (actualFileSize == expectedFileSize) { return true; } QMessageBox msgBox; msgBox.setWindowTitle(QObject::tr("Most recent Morrowind not detected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); msgBox.setText(QObject::tr("
There may be a more recent version of Morrowind available.

\ Do you wish to continue anyway?
")); int ret = msgBox.exec(); if (ret == QMessageBox::Yes) { return true; } return false; } return false; } openmw-openmw-0.48.0/apps/wizard/existinginstallationpage.hpp000066400000000000000000000014541445372753700245250ustar00rootroot00000000000000#ifndef EXISTINGINSTALLATIONPAGE_HPP #define EXISTINGINSTALLATIONPAGE_HPP #include "ui_existinginstallationpage.h" #include namespace Wizard { class MainWizard; class ExistingInstallationPage : public QWizardPage, private Ui::ExistingInstallationPage { Q_OBJECT public: ExistingInstallationPage(QWidget *parent); int nextId() const override; bool isComplete() const override; bool validatePage() override; private slots: void on_browseButton_clicked(); void textChanged(const QString &text); private: MainWizard *mWizard; bool versionIsOK(QString directory_name); protected: void initializePage() override; }; } #endif // EXISTINGINSTALLATIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/importpage.cpp000066400000000000000000000010161445372753700215500ustar00rootroot00000000000000#include "importpage.hpp" #include "mainwizard.hpp" Wizard::ImportPage::ImportPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); registerField(QLatin1String("installation.import-settings"), importCheckBox); registerField(QLatin1String("installation.import-addons"), addonsCheckBox); registerField(QLatin1String("installation.import-fonts"), fontsCheckBox); } int Wizard::ImportPage::nextId() const { return MainWizard::Page_Conclusion; } openmw-openmw-0.48.0/apps/wizard/importpage.hpp000066400000000000000000000005651445372753700215650ustar00rootroot00000000000000#ifndef IMPORTPAGE_HPP #define IMPORTPAGE_HPP #include "ui_importpage.h" namespace Wizard { class MainWizard; class ImportPage : public QWizardPage, private Ui::ImportPage { Q_OBJECT public: ImportPage(QWidget *parent); int nextId() const override; private: MainWizard *mWizard; }; } #endif // IMPORTPAGE_HPP openmw-openmw-0.48.0/apps/wizard/inisettings.cpp000066400000000000000000000141501445372753700217440ustar00rootroot00000000000000#include "inisettings.hpp" #include #include #include #include #include #include Wizard::IniSettings::IniSettings() { } Wizard::IniSettings::~IniSettings() { } QStringList Wizard::IniSettings::findKeys(const QString &text) { QStringList result; for (const QString &key : mSettings.keys()) { if (key.startsWith(text)) result << key; } return result; } bool Wizard::IniSettings::readFile(QTextStream &stream) { // Look for a square bracket, "'\\[" // that has one or more "not nothing" in it, "([^]]+)" // and is closed with a square bracket, "\\]" QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); // Find any character(s) that is/are not equal sign(s), "[^=]+" // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" // and one or more periods, "(.+)" QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); QString currentSection; while (!stream.atEnd()) { const QString line(stream.readLine()); if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) continue; if (sectionRe.exactMatch(line)) { currentSection = sectionRe.cap(1); } else if (keyRe.indexIn(line) != -1) { QString key = keyRe.cap(1).trimmed(); QString value = keyRe.cap(2).trimmed(); // Append the section, but only if there is one if (!currentSection.isEmpty()) key = currentSection + QLatin1Char('/') + key; mSettings[key] = QVariant(value); } } return true; } bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) { // Look for a square bracket, "'\\[" // that has one or more "not nothing" in it, "([^]]+)" // and is closed with a square bracket, "\\]" QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); // Find any character(s) that is/are not equal sign(s), "[^=]+" // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" // and one or more periods, "(.+)" QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); const QStringList keys(mSettings.keys()); QString currentSection; QString buffer; while (!stream.atEnd()) { const QString line(stream.readLine()); if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { buffer.append(line + QLatin1String("\n")); continue; } if (sectionRe.exactMatch(line)) { buffer.append(line + QLatin1String("\n")); currentSection = sectionRe.cap(1); } else if (keyRe.indexIn(line) != -1) { QString key(keyRe.cap(1).trimmed()); QString lookupKey(key); // Append the section, but only if there is one if (!currentSection.isEmpty()) lookupKey = currentSection + QLatin1Char('/') + key; buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); mSettings.remove(lookupKey); } } // Add the new settings to the buffer QHashIterator i(mSettings); while (i.hasNext()) { i.next(); QStringList fullKey(i.key().split(QLatin1Char('/'))); QString section(fullKey.at(0)); section.prepend(QLatin1Char('[')); section.append(QLatin1Char(']')); const QString& key(fullKey.at(1)); int index = buffer.lastIndexOf(section); if (index == -1) { // Add the section to the end of the file, because it's not found buffer.append(QString("\n%1\n").arg(section)); index = buffer.lastIndexOf(section); } // Look for the next section index = buffer.indexOf(QLatin1Char('['), index + 1); if (index == -1 ) { // We are at the last section, append it to the bottom of the file buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); mSettings.remove(i.key()); continue; } else { // Not at last section, add the key at the index buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); mSettings.remove(i.key()); } } // Now we reopen the file, this time we write QFile file(path); if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { QTextStream in(&file); in.setCodec(stream.codec()); // Write the updated buffer to an empty file in << buffer; file.flush(); file.close(); } else { return false; } return true; } bool Wizard::IniSettings::parseInx(const QString &path) { QFile file(path); if (file.open(QIODevice::ReadOnly)) { const QByteArray data(file.readAll()); const QByteArray pattern("\x21\x00\x1A\x01\x04\x00\x04\x97\xFF\x06", 10); int i = 0; while ((i = data.indexOf(pattern, i)) != -1) { int next = data.indexOf(pattern, i + 1); if (next == -1) break; QByteArray array(data.mid(i, (next - i))); // Skip some invalid entries if (array.contains("\x04\x96\xFF")) { ++i; continue; } // Remove the pattern from the beginning array.remove(0, 12); int index = array.indexOf("\x06"); const QString section(array.left(index)); // Figure how many characters to read for the key int length = array.indexOf("\x06", section.length() + 3) - (section.length() + 3); const QString key(array.mid(section.length() + 3, length)); QString value(array.mid(section.length() + key.length() + 6)); // Add the value setValue(section + QLatin1Char('/') + key, QVariant(value)); ++i; } file.close(); } else { qDebug() << "Failed to open INX file: " << path; return false; } return true; } openmw-openmw-0.48.0/apps/wizard/inisettings.hpp000066400000000000000000000021721445372753700217520ustar00rootroot00000000000000#ifndef INISETTINGS_HPP #define INISETTINGS_HPP #include #include class QTextStream; namespace Wizard { typedef QHash SettingsMap; class IniSettings { public: explicit IniSettings(); ~IniSettings(); inline QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const { return mSettings.value(key, defaultValue); } inline QList values() const { return mSettings.values(); } inline void setValue(const QString &key, const QVariant &value) { mSettings.insert(key, value); } inline void remove(const QString &key) { mSettings.remove(key); } QStringList findKeys(const QString &text); bool readFile(QTextStream &stream); bool writeFile(const QString &path, QTextStream &stream); bool parseInx(const QString &path); private: int getLastNewline(const QString &buffer, int from); SettingsMap mSettings; }; } #endif // INISETTINGS_HPP openmw-openmw-0.48.0/apps/wizard/installationpage.cpp000066400000000000000000000230471445372753700227470ustar00rootroot00000000000000#include "installationpage.hpp" #include #include #include #include #include "mainwizard.hpp" Wizard::InstallationPage::InstallationPage(QWidget *parent, Config::GameSettings &gameSettings) : QWizardPage(parent), mGameSettings(gameSettings) { mWizard = qobject_cast(parent); setupUi(this); mFinished = false; mThread = std::make_unique(); mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").toLongLong()); mUnshield->moveToThread(mThread.get()); connect(mThread.get(), SIGNAL(started()), mUnshield.get(), SLOT(extract())); connect(mUnshield.get(), SIGNAL(finished()), mThread.get(), SLOT(quit())); connect(mUnshield.get(), SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(error(QString, QString)), this, SLOT(installationError(QString, QString)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(textChanged(QString)), installProgressLabel, SLOT(setText(QString)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(textChanged(QString)), logTextEdit, SLOT(appendPlainText(QString)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(textChanged(QString)), mWizard, SLOT(addLogText(QString)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(requestFileDialog(Wizard::Component)), this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); connect(mUnshield.get(), SIGNAL(requestOldVersionDialog()), this, SLOT(showOldVersionDialog()) , Qt::QueuedConnection); } Wizard::InstallationPage::~InstallationPage() { if (mThread->isRunning()) { mUnshield->stopWorker(); mThread->quit(); mThread->wait(); } } void Wizard::InstallationPage::initializePage() { QString path(field(QLatin1String("installation.path")).toString()); QStringList components(field(QLatin1String("installation.components")).toStringList()); logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); installProgressBar->setMinimum(0); // Set the progressbar maximum to a multiple of 100 // That way installing all three components would yield 300% // When one component is done the bar will be filled by 33% if (field(QLatin1String("installation.retailDisc")).toBool() == true) { installProgressBar->setMaximum((components.count() * 100)); } else { if (components.contains(QLatin1String("Tribunal")) && !mWizard->mInstallations[path].hasTribunal) installProgressBar->setMaximum(100); if (components.contains(QLatin1String("Bloodmoon")) && !mWizard->mInstallations[path].hasBloodmoon) installProgressBar->setMaximum(installProgressBar->maximum() + 100); } startInstallation(); } void Wizard::InstallationPage::startInstallation() { QStringList components(field(QLatin1String("installation.components")).toStringList()); QString path(field(QLatin1String("installation.path")).toString()); if (field(QLatin1String("installation.retailDisc")).toBool() == true) { // Always install Morrowind mUnshield->setInstallComponent(Wizard::Component_Morrowind, true); if (components.contains(QLatin1String("Tribunal"))) mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); if (components.contains(QLatin1String("Bloodmoon"))) mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); } else { // Morrowind should already be installed mUnshield->setInstallComponent(Wizard::Component_Morrowind, false); if (components.contains(QLatin1String("Tribunal")) && !mWizard->mInstallations[path].hasTribunal) mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); if (components.contains(QLatin1String("Bloodmoon")) && !mWizard->mInstallations[path].hasBloodmoon) mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); // Set the location of the Morrowind.ini to update mUnshield->setIniPath(mWizard->mInstallations[path].iniPath); mUnshield->setupSettings(); } // Set the installation target path mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1250")); } else if (language == QLatin1String("Russian")) { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1251")); } else { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1252")); } mThread->start(); } void Wizard::InstallationPage::showFileDialog(Wizard::Component component) { QString name; switch (component) { case Wizard::Component_Morrowind: name = QLatin1String("Morrowind"); break; case Wizard::Component_Tribunal: name = QLatin1String("Tribunal"); break; case Wizard::Component_Bloodmoon: name = QLatin1String("Bloodmoon"); break; } logTextEdit->appendHtml(tr("

Attempting to install component %1.

").arg(name)); mWizard->addLogText(tr("Attempting to install component %1.").arg(name)); QMessageBox msgBox; msgBox.setWindowTitle(tr("%1 Installation").arg(name)); msgBox.setIcon(QMessageBox::Information); msgBox.setText(QObject::tr("Select a valid %1 installation media.
Hint: make sure that it contains at least one .cab file.").arg(name)); msgBox.exec(); QString path = QFileDialog::getExistingDirectory(this, tr("Select %1 installation media").arg(name), QDir::rootPath()); if (path.isEmpty()) { logTextEdit->appendHtml(tr("


\ Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; emit completeChanged(); return; } mUnshield->setDiskPath(path); } void Wizard::InstallationPage::showOldVersionDialog() { logTextEdit->appendHtml(tr("

Detected old version of component Morrowind.

")); mWizard->addLogText(tr("Detected old version of component Morrowind.")); QMessageBox msgBox; msgBox.setWindowTitle(tr("Morrowind Installation")); msgBox.setIcon(QMessageBox::Information); msgBox.setText(QObject::tr("There may be a more recent version of Morrowind available.

Do you wish to continue anyway?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); int ret = msgBox.exec(); if (ret == QMessageBox::No) { logTextEdit->appendHtml(tr("


\ Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; emit completeChanged(); return; } mUnshield->wakeAll(); } void Wizard::InstallationPage::installationFinished() { QMessageBox msgBox; msgBox.setWindowTitle(tr("Installation finished")); msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("Installation completed successfully!")); msgBox.exec(); mFinished = true; emit completeChanged(); } void Wizard::InstallationPage::installationError(const QString &text, const QString &details) { installProgressLabel->setText(tr("Installation failed!")); logTextEdit->appendHtml(tr("


\ Error: %1

").arg(text)); logTextEdit->appendHtml(tr("

\ %1

").arg(details)); mWizard->addLogText(QLatin1String("Error: ") + text); mWizard->addLogText(details); mWizard->mError = true; QMessageBox msgBox; msgBox.setWindowTitle(tr("An error occurred")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

The Wizard has encountered an error

\

The error reported was:

%1

\

Press "Show Details..." for more information.

").arg(text)); msgBox.setDetailedText(details); msgBox.exec(); emit completeChanged(); } bool Wizard::InstallationPage::isComplete() const { if (!mWizard->mError) { return mFinished; } else { return true; } } int Wizard::InstallationPage::nextId() const { if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_Conclusion; } else { if (!mWizard->mError) { return MainWizard::Page_Import; } else { return MainWizard::Page_Conclusion; } } } openmw-openmw-0.48.0/apps/wizard/installationpage.hpp000066400000000000000000000023251445372753700227500ustar00rootroot00000000000000#ifndef INSTALLATIONPAGE_HPP #define INSTALLATIONPAGE_HPP #include #include #include "unshield/unshieldworker.hpp" #include "ui_installationpage.h" #include "inisettings.hpp" #include class QThread; namespace Wizard { class MainWizard; class IniSettings; class UnshieldWorker; class InstallationPage : public QWizardPage, private Ui::InstallationPage { Q_OBJECT public: InstallationPage(QWidget *parent, Config::GameSettings &gameSettings); ~InstallationPage() override; int nextId() const override; bool isComplete() const override; private: MainWizard *mWizard; bool mFinished; std::unique_ptr mThread; std::unique_ptr mUnshield; void startInstallation(); Config::GameSettings &mGameSettings; private slots: void showFileDialog(Wizard::Component component); void showOldVersionDialog(); void installationFinished(); void installationError(const QString &text, const QString &details); protected: void initializePage() override; }; } #endif // INSTALLATIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/installationtargetpage.cpp000066400000000000000000000065571445372753700241650ustar00rootroot00000000000000#include "installationtargetpage.hpp" #include #include #include #include "mainwizard.hpp" Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg) : QWizardPage(parent), mCfgMgr(cfg) { mWizard = qobject_cast(parent); setupUi(this); registerField(QLatin1String("installation.path*"), targetLineEdit); } void Wizard::InstallationTargetPage::initializePage() { QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); path.append(QDir::separator() + QLatin1String("basedata")); QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); } bool Wizard::InstallationTargetPage::validatePage() { QString path(field(QLatin1String("installation.path")).toString()); qDebug() << "Validating path: " << path; if (!QFile::exists(path)) { QDir dir; if (!dir.mkpath(path)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error creating destination")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not create the destination directory

\

Please make sure you have the right permissions \ and try again, or specify a different location.

")); msgBox.exec(); return false; } } QFileInfo info(path); if (!info.isWritable()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Insufficient permissions")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not write to the destination directory

\

Please make sure you have the right permissions \ and try again, or specify a different location.

")); msgBox.exec(); return false; } if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Destination not empty")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

The destination directory is not empty

\

An existing Morrowind installation is present in the specified location.

\

Please specify a different location, or go back and select the location as an existing installation.

")); msgBox.exec(); return false; } return true; } void Wizard::InstallationTargetPage::on_browseButton_clicked() { QString selectedPath = QFileDialog::getExistingDirectory( this, tr("Select where to install Morrowind"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); qDebug() << selectedPath; QFileInfo info(selectedPath); if (!info.exists()) return; if (info.isWritable()) targetLineEdit->setText(info.absoluteFilePath()); } int Wizard::InstallationTargetPage::nextId() const { return MainWizard::Page_LanguageSelection; } openmw-openmw-0.48.0/apps/wizard/installationtargetpage.hpp000066400000000000000000000013651445372753700241620ustar00rootroot00000000000000#ifndef INSTALLATIONTARGETPAGE_HPP #define INSTALLATIONTARGETPAGE_HPP #include "ui_installationtargetpage.h" namespace Files { struct ConfigurationManager; } namespace Wizard { class MainWizard; class InstallationTargetPage : public QWizardPage, private Ui::InstallationTargetPage { Q_OBJECT public: InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg); int nextId() const override; bool validatePage() override; private slots: void on_browseButton_clicked(); private: MainWizard *mWizard; const Files::ConfigurationManager &mCfgMgr; protected: void initializePage() override; }; } #endif // INSTALLATIONTARGETPAGE_HPP openmw-openmw-0.48.0/apps/wizard/intropage.cpp000066400000000000000000000006041445372753700213730ustar00rootroot00000000000000#include "intropage.hpp" #include "mainwizard.hpp" Wizard::IntroPage::IntroPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } int Wizard::IntroPage::nextId() const { return MainWizard::Page_MethodSelection; } openmw-openmw-0.48.0/apps/wizard/intropage.hpp000066400000000000000000000005551445372753700214050ustar00rootroot00000000000000#ifndef INTROPAGE_HPP #define INTROPAGE_HPP #include "ui_intropage.h" namespace Wizard { class MainWizard; class IntroPage : public QWizardPage, private Ui::IntroPage { Q_OBJECT public: IntroPage(QWidget *parent); int nextId() const override; private: MainWizard *mWizard; }; } #endif // INTROPAGE_HPP openmw-openmw-0.48.0/apps/wizard/languageselectionpage.cpp000066400000000000000000000027411445372753700237350ustar00rootroot00000000000000#include "languageselectionpage.hpp" #include "mainwizard.hpp" #include Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); registerField(QLatin1String("installation.language"), languageComboBox); } void Wizard::LanguageSelectionPage::initializePage() { QStringList languages; languages << QLatin1String("English") << QLatin1String("French") << QLatin1String("German") << QLatin1String("Italian") << QLatin1String("Polish") << QLatin1String("Russian") << QLatin1String("Spanish"); languageComboBox->addItems(languages); } int Wizard::LanguageSelectionPage::nextId() const { if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_ComponentSelection; } else { QString path(field(QLatin1String("installation.path")).toString()); if (path.isEmpty()) return MainWizard::Page_ComponentSelection; // Check if we have to install something if (mWizard->mInstallations[path].hasMorrowind == true && mWizard->mInstallations[path].hasTribunal == true && mWizard->mInstallations[path].hasBloodmoon == true) { return MainWizard::Page_Import; } else { return MainWizard::Page_ComponentSelection; } } } openmw-openmw-0.48.0/apps/wizard/languageselectionpage.hpp000066400000000000000000000007701445372753700237420ustar00rootroot00000000000000#ifndef LANGUAGESELECTIONPAGE_HPP #define LANGUAGESELECTIONPAGE_HPP #include "ui_languageselectionpage.h" namespace Wizard { class MainWizard; class LanguageSelectionPage : public QWizardPage, private Ui::LanguageSelectionPage { Q_OBJECT public: LanguageSelectionPage(QWidget *parent); int nextId() const override; private: MainWizard *mWizard; protected: void initializePage() override; }; } #endif // LANGUAGESELECTIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/main.cpp000066400000000000000000000020311445372753700203230ustar00rootroot00000000000000#include #include #include "mainwizard.hpp" #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED int main(int argc, char *argv[]) { QApplication app(argc, argv); // Now we make sure the current dir is set to application path QDir dir(QCoreApplication::applicationDirPath()); #ifdef Q_OS_MAC // force Qt to load only LOCAL plugins, don't touch system Qt installation QDir pluginsPath(QCoreApplication::applicationDirPath()); pluginsPath.cdUp(); pluginsPath.cd("Plugins"); QStringList libraryPaths; libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); app.setLibraryPaths(libraryPaths); #endif QDir::setCurrent(dir.absolutePath()); Wizard::MainWizard wizard; wizard.show(); return app.exec(); } openmw-openmw-0.48.0/apps/wizard/mainwizard.cpp000066400000000000000000000374101445372753700215550ustar00rootroot00000000000000#include "mainwizard.hpp" #include #include #include #include #include #include "intropage.hpp" #include "methodselectionpage.hpp" #include "languageselectionpage.hpp" #include "existinginstallationpage.hpp" #include "installationtargetpage.hpp" #include "componentselectionpage.hpp" #include "importpage.hpp" #include "conclusionpage.hpp" #ifdef OPENMW_USE_UNSHIELD #include "installationpage.hpp" #endif using namespace Process; Wizard::MainWizard::MainWizard(QWidget *parent) : QWizard(parent), mInstallations(), mError(false), mGameSettings(mCfgMgr) { #ifndef Q_OS_MAC setWizardStyle(QWizard::ModernStyle); #else setWizardStyle(QWizard::ClassicStyle); #endif setWindowTitle(tr("OpenMW Wizard")); setWindowIcon(QIcon(QLatin1String(":/images/openmw-wizard.png"))); setMinimumWidth(550); // Set the property for comboboxes to the text instead of index setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); mImporterInvoker = new ProcessInvoker(); connect(mImporterInvoker->getProcess(), SIGNAL(started()), this, SLOT(importerStarted())); connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(importerFinished(int,QProcess::ExitStatus))); mLogError = tr("

Could not open %1 for writing

\

Please make sure you have the right permissions \ and try again.

"); boost::filesystem::create_directories(mCfgMgr.getUserConfigPath()); boost::filesystem::create_directories(mCfgMgr.getUserDataPath()); setupLog(); setupGameSettings(); setupLauncherSettings(); setupInstallations(); setupPages(); const boost::filesystem::path& installationPath = mCfgMgr.getInstallPath(); if (!installationPath.empty()) { const boost::filesystem::path& dataPath = installationPath / "Data Files"; addInstallation(toQString(dataPath)); } } Wizard::MainWizard::~MainWizard() { delete mImporterInvoker; } void Wizard::MainWizard::setupLog() { QString logPath(toQString(mCfgMgr.getLogPath())); logPath.append(QLatin1String("wizard.log")); QFile file(logPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening Wizard log file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(mLogError.arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } addLogText(QString("Started OpenMW Wizard on %1").arg(QDateTime::currentDateTime().toString())); qDebug() << logPath; } void Wizard::MainWizard::addLogText(const QString &text) { QString logPath(toQString(mCfgMgr.getLogPath())); logPath.append(QLatin1String("wizard.log")); QFile file(logPath); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening Wizard log file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(mLogError.arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } if (!file.isSequential()) file.seek(file.size()); QTextStream out(&file); if (!text.isEmpty()) { out << text << "\n"; out.flush(); } } void Wizard::MainWizard::setupGameSettings() { QString userPath(toQString(mCfgMgr.getUserConfigPath())); QString globalPath(toQString(mCfgMgr.getGlobalPath())); QString message(tr("

Could not open %1 for reading

\

Please make sure you have the right permissions \ and try again.

")); // Load the user config file first, separately // So we can write it properly, uncontaminated QString path(userPath + QLatin1String("openmw.cfg")); QFile file(path); qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mGameSettings.readUserFile(stream); } file.close(); // Now the rest QStringList paths; paths.append(userPath + QLatin1String("openmw.cfg")); paths.append(QLatin1String("openmw.cfg")); paths.append(globalPath + QLatin1String("openmw.cfg")); for (const QString &path2 : paths) { qDebug() << "Loading config file:" << path2.toUtf8().constData(); file.setFileName(path2); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mGameSettings.readFile(stream); } file.close(); } } void Wizard::MainWizard::setupLauncherSettings() { QString path(toQString(mCfgMgr.getUserConfigPath())); path.append(QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); QString message(tr("

Could not open %1 for reading

\

Please make sure you have the right permissions \ and try again.

")); QFile file(path); qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mLauncherSettings.readFile(stream); } file.close(); } void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation for (const QString& path : mGameSettings.getDataDirs()) { if (findFiles(QLatin1String("Morrowind"), path)) addInstallation(path); } } void Wizard::MainWizard::runSettingsImporter() { writeSettings(); QString path(field(QLatin1String("installation.path")).toString()); QString userPath(toQString(mCfgMgr.getUserConfigPath())); QFile file(userPath + QLatin1String("openmw.cfg")); // Construct the arguments to run the importer QStringList arguments; // Import plugin selection? if (field(QLatin1String("installation.retailDisc")).toBool() == true || field(QLatin1String("installation.import-addons")).toBool() == true) arguments.append(QLatin1String("--game-files")); arguments.append(QLatin1String("--encoding")); // Set encoding QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { arguments.append(QLatin1String("win1250")); } else if (language == QLatin1String("Russian")) { arguments.append(QLatin1String("win1251")); } else { arguments.append(QLatin1String("win1252")); } // Import fonts if (field(QLatin1String("installation.import-fonts")).toBool() == true) arguments.append(QLatin1String("--fonts")); // Now the paths arguments.append(QLatin1String("--ini")); if (field(QLatin1String("installation.retailDisc")).toBool() == true) { arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); } else { arguments.append(mInstallations[path].iniPath); } arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) return qApp->quit(); } void Wizard::MainWizard::addInstallation(const QString &path) { qDebug() << "add installation in: " << path; Installation install;// = new Installation(); install.hasMorrowind = findFiles(QLatin1String("Morrowind"), path); install.hasTribunal = findFiles(QLatin1String("Tribunal"), path); install.hasBloodmoon = findFiles(QLatin1String("Bloodmoon"), path); // Try to autodetect the Morrowind.ini location QDir dir(path); QFile file(dir.filePath("Morrowind.ini")); // Try the parent directory // In normal Morrowind installations that's where Morrowind.ini is if (!file.exists()) { dir.cdUp(); file.setFileName(dir.filePath(QLatin1String("Morrowind.ini"))); } if (file.exists()) install.iniPath = file.fileName(); mInstallations.insert(QDir::toNativeSeparators(path), install); // Add it to the openmw.cfg too if (!mGameSettings.getDataDirs().contains(path)) { mGameSettings.setMultiValue(QLatin1String("data"), path); mGameSettings.addDataDir(path); } } void Wizard::MainWizard::setupPages() { setPage(Page_Intro, new IntroPage(this)); setPage(Page_MethodSelection, new MethodSelectionPage(this)); setPage(Page_LanguageSelection, new LanguageSelectionPage(this)); setPage(Page_ExistingInstallation, new ExistingInstallationPage(this)); setPage(Page_InstallationTarget, new InstallationTargetPage(this, mCfgMgr)); setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); #ifdef OPENMW_USE_UNSHIELD setPage(Page_Installation, new InstallationPage(this, mGameSettings)); #endif setPage(Page_Import, new ImportPage(this)); setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); } void Wizard::MainWizard::importerStarted() { } void Wizard::MainWizard::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) return; // Re-read the settings setupGameSettings(); } void Wizard::MainWizard::accept() { writeSettings(); QWizard::accept(); } void Wizard::MainWizard::reject() { QMessageBox msgBox; msgBox.setWindowTitle(tr("Quit Wizard")); msgBox.setIcon(QMessageBox::Question); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setText(tr("Are you sure you want to exit the Wizard?")); if (msgBox.exec() == QMessageBox::Yes) { QWizard::reject(); } } void Wizard::MainWizard::writeSettings() { // Write the encoding and language settings QString language(field(QLatin1String("installation.language")).toString()); mLauncherSettings.setValue(QLatin1String("Settings/language"), language); if (language == QLatin1String("Polish")) { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); } else if (language == QLatin1String("Russian")) { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); } else { mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); } // Write the installation path so that openmw can find them QString path(field(QLatin1String("installation.path")).toString()); // Make sure the installation path is the last data= entry mGameSettings.removeDataDir(path); mGameSettings.addDataDir(path); QString userPath(toQString(mCfgMgr.getUserConfigPath())); QDir dir(userPath); if (!dir.exists()) { if (!dir.mkpath(userPath)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not create %1

\

Please make sure you have the right permissions \ and try again.

").arg(userPath)); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } } // Game settings QFile file(userPath + QLatin1String("openmw.cfg")); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created QMessageBox msgBox; msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not open %1 for writing

\

Please make sure you have the right permissions \ and try again.

").arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mGameSettings.writeFile(stream); file.close(); // Launcher settings file.setFileName(userPath + QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created QMessageBox msgBox; msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not open %1 for writing

\

Please make sure you have the right permissions \ and try again.

").arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); return; } stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); mLauncherSettings.writeFile(stream); file.close(); } bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) { QDir dir(path); if (!dir.exists()) return false; // TODO: add MIME handling to make sure the files are real return (dir.entryList().contains(name + QLatin1String(".esm"), Qt::CaseInsensitive) && dir.entryList().contains(name + QLatin1String(".bsa"), Qt::CaseInsensitive)); } QString Wizard::MainWizard::toQString(const boost::filesystem::path& path) { return QString::fromUtf8(path.string().c_str()); } openmw-openmw-0.48.0/apps/wizard/mainwizard.hpp000066400000000000000000000037451445372753700215660ustar00rootroot00000000000000#ifndef MAINWIZARD_HPP #define MAINWIZARD_HPP #include #include #ifndef Q_MOC_RUN #include #include #include #endif namespace Wizard { class MainWizard : public QWizard { Q_OBJECT public: struct Installation { bool hasMorrowind; bool hasTribunal; bool hasBloodmoon; QString iniPath; }; enum { Page_Intro, Page_MethodSelection, Page_LanguageSelection, Page_ExistingInstallation, Page_InstallationTarget, Page_ComponentSelection, Page_Installation, Page_Import, Page_Conclusion }; MainWizard(QWidget *parent = nullptr); ~MainWizard() override; bool findFiles(const QString &name, const QString &path); void addInstallation(const QString &path); void runSettingsImporter(); QMap mInstallations; Files::ConfigurationManager mCfgMgr; Process::ProcessInvoker *mImporterInvoker; bool mError; public slots: void addLogText(const QString &text); private: /// convert boost::filesystem::path to QString QString toQString(const boost::filesystem::path& path); void setupLog(); void setupGameSettings(); void setupLauncherSettings(); void setupInstallations(); void setupPages(); void writeSettings(); Config::GameSettings mGameSettings; Config::LauncherSettings mLauncherSettings; QString mLogError; private slots: void importerStarted(); void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); void accept() override; void reject() override; }; } #endif // MAINWIZARD_HPP openmw-openmw-0.48.0/apps/wizard/methodselectionpage.cpp000066400000000000000000000017541445372753700234350ustar00rootroot00000000000000#include "methodselectionpage.hpp" #include "mainwizard.hpp" #include #include Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : QWizardPage(parent) { mWizard = qobject_cast(parent); setupUi(this); #ifndef OPENMW_USE_UNSHIELD retailDiscRadioButton->setEnabled(false); existingLocationRadioButton->setChecked(true); buyLinkButton->released(); #endif registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton); connect(buyLinkButton, SIGNAL(released()), this, SLOT(handleBuyButton())); } int Wizard::MethodSelectionPage::nextId() const { if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_InstallationTarget; } else { return MainWizard::Page_ExistingInstallation; } } void Wizard::MethodSelectionPage::handleBuyButton() { QDesktopServices::openUrl(QUrl("https://openmw.org/faq/#do_i_need_morrowind")); } openmw-openmw-0.48.0/apps/wizard/methodselectionpage.hpp000066400000000000000000000007601445372753700234360ustar00rootroot00000000000000#ifndef METHODSELECTIONPAGE_HPP #define METHODSELECTIONPAGE_HPP #include "ui_methodselectionpage.h" namespace Wizard { class MainWizard; class MethodSelectionPage : public QWizardPage, private Ui::MethodSelectionPage { Q_OBJECT public: MethodSelectionPage(QWidget *parent); int nextId() const override; private slots: void handleBuyButton(); private: MainWizard *mWizard; }; } #endif // METHODSELECTIONPAGE_HPP openmw-openmw-0.48.0/apps/wizard/unshield/000077500000000000000000000000001445372753700205125ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/wizard/unshield/unshieldworker.cpp000066400000000000000000000715771445372753700243040ustar00rootroot00000000000000#include "unshieldworker.hpp" #include #include #include #include #include #include #include #include Wizard::UnshieldWorker::UnshieldWorker(qint64 expectedMorrowindBsaSize, QObject *parent) : QObject(parent), mExpectedMorrowindBsaSize(expectedMorrowindBsaSize), mIniSettings() { unshield_set_log_level(0); mPath = QString(); mIniPath = QString(); mDiskPath = QString(); // Default to Latin encoding mIniCodec = QTextCodec::codecForName("windows-1252"); mInstallMorrowind = false; mInstallTribunal = false; mInstallBloodmoon = false; mMorrowindDone = false; mTribunalDone = false; mBloodmoonDone = false; mStopped = false; qRegisterMetaType("Wizard::Component"); } Wizard::UnshieldWorker::~UnshieldWorker() { } void Wizard::UnshieldWorker::stopWorker() { mStopped = true; mWait.wakeOne(); } void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) { QWriteLocker writeLock(&mLock); switch (component) { case Wizard::Component_Morrowind: mInstallMorrowind = install; break; case Wizard::Component_Tribunal: mInstallTribunal = install; break; case Wizard::Component_Bloodmoon: mInstallBloodmoon = install; break; } } bool Wizard::UnshieldWorker::getInstallComponent(Component component) { QReadLocker readLock(&mLock); switch (component) { case Wizard::Component_Morrowind: return mInstallMorrowind; case Wizard::Component_Tribunal: return mInstallTribunal; case Wizard::Component_Bloodmoon: return mInstallBloodmoon; } return false; } void Wizard::UnshieldWorker::setComponentDone(Component component, bool done) { QWriteLocker writeLock(&mLock); switch (component) { case Wizard::Component_Morrowind: mMorrowindDone = done; break; case Wizard::Component_Tribunal: mTribunalDone = done; break; case Wizard::Component_Bloodmoon: mBloodmoonDone = done; break; } } bool Wizard::UnshieldWorker::getComponentDone(Component component) { QReadLocker readLock(&mLock); switch (component) { case Wizard::Component_Morrowind: return mMorrowindDone; case Wizard::Component_Tribunal: return mTribunalDone; case Wizard::Component_Bloodmoon: return mBloodmoonDone; } return false; } void Wizard::UnshieldWorker::setPath(const QString &path) { QWriteLocker writeLock(&mLock); mPath = path; } void Wizard::UnshieldWorker::setIniPath(const QString &path) { QWriteLocker writeLock(&mLock); mIniPath = path; } void Wizard::UnshieldWorker::setDiskPath(const QString &path) { QWriteLocker writeLock(&mLock); mDiskPath = path; mWait.wakeAll(); } QString Wizard::UnshieldWorker::getPath() { QReadLocker readLock(&mLock); return mPath; } QString Wizard::UnshieldWorker::getIniPath() { QReadLocker readLock(&mLock); return mIniPath; } QString Wizard::UnshieldWorker::getDiskPath() { QReadLocker readLock(&mLock); return mDiskPath; } void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) { QWriteLocker writeLock(&mLock); mIniCodec = codec; } void Wizard::UnshieldWorker::wakeAll() { mWait.wakeAll(); } bool Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map if (getIniPath().isEmpty()) return false; QFile file(getIniPath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); return false; } QTextStream stream(&file); stream.setCodec(mIniCodec); mIniSettings.readFile(stream); return true; } bool Wizard::UnshieldWorker::writeSettings() { if (getIniPath().isEmpty()) return false; QFile file(getIniPath()); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); return false; } QTextStream stream(&file); stream.setCodec(mIniCodec); if (!mIniSettings.writeFile(getIniPath(), stream)) { emit error(tr("Failed to write Morrowind configuration file!"), tr("Writing to %1 failed: %2.").arg(getIniPath(), file.errorString())); return false; } return true; } bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) { bool result = false; QDir dir(dirName); if (dir.exists(dirName)) { QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); for (const QFileInfo& info : list) { if (info.isDir()) { result = removeDirectory(info.absoluteFilePath()); } else { result = QFile::remove(info.absoluteFilePath()); } if (!result) return result; } result = dir.rmdir(dirName); } return result; } bool Wizard::UnshieldWorker::copyFile(const QString &source, const QString &destination, bool keepSource) { QDir dir; QFile file; QFileInfo info(destination); if (info.exists()) { if (!dir.remove(info.absoluteFilePath())) return false; } if (file.copy(source, destination)) { if (!keepSource) { if (!file.remove(source)) return false; } else { return true; } } else { return false; } return true; } bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString &destination, bool keepSource) { QDir sourceDir(source); QDir destDir(destination); bool result = true; if (!destDir.exists()) { if (!sourceDir.mkpath(destination)) return false; } destDir.refresh(); if (!destDir.exists()) return false; QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); for (const QFileInfo &info : list) { QString relativePath(info.absoluteFilePath()); relativePath.remove(source); QString destinationPath(destDir.absolutePath() + relativePath); if (info.isSymLink()) continue; if (info.isDir()) { result = copyDirectory(info.absoluteFilePath(), destinationPath); } else { result = copyFile(info.absoluteFilePath(), destinationPath); } } if (!keepSource) return result && removeDirectory(sourceDir.absolutePath()); return result; } bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource) { return installFiles(fileName, path, flags, keepSource, true); } bool Wizard::UnshieldWorker::installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource, bool single) { QDir dir(path); if (!dir.exists()) return false; QStringList files(findFiles(fileName, path, flags)); for (const QString &file : files) { QFileInfo info(file); emit textChanged(tr("Installing: %1").arg(info.fileName())); if (single) { return copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource); } else { if (!copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) return false; } } return true; } bool Wizard::UnshieldWorker::installDirectories(const QString &dirName, const QString &path, bool recursive, bool keepSource) { QDir dir(path); if (!dir.exists()) return false; QStringList directories(findDirectories(dirName, path, recursive)); for (const QString &dir : directories) { QFileInfo info(dir); emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); if (!copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) return false; } return true; } void Wizard::UnshieldWorker::extract() { if (getInstallComponent(Wizard::Component_Morrowind)) { if (!getComponentDone(Wizard::Component_Morrowind)) if (!setupComponent(Wizard::Component_Morrowind)) return; } if (getInstallComponent(Wizard::Component_Tribunal)) { if (!getComponentDone(Wizard::Component_Tribunal)) if (!setupComponent(Wizard::Component_Tribunal)) return; } if (getInstallComponent(Wizard::Component_Bloodmoon)) { if (!getComponentDone(Wizard::Component_Bloodmoon)) if (!setupComponent(Wizard::Component_Bloodmoon)) return; } // Update Morrowind configuration if (getInstallComponent(Wizard::Component_Tribunal)) { mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); } if (getInstallComponent(Wizard::Component_Bloodmoon)) { mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Bloodmoon.bsa"))); mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Bloodmoon.esm"))); } if (getInstallComponent(Wizard::Component_Tribunal) && getInstallComponent(Wizard::Component_Bloodmoon)) { mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); mIniSettings.setValue(QLatin1String("Archives/Archive 1"), QVariant(QString("Bloodmoon.bsa"))); mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); mIniSettings.setValue(QLatin1String("Game Files/GameFile2"), QVariant(QString("Bloodmoon.esm"))); } // Write the settings to the Morrowind config file if (!writeSettings()) return; // Remove the temporary directory removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); // Fill the progress bar int total = 0; if (getInstallComponent(Wizard::Component_Morrowind)) total = 100; if (getInstallComponent(Wizard::Component_Tribunal)) total = total + 100; if (getInstallComponent(Wizard::Component_Bloodmoon)) total = total + 100; emit textChanged(tr("Installation finished!")); emit progressChanged(total); emit finished(); } bool Wizard::UnshieldWorker::setupComponent(Component component) { QString name; switch (component) { case Wizard::Component_Morrowind: name = QLatin1String("Morrowind"); break; case Wizard::Component_Tribunal: name = QLatin1String("Tribunal"); break; case Wizard::Component_Bloodmoon: name = QLatin1String("Bloodmoon"); break; } if (name.isEmpty()) { emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); return false; } bool found = false; QString cabFile; QDir disk; // Keep showing the file dialog until we find the necessary install files while (!found) { if (getDiskPath().isEmpty()) { QReadLocker readLock(&mLock); emit requestFileDialog(component); mWait.wait(&mLock); if(mStopped) { qDebug() << "We are asked to stop !!"; break; } disk.setPath(getDiskPath()); } else { disk.setPath(getDiskPath()); } QStringList list(findFiles(QLatin1String("data1.hdr"), disk.absolutePath())); for (const QString &file : list) { qDebug() << "current archive: " << file; if (component == Wizard::Component_Morrowind) { bool morrowindFound = findInCab(QLatin1String("Morrowind.bsa"), file); bool tribunalFound = findInCab(QLatin1String("Tribunal.bsa"), file); bool bloodmoonFound = findInCab(QLatin1String("Bloodmoon.bsa"), file); if (morrowindFound) { // Check if we have correct archive, other archives have Morrowind.bsa too if (tribunalFound == bloodmoonFound) { qint64 actualFileSize = getMorrowindBsaFileSize(file); if (actualFileSize != mExpectedMorrowindBsaSize) { QReadLocker readLock(&mLock); emit requestOldVersionDialog(); mWait.wait(&mLock); if (mStopped) { qDebug() << "We are asked to stop !!"; break; } } cabFile = file; found = true; // We have a GoTY disk or a Morrowind-only disk } } } else { if (findInCab(name + QLatin1String(".bsa"), file)) { cabFile = file; found = true; } } } if (cabFile.isEmpty()) { break; } if (!found) { emit textChanged(tr("Failed to find a valid archive containing %1.bsa! Retrying.").arg(name)); QReadLocker readLock(&mLock); emit requestFileDialog(component); mWait.wait(&mLock); } } if (installComponent(component, cabFile)) { setComponentDone(component, true); return true; } else { return false; } return true; } bool Wizard::UnshieldWorker::installComponent(Component component, const QString &path) { QString name; switch (component) { case Wizard::Component_Morrowind: name = QLatin1String("Morrowind"); break; case Wizard::Component_Tribunal: name = QLatin1String("Tribunal"); break; case Wizard::Component_Bloodmoon: name = QLatin1String("Bloodmoon"); break; } if (name.isEmpty()) { emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); return false; } emit textChanged(tr("Installing %1").arg(name)); QFileInfo info(path); if (!info.exists()) { emit error(tr("Installation media path not set!"), tr("The source path for %1 was not set.").arg(name)); return false; } // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); QDir temp; // Make sure the temporary folder is empty removeDirectory(tempPath); if (!temp.mkpath(tempPath)) { emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(tempPath)); return false; } temp.setPath(tempPath); if (!temp.mkdir(name)) { emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(temp.absoluteFilePath(name))); return false; } if (!temp.cd(name)) { emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %1.").arg(temp.absoluteFilePath(name))); return false; } // Extract the installation files if (!extractCab(info.absoluteFilePath(), temp.absolutePath())) return false; // Move the files from the temporary path to the destination folder emit textChanged(tr("Moving installation files")); // Install extracted directories QStringList directories; directories << QLatin1String("BookArt") << QLatin1String("Fonts") << QLatin1String("Icons") << QLatin1String("Meshes") << QLatin1String("Music") << QLatin1String("Sound") << QLatin1String("Splash") << QLatin1String("Textures") << QLatin1String("Video"); for (const QString &dir : directories) { if (!installDirectories(dir, temp.absolutePath())) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(dir, temp.absolutePath())); return false; } } // Install directories from disk for (const QString &dir : directories) { if (!installDirectories(dir, info.absolutePath(), false, true)) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(dir, info.absolutePath())); return false; } } // Install translation files QStringList extensions; extensions << QLatin1String(".cel") << QLatin1String(".top") << QLatin1String(".mrk"); for (const QString &extension : extensions) { if (!installFiles(extension, info.absolutePath(), Qt::MatchEndsWith)) { emit error(tr("Could not install translation file!"), tr("Failed to install *%1 files.").arg(extension)); return false; } } if (component == Wizard::Component_Morrowind) { QStringList files; files << QLatin1String("Morrowind.esm") << QLatin1String("Morrowind.bsa"); for (const QString &file : files) { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not install Morrowind data file!"), tr("Failed to install %1.").arg(file)); return false; } } // Copy Morrowind configuration file if (!installFile(QLatin1String("Morrowind.ini"), temp.absolutePath())) { emit error(tr("Could not install Morrowind configuration file!"), tr("Failed to install %1.").arg(QLatin1String("Morrowind.ini"))); return false; } // Setup Morrowind configuration setIniPath(getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); if (!setupSettings()) return false; } if (component == Wizard::Component_Tribunal) { QFileInfo sounds(temp.absoluteFilePath(QLatin1String("Sounds"))); QString dest(getPath() + QDir::separator() + QLatin1String("Sound")); if (sounds.exists()) { emit textChanged(tr("Installing: Sound directory")); if (!copyDirectory(sounds.absoluteFilePath(), dest)) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(sounds.absoluteFilePath(), dest)); return false; } } QStringList files; files << QLatin1String("Tribunal.esm") << QLatin1String("Tribunal.bsa"); for (const QString &file : files) { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not find Tribunal data file!"), tr("Failed to find %1.").arg(file)); return false; } } } if (component == Wizard::Component_Bloodmoon) { QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); if (original.exists()) { if (!installFile(QLatin1String("Tribunal.esm"), temp.absolutePath())) { emit error(tr("Could not find Tribunal patch file!"), tr("Failed to find %1.").arg(QLatin1String("Tribunal.esm"))); return false; } } QStringList files; files << QLatin1String("Bloodmoon.esm") << QLatin1String("Bloodmoon.bsa"); for (const QString &file : files) { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not find Bloodmoon data file!"), tr("Failed to find %1.").arg(file)); return false; } } // Load Morrowind configuration settings from the setup script QStringList list(findFiles(QLatin1String("setup.inx"), getDiskPath())); emit textChanged(tr("Updating Morrowind configuration file")); for (const QString &inx : list) { mIniSettings.parseInx(inx); } } // Finally, install Data Files directories from temp and disk QStringList datafiles(findDirectories(QLatin1String("Data Files"), temp.absolutePath())); datafiles.append(findDirectories(QLatin1String("Data Files"), info.absolutePath())); for (const QString &dir : datafiles) { QFileInfo info(dir); emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); if (!copyDirectory(info.absoluteFilePath(), getPath())) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(info.absoluteFilePath(), getPath())); return false; } } emit textChanged(tr("%1 installation finished!").arg(name)); return true; } bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter) { bool success = false; QString path(destination); path.append(QDir::separator()); int directory = unshield_file_directory(unshield, index); if (!prefix.isEmpty()) path.append(prefix + QDir::separator()); if (directory >= 0) path.append(QString::fromUtf8(unshield_directory_name(unshield, directory)) + QDir::separator()); // Ensure the path has the right separators path.replace(QLatin1Char('\\'), QDir::separator()); path = QDir::toNativeSeparators(path); // Ensure the target path exists QDir dir; if (!dir.mkpath(path)) return false; QString fileName(path); fileName.append(QString::fromUtf8(unshield_file_name(unshield, index))); // Calculate the percentage done int progress = (((float) counter / (float) unshield_file_count(unshield)) * 100); if (getComponentDone(Wizard::Component_Morrowind)) progress = progress + 100; if (getComponentDone(Wizard::Component_Tribunal)) progress = progress + 100; emit textChanged(tr("Extracting: %1").arg(QString::fromUtf8(unshield_file_name(unshield, index)))); emit progressChanged(progress); QByteArray array(fileName.toUtf8()); success = unshield_file_save(unshield, index, array.constData()); if (!success) { qDebug() << "error"; dir.remove(fileName); } return success; } bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) { bool success = false; QByteArray array(cabFile.toUtf8()); Unshield *unshield; unshield = unshield_open(array.constData()); if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); unshield_close(unshield); return false; } int counter = 0; for (int i=0; ifirst_file; j<=group->last_file; ++j) { if (mStopped) { qDebug() << "We're asked to stop!"; unshield_close(unshield); return true; } if (unshield_file_is_valid(unshield, j)) { success = extractFile(unshield, destination, group->name, j, counter); if (!success) { QString name(QString::fromUtf8(unshield_file_name(unshield, j))); emit error(tr("Failed to extract %1.").arg(name), tr("Complete path: %1").arg(destination + QDir::separator() + name)); unshield_close(unshield); return false; } ++counter; } } } unshield_close(unshield); return success; } QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) { return findFiles(fileName, path).first(); } QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path, int depth, bool recursive, bool directories, Qt::MatchFlags flags) { static const int MAXIMUM_DEPTH = 10; if (depth >= MAXIMUM_DEPTH) { qWarning("Maximum directory depth limit reached."); return QStringList(); } QStringList result; QDir dir(path); // Prevent parsing over the complete filesystem if (dir == QDir::rootPath()) return QStringList(); if (!dir.exists()) return QStringList(); QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); for (const QFileInfo& info : list) { if (info.isSymLink()) continue; if (info.isDir()) { if (directories) { if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) { result.append(info.absoluteFilePath()); } else { if (recursive) result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1, recursive, true)); } } else { if (recursive) result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1)); } } else { if (directories) break; switch (flags) { case Qt::MatchExactly: if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) result.append(info.absoluteFilePath()); break; case Qt::MatchEndsWith: if (info.fileName().endsWith(fileName, Qt::CaseInsensitive)) result.append(info.absoluteFilePath()); break; } } } return result; } QStringList Wizard::UnshieldWorker::findDirectories(const QString &dirName, const QString &path, bool recursive) { return findFiles(dirName, path, 0, true, true); } bool Wizard::UnshieldWorker::findInCab(const QString &fileName, const QString &cabFile) { QByteArray array(cabFile.toUtf8()); Unshield *unshield; unshield = unshield_open(array.constData()); if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); unshield_close(unshield); return false; } for (int i=0; ifirst_file; j<=group->last_file; ++j) { if (unshield_file_is_valid(unshield, j)) { QString current(QString::fromUtf8(unshield_file_name(unshield, j))); if (current.toLower() == fileName.toLower()) { unshield_close(unshield); return true; // File is found! } } } } unshield_close(unshield); return false; } size_t Wizard::UnshieldWorker::getMorrowindBsaFileSize(const QString &cabFile) { QString fileName = QString("Morrowind.bsa"); QByteArray array(cabFile.toUtf8()); Unshield *unshield; unshield = unshield_open(array.constData()); if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); unshield_close(unshield); return false; } for (int i = 0; i < unshield_file_group_count(unshield); ++i) { UnshieldFileGroup *group = unshield_file_group_get(unshield, i); for (size_t j = group->first_file; j <= group->last_file; ++j) { if (unshield_file_is_valid(unshield, j)) { QString current(QString::fromUtf8(unshield_file_name(unshield, j))); if (current.toLower() == fileName.toLower()) { size_t fileSize = unshield_file_size(unshield, j); unshield_close(unshield); return fileSize; // File is found! } } } } unshield_close(unshield); return 0; } openmw-openmw-0.48.0/apps/wizard/unshield/unshieldworker.hpp000066400000000000000000000071661445372753700243020ustar00rootroot00000000000000#ifndef UNSHIELDWORKER_HPP #define UNSHIELDWORKER_HPP #include #include #include #include #include #include "../inisettings.hpp" #include namespace Wizard { enum Component { Component_Morrowind, Component_Tribunal, Component_Bloodmoon }; class UnshieldWorker : public QObject { Q_OBJECT public: UnshieldWorker(qint64 expectedMorrowindBsaSize, QObject *parent = nullptr); ~UnshieldWorker() override; void stopWorker(); void setInstallComponent(Wizard::Component component, bool install); void setDiskPath(const QString &path); void setPath(const QString &path); void setIniPath(const QString &path); void wakeAll(); QString getPath(); QString getIniPath(); void setIniCodec(QTextCodec *codec); bool setupSettings(); size_t getMorrowindBsaFileSize(const QString &cabFile); private: bool writeSettings(); bool getInstallComponent(Component component); QString getDiskPath(); void setComponentDone(Component component, bool done = true); bool getComponentDone(Component component); bool removeDirectory(const QString &dirName); bool copyFile(const QString &source, const QString &destination, bool keepSource = true); bool copyDirectory(const QString &source, const QString &destination, bool keepSource = true); bool extractCab(const QString &cabFile, const QString &destination); bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); bool findInCab(const QString &fileName, const QString &cabFile); QString findFile(const QString &fileName, const QString &path); QStringList findFiles(const QString &fileName, const QString &path, int depth = 0, bool recursive = true, bool directories = false, Qt::MatchFlags flags = Qt::MatchExactly); QStringList findDirectories(const QString &dirName, const QString &path, bool recursive = true); bool installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, bool keepSource = false); bool installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, bool keepSource = false, bool single = false); bool installDirectories(const QString &dirName, const QString &path, bool recursive = true, bool keepSource = false); bool installComponent(Component component, const QString &path); bool setupComponent(Component component); bool mInstallMorrowind; bool mInstallTribunal; bool mInstallBloodmoon; bool mMorrowindDone; bool mTribunalDone; bool mBloodmoonDone; bool mStopped; qint64 mExpectedMorrowindBsaSize; QString mPath; QString mIniPath; QString mDiskPath; IniSettings mIniSettings; QTextCodec *mIniCodec; QWaitCondition mWait; QReadWriteLock mLock; public slots: void extract(); signals: void finished(); void requestFileDialog(Wizard::Component component); void requestOldVersionDialog(); void textChanged(const QString &text); void error(const QString &text, const QString &details); void progressChanged(int progress); }; } #endif // UNSHIELDWORKER_HPP openmw-openmw-0.48.0/apps/wizard/utils/000077500000000000000000000000001445372753700200375ustar00rootroot00000000000000openmw-openmw-0.48.0/apps/wizard/utils/componentlistwidget.cpp000066400000000000000000000021671445372753700246530ustar00rootroot00000000000000#include "componentlistwidget.hpp" #include ComponentListWidget::ComponentListWidget(QWidget *parent) : QListWidget(parent) { mCheckedItems = QStringList(); connect(this, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(updateCheckedItems(QListWidgetItem *))); connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(updateCheckedItems(QModelIndex, int, int))); } QStringList ComponentListWidget::checkedItems() { mCheckedItems.removeDuplicates(); return mCheckedItems; } void ComponentListWidget::updateCheckedItems(const QModelIndex &index, int start, int end) { updateCheckedItems(item(start)); } void ComponentListWidget::updateCheckedItems(QListWidgetItem *item) { if (!item) return; QString text = item->text(); if (item->checkState() == Qt::Checked) { if (!mCheckedItems.contains(text)) mCheckedItems.append(text); } else { if (mCheckedItems.contains(text)) mCheckedItems.removeAll(text); } mCheckedItems.removeDuplicates(); emit checkedItemsChanged(mCheckedItems); } openmw-openmw-0.48.0/apps/wizard/utils/componentlistwidget.hpp000066400000000000000000000011101445372753700246430ustar00rootroot00000000000000#ifndef COMPONENTLISTWIDGET_HPP #define COMPONENTLISTWIDGET_HPP #include class ComponentListWidget : public QListWidget { Q_OBJECT Q_PROPERTY(QStringList mCheckedItems READ checkedItems) public: ComponentListWidget(QWidget *parent = nullptr); QStringList mCheckedItems; QStringList checkedItems(); signals: void checkedItemsChanged(const QStringList &items); private slots: void updateCheckedItems(QListWidgetItem *item); void updateCheckedItems(const QModelIndex &index, int start, int end); }; #endif // COMPONENTLISTWIDGET_HPP openmw-openmw-0.48.0/cmake/000077500000000000000000000000001445372753700155145ustar00rootroot00000000000000openmw-openmw-0.48.0/cmake/COPYING-CMAKE-SCRIPTS000066400000000000000000000027221445372753700205150ustar00rootroot00000000000000The following files are derived from the Thermite project (http://www.thermite3d.org) and are covered under the license below. FindMYGUI.cmake, FindBullet.cmake Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. openmw-openmw-0.48.0/cmake/CheckBulletPrecision.cmake000066400000000000000000000012771445372753700225660ustar00rootroot00000000000000set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile) file(MAKE_DIRECTORY ${TMP_ROOT}) file(WRITE ${TMP_ROOT}/checkbullet.cpp " #include int main(int argc, char** argv) { btSphereShape shape(1.0); btScalar mass(1.0); btVector3 inertia; shape.calculateLocalInertia(mass, inertia); return 0; } ") message(STATUS "Checking if Bullet uses double precision") try_compile(RESULT_VAR ${TMP_ROOT}/temp ${TMP_ROOT}/checkbullet.cpp COMPILE_DEFINITIONS "-DBT_USE_DOUBLE_PRECISION" LINK_LIBRARIES ${BULLET_LIBRARIES} CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${BULLET_INCLUDE_DIRS}" ) set(HAS_DOUBLE_PRECISION_BULLET ${RESULT_VAR}) openmw-openmw-0.48.0/cmake/CheckOsgMultiview.cmake000066400000000000000000000012531445372753700221130ustar00rootroot00000000000000set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile) file(MAKE_DIRECTORY ${TMP_ROOT}) file(WRITE ${TMP_ROOT}/checkmultiview.cpp " #include int main(void) { (void)osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER; return 0; } ") message(STATUS "Checking if OSG supports multiview") try_compile(RESULT_VAR ${TMP_ROOT}/temp ${TMP_ROOT}/checkmultiview.cpp CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OPENSCENEGRAPH_INCLUDE_DIRS}" ) set(HAVE_MULTIVIEW ${RESULT_VAR}) if(HAVE_MULTIVIEW) message(STATUS "Osg supports multiview") else(HAVE_MULTIVIEW) message(NOTICE "Osg does not support multiview, disabling use of GL_OVR_multiview") endif(HAVE_MULTIVIEW) openmw-openmw-0.48.0/cmake/FindFFmpeg.cmake000066400000000000000000000105251445372753700204660ustar00rootroot00000000000000# vim: ts=2 sw=2 # - Try to find the required ffmpeg components # # This module accepts the following env variable # FFMPEG_HOME - Can be set to custom install path # # Once done this will define # FFmpeg_FOUND - System has the all required components. # FFmpeg_INCLUDE_DIRS - Include directory necessary for using the required components headers. # FFmpeg_LIBRARIES - Link these to use the required ffmpeg components. # FFmpeg_DEFINITIONS - Compiler switches required for using the required ffmpeg components. # # For each of the components it will additionaly set. # - AVCODEC # - AVDEVICE # - AVFORMAT # - AVUTIL # - POSTPROCESS # - SWSCALE # - SWRESAMPLE # the following variables will be defined # FFmpeg__FOUND - System has # FFmpeg__INCLUDE_DIRS - Include directory necessary for using the headers # FFmpeg__LIBRARIES - Link these to use # FFmpeg__DEFINITIONS - Compiler switches required for using # FFmpeg__VERSION - The components version # # Copyright (c) 2006, Matthias Kretz, # Copyright (c) 2008, Alexander Neundorf, # Copyright (c) 2011, Michael Jansen, # Copyright (c) 2016, Roman Proskuryakov, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(LibFindMacros) include(FindPackageHandleStandardArgs) # Macro: _internal_find_component # Checks for the given component by invoking pkgconfig etc. macro(_internal_find_component _component _pkgconfig _library _header) set(_package_component FFmpeg_${_component}) libfind_pkg_detect(${_package_component} ${_pkgconfig} FIND_PATH ${_header} HINTS $ENV{FFMPEG_HOME} PATH_SUFFIXES include ffmpeg FIND_LIBRARY ${_library} HINTS $ENV{FFMPEG_HOME} PATH_SUFFIXES lib ) set(${_package_component}_DEFINITIONS ${${_package_component}_PKGCONF_CFLAGS_OTHER}) set(${_package_component}_VERSION ${${_package_component}_PKGCONF_VERSION}) libfind_process(${_package_component}) endmacro() # setter for 'hashmap' macro(hashmap_set _table _key) # ARGN set(${_table}_${_key} ${ARGN}) endmacro() # check for key in 'hashmap' macro(hashmap_exists _table _key _out_var) if (DEFINED ${_table}_${_key}) set(${_out_var} TRUE) else() set(${_out_var} FALSE) endif() endmacro() # getter for 'hashmap' macro(hashmap_get _table _key _out_var) set(${_out_var} ${${_table}_${_key}}) endmacro() # fill 'hashmap' named find_args hashmap_set(find_args AVCODEC libavcodec avcodec libavcodec/avcodec.h) hashmap_set(find_args AVFORMAT libavformat avformat libavformat/avformat.h) hashmap_set(find_args AVDEVICE libavdevice avdevice libavdevice/avdevice.h) hashmap_set(find_args AVUTIL libavutil avutil libavutil/avutil.h) hashmap_set(find_args SWSCALE libswscale swscale libswscale/swscale.h) hashmap_set(find_args POSTPROC libpostproc postproc libpostproc/postprocess.h) hashmap_set(find_args SWRESAMPLE libswresample swresample libswresample/swresample.h) hashmap_set(find_args AVRESAMPLE libavresample avresample libavresample/avresample.h) # Check if the required components were found and add their stuff to the FFmpeg_* vars. foreach (_component ${FFmpeg_FIND_COMPONENTS}) hashmap_exists(find_args ${_component} _known_component) if (NOT _known_component) message(FATAL_ERROR "Unknown component '${_component}'") endif() hashmap_get(find_args ${_component} _component_find_args) _internal_find_component(${_component} ${_component_find_args}) set(_package_component FFmpeg_${_component}) if (${_package_component}_FOUND) list(APPEND FFmpeg_LIBRARIES ${${_package_component}_LIBRARIES}) list(APPEND FFmpeg_INCLUDE_DIRS ${${_package_component}_INCLUDE_DIRS}) list(APPEND FFmpeg_DEFINITIONS ${${_package_component}_DEFINITIONS}) endif () endforeach () # Build the include path with duplicates removed. if (FFmpeg_INCLUDE_DIRS) list(REMOVE_DUPLICATES FFmpeg_INCLUDE_DIRS) endif() FIND_PACKAGE_HANDLE_STANDARD_ARGS(FFmpeg FOUND_VAR FFmpeg_FOUND HANDLE_COMPONENTS REQUIRED_VARS FFmpeg_LIBRARIES FFmpeg_INCLUDE_DIRS ) openmw-openmw-0.48.0/cmake/FindGMock.cmake000066400000000000000000000170431445372753700203240ustar00rootroot00000000000000# Get the Google C++ Mocking Framework. # (This file is almost an copy of the original FindGTest.cmake file for GMock, # feel free to use it as it is or modify it for your own needs.) # # Defines the following variables: # # GMOCK_FOUND - Found or got the Google Mocking framework # GMOCK_INCLUDE_DIRS - GMock include directory # # Also defines the library variables below as normal variables # # GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock_main # GMOCK_LIBRARIES - libgmock # GMOCK_MAIN_LIBRARIES - libgmock-main # # Accepts the following variables as input: # # GMOCK_SRC_DIR -The directory of the gmock sources # GMOCK_VER - The version of the gmock sources to be downloaded # #----------------------- # Example Usage: # # set(GMOCK_ROOT "~/gmock") # find_package(GMock REQUIRED) # include_directories(${GMOCK_INCLUDE_DIRS}) # # add_executable(foo foo.cc) # target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES}) # #============================================================================= # Copyright (c) 2016 Michel Estermann # Copyright (c) 2016 Kamil Strzempowicz # Copyright (c) 2011 Matej Svec # # CMake - Cross Platform Makefile Generator # Copyright 2000-2016 Kitware, Inc. # Copyright 2000-2011 Insight Software Consortium # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the names of Kitware, Inc., the Insight Software Consortium, # nor the names of their contributors may be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ------------------------------------------------------------------------------ # # The above copyright and license notice applies to distributions of # CMake in source and binary form. Some source files contain additional # notices of original copyright by their contributors; see each source # for details. Third-party software packages supplied with CMake under # compatible licenses provide their own copyright notices documented in # corresponding subdirectories. # # ------------------------------------------------------------------------------ # # CMake was initially developed by Kitware with the following sponsorship: # # * National Library of Medicine at the National Institutes of Health # as part of the Insight Segmentation and Registration Toolkit (ITK). # # * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel # Visualization Initiative. # # * National Alliance for Medical Image Computing (NAMIC) is funded by the # National Institutes of Health through the NIH Roadmap for Medical Research, # Grant U54 EB005149. # # * Kitware, Inc. #============================================================================= function(_gmock_find_library _name) find_library(${_name} NAMES ${ARGN} HINTS ENV GMOCK_ROOT ${GMOCK_ROOT} PATH_SUFFIXES ${_gmock_libpath_suffixes} ) mark_as_advanced(${_name}) endfunction() if(NOT DEFINED GMOCK_MSVC_SEARCH) set(GMOCK_MSVC_SEARCH MD) endif() set(_gmock_libpath_suffixes lib) if(MSVC) if(GMOCK_MSVC_SEARCH STREQUAL "MD") list(APPEND _gmock_libpath_suffixes msvc/gmock-md/Debug msvc/gmock-md/Release) elseif(GMOCK_MSVC_SEARCH STREQUAL "MT") list(APPEND _gmock_libpath_suffixes msvc/gmock/Debug msvc/gmock/Release) endif() endif() find_path(GMOCK_INCLUDE_DIR gmock/gmock.h HINTS $ENV{GMOCK_ROOT}/include ${GMOCK_ROOT}/include ) mark_as_advanced(GMOCK_INCLUDE_DIR) if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") # The provided /MD project files for Google Mock add -md suffixes to the # library names. _gmock_find_library(GMOCK_LIBRARY gmock-md gmock) _gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind) else() _gmock_find_library(GMOCK_LIBRARY gmock) _gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd) _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main) _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind) endif() if(NOT TARGET GMock::GMock) add_library(GMock::GMock UNKNOWN IMPORTED) endif() if(NOT TARGET GMock::Main) add_library(GMock::Main UNKNOWN IMPORTED) endif() set(GMOCK_LIBRARY_EXISTS OFF) if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR) set(GMOCK_LIBRARY_EXISTS ON) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) include(CMakeFindDependencyMacro) find_dependency(Threads) set_target_properties(GMock::GMock PROPERTIES INTERFACE_LINK_LIBRARIES "Threads::Threads" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX") if(EXISTS "${GMOCK_LIBRARY}") set_target_properties(GMock::GMock PROPERTIES IMPORTED_LOCATION "${GMOCK_LIBRARY}") endif() if(EXISTS "${GMOCK_LIBRARY_DEBUG}") set_target_properties(GMock::GMock PROPERTIES IMPORTED_LOCATION_DEBUG "${GMOCK_LIBRARY_DEBUG}") endif() if(GMOCK_INCLUDE_DIR) set_target_properties(GMock::GMock PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" ) if(GMOCK_VER VERSION_LESS "1.7") # GMock 1.6 still has GTest as an external link-time dependency, # so just specify it on the link interface. set_property(TARGET GMock::GMock APPEND PROPERTY INTERFACE_LINK_LIBRARIES GTest::GTest) endif() endif() set_target_properties(GMock::Main PROPERTIES INTERFACE_LINK_LIBRARIES "GMock::GMock" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX") if(EXISTS "${GMOCK_MAIN_LIBRARY}") set_target_properties(GMock::Main PROPERTIES IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") endif() if(EXISTS "${GMOCK_MAIN_LIBRARY_DEBUG}") set_target_properties(GMock::Main PROPERTIES IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY_DEBUG}") endif() if(GMOCK_FOUND) set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) set(GMOCK_LIBRARIES GMock::GMock) set(GMOCK_MAIN_LIBRARIES GMock::Main) set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES}) if(VERBOSE) message(STATUS "GMock includes: ${GMOCK_INCLUDE_DIRS}") message(STATUS "GMock libs: ${GMOCK_BOTH_LIBRARIES}") endif() endif() openmw-openmw-0.48.0/cmake/FindLIBUNSHIELD.cmake000066400000000000000000000020001445372753700210510ustar00rootroot00000000000000# Locate LIBUNSHIELD # This module defines # LIBUNSHIELD_LIBRARIES # LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield # LIBUNSHIELD_INCLUDE_DIRS, where to find the headers # # Created by Tom Mason (wheybags) for OpenMW (https://openmw.org), based on FindMPG123.cmake # # Ripped off from other sources. In fact, this file is so generic (I # just did a search and replace on another file) that I wonder why the # CMake guys haven't wrapped this entire thing in a single # function. Do we really need to repeat this stuff for every single # library when they all work the same? include(LibFindMacros) set(POSSIBLE_LOCATIONS ~/Library/Frameworks /Library/Frameworks /usr/local /usr /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave /opt /usr/include ) libfind_pkg_detect(LIBUNSHIELD libunshield FIND_PATH libunshield.h HINTS ${POSSIBLE_LOCATIONS} FIND_LIBRARY unshield HINTS ${POSSIBLE_LOCATIONS} ) libfind_process(LIBUNSHIELD) openmw-openmw-0.48.0/cmake/FindLZ4.cmake000066400000000000000000000103151445372753700177300ustar00rootroot00000000000000# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: FindLZ4 ------- Find the LZ4 include directory and library. Use this module by invoking find_package with the form:: .. code-block:: cmake find_package(LZ4 [version] # Minimum version e.g. 1.8.0 [REQUIRED] # Fail with error if LZ4 is not found ) Imported targets ^^^^^^^^^^^^^^^^ This module defines the following :prop_tgt:`IMPORTED` targets: .. variable:: LZ4::LZ4 Imported target for using the LZ4 library, if found. Result variables ^^^^^^^^^^^^^^^^ .. variable:: LZ4_FOUND Set to true if LZ4 library found, otherwise false or undefined. .. variable:: LZ4_INCLUDE_DIRS Paths to include directories listed in one variable for use by LZ4 client. .. variable:: LZ4_LIBRARIES Paths to libraries to linked against to use LZ4. .. variable:: LZ4_VERSION The version string of LZ4 found. Cache variables ^^^^^^^^^^^^^^^ For users who wish to edit and control the module behavior, this module reads hints about search locations from the following variables:: .. variable:: LZ4_INCLUDE_DIR Path to LZ4 include directory with ``lz4.h`` header. .. variable:: LZ4_LIBRARY Path to LZ4 library to be linked. NOTE: The variables above should not usually be used in CMakeLists.txt files! #]=======================================================================] ### Find library ############################################################## if(NOT LZ4_LIBRARY) find_library(LZ4_LIBRARY_RELEASE NAMES lz4) find_library(LZ4_LIBRARY_DEBUG NAMES lz4d) include(SelectLibraryConfigurations) select_library_configurations(LZ4) else() file(TO_CMAKE_PATH "${LZ4_LIBRARY}" LZ4_LIBRARY) endif() ### Find include directory #################################################### find_path(LZ4_INCLUDE_DIR NAMES lz4.h) if(LZ4_INCLUDE_DIR AND EXISTS "${LZ4_INCLUDE_DIR}/lz4.h") file(STRINGS "${LZ4_INCLUDE_DIR}/lz4.h" _lz4_h_contents REGEX "#define LZ4_VERSION_[A-Z]+[ ]+[0-9]+") string(REGEX REPLACE "#define LZ4_VERSION_MAJOR[ ]+([0-9]+).+" "\\1" LZ4_VERSION_MAJOR "${_lz4_h_contents}") string(REGEX REPLACE ".+#define LZ4_VERSION_MINOR[ ]+([0-9]+).+" "\\1" LZ4_VERSION_MINOR "${_lz4_h_contents}") string(REGEX REPLACE ".+#define LZ4_VERSION_RELEASE[ ]+([0-9]+).*" "\\1" LZ4_VERSION_RELEASE "${_lz4_h_contents}") set(LZ4_VERSION "${LZ4_VERSION_MAJOR}.${LZ4_VERSION_MINOR}.${LZ4_VERSION_RELEASE}") unset(_lz4_h_contents) endif() ### Set result variables ###################################################### include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LZ4 DEFAULT_MSG LZ4_LIBRARY LZ4_INCLUDE_DIR LZ4_VERSION) set(LZ4_INCLUDE_DIR ${LZ4_INCLUDE_DIR} CACHE PATH "LZ4 include dir hint") set(LZ4_LIBRARY ${LZ4_LIBRARY} CACHE FILEPATH "LZ4 library path hint") mark_as_advanced(LZ4_INCLUDE_DIR LZ4_LIBRARY) set(LZ4_LIBRARIES ${LZ4_LIBRARY}) set(LZ4_INCLUDE_DIRS ${LZ4_INCLUDE_DIR}) ### Import targets ############################################################ if(LZ4_FOUND) if(NOT TARGET LZ4::LZ4) add_library(LZ4::LZ4 UNKNOWN IMPORTED) set_target_properties(LZ4::LZ4 PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" INTERFACE_INCLUDE_DIRECTORIES "${LZ4_INCLUDE_DIR}") if(LZ4_LIBRARY_RELEASE) set_property(TARGET LZ4::LZ4 APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(LZ4::LZ4 PROPERTIES IMPORTED_LOCATION_RELEASE "${LZ4_LIBRARY_RELEASE}") endif() if(LZ4_LIBRARY_DEBUG) set_property(TARGET LZ4::LZ4 APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(LZ4::LZ4 PROPERTIES IMPORTED_LOCATION_DEBUG "${LZ4_LIBRARY_DEBUG}") endif() if(NOT LZ4_LIBRARY_RELEASE AND NOT LZ4_LIBRARY_DEBUG) set_property(TARGET LZ4::LZ4 APPEND PROPERTY IMPORTED_LOCATION "${LZ4_LIBRARY}") endif() endif() endif() openmw-openmw-0.48.0/cmake/FindLuaJit.cmake000066400000000000000000000004211445372753700205040ustar00rootroot00000000000000# Once found, defines: # LuaJit_FOUND # LuaJit_INCLUDE_DIR # LuaJit_LIBRARIES include(LibFindMacros) libfind_pkg_detect(LuaJit luajit FIND_PATH luajit.h PATH_SUFFIXES luajit luajit-2.1 FIND_LIBRARY luajit-5.1 luajit ) libfind_process(LuaJit) openmw-openmw-0.48.0/cmake/FindMyGUI.cmake000066400000000000000000000031751445372753700202570ustar00rootroot00000000000000# - Find MyGUI includes and library # # This module accepts the following env variables # MYGUI_HOME - Can be set to MyGUI install path or Windows build path # # This module defines # MyGUI_INCLUDE_DIRS # MyGUI_LIBRARIES, the libraries to link against to use MyGUI. # MyGUI_FOUND, If false, do not try to use MyGUI # # Copyright © 2007, Matt Williams # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(LibFindMacros) if (MYGUI_STATIC) set(MYGUI_STATIC_SUFFIX "Static") else() set(MYGUI_STATIC_SUFFIX "") endif() libfind_pkg_detect(MyGUI_Debug MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX} FIND_LIBRARY MyGUIEngine_d${MYGUI_STATIC_SUFFIX} HINTS $ENV{MYGUI_HOME}/lib PATH_SUFFIXES "" debug ) set(MyGUI_Debug_FIND_QUIETLY TRUE) libfind_process(MyGUI_Debug) libfind_pkg_detect(MyGUI MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX} FIND_PATH MyGUI.h HINTS $ENV{MYGUI_HOME}/include PATH_SUFFIXES MYGUI MyGUI FIND_LIBRARY MyGUIEngine${MYGUI_STATIC_SUFFIX} HINTS $ENV{MYGUI_HOME}/lib PATH_SUFFIXES "" release relwithdebinfo minsizerel ) if (MYGUI_STATIC AND (APPLE OR ANDROID)) # we need explicit Freetype libs only on OS X and ANDROID for static build libfind_package(MyGUI Freetype) endif() libfind_version_n_header(MyGUI NAMES MyGUI_Prerequest.h DEFINES MYGUI_VERSION_MAJOR MYGUI_VERSION_MINOR MYGUI_VERSION_PATCH ) libfind_process(MyGUI) if (MyGUI_Debug_FOUND) set(MyGUI_LIBRARIES optimized ${MyGUI_LIBRARIES} debug ${MyGUI_Debug_LIBRARIES}) endif() openmw-openmw-0.48.0/cmake/FindOSGPlugins.cmake000066400000000000000000000051001445372753700213050ustar00rootroot00000000000000# This module accepts the following env variable # OSGPlugins_LIB_DIR - /lib/osgPlugins- , path to search plugins # OSGPlugins_DONT_FIND_DEPENDENCIES - Set to skip also finding png, zlib and jpeg # # Once done this will define # OSGPlugins_FOUND - System has the all required components. # OSGPlugins_LIBRARIES - Link these to use the required osg plugins components. # # Components: # - osgdb_png # - osgdb_tga # - osgdb_dds # - osgdb_jpeg include(LibFindMacros) include(Findosg_functions) if (NOT OSGPlugins_LIB_DIR) unset(OSGPlugins_LIB_DIR) foreach(OSGDB_LIB ${OSGDB_LIBRARY}) # Skip library type names if(EXISTS ${OSGDB_LIB} AND NOT IS_DIRECTORY ${OSGDB_LIB}) get_filename_component(OSG_LIB_DIR ${OSGDB_LIB} DIRECTORY) list(APPEND OSGPlugins_LIB_DIR "${OSG_LIB_DIR}/osgPlugins-${OPENSCENEGRAPH_VERSION}") endif() endforeach(OSGDB_LIB) endif() if (NOT OSGPlugins_LIB_DIR) set(_mode WARNING) if (OSGPlugins_FIND_REQUIRED) set(_mode FATAL_ERROR) endif() message(${_mode} "OSGPlugins_LIB_DIR variable must be set") endif() foreach(_library ${OSGPlugins_FIND_COMPONENTS}) string(TOUPPER ${_library} _library_uc) set(_component OSGPlugins_${_library}) # On some systems, notably Debian and Ubuntu, the OSG plugins do not have # the usual "lib" prefix. We temporarily add the empty string to the list # of prefixes CMake searches for (via osg_find_library) to support these systems. set(_saved_lib_prefix ${CMAKE_FIND_LIBRARY_PREFIXES}) # save CMAKE_FIND_LIBRARY_PREFIXES list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library osg_find_library(${_library_uc} ${_library}) # find it into ${_library_uc}_LIBRARIES set(CMAKE_FIND_LIBRARY_PREFIXES ${_saved_lib_prefix}) # restore prefix if (${_library_uc}_LIBRARIES) set(${_component}_LIBRARY ${${_library_uc}_LIBRARIES}) # fake as if we call find_library else() set(${_component}_LIBRARY ${_component}_LIBRARY-NOTFOUND) endif() list(APPEND OSGPlugins_PROCESS_LIBS ${_component}_LIBRARY) endforeach() if(NOT DEFINED OSGPlugins_DONT_FIND_DEPENDENCIES) foreach(_dependency PNG ZLIB JPEG) # needed by osgdb_png or osgdb_jpeg libfind_package(OSGPlugins ${_dependency}) set(${_dependency}_LIBRARY_OPTS ${_dependency}_LIBRARY) #list(APPEND OSGPlugins_PROCESS_LIBS ${_dependency}_LIBRARY) endforeach() endif() libfind_process(OSGPlugins) openmw-openmw-0.48.0/cmake/FindRecastNavigation.cmake000066400000000000000000000203541445372753700225640ustar00rootroot00000000000000# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. # Copyright 2021 Bret Curtis for OpenMW #[=======================================================================[.rst: FindRecastNavigation ------- Find the RecastNavigation include directory and library. Use this module by invoking find_package with the form:: .. code-block:: cmake find_package(RecastNavigation [version] # Minimum version e.g. 1.8.0 [REQUIRED] # Fail with error if RECAST is not found ) Imported targets ^^^^^^^^^^^^^^^^ This module defines the following :prop_tgt:`IMPORTED` targets: .. variable:: RecastNavigation::Recast Imported target for using the RECAST library, if found. Result variables ^^^^^^^^^^^^^^^^ .. variable:: RECAST_FOUND Set to true if RECAST library found, otherwise false or undefined. .. variable:: RECAST_INCLUDE_DIRS Paths to include directories listed in one variable for use by RECAST client. .. variable:: RECAST_LIBRARIES Paths to libraries to linked against to use RECAST. .. variable:: RECAST_VERSION The version string of RECAST found. Cache variables ^^^^^^^^^^^^^^^ For users who wish to edit and control the module behavior, this module reads hints about search locations from the following variables:: .. variable:: RECAST_INCLUDE_DIR Path to RECAST include directory with ``Recast.h`` header. .. variable:: RECAST_LIBRARY Path to RECAST library to be linked. NOTE: The variables above should not usually be used in CMakeLists.txt files! #]=======================================================================] ### Find libraries ############################################################## if(NOT RECAST_LIBRARY) find_library(RECAST_LIBRARY_RELEASE NAMES Recast HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) find_library(RECAST_LIBRARY_DEBUG NAMES Recast-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) include(SelectLibraryConfigurations) select_library_configurations(RECAST) mark_as_advanced(RECAST_LIBRARY_RELEASE RECAST_LIBRARY_DEBUG) else() file(TO_CMAKE_PATH "${RECAST_LIBRARY}" RECAST_LIBRARY) endif() if(NOT DETOUR_LIBRARY) find_library(DETOUR_LIBRARY_RELEASE NAMES Detour HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) find_library(DETOUR_LIBRARY_DEBUG NAMES Detour-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) include(SelectLibraryConfigurations) select_library_configurations(DETOUR) mark_as_advanced(DETOUR_LIBRARY_RELEASE DETOUR_LIBRARY_DEBUG) else() file(TO_CMAKE_PATH "${DETOUR_LIBRARY}" DETOUR_LIBRARY) endif() if(NOT DEBUGUTILS_LIBRARY) find_library(DEBUGUTILS_LIBRARY_RELEASE NAMES DebugUtils HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) find_library(DEBUGUTILS_LIBRARY_DEBUG NAMES DebugUtils-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) include(SelectLibraryConfigurations) select_library_configurations(DEBUGUTILS) mark_as_advanced(DEBUGUTILS_LIBRARY_RELEASE DEBUGUTILS_LIBRARY_DEBUG) else() file(TO_CMAKE_PATH "${DEBUGUTILS_LIBRARY}" DEBUGUTILS_LIBRARY) endif() ### Find include directory #################################################### find_path(RECAST_INCLUDE_DIR NAMES Recast.h HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES include RECAST include/recastnavigation) mark_as_advanced(RECAST_INCLUDE_DIR) if(RECAST_INCLUDE_DIR AND EXISTS "${RECAST_INCLUDE_DIR}/Recast.h") file(STRINGS "${RECAST_INCLUDE_DIR}/Recast.h" _Recast_h_contents REGEX "#define RECAST_VERSION_[A-Z]+[ ]+[0-9]+") string(REGEX REPLACE "#define RECAST_VERSION_MAJOR[ ]+([0-9]+).+" "\\1" RECAST_VERSION_MAJOR "${_Recast_h_contents}") string(REGEX REPLACE ".+#define RECAST_VERSION_MINOR[ ]+([0-9]+).+" "\\1" RECAST_VERSION_MINOR "${_Recast_h_contents}") string(REGEX REPLACE ".+#define RECAST_VERSION_RELEASE[ ]+([0-9]+).*" "\\1" RECAST_VERSION_RELEASE "${_Recast_h_contents}") set(RECAST_VERSION "${RECAST_VERSION_MAJOR}.${RECAST_VERSION_MINOR}.${RECAST_VERSION_RELEASE}") unset(_Recast_h_contents) endif() #TODO: they don't include a version yet set(RECAST_VERSION "1.5.1") ### Set result variables ###################################################### include(FindPackageHandleStandardArgs) find_package_handle_standard_args(RecastNavigation DEFAULT_MSG RECAST_LIBRARY RECAST_INCLUDE_DIR RECAST_VERSION) set(RECAST_LIBRARIES ${RECAST_LIBRARY}) set(RECAST_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) set(DETOUR_LIBRARIES ${DETOUR_LIBRARY}) set(DETOUR_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) set(DEBUGUTILS_LIBRARIES ${DEBUGUTILS_LIBRARY}) set(DEBUGUTILS_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) ### Import targets ############################################################ if(RecastNavigation_FOUND) if(NOT TARGET RecastNavigation::Recast) add_library(RecastNavigation::Recast UNKNOWN IMPORTED) set_target_properties(RecastNavigation::Recast PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" INTERFACE_INCLUDE_DIRECTORIES "${RECAST_INCLUDE_DIR}") if(RECAST_LIBRARY_RELEASE) set_property(TARGET RecastNavigation::Recast APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(RecastNavigation::Recast PROPERTIES IMPORTED_LOCATION_RELEASE "${RECAST_LIBRARY_RELEASE}") endif() if(RECAST_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::Recast APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(RecastNavigation::Recast PROPERTIES IMPORTED_LOCATION_DEBUG "${RECAST_LIBRARY_DEBUG}") endif() if(NOT RECAST_LIBRARY_RELEASE AND NOT RECAST_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::Recast APPEND PROPERTY IMPORTED_LOCATION "${RECAST_LIBRARY}") endif() endif() if(NOT TARGET RecastNavigation::Detour) add_library(RecastNavigation::Detour UNKNOWN IMPORTED) set_target_properties(RecastNavigation::Detour PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" INTERFACE_INCLUDE_DIRECTORIES "${DETOUR_INCLUDE_DIR}") if(DETOUR_LIBRARY_RELEASE) set_property(TARGET RecastNavigation::Detour APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(RecastNavigation::Detour PROPERTIES IMPORTED_LOCATION_RELEASE "${DETOUR_LIBRARY_RELEASE}") endif() if(DETOUR_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::Detour APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(RecastNavigation::Detour PROPERTIES IMPORTED_LOCATION_DEBUG "${DETOUR_LIBRARY_DEBUG}") endif() if(NOT DETOUR_LIBRARY_RELEASE AND NOT DETOUR_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::Detour APPEND PROPERTY IMPORTED_LOCATION "${DETOUR_LIBRARY}") endif() endif() if(NOT TARGET RecastNavigation::DebugUtils) add_library(RecastNavigation::DebugUtils UNKNOWN IMPORTED) set_target_properties(RecastNavigation::DebugUtils PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" INTERFACE_INCLUDE_DIRECTORIES "${DEBUGUTILS_INCLUDE_DIR}") if(DEBUGUTILS_LIBRARY_RELEASE) set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(RecastNavigation::DebugUtils PROPERTIES IMPORTED_LOCATION_RELEASE "${DEBUGUTILS_LIBRARY_RELEASE}") endif() if(DEBUGUTILS_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(RecastNavigation::DebugUtils PROPERTIES IMPORTED_LOCATION_DEBUG "${DEBUGUTILS_LIBRARY_DEBUG}") endif() if(NOT DEBUGUTILS_LIBRARY_RELEASE AND NOT DEBUGUTILS_LIBRARY_DEBUG) set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY IMPORTED_LOCATION "${DEBUGUTILS_LIBRARY}") endif() endif() endif() openmw-openmw-0.48.0/cmake/FindSDL2.cmake000066400000000000000000000121601445372753700200230ustar00rootroot00000000000000# Locate SDL2 library # This module defines # SDL2_LIBRARY, the SDL2 library, with no other libraries # SDL2_LIBRARIES, the SDL library and required components with compiler flags # SDL2_FOUND, if false, do not try to link to SDL2 # SDL2_INCLUDE_DIR, where to find SDL.h # SDL2_VERSION, the version of the found library # # This module accepts the following env variables # SDL2DIR - Can be set to ./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02 # This module responds to the the flag: # SDL2_BUILDING_LIBRARY # If this is defined, then no SDL2_main will be linked in because # only applications need main(). # Otherwise, it is assumed you are building an application and this # module will attempt to locate and set the the proper link flags # as part of the returned SDL2_LIBRARIES variable. # # Don't forget to include SDL2main.h and SDL2main.m your project for the # OS X framework based version. (Other versions link to -lSDL2main which # this module will try to find on your behalf.) Also for OS X, this # module will automatically add the -framework Cocoa on your behalf. # # # Modified by Eric Wing. # Added code to assist with automated building by using environmental variables # and providing a more controlled/consistent search behavior. # Added new modifications to recognize OS X frameworks and # additional Unix paths (FreeBSD, etc). # Also corrected the header search path to follow "proper" SDL2 guidelines. # Added a search for SDL2main which is needed by some platforms. # Added a search for threads which is needed by some platforms. # Added needed compile switches for MinGW. # # On OSX, this will prefer the Framework version (if found) over others. # People will have to manually change the cache values of # SDL2_LIBRARY to override this selection or set the CMake environment # CMAKE_INCLUDE_PATH to modify the search paths. # # Note that the header path has changed from SDL2/SDL.h to just SDL.h # This needed to change because "proper" SDL2 convention # is #include "SDL.h", not . This is done for portability # reasons because not all systems place things in SDL2/ (see FreeBSD). # # Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake # module with the minor edit of changing "SDL" to "SDL2" where necessary. This # was not created for redistribution, and exists temporarily pending official # SDL2 CMake modules. #============================================================================= # Copyright 2003-2009 Kitware, Inc. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) if (CMAKE_SIZEOF_VOID_P EQUAL 8) set(_sdl_lib_suffix lib/x64) else() set(_sdl_lib_suffix lib/x86) endif() libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h HINTS $ENV{SDL2DIR} PATH_SUFFIXES include SDL2 include/SDL2 FIND_LIBRARY SDL2 HINTS $ENV{SDL2DIR} PATH_SUFFIXES lib ${_sdl_lib_suffix} ) libfind_version_n_header(SDL2 NAMES SDL_version.h DEFINES SDL_MAJOR_VERSION SDL_MINOR_VERSION SDL_PATCHLEVEL) IF(NOT SDL2_BUILDING_LIBRARY AND NOT APPLE) # Non-OS X framework versions expect you to also dynamically link to # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms # seem to provide SDL2main for compatibility even though they don't # necessarily need it. libfind_pkg_detect(SDL2MAIN sdl2 FIND_LIBRARY SDL2main HINTS $ENV{SDL2DIR} PATH_SUFFIXES lib ${_sdl_lib_suffix} ) set(SDL2MAIN_FIND_QUIETLY TRUE) libfind_process(SDL2MAIN) list(APPEND SDL2_PROCESS_LIBS SDL2MAIN_LIBRARY) ENDIF() set(SDL2_TARGET_SPECIFIC) if (APPLE) # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. list(APPEND SDL2_TARGET_SPECIFIC "-framework Cocoa") else() # SDL2 may require threads on your system. # The Apple build may not need an explicit flag because one of the # frameworks may already provide it. # But for non-OSX systems, I will use the CMake Threads package. libfind_package(SDL2 Threads) list(APPEND SDL2_TARGET_SPECIFIC ${CMAKE_THREAD_LIBS_INIT}) endif() # MinGW needs an additional library, mwindows # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows # (Actually on second look, I think it only needs one of the m* libraries.) if(MINGW) list(APPEND SDL2_TARGET_SPECIFIC mingw32) endif() if(WIN32) list(APPEND SDL2_TARGET_SPECIFIC winmm imm32 version msimg32) endif() set(SDL2_PROCESS_LIBS SDL2_TARGET_SPECIFIC) libfind_process(SDL2) if (SDL2_STATIC AND UNIX AND NOT APPLE) execute_process(COMMAND sdl2-config --static-libs OUTPUT_VARIABLE SDL2_STATIC_FLAGS) string(REGEX REPLACE "(\r?\n)+$" "" SDL2_STATIC_FLAGS "${SDL2_STATIC_FLAGS}") set(SDL2_LIBRARIES ${SDL2_STATIC_FLAGS}) endif() openmw-openmw-0.48.0/cmake/FindSphinx.cmake000066400000000000000000000062761445372753700206030ustar00rootroot00000000000000# - This module looks for Sphinx # Find the Sphinx documentation generator # # This modules defines # Sphinx_EXECUTABLE # Sphinx_FOUND # function Sphinx_add_target # function Sphinx_add_targets # include(FindPackageHandleStandardArgs) include(CMakeParseArguments) set(_sphinx_names sphinx-build) foreach(_version 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0 1.6 1.5) list(APPEND _sphinx_names sphinx-build-${_version}) endforeach() find_program(Sphinx_EXECUTABLE NAMES ${_sphinx_names} PATHS /usr/bin /usr/local/bin /opt/local/bin DOC "Sphinx documentation generator" ) mark_as_advanced(Sphinx_EXECUTABLE) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Sphinx FOUND_VAR Sphinx_FOUND REQUIRED_VARS Sphinx_EXECUTABLE ) option( SPHINX_HTML_OUTPUT "Build a single HTML with the whole content." ON ) option( SPHINX_DIRHTML_OUTPUT "Build HTML pages, but with a single directory per document." OFF ) option( SPHINX_HTMLHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in htmlhelp." OFF ) option( SPHINX_QTHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in qthelp." OFF ) option( SPHINX_DEVHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in devhelp." OFF ) option( SPHINX_EPUB_OUTPUT "Build HTML pages with additional information for building a documentation collection in epub." OFF ) option( SPHINX_LATEX_OUTPUT "Build LaTeX sources that can be compiled to a PDF document using pdflatex." OFF ) option( SPHINX_MAN_OUTPUT "Build manual pages in groff format for UNIX systems." OFF ) option( SPHINX_TEXT_OUTPUT "Build plain text files." OFF ) function(Sphinx_add_target target_name builder conf source destination) add_custom_target( ${target_name} ALL COMMAND ${Sphinx_EXECUTABLE} -b ${builder} -c ${conf} ${source} ${destination} DEPENDS ${conf}/conf.py COMMENT "Generating sphinx documentation: ${builder}" ) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${destination} ) endfunction() # Usage: # # Sphinx_add_targets(NAME # SRC_DIR / # DST_DIR / # CONFIG_DIR / # [DEPENDENCIES dep1 dep2 dep3 ...] # ) function(Sphinx_add_targets) set(options ) set(one_value_keywords NAME SRC_DIR DST_DIR CONFIG_DIR) set(multi_value_keywords DEPENDENCIES) CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) if (NOT OPT_NAME OR NOT OPT_SRC_DIR OR NOT OPT_DST_DIR OR NOT OPT_CONFIG_DIR) message(FATAL_ERROR "Arguments NAME, SRC_DIR, DST_DIR, CONFIG_DIR are required!") endif() foreach(_generator html dirhtml qthelp devhelp epub latex man text linkcheck) string(TOUPPER ${_generator} _generator_uc) if (SPHINX_${_generator_uc}_OUTPUT) Sphinx_add_target(${OPT_NAME}_${_generator} ${_generator} ${OPT_CONFIG_DIR} ${OPT_SRC_DIR} ${OPT_DST_DIR}/${_generator}/) if (OPT_DEPENDENCIES) add_dependencies(${OPT_NAME}_${_generator} ${OPT_DEPENDENCIES}) endif() endif() endforeach() endfunction() openmw-openmw-0.48.0/cmake/FindTinyXML.cmake000066400000000000000000000010421445372753700206200ustar00rootroot00000000000000# Try to find TinyXML library # Once done this will define # TinyXML_FOUND - System has the all required components. # TinyXML_INCLUDE_DIRS - Include directory necessary for using the required components headers. # TinyXML_LIBRARIES - Link these to use TinyXML. # include(LibFindMacros) libfind_pkg_detect(TinyXML tinyxml FIND_PATH tinyxml.h FIND_LIBRARY tinyxml ) libfind_version_n_header(TinyXML NAMES tinyxml.h CONSTANTS TIXML_MAJOR_VERSION TIXML_MINOR_VERSION TIXML_PATCH_VERSION ) libfind_process(TinyXML) openmw-openmw-0.48.0/cmake/GitVersion.cmake000066400000000000000000000020721445372753700206100ustar00rootroot00000000000000execute_process ( COMMAND ${GIT_EXECUTABLE} rev-list --tags --max-count=1 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE EXITCODE1 OUTPUT_VARIABLE TAGHASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) execute_process ( COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE EXITCODE2 OUTPUT_VARIABLE COMMITHASH OUTPUT_STRIP_TRAILING_WHITESPACE) string (COMPARE EQUAL "${EXITCODE1}:${EXITCODE2}" "0:0" FULL_SUCCESS) string (COMPARE EQUAL "${EXITCODE2}" "0" COMMIT_SUCCESS) if (FULL_SUCCESS) set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") set(OPENMW_VERSION_TAGHASH "${TAGHASH}") message(STATUS "OpenMW version ${OPENMW_VERSION}") elseif (COMMIT_SUCCESS) set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") message(STATUS "OpenMW version ${OPENMW_VERSION}") else () message(WARNING "Failed to get valid version information from Git") endif () include(${MACROSFILE}) configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) openmw-openmw-0.48.0/cmake/LibFindMacros.cmake000066400000000000000000000337321445372753700212020ustar00rootroot00000000000000# Version 2.2 # Public Domain, originally written by Lasse Kärkkäinen # Maintained at https://github.com/Tronic/cmake-modules # Please send your improvements as pull requests on Github. include(CMakeParseArguments) # Find another package and make it a dependency of the current package. # This also automatically forwards the "REQUIRED" argument. # Usage: libfind_package( [extra args to find_package]) macro (libfind_package PREFIX PKG) set(${PREFIX}_args ${PKG} ${ARGN}) if (${PREFIX}_FIND_REQUIRED) set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) endif() find_package(${${PREFIX}_args}) set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) unset(${PREFIX}_args) endmacro() # A simple wrapper to make pkg-config searches a bit easier. # Works the same as CMake's internal pkg_search_module but is always quiet. macro (libfind_pkg_search_module) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_search_module(${ARGN} QUIET) endif() endmacro() # Avoid useless copy&pasta by doing what most simple libraries do anyway: # pkg-config, find headers, find library. # Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) # E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) function (libfind_pkg_detect PREFIX) # Parse arguments set(argname pkgargs) foreach (i ${ARGN}) if ("${i}" STREQUAL "FIND_PATH") set(argname pathargs) elseif ("${i}" STREQUAL "FIND_LIBRARY") set(argname libraryargs) else() set(${argname} ${${argname}} ${i}) endif() endforeach() if (NOT pkgargs) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library libfind_pkg_search_module(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() if (libraryargs) find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) endif() endfunction() # libfind_header_path( [PATHS [ ...]] NAMES [name ...] VAR [QUIET]) # Get fullpath of the first found header looking inside _INCLUDE_DIR or in the given PATHS # Usage: libfind_header_path(Foobar NAMES foobar/version.h VAR filepath) function (libfind_header_path PREFIX) set(options QUIET) set(one_value_keywords VAR PATH) set(multi_value_keywords NAMES PATHS) CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) if (NOT OPT_VAR OR NOT OPT_NAMES) message(FATAL_ERROR "Arguments VAR, NAMES are required!") endif() if (OPT_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") endif() if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) set(quiet TRUE) endif() set(paths ${OPT_PATHS} ${PREFIX}_INCLUDE_DIR) foreach(name ${OPT_NAMES}) foreach(path ${paths}) set(filepath "${${path}}/${name}") # check for existance if (EXISTS ${filepath}) set(${OPT_VAR} ${filepath} PARENT_SCOPE) # export path return() endif() endforeach() endforeach() # report error if not found set(${OPT_VAR} NOTFOUND PARENT_SCOPE) if (NOT quiet) message(AUTHOR_WARNING "Unable to find '${OPT_NAMES}'") endif() endfunction() # libfind_version_n_header( # NAMES [ ...] # DEFINES [ ...] | CONSTANTS [ ...] # [PATHS [ ...]] # [QUIET] # ) # Collect all defines|constants from a header inside _INCLUDE_DIR or in the given PATHS # output stored to _VERSION. # This function does nothing if the version variable is already defined. # Usage: libfind_version_n_header(Foobar NAMES foobar/version.h DEFINES FOOBAR_VERSION_MAJOR FOOBAR_VERSION_MINOR) function (libfind_version_n_header PREFIX) # Skip processing if we already have a version if (${PREFIX}_VERSION) return() endif() set(options QUIET) set(one_value_keywords ) set(multi_value_keywords NAMES PATHS DEFINES CONSTANTS) CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) if (NOT OPT_NAMES OR (NOT OPT_DEFINES AND NOT OPT_CONSTANTS)) message(FATAL_ERROR "Arguments NAMES, DEFINES|CONSTANTS are required!") endif() if (OPT_DEFINES AND OPT_CONSTANTS) message(FATAL_ERROR "Either DEFINES or CONSTANTS must be set!") endif() if (OPT_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") endif() if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) set(quiet TRUE) set(force_quiet "QUIET") # to propagate argument QUIET endif() # Read the header libfind_header_path(${PREFIX} NAMES ${OPT_NAMES} PATHS ${OPT_PATHS} VAR filename ${force_quiet}) if (NOT filename) return() endif() file(READ "${filename}" header) # Parse for version number unset(version_parts) foreach(define_name ${OPT_DEFINES}) string(REGEX MATCH "# *define +${define_name} +((\"([^\n]*)\")|([^ \n]*))" match "${header}") # No regex match? if (NOT match OR match STREQUAL header) if (NOT quiet) message(AUTHOR_WARNING "Unable to find \#define ${define_name} \"\" from ${filename}") endif() return() else() list(APPEND version_parts "${CMAKE_MATCH_3}${CMAKE_MATCH_4}") endif() endforeach() foreach(constant_name ${OPT_CONSTANTS}) string(REGEX MATCH "${constant_name} *= *((\"([^\;]*)\")|([^ \;]*))" match "${header}") # No regex match? if (NOT match OR match STREQUAL header) if (NOT quiet) message(AUTHOR_WARNING "Unable to find ${constant_name} = \"\" from ${filename}") endif() return() else() list(APPEND version_parts "${CMAKE_MATCH_3}${CMAKE_MATCH_4}") endif() endforeach() # Export the version string string(REPLACE ";" "." version "${version_parts}") set(${PREFIX}_VERSION "${version}" PARENT_SCOPE) endfunction() # libfind_version_header(
[PATHS [ ...]] [QUIET]) # Extracts a version #define from a version.h file, output stored to _VERSION. # This function does nothing if the version variable is already defined. # Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) # Skip processing if we already have a version if (${PREFIX}_VERSION) return() endif() set(options QUIET) set(one_value_keywords ) set(multi_value_keywords PATHS) CMAKE_PARSE_ARGUMENTS(OPT "${options}" "${one_value_keywords}" "${multi_value_keywords}" ${ARGN}) if (OPT_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Calling function with unused arguments '${OPT_UNPARSED_ARGUMENTS}'!") endif() if (OPT_QUIET OR ${PREFIX}_FIND_QUIETLY) set(force_quiet "QUIET") # to propagate argument QUIET endif() libfind_version_n_header(${PREFIX} NAMES ${VERSION_H} PATHS ${OPT_PATHS} DEFINES ${DEFINE_NAME} ${force_quiet}) set(${PREFIX}_VERSION "${${PREFIX}_VERSION}" PARENT_SCOPE) endfunction() # Do the final processing once the paths have been detected. # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain # all the variables, each of which contain one include directory. # Ditto for ${PREFIX}_PROCESS_LIBS and library files. # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. # Also handles errors in case library detection was required, etc. function (libfind_process PREFIX) # Skip processing if already processed during this configuration run if (${PREFIX}_FOUND) return() endif() set(found TRUE) # Start with the assumption that the package was found # Did we find any files? Did we miss includes? These are for formatting better error messages. set(some_files FALSE) set(missing_headers FALSE) # Shorthands for some variables that we need often set(quiet ${${PREFIX}_FIND_QUIETLY}) set(required ${${PREFIX}_FIND_REQUIRED}) set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) set(findver "${${PREFIX}_FIND_VERSION}") set(version "${${PREFIX}_VERSION}") # Lists of config option names (all, includes, libs) unset(configopts) set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) set(libraryopts ${${PREFIX}_PROCESS_LIBS}) # Process deps to add to foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) # The package seems to export option lists that we can use, woohoo! list(APPEND includeopts ${${i}_INCLUDE_OPTS}) list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) else() # If plural forms don't exist or they equal singular forms if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) # Singular forms can be used if (DEFINED ${i}_INCLUDE_DIR) list(APPEND includeopts ${i}_INCLUDE_DIR) endif() if (DEFINED ${i}_LIBRARY) list(APPEND libraryopts ${i}_LIBRARY) endif() else() # Oh no, we don't know the option names message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") endif() endif() endforeach() if (includeopts) list(REMOVE_DUPLICATES includeopts) endif() if (libraryopts) list(REMOVE_DUPLICATES libraryopts) endif() string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") endif() # Include/library names separated by spaces (notice: not CMake lists) unset(includes) unset(libs) # Process all includes and set found false if any are missing foreach (i ${includeopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND includes "${${i}}") else() set(found FALSE) set(missing_headers TRUE) endif() endforeach() # Process all libraries and set found false if any are missing foreach (i ${libraryopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND libs "${${i}}") else() set (found FALSE) endif() endforeach() # Version checks if (found AND findver) if (NOT version) message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) set(found FALSE) set(version_unsuitable TRUE) endif() endif() # If all-OK, hide all config options, export variables, print status and exit if (found) foreach (i ${configopts}) mark_as_advanced(${i}) endforeach() set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) set (${PREFIX}_FOUND TRUE PARENT_SCOPE) if (NOT quiet) message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") if (LIBFIND_DEBUG) message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") message(STATUS " ${PREFIX}_LIBRARIES=${libs}") endif() endif() return() endif() # Format messages for debug info and the type of error set(vars "Relevant CMake configuration variables:\n") foreach (i ${configopts}) mark_as_advanced(CLEAR ${i}) set(val ${${i}}) if ("${val}" STREQUAL "${i}-NOTFOUND") set (val "") elseif (val AND NOT EXISTS "${val}") set (val "${val} (does not exist)") else() set(some_files TRUE) endif() set(vars "${vars} ${i}=${val}\n") endforeach() set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") if (version_unsuitable) set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") if (exactver) set(msg "${msg} only version ${findver} is acceptable.") else() set(msg "${msg} version ${findver} is the minimum requirement.") endif() else() if (missing_headers) set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") elseif (some_files) set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") if(findver) set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") endif() else() set(msg "We were unable to find package ${PREFIX}.") endif() endif() # Fatal error out if REQUIRED if (required) set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") message(FATAL_ERROR "${msg}\n${vars}") endif() # Otherwise just print a nasty warning if (NOT quiet) message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") endif() endfunction() openmw-openmw-0.48.0/cmake/OpenMWMacros.cmake000066400000000000000000000155651445372753700210440ustar00rootroot00000000000000function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) set(files ${SOURCE_VARIABLE_NAME}) # Generate a unique filename for the unity build translation unit set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) # Exclude all translation units from compilation set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) # Open the ub file FILE(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") # Add include statement for each translation unit foreach(source_file ${files} ) FILE( APPEND ${unit_build_file} "#include <${source_file}>\n") endforeach(source_file) # Complement list of translation units with the name of ub set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) endfunction(enable_unity_build) macro (add_openmw_dir dir) set (files) set (cppfiles) foreach (u ${ARGN}) # Add cpp and hpp to OPENMW_FILES file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND OPENMW_FILES "${f}") endforeach (f) # Add cpp to unity build file (GLOB ALL "${dir}/${u}.cpp") foreach (f ${ALL}) list (APPEND cppfiles "${f}") endforeach (f) endforeach (u) if (OPENMW_UNITY_BUILD) enable_unity_build(${dir} "${cppfiles}") list (APPEND OPENMW_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp) endif() source_group ("apps\\openmw\\${dir}" FILES ${files}) endmacro (add_openmw_dir) macro (add_component_dir dir) set (files) set (cppfiles) foreach (u ${ARGN}) file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_FILES "${f}") endforeach (f) # Add cpp to unity build file (GLOB ALL "${dir}/${u}.cpp") foreach (f ${ALL}) list (APPEND cppfiles "${f}") endforeach (f) endforeach (u) if (OPENMW_UNITY_BUILD) enable_unity_build(${dir} "${cppfiles}") list (APPEND COMPONENT_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp) endif() source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) macro (add_component_qt_dir dir) set (files) foreach (u ${ARGN}) file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_QT_FILES "${f}") endforeach (f) endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_qt_dir) macro (add_file project type file) list (APPEND ${project}${type} ${file}) endmacro (add_file) macro (add_unit project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_unit) macro (add_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") endmacro (add_hdr) macro (opencs_units dir) foreach (u ${ARGN}) add_unit (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_units) macro (opencs_hdrs dir) foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_hdrs) include(CMakeParseArguments) macro (openmw_add_executable target) set(OMW_ADD_EXE_OPTIONS WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL) set(OMW_ADD_EXE_VALUES) set(OMW_ADD_EXE_MULTI_VALUES) cmake_parse_arguments(OMW_ADD_EXE "${OMW_ADD_EXE_OPTIONS}" "${OMW_ADD_EXE_VALUES}" "${OMW_ADD_EXE_MULTI_VALUES}" ${ARGN}) if (OMW_ADD_EXE_WIN32) set(OMW_ADD_EXE_WIN32_VALUE WIN32) endif (OMW_ADD_EXE_WIN32) if (OMW_ADD_EXE_MACOSX_BUNDLE) set(OMW_ADD_EXE_MACOSX_BUNDLE_VALUE MACOSX_BUNDLE) endif (OMW_ADD_EXE_MACOSX_BUNDLE) if (OMW_ADD_EXE_EXCLUDE_FROM_ALL) set(OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE EXCLUDE_FROM_ALL) endif (OMW_ADD_EXE_EXCLUDE_FROM_ALL) add_executable(${target} ${OMW_ADD_EXE_WIN32_VALUE} ${OMW_ADD_EXE_MACOSX_BUNDLE_VALUE} ${OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE} ${OMW_ADD_EXE_UNPARSED_ARGUMENTS}) if (MSVC) if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$") endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) endif (MSVC) endmacro (openmw_add_executable) macro (get_generator_is_multi_config VALUE) if (DEFINED generator_is_multi_config_var) set(${VALUE} ${generator_is_multi_config_var}) else (DEFINED generator_is_multi_config_var) if (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) get_cmake_property(${VALUE} GENERATOR_IS_MULTI_CONFIG) else (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) list(LENGTH CMAKE_CONFIGURATION_TYPES ${VALUE}) endif (CMAKE_VERSION VERSION_GREATER 3.9 OR CMAKE_VERSION VERSION_EQUAL 3.9) endif (DEFINED generator_is_multi_config_var) endmacro (get_generator_is_multi_config) macro (copy_resource_file source_path destination_dir_base dest_path_relative) get_generator_is_multi_config(multi_config) if (multi_config) foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) configure_file(${source_path} "${destination_dir_base}/${cfgtype}/${dest_path_relative}" COPYONLY) endforeach(cfgtype) else (multi_config) configure_file(${source_path} "${destination_dir_base}/${dest_path_relative}" COPYONLY) endif (multi_config) endmacro (copy_resource_file) macro (configure_resource_file source_path destination_dir_base dest_path_relative) get_generator_is_multi_config(multi_config) if (multi_config) foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) configure_file(${source_path} "${destination_dir_base}/${cfgtype}/${dest_path_relative}") endforeach(cfgtype) else (multi_config) configure_file(${source_path} "${destination_dir_base}/${dest_path_relative}") endif (multi_config) endmacro (configure_resource_file) macro (pack_resource_file source_path destination_dir_base dest_path_relative) get_generator_is_multi_config(multi_config) if (multi_config) foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) execute_process(COMMAND ${CMAKE_COMMAND} "-DINPUT_FILE=${source_path}" "-DOUTPUT_FILE=${destination_dir_base}/${cfgtype}/${dest_path_relative}" -P "${CMAKE_SOURCE_DIR}/cmake/base64.cmake") endforeach(cfgtype) else (multi_config) execute_process(COMMAND ${CMAKE_COMMAND} "-DINPUT_FILE=${source_path}" "-DOUTPUT_FILE=${destination_dir_base}/${dest_path_relative}" -P "${CMAKE_SOURCE_DIR}/cmake/base64.cmake") endif (multi_config) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source_path}") endmacro (pack_resource_file) macro (copy_all_resource_files source_dir destination_dir_base destination_dir_relative files) foreach (f ${files}) get_filename_component(filename ${f} NAME) copy_resource_file("${source_dir}/${f}" "${destination_dir_base}" "${destination_dir_relative}/${filename}") endforeach (f) endmacro (copy_all_resource_files) openmw-openmw-0.48.0/cmake/SignMacApplications.cmake000066400000000000000000000023431445372753700224100ustar00rootroot00000000000000# This script re-signs OpenMW.app and OpenMW-CS.app after CPack packages them. This is necessary because CPack modifies # the library references used by OpenMW to App relative paths, invalidating the code signature. # Obviously, we only need to run this on Apple targets. if (APPLE) set(OPENMW_APP "OpenMW") set(OPENMW_CS_APP "OpenMW-CS") set(APPLICATIONS "${OPENMW_APP}" "${OPENMW_CS_APP}") foreach(app_name IN LISTS APPLICATIONS) set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/ALL_IN_ONE/${app_name}.app") message(STATUS "Re-signing ${app_name}.app") # Apple's codesign utility does not like directories with periods (.) in their names, so we'll remove it and # create a symlink using the original name, which codesign is fine with. file(GLOB OSG_PLUGINS_DIR "${FULL_APP_PATH}/Contents/PlugIns/osgPlugins*") file(RENAME "${OSG_PLUGINS_DIR}" "${FULL_APP_PATH}/Contents/PlugIns/osgPlugins") execute_process(COMMAND "ln" "-s" "osgPlugins" "${OSG_PLUGINS_DIR}" WORKING_DIRECTORY "${FULL_APP_PATH}/Contents/PlugIns/") execute_process(COMMAND "codesign" "--force" "--deep" "-s" "-" "${FULL_APP_PATH}") endforeach(app_name) endif (APPLE)openmw-openmw-0.48.0/cmake/WholeArchive.cmake000066400000000000000000000011071445372753700210750ustar00rootroot00000000000000function (get_whole_archive_options OUT_VAR) # We use --whole-archive because OSG plugins use registration. if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(${OUT_VAR} -Wl,--whole-archive ${ARGN} -Wl,--no-whole-archive PARENT_SCOPE) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(${OUT_VAR} -Wl,-all_load ${ARGN} -Wl,-noall_load PARENT_SCOPE) else () message(FATAL_ERROR "get_whole_archive_options not implemented for CMAKE_CXX_COMPILER_ID ${CMAKE_CXX_COMPILER_ID}") endif() endfunction () openmw-openmw-0.48.0/cmake/base64.cmake000066400000000000000000000044141445372753700176050ustar00rootroot00000000000000# math(EXPR "...") can't parse hex until 3.13 cmake_minimum_required(VERSION 3.13) if (NOT DEFINED INPUT_FILE) message(STATUS "Usage: cmake -DINPUT_FILE=\"infile.ext\" -DOUTPUT_FILE=\"out.txt\" -P base64.cmake") message(FATAL_ERROR "INPUT_FILE not specified") endif() if (NOT DEFINED OUTPUT_FILE) message(STATUS "Usage: cmake -DINPUT_FILE=\"infile.ext\" -DOUTPUT_FILE=\"out.txt\" -P base64.cmake") message(FATAL_ERROR "OUTPUT_FILE not specified") endif() if (NOT EXISTS ${INPUT_FILE}) message(FATAL_ERROR "INPUT_FILE ${INPUT_FILE} does not exist") endif() set(lut "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") file(READ "${INPUT_FILE}" hexContent HEX) set(base64Content "") while(TRUE) string(LENGTH "${hexContent}" tailLength) if (tailLength LESS 1) break() endif() string(SUBSTRING "${hexContent}" 0 6 head) # base64 works on three-byte chunks. Pad. string(LENGTH "${head}" headLength) if (headLength LESS 6) set(hexContent "") math(EXPR padSize "6 - ${headLength}") set(pad "") foreach(i RANGE 1 ${padSize}) string(APPEND pad "0") endforeach() string(APPEND head "${pad}") else() string(SUBSTRING "${hexContent}" 6 -1 hexContent) set(padSize 0) endif() # get six-bit slices math(EXPR first "0x${head} >> 18") math(EXPR second "(0x${head} & 0x3F000) >> 12") math(EXPR third "(0x${head} & 0xFC0) >> 6") math(EXPR fourth "0x${head} & 0x3F") # first two characters are always needed to represent the first byte string(SUBSTRING "${lut}" ${first} 1 char) string(APPEND base64Content "${char}") string(SUBSTRING "${lut}" ${second} 1 char) string(APPEND base64Content "${char}") # if there's no second byte, pad with = if (NOT padSize EQUAL 4) string(SUBSTRING "${lut}" ${third} 1 char) string(APPEND base64Content "${char}") else() string(APPEND base64Content "=") endif() # if there's no third byte, pad with = if (padSize EQUAL 0) string(SUBSTRING "${lut}" ${fourth} 1 char) string(APPEND base64Content "${char}") else() string(APPEND base64Content "=") endif() endwhile() file(WRITE "${OUTPUT_FILE}" "${base64Content}")openmw-openmw-0.48.0/components/000077500000000000000000000000001445372753700166215ustar00rootroot00000000000000openmw-openmw-0.48.0/components/CMakeLists.txt000066400000000000000000000344211445372753700213650ustar00rootroot00000000000000project (Components) if(APPLE) set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR}) endif(APPLE) # Version file set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in") set (VERSION_FILE_PATH_BASE "${OpenMW_BINARY_DIR}") set (VERSION_FILE_PATH_RELATIVE resources/version) if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DVERSION_IN_FILE=${VERSION_IN_FILE} -DVERSION_FILE_PATH_BASE=${VERSION_FILE_PATH_BASE} -DVERSION_FILE_PATH_RELATIVE=${VERSION_FILE_PATH_RELATIVE} -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} -DOPENMW_VERSION=${OPENMW_VERSION} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake VERBATIM) else (GIT_CHECKOUT) configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) endif (GIT_CHECKOUT) # source files add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage ) add_component_dir (l10n messagebundles ) add_component_dir (settings settings parser ) add_component_dir (bsa bsa_file compressedbsafile ) add_component_dir (vfs manager archive bsaarchive filesystemarchive registerarchives ) add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager stats animation foreachbulletobject ) add_component_dir (shader shadermanager shadervisitor removedalphafunc ) add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue ) add_component_dir (nif controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics ) add_component_dir (nifosg nifloader controller particle matrixtransform ) add_component_dir (nifbullet bulletnifloader ) add_component_dir (to_utf8 to_utf8 ) add_component_dir(esm attr common defs esmcommon reader records util luascripts format) add_component_dir(fx pass technique lexer widgets stateupdater) add_component_dir(std140 ubo) add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache ) add_component_dir (esm3terrain storage ) add_component_dir (esm4 acti actor common dialogue effect formid inventory lighting loadachr loadacre loadacti loadalch loadaloc loadammo loadanio loadappa loadarma loadarmo loadaspc loadbook loadbptd loadcell loadclas loadclfm loadclot loadcont loadcrea loaddial loaddobj loaddoor loadeyes loadflor loadflst loadfurn loadglob loadgras loadgrup loadhair loadhdpt loadidle loadidlm loadimod loadinfo loadingr loadkeym loadland loadlgtm loadligh loadltex loadlvlc loadlvli loadlvln loadmato loadmisc loadmset loadmstt loadmusc loadnavi loadnavm loadnote loadnpc loadotft loadpack loadpgrd loadpgre loadpwat loadqust loadrace loadrefr loadregn loadroad loadsbsp loadscol loadscpt loadscrl loadsgst loadslgm loadsndr loadsoun loadstat loadtact loadterm loadtes4 loadtree loadtxst loadweap loadwrld reader reference script ) add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread compression osguservalues errorMarker color ) add_component_dir (stereo frustum multiview stereomanager types ) add_component_dir (debug debugging debuglog gldebug ) IF(NOT WIN32 AND NOT APPLE) add_definitions(-DGLOBAL_DATA_PATH="${GLOBAL_DATA_PATH}") add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager constrainedfilestream memorystream hash configfileparser openfile constrainedfilestreambuf ) add_component_dir (compiler context controlparser errorhandler exception exprparser extensions fileparser generator lineparser literals locals output parser scanner scriptparser skipparser streamerrorhandler stringparser tokenloc nullerrorhandler opcodes extensions0 declarationparser quickfileparser discardparser junkparser ) add_component_dir (interpreter context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes miscopcodes opcodes runtime types defines ) add_component_dir (translation translation ) add_component_dir (terrain storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode viewdata cellborder view heightcull ) add_component_dir (loadinglistener loadinglistener ) add_component_dir (myguiplatform myguirendermanager myguidatamanager myguiplatform myguitexture myguiloglistener additivelayer scalinglayer ) add_component_dir (widgets box fontwrapper imagebutton tags list numericeditbox sharedstatebutton windowcaption widgets ) add_component_dir (fontloader fontloader ) add_component_dir (sdlutil gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager sdlmappings ) add_component_dir (version version ) add_component_dir (fallback fallback validate ) add_component_dir (lua_ui registerscriptsettings scriptsettings properties widget element util layers content alignment resources adapter text textedit window image container flex ) copy_resource_file("lua_ui/content.lua" "${OPENMW_RESOURCES_ROOT}" "resources/lua_libs/content.lua") if(WIN32) add_component_dir (crashcatcher windows_crashcatcher windows_crashmonitor windows_crashshm windowscrashdumppathhelpers ) elseif(NOT ANDROID) add_component_dir (crashcatcher crashcatcher ) endif() add_component_dir(detournavigator debug makenavmesh findsmoothpath recastmeshbuilder recastmeshmanager cachedrecastmeshmanager navmeshmanager navigatorimpl asyncnavmeshupdater recastmesh tilecachedrecastmeshmanager recastmeshobject navmeshtilescache settings navigator findrandompointaroundcircle raycast navmeshtileview oscillatingrecastmeshobject offmeshconnectionsmanager preparednavmeshdata navmeshcacheitem navigatorutils generatenavmeshtile navmeshdb serialization navmeshdbutils recast gettilespositions collisionshapetype ) add_component_dir(loadinglistener reporter ) add_component_dir(sqlite3 db statement transaction ) add_component_dir(esmloader load esmdata ) add_component_dir(navmeshtool protocol ) add_component_dir(platform platform file ) if (WIN32) add_component_dir(platform file.win32 ) elseif (UNIX) add_component_dir(platform file.posix ) else () add_component_dir(platform file.stdio ) endif() set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) if (USE_QT) add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel model/loadordererror view/combobox view/contentselector ) add_component_qt_dir (config gamesettings launchersettings settingsbase ) add_component_qt_dir (process processinvoker ) add_component_qt_dir (misc helpviewer ) QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) add_definitions(-fPIC) endif() endif () include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) find_package(SQLite3 REQUIRED) add_library(components STATIC ${COMPONENT_FILES}) target_link_libraries(components # CMake's built-in OSG finder does not use pkgconfig, so we have to # manually ensure the order is correct for inter-library dependencies. # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGPARTICLE_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSGDB_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_IOSTREAMS_LIBRARY} ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} ${LUA_LIBRARIES} LZ4::LZ4 RecastNavigation::DebugUtils RecastNavigation::Detour RecastNavigation::Recast Base64 SQLite::SQLite3 smhasher ${ICU_LIBRARIES} yaml-cpp ) if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0) target_link_libraries(components ${Boost_ATOMIC_LIBRARY}) endif() target_link_libraries(components ${BULLET_LIBRARIES}) if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY} ${Boost_ZLIB_LIBRARY}) endif() if (USE_QT) add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt5::Widgets Qt5::Core) target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") endif() if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) if (OSG_STATIC AND CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(X11 REQUIRED COMPONENTS Xinerama Xrandr) target_link_libraries(components ${CMAKE_DL_LIBS} X11::X11 X11::Xinerama X11::Xrandr) find_package(Fontconfig MODULE) if(Fontconfig_FOUND) target_link_libraries(components Fontconfig::Fontconfig) endif() endif() if (WIN32) target_link_libraries(components shlwapi) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) endif() if (BUILD_WITH_CODE_COVERAGE) add_definitions(--coverage) target_link_libraries(components gcov) endif() # Make the variable accessible for other subdirectories set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) if(OSG_STATIC) unset(_osg_plugins_static_files) add_library(components_osg_plugins INTERFACE) foreach(_plugin ${USED_OSG_PLUGINS}) string(TOUPPER ${_plugin} _plugin_uc) if(OPENMW_USE_SYSTEM_OSG) list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) else() list(APPEND _osg_plugins_static_files $) target_link_libraries(components_osg_plugins INTERFACE $) add_dependencies(components_osg_plugins ${${_plugin_uc}_LIBRARY}) endif() endforeach() # We use --whole-archive because OSG plugins use registration. get_whole_archive_options(_opts ${_osg_plugins_static_files}) target_link_options(components_osg_plugins INTERFACE ${_opts}) target_link_libraries(components components_osg_plugins) if(OPENMW_USE_SYSTEM_OSG) # OSG plugin pkgconfig files are missing these dependencies. # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 find_package(Freetype REQUIRED) find_package(JPEG REQUIRED) find_package(PNG REQUIRED) target_link_libraries(components Freetype::Freetype JPEG::JPEG PNG::PNG) endif() endif(OSG_STATIC) if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) target_precompile_headers(components PUBLIC ) target_precompile_headers(components PRIVATE ) endif() openmw-openmw-0.48.0/components/bsa/000077500000000000000000000000001445372753700173665ustar00rootroot00000000000000openmw-openmw-0.48.0/components/bsa/bsa_file.cpp000066400000000000000000000237611445372753700216470ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (bsa_file.cpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #include "bsa_file.hpp" #include #include #include #include #include using namespace Bsa; /// Error handling [[noreturn]] void BSAFile::fail(const std::string &msg) { throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); } //the getHash code is from bsapack from ghostwheel //the code is also the same as in https://github.com/arviceblot/bsatool_rs/commit/67cb59ec3aaeedc0849222ea387f031c33e48c81 BSAFile::Hash getHash(const std::string& name) { BSAFile::Hash hash; unsigned l = (static_cast(name.size()) >> 1); unsigned sum, off, temp, i, n; for (sum = off = i = 0; i < l; i++) { sum ^= (((unsigned)(name[i])) << (off & 0x1F)); off += 8; } hash.low = sum; for (sum = off = 0; i < name.size(); i++) { temp = (((unsigned)(name[i])) << (off & 0x1F)); sum ^= temp; n = temp & 0x1F; sum = (sum << (32 - n)) | (sum >> n); // binary "rotate right" off += 8; } hash.high = sum; return hash; } /// Read header information from the input source void BSAFile::readHeader() { /* * The layout of a BSA archive is as follows: * * - 12 bytes header, contains 3 ints: * id number - equal to 0x100 * dirsize - size of the directory block (see below) * numfiles - number of files * * ---------- start of directory block ----------- * * - 8 bytes*numfiles, each record contains: * fileSize * offset into data buffer (see below) * * - 4 bytes*numfiles, each record is an offset into the following name buffer * * - name buffer, indexed by the previous table, each string is * null-terminated. Size is (dirsize - 12*numfiles). * * ---------- end of directory block ------------- * * - 8*filenum - hash table block, we currently ignore this * * ----------- start of data buffer -------------- * * - The rest of the archive is file data, indexed by the * offsets in the directory block. The offsets start at 0 at * the beginning of this buffer. * */ assert(!mIsLoaded); namespace bfs = boost::filesystem; bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); // Total archive size std::streamoff fsize = 0; if(input.seekg(0, std::ios_base::end)) { fsize = input.tellg(); input.seekg(0); } if(fsize < 12) fail("File too small to be a valid BSA archive"); // Get essential header numbers size_t dirsize, filenum; { // First 12 bytes uint32_t head[3]; input.read(reinterpret_cast(head), 12); if(head[0] != 0x100) fail("Unrecognized BSA header"); // Total number of bytes used in size/offset-table + filename // sections. dirsize = head[1]; // Number of files filenum = head[2]; } // Each file must take up at least 21 bytes of data in the bsa. So // if files*21 overflows the file size then we are guaranteed that // the archive is corrupt. if((filenum*21 > unsigned(fsize -12)) || (dirsize+8*filenum > unsigned(fsize -12)) ) fail("Directory information larger than entire archive"); // Read the offset info into a temporary buffer std::vector offsets(3*filenum); input.read(reinterpret_cast(offsets.data()), 12*filenum); // Read the string table mStringBuf.resize(dirsize-12*filenum); input.read(mStringBuf.data(), mStringBuf.size()); // Check our position assert(input.tellg() == std::streampos(12+dirsize)); std::vector hashes(filenum); static_assert(sizeof(Hash) == 8); input.read(reinterpret_cast(hashes.data()), 8*filenum); // Calculate the offset of the data buffer. All file offsets are // relative to this. 12 header bytes + directory + hash table // (skipped) size_t fileDataOffset = 12 + dirsize + 8*filenum; // Set up the the FileStruct table mFiles.resize(filenum); size_t endOfNameBuffer = 0; for(size_t i=0;i(offsets[i*2+1] + fileDataOffset); auto namesOffset = offsets[2*filenum+i]; fs.setNameInfos(namesOffset, &mStringBuf); fs.hash = hashes[i]; if (namesOffset >= mStringBuf.size()) { fail("Archive contains names offset outside itself"); } const void* end = std::memchr(fs.name(), '\0', mStringBuf.size()-namesOffset); if (!end) { fail("Archive contains non-zero terminated string"); } endOfNameBuffer = std::max(endOfNameBuffer, namesOffset + std::strlen(fs.name())+1); assert(endOfNameBuffer <= mStringBuf.size()); if(fs.offset + fs.fileSize > fsize) fail("Archive contains offsets outside itself"); } mStringBuf.resize(endOfNameBuffer); std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) { return left.offset < right.offset; }); mIsLoaded = true; } /// Write header information to the output sink void Bsa::BSAFile::writeHeader() { namespace bfs = boost::filesystem; bfs::fstream output(mFilename, std::ios::binary | std::ios::in | std::ios::out); uint32_t head[3]; head[0] = 0x100; auto fileDataOffset = mFiles.empty() ? 12 : mFiles.front().offset; head[1] = static_cast(fileDataOffset - 12 - 8*mFiles.size()); output.seekp(0, std::ios_base::end); head[2] = static_cast(mFiles.size()); output.seekp(0); output.write(reinterpret_cast(head), 12); std::sort(mFiles.begin(), mFiles.end(), [](const FileStruct& left, const FileStruct& right) { return std::make_pair(left.hash.low, left.hash.high) < std::make_pair(right.hash.low, right.hash.high); }); size_t filenum = mFiles.size(); std::vector offsets(3* filenum); std::vector hashes(filenum); for(size_t i=0;i(offsets.data()), sizeof(uint32_t)*offsets.size()); output.write(reinterpret_cast(mStringBuf.data()), mStringBuf.size()); output.seekp(fileDataOffset - 8*mFiles.size(), std::ios_base::beg); output.write(reinterpret_cast(hashes.data()), sizeof(Hash)*hashes.size()); } /// Open an archive file. void BSAFile::open(const std::string &file) { if (mIsLoaded) close(); mFilename = file; if(boost::filesystem::exists(file)) readHeader(); else { { boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); } writeHeader(); mIsLoaded = true; } } /// Close the archive, write the updated headers to the file void Bsa::BSAFile::close() { if (mHasChanged) writeHeader(); mFiles.clear(); mStringBuf.clear(); mIsLoaded = false; } Files::IStreamPtr Bsa::BSAFile::getFile(const FileStruct *file) { return Files::openConstrainedFileStream(mFilename, file->offset, file->fileSize); } void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) { if (!mIsLoaded) fail("Unable to add file " + filename + " the archive is not opened"); namespace bfs = boost::filesystem; auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1; if (mFiles.empty()) bfs::resize_file(mFilename, newStartOfDataBuffer); bfs::fstream stream(mFilename, std::ios::binary | std::ios::in | std::ios::out); FileStruct newFile; file.seekg(0, std::ios::end); newFile.fileSize = static_cast(file.tellg()); newFile.setNameInfos(mStringBuf.size(), &mStringBuf); newFile.hash = getHash(filename); if(mFiles.empty()) newFile.offset = static_cast(newStartOfDataBuffer); else { std::vector buffer; while (mFiles.front().offset < newStartOfDataBuffer) { FileStruct& firstFile = mFiles.front(); buffer.resize(firstFile.fileSize); stream.seekg(firstFile.offset, std::ios::beg); stream.read(buffer.data(), firstFile.fileSize); stream.seekp(0, std::ios::end); firstFile.offset = static_cast(stream.tellp()); stream.write(buffer.data(), firstFile.fileSize); //ensure sort order is preserved std::rotate(mFiles.begin(), mFiles.begin() + 1, mFiles.end()); } stream.seekp(0, std::ios::end); newFile.offset = static_cast(stream.tellp()); } mStringBuf.insert(mStringBuf.end(), filename.begin(), filename.end()); mStringBuf.push_back('\0'); mFiles.push_back(newFile); mHasChanged = true; stream.seekp(0, std::ios::end); file.seekg(0, std::ios::beg); stream << file.rdbuf(); } openmw-openmw-0.48.0/components/bsa/bsa_file.hpp000066400000000000000000000064121445372753700216460ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (bsa_file.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef BSA_BSA_FILE_H #define BSA_BSA_FILE_H #include #include #include #include namespace Bsa { /** This class is used to read "Bethesda Archive Files", or BSAs. */ class BSAFile { public: #pragma pack(push) #pragma pack(1) struct Hash { uint32_t low, high; }; #pragma pack(pop) /// Represents one file entry in the archive struct FileStruct { void setNameInfos(size_t index, std::vector* stringBuf ) { namesOffset = static_cast(index); namesBuffer = stringBuf; } // File size and offset in file. We store the offset from the // beginning of the file, not the offset into the data buffer // (which is what is stored in the archive.) uint32_t fileSize, offset; Hash hash; // Zero-terminated file name const char* name() const { return &(*namesBuffer)[namesOffset]; }; uint32_t namesOffset = 0; std::vector* namesBuffer = nullptr; }; typedef std::vector FileList; protected: bool mHasChanged = false; /// Table of files in this archive FileList mFiles; /// Filename string buffer std::vector mStringBuf; /// True when an archive has been loaded bool mIsLoaded; /// Used for error messages std::string mFilename; /// Error handling [[noreturn]] void fail(const std::string &msg); /// Read header information from the input source virtual void readHeader(); virtual void writeHeader(); public: /* ----------------------------------- * BSA management methods * ----------------------------------- */ BSAFile() : mIsLoaded(false) { } virtual ~BSAFile() { close(); } /// Open an archive file. void open(const std::string &file); void close(); /* ----------------------------------- * Archive file routines * ----------------------------------- */ /** Open a file contained in the archive. * @note Thread safe. */ Files::IStreamPtr getFile(const FileStruct *file); void addFile(const std::string& filename, std::istream& file); /// Get a list of all files /// @note Thread safe. const FileList &getList() const { return mFiles; } const std::string& getFilename() const { return mFilename; } }; } #endif openmw-openmw-0.48.0/components/bsa/compressedbsafile.cpp000066400000000000000000000407701445372753700235740ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: http://openmw.sourceforge.net/ This file (compressedbsafile.cpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see http://www.gnu.org/licenses/ . Compressed BSA stuff added by cc9cii 2018 */ #include "compressedbsafile.hpp" #include #include #include #include #include #include #include #if defined(_MSC_VER) #pragma warning (push) #pragma warning (disable : 4706) #include #pragma warning (pop) #else #include #endif #include #include #include #include namespace Bsa { //special marker for invalid records, //equal to max uint32_t value const uint32_t CompressedBSAFile::sInvalidOffset = std::numeric_limits::max(); //bit marking compression on file size const uint32_t CompressedBSAFile::sCompressedFlag = 1u << 30u; CompressedBSAFile::FileRecord::FileRecord() : size(0), offset(sInvalidOffset) { } bool CompressedBSAFile::FileRecord::isValid() const { return offset != sInvalidOffset; } bool CompressedBSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const { bool recordCompressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); //record is compressed when: //- bsaCompressedByDefault flag is set and 30th bit is NOT set, or //- bsaCompressedByDefault flag is NOT set and 30th bit is set //record is NOT compressed when: //- bsaCompressedByDefault flag is NOT set and 30th bit is NOT set, or //- bsaCompressedByDefault flag is set and 30th bit is set return (bsaCompressedByDefault != recordCompressionFlagEnabled); } std::uint32_t CompressedBSAFile::FileRecord::getSizeWithoutCompressionFlag() const { return size & (~sCompressedFlag); } void CompressedBSAFile::getBZString(std::string& str, std::istream& filestream) { char size = 0; filestream.read(&size, 1); auto buf = std::vector(size); filestream.read(buf.data(), size); if (buf[size - 1] != 0) { str.assign(buf.data(), size); if (str.size() != ((size_t)size)) { fail("getBZString string size mismatch"); } } else { str.assign(buf.data(), size - 1); // don't copy null terminator if (str.size() != ((size_t)size - 1)) { fail("getBZString string size mismatch (null terminator)"); } } } CompressedBSAFile::CompressedBSAFile() : mCompressedByDefault(false), mEmbeddedFileNames(false) { } CompressedBSAFile::~CompressedBSAFile() = default; /// Read header information from the input source void CompressedBSAFile::readHeader() { assert(!mIsLoaded); namespace bfs = boost::filesystem; bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); // Total archive size std::streamoff fsize = 0; if(input.seekg(0, std::ios_base::end)) { fsize = input.tellg(); input.seekg(0); } if(fsize < 36) // header is 36 bytes fail("File too small to be a valid BSA archive"); // Get essential header numbers //size_t dirsize, filenum; std::uint32_t archiveFlags, folderCount, totalFileNameLength; { // First 36 bytes std::uint32_t header[9]; input.read(reinterpret_cast(header), 36); if (header[0] != 0x00415342) /*"BSA\x00"*/ fail("Unrecognized compressed BSA format"); mVersion = header[1]; if (mVersion != 0x67 /*TES4*/ && mVersion != 0x68 /*FO3, FNV, TES5*/ && mVersion != 0x69 /*SSE*/) fail("Unrecognized compressed BSA version"); // header[2] is offset, should be 36 = 0x24 which is the size of the header // Oblivion - Meshes.bsa // // 0111 1000 0111 = 0x0787 // ^^^ ^ ^^^ // ||| | ||+-- has names for dirs (mandatory?) // ||| | |+--- has names for files (mandatory?) // ||| | +---- files are compressed by default // ||| | // ||| +---------- unknown (TES5: retain strings during startup) // ||+------------ unknown (TES5: embedded file names) // |+------------- unknown // +-------------- unknown // archiveFlags = header[3]; folderCount = header[4]; // header[5] - fileCount // totalFolderNameLength = header[6]; totalFileNameLength = header[7]; // header[8]; // fileFlags : an opportunity to optimize here mCompressedByDefault = (archiveFlags & 0x4) != 0; if (mVersion == 0x68 || mVersion == 0x69) /*FO3, FNV, TES5, SSE*/ mEmbeddedFileNames = (archiveFlags & 0x100) != 0; } // folder records std::uint64_t hash; FolderRecord fr; for (std::uint32_t i = 0; i < folderCount; ++i) { input.read(reinterpret_cast(&hash), 8); input.read(reinterpret_cast(&fr.count), 4); // not sure purpose of count if (mVersion == 0x69) // SSE { std::uint32_t unknown; input.read(reinterpret_cast(&unknown), 4); input.read(reinterpret_cast(&fr.offset), 8); } else input.read(reinterpret_cast(&fr.offset), 4); // not sure purpose of offset auto lb = mFolders.lower_bound(hash); if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) fail("Archive found duplicate folder name hash"); else mFolders.insert(lb, std::pair(hash, fr)); } // file record blocks std::uint64_t fileHash; FileRecord file; std::string folder; std::uint64_t folderHash; if ((archiveFlags & 0x1) == 0) folderCount = 1; // TODO: not tested - unit test necessary mFiles.clear(); std::vector fullPaths; for (std::uint32_t i = 0; i < folderCount; ++i) { if ((archiveFlags & 0x1) != 0) getBZString(folder, input); folderHash = generateHash(folder, std::string()); auto iter = mFolders.find(folderHash); if (iter == mFolders.end()) fail("Archive folder name hash not found"); for (std::uint32_t j = 0; j < iter->second.count; ++j) { input.read(reinterpret_cast(&fileHash), 8); input.read(reinterpret_cast(&file.size), 4); input.read(reinterpret_cast(&file.offset), 4); auto lb = iter->second.files.lower_bound(fileHash); if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) fail("Archive found duplicate file name hash"); iter->second.files.insert(lb, std::pair(fileHash, file)); FileStruct fileStruct{}; fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); fileStruct.offset = file.offset; mFiles.push_back(fileStruct); fullPaths.push_back(folder); } } // file record blocks if ((archiveFlags & 0x2) != 0) { mStringBuf.resize(totalFileNameLength); input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map? } size_t mStringBuffOffset = 0; size_t totalStringsSize = 0; for (std::uint32_t fileIndex = 0; fileIndex < mFiles.size(); ++fileIndex) { if (mStringBuffOffset >= totalFileNameLength) { fail("Corrupted names record in BSA file"); } //The vector guarantees that its elements occupy contiguous memory mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf); fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); while (mStringBuffOffset < totalFileNameLength) { if (mStringBuf[mStringBuffOffset] != '\0') { mStringBuffOffset++; } else { mStringBuffOffset++; break; } } //we want to keep one more 0 character at the end of each string totalStringsSize += fullPaths.at(fileIndex).length() + 1u; } mStringBuf.resize(totalStringsSize); mStringBuffOffset = 0; for (std::uint32_t fileIndex = 0u; fileIndex < mFiles.size(); fileIndex++) { size_t stringLength = fullPaths.at(fileIndex).length(); std::copy(fullPaths.at(fileIndex).c_str(), //plus 1 because we also want to copy 0 at the end of the string fullPaths.at(fileIndex).c_str() + stringLength + 1u, mStringBuf.data() + mStringBuffOffset); mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf); mStringBuffOffset += stringLength + 1u; } if (mStringBuffOffset != mStringBuf.size()) { fail("Could not resolve names of files in BSA file"); } convertCompressedSizesToUncompressed(); mIsLoaded = true; } CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string& str) const { // Force-convert the path into something both Windows and UNIX can handle first // to make sure Boost doesn't think the entire path is the filename on Linux // and subsequently purge it to determine the file folder. std::string path = str; std::replace(path.begin(), path.end(), '\\', '/'); boost::filesystem::path p(path); std::string stem = p.stem().string(); std::string ext = p.extension().string(); std::string folder = p.parent_path().string(); std::uint64_t folderHash = generateHash(folder, std::string()); auto it = mFolders.find(folderHash); if (it == mFolders.end()) return FileRecord(); // folder not found, return default which has offset of sInvalidOffset std::uint64_t fileHash = generateHash(stem, ext); auto iter = it->second.files.find(fileHash); if (iter == it->second.files.end()) return FileRecord(); // file not found, return default which has offset of sInvalidOffset return iter->second; } Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file) { FileRecord fileRec = getFileRecord(file->name()); if (!fileRec.isValid()) { fail("File not found: " + std::string(file->name())); } return getFile(fileRec); } void CompressedBSAFile::addFile(const std::string& filename, std::istream& file) { assert(false); //not implemented yet fail("Add file is not implemented for compressed BSA: " + filename); } Files::IStreamPtr CompressedBSAFile::getFile(const char* file) { FileRecord fileRec = getFileRecord(file); if (!fileRec.isValid()) { fail("File not found: " + std::string(file)); } return getFile(fileRec); } Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) { size_t size = fileRecord.getSizeWithoutCompressionFlag(); size_t uncompressedSize = size; bool compressed = fileRecord.isCompressed(mCompressedByDefault); Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename, fileRecord.offset, size); std::istream* fileStream = streamPtr.get(); if (mEmbeddedFileNames) { // Skip over the embedded file name char length = 0; fileStream->read(&length, 1); fileStream->ignore(length); size -= length + sizeof(char); } if (compressed) { fileStream->read(reinterpret_cast(&uncompressedSize), sizeof(uint32_t)); size -= sizeof(uint32_t); } auto memoryStreamPtr = std::make_unique(uncompressedSize); if (compressed) { if (mVersion != 0x69) // Non-SSE: zlib { boost::iostreams::filtering_streambuf inputStreamBuf; inputStreamBuf.push(boost::iostreams::zlib_decompressor()); inputStreamBuf.push(*fileStream); boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); boost::iostreams::copy(inputStreamBuf, sr); } else // SSE: lz4 { auto buffer = std::vector(size); fileStream->read(buffer.data(), size); LZ4F_decompressionContext_t context = nullptr; LZ4F_createDecompressionContext(&context, LZ4F_VERSION); LZ4F_decompressOptions_t options = {}; LZ4F_errorCode_t errorCode = LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.data(), &size, &options); if (LZ4F_isError(errorCode)) fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); errorCode = LZ4F_freeDecompressionContext(context); if (LZ4F_isError(errorCode)) fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); } } else { fileStream->read(memoryStreamPtr->getRawData(), size); } return std::make_unique>(std::move(memoryStreamPtr)); } BsaVersion CompressedBSAFile::detectVersion(const std::string& filePath) { namespace bfs = boost::filesystem; bfs::ifstream input(bfs::path(filePath), std::ios_base::binary); // Total archive size std::streamoff fsize = 0; if (input.seekg(0, std::ios_base::end)) { fsize = input.tellg(); input.seekg(0); } if (fsize < 12) { return BSAVER_UNKNOWN; } // Get essential header numbers // First 12 bytes uint32_t head[3]; input.read(reinterpret_cast(head), 12); if (head[0] == static_cast(BSAVER_UNCOMPRESSED)) { return BSAVER_UNCOMPRESSED; } if (head[0] == static_cast(BSAVER_COMPRESSED)) { return BSAVER_COMPRESSED; } return BSAVER_UNKNOWN; } //mFiles used by OpenMW expects uncompressed sizes void CompressedBSAFile::convertCompressedSizesToUncompressed() { for (auto & mFile : mFiles) { const FileRecord& fileRecord = getFileRecord(mFile.name()); if (!fileRecord.isValid()) { fail("Could not find file " + std::string(mFile.name()) + " in BSA"); } if (!fileRecord.isCompressed(mCompressedByDefault)) { //no need to fix fileSize in mFiles - uncompressed size already set continue; } Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename, fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); if (mEmbeddedFileNames) { std::string embeddedFileName; getBZString(embeddedFileName, *(dataBegin.get())); } dataBegin->read(reinterpret_cast(&(mFile.fileSize)), sizeof(mFile.fileSize)); } } std::uint64_t CompressedBSAFile::generateHash(std::string stem, std::string extension) { size_t len = stem.length(); if (len == 0) return 0; std::replace(stem.begin(), stem.end(), '/', '\\'); Misc::StringUtils::lowerCaseInPlace(stem); uint64_t result = stem[len-1] | (len >= 3 ? (stem[len-2] << 8) : 0) | (len << 16) | (stem[0] << 24); if (len >= 4) { uint32_t hash = 0; for (size_t i = 1; i <= len-3; ++i) hash = hash * 0x1003f + stem[i]; result += static_cast(hash) << 32; } if (extension.empty()) return result; Misc::StringUtils::lowerCaseInPlace(extension); if (extension == ".kf") result |= 0x80; else if (extension == ".nif") result |= 0x8000; else if (extension == ".dds") result |= 0x8080; else if (extension == ".wav") result |= 0x80000000; uint32_t hash = 0; for (const char &c : extension) hash = hash * 0x1003f + c; result += static_cast(hash) << 32; return result; } } //namespace Bsa openmw-openmw-0.48.0/components/bsa/compressedbsafile.hpp000066400000000000000000000065441445372753700236020ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: http://openmw.sourceforge.net/ This file (compressedbsafile.hpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see http://www.gnu.org/licenses/ . Compressed BSA stuff added by cc9cii 2018 */ #ifndef BSA_COMPRESSED_BSA_FILE_H #define BSA_COMPRESSED_BSA_FILE_H #include #include namespace Bsa { enum BsaVersion { BSAVER_UNKNOWN = 0x0, BSAVER_UNCOMPRESSED = 0x100, BSAVER_COMPRESSED = 0x415342 //B, S, A }; class CompressedBSAFile : private BSAFile { private: //special marker for invalid records, //equal to max uint32_t value static const uint32_t sInvalidOffset; //bit marking compression on file size static const uint32_t sCompressedFlag; struct FileRecord { std::uint32_t size; std::uint32_t offset; FileRecord(); bool isCompressed(bool bsaCompressedByDefault) const; bool isValid() const; std::uint32_t getSizeWithoutCompressionFlag() const; }; //if files in BSA without 30th bit enabled are compressed bool mCompressedByDefault; //if each file record begins with BZ string with file name bool mEmbeddedFileNames; std::uint32_t mVersion{0u}; struct FolderRecord { std::uint32_t count; std::uint64_t offset; std::map files; }; std::map mFolders; FileRecord getFileRecord(const std::string& str) const; void getBZString(std::string& str, std::istream& filestream); //mFiles used by OpenMW will contain uncompressed file sizes void convertCompressedSizesToUncompressed(); /// \brief Normalizes given filename or folder and generates format-compatible hash. See https://en.uesp.net/wiki/Tes4Mod:Hash_Calculation. static std::uint64_t generateHash(std::string stem, std::string extension) ; Files::IStreamPtr getFile(const FileRecord& fileRecord); public: using BSAFile::open; using BSAFile::getList; using BSAFile::getFilename; CompressedBSAFile(); virtual ~CompressedBSAFile(); //checks version of BSA from file header static BsaVersion detectVersion(const std::string& filePath); /// Read header information from the input source void readHeader() override; Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file); }; } #endif openmw-openmw-0.48.0/components/bsa/memorystream.hpp000066400000000000000000000032231445372753700226230ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: http://openmw.sourceforge.net/ This file (memorystream.hpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see http://www.gnu.org/licenses/ . Compressed BSA upgrade added by Azdul 2019 */ #ifndef BSA_MEMORY_STREAM_H #define BSA_MEMORY_STREAM_H #include #include #include namespace Bsa { /** Class replaces Ogre memory streams without introducing any new external dependencies beyond standard library. Allows to pass memory buffer as Files::IStreamPtr. Memory buffer is freed once the class instance is destroyed. */ class MemoryInputStream : private std::vector, public Files::MemBuf, public std::istream { public: explicit MemoryInputStream(size_t bufferSize) : std::vector(bufferSize) , Files::MemBuf(this->data(), this->size()) , std::istream(static_cast(this)) {} char* getRawData() { return this->data(); } }; } #endif openmw-openmw-0.48.0/components/bullethelpers/000077500000000000000000000000001445372753700214735ustar00rootroot00000000000000openmw-openmw-0.48.0/components/bullethelpers/aabb.hpp000066400000000000000000000012201445372753700230640ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_AABB_H #define OPENMW_COMPONENTS_BULLETHELPERS_AABB_H #include #include inline bool operator==(const btAABB& lhs, const btAABB& rhs) { return lhs.m_min == rhs.m_min && lhs.m_max == rhs.m_max; } inline bool operator!=(const btAABB& lhs, const btAABB& rhs) { return !(lhs == rhs); } namespace BulletHelpers { inline btAABB getAabb(const btCollisionShape& shape, const btTransform& transform) { btAABB result; shape.getAabb(transform, result.m_min, result.m_max); return result; } } #endif openmw-openmw-0.48.0/components/bullethelpers/collisionobject.hpp000066400000000000000000000013471445372753700253730ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_COLLISIONOBJECT_H #define OPENMW_COMPONENTS_BULLETHELPERS_COLLISIONOBJECT_H #include #include #include #include #include namespace BulletHelpers { inline std::unique_ptr makeCollisionObject(btCollisionShape* shape, const btVector3& position, const btQuaternion& rotation) { std::unique_ptr result = std::make_unique(); result->setCollisionShape(shape); result->setWorldTransform(btTransform(rotation, position)); return result; } } #endif openmw-openmw-0.48.0/components/bullethelpers/debug.hpp000066400000000000000000000053271445372753700233010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_DEBUG_H #define OPENMW_COMPONENTS_BULLETHELPERS_DEBUG_H #include #include #include #include #include inline std::ostream& operator <<(std::ostream& stream, const btVector3& value) { return stream << "btVector3(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.z() << ')'; } inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes value) { switch (value) { #ifndef SHAPE_NAME #define SHAPE_NAME(name) case name: return stream << #name; SHAPE_NAME(BOX_SHAPE_PROXYTYPE) SHAPE_NAME(TRIANGLE_SHAPE_PROXYTYPE) SHAPE_NAME(TETRAHEDRAL_SHAPE_PROXYTYPE) SHAPE_NAME(CONVEX_TRIANGLEMESH_SHAPE_PROXYTYPE) SHAPE_NAME(CONVEX_HULL_SHAPE_PROXYTYPE) SHAPE_NAME(CONVEX_POINT_CLOUD_SHAPE_PROXYTYPE) SHAPE_NAME(CUSTOM_POLYHEDRAL_SHAPE_TYPE) SHAPE_NAME(IMPLICIT_CONVEX_SHAPES_START_HERE) SHAPE_NAME(SPHERE_SHAPE_PROXYTYPE) SHAPE_NAME(MULTI_SPHERE_SHAPE_PROXYTYPE) SHAPE_NAME(CAPSULE_SHAPE_PROXYTYPE) SHAPE_NAME(CONE_SHAPE_PROXYTYPE) SHAPE_NAME(CONVEX_SHAPE_PROXYTYPE) SHAPE_NAME(CYLINDER_SHAPE_PROXYTYPE) SHAPE_NAME(UNIFORM_SCALING_SHAPE_PROXYTYPE) SHAPE_NAME(MINKOWSKI_SUM_SHAPE_PROXYTYPE) SHAPE_NAME(MINKOWSKI_DIFFERENCE_SHAPE_PROXYTYPE) SHAPE_NAME(BOX_2D_SHAPE_PROXYTYPE) SHAPE_NAME(CONVEX_2D_SHAPE_PROXYTYPE) SHAPE_NAME(CUSTOM_CONVEX_SHAPE_TYPE) SHAPE_NAME(CONCAVE_SHAPES_START_HERE) SHAPE_NAME(TRIANGLE_MESH_SHAPE_PROXYTYPE) SHAPE_NAME(SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) SHAPE_NAME(FAST_CONCAVE_MESH_PROXYTYPE) SHAPE_NAME(TERRAIN_SHAPE_PROXYTYPE) SHAPE_NAME(GIMPACT_SHAPE_PROXYTYPE) SHAPE_NAME(MULTIMATERIAL_TRIANGLE_MESH_PROXYTYPE) SHAPE_NAME(EMPTY_SHAPE_PROXYTYPE) SHAPE_NAME(STATIC_PLANE_PROXYTYPE) SHAPE_NAME(CUSTOM_CONCAVE_SHAPE_TYPE) SHAPE_NAME(CONCAVE_SHAPES_END_HERE) SHAPE_NAME(COMPOUND_SHAPE_PROXYTYPE) SHAPE_NAME(SOFTBODY_SHAPE_PROXYTYPE) SHAPE_NAME(HFFLUID_SHAPE_PROXYTYPE) SHAPE_NAME(HFFLUID_BUOYANT_CONVEX_SHAPE_PROXYTYPE) SHAPE_NAME(INVALID_SHAPE_PROXYTYPE) SHAPE_NAME(MAX_BROADPHASE_COLLISION_TYPES) #undef SHAPE_NAME #endif default: return stream << "undefined(" << static_cast(value) << ")"; } } #endif openmw-openmw-0.48.0/components/bullethelpers/heightfield.hpp000066400000000000000000000006031445372753700244570ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_HEIGHTFIELD_H #define OPENMW_COMPONENTS_BULLETHELPERS_HEIGHTFIELD_H #include namespace BulletHelpers { inline btVector3 getHeightfieldShift(int x, int y, int size, float minHeight, float maxHeight) { return btVector3((x + 0.5f) * size, (y + 0.5f) * size, (maxHeight + minHeight) * 0.5f); } } #endif openmw-openmw-0.48.0/components/bullethelpers/operators.hpp000066400000000000000000000012611445372753700242220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H #define OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H #include #include #include inline bool operator <(const btVector3& lhs, const btVector3& rhs) { return std::tie(lhs.x(), lhs.y(), lhs.z()) < std::tie(rhs.x(), rhs.y(), rhs.z()); } inline bool operator <(const btMatrix3x3& lhs, const btMatrix3x3& rhs) { return std::tie(lhs[0], lhs[1], lhs[2]) < std::tie(rhs[0], rhs[1], rhs[2]); } inline bool operator <(const btTransform& lhs, const btTransform& rhs) { return std::tie(lhs.getBasis(), lhs.getOrigin()) < std::tie(rhs.getBasis(), rhs.getOrigin()); } #endif openmw-openmw-0.48.0/components/bullethelpers/processtrianglecallback.hpp000066400000000000000000000016231445372753700270670ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H #define OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H #include #include namespace BulletHelpers { template class ProcessTriangleCallback : public btTriangleCallback { public: explicit ProcessTriangleCallback(Impl impl) : mImpl(std::move(impl)) {} void processTriangle(btVector3* triangle, int partId, int triangleIndex) override { return mImpl(triangle, partId, triangleIndex); } private: Impl mImpl; }; template ProcessTriangleCallback::type> makeProcessTriangleCallback(Impl&& impl) { return ProcessTriangleCallback::type>(std::forward(impl)); } } #endif openmw-openmw-0.48.0/components/bullethelpers/transformboundingbox.hpp000066400000000000000000000026101445372753700264550ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETHELPERS_TRANSFORMBOUNDINGBOX_H #define OPENMW_COMPONENTS_BULLETHELPERS_TRANSFORMBOUNDINGBOX_H #include #include #include namespace BulletHelpers { inline btVector3 min(const btVector3& a, const btVector3& b) { return btVector3(std::min(a.x(), b.x()), std::min(a.y(), b.y()), std::min(a.z(), b.z())); } inline btVector3 max(const btVector3& a, const btVector3& b) { return btVector3(std::max(a.x(), b.x()), std::max(a.y(), b.y()), std::max(a.z(), b.z())); } // http://dev.theomader.com/transform-bounding-boxes/ inline void transformBoundingBox(const btTransform& transform, btVector3& aabbMin, btVector3& aabbMax) { const btVector3 xa(transform.getBasis().getColumn(0) * aabbMin.x()); const btVector3 xb(transform.getBasis().getColumn(0) * aabbMax.x()); const btVector3 ya(transform.getBasis().getColumn(1) * aabbMin.y()); const btVector3 yb(transform.getBasis().getColumn(1) * aabbMax.y()); const btVector3 za(transform.getBasis().getColumn(2) * aabbMin.z()); const btVector3 zb(transform.getBasis().getColumn(2) * aabbMax.z()); aabbMin = min(xa, xb) + min(ya, yb) + min(za, zb) + transform.getOrigin(); aabbMax = max(xa, xb) + max(ya, yb) + max(za, zb) + transform.getOrigin(); } } #endif openmw-openmw-0.48.0/components/compiler/000077500000000000000000000000001445372753700204335ustar00rootroot00000000000000openmw-openmw-0.48.0/components/compiler/context.hpp000066400000000000000000000025571445372753700226410ustar00rootroot00000000000000#ifndef COMPILER_CONTEXT_H_INCLUDED #define COMPILER_CONTEXT_H_INCLUDED #include namespace Compiler { class Extensions; class Context { const Extensions *mExtensions; public: Context() : mExtensions (nullptr) {} virtual ~Context() = default; virtual bool canDeclareLocals() const = 0; ///< Is the compiler allowed to declare local variables? void setExtensions (const Extensions *extensions = nullptr) { mExtensions = extensions; } const Extensions *getExtensions() const { return mExtensions; } virtual char getGlobalType (const std::string& name) const = 0; ///< 'l: long, 's': short, 'f': float, ' ': does not exist. virtual std::pair getMemberType (const std::string& name, const std::string& id) const = 0; ///< Return type of member variable \a name in script \a id or in script of reference of /// \a id /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. /// second: true: script of reference virtual bool isId (const std::string& name) const = 0; ///< Does \a name match an ID, that can be referenced? }; } #endif openmw-openmw-0.48.0/components/compiler/controlparser.cpp000066400000000000000000000213331445372753700240360ustar00rootroot00000000000000#include "controlparser.hpp" #include #include #include #include "scanner.hpp" #include "generator.hpp" #include "errorhandler.hpp" #include "skipparser.hpp" namespace Compiler { bool ControlParser::parseIfBody (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==Scanner::K_endif || keyword==Scanner::K_elseif || keyword==Scanner::K_else) { std::pair entry; if (mState!=IfElseBodyState) mExprParser.append (entry.first); std::copy (mCodeBlock.begin(), mCodeBlock.end(), std::back_inserter (entry.second)); mIfCode.push_back (entry); mCodeBlock.clear(); if (keyword==Scanner::K_endif) { // store code for if-cascade Codes codes; for (auto iter (mIfCode.rbegin()); iter!=mIfCode.rend(); ++iter) { Codes block; if (iter!=mIfCode.rbegin()) Generator::jump (iter->second, static_cast(codes.size()+1)); if (!iter->first.empty()) { // if or elseif std::copy (iter->first.begin(), iter->first.end(), std::back_inserter (block)); Generator::jumpOnZero (block, static_cast(iter->second.size()+1)); } std::copy (iter->second.begin(), iter->second.end(), std::back_inserter (block)); std::swap (codes, block); std::copy (block.begin(), block.end(), std::back_inserter (codes)); } std::copy (codes.begin(), codes.end(), std::back_inserter (mCode)); mIfCode.clear(); mState = IfEndifState; } else if (keyword==Scanner::K_elseif) { mExprParser.reset(); scanner.scan (mExprParser); mState = IfElseifEndState; } else if (keyword==Scanner::K_else) { mState = IfElseJunkState; /// \todo should be IfElseEndState; add an option for that } return true; } else if (keyword==Scanner::K_if || keyword==Scanner::K_while) { // nested ControlParser parser (getErrorHandler(), getContext(), mLocals, mLiterals); if (parser.parseKeyword (keyword, loc, scanner)) scanner.scan (parser); parser.appendCode (mCodeBlock); return true; } else { mLineParser.reset(); if (mLineParser.parseKeyword (keyword, loc, scanner)) scanner.scan (mLineParser); return true; } } bool ControlParser::parseWhileBody (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==Scanner::K_endwhile) { Codes loop; Codes expr; mExprParser.append (expr); Generator::jump (loop, -static_cast (mCodeBlock.size()+expr.size())); std::copy (expr.begin(), expr.end(), std::back_inserter (mCode)); Codes skip; Generator::jumpOnZero (skip, static_cast (mCodeBlock.size()+loop.size()+1)); std::copy (skip.begin(), skip.end(), std::back_inserter (mCode)); std::copy (mCodeBlock.begin(), mCodeBlock.end(), std::back_inserter (mCode)); Codes loop2; Generator::jump (loop2, -static_cast (mCodeBlock.size()+expr.size()+skip.size())); if (loop.size()!=loop2.size()) throw std::logic_error ( "Internal compiler error: failed to generate a while loop"); std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode)); mState = WhileEndwhileState; return true; } else if (keyword==Scanner::K_if || keyword==Scanner::K_while) { // nested ControlParser parser (getErrorHandler(), getContext(), mLocals, mLiterals); if (parser.parseKeyword (keyword, loc, scanner)) scanner.scan (parser); parser.appendCode (mCodeBlock); return true; } else { mLineParser.reset(); if (mLineParser.parseKeyword (keyword, loc, scanner)) scanner.scan (mLineParser); return true; } } ControlParser::ControlParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals) : Parser (errorHandler, context), mLocals (locals), mLiterals (literals), mLineParser (errorHandler, context, locals, literals, mCodeBlock), mExprParser (errorHandler, context, locals, literals), mState (StartState) { } void ControlParser::appendCode (std::vector& code) const { std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); } bool ControlParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState || mState==WhileBodyState) { scanner.putbackName (name, loc); mLineParser.reset(); scanner.scan (mLineParser); return true; } else if (mState==IfElseJunkState) { getErrorHandler().warning ("Extra text after else", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); mState = IfElseBodyState; return true; } return Parser::parseName (name, loc, scanner); } bool ControlParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (mState==StartState) { if (keyword==Scanner::K_if || keyword==Scanner::K_elseif) { if (keyword==Scanner::K_elseif) getErrorHandler().warning ("elseif without matching if", loc); mExprParser.reset(); scanner.scan (mExprParser); mState = IfEndState; return true; } else if (keyword==Scanner::K_while) { mExprParser.reset(); scanner.scan (mExprParser); mState = WhileEndState; return true; } } else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState) { if (parseIfBody (keyword, loc, scanner)) return true; } else if (mState==WhileBodyState) { if ( parseWhileBody (keyword, loc, scanner)) return true; } else if (mState==IfElseJunkState) { getErrorHandler().warning ("Extra text after else", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); mState = IfElseBodyState; return true; } return Parser::parseKeyword (keyword, loc, scanner); } bool ControlParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_newline) { switch (mState) { case IfEndState: mState = IfBodyState; return true; case IfElseifEndState: mState = IfElseifBodyState; return true; case IfElseEndState: mState = IfElseBodyState; return true; case IfElseJunkState: mState = IfElseBodyState; return true; case WhileEndState: mState = WhileBodyState; return true; case IfBodyState: case IfElseifBodyState: case IfElseBodyState: case WhileBodyState: return true; // empty line case IfEndifState: case WhileEndwhileState: return false; default: ; } } else if (mState==IfElseJunkState) { getErrorHandler().warning ("Extra text after else", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); mState = IfElseBodyState; return true; } return Parser::parseSpecial (code, loc, scanner); } void ControlParser::reset() { mCode.clear(); mCodeBlock.clear(); mIfCode.clear(); mState = StartState; Parser::reset(); } } openmw-openmw-0.48.0/components/compiler/controlparser.hpp000066400000000000000000000043051445372753700240430ustar00rootroot00000000000000#ifndef COMPILER_CONTROLPARSER_H_INCLUDED #define COMPILER_CONTROLPARSER_H_INCLUDED #include #include #include "parser.hpp" #include "exprparser.hpp" #include "lineparser.hpp" namespace Compiler { class Locals; class Literals; // Control structure parser class ControlParser : public Parser { enum State { StartState, IfEndState, IfBodyState, IfElseifEndState, IfElseifBodyState, IfElseEndState, IfElseBodyState, IfEndifState, WhileEndState, WhileBodyState, WhileEndwhileState, IfElseJunkState }; typedef std::vector Codes; typedef std::vector > IfCodes; Locals& mLocals; Literals& mLiterals; Codes mCode; Codes mCodeBlock; IfCodes mIfCode; // condition, body LineParser mLineParser; ExprParser mExprParser; State mState; bool parseIfBody (int keyword, const TokenLoc& loc, Scanner& scanner); bool parseWhileBody (int keyword, const TokenLoc& loc, Scanner& scanner); public: ControlParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals); void appendCode (std::vector& code) const; ///< store generated code in \a code. bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void reset() override; ///< Reset parser to clean state. }; } #endif openmw-openmw-0.48.0/components/compiler/declarationparser.cpp000066400000000000000000000056311445372753700246460ustar00rootroot00000000000000#include "declarationparser.hpp" #include #include "scanner.hpp" #include "errorhandler.hpp" #include "skipparser.hpp" #include "locals.hpp" Compiler::DeclarationParser::DeclarationParser (ErrorHandler& errorHandler, const Context& context, Locals& locals) : Parser (errorHandler, context), mLocals (locals), mState (State_Begin), mType (0) {} bool Compiler::DeclarationParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==State_Name) { std::string name2 = ::Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); if (type!=' ') getErrorHandler().warning ("Local variable re-declaration", loc); else mLocals.declare (mType, name2); mState = State_End; return true; } else if (mState==State_End) { getErrorHandler().warning ("Extra text after local variable declaration", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; } return Parser::parseName (name, loc, scanner); } bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (mState==State_Begin) { switch (keyword) { case Scanner::K_short: mType = 's'; break; case Scanner::K_long: mType = 'l'; break; case Scanner::K_float: mType = 'f'; break; default: mType = 0; } if (mType) { mState = State_Name; return true; } } else if (mState==State_Name) { // allow keywords to be used as local variable names. MW script compiler, you suck! return parseName (loc.mLiteral, loc, scanner); } else if (mState==State_End) { getErrorHandler().warning ("Extra text after local variable declaration", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; } return Parser::parseKeyword (keyword, loc, scanner); } bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (mState==State_End) { if (code!=Scanner::S_newline) { getErrorHandler().warning ("Extra text after local variable declaration", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); } return false; } return Parser::parseSpecial (code, loc, scanner); } bool Compiler::DeclarationParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner) { if(mState == State_Name) { // Allow integers to be used as variable names return parseName(loc.mLiteral, loc, scanner); } return Parser::parseInt(value, loc, scanner); } void Compiler::DeclarationParser::reset() { mState = State_Begin; } openmw-openmw-0.48.0/components/compiler/declarationparser.hpp000066400000000000000000000024251445372753700246510ustar00rootroot00000000000000#ifndef COMPILER_DECLARATIONPARSER_H_INCLUDED #define COMPILER_DECLARATIONPARSER_H_INCLUDED #include "parser.hpp" namespace Compiler { class Locals; class DeclarationParser : public Parser { enum State { State_Begin, State_Name, State_End }; Locals& mLocals; State mState; char mType; public: DeclarationParser (ErrorHandler& errorHandler, const Context& context, Locals& locals); bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? void reset() override; }; } #endif openmw-openmw-0.48.0/components/compiler/discardparser.cpp000066400000000000000000000034711445372753700237720ustar00rootroot00000000000000#include "discardparser.hpp" #include "scanner.hpp" namespace Compiler { DiscardParser::DiscardParser (ErrorHandler& errorHandler, const Context& context) : Parser (errorHandler, context), mState (StartState) { } bool DiscardParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { if (mState==StartState || mState==MinusState) { if (isEmpty()) mTokenLoc = loc; start(); return false; } return Parser::parseInt (value, loc, scanner); } bool DiscardParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { if (mState==StartState || mState==MinusState) { if (isEmpty()) mTokenLoc = loc; start(); return false; } return Parser::parseFloat (value, loc, scanner); } bool DiscardParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==StartState) { if (isEmpty()) mTokenLoc = loc; start(); return false; } return Parser::parseName (name, loc, scanner); } bool DiscardParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_minus && mState==StartState) { if (isEmpty()) mTokenLoc = loc; start(); mState = MinusState; return true; } return Parser::parseSpecial (code, loc, scanner); } void DiscardParser::reset() { mState = StartState; mTokenLoc = TokenLoc(); Parser::reset(); } const TokenLoc& DiscardParser::getTokenLoc() const { return mTokenLoc; } } openmw-openmw-0.48.0/components/compiler/discardparser.hpp000066400000000000000000000030231445372753700237700ustar00rootroot00000000000000#ifndef COMPILER_DISCARDPARSER_H_INCLUDED #define COMPILER_DISCARDPARSER_H_INCLUDED #include "parser.hpp" #include "tokenloc.hpp" namespace Compiler { /// \brief Parse a single optional numeric value or string and discard it class DiscardParser : public Parser { enum State { StartState, MinusState }; State mState; TokenLoc mTokenLoc; public: DiscardParser (ErrorHandler& errorHandler, const Context& context); bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void reset() override; ///< Reset parser to clean state. /// Returns TokenLoc object for value. If no value has been parsed, the TokenLoc /// object will be default initialised. const TokenLoc& getTokenLoc() const; }; } #endif openmw-openmw-0.48.0/components/compiler/errorhandler.cpp000066400000000000000000000040601445372753700236260ustar00rootroot00000000000000#include "errorhandler.hpp" namespace Compiler { ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0), mWarningsMode (1), mDowngradeErrors (false) {} ErrorHandler::~ErrorHandler() = default; // Was compiling successful? bool ErrorHandler::isGood() const { return mErrors==0; } // Return number of errors int ErrorHandler::countErrors() const { return mErrors; } // Return number of warnings int ErrorHandler::countWarnings() const { return mWarnings; } // Generate a warning message. void ErrorHandler::warning (const std::string& message, const TokenLoc& loc) { if (mWarningsMode==1 || // temporarily change from mode 2 to mode 1 if error downgrading is enabled to // avoid infinite recursion (mWarningsMode==2 && mDowngradeErrors)) { ++mWarnings; report (message, loc, WarningMessage); } else if (mWarningsMode==2) error (message, loc); } // Generate an error message. void ErrorHandler::error (const std::string& message, const TokenLoc& loc) { if (mDowngradeErrors) { warning (message, loc); return; } ++mErrors; report (message, loc, ErrorMessage); } // Generate an error message for an unexpected EOF. void ErrorHandler::endOfFile() { ++mErrors; report ("unexpected end of file", ErrorMessage); } // Remove all previous error/warning events void ErrorHandler::reset() { mErrors = mWarnings = 0; } void ErrorHandler::setWarningsMode (int mode) { mWarningsMode = mode; } void ErrorHandler::downgradeErrors (bool downgrade) { mDowngradeErrors = downgrade; } ErrorDowngrade::ErrorDowngrade (ErrorHandler& handler) : mHandler (handler) { mHandler.downgradeErrors (true); } ErrorDowngrade::~ErrorDowngrade() { mHandler.downgradeErrors (false); } } openmw-openmw-0.48.0/components/compiler/errorhandler.hpp000066400000000000000000000043051445372753700236350ustar00rootroot00000000000000#ifndef COMPILER_ERRORHANDLER_H_INCLUDED #define COMPILER_ERRORHANDLER_H_INCLUDED #include namespace Compiler { struct TokenLoc; /// \brief Error handling /// /// This class collects errors and provides an interface for reporting them to the user. class ErrorHandler { int mWarnings; int mErrors; int mWarningsMode; bool mDowngradeErrors; protected: enum Type { WarningMessage, ErrorMessage }; private: // mutators virtual void report (const std::string& message, const TokenLoc& loc, Type type) = 0; ///< Report error to the user. virtual void report (const std::string& message, Type type) = 0; ///< Report a file related error public: ErrorHandler(); ///< constructor virtual ~ErrorHandler(); ///< destructor bool isGood() const; ///< Was compiling successful? int countErrors() const; ///< Return number of errors int countWarnings() const; ///< Return number of warnings void warning (const std::string& message, const TokenLoc& loc); ///< Generate a warning message. void error (const std::string& message, const TokenLoc& loc); ///< Generate an error message. void endOfFile(); ///< Generate an error message for an unexpected EOF. virtual void reset(); ///< Remove all previous error/warning events void setWarningsMode (int mode); ///< // 0 ignore, 1 rate as warning, 2 rate as error /// Treat errors as warnings. void downgradeErrors (bool downgrade); }; class ErrorDowngrade { ErrorHandler& mHandler; /// not implemented ErrorDowngrade (const ErrorDowngrade&); /// not implemented ErrorDowngrade& operator= (const ErrorDowngrade&); public: explicit ErrorDowngrade (ErrorHandler& handler); ~ErrorDowngrade(); }; } #endif openmw-openmw-0.48.0/components/compiler/exception.hpp000066400000000000000000000015611445372753700231450ustar00rootroot00000000000000#ifndef COMPILER_EXCEPTION_H_INCLUDED #define COMPILER_EXCEPTION_H_INCLUDED #include namespace Compiler { /// \brief Exception: Error while parsing the source class SourceException : public std::exception { public: const char *what() const noexcept override { return "Compile error";} ///< Return error message }; /// \brief Exception: File error class FileException : public SourceException { public: const char *what() const noexcept override { return "Can't read file"; } ///< Return error message }; /// \brief Exception: EOF condition encountered class EOFException : public SourceException { public: const char *what() const noexcept override { return "End of file"; } ///< Return error message }; } #endif openmw-openmw-0.48.0/components/compiler/exprparser.cpp000066400000000000000000000501351445372753700233360ustar00rootroot00000000000000#include "exprparser.hpp" #include #include #include #include #include #include #include #include "generator.hpp" #include "scanner.hpp" #include "errorhandler.hpp" #include "locals.hpp" #include "stringparser.hpp" #include "extensions.hpp" #include "context.hpp" #include "discardparser.hpp" #include "junkparser.hpp" namespace Compiler { int ExprParser::getPriority (char op) { switch (op) { case '(': return 0; case 'e': // == case 'n': // != case 'l': // < case 'L': // <= case 'g': // < case 'G': // >= return 1; case '+': case '-': return 2; case '*': case '/': return 3; case 'm': return 4; } return 0; } char ExprParser::getOperandType (int Index) const { assert (!mOperands.empty()); assert (Index>=0); assert (Index (mOperands.size())); return mOperands[mOperands.size()-1-Index]; } char ExprParser::getOperator() const { assert (!mOperators.empty()); return mOperators[mOperators.size()-1]; } bool ExprParser::isOpen() const { return std::find (mOperators.begin(), mOperators.end(), '(')!=mOperators.end(); } void ExprParser::popOperator() { assert (!mOperators.empty()); mOperators.resize (mOperators.size()-1); } void ExprParser::popOperand() { assert (!mOperands.empty()); mOperands.resize (mOperands.size()-1); } void ExprParser::replaceBinaryOperands() { char t1 = getOperandType (1); char t2 = getOperandType(); popOperand(); popOperand(); if (t1==t2) mOperands.push_back (t1); else if (t1=='f' || t2=='f') mOperands.push_back ('f'); else throw std::logic_error ("Failed to determine result operand type"); } void ExprParser::pop() { char op = getOperator(); switch (op) { case 'm': Generator::negate (mCode, getOperandType()); popOperator(); break; case '+': Generator::add (mCode, getOperandType (1), getOperandType()); popOperator(); replaceBinaryOperands(); break; case '-': Generator::sub (mCode, getOperandType (1), getOperandType()); popOperator(); replaceBinaryOperands(); break; case '*': Generator::mul (mCode, getOperandType (1), getOperandType()); popOperator(); replaceBinaryOperands(); break; case '/': Generator::div (mCode, getOperandType (1), getOperandType()); popOperator(); replaceBinaryOperands(); break; case 'e': case 'n': case 'l': case 'L': case 'g': case 'G': Generator::compare (mCode, op, getOperandType (1), getOperandType()); popOperator(); popOperand(); popOperand(); mOperands.push_back ('l'); break; default: throw std::logic_error ("Unknown operator"); } } void ExprParser::pushIntegerLiteral (int value) { mNextOperand = false; mOperands.push_back ('l'); Generator::pushInt (mCode, mLiterals, value); } void ExprParser::pushFloatLiteral (float value) { mNextOperand = false; mOperands.push_back ('f'); Generator::pushFloat (mCode, mLiterals, value); } void ExprParser::pushBinaryOperator (char c) { while (!mOperators.empty() && getPriority (getOperator())>=getPriority (c)) pop(); mOperators.push_back (c); mNextOperand = true; } void ExprParser::close() { while (getOperator()!='(') pop(); popOperator(); } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner) { return parseArguments (arguments, scanner, mCode); } bool ExprParser::handleMemberAccess (const std::string& name) { mMemberOp = false; std::string name2 = Misc::StringUtils::lowerCase (name); std::string id = Misc::StringUtils::lowerCase (mExplicit); std::pair type = getContext().getMemberType (name2, id); if (type.first!=' ') { Generator::fetchMember (mCode, mLiterals, type.first, name2, id, !type.second); mNextOperand = false; mExplicit.clear(); mOperands.push_back (type.first=='f' ? 'f' : 'l'); return true; } return false; } ExprParser::ExprParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, bool argument) : Parser (errorHandler, context), mLocals (locals), mLiterals (literals), mNextOperand (true), mFirst (true), mArgument (argument), mRefOp (false), mMemberOp (false) {} bool ExprParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { if (!mExplicit.empty()) return Parser::parseInt (value, loc, scanner); mFirst = false; if (mNextOperand) { start(); pushIntegerLiteral (value); mTokenLoc = loc; return true; } else { scanner.putbackInt (value, loc); return false; } } bool ExprParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { if (!mExplicit.empty()) return Parser::parseFloat (value, loc, scanner); mFirst = false; if (mNextOperand) { start(); pushFloatLiteral (value); mTokenLoc = loc; return true; } else { scanner.putbackFloat (value, loc); return false; } } bool ExprParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (!mExplicit.empty()) { if (!mRefOp) { if (mMemberOp && handleMemberAccess (name)) return true; return Parser::parseName (name, loc, scanner); } else { mExplicit.clear(); getErrorHandler().warning ("Stray explicit reference", loc); } } mFirst = false; if (mNextOperand) { start(); std::string name2 = Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); if (type!=' ') { Generator::fetchLocal (mCode, type, mLocals.getIndex (name2)); mNextOperand = false; mOperands.push_back (type=='f' ? 'f' : 'l'); return true; } type = getContext().getGlobalType (name2); if (type!=' ') { Generator::fetchGlobal (mCode, mLiterals, type, name2); mNextOperand = false; mOperands.push_back (type=='f' ? 'f' : 'l'); return true; } if (mExplicit.empty() && getContext().isId (name2)) { mExplicit = name2; return true; } // This is terrible, but of course we must have this for legacy content. // Convert the string to a number even if it's impossible and use it as a number literal. // Can't use stof/atof or to_string out of locale concerns. float number; std::stringstream stream(name2); stream >> number; stream.str(std::string()); stream.clear(); stream << number; pushFloatLiteral(number); mTokenLoc = loc; getErrorHandler().warning ("Parsing a non-variable string as a number: " + stream.str(), loc); return true; } else { scanner.putbackName (name, loc); return false; } } bool ExprParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (const Extensions *extensions = getContext().getExtensions()) { char returnType; // ignored std::string argumentType; // ignored bool hasExplicit = false; // ignored bool isInstruction = extensions->isInstruction (keyword, argumentType, hasExplicit); if(isInstruction || (mExplicit.empty() && extensions->isFunction(keyword, returnType, argumentType, hasExplicit))) { std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); if(isInstruction || mLocals.getType(Misc::StringUtils::lowerCase(name)) != ' ') { // pretend this is not a keyword return parseName (name, loc, scanner); } } } if (keyword==Scanner::K_end || keyword==Scanner::K_begin || keyword==Scanner::K_short || keyword==Scanner::K_long || keyword==Scanner::K_float || keyword==Scanner::K_if || keyword==Scanner::K_endif || keyword==Scanner::K_else || keyword==Scanner::K_elseif || keyword==Scanner::K_while || keyword==Scanner::K_endwhile || keyword==Scanner::K_return || keyword==Scanner::K_messagebox || keyword==Scanner::K_set || keyword==Scanner::K_to) { return parseName (loc.mLiteral, loc, scanner); } mFirst = false; if (!mExplicit.empty()) { if (mRefOp && mNextOperand) { // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) { char returnType; std::string argumentType; bool hasExplicit = true; if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) { if (!hasExplicit) { getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } start(); mTokenLoc = loc; int optionals = parseArguments (argumentType, scanner); extensions->generateFunctionCode (keyword, mCode, mLiterals, mExplicit, optionals); mOperands.push_back (returnType); mExplicit.clear(); mRefOp = false; mNextOperand = false; return true; } } } return Parser::parseKeyword (keyword, loc, scanner); } if (mNextOperand) { // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) { start(); char returnType; std::string argumentType; bool hasExplicit = false; if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) { mTokenLoc = loc; int optionals = parseArguments (argumentType, scanner); extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals); mOperands.push_back (returnType); mNextOperand = false; return true; } } } else { scanner.putbackKeyword (keyword, loc); return false; } return Parser::parseKeyword (keyword, loc, scanner); } bool ExprParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (!mExplicit.empty()) { if (mRefOp && code==Scanner::S_open) { /// \todo add option to disable this workaround mOperators.push_back ('('); mTokenLoc = loc; return true; } if (!mRefOp && code==Scanner::S_ref) { mRefOp = true; return true; } if (!mMemberOp && code==Scanner::S_member) { mMemberOp = true; return true; } return Parser::parseSpecial (code, loc, scanner); } mFirst = false; if (code==Scanner::S_newline) { // end marker if (mTokenLoc.mLiteral.empty()) mTokenLoc = loc; scanner.putbackSpecial (code, loc); return false; } if (code==Scanner::S_minus && mNextOperand) { // unary mOperators.push_back ('m'); mTokenLoc = loc; return true; } if (code ==Scanner::S_plus && mNextOperand) { // Also unary, but +, just ignore it mTokenLoc = loc; return true; } if (code==Scanner::S_open) { if (mNextOperand) { mOperators.push_back ('('); mTokenLoc = loc; return true; } else { scanner.putbackSpecial (code, loc); return false; } } if (code==Scanner::S_close && !mNextOperand) { if (isOpen()) { close(); return true; } mTokenLoc = loc; scanner.putbackSpecial (code, loc); return false; } if (!mNextOperand) { mTokenLoc = loc; char c = 0; // comparison switch (code) { case Scanner::S_plus: c = '+'; break; case Scanner::S_minus: c = '-'; break; case Scanner::S_mult: pushBinaryOperator ('*'); return true; case Scanner::S_div: pushBinaryOperator ('/'); return true; case Scanner::S_cmpEQ: c = 'e'; break; case Scanner::S_cmpNE: c = 'n'; break; case Scanner::S_cmpLT: c = 'l'; break; case Scanner::S_cmpLE: c = 'L'; break; case Scanner::S_cmpGT: c = 'g'; break; case Scanner::S_cmpGE: c = 'G'; break; } if (c) { if (mArgument && !isOpen()) { // expression ends here // Thank you Morrowind for this rotten syntax :( scanner.putbackSpecial (code, loc); return false; } pushBinaryOperator (c); return true; } } return Parser::parseSpecial (code, loc, scanner); } void ExprParser::reset() { mOperands.clear(); mOperators.clear(); mNextOperand = true; mCode.clear(); mFirst = true; mExplicit.clear(); mRefOp = false; mMemberOp = false; Parser::reset(); } char ExprParser::append (std::vector& code) { if (mOperands.empty() && mOperators.empty()) { getErrorHandler().error ("Missing expression", mTokenLoc); return 'l'; } if (mNextOperand || mOperands.empty()) { getErrorHandler().error ("Syntax error in expression", mTokenLoc); return 'l'; } while (!mOperators.empty()) pop(); std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); assert (mOperands.size()==1); return mOperands[0]; } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner, std::vector& code, int ignoreKeyword, bool expectNames) { bool optional = false; int optionalCount = 0; ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true); StringParser stringParser (getErrorHandler(), getContext(), mLiterals); DiscardParser discardParser (getErrorHandler(), getContext()); JunkParser junkParser (getErrorHandler(), getContext(), ignoreKeyword); std::stack > stack; for (char argument : arguments) { if (argument=='/') { optional = true; } else if (argument=='S' || argument=='c' || argument=='x') { stringParser.reset(); if (optional || argument=='x') stringParser.setOptional (true); if (argument=='c') stringParser.smashCase(); if (argument=='x') stringParser.discard(); scanner.enableExpectName(); scanner.scan (stringParser); if ((optional || argument=='x') && stringParser.isEmpty()) break; if (argument!='x') { std::vector tmp; stringParser.append (tmp); stack.push (tmp); if (optional) ++optionalCount; } else getErrorHandler().warning ("Extra argument", stringParser.getTokenLoc()); } else if (argument=='X') { parser.reset(); parser.setOptional (true); scanner.scan (parser); if (parser.isEmpty()) break; else getErrorHandler().warning("Extra argument", parser.getTokenLoc()); } else if (argument=='z') { discardParser.reset(); discardParser.setOptional (true); scanner.scan (discardParser); if (discardParser.isEmpty()) break; else getErrorHandler().warning("Extra argument", discardParser.getTokenLoc()); } else if (argument=='j') { /// \todo disable this when operating in strict mode junkParser.reset(); scanner.scan (junkParser); } else { parser.reset(); if (optional) parser.setOptional (true); if(expectNames) scanner.enableExpectName(); scanner.scan (parser); if (optional && parser.isEmpty()) break; std::vector tmp; char type = parser.append (tmp); if (type!=argument) Generator::convert (tmp, type, argument); stack.push (tmp); if (optional) ++optionalCount; } } while (!stack.empty()) { std::vector& tmp = stack.top(); std::copy (tmp.begin(), tmp.end(), std::back_inserter (code)); stack.pop(); } return optionalCount; } const TokenLoc& ExprParser::getTokenLoc() const { return mTokenLoc; } } openmw-openmw-0.48.0/components/compiler/exprparser.hpp000066400000000000000000000067711445372753700233520ustar00rootroot00000000000000#ifndef COMPILER_EXPRPARSER_H_INCLUDED #define COMPILER_EXPRPARSER_H_INCLUDED #include #include #include "parser.hpp" #include "tokenloc.hpp" namespace Compiler { class Locals; class Literals; class ExprParser : public Parser { Locals& mLocals; Literals& mLiterals; std::vector mOperands; std::vector mOperators; bool mNextOperand; TokenLoc mTokenLoc; std::vector mCode; bool mFirst; bool mArgument; std::string mExplicit; bool mRefOp; bool mMemberOp; static int getPriority (char op) ; char getOperandType (int Index = 0) const; char getOperator() const; bool isOpen() const; void popOperator(); void popOperand(); void replaceBinaryOperands(); void pop(); void pushIntegerLiteral (int value); void pushFloatLiteral (float value); void pushBinaryOperator (char c); void close(); int parseArguments (const std::string& arguments, Scanner& scanner); bool handleMemberAccess (const std::string& name); public: ExprParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, bool argument = false); ///< constructor /// \param argument Parser is used to parse function- or instruction- /// arguments (this influences the precedence rules). char getType() const; ///< Return type of parsed expression ('l' integer, 'f' float) bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void reset() override; ///< Reset parser to clean state. char append (std::vector& code); ///< Generate code for parsed expression. /// \return Type ('l': integer, 'f': float) int parseArguments (const std::string& arguments, Scanner& scanner, std::vector& code, int ignoreKeyword = -1, bool expectNames = false); ///< Parse sequence of arguments specified by \a arguments. /// \param arguments Uses ScriptArgs typedef /// \see Compiler::ScriptArgs /// \param invert Store arguments in reverted order. /// \param ignoreKeyword A keyword that is seen as junk /// \return number of optional arguments const TokenLoc& getTokenLoc() const; }; } #endif openmw-openmw-0.48.0/components/compiler/extensions.cpp000066400000000000000000000147051445372753700233450ustar00rootroot00000000000000#include "extensions.hpp" #include #include #include "generator.hpp" #include "literals.hpp" namespace Compiler { Extensions::Extensions() : mNextKeywordIndex (-1) {} int Extensions::searchKeyword (const std::string& keyword) const { auto iter = mKeywords.find (keyword); if (iter==mKeywords.end()) return 0; return iter->second; } bool Extensions::isFunction (int keyword, ScriptReturn& returnType, ScriptArgs& argumentType, bool& explicitReference) const { auto iter = mFunctions.find (keyword); if (iter==mFunctions.end()) return false; if (explicitReference && iter->second.mCodeExplicit==-1) explicitReference = false; returnType = iter->second.mReturn; argumentType = iter->second.mArguments; return true; } bool Extensions::isInstruction (int keyword, ScriptArgs& argumentType, bool& explicitReference) const { auto iter = mInstructions.find (keyword); if (iter==mInstructions.end()) return false; if (explicitReference && iter->second.mCodeExplicit==-1) explicitReference = false; argumentType = iter->second.mArguments; return true; } void Extensions::registerFunction (const std::string& keyword, ScriptReturn returnType, const ScriptArgs& argumentType, int code, int codeExplicit) { Function function; if (argumentType.find ('/')==std::string::npos) { function.mSegment = 5; assert (code>=33554432 && code<=67108863); assert (codeExplicit==-1 || (codeExplicit>=33554432 && codeExplicit<=67108863)); } else { function.mSegment = 3; assert (code>=0x20000 && code<=0x2ffff); assert (codeExplicit==-1 || (codeExplicit>=0x20000 && codeExplicit<=0x2ffff)); } int keywordIndex = mNextKeywordIndex--; mKeywords.insert (std::make_pair (keyword, keywordIndex)); function.mReturn = returnType; function.mArguments = argumentType; function.mCode = code; function.mCodeExplicit = codeExplicit; mFunctions.insert (std::make_pair (keywordIndex, function)); } void Extensions::registerInstruction (const std::string& keyword, const ScriptArgs& argumentType, int code, int codeExplicit) { Instruction instruction; if (argumentType.find ('/')==std::string::npos) { instruction.mSegment = 5; assert (code>=33554432 && code<=67108863); assert (codeExplicit==-1 || (codeExplicit>=33554432 && codeExplicit<=67108863)); } else { instruction.mSegment = 3; assert (code>=0x20000 && code<=0x2ffff); assert (codeExplicit==-1 || (codeExplicit>=0x20000 && codeExplicit<=0x2ffff)); } int keywordIndex = mNextKeywordIndex--; mKeywords.insert (std::make_pair (keyword, keywordIndex)); instruction.mArguments = argumentType; instruction.mCode = code; instruction.mCodeExplicit = codeExplicit; mInstructions.insert (std::make_pair (keywordIndex, instruction)); } void Extensions::generateFunctionCode (int keyword, std::vector& code, Literals& literals, const std::string& id, int optionalArguments) const { assert (optionalArguments>=0); auto iter = mFunctions.find (keyword); if (iter==mFunctions.end()) throw std::logic_error ("unknown custom function keyword"); if (optionalArguments && iter->second.mSegment!=3) throw std::logic_error ("functions with optional arguments must be placed into segment 3"); if (!id.empty()) { if (iter->second.mCodeExplicit==-1) throw std::logic_error ("explicit references not supported"); int index = literals.addString (id); Generator::pushInt (code, literals, index); } switch (iter->second.mSegment) { case 3: if (optionalArguments>=256) throw std::logic_error ("number of optional arguments is too large for segment 3"); code.push_back (Generator::segment3 ( id.empty() ? iter->second.mCode : iter->second.mCodeExplicit, optionalArguments)); break; case 5: code.push_back (Generator::segment5 ( id.empty() ? iter->second.mCode : iter->second.mCodeExplicit)); break; default: throw std::logic_error ("unsupported code segment"); } } void Extensions::generateInstructionCode (int keyword, std::vector& code, Literals& literals, const std::string& id, int optionalArguments) const { assert (optionalArguments>=0); auto iter = mInstructions.find (keyword); if (iter==mInstructions.end()) throw std::logic_error ("unknown custom instruction keyword"); if (optionalArguments && iter->second.mSegment!=3) throw std::logic_error ("instructions with optional arguments must be placed into segment 3"); if (!id.empty()) { if (iter->second.mCodeExplicit==-1) throw std::logic_error ("explicit references not supported"); int index = literals.addString (id); Generator::pushInt (code, literals, index); } switch (iter->second.mSegment) { case 3: if (optionalArguments>=256) throw std::logic_error ("number of optional arguments is too large for segment 3"); code.push_back (Generator::segment3 ( id.empty() ? iter->second.mCode : iter->second.mCodeExplicit, optionalArguments)); break; case 5: code.push_back (Generator::segment5 ( id.empty() ? iter->second.mCode : iter->second.mCodeExplicit)); break; default: throw std::logic_error ("unsupported code segment"); } } void Extensions::listKeywords (std::vector& keywords) const { for (const auto & mKeyword : mKeywords) keywords.push_back (mKeyword.first); } } openmw-openmw-0.48.0/components/compiler/extensions.hpp000066400000000000000000000111301445372753700233370ustar00rootroot00000000000000#ifndef COMPILER_EXTENSIONS_H_INCLUDED #define COMPILER_EXTENSIONS_H_INCLUDED #include #include #include #include namespace Compiler { class Literals; /// Typedef for script arguments string /** Every character reperesents an argument to the command. All arguments are required until a /, after which every argument is optional.
Eg: fff/f represents 3 required floats followed by one optional float
f - Float
c - String, case smashed
l - Integer
s - Short
S - String, case preserved
x - Optional, ignored string argument. Emits a parser warning when this argument is supplied.
X - Optional, ignored numeric expression. Emits a parser warning when this argument is supplied.
z - Optional, ignored string or numeric argument. Emits a parser warning when this argument is supplied.
j - A piece of junk (either . or a specific keyword) **/ typedef std::string ScriptArgs; /// Typedef for script return char /** The character represents the type of data being returned.
f - float
S - String (Cell names)
l - Integer **/ typedef char ScriptReturn; /// \brief Collection of compiler extensions class Extensions { struct Function { char mReturn; ScriptArgs mArguments; int mCode; int mCodeExplicit; int mSegment; }; struct Instruction { ScriptArgs mArguments; int mCode; int mCodeExplicit; int mSegment; }; int mNextKeywordIndex; std::map mKeywords; std::map mFunctions; std::map mInstructions; public: Extensions(); int searchKeyword (const std::string& keyword) const; ///< Return extension keyword code, that is assigned to the string \a keyword. /// - if no match is found 0 is returned. /// - keyword must be all lower case. bool isFunction (int keyword, ScriptReturn& returnType, ScriptArgs& argumentType, bool& explicitReference) const; ///< Is this keyword registered with a function? If yes, return return and argument /// types. /// \param explicitReference In: has explicit reference; Out: set to false, if /// explicit reference is not available for this instruction. bool isInstruction (int keyword, ScriptArgs& argumentType, bool& explicitReference) const; ///< Is this keyword registered with a function? If yes, return argument types. /// \param explicitReference In: has explicit reference; Out: set to false, if /// explicit reference is not available for this instruction. void registerFunction (const std::string& keyword, ScriptReturn returnType, const ScriptArgs& argumentType, int code, int codeExplicit = -1); ///< Register a custom function /// - keyword must be all lower case. /// - keyword must be unique /// - if explicit references are not supported, segment5codeExplicit must be set to -1 /// \note Currently only segment 3 and segment 5 opcodes are supported. void registerInstruction (const std::string& keyword, const ScriptArgs& argumentType, int code, int codeExplicit = -1); ///< Register a custom instruction /// - keyword must be all lower case. /// - keyword must be unique /// - if explicit references are not supported, segment5codeExplicit must be set to -1 /// \note Currently only segment 3 and segment 5 opcodes are supported. void generateFunctionCode (int keyword, std::vector& code, Literals& literals, const std::string& id, int optionalArguments) const; ///< Append code for function to \a code. void generateInstructionCode (int keyword, std::vector& code, Literals& literals, const std::string& id, int optionalArguments) const; ///< Append code for function to \a code. void listKeywords (std::vector& keywords) const; ///< Append all known keywords to \a kaywords. }; } #endif openmw-openmw-0.48.0/components/compiler/extensions0.cpp000066400000000000000000001072551445372753700234300ustar00rootroot00000000000000#include "extensions0.hpp" #include "opcodes.hpp" #include "extensions.hpp" namespace Compiler { void registerExtensions (Extensions& extensions, bool consoleOnly) { Ai::registerExtensions (extensions); Animation::registerExtensions (extensions); Cell::registerExtensions (extensions); Container::registerExtensions (extensions); Control::registerExtensions (extensions); Dialogue::registerExtensions (extensions); Gui::registerExtensions (extensions); Misc::registerExtensions (extensions); Sky::registerExtensions (extensions); Sound::registerExtensions (extensions); Stats::registerExtensions (extensions); Transformation::registerExtensions (extensions); if (consoleOnly) { Console::registerExtensions (extensions); User::registerExtensions (extensions); } } namespace Ai { void registerExtensions (Extensions& extensions) { extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate, opcodeAIActivateExplicit); extensions.registerInstruction ("aitravel", "fff/lx", opcodeAiTravel, opcodeAiTravelExplicit); extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort, opcodeAiEscortExplicit); extensions.registerInstruction ("aiescortcell", "ccffff/l", opcodeAiEscortCell, opcodeAiEscortCellExplicit); extensions.registerInstruction ("aiwander", "fff/llllllllll", opcodeAiWander, opcodeAiWanderExplicit); extensions.registerInstruction ("aifollow", "cffff/llllllll", opcodeAiFollow, opcodeAiFollowExplicit); extensions.registerInstruction ("aifollowcell", "ccffff/l", opcodeAiFollowCell, opcodeAiFollowCellExplicit); extensions.registerFunction ("getaipackagedone", 'l', "", opcodeGetAiPackageDone, opcodeGetAiPackageDoneExplicit); extensions.registerFunction ("getcurrentaipackage", 'l', "", opcodeGetCurrentAiPackage, opcodeGetCurrentAiPackageExplicit); extensions.registerFunction ("getdetected", 'l', "c", opcodeGetDetected, opcodeGetDetectedExplicit); extensions.registerInstruction ("sethello", "l", opcodeSetHello, opcodeSetHelloExplicit); extensions.registerInstruction ("setfight", "l", opcodeSetFight, opcodeSetFightExplicit); extensions.registerInstruction ("setflee", "l", opcodeSetFlee, opcodeSetFleeExplicit); extensions.registerInstruction ("setalarm", "l", opcodeSetAlarm, opcodeSetAlarmExplicit); extensions.registerInstruction ("modhello", "l", opcodeModHello, opcodeModHelloExplicit); extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); extensions.registerInstruction ("toggleai", "", opcodeToggleAI); extensions.registerInstruction ("tai", "", opcodeToggleAI); extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit); extensions.registerInstruction("stopcombat", "x", opcodeStopCombat, opcodeStopCombatExplicit); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction("gettarget", 'l', "c", opcodeGetTarget, opcodeGetTargetExplicit); extensions.registerInstruction("face", "ffX", opcodeFace, opcodeFaceExplicit); } } namespace Animation { void registerExtensions (Extensions& extensions) { extensions.registerInstruction ("skipanim", "", opcodeSkipAnim, opcodeSkipAnimExplicit); extensions.registerInstruction ("playgroup", "c/l", opcodePlayAnim, opcodePlayAnimExplicit); extensions.registerInstruction ("loopgroup", "cl/l", opcodeLoopAnim, opcodeLoopAnimExplicit); } } namespace Cell { void registerExtensions (Extensions& extensions) { extensions.registerFunction ("cellchanged", 'l', "", opcodeCellChanged); extensions.registerInstruction("testcells", "", opcodeTestCells); extensions.registerInstruction("testinteriorcells", "", opcodeTestInteriorCells); extensions.registerInstruction ("coc", "S", opcodeCOC); extensions.registerInstruction ("centeroncell", "S", opcodeCOC); extensions.registerInstruction ("coe", "ll", opcodeCOE); extensions.registerInstruction ("centeronexterior", "ll", opcodeCOE); extensions.registerInstruction ("setwaterlevel", "f", opcodeSetWaterLevel); extensions.registerInstruction ("modwaterlevel", "f", opcodeModWaterLevel); extensions.registerFunction ("getinterior", 'l', "", opcodeGetInterior); extensions.registerFunction ("getpccell", 'l', "c", opcodeGetPCCell); extensions.registerFunction ("getwaterlevel", 'f', "", opcodeGetWaterLevel); } } namespace Console { void registerExtensions (Extensions& extensions) { } } namespace Container { void registerExtensions (Extensions& extensions) { extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "cX", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "clX", opcodeRemoveItem, opcodeRemoveItemExplicit); extensions.registerInstruction ("equip", "cX", opcodeEquip, opcodeEquipExplicit); extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); extensions.registerFunction ("hasitemequipped", 'l', "c", opcodeHasItemEquipped, opcodeHasItemEquippedExplicit); extensions.registerFunction ("hassoulgem", 'l', "c", opcodeHasSoulGem, opcodeHasSoulGemExplicit); extensions.registerFunction ("getweapontype", 'l', "", opcodeGetWeaponType, opcodeGetWeaponTypeExplicit); } } namespace Control { void registerExtensions (Extensions& extensions) { std::string enable ("enable"); std::string disable ("disable"); for (int i=0; i& code) const { mScriptParser.getCode (code); } const Locals& FileParser::getLocals() const { return mLocals; } bool FileParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==NameState) { mName = name; mState = BeginCompleteState; return true; } if (mState==EndNameState) { // optional repeated name after end statement if (mName!=name) reportWarning ("Names for script " + mName + " do not match", loc); mState = EndCompleteState; return false; // we are stopping here, because there might be more garbage on the end line, // that we must ignore. // /// \todo allow this workaround to be disabled for newer scripts } if (mState==BeginCompleteState) { reportWarning ("Stray string (" + name + ") after begin statement", loc); return true; } return Parser::parseName (name, loc, scanner); } bool FileParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; scanner.enableTolerantNames(); /// \todo disable return true; } if (mState==NameState) { // keywords can be used as script names too. Thank you Morrowind for another // syntactic perversity :( mName = loc.mLiteral; mState = BeginCompleteState; return true; } if (mState==EndNameState) { // optional repeated name after end statement if (mName!=loc.mLiteral) reportWarning ("Names for script " + mName + " do not match", loc); mState = EndCompleteState; return false; // we are stopping here, because there might be more garbage on the end line, // that we must ignore. // /// \todo allow this workaround to be disabled for newer scripts } return Parser::parseKeyword (keyword, loc, scanner); } bool FileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { // Ignore any junk special characters if (mState == BeginState) { if (code != Scanner::S_newline) reportWarning ("Stray special character before begin statement", loc); return true; } if (code==Scanner::S_newline) { if (mState==BeginCompleteState) { // parse the script body mScriptParser.reset(); scanner.scan (mScriptParser); mState = EndNameState; return true; } if (mState==EndCompleteState || mState==EndNameState) { // we are done here -> ignore the rest of the script return false; } } return Parser::parseSpecial (code, loc, scanner); } void FileParser::parseEOF (Scanner& scanner) { if (mState!=EndNameState && mState!=EndCompleteState) Parser::parseEOF (scanner); } void FileParser::reset() { mState = BeginState; mName.clear(); mScriptParser.reset(); Parser::reset(); } } openmw-openmw-0.48.0/components/compiler/fileparser.hpp000066400000000000000000000034321445372753700233020ustar00rootroot00000000000000#ifndef COMPILER_FILEPARSER_H_INCLUDED #define COMPILER_FILEPARSER_H_INCLUDED #include "parser.hpp" #include "scriptparser.hpp" #include "locals.hpp" #include "literals.hpp" namespace Compiler { // Top-level parser, to be used for global scripts, local scripts and targeted scripts class FileParser : public Parser { enum State { BeginState, NameState, BeginCompleteState, EndNameState, EndCompleteState }; ScriptParser mScriptParser; State mState; std::string mName; Locals mLocals; public: FileParser (ErrorHandler& errorHandler, Context& context); std::string getName() const; ///< Return script name. void getCode (std::vector& code) const; ///< store generated code in \a code. const Locals& getLocals() const; ///< get local variable declarations. bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void parseEOF (Scanner& scanner) override; ///< Handle EOF token. void reset() override; ///< Reset parser to clean state. }; } #endif openmw-openmw-0.48.0/components/compiler/generator.cpp000066400000000000000000000457151445372753700231410ustar00rootroot00000000000000#include "generator.hpp" #include #include #include #include #include "literals.hpp" namespace { void opPushInt (Compiler::Generator::CodeContainer& code, int value) { code.push_back (Compiler::Generator::segment0 (0, value)); } void opFetchIntLiteral (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (4)); } void opFetchFloatLiteral (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (5)); } void opIntToFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (3)); } void opFloatToInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (6)); } void opStoreLocalShort (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (0)); } void opStoreLocalLong (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (1)); } void opStoreLocalFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (2)); } void opNegateInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (7)); } void opNegateFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (8)); } void opAddInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (9)); } void opAddFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (10)); } void opSubInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (11)); } void opSubFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (12)); } void opMulInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (13)); } void opMulFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (14)); } void opDivInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (15)); } void opDivFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (16)); } void opIntToFloat1 (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (17)); } void opReturn (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (20)); } void opMessageBox (Compiler::Generator::CodeContainer& code, int buttons) { code.push_back (Compiler::Generator::segment3 (0, buttons)); } void opReport (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (58)); } void opFetchLocalShort (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (21)); } void opFetchLocalLong (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (22)); } void opFetchLocalFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (23)); } void opJumpForward (Compiler::Generator::CodeContainer& code, int offset) { code.push_back (Compiler::Generator::segment0 (1, offset)); } void opJumpBackward (Compiler::Generator::CodeContainer& code, int offset) { code.push_back (Compiler::Generator::segment0 (2, offset)); } /* Currently unused void opSkipOnZero (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (24)); } */ void opSkipOnNonZero (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (25)); } void opEqualInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (26)); } void opNonEqualInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (27)); } void opLessThanInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (28)); } void opLessOrEqualInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (29)); } void opGreaterThanInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (30)); } void opGreaterOrEqualInt (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (31)); } void opEqualFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (32)); } void opNonEqualFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (33)); } void opLessThanFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (34)); } void opLessOrEqualFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (35)); } void opGreaterThanFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (36)); } void opGreaterOrEqualFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (37)); } void opStoreGlobalShort (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (39)); } void opStoreGlobalLong (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (40)); } void opStoreGlobalFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (41)); } void opFetchGlobalShort (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (42)); } void opFetchGlobalLong (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (43)); } void opFetchGlobalFloat (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (44)); } void opStoreMemberShort (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 65 : 59)); } void opStoreMemberLong (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 66 : 60)); } void opStoreMemberFloat (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 67 : 61)); } void opFetchMemberShort (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 68 : 62)); } void opFetchMemberLong (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 69 : 63)); } void opFetchMemberFloat (Compiler::Generator::CodeContainer& code, bool global) { code.push_back (Compiler::Generator::segment5 (global ? 70 : 64)); } } namespace Compiler::Generator { void pushInt (CodeContainer& code, Literals& literals, int value) { int index = literals.addInteger (value); opPushInt (code, index); opFetchIntLiteral (code); } void pushFloat (CodeContainer& code, Literals& literals, float value) { int index = literals.addFloat (value); opPushInt (code, index); opFetchFloatLiteral (code); } void pushString (CodeContainer& code, Literals& literals, const std::string& value) { int index = literals.addString (value); opPushInt (code, index); } void assignToLocal (CodeContainer& code, char localType, int localIndex, const CodeContainer& value, char valueType) { opPushInt (code, localIndex); std::copy (value.begin(), value.end(), std::back_inserter (code)); if (localType!=valueType) { if (localType=='f' && valueType=='l') { opIntToFloat (code); } else if ((localType=='l' || localType=='s') && valueType=='f') { opFloatToInt (code); } } switch (localType) { case 'f': opStoreLocalFloat (code); break; case 's': opStoreLocalShort (code); break; case 'l': opStoreLocalLong (code); break; default: assert (0); } } void negate (CodeContainer& code, char valueType) { switch (valueType) { case 'l': opNegateInt (code); break; case 'f': opNegateFloat (code); break; default: assert (0); } } void add (CodeContainer& code, char valueType1, char valueType2) { if (valueType1=='l' && valueType2=='l') { opAddInt (code); } else { if (valueType1=='l') opIntToFloat1 (code); if (valueType2=='l') opIntToFloat (code); opAddFloat (code); } } void sub (CodeContainer& code, char valueType1, char valueType2) { if (valueType1=='l' && valueType2=='l') { opSubInt (code); } else { if (valueType1=='l') opIntToFloat1 (code); if (valueType2=='l') opIntToFloat (code); opSubFloat (code); } } void mul (CodeContainer& code, char valueType1, char valueType2) { if (valueType1=='l' && valueType2=='l') { opMulInt (code); } else { if (valueType1=='l') opIntToFloat1 (code); if (valueType2=='l') opIntToFloat (code); opMulFloat (code); } } void div (CodeContainer& code, char valueType1, char valueType2) { if (valueType1=='l' && valueType2=='l') { opDivInt (code); } else { if (valueType1=='l') opIntToFloat1 (code); if (valueType2=='l') opIntToFloat (code); opDivFloat (code); } } void convert (CodeContainer& code, char fromType, char toType) { if (fromType!=toType) { if (fromType=='f' && toType=='l') opFloatToInt (code); else if (fromType=='l' && toType=='f') opIntToFloat (code); else throw std::logic_error ("illegal type conversion"); } } void exit (CodeContainer& code) { opReturn (code); } void message (CodeContainer& code, Literals& literals, const std::string& message, int buttons) { assert (buttons>=0); if (buttons>=256) throw std::runtime_error ("A message box can't have more than 255 buttons"); int index = literals.addString (message); opPushInt (code, index); opMessageBox (code, buttons); } void report (CodeContainer& code, Literals& literals, const std::string& message) { int index = literals.addString (message); opPushInt (code, index); opReport (code); } void fetchLocal (CodeContainer& code, char localType, int localIndex) { opPushInt (code, localIndex); switch (localType) { case 'f': opFetchLocalFloat (code); break; case 's': opFetchLocalShort (code); break; case 'l': opFetchLocalLong (code); break; default: assert (0); } } void jump (CodeContainer& code, int offset) { if (offset>0) opJumpForward (code, offset); else if (offset<0) opJumpBackward (code, -offset); else throw std::logic_error ("infinite loop"); } void jumpOnZero (CodeContainer& code, int offset) { opSkipOnNonZero (code); if (offset<0) --offset; // compensate for skip instruction jump (code, offset); } void compare (CodeContainer& code, char op, char valueType1, char valueType2) { if (valueType1=='l' && valueType2=='l') { switch (op) { case 'e': opEqualInt (code); break; case 'n': opNonEqualInt (code); break; case 'l': opLessThanInt (code); break; case 'L': opLessOrEqualInt (code); break; case 'g': opGreaterThanInt (code); break; case 'G': opGreaterOrEqualInt (code); break; default: assert (0); } } else { if (valueType1=='l') opIntToFloat1 (code); if (valueType2=='l') opIntToFloat (code); switch (op) { case 'e': opEqualFloat (code); break; case 'n': opNonEqualFloat (code); break; case 'l': opLessThanFloat (code); break; case 'L': opLessOrEqualFloat (code); break; case 'g': opGreaterThanFloat (code); break; case 'G': opGreaterOrEqualFloat (code); break; default: assert (0); } } } void assignToGlobal (CodeContainer& code, Literals& literals, char localType, const std::string& name, const CodeContainer& value, char valueType) { int index = literals.addString (name); opPushInt (code, index); std::copy (value.begin(), value.end(), std::back_inserter (code)); if (localType!=valueType) { if (localType=='f' && (valueType=='l' || valueType=='s')) { opIntToFloat (code); } else if ((localType=='l' || localType=='s') && valueType=='f') { opFloatToInt (code); } } switch (localType) { case 'f': opStoreGlobalFloat (code); break; case 's': opStoreGlobalShort (code); break; case 'l': opStoreGlobalLong (code); break; default: assert (0); } } void fetchGlobal (CodeContainer& code, Literals& literals, char localType, const std::string& name) { int index = literals.addString (name); opPushInt (code, index); switch (localType) { case 'f': opFetchGlobalFloat (code); break; case 's': opFetchGlobalShort (code); break; case 'l': opFetchGlobalLong (code); break; default: assert (0); } } void assignToMember (CodeContainer& code, Literals& literals, char localType, const std::string& name, const std::string& id, const CodeContainer& value, char valueType, bool global) { int index = literals.addString (name); opPushInt (code, index); index = literals.addString (id); opPushInt (code, index); std::copy (value.begin(), value.end(), std::back_inserter (code)); if (localType!=valueType) { if (localType=='f' && (valueType=='l' || valueType=='s')) { opIntToFloat (code); } else if ((localType=='l' || localType=='s') && valueType=='f') { opFloatToInt (code); } } switch (localType) { case 'f': opStoreMemberFloat (code, global); break; case 's': opStoreMemberShort (code, global); break; case 'l': opStoreMemberLong (code, global); break; default: assert (0); } } void fetchMember (CodeContainer& code, Literals& literals, char localType, const std::string& name, const std::string& id, bool global) { int index = literals.addString (name); opPushInt (code, index); index = literals.addString (id); opPushInt (code, index); switch (localType) { case 'f': opFetchMemberFloat (code, global); break; case 's': opFetchMemberShort (code, global); break; case 'l': opFetchMemberLong (code, global); break; default: assert (0); } } } openmw-openmw-0.48.0/components/compiler/generator.hpp000066400000000000000000000072141445372753700231360ustar00rootroot00000000000000#ifndef COMPILER_GENERATOR_H_INCLUDED #define COMPILER_GENERATOR_H_INCLUDED #include #include #include #include namespace Compiler { class Literals; namespace Generator { typedef std::vector CodeContainer; inline Interpreter::Type_Code segment0 (unsigned int c, unsigned int arg0) { assert (c<64); return (c<<24) | (arg0 & 0xffffff); } inline Interpreter::Type_Code segment1 (unsigned int c, unsigned int arg0, unsigned int arg1) { assert (c<64); return 0x40000000 | (c<<24) | ((arg0 & 0xfff)<<12) | (arg1 & 0xfff); } inline Interpreter::Type_Code segment2 (unsigned int c, unsigned int arg0) { assert (c<1024); return 0x80000000 | (c<<20) | (arg0 & 0xfffff); } inline Interpreter::Type_Code segment3 (unsigned int c, unsigned int arg0) { assert (c<262144); return 0xc0000000 | (c<<8) | (arg0 & 0xff); } inline Interpreter::Type_Code segment4 (unsigned int c, unsigned int arg0, unsigned int arg1) { assert (c<1024); return 0xc4000000 | (c<<16) | ((arg0 & 0xff)<<8) | (arg1 & 0xff); } inline Interpreter::Type_Code segment5 (unsigned int c) { assert (c<67108864); return 0xc8000000 | c; } void pushInt (CodeContainer& code, Literals& literals, int value); void pushFloat (CodeContainer& code, Literals& literals, float value); void pushString (CodeContainer& code, Literals& literals, const std::string& value); void assignToLocal (CodeContainer& code, char localType, int localIndex, const CodeContainer& value, char valueType); void negate (CodeContainer& code, char valueType); void add (CodeContainer& code, char valueType1, char valueType2); void sub (CodeContainer& code, char valueType1, char valueType2); void mul (CodeContainer& code, char valueType1, char valueType2); void div (CodeContainer& code, char valueType1, char valueType2); void convert (CodeContainer& code, char fromType, char toType); void exit (CodeContainer& code); void message (CodeContainer& code, Literals& literals, const std::string& message, int buttons); void report (CodeContainer& code, Literals& literals, const std::string& message); void fetchLocal (CodeContainer& code, char localType, int localIndex); void jump (CodeContainer& code, int offset); void jumpOnZero (CodeContainer& code, int offset); void compare (CodeContainer& code, char op, char valueType1, char valueType2); void assignToGlobal (CodeContainer& code, Literals& literals, char localType, const std::string& name, const CodeContainer& value, char valueType); void fetchGlobal (CodeContainer& code, Literals& literals, char localType, const std::string& name); void assignToMember (CodeContainer& code, Literals& literals, char memberType, const std::string& name, const std::string& id, const CodeContainer& value, char valueType, bool global); ///< \param global Member of a global script instead of a script of a reference. void fetchMember (CodeContainer& code, Literals& literals, char memberType, const std::string& name, const std::string& id, bool global); ///< \param global Member of a global script instead of a script of a reference. } } #endif openmw-openmw-0.48.0/components/compiler/junkparser.cpp000066400000000000000000000022641445372753700233270ustar00rootroot00000000000000#include "junkparser.hpp" #include "scanner.hpp" Compiler::JunkParser::JunkParser (ErrorHandler& errorHandler, const Context& context, int ignoreKeyword) : Parser (errorHandler, context), mIgnoreKeyword (ignoreKeyword) {} bool Compiler::JunkParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { scanner.putbackInt (value, loc); return false; } bool Compiler::JunkParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { scanner.putbackFloat (value, loc); return false; } bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { scanner.putbackName (name, loc); return false; } bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==mIgnoreKeyword) reportWarning ("Ignoring found junk", loc); else scanner.putbackKeyword (keyword, loc); return false; } bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_member) reportWarning ("Ignoring found junk", loc); else scanner.putbackSpecial (code, loc); return false; } openmw-openmw-0.48.0/components/compiler/junkparser.hpp000066400000000000000000000024451445372753700233350ustar00rootroot00000000000000#ifndef COMPILER_JUNKPARSER_H_INCLUDED #define COMPILER_JUNKPARSER_H_INCLUDED #include "parser.hpp" namespace Compiler { /// \brief Parse an optional single junk token class JunkParser : public Parser { int mIgnoreKeyword; public: JunkParser (ErrorHandler& errorHandler, const Context& context, int ignoreKeyword = -1); bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? }; } #endif openmw-openmw-0.48.0/components/compiler/lineparser.cpp000066400000000000000000000362631445372753700233150ustar00rootroot00000000000000#include "lineparser.hpp" #include #include #include "scanner.hpp" #include "context.hpp" #include "errorhandler.hpp" #include "skipparser.hpp" #include "locals.hpp" #include "generator.hpp" #include "extensions.hpp" #include "declarationparser.hpp" namespace Compiler { void LineParser::parseExpression (Scanner& scanner, const TokenLoc& loc) { mExprParser.reset(); if (!mExplicit.empty()) { mExprParser.parseName (mExplicit, loc, scanner); if (mState==MemberState) mExprParser.parseSpecial (Scanner::S_member, loc, scanner); else mExprParser.parseSpecial (Scanner::S_ref, loc, scanner); } scanner.scan (mExprParser); char type = mExprParser.append (mCode); mState = EndState; switch (type) { case 'l': Generator::report (mCode, mLiterals, "%d"); break; case 'f': Generator::report (mCode, mLiterals, "%f"); break; default: throw std::runtime_error ("Unknown expression result type"); } } LineParser::LineParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, std::vector& code, bool allowExpression) : Parser (errorHandler, context), mLocals (locals), mLiterals (literals), mCode (code), mState (BeginState), mReferenceMember(false), mButtons(0), mType(0), mExprParser (errorHandler, context, locals, literals), mAllowExpression (allowExpression) {} bool LineParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { if (mAllowExpression && mState==BeginState) { scanner.putbackInt (value, loc); parseExpression (scanner, loc); return true; } else if (mState == SetState) { // Allow ints to be used as variable names return parseName(loc.mLiteral, loc, scanner); } return Parser::parseInt (value, loc, scanner); } bool LineParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { if (mAllowExpression && mState==BeginState) { scanner.putbackFloat (value, loc); parseExpression (scanner, loc); return true; } return Parser::parseFloat (value, loc, scanner); } bool LineParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (mState==SetState) { std::string name2 = Misc::StringUtils::lowerCase (name); mName = name2; // local variable? char type = mLocals.getType (name2); if (type!=' ') { mType = type; mState = SetLocalVarState; return true; } type = getContext().getGlobalType (name2); if (type!=' ') { mType = type; mState = SetGlobalVarState; return true; } mState = SetPotentialMemberVarState; return true; } if (mState==SetMemberVarState) { mMemberName = Misc::StringUtils::lowerCase (name); std::pair type = getContext().getMemberType (mMemberName, mName); if (type.first!=' ') { mState = SetMemberVarState2; mType = type.first; mReferenceMember = type.second; return true; } getErrorHandler().error ("Unknown variable", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; } if (mState==MessageState) { GetArgumentsFromMessageFormat processor; processor.process(name); std::string arguments = processor.getArguments(); if (!arguments.empty()) { mExprParser.reset(); mExprParser.parseArguments (arguments, scanner, mCode, -1, true); } mName = name; mButtons = 0; mState = MessageButtonState; return true; } if (mState==MessageButtonState) { Generator::pushString (mCode, mLiterals, name); mState = MessageButtonState; ++mButtons; return true; } if (mState==BeginState && getContext().isId (name)) { mState = PotentialExplicitState; mExplicit = Misc::StringUtils::lowerCase (name); return true; } if (mState==BeginState && mAllowExpression) { std::string name2 = Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); if (type!=' ') { scanner.putbackName (name, loc); parseExpression (scanner, loc); return true; } type = getContext().getGlobalType (name2); if (type!=' ') { scanner.putbackName (name, loc); parseExpression (scanner, loc); return true; } } return Parser::parseName (name, loc, scanner); } bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (mState==MessageState) { if (const Extensions *extensions = getContext().getExtensions()) { std::string argumentType; // ignored bool hasExplicit = false; // ignored if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); return parseName (name, loc, scanner); } } } if (mState==SetMemberVarState) { mMemberName = loc.mLiteral; std::pair type = getContext().getMemberType (mMemberName, mName); if (type.first!=' ') { mState = SetMemberVarState2; mType = type.first; mReferenceMember = type.second; return true; } } if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to) { getErrorHandler().warning ("Unknown variable", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; } if (mState==SetState) { // allow keywords to be used as variable names when assigning a value to a variable. return parseName (loc.mLiteral, loc, scanner); } if (mState==BeginState || mState==ExplicitState) { // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) { std::string argumentType; bool hasExplicit = mState==ExplicitState; if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { if (!hasExplicit && mState==ExplicitState) { getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } std::vector code; int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); mCode.insert (mCode.end(), code.begin(), code.end()); extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); mState = EndState; return true; } } if (const Extensions *extensions = getContext().getExtensions()) { char returnType; std::string argumentType; bool hasExplicit = mState==ExplicitState; if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) { if (!hasExplicit && mState==ExplicitState) { getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } if (mAllowExpression) { scanner.putbackKeyword (keyword, loc); parseExpression (scanner, loc); } else { std::vector code; int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); mCode.insert(mCode.end(), code.begin(), code.end()); extensions->generateFunctionCode (keyword, mCode, mLiterals, mExplicit, optionals); } mState = EndState; return true; } } } if (mState==ExplicitState) { // drop stray explicit reference getErrorHandler().warning ("Stray explicit reference", loc); mState = BeginState; mExplicit.clear(); } if (mState==BeginState) { switch (keyword) { case Scanner::K_short: case Scanner::K_long: case Scanner::K_float: { if (!getContext().canDeclareLocals()) { getErrorHandler().error("Local variables cannot be declared in this context", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return true; } DeclarationParser declaration (getErrorHandler(), getContext(), mLocals); if (declaration.parseKeyword (keyword, loc, scanner)) scanner.scan (declaration); return false; } case Scanner::K_set: mState = SetState; return true; case Scanner::K_messagebox: mState = MessageState; scanner.enableStrictKeywords(); return true; case Scanner::K_return: Generator::exit (mCode); mState = EndState; return true; case Scanner::K_else: getErrorHandler().warning ("Stray else", loc); mState = EndState; return true; case Scanner::K_endif: getErrorHandler().warning ("Stray endif", loc); mState = EndState; return true; case Scanner::K_begin: getErrorHandler().warning ("Stray begin", loc); mState = EndState; return true; } } else if (mState==SetLocalVarState && keyword==Scanner::K_to) { mExprParser.reset(); scanner.scan (mExprParser); std::vector code; char type = mExprParser.append (code); Generator::assignToLocal (mCode, mLocals.getType (mName), mLocals.getIndex (mName), code, type); mState = EndState; return true; } else if (mState==SetGlobalVarState && keyword==Scanner::K_to) { mExprParser.reset(); scanner.scan (mExprParser); std::vector code; char type = mExprParser.append (code); Generator::assignToGlobal (mCode, mLiterals, mType, mName, code, type); mState = EndState; return true; } else if (mState==SetMemberVarState2 && keyword==Scanner::K_to) { mExprParser.reset(); scanner.scan (mExprParser); std::vector code; char type = mExprParser.append (code); Generator::assignToMember (mCode, mLiterals, mType, mMemberName, mName, code, type, !mReferenceMember); mState = EndState; return true; } return Parser::parseKeyword (keyword, loc, scanner); } bool LineParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (mState==EndState && code==Scanner::S_open) { getErrorHandler().warning ("Stray '[' or '(' at the end of the line", loc); return true; } if (code==Scanner::S_newline && (mState==EndState || mState==BeginState)) return false; if (code==Scanner::S_ref && mState==SetPotentialMemberVarState) { getErrorHandler().warning ("Stray explicit reference", loc); mState = SetState; return true; } if (code==Scanner::S_ref && mState==PotentialExplicitState) { mState = ExplicitState; return true; } if (code==Scanner::S_member && mState==PotentialExplicitState && mAllowExpression) { mState = MemberState; parseExpression (scanner, loc); mState = EndState; return true; } if (code==Scanner::S_newline && mState==MessageButtonState) { Generator::message (mCode, mLiterals, mName, mButtons); return false; } if (code==Scanner::S_member && mState==SetPotentialMemberVarState) { mState = SetMemberVarState; return true; } if (mAllowExpression && mState==BeginState && (code==Scanner::S_open || code==Scanner::S_minus || code==Scanner::S_plus)) { scanner.putbackSpecial (code, loc); parseExpression (scanner, loc); mState = EndState; return true; } return Parser::parseSpecial (code, loc, scanner); } void LineParser::reset() { mState = BeginState; mName.clear(); mExplicit.clear(); } void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/, Notation /*notation*/) { switch (placeholder) { case StringPlaceholder: mArguments += 'S'; break; case IntegerPlaceholder: mArguments += 'l'; break; case FloatPlaceholder: mArguments += 'f'; break; default: break; } } } openmw-openmw-0.48.0/components/compiler/lineparser.hpp000066400000000000000000000063441445372753700233170ustar00rootroot00000000000000#ifndef COMPILER_LINEPARSER_H_INCLUDED #define COMPILER_LINEPARSER_H_INCLUDED #include #include #include #include #include "parser.hpp" #include "exprparser.hpp" namespace Compiler { class Locals; class Literals; /// \brief Line parser, to be used in console scripts and as part of ScriptParser class LineParser : public Parser { enum State { BeginState, SetState, SetLocalVarState, SetGlobalVarState, SetPotentialMemberVarState, SetMemberVarState, SetMemberVarState2, MessageState, MessageButtonState, EndState, PotentialExplicitState, ExplicitState, MemberState }; Locals& mLocals; Literals& mLiterals; std::vector& mCode; State mState; std::string mName; std::string mMemberName; bool mReferenceMember; int mButtons; std::string mExplicit; char mType; ExprParser mExprParser; bool mAllowExpression; void parseExpression (Scanner& scanner, const TokenLoc& loc); public: LineParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, std::vector& code, bool allowExpression = false); ///< \param allowExpression Allow lines consisting of a naked expression /// (result is send to the messagebox interface) bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void reset() override; ///< Reset parser to clean state. }; class GetArgumentsFromMessageFormat : public ::Misc::MessageFormatParser { private: std::string mArguments; protected: void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) override; void visitedCharacter(char c) override {} public: void process(std::string_view message) override { mArguments.clear(); ::Misc::MessageFormatParser::process(message); } std::string getArguments() const { return mArguments; } }; } #endif openmw-openmw-0.48.0/components/compiler/literals.cpp000066400000000000000000000044521445372753700227630ustar00rootroot00000000000000#include "literals.hpp" #include namespace Compiler { int Literals::getIntegerSize() const { return static_cast(mIntegers.size() * sizeof (Interpreter::Type_Integer)); } int Literals::getFloatSize() const { return static_cast(mFloats.size() * sizeof (Interpreter::Type_Float)); } int Literals::getStringSize() const { int size = 0; for (std::vector::const_iterator iter (mStrings.begin()); iter!=mStrings.end(); ++iter) size += static_cast (iter->size()) + 1; if (size % 4) // padding size += 4 - size % 4; return size; } void Literals::append (std::vector& code) const { for (const int & mInteger : mIntegers) code.push_back (*reinterpret_cast (&mInteger)); for (const float & mFloat : mFloats) code.push_back (*reinterpret_cast (&mFloat)); int stringBlockSize = getStringSize(); int size = static_cast (code.size()); code.resize (size+stringBlockSize/4); size_t offset = 0; for (const auto & mString : mStrings) { size_t stringSize = mString.size()+1; std::copy (mString.c_str(), mString.c_str()+stringSize, reinterpret_cast (&code[size]) + offset); offset += stringSize; } } int Literals::addInteger (Interpreter::Type_Integer value) { int index = static_cast (mIntegers.size()); mIntegers.push_back (value); return index; } int Literals::addFloat (Interpreter::Type_Float value) { int index = static_cast (mFloats.size()); mFloats.push_back (value); return index; } int Literals::addString (const std::string& value) { int index = static_cast (mStrings.size()); mStrings.push_back (value); return index; } void Literals::clear() { mIntegers.clear(); mFloats.clear(); mStrings.clear(); } } openmw-openmw-0.48.0/components/compiler/literals.hpp000066400000000000000000000025701445372753700227670ustar00rootroot00000000000000#ifndef COMPILER_LITERALS_H_INCLUDED #define COMPILER_LITERALS_H_INCLUDED #include #include #include namespace Compiler { /// \brief Literal values. class Literals { std::vector mIntegers; std::vector mFloats; std::vector mStrings; public: int getIntegerSize() const; ///< Return size of integer block (in bytes). int getFloatSize() const; ///< Return size of float block (in bytes). int getStringSize() const; ///< Return size of string block (in bytes). void append (std::vector& code) const; ///< Apepnd literal blocks to code. /// \note code blocks will be padded for 32-bit alignment. int addInteger (Interpreter::Type_Integer value); ///< add integer liternal and return index. int addFloat (Interpreter::Type_Float value); ///< add float literal and return value. int addString (const std::string& value); ///< add string literal and return value. void clear(); ///< remove all literals. }; } #endif openmw-openmw-0.48.0/components/compiler/locals.cpp000066400000000000000000000050471445372753700224220ustar00rootroot00000000000000#include "locals.hpp" #include #include #include #include #include namespace Compiler { const std::vector& Locals::get (char type) const { switch (type) { case 's': return mShorts; case 'l': return mLongs; case 'f': return mFloats; } throw std::logic_error ("Unknown variable type"); } int Locals::searchIndex (char type, std::string_view name) const { const std::vector& collection = get (type); auto iter = std::find (collection.begin(), collection.end(), name); if (iter==collection.end()) return -1; return static_cast(iter-collection.begin()); } bool Locals::search (char type, std::string_view name) const { return searchIndex (type, name)!=-1; } std::vector& Locals::get (char type) { switch (type) { case 's': return mShorts; case 'l': return mLongs; case 'f': return mFloats; } throw std::logic_error ("Unknown variable type"); } char Locals::getType (std::string_view name) const { if (search ('s', name)) return 's'; if (search ('l', name)) return 'l'; if (search ('f', name)) return 'f'; return ' '; } int Locals::getIndex (std::string_view name) const { int index = searchIndex ('s', name); if (index!=-1) return index; index = searchIndex ('l', name); if (index!=-1) return index; return searchIndex ('f', name); } void Locals::write (std::ostream& localFile) const { localFile << get ('s').size() << ' ' << get ('l').size() << ' ' << get ('f').size() << std::endl; std::copy (get ('s').begin(), get ('s').end(), std::ostream_iterator (localFile, " ")); std::copy (get ('l').begin(), get ('l').end(), std::ostream_iterator (localFile, " ")); std::copy (get ('f').begin(), get ('f').end(), std::ostream_iterator (localFile, " ")); } void Locals::declare (char type, std::string_view name) { get (type).push_back (Misc::StringUtils::lowerCase (name)); } void Locals::clear() { get ('s').clear(); get ('l').clear(); get ('f').clear(); } } openmw-openmw-0.48.0/components/compiler/locals.hpp000066400000000000000000000024551445372753700224270ustar00rootroot00000000000000#ifndef COMPILER_LOCALS_H_INCLUDED #define COMPILER_LOCALS_H_INCLUDED #include #include #include #include namespace Compiler { /// \brief Local variable declarations class Locals { std::vector mShorts; std::vector mLongs; std::vector mFloats; std::vector& get (char type); public: char getType (std::string_view name) const; ///< 's': short, 'l': long, 'f': float, ' ': does not exist. int getIndex (std::string_view name) const; ///< return index for local variable \a name (-1: does not exist). bool search (char type, std::string_view name) const; /// Return index for local variable \a name of type \a type (-1: variable does not /// exit). int searchIndex (char type, std::string_view name) const; const std::vector& get (char type) const; void write (std::ostream& localFile) const; ///< write declarations to file. void declare (char type, std::string_view name); ///< declares a variable. void clear(); ///< remove all declarations. }; } #endif openmw-openmw-0.48.0/components/compiler/nullerrorhandler.cpp000066400000000000000000000003351445372753700245220ustar00rootroot00000000000000#include "nullerrorhandler.hpp" void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {} void Compiler::NullErrorHandler::report (const std::string& message, Type type) {} openmw-openmw-0.48.0/components/compiler/nullerrorhandler.hpp000066400000000000000000000010371445372753700245270ustar00rootroot00000000000000#ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED #define COMPILER_NULLERRORHANDLER_H_INCLUDED #include "errorhandler.hpp" namespace Compiler { /// \brief Error handler implementation: Ignore all error messages class NullErrorHandler : public ErrorHandler { void report (const std::string& message, const TokenLoc& loc, Type type) override; ///< Report error to the user. void report (const std::string& message, Type type) override; ///< Report a file related error }; } #endif openmw-openmw-0.48.0/components/compiler/opcodes.cpp000066400000000000000000000004241445372753700225730ustar00rootroot00000000000000#include "opcodes.hpp" namespace Compiler::Control { const char *controls[numberOfControls] = { "playercontrols", "playerfighting", "playerjumping", "playerlooking", "playermagic", "playerviewswitch", "vanitymode" }; } openmw-openmw-0.48.0/components/compiler/opcodes.hpp000066400000000000000000000573561445372753700226200ustar00rootroot00000000000000#ifndef COMPILER_OPCODES_H #define COMPILER_OPCODES_H namespace Compiler { namespace Ai { const int opcodeAiTravel = 0x20000; const int opcodeAiTravelExplicit = 0x20001; const int opcodeAiEscort = 0x20002; const int opcodeAiEscortExplicit = 0x20003; const int opcodeGetAiPackageDone = 0x200007c; const int opcodeGetAiPackageDoneExplicit = 0x200007d; const int opcodeGetCurrentAiPackage = 0x20001ef; const int opcodeGetCurrentAiPackageExplicit = 0x20001f0; const int opcodeGetDetected = 0x20001f1; const int opcodeGetDetectedExplicit = 0x20001f2; const int opcodeAiWander = 0x20010; const int opcodeAiWanderExplicit = 0x20011; const int opcodeAIActivate = 0x2001e; const int opcodeAIActivateExplicit = 0x2001f; const int opcodeAiEscortCell = 0x20020; const int opcodeAiEscortCellExplicit = 0x20021; const int opcodeAiFollow = 0x20022; const int opcodeAiFollowExplicit = 0x20023; const int opcodeAiFollowCell = 0x20024; const int opcodeAiFollowCellExplicit = 0x20025; const int opcodeSetHello = 0x200015c; const int opcodeSetHelloExplicit = 0x200015d; const int opcodeSetFight = 0x200015e; const int opcodeSetFightExplicit = 0x200015f; const int opcodeSetFlee = 0x2000160; const int opcodeSetFleeExplicit = 0x2000161; const int opcodeSetAlarm = 0x2000162; const int opcodeSetAlarmExplicit = 0x2000163; const int opcodeModHello = 0x20001b7; const int opcodeModHelloExplicit = 0x20001b8; const int opcodeModFight = 0x20001b9; const int opcodeModFightExplicit = 0x20001ba; const int opcodeModFlee = 0x20001bb; const int opcodeModFleeExplicit = 0x20001bc; const int opcodeModAlarm = 0x20001bd; const int opcodeModAlarmExplicit = 0x20001be; const int opcodeGetHello = 0x20001bf; const int opcodeGetHelloExplicit = 0x20001c0; const int opcodeGetFight = 0x20001c1; const int opcodeGetFightExplicit = 0x20001c2; const int opcodeGetFlee = 0x20001c3; const int opcodeGetFleeExplicit = 0x20001c4; const int opcodeGetAlarm = 0x20001c5; const int opcodeGetAlarmExplicit = 0x20001c6; const int opcodeGetLineOfSight = 0x2000222; const int opcodeGetLineOfSightExplicit = 0x2000223; const int opcodeToggleAI = 0x2000224; const int opcodeGetTarget = 0x2000238; const int opcodeGetTargetExplicit = 0x2000239; const int opcodeStartCombat = 0x200023a; const int opcodeStartCombatExplicit = 0x200023b; const int opcodeStopCombat = 0x200023c; const int opcodeStopCombatExplicit = 0x200023d; const int opcodeFace = 0x200024c; const int opcodeFaceExplicit = 0x200024d; } namespace Animation { const int opcodeSkipAnim = 0x2000138; const int opcodeSkipAnimExplicit = 0x2000139; const int opcodePlayAnim = 0x20006; const int opcodePlayAnimExplicit = 0x20007; const int opcodeLoopAnim = 0x20008; const int opcodeLoopAnimExplicit = 0x20009; } namespace Cell { const int opcodeCellChanged = 0x2000000; const int opcodeTestCells = 0x200030e; const int opcodeTestInteriorCells = 0x200030f; const int opcodeCOC = 0x2000026; const int opcodeCOE = 0x2000226; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; const int opcodeGetWaterLevel = 0x2000141; const int opcodeSetWaterLevel = 0x2000142; const int opcodeModWaterLevel = 0x2000143; } namespace Console { } namespace Container { const int opcodeAddItem = 0x2000076; const int opcodeAddItemExplicit = 0x2000077; const int opcodeGetItemCount = 0x2000078; const int opcodeGetItemCountExplicit = 0x2000079; const int opcodeRemoveItem = 0x200007a; const int opcodeRemoveItemExplicit = 0x200007b; const int opcodeEquip = 0x20001b3; const int opcodeEquipExplicit = 0x20001b4; const int opcodeGetArmorType = 0x20001d1; const int opcodeGetArmorTypeExplicit = 0x20001d2; const int opcodeHasItemEquipped = 0x20001d5; const int opcodeHasItemEquippedExplicit = 0x20001d6; const int opcodeHasSoulGem = 0x20001de; const int opcodeHasSoulGemExplicit = 0x20001df; const int opcodeGetWeaponType = 0x20001e0; const int opcodeGetWeaponTypeExplicit = 0x20001e1; } namespace Control { const int numberOfControls = 7; extern const char *controls[numberOfControls]; const int opcodeEnable = 0x200007e; const int opcodeDisable = 0x2000085; const int opcodeToggleCollision = 0x2000130; const int opcodeClearForceRun = 0x2000154; const int opcodeClearForceRunExplicit = 0x2000155; const int opcodeForceRun = 0x2000156; const int opcodeForceRunExplicit = 0x2000157; const int opcodeClearForceJump = 0x2000258; const int opcodeClearForceJumpExplicit = 0x2000259; const int opcodeForceJump = 0x200025a; const int opcodeForceJumpExplicit = 0x200025b; const int opcodeClearForceMoveJump = 0x200025c; const int opcodeClearForceMoveJumpExplicit = 0x200025d; const int opcodeForceMoveJump = 0x200025e; const int opcodeForceMoveJumpExplicit = 0x200025f; const int opcodeClearForceSneak = 0x2000158; const int opcodeClearForceSneakExplicit = 0x2000159; const int opcodeForceSneak = 0x200015a; const int opcodeForceSneakExplicit = 0x200015b; const int opcodeGetDisabled = 0x2000175; const int opcodeGetPcRunning = 0x20001c9; const int opcodeGetPcSneaking = 0x20001ca; const int opcodeGetForceRun = 0x20001cb; const int opcodeGetForceSneak = 0x20001cc; const int opcodeGetForceRunExplicit = 0x20001cd; const int opcodeGetForceSneakExplicit = 0x20001ce; const int opcodeGetForceJump = 0x2000260; const int opcodeGetForceMoveJump = 0x2000262; const int opcodeGetForceJumpExplicit = 0x2000261; const int opcodeGetForceMoveJumpExplicit = 0x2000263; } namespace Dialogue { const int opcodeJournal = 0x2000133; const int opcodeJournalExplicit = 0x200030b; const int opcodeSetJournalIndex = 0x2000134; const int opcodeGetJournalIndex = 0x2000135; const int opcodeAddTopic = 0x200013a; const int opcodeChoice = 0x2000a; const int opcodeForceGreeting = 0x200014f; const int opcodeForceGreetingExplicit = 0x2000150; const int opcodeGoodbye = 0x2000152; const int opcodeSetReputation = 0x20001ad; const int opcodeModReputation = 0x20001ae; const int opcodeSetReputationExplicit = 0x20001af; const int opcodeModReputationExplicit = 0x20001b0; const int opcodeGetReputation = 0x20001b1; const int opcodeGetReputationExplicit = 0x20001b2; const int opcodeSameFaction = 0x20001b5; const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; const int opcodeSetFactionReaction = 0x20002ff; const int opcodeGetFactionReaction = 0x2000243; const int opcodeClearInfoActor = 0x2000245; const int opcodeClearInfoActorExplicit = 0x2000246; } namespace Gui { const int opcodeEnableBirthMenu = 0x200000e; const int opcodeEnableClassMenu = 0x200000f; const int opcodeEnableNameMenu = 0x2000010; const int opcodeEnableRaceMenu = 0x2000011; const int opcodeEnableStatsReviewMenu = 0x2000012; const int opcodeEnableInventoryMenu = 0x2000013; const int opcodeEnableMagicMenu = 0x2000014; const int opcodeEnableMapMenu = 0x2000015; const int opcodeEnableStatsMenu = 0x2000016; const int opcodeEnableRest = 0x2000017; const int opcodeEnableLevelupMenu = 0x2000300; const int opcodeShowRestMenu = 0x2000018; const int opcodeShowRestMenuExplicit = 0x2000234; const int opcodeGetButtonPressed = 0x2000137; const int opcodeToggleFogOfWar = 0x2000145; const int opcodeToggleFullHelp = 0x2000151; const int opcodeShowMap = 0x20001a0; const int opcodeFillMap = 0x20001a1; const int opcodeMenuTest = 0x2002c; const int opcodeToggleMenus = 0x200024b; } namespace Misc { const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeOnActivateExplicit = 0x2000306; const int opcodeActivate = 0x2000075; const int opcodeActivateExplicit = 0x2000244; const int opcodeLock = 0x20004; const int opcodeLockExplicit = 0x20005; const int opcodeUnlock = 0x200008c; const int opcodeUnlockExplicit = 0x200008d; const int opcodeToggleCollisionDebug = 0x2000132; const int opcodeToggleCollisionBoxes = 0x20001ac; const int opcodeToggleWireframe = 0x200013b; const int opcodeFadeIn = 0x200013c; const int opcodeFadeOut = 0x200013d; const int opcodeFadeTo = 0x200013e; const int opcodeToggleWater = 0x2000144; const int opcodeToggleWorld = 0x20002f5; const int opcodeTogglePathgrid = 0x2000146; const int opcodeDontSaveObject = 0x2000153; const int opcodePcForce1stPerson = 0x20002f6; const int opcodePcForce3rdPerson = 0x20002f7; const int opcodePcGet3rdPerson = 0x20002f8; const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; const int opcodeGetPcJumping = 0x2000233; const int opcodeWakeUpPc = 0x20001a2; const int opcodeGetLocked = 0x20001c7; const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; const int opcodeBetaComment = 0x2002d; const int opcodeBetaCommentExplicit = 0x2002e; const int opcodeAddSoulGem = 0x20001f3; const int opcodeAddSoulGemExplicit = 0x20001f4; const int opcodeRemoveSoulGem = 0x20027; const int opcodeRemoveSoulGemExplicit = 0x20028; const int opcodeDrop = 0x20001f8; const int opcodeDropExplicit = 0x20001f9; const int opcodeDropSoulGem = 0x20001fa; const int opcodeDropSoulGemExplicit = 0x20001fb; const int opcodeGetAttacked = 0x20001d3; const int opcodeGetAttackedExplicit = 0x20001d4; const int opcodeGetWeaponDrawn = 0x20001d7; const int opcodeGetWeaponDrawnExplicit = 0x20001d8; const int opcodeGetSpellReadied = 0x2000231; const int opcodeGetSpellReadiedExplicit = 0x2000232; const int opcodeGetSpellEffects = 0x20001db; const int opcodeGetSpellEffectsExplicit = 0x20001dc; const int opcodeGetCurrentTime = 0x20001dd; const int opcodeSetDelete = 0x20001e5; const int opcodeSetDeleteExplicit = 0x20001e6; const int opcodeGetSquareRoot = 0x20001e7; const int opcodeFall = 0x200020a; const int opcodeFallExplicit = 0x200020b; const int opcodeGetStandingPc = 0x200020c; const int opcodeGetStandingPcExplicit = 0x200020d; const int opcodeGetStandingActor = 0x200020e; const int opcodeGetStandingActorExplicit = 0x200020f; const int opcodeGetCollidingPc = 0x2000250; const int opcodeGetCollidingPcExplicit = 0x2000251; const int opcodeGetCollidingActor = 0x2000252; const int opcodeGetCollidingActorExplicit = 0x2000253; const int opcodeHurtStandingActor = 0x2000254; const int opcodeHurtStandingActorExplicit = 0x2000255; const int opcodeHurtCollidingActor = 0x2000256; const int opcodeHurtCollidingActorExplicit = 0x2000257; const int opcodeGetWindSpeed = 0x2000212; const int opcodePlayBink = 0x20001f7; const int opcodeGoToJail = 0x2000235; const int opcodePayFine = 0x2000236; const int opcodePayFineThief = 0x2000237; const int opcodeHitOnMe = 0x2000213; const int opcodeHitOnMeExplicit = 0x2000214; const int opcodeHitAttemptOnMe = 0x20002f9; const int opcodeHitAttemptOnMeExplicit = 0x20002fa; const int opcodeDisableTeleporting = 0x2000215; const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; const int opcodeShow = 0x2000304; const int opcodeShowExplicit = 0x2000305; const int opcodeToggleGodMode = 0x200021f; const int opcodeToggleScripts = 0x2000301; const int opcodeDisableLevitation = 0x2000220; const int opcodeEnableLevitation = 0x2000221; const int opcodeCast = 0x2000227; const int opcodeCastExplicit = 0x2000228; const int opcodeExplodeSpell = 0x2000229; const int opcodeExplodeSpellExplicit = 0x200022a; const int opcodeGetPcInJail = 0x200023e; const int opcodeGetPcTraveling = 0x200023f; const int opcodeAddToLevCreature = 0x20002fb; const int opcodeRemoveFromLevCreature = 0x20002fc; const int opcodeAddToLevItem = 0x20002fd; const int opcodeRemoveFromLevItem = 0x20002fe; const int opcodeShowSceneGraph = 0x2002f; const int opcodeShowSceneGraphExplicit = 0x20030; const int opcodeToggleBorders = 0x2000307; const int opcodeToggleNavMesh = 0x2000308; const int opcodeToggleActorsPaths = 0x2000309; const int opcodeSetNavMeshNumberToRender = 0x200030a; const int opcodeRepairedOnMe = 0x200030c; const int opcodeRepairedOnMeExplicit = 0x200030d; const int opcodeToggleRecastMesh = 0x2000310; const int opcodeMenuMode = 0x2000311; const int opcodeRandom = 0x2000312; const int opcodeScriptRunning = 0x2000313; const int opcodeStartScript = 0x2000314; const int opcodeStopScript = 0x2000315; const int opcodeGetSecondsPassed = 0x2000316; const int opcodeEnable = 0x2000317; const int opcodeDisable = 0x2000318; const int opcodeGetDisabled = 0x2000319; const int opcodeEnableExplicit = 0x200031a; const int opcodeDisableExplicit = 0x200031b; const int opcodeGetDisabledExplicit = 0x200031c; const int opcodeStartScriptExplicit = 0x200031d; const int opcodeHelp = 0x2000320; const int opcodeReloadLua = 0x2000321; } namespace Sky { const int opcodeToggleSky = 0x2000021; const int opcodeTurnMoonWhite = 0x2000022; const int opcodeTurnMoonRed = 0x2000023; const int opcodeGetMasserPhase = 0x2000024; const int opcodeGetSecundaPhase = 0x2000025; const int opcodeGetCurrentWeather = 0x200013f; const int opcodeChangeWeather = 0x2000140; const int opcodeModRegion = 0x20026; } namespace Sound { const int opcodeSay = 0x2000001; const int opcodeSayDone = 0x2000002; const int opcodeStreamMusic = 0x2000003; const int opcodePlaySound = 0x2000004; const int opcodePlaySoundVP = 0x2000005; const int opcodePlaySound3D = 0x2000006; const int opcodePlaySound3DVP = 0x2000007; const int opcodePlayLoopSound3D = 0x2000008; const int opcodePlayLoopSound3DVP = 0x2000009; const int opcodeStopSound = 0x200000a; const int opcodeGetSoundPlaying = 0x200000b; const int opcodeSayExplicit = 0x2000019; const int opcodeSayDoneExplicit = 0x200001a; const int opcodePlaySound3DExplicit = 0x200001b; const int opcodePlaySound3DVPExplicit = 0x200001c; const int opcodePlayLoopSound3DExplicit = 0x200001d; const int opcodePlayLoopSound3DVPExplicit = 0x200001e; const int opcodeStopSoundExplicit = 0x200001f; const int opcodeGetSoundPlayingExplicit = 0x2000020; } namespace Stats { const int numberOfAttributes = 8; const int numberOfDynamics = 3; const int numberOfSkills = 27; const int numberOfMagicEffects = 24; const int opcodeGetAttribute = 0x2000027; const int opcodeGetAttributeExplicit = 0x200002f; const int opcodeSetAttribute = 0x2000037; const int opcodeSetAttributeExplicit = 0x200003f; const int opcodeModAttribute = 0x2000047; const int opcodeModAttributeExplicit = 0x200004f; const int opcodeGetDynamic = 0x2000057; const int opcodeGetDynamicExplicit = 0x200005a; const int opcodeSetDynamic = 0x200005d; const int opcodeSetDynamicExplicit = 0x2000060; const int opcodeModDynamic = 0x2000063; const int opcodeModDynamicExplicit = 0x2000066; const int opcodeModCurrentDynamic = 0x2000069; const int opcodeModCurrentDynamicExplicit = 0x200006c; const int opcodeGetDynamicGetRatio = 0x200006f; const int opcodeGetDynamicGetRatioExplicit = 0x2000072; const int opcodeGetSkill = 0x200008e; const int opcodeGetSkillExplicit = 0x20000a9; const int opcodeSetSkill = 0x20000c4; const int opcodeSetSkillExplicit = 0x20000df; const int opcodeModSkill = 0x20000fa; const int opcodeModSkillExplicit = 0x2000115; const int opcodeGetMagicEffect = 0x2000264; const int opcodeGetMagicEffectExplicit = 0x200027c; const int opcodeSetMagicEffect = 0x2000294; const int opcodeSetMagicEffectExplicit = 0x20002ac; const int opcodeModMagicEffect = 0x20002c4; const int opcodeModMagicEffectExplicit = 0x20002dc; const int opcodeGetPCCrimeLevel = 0x20001ec; const int opcodeSetPCCrimeLevel = 0x20001ed; const int opcodeModPCCrimeLevel = 0x20001ee; const int opcodeAddSpell = 0x2000147; const int opcodeAddSpellExplicit = 0x2000148; const int opcodeRemoveSpell = 0x2000149; const int opcodeRemoveSpellExplicit = 0x200014a; const int opcodeGetSpell = 0x200014b; const int opcodeGetSpellExplicit = 0x200014c; const int opcodePCRaiseRank = 0x2000b; const int opcodePCLowerRank = 0x2000c; const int opcodePCJoinFaction = 0x2000d; const int opcodePCRaiseRankExplicit = 0x20029; const int opcodePCLowerRankExplicit = 0x2002a; const int opcodePCJoinFactionExplicit = 0x2002b; const int opcodeGetPCRank = 0x2000e; const int opcodeGetPCRankExplicit = 0x2000f; const int opcodeModDisposition = 0x200014d; const int opcodeModDispositionExplicit = 0x200014e; const int opcodeSetDisposition = 0x20001a4; const int opcodeSetDispositionExplicit = 0x20001a5; const int opcodeGetDisposition = 0x20001a6; const int opcodeGetDispositionExplicit = 0x20001a7; const int opcodeGetLevel = 0x200018c; const int opcodeGetLevelExplicit = 0x200018d; const int opcodeSetLevel = 0x200018e; const int opcodeSetLevelExplicit = 0x200018f; const int opcodeGetDeadCount = 0x20001a3; const int opcodeGetPCFacRep = 0x20012; const int opcodeGetPCFacRepExplicit = 0x20013; const int opcodeSetPCFacRep = 0x20014; const int opcodeSetPCFacRepExplicit = 0x20015; const int opcodeModPCFacRep = 0x20016; const int opcodeModPCFacRepExplicit = 0x20017; const int opcodeGetCommonDisease = 0x20001a8; const int opcodeGetCommonDiseaseExplicit = 0x20001a9; const int opcodeGetBlightDisease = 0x20001aa; const int opcodeGetBlightDiseaseExplicit = 0x20001ab; const int opcodeGetRace = 0x20001d9; const int opcodeGetRaceExplicit = 0x20001da; const int opcodePcExpelled = 0x20018; const int opcodePcExpelledExplicit = 0x20019; const int opcodePcExpell = 0x2001a; const int opcodePcExpellExplicit = 0x2001b; const int opcodePcClearExpelled = 0x2001c; const int opcodePcClearExpelledExplicit = 0x2001d; const int opcodeRaiseRank = 0x20001e8; const int opcodeRaiseRankExplicit = 0x20001e9; const int opcodeLowerRank = 0x20001ea; const int opcodeLowerRankExplicit = 0x20001eb; const int opcodeOnDeath = 0x20001fc; const int opcodeOnDeathExplicit = 0x2000205; const int opcodeOnMurder = 0x2000249; const int opcodeOnMurderExplicit = 0x200024a; const int opcodeOnKnockout = 0x2000240; const int opcodeOnKnockoutExplicit = 0x2000241; const int opcodeBecomeWerewolf = 0x2000217; const int opcodeBecomeWerewolfExplicit = 0x2000218; const int opcodeUndoWerewolf = 0x2000219; const int opcodeUndoWerewolfExplicit = 0x200021a; const int opcodeSetWerewolfAcrobatics = 0x200021b; const int opcodeSetWerewolfAcrobaticsExplicit = 0x200021c; const int opcodeIsWerewolf = 0x20001fd; const int opcodeIsWerewolfExplicit = 0x20001fe; const int opcodeGetWerewolfKills = 0x20001e2; const int opcodeRemoveSpellEffects = 0x200022b; const int opcodeRemoveSpellEffectsExplicit = 0x200022c; const int opcodeRemoveEffects = 0x200022d; const int opcodeRemoveEffectsExplicit = 0x200022e; const int opcodeResurrect = 0x200022f; const int opcodeResurrectExplicit = 0x2000230; const int opcodeGetStat = 0x200024e; const int opcodeGetStatExplicit = 0x200024f; } namespace Transformation { const int opcodeSetScale = 0x2000164; const int opcodeSetScaleExplicit = 0x2000165; const int opcodeSetAngle = 0x2000166; const int opcodeSetAngleExplicit = 0x2000167; const int opcodeGetScale = 0x2000168; const int opcodeGetScaleExplicit = 0x2000169; const int opcodeGetAngle = 0x200016a; const int opcodeGetAngleExplicit = 0x200016b; const int opcodeGetPos = 0x2000190; const int opcodeGetPosExplicit = 0x2000191; const int opcodeSetPos = 0x2000192; const int opcodeSetPosExplicit = 0x2000193; const int opcodeGetStartingPos = 0x2000194; const int opcodeGetStartingPosExplicit = 0x2000195; const int opcodeGetStartingAngle = 0x2000210; const int opcodeGetStartingAngleExplicit = 0x2000211; const int opcodePosition = 0x2000196; const int opcodePositionExplicit = 0x2000197; const int opcodePositionCell = 0x2000198; const int opcodePositionCellExplicit = 0x2000199; const int opcodePlaceItemCell = 0x200019a; const int opcodePlaceItem = 0x200019b; const int opcodePlaceAtPc = 0x200019c; const int opcodePlaceAtMe = 0x200019d; const int opcodePlaceAtMeExplicit = 0x200019e; const int opcodeModScale = 0x20001e3; const int opcodeModScaleExplicit = 0x20001e4; const int opcodeRotate = 0x20001ff; const int opcodeRotateExplicit = 0x2000200; const int opcodeRotateWorld = 0x2000201; const int opcodeRotateWorldExplicit = 0x2000202; const int opcodeSetAtStart = 0x2000203; const int opcodeSetAtStartExplicit = 0x2000204; const int opcodeMove = 0x2000206; const int opcodeMoveExplicit = 0x2000207; const int opcodeMoveWorld = 0x2000208; const int opcodeMoveWorldExplicit = 0x2000209; const int opcodeResetActors = 0x20002f4; const int opcodeFixme = 0x2000302; const int opcodeGetDistance = 0x200031e; const int opcodeGetDistanceExplicit = 0x200031f; } namespace User { const int opcodeUser1 = 0x200016c; const int opcodeUser2 = 0x200016d; const int opcodeUser3 = 0x200016e; const int opcodeUser3Explicit = 0x200016f; const int opcodeUser4 = 0x2000170; const int opcodeUser4Explicit = 0x2000171; } } #endif openmw-openmw-0.48.0/components/compiler/output.cpp000066400000000000000000000032141445372753700224770ustar00rootroot00000000000000#include "output.hpp" #include #include #include #include "locals.hpp" namespace Compiler { Output::Output (Locals& locals) : mLocals (locals) {} void Output::getCode (std::vector& code) const { code.clear(); // header code.push_back (static_cast (mCode.size())); assert (mLiterals.getIntegerSize()%4==0); code.push_back (static_cast (mLiterals.getIntegerSize()/4)); assert (mLiterals.getFloatSize()%4==0); code.push_back (static_cast (mLiterals.getFloatSize()/4)); assert (mLiterals.getStringSize()%4==0); code.push_back (static_cast (mLiterals.getStringSize()/4)); // code std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); // literals mLiterals.append (code); } const Literals& Output::getLiterals() const { return mLiterals; } const std::vector& Output::getCode() const { return mCode; } const Locals& Output::getLocals() const { return mLocals; } Literals& Output::getLiterals() { return mLiterals; } std::vector& Output::getCode() { return mCode; } Locals& Output::getLocals() { return mLocals; } void Output::clear() { mLiterals.clear(); mCode.clear(); mLocals.clear(); } } openmw-openmw-0.48.0/components/compiler/output.hpp000066400000000000000000000016741445372753700225140ustar00rootroot00000000000000#ifndef COMPILER_OUTPUT_H_INCLUDED #define COMPILER_OUTPUT_H_INCLUDED #include "literals.hpp" #include #include namespace Compiler { class Locals; class Output { Literals mLiterals; std::vector mCode; Locals& mLocals; public: Output (Locals& locals); void getCode (std::vector& code) const; ///< store generated code in \a code. const Literals& getLiterals() const; const Locals& getLocals() const; const std::vector& getCode() const; Literals& getLiterals(); std::vector& getCode(); Locals& getLocals(); void clear(); }; } #endif openmw-openmw-0.48.0/components/compiler/parser.cpp000066400000000000000000000076521445372753700224450ustar00rootroot00000000000000#include "parser.hpp" #include "errorhandler.hpp" #include "exception.hpp" #include "scanner.hpp" #include namespace Compiler { // Report the error and throw an exception. [[noreturn]] void Parser::reportSeriousError (const std::string& message, const TokenLoc& loc) { mErrorHandler.error (message, loc); throw SourceException(); } // Report the warning without throwing an exception. void Parser::reportWarning (const std::string& message, const TokenLoc& loc) { mErrorHandler.warning (message, loc); } // Report an unexpected EOF condition. [[noreturn]] void Parser::reportEOF() { mErrorHandler.endOfFile(); throw EOFException(); } // Return error handler ErrorHandler& Parser::getErrorHandler() { return mErrorHandler; } // Return context const Context& Parser::getContext() const { return mContext; } std::string Parser::toLower (const std::string& name) { std::string lowerCase = Misc::StringUtils::lowerCase(name); return lowerCase; } Parser::Parser (ErrorHandler& errorHandler, const Context& context) : mErrorHandler (errorHandler), mContext (context), mOptional (false), mEmpty (true) {} // destructor Parser::~Parser() = default; // Handle an int token. // \return fetch another token? // // - Default-implementation: Report an error. bool Parser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected numeric value", loc); else scanner.putbackInt (value, loc); return false; } // Handle a float token. // \return fetch another token? // // - Default-implementation: Report an error. bool Parser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected floating point value", loc); else scanner.putbackFloat (value, loc); return false; } // Handle a name token. // \return fetch another token? // // - Default-implementation: Report an error. bool Parser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected name", loc); else scanner.putbackName (name, loc); return false; } // Handle a keyword token. // \return fetch another token? // // - Default-implementation: Report an error. bool Parser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected keyword", loc); else scanner.putbackKeyword (keyword, loc); return false; } // Handle a special character token. // \return fetch another token? // // - Default-implementation: Report an error. bool Parser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (!(mOptional && mEmpty)) reportSeriousError ("Unexpected special token", loc); else scanner.putbackSpecial (code, loc); return false; } bool Parser::parseComment (const std::string& comment, const TokenLoc& loc, Scanner& scanner) { return true; } // Handle an EOF token. // // - Default-implementation: Report an error. void Parser::parseEOF (Scanner& scanner) { reportEOF(); } void Parser::reset() { mOptional = false; mEmpty = true; } void Parser::setOptional (bool optional) { mOptional = optional; } void Parser::start() { mEmpty = false; } bool Parser::isEmpty() const { return mEmpty; } } openmw-openmw-0.48.0/components/compiler/parser.hpp000066400000000000000000000067461445372753700224550ustar00rootroot00000000000000#ifndef COMPILER_PARSER_H_INCLUDED #define COMPILER_PARSER_H_INCLUDED #include namespace Compiler { class Scanner; struct TokenLoc; class ErrorHandler; class Context; /// \brief Parser base class /// /// This class defines a callback-parser. class Parser { ErrorHandler& mErrorHandler; const Context& mContext; bool mOptional; bool mEmpty; protected: [[noreturn]] void reportSeriousError (const std::string& message, const TokenLoc& loc); ///< Report the error and throw a exception. void reportWarning (const std::string& message, const TokenLoc& loc); ///< Report the warning without throwing an exception. [[noreturn]] void reportEOF(); ///< Report an unexpected EOF condition. ErrorHandler& getErrorHandler(); ///< Return error handler const Context& getContext() const; ///< Return context static std::string toLower (const std::string& name); public: Parser (ErrorHandler& errorHandler, const Context& context); ///< constructor virtual ~Parser(); ///< destructor virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner); ///< Handle an int token. /// \return fetch another token? /// /// - Default-implementation: Report an error. virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner); ///< Handle a float token. /// \return fetch another token? /// /// - Default-implementation: Report an error. virtual bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner); ///< Handle a name token. /// \return fetch another token? /// /// - Default-implementation: Report an error. virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); ///< Handle a keyword token. /// \return fetch another token? /// /// - Default-implementation: Report an error. virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); ///< Handle a special character token. /// \return fetch another token? /// /// - Default-implementation: Report an error. virtual bool parseComment (const std::string& comment, const TokenLoc& loc, Scanner& scanner); ///< Handle comment token. /// \return fetch another token? /// /// - Default-implementation: ignored (and return true). virtual void parseEOF (Scanner& scanner); ///< Handle EOF token. /// /// - Default-implementation: Report an error. virtual void reset(); ///< Reset parser to clean state. void setOptional (bool optional); ///< Optional mode: If nothign has been parsed yet and an unexpected token is delivered, stop /// parsing without raising an exception (after a reset the parser is in non-optional mode). void start(); ///< Mark parser as non-empty (at least one token has been parser). bool isEmpty() const; ///< Has anything been parsed? }; } #endif openmw-openmw-0.48.0/components/compiler/quickfileparser.cpp000066400000000000000000000024551445372753700243360ustar00rootroot00000000000000#include "quickfileparser.hpp" #include "skipparser.hpp" #include "scanner.hpp" Compiler::QuickFileParser::QuickFileParser (ErrorHandler& errorHandler, const Context& context, Locals& locals) : Parser (errorHandler, context), mDeclarationParser (errorHandler, context, locals) {} bool Compiler::QuickFileParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return true; } bool Compiler::QuickFileParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==Scanner::K_end) return false; if (keyword==Scanner::K_short || keyword==Scanner::K_long || keyword==Scanner::K_float) { mDeclarationParser.reset(); scanner.putbackKeyword (keyword, loc); scanner.scan (mDeclarationParser); return true; } SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return true; } bool Compiler::QuickFileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code!=Scanner::S_newline) { SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); } return true; } void Compiler::QuickFileParser::parseEOF (Scanner& scanner) { } openmw-openmw-0.48.0/components/compiler/quickfileparser.hpp000066400000000000000000000022121445372753700243320ustar00rootroot00000000000000#ifndef COMPILER_QUICKFILEPARSER_H_INCLUDED #define COMPILER_QUICKFILEPARSER_H_INCLUDED #include "parser.hpp" #include "declarationparser.hpp" namespace Compiler { class Locals; /// \brief File parser variant that ignores everything but variable declarations class QuickFileParser : public Parser { DeclarationParser mDeclarationParser; public: QuickFileParser (ErrorHandler& errorHandler, const Context& context, Locals& locals); bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void parseEOF (Scanner& scanner) override; ///< Handle EOF token. }; } #endif openmw-openmw-0.48.0/components/compiler/scanner.cpp000066400000000000000000000427241445372753700226010ustar00rootroot00000000000000#include "scanner.hpp" #include #include #include "exception.hpp" #include "errorhandler.hpp" #include "parser.hpp" #include "extensions.hpp" #include namespace Compiler { bool Scanner::get (MultiChar& c) { if (!c.getFrom(mStream)) return false; mPrevLoc = mLoc; if (c=='\n') { mStrictKeywords = false; mTolerantNames = false; mExpectName = false; mLoc.mColumn = 0; ++mLoc.mLine; mLoc.mLiteral.clear(); } else { ++mLoc.mColumn; c.appendTo(mLoc.mLiteral); } return true; } void Scanner::putback (MultiChar& c) { c.putback(mStream); mLoc = mPrevLoc; } bool Scanner::scanToken (Parser& parser) { switch (mPutback) { case Putback_Special: mPutback = Putback_None; return parser.parseSpecial (mPutbackCode, mPutbackLoc, *this); case Putback_Integer: mPutback = Putback_None; return parser.parseInt (mPutbackInteger, mPutbackLoc, *this); case Putback_Float: mPutback = Putback_None; return parser.parseFloat (mPutbackFloat, mPutbackLoc, *this); case Putback_Name: mPutback = Putback_None; return parser.parseName (mPutbackName, mPutbackLoc, *this); case Putback_Keyword: mPutback = Putback_None; return parser.parseKeyword (mPutbackCode, mPutbackLoc, *this); case Putback_None: break; } MultiChar c; if (!get (c)) { parser.parseEOF (*this); return false; } else if (c==';') { std::string comment; c.appendTo(comment); while (get (c)) { if (c=='\n') { putback (c); break; } else c.appendTo(comment); } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); return parser.parseComment (comment, loc, *this); } else if (c.isWhitespace()) { mLoc.mLiteral.clear(); return true; } else if (c==':') { // treat : as a whitespace :( mLoc.mLiteral.clear(); return true; } else if (c.isAlpha() || c=='_' || c=='"') { bool cont = false; if (scanName (c, parser, cont)) { mLoc.mLiteral.clear(); return cont; } } else if (c.isDigit()) { bool cont = false; bool scanned = mExpectName ? scanName(c, parser, cont) : scanInt(c, parser, cont); if (scanned) { mLoc.mLiteral.clear(); return cont; } } else if (c==13) // linux compatibility hack { return true; } else { bool cont = false; if (scanSpecial (c, parser, cont)) { mLoc.mLiteral.clear(); return cont; } } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); mErrorHandler.error ("Syntax error", loc); throw SourceException(); } bool Scanner::scanInt (MultiChar& c, Parser& parser, bool& cont) { assert(c != '\0'); std::string value; c.appendTo(value); while (get (c)) { if (c.isDigit()) { c.appendTo(value); } else if (!c.isMinusSign() && isStringCharacter (c)) { /// workaround that allows names to begin with digits return scanName(c, parser, cont, value); } else if (c=='.') { return scanFloat (value, parser, cont); } else { putback (c); break; } } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); std::istringstream stream (value); int intValue = 0; stream >> intValue; cont = parser.parseInt (intValue, loc, *this); return true; } bool Scanner::scanFloat (const std::string& intValue, Parser& parser, bool& cont) { std::string value = intValue + "."; MultiChar c; bool empty = intValue.empty() || intValue=="-"; bool error = false; while (get (c)) { if (c.isDigit()) { c.appendTo(value); empty = false; } else if (c.isAlpha() || c=='_') error = true; else { putback (c); break; } } if (empty || error) return false; TokenLoc loc (mLoc); mLoc.mLiteral.clear(); std::istringstream stream (value); float floatValue = 0; stream >> floatValue; cont = parser.parseFloat (floatValue, loc, *this); return true; } static const char *sKeywords[] = { "begin", "end", "short", "long", "float", "if", "endif", "else", "elseif", "while", "endwhile", "return", "messagebox", "set", "to", nullptr }; bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont, std::string name) { c.appendTo(name); if (!scanName (name)) return false; else if(name.empty()) return true; TokenLoc loc (mLoc); mLoc.mLiteral.clear(); if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') { name = name.substr (1, name.size()-2); // allow keywords enclosed in "" /// \todo optionally disable if (mStrictKeywords) { cont = parser.parseName (name, loc, *this); return true; } } int i = 0; std::string lowerCase = Misc::StringUtils::lowerCase(name); bool isKeyword = false; for (; sKeywords[i]; ++i) if (lowerCase==sKeywords[i]) { isKeyword = true; break; } // Russian localization and some mods use a quirk - add newline character directly // to compiled bytecode via HEX-editor to implement multiline messageboxes. // Of course, original editor can not compile such script. // Allow messageboxes to bypass the "incomplete string or name" error. if (lowerCase == "messagebox") enableIgnoreNewlines(); else if (isKeyword) mIgnoreNewline = false; if (sKeywords[i]) { cont = parser.parseKeyword (i, loc, *this); return true; } if (mExtensions) { if (int keyword = mExtensions->searchKeyword (lowerCase)) { cont = parser.parseKeyword (keyword, loc, *this); return true; } } cont = parser.parseName (name, loc, *this); return true; } bool Scanner::scanName (std::string& name) { MultiChar c; bool error = false; while (get (c)) { if (!name.empty() && name[0]=='"') { if (c=='"') { c.appendTo(name); break; } // ignoring escape sequences for now, because they are messing up stupid Windows path names. // else if (c=='\\') // { // if (!get (c)) // { // error = true; // mErrorHandler.error ("incomplete escape sequence", mLoc); // break; // } // } else if (c=='\n') { if (mIgnoreNewline) mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc); else { bool allWhitespace = true; for (size_t i = 1; i < name.size(); i++) { //ignore comments if (name[i] == ';') break; else if (name[i] != '\t' && name[i] != ' ' && name[i] != '\r') { allWhitespace = false; break; } } if (allWhitespace) { name.clear(); mLoc.mLiteral.clear(); mErrorHandler.warning ("unterminated empty string", mLoc); return true; } error = true; mErrorHandler.error ("incomplete string or name", mLoc); break; } } } else if (!(c=='"' && name.empty())) { if (!isStringCharacter (c) && !(mTolerantNames && (c=='.' || c == '-'))) { putback (c); break; } } c.appendTo(name); } return !error; } bool Scanner::scanSpecial (MultiChar& c, Parser& parser, bool& cont) { bool expectName = mExpectName; mExpectName = false; int special = -1; if (c=='\n') special = S_newline; else if (c=='(' || c=='[') /// \todo option to disable the use of [ as alias for ( special = S_open; else if (c==')' || c==']') /// \todo option to disable the use of ] as alias for ) special = S_close; else if (c=='.') { MultiChar next; // check, if this starts a float literal if (get (next)) { putback (next); if (next.isDigit()) return scanFloat ("", parser, cont); } special = S_member; } else if (c=='=') { if (get (c)) { /// \todo hack to allow a space in comparison operators (add option to disable) if (c==' ' && !get (c)) special = S_cmpEQ; else if (c=='=') special = S_cmpEQ; else if (c == '>' || c == '<') // Treat => and =< as == { special = S_cmpEQ; mErrorHandler.warning (std::string("invalid operator =") + c.data() + ", treating it as ==", mLoc); } else { special = S_cmpEQ; putback (c); // return false; /// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. } } else { putback (c); return false; } } else if (c=='!') { if (get (c)) { /// \todo hack to allow a space in comparison operators (add option to disable) if (c==' ' && !get (c)) return false; if (c=='=') special = S_cmpNE; else { putback (c); return false; } } else return false; } else if (c.isMinusSign()) { MultiChar next; if (get (next)) { if (next=='>') special = S_ref; else { putback (next); special = S_minus; } } else special = S_minus; } else if (c=='<') { if (get (c)) { /// \todo hack to allow a space in comparison operators (add option to disable) if (c==' ' && !get (c)) special = S_cmpLT; else if (c=='=') { special = S_cmpLE; if (get (c) && c!='=') // <== is a allowed as an alternative to <= :( putback (c); } else if (c == '<' || c == '>') // Treat <> and << as < { special = S_cmpLT; mErrorHandler.warning ("Invalid operator, treating it as <", mLoc); } else { putback (c); special = S_cmpLT; } } else special = S_cmpLT; } else if (c=='>') { if (get (c)) { /// \todo hack to allow a space in comparison operators (add option to disable) if (c==' ' && !get (c)) special = S_cmpGT; else if (c=='=') { special = S_cmpGE; if (get (c) && c!='=') // >== is a allowed as an alternative to >= :( putback (c); } else if (c == '<' || c == '>') // Treat >< and >> as > { special = S_cmpGT; mErrorHandler.warning ("Invalid operator, treating it as >", mLoc); } else { putback (c); special = S_cmpGT; } } else special = S_cmpGT; } else if (c=='+') special = S_plus; else if (c=='*') special = S_mult; else if (c=='/') special = S_div; else return false; if (special==S_newline) mLoc.mLiteral = ""; else if (expectName && (special == S_member || special == S_minus)) { bool tolerant = mTolerantNames; mTolerantNames = true; bool out = scanName(c, parser, cont); mTolerantNames = tolerant; return out; } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); cont = parser.parseSpecial (special, loc, *this); return true; } bool Scanner::isStringCharacter (MultiChar& c, bool lookAhead) { if (lookAhead && c.isMinusSign()) { /// \todo disable this when doing more stricter compiling. Also, find out who is /// responsible for allowing it in the first place and meet up with that person in /// a dark alley. MultiChar next; if (next.peek(mStream) && isStringCharacter (next, false)) return true; } return c.isAlpha() || c.isDigit() || c=='_' || /// \todo disable this when doing more stricter compiling c=='`' || c=='\''; } // constructor Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), mStrictKeywords (false), mTolerantNames (false), mIgnoreNewline(false), mExpectName(false) { } void Scanner::scan (Parser& parser) { while (scanToken (parser)); mExpectName = false; } void Scanner::putbackSpecial (int code, const TokenLoc& loc) { mPutback = Putback_Special; mPutbackCode = code; mPutbackLoc = loc; } void Scanner::putbackInt (int value, const TokenLoc& loc) { mPutback = Putback_Integer; mPutbackInteger = value; mPutbackLoc = loc; } void Scanner::putbackFloat (float value, const TokenLoc& loc) { mPutback = Putback_Float; mPutbackFloat = value; mPutbackLoc = loc; } void Scanner::putbackName (const std::string& name, const TokenLoc& loc) { mPutback = Putback_Name; mPutbackName = name; mPutbackLoc = loc; } void Scanner::putbackKeyword (int keyword, const TokenLoc& loc) { mPutback = Putback_Keyword; mPutbackCode = keyword; mPutbackLoc = loc; } void Scanner::listKeywords (std::vector& keywords) { for (int i=0; Compiler::sKeywords[i]; ++i) keywords.emplace_back(Compiler::sKeywords[i]); if (mExtensions) mExtensions->listKeywords (keywords); } void Scanner::enableIgnoreNewlines() { mIgnoreNewline = true; } void Scanner::enableStrictKeywords() { mStrictKeywords = true; } void Scanner::enableTolerantNames() { mTolerantNames = true; } void Scanner::enableExpectName() { mExpectName = true; } } openmw-openmw-0.48.0/components/compiler/scanner.hpp000066400000000000000000000173701445372753700226050ustar00rootroot00000000000000#ifndef COMPILER_SCANNER_H_INCLUDED #define COMPILER_SCANNER_H_INCLUDED #include #include #include #include #include #include "tokenloc.hpp" namespace Compiler { class ErrorHandler; class Parser; class Extensions; /// \brief Scanner /// /// This class translate a char-stream to a token stream (delivered via /// parser-callbacks). class MultiChar { public: MultiChar() { blank(); } explicit MultiChar(const char ch) { blank(); mData[0] = ch; mLength = getCharLength(ch); } static int getCharLength(const char ch) { unsigned char c = ch; if (c<=127) return 0; else if ((c & 0xE0) == 0xC0) return 1; else if ((c & 0xF0) == 0xE0) return 2; else if ((c & 0xF8) == 0xF0) return 3; else return -1; } bool operator== (const char ch) { return mData[0]==ch && mData[1]==0 && mData[2]==0 && mData[3]==0; } bool operator== (const MultiChar& ch) { return mData[0]==ch.mData[0] && mData[1]==ch.mData[1] && mData[2]==ch.mData[2] && mData[3]==ch.mData[3]; } bool operator!= (const char ch) { return mData[0]!=ch || mData[1]!=0 || mData[2]!=0 || mData[3]!=0; } bool isWhitespace() { return (mData[0]==' ' || mData[0]=='\t' || mData[0]==',') && mData[1]==0 && mData[2]==0 && mData[3]==0; } bool isDigit() { return std::isdigit(mData[0]) && mData[1]==0 && mData[2]==0 && mData[3]==0; } bool isMinusSign() { if (mData[0] == '-' && mData[1] == 0 && mData[2] == 0 && mData[3] == 0) return true; return mData[0] == '\xe2' && mData[1] == '\x80' && mData[2] == '\x93' && mData[3] == 0; } bool isAlpha() { if (isMinusSign()) return false; return std::isalpha(mData[0]) || mData[1]!=0 || mData[2]!=0 || mData[3]!=0; } void appendTo(std::string& str) { for (int i = 0; i <= mLength; i++) str += mData[i]; } void putback (std::istream& in) { for (int i = mLength; i >= 0; i--) in.putback (mData[i]); } bool getFrom(std::istream& in) { blank(); char ch = static_cast(in.peek()); if (!in.good()) return false; int length = getCharLength(ch); if (length < 0) return false; for (int i = 0; i <= length; i++) { in.get (ch); if (!in.good()) return false; mData[i] = ch; } mLength = length; return true; } bool peek(std::istream& in) { std::streampos p_orig = in.tellg(); char ch = static_cast(in.peek()); if (!in.good()) return false; int length = getCharLength(ch); if (length < 0) return false; for (int i = 0; i <= length; i++) { in.get (ch); if (!in.good()) return false; mData[i] = ch; } mLength = length; in.seekg(p_orig); return true; }; void blank() { std::fill(std::begin(mData), std::end(mData), '\0'); mLength = -1; } std::string data() { // NB: mLength is the number of the last element in the array return std::string(mData, mLength + 1); } private: char mData[4]{}; int mLength{}; }; class Scanner { enum putback_type { Putback_None, Putback_Special, Putback_Integer, Putback_Float, Putback_Name, Putback_Keyword }; ErrorHandler& mErrorHandler; TokenLoc mLoc; TokenLoc mPrevLoc; std::istream& mStream; const Extensions *mExtensions; putback_type mPutback; int mPutbackCode; int mPutbackInteger; float mPutbackFloat; std::string mPutbackName; TokenLoc mPutbackLoc; bool mStrictKeywords; bool mTolerantNames; bool mIgnoreNewline; bool mExpectName; public: enum keyword { K_begin, K_end, K_short, K_long, K_float, K_if, K_endif, K_else, K_elseif, K_while, K_endwhile, K_return, K_messagebox, K_set, K_to }; enum special { S_newline, S_open, S_close, S_cmpEQ, S_cmpNE, S_cmpLT, S_cmpLE, S_cmpGT, S_cmpGE, S_plus, S_minus, S_mult, S_div, S_ref, S_member }; private: // not implemented Scanner (const Scanner&); Scanner& operator= (const Scanner&); bool get (MultiChar& c); void putback (MultiChar& c); bool scanToken (Parser& parser); bool scanInt (MultiChar& c, Parser& parser, bool& cont); bool scanFloat (const std::string& intValue, Parser& parser, bool& cont); bool scanName (MultiChar& c, Parser& parser, bool& cont, std::string name = {}); /// \param name May contain the start of the name (one or more characters) bool scanName (std::string& name); bool scanSpecial (MultiChar& c, Parser& parser, bool& cont); bool isStringCharacter (MultiChar& c, bool lookAhead = true); public: Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions = nullptr); ///< constructor void scan (Parser& parser); ///< Scan a token and deliver it to the parser. void putbackSpecial (int code, const TokenLoc& loc); ///< put back a special token void putbackInt (int value, const TokenLoc& loc); ///< put back an integer token void putbackFloat (float value, const TokenLoc& loc); ///< put back a float token void putbackName (const std::string& name, const TokenLoc& loc); ///< put back a name token void putbackKeyword (int keyword, const TokenLoc& loc); ///< put back a keyword token void listKeywords (std::vector& keywords); ///< Append all known keywords to \a keywords. /// Treat newline character as a part of script command. /// /// \attention This mode lasts only until the next keyword is reached. void enableIgnoreNewlines(); /// Do not accept keywords in quotation marks anymore. /// /// \attention This mode lasts only until the next newline is reached. void enableStrictKeywords(); /// Continue parsing a name when hitting a '.' or a '-' /// /// \attention This mode lasts only until the next newline is reached. void enableTolerantNames(); /// Treat '.' and '-' as the start of a name. /// /// \attention This mode lasts only until the next newline is reached or the call to scan ends. void enableExpectName(); }; } #endif openmw-openmw-0.48.0/components/compiler/scriptparser.cpp000066400000000000000000000052021445372753700236570ustar00rootroot00000000000000#include "scriptparser.hpp" #include "scanner.hpp" #include "skipparser.hpp" #include "errorhandler.hpp" namespace Compiler { ScriptParser::ScriptParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, bool end) : Parser (errorHandler, context), mOutput (locals), mLineParser (errorHandler, context, locals, mOutput.getLiterals(), mOutput.getCode()), mControlParser (errorHandler, context, locals, mOutput.getLiterals()), mEnd (end) {} void ScriptParser::getCode (std::vector& code) const { mOutput.getCode (code); } bool ScriptParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { mLineParser.reset(); if (mLineParser.parseName (name, loc, scanner)) scanner.scan (mLineParser); return true; } bool ScriptParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==Scanner::K_while || keyword==Scanner::K_if || keyword==Scanner::K_elseif) { mControlParser.reset(); if (mControlParser.parseKeyword (keyword, loc, scanner)) scanner.scan (mControlParser); mControlParser.appendCode (mOutput.getCode()); return true; } /// \todo add an option to disable this nonsense if (keyword==Scanner::K_endif) { // surplus endif getErrorHandler().warning ("endif without matching if/elseif", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return true; } if (keyword==Scanner::K_end && mEnd) { return false; } mLineParser.reset(); if (mLineParser.parseKeyword (keyword, loc, scanner)) scanner.scan (mLineParser); return true; } bool ScriptParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_newline) // empty line return true; if (code==Scanner::S_open) /// \todo Option to switch this off { scanner.putbackSpecial (code, loc); return parseKeyword (Scanner::K_if, loc, scanner); } mLineParser.reset(); if (mLineParser.parseSpecial (code, loc, scanner)) scanner.scan (mLineParser); return true; } void ScriptParser::parseEOF (Scanner& scanner) { if (mEnd) Parser::parseEOF (scanner); } void ScriptParser::reset() { mLineParser.reset(); mOutput.clear(); } } openmw-openmw-0.48.0/components/compiler/scriptparser.hpp000066400000000000000000000030461445372753700236700ustar00rootroot00000000000000#ifndef COMPILER_SCRIPTPARSER_H_INCLUDED #define COMPILER_SCRIPTPARSER_H_INCLUDED #include "parser.hpp" #include "lineparser.hpp" #include "controlparser.hpp" #include "output.hpp" namespace Compiler { class Locals; // Script parser, to be used in dialogue scripts and as part of FileParser class ScriptParser : public Parser { Output mOutput; LineParser mLineParser; ControlParser mControlParser; bool mEnd; public: /// \param end of script is marked by end keyword. ScriptParser (ErrorHandler& errorHandler, const Context& context, Locals& locals, bool end = false); void getCode (std::vector& code) const; ///< store generated code in \a code. bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? void parseEOF (Scanner& scanner) override; ///< Handle EOF token. void reset() override; ///< Reset parser to clean state. }; } #endif openmw-openmw-0.48.0/components/compiler/skipparser.cpp000066400000000000000000000015531445372753700233260ustar00rootroot00000000000000#include "skipparser.hpp" #include "scanner.hpp" namespace Compiler { SkipParser::SkipParser (ErrorHandler& errorHandler, const Context& context) : Parser (errorHandler, context) {} bool SkipParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { return true; } bool SkipParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { return true; } bool SkipParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { return true; } bool SkipParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { return true; } bool SkipParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_newline) return false; return true; } } openmw-openmw-0.48.0/components/compiler/skipparser.hpp000066400000000000000000000024561445372753700233360ustar00rootroot00000000000000#ifndef COMPILER_SKIPPARSER_H_INCLUDED #define COMPILER_SKIPPARSER_H_INCLUDED #include "parser.hpp" namespace Compiler { // \brief Skip parser for skipping a line // // This parser is mainly intended for skipping the rest of a faulty line. class SkipParser : public Parser { public: SkipParser (ErrorHandler& errorHandler, const Context& context); bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a float token. /// \return fetch another token? bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a special character token. /// \return fetch another token? }; } #endif openmw-openmw-0.48.0/components/compiler/streamerrorhandler.cpp000066400000000000000000000034501445372753700250440ustar00rootroot00000000000000#include "streamerrorhandler.hpp" #include #include #include "tokenloc.hpp" namespace Compiler { // Report error to the user. void StreamErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) { Debug::Level logLevel = Debug::Info; // Usually script warnings are not too important if (type == ErrorMessage) logLevel = Debug::Error; std::stringstream text; if (type==ErrorMessage) text << "Error: "; else text << "Warning: "; if (!mContext.empty()) text << mContext << " "; text << "line " << loc.mLine+1 << ", column " << loc.mColumn+1 << " (" << loc.mLiteral << "): " << message; Log(logLevel) << text.str(); } // Report a file related error void StreamErrorHandler::report (const std::string& message, Type type) { Debug::Level logLevel = Debug::Info; if (type==ErrorMessage) logLevel = Debug::Error; std::stringstream text; if (type==ErrorMessage) text << "Error: "; else text << "Warning: "; if (!mContext.empty()) text << mContext << " "; text << "file: " << message << std::endl; Log(logLevel) << text.str(); } void StreamErrorHandler::setContext(const std::string &context) { mContext = context; } StreamErrorHandler::StreamErrorHandler() = default; ContextOverride::ContextOverride(StreamErrorHandler& handler, const std::string& context) : mHandler(handler), mContext(handler.mContext) { mHandler.setContext(context); } ContextOverride::~ContextOverride() { mHandler.setContext(mContext); } } openmw-openmw-0.48.0/components/compiler/streamerrorhandler.hpp000066400000000000000000000025451445372753700250550ustar00rootroot00000000000000#ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED #include "errorhandler.hpp" namespace Compiler { class ContextOverride; /// \brief Error handler implementation: Write errors into logging stream class StreamErrorHandler : public ErrorHandler { std::string mContext; friend class ContextOverride; // not implemented StreamErrorHandler (const StreamErrorHandler&); StreamErrorHandler& operator= (const StreamErrorHandler&); void report (const std::string& message, const TokenLoc& loc, Type type) override; ///< Report error to the user. void report (const std::string& message, Type type) override; ///< Report a file related error public: void setContext(const std::string& context); // constructors StreamErrorHandler (); ///< constructor }; class ContextOverride { StreamErrorHandler& mHandler; const std::string mContext; public: ContextOverride (StreamErrorHandler& handler, const std::string& context); ContextOverride (const ContextOverride&) = delete; ContextOverride& operator= (const ContextOverride&) = delete; ~ContextOverride(); }; } #endif openmw-openmw-0.48.0/components/compiler/stringparser.cpp000066400000000000000000000062651445372753700236730ustar00rootroot00000000000000#include "stringparser.hpp" #include #include #include #include "scanner.hpp" #include "generator.hpp" #include "context.hpp" #include "extensions.hpp" namespace Compiler { StringParser::StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals) : Parser (errorHandler, context), mLiterals (literals), mSmashCase (false), mDiscard (false) { } bool StringParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { start(); mTokenLoc = loc; if (!mDiscard) { if (mSmashCase) Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); else Generator::pushString (mCode, mLiterals, name); } return false; } bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (const Extensions *extensions = getContext().getExtensions()) { std::string argumentType; // ignored bool hasExplicit = false; // ignored if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); return parseName (name, loc, scanner); } } if (keyword==Scanner::K_end || keyword==Scanner::K_begin || keyword==Scanner::K_short || keyword==Scanner::K_long || keyword==Scanner::K_float || keyword==Scanner::K_if || keyword==Scanner::K_endif || keyword==Scanner::K_else || keyword==Scanner::K_elseif || keyword==Scanner::K_while || keyword==Scanner::K_endwhile || keyword==Scanner::K_return || keyword==Scanner::K_messagebox || keyword==Scanner::K_set || keyword==Scanner::K_to) { // pretend this is not a keyword std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); return parseName (name, loc, scanner); } return Parser::parseKeyword (keyword, loc, scanner); } bool StringParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { reportWarning("Treating integer argument as a string", loc); return parseName(loc.mLiteral, loc, scanner); } void StringParser::append (std::vector& code) { std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); } void StringParser::reset() { mCode.clear(); mSmashCase = false; mTokenLoc = TokenLoc(); mDiscard = false; Parser::reset(); } void StringParser::smashCase() { mSmashCase = true; } const TokenLoc& StringParser::getTokenLoc() const { return mTokenLoc; } void StringParser::discard() { mDiscard = true; } } openmw-openmw-0.48.0/components/compiler/stringparser.hpp000066400000000000000000000034341445372753700236730ustar00rootroot00000000000000#ifndef COMPILER_STRINGPARSER_H_INCLUDED #define COMPILER_STRINGPARSER_H_INCLUDED #include #include #include "parser.hpp" #include "tokenloc.hpp" namespace Compiler { class Literals; class StringParser : public Parser { Literals& mLiterals; std::vector mCode; bool mSmashCase; TokenLoc mTokenLoc; bool mDiscard; public: StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals); bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a name token. /// \return fetch another token? bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) override; ///< Handle a keyword token. /// \return fetch another token? bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? void append (std::vector& code); ///< Append code for parsed string. void smashCase(); ///< Transform all scanned strings to lower case void reset() override; ///< Reset parser to clean state (this includes the smashCase function). /// Returns TokenLoc object for string. If no string has been parsed, the TokenLoc /// object will be default initialised. const TokenLoc& getTokenLoc() const; /// If parsing a string, do not add it to the literal table and do not create code /// for it. void discard(); }; } #endif openmw-openmw-0.48.0/components/compiler/tokenloc.hpp000066400000000000000000000005551445372753700227670ustar00rootroot00000000000000#ifndef COMPILER_TOKENLOC_H_INCLUDED #define COMPILER_TOKENLOC_H_INCLUDED #include namespace Compiler { /// \brief Location of a token in a source file struct TokenLoc { int mColumn; int mLine; std::string mLiteral; TokenLoc() : mColumn (0), mLine (0), mLiteral () {} }; } #endif // TOKENLOC_H_INCLUDED openmw-openmw-0.48.0/components/config/000077500000000000000000000000001445372753700200665ustar00rootroot00000000000000openmw-openmw-0.48.0/components/config/gamesettings.cpp000066400000000000000000000422011445372753700232630ustar00rootroot00000000000000#include "gamesettings.hpp" #include "launchersettings.hpp" #include #include #include #include const char Config::GameSettings::sArchiveKey[] = "fallback-archive"; const char Config::GameSettings::sContentKey[] = "content"; const char Config::GameSettings::sDirectoryKey[] = "data"; Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) { } void Config::GameSettings::validatePaths() { QStringList paths = mSettings.values(QString("data")); Files::PathContainer dataDirs; for (const QString &path : paths) { QByteArray bytes = path.toUtf8(); dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); } // Parse the data dirs to convert the tokenized paths mCfgMgr.processPaths(dataDirs, /*basePath=*/""); mDataDirs.clear(); for (auto & dataDir : dataDirs) { QString path = QString::fromUtf8(dataDir.string().c_str()); QDir dir(path); if (dir.exists()) mDataDirs.append(path); } // Do the same for data-local QString local = mSettings.value(QString("data-local")); if (local.length() && local.at(0) == QChar('\"')) { local.remove(0, 1); local.chop(1); } if (local.isEmpty()) return; dataDirs.clear(); QByteArray bytes = local.toUtf8(); dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); mCfgMgr.processPaths(dataDirs, /*basePath=*/""); if (!dataDirs.empty()) { QString path = QString::fromUtf8(dataDirs.front().string().c_str()); QDir dir(path); if (dir.exists()) mDataLocal = path; } } std::string Config::GameSettings::getGlobalDataDir() const { // global data dir may not exists if OpenMW is not installed (ie if run from build directory) if (boost::filesystem::exists(mCfgMgr.getGlobalDataPath())) return boost::filesystem::canonical(mCfgMgr.getGlobalDataPath()).string(); return {}; } QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) const { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } bool Config::GameSettings::readFile(QTextStream &stream, bool ignoreContent) { return readFile(stream, mSettings, ignoreContent); } bool Config::GameSettings::readUserFile(QTextStream &stream, bool ignoreContent) { return readFile(stream, mUserSettings, ignoreContent); } bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap &settings, bool ignoreContent) { QMultiMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.isEmpty() || line.startsWith("#")) continue; if (keyRe.indexIn(line) != -1) { QString key = keyRe.cap(1).trimmed(); QString value = keyRe.cap(2).trimmed(); // Don't remove composing entries if (key != QLatin1String("data") && key != QLatin1String("fallback-archive") && key != QLatin1String("content") && key != QLatin1String("groundcover") && key != QLatin1String("script-blacklist")) settings.remove(key); if (key == QLatin1String("data") || key == QLatin1String("data-local") || key == QLatin1String("resources") || key == QLatin1String("load-savegame")) { // Path line (e.g. 'data=...'), so needs processing to deal with ampersands and quotes // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved QChar delim = '\"'; QChar escape = '&'; if (value.at(0) == delim) { QString valueOriginal = value; value = ""; for (QString::const_iterator it = valueOriginal.begin() + 1; it != valueOriginal.end(); ++it) { if (*it == escape) ++it; else if (*it == delim) break; value += *it; } } } else if(ignoreContent && key == QLatin1String("content")) continue; QStringList values = cache.values(key); values.append(settings.values(key)); if (!values.contains(value)) { cache.insert(key, value); } } } if (settings.isEmpty()) { settings = cache; // This is the first time we read a file validatePaths(); return true; } // Merge the changed keys with those which didn't settings.unite(cache); validatePaths(); return true; } bool Config::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order QMapIterator i(mUserSettings); i.toBack(); while (i.hasPrevious()) { i.previous(); // path lines (e.g. 'data=...') need quotes and ampersands escaping to match how boost::filesystem::path uses boost::io::quoted if (i.key() == QLatin1String("data") || i.key() == QLatin1String("data-local") || i.key() == QLatin1String("resources") || i.key() == QLatin1String("load-savegame")) { stream << i.key() << "="; // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved QChar delim = '\"'; QChar escape = '&'; QString string = i.value(); stream << delim; for (auto& it : string) { if (it == delim || it == escape) stream << escape; stream << it; } stream << delim; stream << '\n'; continue; } stream << i.key() << "=" << i.value() << "\n"; } return true; } bool Config::GameSettings::isOrderedLine(const QString& line) { return line.contains(QRegExp("^\\s*fallback-archive\\s*=")) || line.contains(QRegExp("^\\s*fallback\\s*=")) || line.contains(QRegExp("^\\s*data\\s*=")) || line.contains(QRegExp("^\\s*data-local\\s*=")) || line.contains(QRegExp("^\\s*resources\\s*=")) || line.contains(QRegExp("^\\s*groundcover\\s*=")) || line.contains(QRegExp("^\\s*content\\s*=")); } // Policy: // // - Always ignore a line beginning with '#' or empty lines; added above a config // entry. // // - If a line in file exists with matching key and first part of value (before ',', // '\n', etc) also matches, then replace the line with that of mUserSettings. // - else remove line // // - If there is no corresponding line in file, add at the end // // - Removed content items are saved as comments if the item had any comments. // Content items prepended with '##' are considered previously removed. // bool Config::GameSettings::writeFileWithComments(QFile &file) { QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); // slurp std::vector fileCopy; QString line = stream.readLine(); while (!line.isNull()) { fileCopy.push_back(line); line = stream.readLine(); } stream.seek(0); // empty file, no comments to keep if (fileCopy.empty()) return writeFile(stream); // start // | // | +----------------------------------------------------------+ // | | | // v v | // skip non-"ordered" lines (remove "ordered" lines) | // | ^ | // | | | // | non-"ordered" line, write saved comments | // | ^ | // v | | // blank or comment line, save in temp buffer <--------+ | // | | | | // | +------- comment line ------+ | // v (special processing '##') | // "ordered" line | // | | // v | // save in a separate map of comments keyed by "ordered" line | // | | // +----------------------------------------------------------+ // // QRegExp settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$"); std::vector comments; auto commentStart = fileCopy.end(); std::map > commentsMap; for (auto iter = fileCopy.begin(); iter != fileCopy.end(); ++iter) { if (isOrderedLine(*iter)) { // save in a separate map of comments keyed by "ordered" line if (!comments.empty()) { if (settingRegex.indexIn(*iter) != -1) { commentsMap[settingRegex.cap(1)+"="+settingRegex.cap(2)] = comments; comments.clear(); commentStart = fileCopy.end(); } // else do nothing, malformed line } *iter = QString(); // "ordered" lines to be removed later } else if ((*iter).isEmpty() || (*iter).contains(QRegExp("^\\s*#"))) { // comment line, save in temp buffer if (comments.empty()) commentStart = iter; // special removed content processing if ((*iter).contains(QRegExp("^##content\\s*="))) { if (!comments.empty()) { commentsMap[*iter] = comments; comments.clear(); commentStart = fileCopy.end(); } } else comments.push_back(*iter); *iter = QString(); // assume to be deleted later } else { int index = settingRegex.indexIn(*iter); // blank or non-"ordered" line, write saved comments if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 && mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end()) { if (commentStart == fileCopy.end()) throw std::runtime_error("Config::GameSettings: failed to parse settings - iterator is past of end of settings file"); for (const auto & comment : comments) { *commentStart = comment; ++commentStart; } comments.clear(); commentStart = fileCopy.end(); } // keep blank lines and non-"ordered" lines other than comments // look for a key in the line if (index == -1 || settingRegex.captureCount() < 2) { // no key or first part of value found in line, replace with a null string which // will be remved later *iter = QString(); comments.clear(); commentStart = fileCopy.end(); continue; } // look for a matching key in user settings *iter = QString(); // assume no match QString key = settingRegex.cap(1); QString keyVal = settingRegex.cap(1)+"="+settingRegex.cap(2); QMultiMap::const_iterator i = mUserSettings.find(key); while (i != mUserSettings.end() && i.key() == key) { QString settingLine = i.key() + "=" + i.value(); if (settingRegex.indexIn(settingLine) != -1) { if ((settingRegex.cap(1)+"="+settingRegex.cap(2)) == keyVal) { *iter = settingLine; break; } } ++i; } } } // comments at top of file for (auto & iter : fileCopy) { if (iter.isNull()) continue; // Below is based on readFile() code, if that changes corresponding change may be // required (for example duplicates may be inserted if the rules don't match) if (/*(*iter).isEmpty() ||*/ iter.contains(QRegExp("^\\s*#"))) { stream << iter << "\n"; continue; } } // Iterate in reverse order to preserve insertion order QString settingLine; QMapIterator it(mUserSettings); it.toBack(); while (it.hasPrevious()) { it.previous(); if (it.key() == QLatin1String("data") || it.key() == QLatin1String("data-local") || it.key() == QLatin1String("resources") || it.key() == QLatin1String("load-savegame")) { settingLine = it.key() + "="; // The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not work as there are too may QStrings involved QChar delim = '\"'; QChar escape = '&'; QString string = it.value(); settingLine += delim; for (auto& iter : string) { if (iter == delim || iter == escape) settingLine += escape; settingLine += iter; } settingLine += delim; } else settingLine = it.key() + "=" + it.value(); if (settingRegex.indexIn(settingLine) != -1) { auto i = commentsMap.find(settingRegex.cap(1)+"="+settingRegex.cap(2)); // check if previous removed content item with comments if (i == commentsMap.end()) i = commentsMap.find("##"+settingRegex.cap(1)+"="+settingRegex.cap(2)); if (i != commentsMap.end()) { std::vector cLines = i->second; for (const auto & cLine : cLines) stream << cLine << "\n"; commentsMap.erase(i); } } stream << settingLine << "\n"; } // flush any removed settings if (!commentsMap.empty()) { auto i = commentsMap.begin(); for (; i != commentsMap.end(); ++i) { if (i->first.contains(QRegExp("^\\s*content\\s*="))) { std::vector cLines = i->second; for (const auto & cLine : cLines) stream << cLine << "\n"; // mark the content line entry for future preocessing stream << "##" << i->first << "\n"; //commentsMap.erase(i); } } } // flush any end comments if (!comments.empty()) { for (const auto & comment : comments) stream << comment << "\n"; } file.resize(file.pos()); return true; } bool Config::GameSettings::hasMaster() { bool result = false; QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) { result = true; break; } } return result; } void Config::GameSettings::setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) { auto const reset = [this](const char* key, const QStringList& list) { remove(key); for (auto const& item : list) setMultiValue(key, item); }; reset(sDirectoryKey, dirNames); reset(sArchiveKey, archiveNames); reset(sContentKey, fileNames); } QStringList Config::GameSettings::getDataDirs() const { return Config::LauncherSettings::reverse(mDataDirs); } QStringList Config::GameSettings::getArchiveList() const { // QMap returns multiple rows in LIFO order, so need to reverse return Config::LauncherSettings::reverse(values(sArchiveKey)); } QStringList Config::GameSettings::getContentList() const { // QMap returns multiple rows in LIFO order, so need to reverse return Config::LauncherSettings::reverse(values(sContentKey)); } void Config::GameSettings::clear() { mSettings.clear(); mUserSettings.clear(); mDataDirs.clear(); mDataLocal.clear(); } openmw-openmw-0.48.0/components/config/gamesettings.hpp000066400000000000000000000056001445372753700232720ustar00rootroot00000000000000#ifndef GAMESETTINGS_HPP #define GAMESETTINGS_HPP #include #include #include #include #include #include namespace Files { typedef std::vector PathContainer; struct ConfigurationManager; } namespace Config { class GameSettings { public: GameSettings(Files::ConfigurationManager &cfg); inline QString value(const QString &key, const QString &defaultValue = QString()) { return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); } inline void setValue(const QString &key, const QString &value) { mSettings.remove(key); mSettings.insert(key, value); mUserSettings.remove(key); mUserSettings.insert(key, value); } inline void setMultiValue(const QString &key, const QString &value) { QStringList values = mSettings.values(key); if (!values.contains(value)) mSettings.insert(key, value); values = mUserSettings.values(key); if (!values.contains(value)) mUserSettings.insert(key, value); } inline void remove(const QString &key) { mSettings.remove(key); mUserSettings.remove(key); } QStringList getDataDirs() const; std::string getGlobalDataDir() const; inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } inline QString getDataLocal() const {return mDataLocal; } bool hasMaster(); QStringList values(const QString &key, const QStringList &defaultValues = QStringList()) const; bool readFile(QTextStream &stream, bool ignoreContent = false); bool readFile(QTextStream &stream, QMultiMap &settings, bool ignoreContent = false); bool readUserFile(QTextStream &stream, bool ignoreContent = false); bool writeFile(QTextStream &stream); bool writeFileWithComments(QFile &file); QStringList getArchiveList() const; void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); QStringList getContentList() const; void clear(); private: Files::ConfigurationManager &mCfgMgr; void validatePaths(); QMultiMap mSettings; QMultiMap mUserSettings; QStringList mDataDirs; QString mDataLocal; static const char sArchiveKey[]; static const char sContentKey[]; static const char sDirectoryKey[]; static bool isOrderedLine(const QString& line) ; }; } #endif // GAMESETTINGS_HPP openmw-openmw-0.48.0/components/config/launchersettings.cpp000066400000000000000000000163071445372753700241630ustar00rootroot00000000000000#include "launchersettings.hpp" #include #include #include #include #include #include const char Config::LauncherSettings::sCurrentContentListKey[] = "Profiles/currentprofile"; const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg"; const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/"; const char Config::LauncherSettings::sDirectoryListSuffix[] = "/data"; const char Config::LauncherSettings::sArchiveListSuffix[] = "/fallback-archive"; const char Config::LauncherSettings::sContentListSuffix[] = "/content"; QStringList Config::LauncherSettings::subKeys(const QString &key) { QMultiMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); QRegExp keyRe("(.+)/"); QStringList result; for (const QString ¤tKey : keys) { if (keyRe.indexIn(currentKey) != -1) { QString prefixedKey = keyRe.cap(1); if(prefixedKey.startsWith(key)) { QString subKey = prefixedKey.remove(key); if (!subKey.isEmpty()) result.append(subKey); } } } result.removeDuplicates(); return result; } bool Config::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); QMultiMap settings = SettingsBase::getSettings(); QMapIterator i(settings); i.toBack(); while (i.hasPrevious()) { i.previous(); QString prefix; QString key; if (sectionRe.exactMatch(i.key())) { prefix = sectionRe.cap(1); key = sectionRe.cap(2); } // Get rid of legacy settings if (key.contains(QChar('\\'))) continue; if (key == QLatin1String("CurrentProfile")) continue; if (sectionPrefix != prefix) { sectionPrefix = prefix; stream << "\n[" << prefix << "]\n"; } stream << key << "=" << i.value() << "\n"; } return true; } QStringList Config::LauncherSettings::getContentLists() { return subKeys(QString(sContentListsSectionPrefix)); } QString Config::LauncherSettings::makeDirectoryListKey(const QString& contentListName) { return QString(sContentListsSectionPrefix) + contentListName + QString(sDirectoryListSuffix); } QString Config::LauncherSettings::makeArchiveListKey(const QString& contentListName) { return QString(sContentListsSectionPrefix) + contentListName + QString(sArchiveListSuffix); } QString Config::LauncherSettings::makeContentListKey(const QString& contentListName) { return QString(sContentListsSectionPrefix) + contentListName + QString(sContentListSuffix); } void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) { // obtain content list from game settings (if present) QStringList dirs(gameSettings.getDataDirs()); const QStringList archives(gameSettings.getArchiveList()); const QStringList files(gameSettings.getContentList()); // if openmw.cfg has no content, exit so we don't create an empty content list. if (dirs.isEmpty() || files.isEmpty()) { return; } // global and local data directories are not part of any profile const auto globalDataDir = QString(gameSettings.getGlobalDataDir().c_str()); const auto dataLocal = gameSettings.getDataLocal(); dirs.removeAll(globalDataDir); dirs.removeAll(dataLocal); // if any existing profile in launcher matches the content list, make that profile the default for (const QString &listName : getContentLists()) { if (isEqual(files, getContentListFiles(listName)) && isEqual(archives, getArchiveList(listName)) && isEqual(dirs, getDataDirectoryList(listName))) { setCurrentContentListName(listName); return; } } // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); setContentList(newContentListName, dirs, archives, files); } void Config::LauncherSettings::removeContentList(const QString &contentListName) { remove(makeDirectoryListKey(contentListName)); remove(makeArchiveListKey(contentListName)); remove(makeContentListKey(contentListName)); } void Config::LauncherSettings::setCurrentContentListName(const QString &contentListName) { remove(QString(sCurrentContentListKey)); setValue(QString(sCurrentContentListKey), contentListName); } void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) { auto const assign = [this](const QString key, const QStringList& list) { for (auto const& item : list) setMultiValue(key, item); }; removeContentList(contentListName); assign(makeDirectoryListKey(contentListName), dirNames); assign(makeArchiveListKey(contentListName), archiveNames); assign(makeContentListKey(contentListName), fileNames); } QString Config::LauncherSettings::getCurrentContentListName() const { return value(QString(sCurrentContentListKey)); } QStringList Config::LauncherSettings::getDataDirectoryList(const QString& contentListName) const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(getSettings().values(makeDirectoryListKey(contentListName))); } QStringList Config::LauncherSettings::getArchiveList(const QString& contentListName) const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(getSettings().values(makeArchiveListKey(contentListName))); } QStringList Config::LauncherSettings::getContentListFiles(const QString& contentListName) const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(getSettings().values(makeContentListKey(contentListName))); } QStringList Config::LauncherSettings::reverse(const QStringList& toReverse) { QStringList result; result.reserve(toReverse.size()); std::reverse_copy(toReverse.begin(), toReverse.end(), std::back_inserter(result)); return result; } bool Config::LauncherSettings::isEqual(const QStringList& list1, const QStringList& list2) { if (list1.count() != list2.count()) { return false; } for (int i = 0; i < list1.count(); ++i) { if (list1.at(i) != list2.at(i)) { return false; } } // if get here, lists are same return true; } QString Config::LauncherSettings::makeNewContentListName() { // basically, use date and time as the name e.g. YYYY-MM-DDThh:mm:ss time_t rawtime; struct tm * timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); int base = 10; QChar zeroPad('0'); return QString("%1-%2-%3T%4:%5:%6") .arg(timeinfo->tm_year + 1900, 4).arg(timeinfo->tm_mon + 1, 2, base, zeroPad).arg(timeinfo->tm_mday, 2, base, zeroPad) .arg(timeinfo->tm_hour, 2, base, zeroPad).arg(timeinfo->tm_min, 2, base, zeroPad).arg(timeinfo->tm_sec, 2, base, zeroPad); } openmw-openmw-0.48.0/components/config/launchersettings.hpp000066400000000000000000000050101445372753700241550ustar00rootroot00000000000000#ifndef LAUNCHERSETTINGS_HPP #define LAUNCHERSETTINGS_HPP #include "settingsbase.hpp" #include "gamesettings.hpp" namespace Config { class LauncherSettings : public SettingsBase > { public: bool writeFile(QTextStream &stream); /// \return names of all Content Lists in the launcher's .cfg file. QStringList getContentLists(); /// Set initially selected content list to match values from openmw.cfg, creating if necessary void setContentList(const GameSettings& gameSettings); /// Create a Content List (or replace if it already exists) void setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); void removeContentList(const QString &contentListName); void setCurrentContentListName(const QString &contentListName); QString getCurrentContentListName() const; QStringList getDataDirectoryList(const QString& contentListName) const; QStringList getArchiveList(const QString& contentListName) const; QStringList getContentListFiles(const QString& contentListName) const; /// \return new list that is reversed order of input static QStringList reverse(const QStringList& toReverse); static const char sLauncherConfigFileName[]; private: /// \return key to use to get/set the files in the specified data Directory List static QString makeDirectoryListKey(const QString& contentListName); /// \return key to use to get/set the files in the specified Archive List static QString makeArchiveListKey(const QString& contentListName); /// \return key to use to get/set the files in the specified Content List static QString makeContentListKey(const QString& contentListName); /// \return true if both lists are same static bool isEqual(const QStringList& list1, const QStringList& list2); static QString makeNewContentListName(); QStringList subKeys(const QString &key); /// name of entry in launcher.cfg that holds name of currently selected Content List static const char sCurrentContentListKey[]; /// section of launcher.cfg holding the Content Lists static const char sContentListsSectionPrefix[]; static const char sDirectoryListSuffix[]; static const char sArchiveListSuffix[]; static const char sContentListSuffix[]; }; } #endif // LAUNCHERSETTINGS_HPP openmw-openmw-0.48.0/components/config/settingsbase.hpp000066400000000000000000000056121445372753700232760ustar00rootroot00000000000000#ifndef SETTINGSBASE_HPP #define SETTINGSBASE_HPP #include #include #include #include namespace Config { template class SettingsBase { public: SettingsBase() { mMultiValue = false; } ~SettingsBase() = default; inline QString value(const QString &key, const QString &defaultValue = QString()) const { return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); } inline void setValue(const QString &key, const QString &value) { mSettings.replace(key, value); } inline void setMultiValue(const QString &key, const QString &value) { QStringList values = mSettings.values(key); if (!values.contains(value)) mSettings.insert(key, value); } inline void setMultiValueEnabled(bool enable) { mMultiValue = enable; } inline void remove(const QString &key) { mSettings.remove(key); } Map getSettings() const {return mSettings;} bool readFile(QTextStream &stream) { Map cache; QString sectionPrefix; QRegExp sectionRe("^\\[([^]]+)\\]"); QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.isEmpty() || line.startsWith("#")) continue; if (sectionRe.exactMatch(line)) { sectionPrefix = sectionRe.cap(1); sectionPrefix.append("/"); continue; } if (keyRe.indexIn(line) != -1) { QString key = keyRe.cap(1).trimmed(); QString value = keyRe.cap(2).trimmed(); if (!sectionPrefix.isEmpty()) key.prepend(sectionPrefix); mSettings.remove(key); QStringList values = cache.values(key); if (!values.contains(value)) { if (mMultiValue) { cache.insert(key, value); } else { cache.remove(key); cache.insert(key, value); } } } } if (mSettings.isEmpty()) { mSettings = cache; // This is the first time we read a file return true; } // Merge the changed keys with those which didn't mSettings.unite(cache); return true; } void clear() { mSettings.clear(); } private: Map mSettings; bool mMultiValue; }; } #endif // SETTINGSBASE_HPP openmw-openmw-0.48.0/components/contentselector/000077500000000000000000000000001445372753700220345ustar00rootroot00000000000000openmw-openmw-0.48.0/components/contentselector/model/000077500000000000000000000000001445372753700231345ustar00rootroot00000000000000openmw-openmw-0.48.0/components/contentselector/model/contentmodel.cpp000066400000000000000000000516641445372753700263470ustar00rootroot00000000000000#include "contentmodel.hpp" #include "esmfile.hpp" #include #include #include #include #include ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon, bool showOMWScripts) : QAbstractTableModel(parent), mWarningIcon(warningIcon), mShowOMWScripts(showOMWScripts), mMimeType ("application/omwcontent"), mMimeTypes (QStringList() << mMimeType), mColumnCount (1), mDropActions (Qt::MoveAction) { setEncoding ("win1252"); uncheckAll(); } ContentSelectorModel::ContentModel::~ContentModel() { qDeleteAll(mFiles); mFiles.clear(); } void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { mEncoding = encoding; } int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return mColumnCount; } int ContentSelectorModel::ContentModel::rowCount(const QModelIndex &parent) const { if(parent.isValid()) return 0; return mFiles.size(); } const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) const { if (row >= 0 && row < mFiles.size()) return mFiles.at(row); return nullptr; } ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) { if (row >= 0 && row < mFiles.count()) return mFiles.at(row); return nullptr; } const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const { EsmFile::FileProperty fp = EsmFile::FileProperty_FileName; if (name.contains ('/')) fp = EsmFile::FileProperty_FilePath; for (const EsmFile *file : mFiles) { if (name.compare(file->fileProperty (fp).toString(), Qt::CaseInsensitive) == 0) return file; } return nullptr; } QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *item) const { //workaround: non-const pointer cast for calls from outside contentmodel/contentselector EsmFile *non_const_file_ptr = const_cast(item); if (item) return index(mFiles.indexOf(non_const_file_ptr),0); return QModelIndex(); } Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsDropEnabled; const EsmFile *file = item(index.row()); if (!file) return Qt::NoItemFlags; //game files can always be checked if (file->isGameFile()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; Qt::ItemFlags returnFlags; // addon can be checked if its gamefile is // ... special case, addon with no dependency can be used with any gamefile. bool gamefileChecked = false; bool noGameFiles = true; for (const QString &fileName : file->gameFiles()) { for (QListIterator dependencyIter(mFiles); dependencyIter.hasNext(); dependencyIter.next()) { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. EsmFile* depFile = dependencyIter.peekNext(); if (!depFile->isGameFile() || depFile->fileName().compare(fileName, Qt::CaseInsensitive) != 0) continue; noGameFiles = false; if (isChecked(depFile->filePath())) { gamefileChecked = true; break; } } } if (gamefileChecked || noGameFiles) { returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } return returnFlags; } QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= mFiles.size()) return QVariant(); const EsmFile *file = item(index.row()); if (!file) return QVariant(); const int column = index.column(); switch (role) { case Qt::DecorationRole: { return isLoadOrderError(file) ? mWarningIcon : QVariant(); } case Qt::BackgroundRole: { if (isNew(file->fileName())) { return QVariant(QColor(Qt::green)); } return QVariant(); } case Qt::ForegroundRole: { if (isNew(file->fileName())) { return QVariant(QColor(Qt::black)); } return QVariant(); } case Qt::EditRole: case Qt::DisplayRole: { if (column >=0 && column <=EsmFile::FileProperty_GameFile) return file->fileProperty(static_cast(column)); return QVariant(); } case Qt::TextAlignmentRole: { switch (column) { case 0: case 1: return Qt::AlignLeft + Qt::AlignVCenter; case 2: case 3: return Qt::AlignRight + Qt::AlignVCenter; default: return Qt::AlignLeft + Qt::AlignVCenter; } } case Qt::ToolTipRole: { if (column != 0) return QVariant(); return toolTip(file); } case Qt::CheckStateRole: { if (file->isGameFile()) return QVariant(); return mCheckStates[file->filePath()]; } case Qt::UserRole: { if (file->isGameFile()) return ContentType_GameFile; else if (flags(index)) return ContentType_Addon; break; } case Qt::UserRole + 1: return isChecked(file->filePath()); } return QVariant(); } bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(!index.isValid()) return false; EsmFile *file = item(index.row()); QString fileName = file->fileName(); bool success = false; switch(role) { case Qt::EditRole: { QStringList list = value.toStringList(); for (int i = 0; i < EsmFile::FileProperty_GameFile; i++) file->setFileProperty(static_cast(i), list.at(i)); for (int i = EsmFile::FileProperty_GameFile; i < list.size(); i++) file->setFileProperty (EsmFile::FileProperty_GameFile, list.at(i)); emit dataChanged(index, index); success = true; } break; case Qt::UserRole+1: { success = (flags (index) & Qt::ItemIsEnabled); if (success) { success = setCheckState(file->filePath(), value.toBool()); emit dataChanged(index, index); } } break; case Qt::CheckStateRole: { int checkValue = value.toInt(); bool setState = false; if ((checkValue==Qt::Checked) && !isChecked(file->filePath())) { setState = true; success = true; } else if ((checkValue == Qt::Checked) && isChecked (file->filePath())) setState = true; else if (checkValue == Qt::Unchecked) setState = true; if (setState) { setCheckState(file->filePath(), success); emit dataChanged(index, index); checkForLoadOrderErrors(); } else return success; for (EsmFile *file2 : mFiles) { if (file2->gameFiles().contains(fileName, Qt::CaseInsensitive)) { QModelIndex idx = indexFromItem(file2); emit dataChanged(idx, idx); } } success = true; } break; } return success; } bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) { if (parent.isValid()) return false; beginInsertRows(parent, position, position+rows-1); { for (int row = 0; row < rows; ++row) mFiles.insert(position, new EsmFile); } endInsertRows(); return true; } bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) { if (parent.isValid()) return false; beginRemoveRows(parent, position, position+rows-1); { for (int row = 0; row < rows; ++row) delete mFiles.takeAt(position); } endRemoveRows(); // at this point we know that drag and drop has finished. checkForLoadOrderErrors(); return true; } Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const { return mDropActions; } QStringList ContentSelectorModel::ContentModel::mimeTypes() const { return mMimeTypes; } QMimeData *ContentSelectorModel::ContentModel::mimeData(const QModelIndexList &indexes) const { QByteArray encodedData; for (const QModelIndex &index : indexes) { if (!index.isValid()) continue; encodedData.append(item(index.row())->encodedData()); } QMimeData *mimeData = new QMimeData(); mimeData->setData(mMimeType, encodedData); return mimeData; } bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) return true; if (column > 0) return false; if (!data->hasFormat(mMimeType)) return false; int beginRow = rowCount(); if (row != -1) beginRow = row; else if (parent.isValid()) beginRow = parent.row(); QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString value; QStringList values; QStringList gamefiles; for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i) { stream >> value; values << value; } stream >> gamefiles; insertRows(beginRow, 1); QModelIndex idx = index(beginRow++, 0, QModelIndex()); setData(idx, QStringList() << values << gamefiles, Qt::EditRole); } return true; } void ContentSelectorModel::ContentModel::addFile(EsmFile *file) { beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); endInsertRows(); QModelIndex idx = index (mFiles.size() - 2, 0, QModelIndex()); emit dataChanged (idx, idx); } void ContentSelectorModel::ContentModel::addFiles(const QString &path, bool newfiles) { QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; if (mShowOMWScripts) filters << "*.omwscripts"; dir.setNameFilters(filters); dir.setSorting(QDir::Name); for (const QString &path2 : dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path2)); if (item(info.fileName())) continue; // Enabled by default in system openmw.cfg; shouldn't be shown in content list. if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) continue; if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { EsmFile *file = new EsmFile(path2); file->setDate(info.lastModified()); file->setFilePath(info.absoluteFilePath()); addFile(file); continue; } try { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); fileReader.setEncoder(&encoder); fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData())); EsmFile *file = new EsmFile(path2); for (std::vector::const_iterator itemIter = fileReader.getGameFiles().begin(); itemIter != fileReader.getGameFiles().end(); ++itemIter) file->addGameFile(QString::fromUtf8(itemIter->name.c_str())); file->setAuthor (QString::fromUtf8(fileReader.getAuthor().c_str())); file->setDate (info.lastModified()); file->setFormat (fileReader.getFormat()); file->setFilePath (info.absoluteFilePath()); file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); // HACK // Load order constraint of Bloodmoon.esm needing Tribunal.esm is missing // from the file supplied by Bethesda, so we have to add it ourselves if (file->fileName().compare("Bloodmoon.esm", Qt::CaseInsensitive) == 0) { file->addGameFile(QString::fromUtf8("Tribunal.esm")); } // Put the file in the table addFile(file); setNew(file->fileName(), newfiles); } catch(std::runtime_error &e) { // An error occurred while reading the .esp qWarning() << "Error reading addon file: " << e.what(); continue; } } } bool ContentSelectorModel::ContentModel::containsDataFiles(const QString &path) { QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); return dir.entryList().count() != 0; } void ContentSelectorModel::ContentModel::clearFiles() { const int filesCount = mFiles.count(); if (filesCount > 0) { beginRemoveRows(QModelIndex(), 0, filesCount - 1); mFiles.clear(); endRemoveRows(); } } QStringList ContentSelectorModel::ContentModel::gameFiles() const { QStringList gameFiles; for (const ContentSelectorModel::EsmFile *file : mFiles) { if (file->isGameFile()) { gameFiles.append(file->fileName()); } } return gameFiles; } void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); //Dependency sort std::unordered_set moved; for(int i = mFiles.size() - 1; i > 0;) { const auto file = mFiles.at(i); if(moved.find(file) == moved.end()) { int index = -1; for(int j = 0; j < i; ++j) { const QStringList& gameFiles = mFiles.at(j)->gameFiles(); if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) || (!mFiles.at(j)->isGameFile() && gameFiles.isEmpty() && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files { index = j; break; } } if(index >= 0) { mFiles.move(i, index); moved.insert(file); continue; } } --i; moved.clear(); } emit layoutChanged(); } bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const { if (mCheckStates.contains(filepath)) return (mCheckStates[filepath] == Qt::Checked); return false; } bool ContentSelectorModel::ContentModel::isEnabled (const QModelIndex& index) const { return (flags(index) & Qt::ItemIsEnabled); } bool ContentSelectorModel::ContentModel::isNew(const QString& filepath) const { if (mNewFiles.contains(filepath)) return mNewFiles[filepath]; return false; } void ContentSelectorModel::ContentModel::setNew(const QString &filepath, bool isNew) { if (filepath.isEmpty()) return; const EsmFile *file = item(filepath); if (!file) return; mNewFiles[filepath] = isNew; } bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const { return mPluginsWithLoadOrderError.contains(file->filePath()); } void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList) { mPluginsWithLoadOrderError.clear(); int previousPosition = -1; for (const QString &filepath : fileList) { if (setCheckState(filepath, true)) { // as necessary, move plug-ins in visible list to match sequence of supplied filelist const EsmFile* file = item(filepath); int filePosition = indexFromItem(file).row(); if (filePosition < previousPosition) { mFiles.move(filePosition, previousPosition); emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex())); } else { previousPosition = filePosition; } } } checkForLoadOrderErrors(); } void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() { for (int row = 0; row < mFiles.count(); ++row) { EsmFile* file = item(row); bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; if (isRowInError) { mPluginsWithLoadOrderError.insert(file->filePath()); } else { mPluginsWithLoadOrderError.remove(file->filePath()); } } } QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const { QList errors = QList(); for (const QString &dependentfileName : file->gameFiles()) { const EsmFile* dependentFile = item(dependentfileName); if (!dependentFile) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_MissingDependency, dependentfileName)); } else { if (!isChecked(dependentFile->filePath())) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); } if (row < indexFromItem(dependentFile).row()) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_LoadOrder, dependentfileName)); } } } return errors; } QString ContentSelectorModel::ContentModel::toolTip(const EsmFile *file) const { if (isLoadOrderError(file)) { QString text(""); int index = indexFromItem(item(file->filePath())).row(); for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) { text += "

"; text += error.toolTip(); text += "

"; } text += ("
"); text += file->toolTip(); return text; } else { return file->toolTip(); } } void ContentSelectorModel::ContentModel::refreshModel() { emit dataChanged (index(0,0), index(rowCount()-1,0)); } bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, bool checkState) { if (filepath.isEmpty()) return false; const EsmFile *file = item(filepath); if (!file) return false; Qt::CheckState state = Qt::Unchecked; if (checkState) state = Qt::Checked; mCheckStates[filepath] = state; emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) refreshModel(); //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. if (state == Qt::Checked) { for (const QString& upstreamName : file->gameFiles()) { const EsmFile *upstreamFile = item(upstreamName); if (!upstreamFile) continue; if (!isChecked(upstreamFile->filePath())) mCheckStates[upstreamFile->filePath()] = Qt::Checked; emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); } } //otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. if (state == Qt::Unchecked) { for (const EsmFile *downstreamFile : mFiles) { QFileInfo fileInfo(filepath); QString filename = fileInfo.fileName(); if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive)) { if (mCheckStates.contains(downstreamFile->filePath())) mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); } } } return true; } ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const { ContentFileList list; // TODO: // First search for game files and next addons, // so we get more or less correct game files vs addons order. for (EsmFile *file : mFiles) if (isChecked(file->filePath())) list << file; return list; } void ContentSelectorModel::ContentModel::uncheckAll() { emit layoutAboutToBeChanged(); mCheckStates.clear(); emit layoutChanged(); } openmw-openmw-0.48.0/components/contentselector/model/contentmodel.hpp000066400000000000000000000066041445372753700263460ustar00rootroot00000000000000#ifndef CONTENTMODEL_HPP #define CONTENTMODEL_HPP #include #include #include #include #include "loadordererror.hpp" namespace ContentSelectorModel { class EsmFile; typedef QList ContentFileList; enum ContentType { ContentType_GameFile, ContentType_Addon }; class ContentModel : public QAbstractTableModel { Q_OBJECT public: explicit ContentModel(QObject *parent, QIcon warningIcon, bool showOMWScripts); ~ContentModel(); void setEncoding(const QString &encoding); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; void addFiles(const QString &path, bool newfiles); void sortFiles(); bool containsDataFiles(const QString &path); void clearFiles(); QModelIndex indexFromItem(const EsmFile *item) const; const EsmFile *item(const QString &name) const; const EsmFile *item(int row) const; EsmFile *item(int row); QStringList gameFiles() const; bool isEnabled (const QModelIndex& index) const; bool isChecked(const QString &filepath) const; bool setCheckState(const QString &filepath, bool isChecked); bool isNew(const QString &filepath) const; void setNew(const QString &filepath, bool isChecked); void setContentList(const QStringList &fileList); ContentFileList checkedItems() const; void uncheckAll(); void refreshModel(); /// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues void checkForLoadOrderErrors(); private: void addFile(EsmFile *file); /// Checks a specific plug-in for load order errors /// \return all errors found for specific plug-in QList checkForLoadOrderErrors(const EsmFile *file, int row) const; /// \return true if plug-in has a Load Order error bool isLoadOrderError(const EsmFile *file) const; QString toolTip(const EsmFile *file) const; ContentFileList mFiles; QStringList mArchives; QHash mCheckStates; QHash mNewFiles; QSet mPluginsWithLoadOrderError; QString mEncoding; QIcon mWarningIcon; bool mShowOMWScripts; public: QString mMimeType; QStringList mMimeTypes; int mColumnCount; Qt::DropActions mDropActions; }; } #endif // CONTENTMODEL_HPP openmw-openmw-0.48.0/components/contentselector/model/esmfile.cpp000066400000000000000000000065571445372753700253010ustar00rootroot00000000000000#include "esmfile.hpp" #include int ContentSelectorModel::EsmFile::sPropertyCount = 7; QString ContentSelectorModel::EsmFile::sToolTip = QString("Author: %1
\ Version: %2
\ Modified: %3
\ Path:
%4
\
Description:
%5
\
Dependencies: %6
"); ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) : ModelItem(parent), mFileName(fileName), mFormat(0) {} void ContentSelectorModel::EsmFile::setFileName(const QString &fileName) { mFileName = fileName; } void ContentSelectorModel::EsmFile::setAuthor(const QString &author) { mAuthor = author; } void ContentSelectorModel::EsmFile::setDate(const QDateTime &modified) { mModified = modified; } void ContentSelectorModel::EsmFile::setFormat(int format) { mFormat = format; } void ContentSelectorModel::EsmFile::setFilePath(const QString &path) { mPath = path; } void ContentSelectorModel::EsmFile::setGameFiles(const QStringList &gamefiles) { mGameFiles = gamefiles; } void ContentSelectorModel::EsmFile::setDescription(const QString &description) { mDescription = description; } QByteArray ContentSelectorModel::EsmFile::encodedData() const { QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); stream << mFileName << mAuthor << QString::number(mFormat) << mModified.toString() << mPath << mDescription << mGameFiles; return encodedData; } bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) && (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive) || mFileName.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)); } QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const { switch (prop) { case FileProperty_FileName: return mFileName; break; case FileProperty_Author: return mAuthor; break; case FileProperty_Format: return mFormat; break; case FileProperty_DateModified: return mModified.toString(Qt::ISODate); break; case FileProperty_FilePath: return mPath; break; case FileProperty_Description: return mDescription; break; case FileProperty_GameFile: return mGameFiles; break; default: break; } return QVariant(); } void ContentSelectorModel::EsmFile::setFileProperty (const FileProperty prop, const QString &value) { switch (prop) { case FileProperty_FileName: mFileName = value; break; case FileProperty_Author: mAuthor = value; break; case FileProperty_Format: mFormat = value.toInt(); break; case FileProperty_DateModified: mModified = QDateTime::fromString(value); break; case FileProperty_FilePath: mPath = value; break; case FileProperty_Description: mDescription = value; break; case FileProperty_GameFile: mGameFiles << value; break; default: break; } } openmw-openmw-0.48.0/components/contentselector/model/esmfile.hpp000066400000000000000000000055641445372753700253030ustar00rootroot00000000000000#ifndef ESMFILE_HPP #define ESMFILE_HPP #include #include #include "modelitem.hpp" class QMimeData; namespace ContentSelectorModel { class EsmFile : public ModelItem { Q_OBJECT Q_PROPERTY(QString filename READ fileName) public: enum FileProperty { FileProperty_FileName = 0, FileProperty_Author = 1, FileProperty_Format = 2, FileProperty_DateModified = 3, FileProperty_FilePath = 4, FileProperty_Description = 5, FileProperty_GameFile = 6 }; EsmFile(QString fileName = QString(), ModelItem *parent = nullptr); // EsmFile(const EsmFile &); ~EsmFile() {} void setFileProperty (const FileProperty prop, const QString &value); void setFileName(const QString &fileName); void setAuthor(const QString &author); void setSize(const int size); void setDate(const QDateTime &modified); void setFormat(const int format); void setFilePath(const QString &path); void setGameFiles(const QStringList &gameFiles); void setDescription(const QString &description); inline void addGameFile (const QString &name) {mGameFiles.append(name); } QVariant fileProperty (const FileProperty prop) const; inline QString fileName() const { return mFileName; } inline QString author() const { return mAuthor; } inline QDateTime modified() const { return mModified; } inline float format() const { return mFormat; } inline QString filePath() const { return mPath; } /// @note Contains file names, not paths. inline const QStringList &gameFiles() const { return mGameFiles; } inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) .arg(mFormat) .arg(mModified.toString(Qt::ISODate)) .arg(mPath) .arg(mDescription) .arg(mGameFiles.join(", ")); } bool isGameFile() const; QByteArray encodedData() const; public: static int sPropertyCount; static QString sToolTip; private: QString mFileName; QString mAuthor; QDateTime mModified; int mFormat; QString mPath; QStringList mGameFiles; QString mDescription; QString mToolTip; }; } #endif openmw-openmw-0.48.0/components/contentselector/model/loadordererror.cpp000066400000000000000000000006701445372753700266700ustar00rootroot00000000000000#include "loadordererror.hpp" #include QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder] = { QString("Unable to find dependent file: %1"), QString("Dependent file needs to be active: %1"), QString("This file needs to load after %1") }; QString ContentSelectorModel::LoadOrderError::toolTip() const { assert(mErrorCode); return sErrorToolTips[mErrorCode - 1].arg(mFileName); } openmw-openmw-0.48.0/components/contentselector/model/loadordererror.hpp000066400000000000000000000017561445372753700267030ustar00rootroot00000000000000#ifndef LOADORDERERROR_HPP #define LOADORDERERROR_HPP #include namespace ContentSelectorModel { /// \brief Details of a suspected Load Order problem a plug-in will have. This is basically a POD. class LoadOrderError { public: enum ErrorCode { ErrorCode_None = 0, ErrorCode_MissingDependency = 1, ErrorCode_InactiveDependency = 2, ErrorCode_LoadOrder = 3 }; inline LoadOrderError() : mErrorCode(ErrorCode_None) {} inline LoadOrderError(ErrorCode errorCode, QString fileName) : mErrorCode(errorCode), mFileName(fileName) {} inline ErrorCode errorCode() const { return mErrorCode; } inline QString fileName() const { return mFileName; } QString toolTip() const; private: ErrorCode mErrorCode; QString mFileName; static QString sErrorToolTips[ErrorCode_LoadOrder]; }; } #endif // LOADORDERERROR_HPP openmw-openmw-0.48.0/components/contentselector/model/modelitem.cpp000066400000000000000000000026121445372753700256200ustar00rootroot00000000000000#include "modelitem.hpp" ContentSelectorModel::ModelItem::ModelItem(ModelItem *parent) : mParentItem(parent) { } /* ContentSelectorModel::ModelItem::ModelItem(const ModelItem *parent) // : mParentItem(parent) { } */ ContentSelectorModel::ModelItem::~ModelItem() { qDeleteAll(mChildItems); } ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::parent() const { return mParentItem; } bool ContentSelectorModel::ModelItem::hasFormat(const QString &mimetype) const { if (mimetype == "application/omwcontent") return true; return QMimeData::hasFormat(mimetype); } int ContentSelectorModel::ModelItem::row() const { if (mParentItem) return 1; //return mParentItem->childRow(const_cast(this)); //return mParentItem->mChildItems.indexOf(const_cast(this)); return -1; } int ContentSelectorModel::ModelItem::childCount() const { return mChildItems.count(); } int ContentSelectorModel::ModelItem::childRow(ModelItem *child) const { Q_ASSERT(child); return mChildItems.indexOf(child); } ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::child(int row) { return mChildItems.value(row); } void ContentSelectorModel::ModelItem::appendChild(ModelItem *item) { mChildItems.append(item); } void ContentSelectorModel::ModelItem::removeChild(int row) { mChildItems.removeAt(row); } openmw-openmw-0.48.0/components/contentselector/model/modelitem.hpp000066400000000000000000000014301445372753700256220ustar00rootroot00000000000000#ifndef MODELITEM_HPP #define MODELITEM_HPP #include #include namespace ContentSelectorModel { class ModelItem : public QMimeData { Q_OBJECT public: ModelItem(ModelItem *parent = nullptr); //ModelItem(const ModelItem *parent = 0); ~ModelItem(); ModelItem *parent() const; int row() const; int childCount() const; int childRow(ModelItem *child) const; ModelItem *child(int row); void appendChild(ModelItem *child); void removeChild(int row); bool hasFormat(const QString &mimetype) const override; //virtual bool acceptChild(ModelItem *child); protected: ModelItem *mParentItem; QList mChildItems; }; } #endif openmw-openmw-0.48.0/components/contentselector/model/naturalsort.cpp000066400000000000000000000066751445372753700262340ustar00rootroot00000000000000/* * This file contains code found in the QtGui module of the Qt Toolkit. * See Qt's qfilesystemmodel source files for more information */ #include "naturalsort.hpp" static inline QChar getNextChar(const QString &s, int location) { return (location < s.length()) ? s.at(location) : QChar(); } /*! * Natural number sort, skips spaces. * * Examples: * 1, 2, 10, 55, 100 * 01.jpg, 2.jpg, 10.jpg * * Note on the algorithm: * Only as many characters as necessary are looked at and at most they all * are looked at once. * * Slower then QString::compare() (of course) */ int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) { for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) { // skip spaces, tabs and 0's QChar c1 = getNextChar(s1, l1); while (c1.isSpace()) c1 = getNextChar(s1, ++l1); QChar c2 = getNextChar(s2, l2); while (c2.isSpace()) c2 = getNextChar(s2, ++l2); if (c1.isDigit() && c2.isDigit()) { while (c1.digitValue() == 0) c1 = getNextChar(s1, ++l1); while (c2.digitValue() == 0) c2 = getNextChar(s2, ++l2); int lookAheadLocation1 = l1; int lookAheadLocation2 = l2; int currentReturnValue = 0; // find the last digit, setting currentReturnValue as we go if it isn't equal for ( QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2) ) { bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); if (!is1ADigit && !is2ADigit) break; if (!is1ADigit) return -1; if (!is2ADigit) return 1; if (currentReturnValue == 0) { if (lookAhead1 < lookAhead2) { currentReturnValue = -1; } else if (lookAhead1 > lookAhead2) { currentReturnValue = 1; } } } if (currentReturnValue != 0) return currentReturnValue; } if (cs == Qt::CaseInsensitive) { if (!c1.isLower()) c1 = c1.toLower(); if (!c2.isLower()) c2 = c2.toLower(); } int r = QString::localeAwareCompare(c1, c2); if (r < 0) return -1; if (r > 0) return 1; } // The two strings are the same (02 == 2) so fall back to the normal sort return QString::compare(s1, s2, cs); } bool naturalSortLessThanCS( const QString &left, const QString &right ) { return (naturalCompare( left, right, Qt::CaseSensitive ) < 0); } bool naturalSortLessThanCI( const QString &left, const QString &right ) { return (naturalCompare( left, right, Qt::CaseInsensitive ) < 0); } bool naturalSortGreaterThanCS( const QString &left, const QString &right ) { return (naturalCompare( left, right, Qt::CaseSensitive ) > 0); } bool naturalSortGreaterThanCI( const QString &left, const QString &right ) { return (naturalCompare( left, right, Qt::CaseInsensitive ) > 0); } openmw-openmw-0.48.0/components/contentselector/model/naturalsort.hpp000066400000000000000000000006031445372753700262220ustar00rootroot00000000000000#ifndef NATURALSORT_H #define NATURALSORT_H #include bool naturalSortLessThanCS( const QString &left, const QString &right ); bool naturalSortLessThanCI( const QString &left, const QString &right ); bool naturalSortGreaterThanCS( const QString &left, const QString &right ); bool naturalSortGreaterThanCI( const QString &left, const QString &right ); #endif openmw-openmw-0.48.0/components/contentselector/view/000077500000000000000000000000001445372753700230065ustar00rootroot00000000000000openmw-openmw-0.48.0/components/contentselector/view/combobox.cpp000066400000000000000000000021201445372753700253150ustar00rootroot00000000000000#include #include #include #include "combobox.hpp" ContentSelectorView::ComboBox::ComboBox(QWidget *parent) : QComboBox(parent) { mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore setValidator(mValidator); setEditable(true); setCompleter(nullptr); setEnabled (true); setInsertPolicy(QComboBox::NoInsert); } void ContentSelectorView::ComboBox::paintEvent(QPaintEvent *) { QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt; initStyleOption(&opt); painter.drawComplexControl(QStyle::CC_ComboBox, opt); // draw the icon and text if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected opt.currentText = mPlaceholderText; painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } void ContentSelectorView::ComboBox::setPlaceholderText(const QString &text) { mPlaceholderText = text; } openmw-openmw-0.48.0/components/contentselector/view/combobox.hpp000066400000000000000000000007711445372753700253340ustar00rootroot00000000000000#ifndef COMBOBOX_HPP #define COMBOBOX_HPP #include class QString; class QRegExpValidator; namespace ContentSelectorView { class ComboBox : public QComboBox { Q_OBJECT public: explicit ComboBox (QWidget *parent = nullptr); void setPlaceholderText(const QString &text); private: QString mPlaceholderText; protected: void paintEvent(QPaintEvent *) override; QRegExpValidator *mValidator; }; } #endif // COMBOBOX_HPP openmw-openmw-0.48.0/components/contentselector/view/contentselector.cpp000066400000000000000000000222311445372753700267250ustar00rootroot00000000000000#include "contentselector.hpp" #include #include #include #include #include ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, bool showOMWScripts) : QObject(parent) { ui.setupUi(parent); ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); buildContentModel(showOMWScripts); buildGameFileView(); buildAddonView(); } void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } void ContentSelectorView::ContentSelector::buildGameFileView() { ui.gameFileView->addItem(""); ui.gameFileView->setVisible(true); connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); ui.gameFileView->setCurrentIndex(0); } class AddOnProxyModel : public QSortFilterProxyModel { public: explicit AddOnProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { static const QString ContentTypeAddon = QString::number((int)ContentSelectorModel::ContentType_Addon); QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent); const QString userRole = sourceModel()->data(nameIndex, Qt::UserRole).toString(); return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent) && userRole == ContentTypeAddon; } }; void ContentSelectorView::ContentSelector::buildAddonView() { ui.addonView->setVisible (true); mAddonProxyModel = new AddOnProxyModel(this); mAddonProxyModel->setFilterRegExp(searchFilter()->text()); mAddonProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mAddonProxyModel->setDynamicSortFilter (true); mAddonProxyModel->setSourceModel (mContentModel); connect(ui.searchFilter, SIGNAL(textEdited(QString)), mAddonProxyModel, SLOT(setFilterWildcard(QString))); connect(ui.searchFilter, SIGNAL(textEdited(QString)), this, SLOT(slotSearchFilterTextChanged(QString))); ui.addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(activated(const QModelIndex&)), this, SLOT(slotAddonTableItemActivated(const QModelIndex&))); connect(mContentModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex))); buildContextMenu(); } void ContentSelectorView::ContentSelector::buildContextMenu() { ui.addonView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.addonView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotShowContextMenu(const QPoint&))); mContextMenu = new QMenu(ui.addonView); mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); } void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList) { clearCheckStates(); for (const QString &filepath : fileList) { const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); if (file && file->isGameFile()) { setGameFile (filepath); break; } } setContentList(fileList); } void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) { int index = 0; if (!filename.isEmpty()) { const ContentSelectorModel::EsmFile *file = mContentModel->item (filename); index = ui.gameFileView->findText (file->fileName()); //verify that the current index is also checked in the model if (!mContentModel->setCheckState(filename, true)) { //throw error in case file not found? return; } } ui.gameFileView->setCurrentIndex(index); } void ContentSelectorView::ContentSelector::clearCheckStates() { mContentModel->uncheckAll(); } void ContentSelectorView::ContentSelector::setEncoding(const QString &encoding) { mContentModel->setEncoding(encoding); } void ContentSelectorView::ContentSelector::setContentList(const QStringList &list) { if (list.isEmpty()) { slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); } else mContentModel->setContentList(list); } ContentSelectorModel::ContentFileList ContentSelectorView::ContentSelector::selectedFiles() const { if (!mContentModel) return ContentSelectorModel::ContentFileList(); return mContentModel->checkedItems(); } void ContentSelectorView::ContentSelector::addFiles(const QString &path, bool newfiles) { mContentModel->addFiles(path, newfiles); // add any game files to the combo box for (const QString& gameFileName : mContentModel->gameFiles()) { if (ui.gameFileView->findText(gameFileName) == -1) { ui.gameFileView->addItem(gameFileName); } } if (ui.gameFileView->currentIndex() != 0) ui.gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); mContentModel->checkForLoadOrderErrors(); } void ContentSelectorView::ContentSelector::sortFiles() { mContentModel->sortFiles(); } bool ContentSelectorView::ContentSelector::containsDataFiles(const QString &path) { return mContentModel->containsDataFiles(path); } void ContentSelectorView::ContentSelector::clearFiles() { mContentModel->clearFiles(); } QString ContentSelectorView::ContentSelector::currentFile() const { QModelIndex currentIdx = ui.addonView->currentIndex(); if (!currentIdx.isValid() && ui.gameFileView->currentIndex() > 0) return ui.gameFileView->currentText(); QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); return mContentModel->data(idx, Qt::DisplayRole).toString(); } void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) { static int oldIndex = -1; if (index != oldIndex) { if (oldIndex > -1) { setGameFileSelected(oldIndex, false); } oldIndex = index; setGameFileSelected(index, true); mContentModel->checkForLoadOrderErrors(); } emit signalCurrentGamefileIndexChanged (index); } void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected) { QString fileName = ui.gameFileView->itemText(index); const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName); if (file != nullptr) { QModelIndex index2(mContentModel->indexFromItem(file)); mContentModel->setData(index2, selected, Qt::UserRole + 1); } } void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex &index) { // toggles check state when an AddOn file is double clicked or activated by keyboard QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index); if (!mContentModel->isEnabled (sourceIndex)) return; Qt::CheckState checkState = Qt::Unchecked; if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() == Qt::Unchecked) checkState = Qt::Checked; mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); } void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos) { QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos); mContextMenu->exec(globalPos); } void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) { Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState) { mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); } } } void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(false); } void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); } void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() { QClipboard *clipboard = QApplication::clipboard(); QString filepaths; for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) { int row = mAddonProxyModel->mapToSource(index).row(); const ContentSelectorModel::EsmFile *file = mContentModel->item(row); filepaths += file->filePath() + "\n"; } if (!filepaths.isEmpty()) { clipboard->setText(filepaths); } } void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText) { ui.addonView->setDragEnabled(newText.isEmpty()); } openmw-openmw-0.48.0/components/contentselector/view/contentselector.hpp000066400000000000000000000046471445372753700267450ustar00rootroot00000000000000#ifndef CONTENTSELECTOR_HPP #define CONTENTSELECTOR_HPP #include #include "ui_contentselector.h" #include class QSortFilterProxyModel; namespace ContentSelectorView { class ContentSelector : public QObject { Q_OBJECT QMenu *mContextMenu; protected: ContentSelectorModel::ContentModel *mContentModel; QSortFilterProxyModel *mAddonProxyModel; public: explicit ContentSelector(QWidget *parent = nullptr, bool showOMWScripts = false); QString currentFile() const; void addFiles(const QString &path, bool newfiles = false); void sortFiles(); bool containsDataFiles(const QString &path); void clearFiles(); void setProfileContent (const QStringList &fileList); void clearCheckStates(); void setEncoding (const QString &encoding); void setContentList(const QStringList &list); ContentSelectorModel::ContentFileList selectedFiles() const; void setGameFile (const QString &filename = QString("")); bool isGamefileSelected() const { return ui.gameFileView->currentIndex() > 0; } QWidget *uiWidget() const { return ui.contentGroupBox; } QToolButton *refreshButton() const { return ui.refreshButton; } QLineEdit *searchFilter() const { return ui.searchFilter; } private: Ui::ContentSelector ui; void buildContentModel(bool showOMWScripts); void buildGameFileView(); void buildAddonView(); void buildContextMenu(); void setGameFileSelected(int index, bool selected); void setCheckStateForMultiSelectedItems(bool checked); signals: void signalCurrentGamefileIndexChanged (int); void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); void signalSelectedFilesChanged(QStringList selectedFiles); private slots: void slotCurrentGameFileIndexChanged(int index); void slotAddonTableItemActivated(const QModelIndex& index); void slotShowContextMenu(const QPoint& pos); void slotCheckMultiSelectedItems(); void slotUncheckMultiSelectedItems(); void slotCopySelectedItemsPaths(); void slotSearchFilterTextChanged(const QString& newText); }; } #endif // CONTENTSELECTOR_HPP openmw-openmw-0.48.0/components/crashcatcher/000077500000000000000000000000001445372753700212535ustar00rootroot00000000000000openmw-openmw-0.48.0/components/crashcatcher/crashcatcher.cpp000066400000000000000000000365701445372753700244240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace bfs = boost::filesystem; #include #ifdef __linux__ #include #include #ifndef PR_SET_PTRACER #define PR_SET_PTRACER 0x59616d61 #endif #elif defined (__APPLE__) || defined (__FreeBSD__) || defined(__OpenBSD__) #include #endif #if defined(__APPLE__) #include #include #endif #if defined(__FreeBSD__) #include #include #endif #include "crashcatcher.hpp" static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; static const char pipe_err[] = "!!! Failed to create pipe\n"; static const char fork_err[] = "!!! Failed to fork debug process\n"; static const char exec_err[] = "!!! Failed to exec debug process\n"; #ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ # define PATH_MAX 256 #endif static char argv0[PATH_MAX]; static struct { int signum; pid_t pid; int has_siginfo; siginfo_t siginfo; char buf[1024]; } crash_info; static const struct { const char *name; int signum; } signals[] = { { "Segmentation fault", SIGSEGV }, { "Illegal instruction", SIGILL }, { "FPU exception", SIGFPE }, { "System BUS error", SIGBUS }, { nullptr, 0 } }; static const struct { int code; const char *name; } sigill_codes[] = { #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) { ILL_ILLOPC, "Illegal opcode" }, { ILL_ILLOPN, "Illegal operand" }, { ILL_ILLADR, "Illegal addressing mode" }, { ILL_ILLTRP, "Illegal trap" }, { ILL_PRVOPC, "Privileged opcode" }, { ILL_PRVREG, "Privileged register" }, { ILL_COPROC, "Coprocessor error" }, { ILL_BADSTK, "Internal stack error" }, #endif { 0, nullptr } }; static const struct { int code; const char *name; } sigfpe_codes[] = { { FPE_INTDIV, "Integer divide by zero" }, { FPE_INTOVF, "Integer overflow" }, { FPE_FLTDIV, "Floating point divide by zero" }, { FPE_FLTOVF, "Floating point overflow" }, { FPE_FLTUND, "Floating point underflow" }, { FPE_FLTRES, "Floating point inexact result" }, { FPE_FLTINV, "Floating point invalid operation" }, { FPE_FLTSUB, "Subscript out of range" }, { 0, nullptr } }; static const struct { int code; const char *name; } sigsegv_codes[] = { #ifndef __FreeBSD__ { SEGV_MAPERR, "Address not mapped to object" }, { SEGV_ACCERR, "Invalid permissions for mapped object" }, #endif { 0, nullptr } }; static const struct { int code; const char *name; } sigbus_codes[] = { #ifndef __FreeBSD__ { BUS_ADRALN, "Invalid address alignment" }, { BUS_ADRERR, "Non-existent physical address" }, { BUS_OBJERR, "Object specific hardware error" }, #endif { 0, nullptr } }; static int (*cc_user_info)(char*, char*); static void gdb_info(pid_t pid) { char respfile[64]; FILE *f; int fd; /* * Create a temp file to put gdb commands into. * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. * Modern systems implement it and suggest to do not touch masks in multithreaded applications. * So CoverityScan warning is valid only for ancient versions of stdlib. */ strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); #ifdef __COVERITY__ umask(0600); #endif if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr) { fprintf(f, "attach %d\n" "shell echo \"\"\n" "shell echo \"* Loaded Libraries\"\n" "info sharedlibrary\n" "shell echo \"\"\n" "shell echo \"* Threads\"\n" "info threads\n" "shell echo \"\"\n" "shell echo \"* FPU Status\"\n" "info float\n" "shell echo \"\"\n" "shell echo \"* Registers\"\n" "info registers\n" "shell echo \"\"\n" "shell echo \"* Backtrace\"\n" "thread apply all backtrace full 1000\n" "detach\n" "quit\n", pid); fclose(f); /* Run gdb and print process info. */ char cmd_buf[128]; snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); printf("Executing: %s\n", cmd_buf); fflush(stdout); int ret = system(cmd_buf); if (ret != 0) printf("\nFailed to create a crash report. Please make sure that 'gdb' is installed and present in PATH then crash again." "\nCurrent PATH: %s\n", getenv("PATH")); fflush(stdout); /* Clean up */ if (remove(respfile) != 0) Log(Debug::Warning) << "Warning: can not remove file '" << respfile << "': " << std::strerror(errno); } else { /* Error creating temp file */ if(fd >= 0) { if (close(fd) != 0) Log(Debug::Warning) << "Warning: can not close file '" << respfile << "': " << std::strerror(errno); else if (remove(respfile) != 0) Log(Debug::Warning) << "Warning: can not remove file '" << respfile << "': " << std::strerror(errno); } printf("!!! Could not create gdb command file\n"); } fflush(stdout); } static void sys_info(void) { #ifdef __unix__ struct utsname info; if(uname(&info)) printf("!!! Failed to get system information\n"); else printf("System: %s %s %s %s %s\n", info.sysname, info.nodename, info.release, info.version, info.machine); fflush(stdout); #endif } static size_t safe_write(int fd, const void *buf, size_t len) { size_t ret = 0; while(ret < len) { ssize_t rem; if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) { if(errno == EINTR) continue; break; } ret += rem; } return ret; } static void crash_catcher(int signum, siginfo_t *siginfo, void *context) { //ucontext_t *ucontext = (ucontext_t*)context; pid_t dbg_pid; int fd[2]; /* Make sure the effective uid is the real uid */ if(getuid() != geteuid()) { raise(signum); return; } safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); if(pipe(fd) == -1) { safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); raise(signum); return; } crash_info.signum = signum; crash_info.pid = getpid(); crash_info.has_siginfo = !!siginfo; if(siginfo) crash_info.siginfo = *siginfo; if(cc_user_info) cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); /* Fork off to start a crash handler */ switch((dbg_pid=fork())) { /* Error */ case -1: safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); raise(signum); return; case 0: dup2(fd[0], STDIN_FILENO); close(fd[0]); close(fd[1]); execl(argv0, argv0, crash_switch, nullptr); safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); _exit(1); default: #ifdef __linux__ prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); #endif safe_write(fd[1], &crash_info, sizeof(crash_info)); close(fd[0]); close(fd[1]); /* Wait; we'll be killed when gdb is done */ do { int status; if(waitpid(dbg_pid, &status, 0) == dbg_pid && (WIFEXITED(status) || WIFSIGNALED(status))) { /* The debug process died before it could kill us */ raise(signum); break; } } while(1); } } static void crash_handler(const char *logfile) { const char *sigdesc = ""; int i; if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) { fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); exit(1); } /* Get the signal description */ for(i = 0;signals[i].name;++i) { if(signals[i].signum == crash_info.signum) { sigdesc = signals[i].name; break; } } if(crash_info.has_siginfo) { switch(crash_info.signum) { case SIGSEGV: for(i = 0;sigsegv_codes[i].name;++i) { if(sigsegv_codes[i].code == crash_info.siginfo.si_code) { sigdesc = sigsegv_codes[i].name; break; } } break; case SIGFPE: for(i = 0;sigfpe_codes[i].name;++i) { if(sigfpe_codes[i].code == crash_info.siginfo.si_code) { sigdesc = sigfpe_codes[i].name; break; } } break; case SIGILL: for(i = 0;sigill_codes[i].name;++i) { if(sigill_codes[i].code == crash_info.siginfo.si_code) { sigdesc = sigill_codes[i].name; break; } } break; case SIGBUS: for(i = 0;sigbus_codes[i].name;++i) { if(sigbus_codes[i].code == crash_info.siginfo.si_code) { sigdesc = sigbus_codes[i].name; break; } } break; } } fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); if(crash_info.has_siginfo) fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); fputc('\n', stderr); if(logfile) { /* Create crash log file and redirect shell output to it */ if(freopen(logfile, "wa", stdout) != stdout) { fprintf(stderr, "!!! Could not create %s following signal\n", logfile); exit(1); } fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); printf("*** Fatal Error ***\n" "%s (signal %i)\n", sigdesc, crash_info.signum); if(crash_info.has_siginfo) printf("Address: %p\n", crash_info.siginfo.si_addr); fputc('\n', stdout); fflush(stdout); } sys_info(); crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; printf("%s\n", crash_info.buf); fflush(stdout); if(crash_info.pid > 0) { gdb_info(crash_info.pid); kill(crash_info.pid, SIGKILL); } // delay between killing of the crashed process and showing the message box to // work around occasional X server lock-up. this can only be a bug in X11 since // even faulty applications shouldn't be able to freeze the X server. usleep(100000); if(logfile) { std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } exit(0); } static void getExecPath(char **argv) { #if defined (__FreeBSD__) int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; size_t size = sizeof(argv0); if (sysctl(mib, 4, argv0, &size, nullptr, 0) == 0) return; #endif #if defined (__APPLE__) if(proc_pidpath(getpid(), argv0, sizeof(argv0)) > 0) return; #endif int cwdlen; const char *statusPaths[] = {"/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file"}; memset(argv0, 0, sizeof(argv0)); for(const char *path : statusPaths) { if (readlink(path, argv0, sizeof(argv0)) != -1) return; } if(argv[0][0] == '/') snprintf(argv0, sizeof(argv0), "%s", argv[0]); else if (getcwd(argv0, sizeof(argv0)) != nullptr) { cwdlen = strlen(argv0); snprintf(argv0+cwdlen, sizeof(argv0)-cwdlen, "/%s", argv[0]); } } int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) { struct sigaction sa; stack_t altss; int retval; if(argc == 2 && strcmp(argv[1], crash_switch) == 0) crash_handler(logfile); cc_user_info = user_info; getExecPath(argv); /* Set an alternate signal stack so SIGSEGVs caused by stack overflows * still run */ static char* altstack = new char [SIGSTKSZ]; altss.ss_sp = altstack; altss.ss_flags = 0; altss.ss_size = SIGSTKSZ; sigaltstack(&altss, nullptr); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_catcher; sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); retval = 0; while(num_signals--) { if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT && *signals != SIGBUS) || sigaction(*signals, &sa, nullptr) == -1) { *signals = 0; retval = -1; } ++signals; } return retval; } static bool is_debugger_present() { #if defined (__linux__) bfs::path procstatus = bfs::path("/proc/self/status"); if (bfs::exists(procstatus)) { bfs::ifstream file((procstatus)); while (!file.eof()) { std::string word; file >> word; if (word == "TracerPid:") { file >> word; return word != "0"; } } } return false; #elif defined(__APPLE__) int junk; int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. return (info.kp_proc.p_flag & P_TRACED) != 0; #elif defined(__FreeBSD__) struct kinfo_proc info; size_t size = sizeof(info); int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) == 0) return (info.ki_flag & P_TRACED) != 0; else perror("Failed to retrieve process info"); return false; #else return false; #endif } void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) { if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1) { Log(Debug::Warning) << "Installing crash handler failed"; } else Log(Debug::Info) << "Crash handler installed"; } } openmw-openmw-0.48.0/components/crashcatcher/crashcatcher.hpp000066400000000000000000000010421445372753700244130ustar00rootroot00000000000000#ifndef CRASHCATCHER_H #define CRASHCATCHER_H #include #if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix)) #define USE_CRASH_CATCHER 1 #else #define USE_CRASH_CATCHER 0 #endif constexpr char crash_switch[] = "--cc-handle-crash"; #if USE_CRASH_CATCHER extern void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath); #else inline void crashCatcherInstall(int, char **, const std::string &crashLogPath) { } #endif #endif openmw-openmw-0.48.0/components/crashcatcher/windows_crashcatcher.cpp000066400000000000000000000166051445372753700261730ustar00rootroot00000000000000#include "windows_crashcatcher.hpp" #include #include #include #include #include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" #include "windowscrashdumppathhelpers.hpp" #include namespace Crash { namespace { template void writePathToShm(T(&buffer)[N], const std::string& path) { memset(buffer, 0, sizeof(buffer)); size_t length = path.length(); if (length >= sizeof(buffer)) length = sizeof(buffer) - 1; strncpy(buffer, path.c_str(), length); } } HANDLE duplicateHandle(HANDLE handle) { HANDLE duplicate; if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &duplicate, 0, TRUE, DUPLICATE_SAME_ACCESS)) { throw std::runtime_error("Crash monitor could not duplicate handle"); } return duplicate; } CrashCatcher* CrashCatcher::sInstance = nullptr; CrashCatcher::CrashCatcher(int argc, char** argv, const std::string& dumpPath, const std::string& crashDumpName, const std::string& freezeDumpName) { assert(sInstance == nullptr); // don't allow two instances sInstance = this; HANDLE shmHandle = nullptr; for (int i=0; i= argc - 1) throw std::runtime_error("Crash monitor is missing the SHM handle argument"); sscanf(argv[i + 1], "%p", &shmHandle); break; } if (!shmHandle) { setupIpc(); startMonitorProcess(dumpPath, crashDumpName, freezeDumpName); installHandler(); } else { CrashMonitor(shmHandle).run(); exit(0); } } CrashCatcher::~CrashCatcher() { sInstance = nullptr; if (mShm && mSignalMonitorEvent) { shmLock(); mShm->mEvent = CrashSHM::Event::Shutdown; shmUnlock(); SetEvent(mSignalMonitorEvent); } if (mShmHandle) CloseHandle(mShmHandle); } void CrashCatcher::updateDumpPath(const std::string& dumpPath) { shmLock(); writePathToShm(mShm->mStartup.mDumpDirectoryPath, dumpPath); shmUnlock(); } void CrashCatcher::updateDumpNames(const std::string& crashDumpName, const std::string& freezeDumpName) { shmLock(); writePathToShm(mShm->mStartup.mCrashDumpFileName, crashDumpName); writePathToShm(mShm->mStartup.mFreezeDumpFileName, freezeDumpName); shmUnlock(); } void CrashCatcher::setupIpc() { SECURITY_ATTRIBUTES attributes; ZeroMemory(&attributes, sizeof(attributes)); attributes.bInheritHandle = TRUE; mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL); if (mShmHandle == nullptr) throw std::runtime_error("Failed to allocate crash catcher shared memory"); mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); if (mShm == nullptr) throw std::runtime_error("Failed to map crash catcher shared memory"); mShmMutex = CreateMutexW(&attributes, FALSE, NULL); if (mShmMutex == nullptr) throw std::runtime_error("Failed to create crash catcher shared memory mutex"); } void CrashCatcher::shmLock() { if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) throw std::runtime_error("SHM lock timed out"); } void CrashCatcher::shmUnlock() { ReleaseMutex(mShmMutex); } void CrashCatcher::waitMonitor() { if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0) throw std::runtime_error("Waiting for monitor failed"); } void CrashCatcher::signalMonitor() { SetEvent(mSignalMonitorEvent); } void CrashCatcher::installHandler() { SetUnhandledExceptionFilter(vectoredExceptionHandler); } void CrashCatcher::startMonitorProcess(const std::string& dumpPath, const std::string& crashDumpName, const std::string& freezeDumpName) { std::wstring executablePath; DWORD copied = 0; do { executablePath.resize(executablePath.size() + MAX_PATH); copied = GetModuleFileNameW(nullptr, executablePath.data(), static_cast(executablePath.size())); } while (copied >= executablePath.size()); executablePath.resize(copied); writePathToShm(mShm->mStartup.mDumpDirectoryPath, dumpPath); writePathToShm(mShm->mStartup.mCrashDumpFileName, crashDumpName); writePathToShm(mShm->mStartup.mFreezeDumpFileName, freezeDumpName); // note that we don't need to lock the SHM here, the other process has not started yet mShm->mEvent = CrashSHM::Event::Startup; mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex); mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess()); mShm->mStartup.mAppMainThreadId = GetThreadId(GetCurrentThread()); mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent); mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent); std::wstringstream ss; ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle); std::wstring arguments(ss.str()); STARTUPINFOW si; ZeroMemory(&si, sizeof(si)); PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) throw std::runtime_error("Could not start crash monitor process"); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); waitMonitor(); } LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info) { switch (info->ExceptionRecord->ExceptionCode) { case EXCEPTION_SINGLE_STEP: case EXCEPTION_BREAKPOINT: case DBG_PRINTEXCEPTION_C: return EXCEPTION_EXECUTE_HANDLER; } if (!sInstance) return EXCEPTION_EXECUTE_HANDLER; sInstance->handleVectoredException(info); _Exit(1); } void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info) { shmLock(); mShm->mEvent = CrashSHM::Event::Crashed; mShm->mCrashed.mThreadId = GetCurrentThreadId(); mShm->mCrashed.mContext = *info->ContextRecord; mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord; shmUnlock(); signalMonitor(); // must remain until monitor has finished waitMonitor(); std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + getCrashDumpPath(*mShm) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } } // namespace Crash openmw-openmw-0.48.0/components/crashcatcher/windows_crashcatcher.hpp000066400000000000000000000051571445372753700262000ustar00rootroot00000000000000#ifndef WINDOWS_CRASHCATCHER_HPP #define WINDOWS_CRASHCATCHER_HPP #include #include #include namespace Crash { // The implementation spawns the current executable as a monitor process which waits // for a global synchronization event which is sent when the parent process crashes. // The monitor process then extracts crash information from the parent process while // the parent process waits for the monitor process to finish. The crashed process // quits and the monitor writes the crash information to a file. // // To detect unexpected shutdowns of the application which are not handled by the // crash handler, the monitor periodically checks the exit code of the parent // process and exits if it does not return STILL_ACTIVE. You can test this by closing // the main openmw process in task manager. static constexpr const int CrashCatcherTimeout = 2500; struct CrashSHM; class CrashCatcher final { public: static CrashCatcher* instance() { return sInstance; } CrashCatcher(int argc, char** argv, const std::string& dumpPath, const std::string& crashDumpName, const std::string& freezeDumpName); ~CrashCatcher(); void updateDumpPath(const std::string& dumpPath); void updateDumpNames(const std::string& crashDumpName, const std::string& freezeDumpName); private: static CrashCatcher* sInstance; // mapped SHM area CrashSHM* mShm = nullptr; // the handle is allocated by the catcher and passed to the monitor // process via the command line which maps the SHM and sends / receives // events through it HANDLE mShmHandle = nullptr; // mutex which guards SHM area HANDLE mShmMutex = nullptr; // triggered when the monitor signals the application HANDLE mSignalAppEvent = INVALID_HANDLE_VALUE; // triggered when the application wants to wake the monitor process HANDLE mSignalMonitorEvent = INVALID_HANDLE_VALUE; void setupIpc(); void shmLock(); void shmUnlock(); void startMonitorProcess(const std::string& dumpPath, const std::string& crashDumpName, const std::string& freezeDumpName); void waitMonitor(); void signalMonitor(); void installHandler(); void handleVectoredException(PEXCEPTION_POINTERS info); public: static LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS info); }; } // namespace Crash #endif // WINDOWS_CRASHCATCHER_HPP openmw-openmw-0.48.0/components/crashcatcher/windows_crashmonitor.cpp000066400000000000000000000264121445372753700262460ustar00rootroot00000000000000#include "windows_crashmonitor.hpp" #include #include #include #include #include #include #include "windows_crashcatcher.hpp" #include "windows_crashshm.hpp" #include "windowscrashdumppathhelpers.hpp" #include namespace Crash { std::unordered_map CrashMonitor::smEventHookOwners{}; using IsHungAppWindowFn = BOOL(WINAPI*)(HWND hwnd); // Obtains the pointer to user32.IsHungAppWindow, this function may be removed in the future. // See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-ishungappwindow static IsHungAppWindowFn getIsHungAppWindow() noexcept { auto user32Handle = LoadLibraryA("user32.dll"); if (user32Handle == nullptr) return nullptr; return reinterpret_cast(GetProcAddress(user32Handle, "IsHungAppWindow")); } static const IsHungAppWindowFn sIsHungAppWindow = getIsHungAppWindow(); CrashMonitor::CrashMonitor(HANDLE shmHandle) : mShmHandle(shmHandle) { mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); if (mShm == nullptr) throw std::runtime_error("Failed to map crash monitor shared memory"); // accessing SHM without lock is OK here, the parent waits for a signal before continuing mShmMutex = mShm->mStartup.mShmMutex; mAppProcessHandle = mShm->mStartup.mAppProcessHandle; mAppMainThreadId = mShm->mStartup.mAppMainThreadId; mSignalAppEvent = mShm->mStartup.mSignalApp; mSignalMonitorEvent = mShm->mStartup.mSignalMonitor; } CrashMonitor::~CrashMonitor() { if (mShm) UnmapViewOfFile(mShm); // the handles received from the app are duplicates, we must close them if (mShmHandle) CloseHandle(mShmHandle); if (mShmMutex) CloseHandle(mShmMutex); if (mSignalAppEvent) CloseHandle(mSignalAppEvent); if (mSignalMonitorEvent) CloseHandle(mSignalMonitorEvent); } void CrashMonitor::shmLock() { if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) throw std::runtime_error("SHM monitor lock timed out"); } void CrashMonitor::shmUnlock() { ReleaseMutex(mShmMutex); } void CrashMonitor::signalApp() const { SetEvent(mSignalAppEvent); } bool CrashMonitor::waitApp() const { return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0; } bool CrashMonitor::isAppAlive() const { DWORD code = 0; GetExitCodeProcess(mAppProcessHandle, &code); return code == STILL_ACTIVE; } bool CrashMonitor::isAppFrozen() { MSG message; // Allow the event hook callback to run PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE); if (!mAppWindowHandle) { EnumWindows([](HWND handle, LPARAM param) -> BOOL { CrashMonitor& crashMonitor = *(CrashMonitor*)param; DWORD processId; if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mAppMainThreadId && processId == GetProcessId(crashMonitor.mAppProcessHandle)) { if (GetWindow(handle, GW_OWNER) == 0) { crashMonitor.mAppWindowHandle = handle; return false; } } return true; }, (LPARAM)this); if (mAppWindowHandle) { DWORD processId; GetWindowThreadProcessId(mAppWindowHandle, &processId); HWINEVENTHOOK eventHookHandle = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, nullptr, [](HWINEVENTHOOK hWinEventHook, DWORD event, HWND windowHandle, LONG objectId, LONG childId, DWORD eventThread, DWORD eventTime) { CrashMonitor& crashMonitor = *smEventHookOwners[hWinEventHook]; if (event == EVENT_OBJECT_DESTROY && windowHandle == crashMonitor.mAppWindowHandle && objectId == OBJID_WINDOW && childId == INDEXID_CONTAINER) { crashMonitor.mAppWindowHandle = nullptr; smEventHookOwners.erase(hWinEventHook); UnhookWinEvent(hWinEventHook); } }, processId, mAppMainThreadId, WINEVENT_OUTOFCONTEXT); smEventHookOwners[eventHookHandle] = this; } else return false; } if (sIsHungAppWindow != nullptr) return sIsHungAppWindow(mAppWindowHandle); else { BOOL debuggerPresent; if (CheckRemoteDebuggerPresent(mAppProcessHandle, &debuggerPresent) && debuggerPresent) return false; if (SendMessageTimeoutA(mAppWindowHandle, WM_NULL, 0, 0, 0, 5000, nullptr) == 0) return GetLastError() == ERROR_TIMEOUT; } return false; } void CrashMonitor::run() { try { // app waits for monitor start up, let it continue signalApp(); bool running = true; bool frozen = false; while (isAppAlive() && running && !mFreezeAbort) { if (isAppFrozen()) { if (!frozen) { showFreezeMessageBox(); frozen = true; } } else if (frozen) { hideFreezeMessageBox(); frozen = false; } if (!mFreezeAbort && waitApp()) { shmLock(); switch (mShm->mEvent) { case CrashSHM::Event::None: break; case CrashSHM::Event::Crashed: handleCrash(false); running = false; break; case CrashSHM::Event::Shutdown: running = false; break; case CrashSHM::Event::Startup: break; } shmUnlock(); } } if (frozen) hideFreezeMessageBox(); if (mFreezeAbort) { handleCrash(true); TerminateProcess(mAppProcessHandle, 0xDEAD); std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + getFreezeDumpPath(*mShm) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } } catch (...) { Log(Debug::Error) << "Exception in crash monitor, exiting"; } signalApp(); } static std::wstring utf8ToUtf16(const std::string& utf8) { const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0); std::wstring utf16; utf16.resize(nLenWide); if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide) return {}; return utf16; } void CrashMonitor::handleCrash(bool isFreeze) { DWORD processId = GetProcessId(mAppProcessHandle); try { HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); if (dbghelp == NULL) return; using MiniDumpWirteDumpFn = BOOL (WINAPI*)( HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump"); if (miniDumpWriteDump == NULL) return; std::wstring utf16Path = utf8ToUtf16(isFreeze ? getFreezeDumpPath(*mShm) : getCrashDumpPath(*mShm)); if (utf16Path.empty()) return; if (utf16Path.length() > MAX_PATH) utf16Path = LR"(\\?\)" + utf16Path; HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE) return; if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0) return; EXCEPTION_POINTERS exp; exp.ContextRecord = &mShm->mCrashed.mContext; exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord; MINIDUMP_EXCEPTION_INFORMATION infos = {}; infos.ThreadId = mShm->mCrashed.mThreadId; infos.ExceptionPointers = &exp; infos.ClientPointers = FALSE; MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData); miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0); } catch (const std::exception&e) { Log(Debug::Error) << "CrashMonitor: " << e.what(); } catch (...) { Log(Debug::Error) << "CrashMonitor: unknown exception"; } } void CrashMonitor::showFreezeMessageBox() { std::thread messageBoxThread([&]() { SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" }; SDL_MessageBoxData messageBoxData = { SDL_MESSAGEBOX_ERROR, nullptr, "OpenMW appears to have frozen", "OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW hasn't actually frozen, this message box will disappear a within a few seconds of it becoming responsive.", 1, &button, nullptr }; int buttonId; if (SDL_ShowMessageBox(&messageBoxData, &buttonId) == 0 && buttonId == 0) mFreezeAbort = true; }); mFreezeMessageBoxThreadId = GetThreadId(messageBoxThread.native_handle()); messageBoxThread.detach(); } void CrashMonitor::hideFreezeMessageBox() { if (!mFreezeMessageBoxThreadId) return; EnumWindows([](HWND handle, LPARAM param) -> BOOL { CrashMonitor& crashMonitor = *(CrashMonitor*)param; DWORD processId; if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mFreezeMessageBoxThreadId && processId == GetCurrentProcessId()) PostMessage(handle, WM_CLOSE, 0, 0); return true; }, (LPARAM)this); mFreezeMessageBoxThreadId = 0; } } // namespace Crash openmw-openmw-0.48.0/components/crashcatcher/windows_crashmonitor.hpp000066400000000000000000000023471445372753700262540ustar00rootroot00000000000000#ifndef WINDOWS_CRASHMONITOR_HPP #define WINDOWS_CRASHMONITOR_HPP #include #include #include namespace Crash { struct CrashSHM; class CrashMonitor final { public: CrashMonitor(HANDLE shmHandle); ~CrashMonitor(); void run(); private: HANDLE mAppProcessHandle = nullptr; DWORD mAppMainThreadId = 0; HWND mAppWindowHandle = nullptr; // triggered when the monitor process wants to wake the parent process (received via SHM) HANDLE mSignalAppEvent = nullptr; // triggered when the application wants to wake the monitor process (received via SHM) HANDLE mSignalMonitorEvent = nullptr; CrashSHM* mShm = nullptr; HANDLE mShmHandle = nullptr; HANDLE mShmMutex = nullptr; DWORD mFreezeMessageBoxThreadId = 0; std::atomic_bool mFreezeAbort; static std::unordered_map smEventHookOwners; void signalApp() const; bool waitApp() const; bool isAppAlive() const; bool isAppFrozen(); void shmLock(); void shmUnlock(); void handleCrash(bool isFreeze); void showFreezeMessageBox(); void hideFreezeMessageBox(); }; } // namespace Crash #endif // WINDOWS_CRASHMONITOR_HPP openmw-openmw-0.48.0/components/crashcatcher/windows_crashshm.hpp000066400000000000000000000021141445372753700253440ustar00rootroot00000000000000#ifndef WINDOWS_CRASHSHM_HPP #define WINDOWS_CRASHSHM_HPP #include namespace Crash { // Used to communicate between the app and the monitor, fields are is overwritten with each event. static constexpr const int MAX_LONG_PATH = 0x7fff; static constexpr const int MAX_FILENAME = 0xff; struct CrashSHM { enum class Event { None, Startup, Crashed, Shutdown }; Event mEvent; struct Startup { HANDLE mAppProcessHandle; DWORD mAppMainThreadId; HANDLE mSignalApp; HANDLE mSignalMonitor; HANDLE mShmMutex; char mDumpDirectoryPath[MAX_LONG_PATH]; char mCrashDumpFileName[MAX_FILENAME]; char mFreezeDumpFileName[MAX_FILENAME]; } mStartup; struct Crashed { DWORD mThreadId; CONTEXT mContext; EXCEPTION_RECORD mExceptionRecord; } mCrashed; }; } // namespace Crash #endif // WINDOWS_CRASHSHM_HPP openmw-openmw-0.48.0/components/crashcatcher/windowscrashdumppathhelpers.cpp000066400000000000000000000007211445372753700276200ustar00rootroot00000000000000#include "windowscrashdumppathhelpers.hpp" #include std::string Crash::getCrashDumpPath(const CrashSHM& crashShm) { return (boost::filesystem::path(crashShm.mStartup.mDumpDirectoryPath) / crashShm.mStartup.mCrashDumpFileName).string(); } std::string Crash::getFreezeDumpPath(const CrashSHM& crashShm) { return (boost::filesystem::path(crashShm.mStartup.mDumpDirectoryPath) / crashShm.mStartup.mFreezeDumpFileName).string(); } openmw-openmw-0.48.0/components/crashcatcher/windowscrashdumppathhelpers.hpp000066400000000000000000000004011445372753700276200ustar00rootroot00000000000000#ifndef COMPONENTS_CRASH_WINDOWSCRASHDUMPPATHHELPERS_H #include "windows_crashshm.hpp" #include namespace Crash { std::string getCrashDumpPath(const CrashSHM& crashShm); std::string getFreezeDumpPath(const CrashSHM& crashShm); } #endif openmw-openmw-0.48.0/components/debug/000077500000000000000000000000001445372753700177075ustar00rootroot00000000000000openmw-openmw-0.48.0/components/debug/debugging.cpp000066400000000000000000000254021445372753700223510ustar00rootroot00000000000000#include "debugging.hpp" #include #include #include #include #include #ifdef _WIN32 #include #include #include #pragma push_macro("FAR") #pragma push_macro("NEAR") #undef FAR #define FAR #undef NEAR #define NEAR #include #pragma pop_macro("NEAR") #pragma pop_macro("FAR") #endif #include namespace Debug { #ifdef _WIN32 bool isRedirected(DWORD nStdHandle) { DWORD fileType = GetFileType(GetStdHandle(nStdHandle)); return (fileType == FILE_TYPE_DISK) || (fileType == FILE_TYPE_PIPE); } bool attachParentConsole() { if (GetConsoleWindow() != nullptr) return true; bool inRedirected = isRedirected(STD_INPUT_HANDLE); bool outRedirected = isRedirected(STD_OUTPUT_HANDLE); bool errRedirected = isRedirected(STD_ERROR_HANDLE); if (AttachConsole(ATTACH_PARENT_PROCESS)) { fflush(stdout); fflush(stderr); std::cout.flush(); std::cerr.flush(); // this looks dubious but is really the right way if (!inRedirected) { _wfreopen(L"CON", L"r", stdin); freopen("CON", "r", stdin); } if (!outRedirected) { _wfreopen(L"CON", L"w", stdout); freopen("CON", "w", stdout); } if (!errRedirected) { _wfreopen(L"CON", L"w", stderr); freopen("CON", "w", stderr); } return true; } return false; } #endif static LogListener logListener; void setLogListener(LogListener listener) { logListener = std::move(listener); } class DebugOutputBase : public boost::iostreams::sink { public: DebugOutputBase() { if (CurrentDebugLevel == NoLevel) fillCurrentDebugLevel(); } virtual std::streamsize write(const char* str, std::streamsize size) { if (size <= 0) return size; std::string_view msg{ str, size_t(size) }; // Skip debug level marker Level level = getLevelMarker(str); if (level != NoLevel) msg = msg.substr(1); char prefix[32]; int prefixSize; { prefix[0] = '['; const auto now = std::chrono::system_clock::now(); const auto time = std::chrono::system_clock::to_time_t(now); prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", std::localtime(&time)) + 1; char levelLetter = " EWIVD*"[int(level)]; const auto ms = std::chrono::duration_cast(now.time_since_epoch()).count(); prefixSize += snprintf(prefix + prefixSize, sizeof(prefix) - prefixSize, ".%03u %c] ", static_cast(ms % 1000), levelLetter); } while (!msg.empty()) { if (msg[0] == 0) break; size_t lineSize = 1; while (lineSize < msg.size() && msg[lineSize - 1] != '\n') lineSize++; writeImpl(prefix, prefixSize, level); writeImpl(msg.data(), lineSize, level); if (logListener) logListener(level, std::string_view(prefix, prefixSize), std::string_view(msg.data(), lineSize)); msg = msg.substr(lineSize); } return size; } virtual ~DebugOutputBase() = default; protected: static Level getLevelMarker(const char* str) { if (unsigned(*str) <= unsigned(Marker)) { return Level(*str); } return NoLevel; } static void fillCurrentDebugLevel() { const char* env = getenv("OPENMW_DEBUG_LEVEL"); if (env) { std::string value(env); if (value == "ERROR") CurrentDebugLevel = Error; else if (value == "WARNING") CurrentDebugLevel = Warning; else if (value == "INFO") CurrentDebugLevel = Info; else if (value == "VERBOSE") CurrentDebugLevel = Verbose; else if (value == "DEBUG") CurrentDebugLevel = Debug; return; } CurrentDebugLevel = Verbose; } virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { return size; } }; #if defined _WIN32 && defined _DEBUG class DebugOutput : public DebugOutputBase { public: std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { // Make a copy for null termination std::string tmp(str, static_cast(size)); // Write string to Visual Studio Debug output OutputDebugString(tmp.c_str()); return size; } virtual ~DebugOutput() = default; }; #else class Tee : public DebugOutputBase { public: Tee(std::ostream& stream, std::ostream& stream2) : out(stream), out2(stream2) { // TODO: check which stream is stderr? mUseColor = useColoredOutput(); mColors[Error] = Red; mColors[Warning] = Yellow; mColors[Info] = Reset; mColors[Verbose] = DarkGray; mColors[Debug] = DarkGray; mColors[NoLevel] = Reset; } std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) override { out.write(str, size); out.flush(); if (mUseColor) { out2 << "\033[0;" << mColors[debugLevel] << "m"; out2.write(str, size); out2 << "\033[0;" << Reset << "m"; } else { out2.write(str, size); } out2.flush(); return size; } virtual ~Tee() = default; private: static bool useColoredOutput() { // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. #if defined(_WIN32) return 0; #else char* term = getenv("TERM"); bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); return useColor; #endif } std::ostream& out; std::ostream& out2; bool mUseColor; std::map mColors; }; #endif } static std::unique_ptr rawStdout = nullptr; static std::unique_ptr rawStderr = nullptr; static std::unique_ptr rawStderrMutex = nullptr; static boost::filesystem::ofstream logfile; #if defined(_WIN32) && defined(_DEBUG) static boost::iostreams::stream_buffer sb; #else static boost::iostreams::stream_buffer coutsb; static boost::iostreams::stream_buffer cerrsb; #endif std::ostream& getRawStdout() { return rawStdout ? *rawStdout : std::cout; } std::ostream& getRawStderr() { return rawStderr ? *rawStderr : std::cerr; } Misc::Locked getLockedRawStderr() { return Misc::Locked(*rawStderrMutex, getRawStderr()); } // Redirect cout and cerr to the log file void setupLogging(const std::string& logDir, std::string_view appName) { #if defined(_WIN32) && defined(_DEBUG) // Redirect cout and cerr to VS debug output when running in debug mode sb.open(Debug::DebugOutput()); std::cout.rdbuf(&sb); std::cerr.rdbuf(&sb); #else const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; logfile.open(boost::filesystem::path(logDir) / logName, std::ios::out); coutsb.open(Debug::Tee(logfile, *rawStdout)); cerrsb.open(Debug::Tee(logfile, *rawStderr)); std::cout.rdbuf(&coutsb); std::cerr.rdbuf(&cerrsb); #endif #ifdef _WIN32 if (Crash::CrashCatcher::instance()) { boost::filesystem::path dumpDirectory(logDir); Crash::CrashCatcher::instance()->updateDumpPath(dumpDirectory.make_preferred().string()); } #endif } int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], std::string_view appName) { #if defined _WIN32 (void)Debug::attachParentConsole(); #endif rawStdout = std::make_unique(std::cout.rdbuf()); rawStderr = std::make_unique(std::cerr.rdbuf()); rawStderrMutex = std::make_unique(); int ret = 0; try { Files::ConfigurationManager cfgMgr; if (const auto env = std::getenv("OPENMW_DISABLE_CRASH_CATCHER"); env == nullptr || std::atol(env) == 0) { #if defined(_WIN32) const std::string crashDumpName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; const std::string freezeDumpName = Misc::StringUtils::lowerCase(appName) + "-freeze.dmp"; boost::filesystem::path dumpDirectory = boost::filesystem::temp_directory_path(); PWSTR userProfile = nullptr; if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &userProfile))) { dumpDirectory = userProfile; } CoTaskMemFree(userProfile); Crash::CrashCatcher crashy(argc, argv, dumpDirectory.make_preferred().string(), crashDumpName, freezeDumpName); #else const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; // install the crash handler as soon as possible. note that the log path // does not depend on config being read. crashCatcherInstall(argc, argv, (boost::filesystem::temp_directory_path() / crashLogName).string()); #endif ret = innerApplication(argc, argv); } else ret = innerApplication(argc, argv); } catch (const std::exception& e) { #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) if (!isatty(fileno(stdin))) #endif SDL_ShowSimpleMessageBox(0, (std::string(appName) + ": Fatal error").c_str(), e.what(), nullptr); Log(Debug::Error) << "Error: " << e.what(); ret = 1; } // Restore cout and cerr std::cout.rdbuf(rawStdout->rdbuf()); std::cerr.rdbuf(rawStderr->rdbuf()); Debug::CurrentDebugLevel = Debug::NoLevel; return ret; } openmw-openmw-0.48.0/components/debug/debugging.hpp000066400000000000000000000017071445372753700223600ustar00rootroot00000000000000#ifndef DEBUG_DEBUGGING_H #define DEBUG_DEBUGGING_H #include #include #include #include #include "debuglog.hpp" namespace Debug { // ANSI colors for terminal enum Color { Reset = 0, DarkGray = 90, Red = 91, Yellow = 93 }; #ifdef _WIN32 bool attachParentConsole(); #endif using LogListener = std::function; void setLogListener(LogListener); } // Can be used to print messages without timestamps std::ostream& getRawStdout(); std::ostream& getRawStderr(); Misc::Locked getLockedRawStderr(); void setupLogging(const std::string& logDir, std::string_view appName); int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], std::string_view appName); #endif openmw-openmw-0.48.0/components/debug/debuglog.cpp000066400000000000000000000014161445372753700222050ustar00rootroot00000000000000#include "debuglog.hpp" #include namespace Debug { Level CurrentDebugLevel = Level::NoLevel; } static std::mutex sLock; Log::Log(Debug::Level level) : mShouldLog(level <= Debug::CurrentDebugLevel) { // No need to hold the lock if there will be no logging anyway if (!mShouldLog) return; // Locks a global lock while the object is alive sLock.lock(); // If the app has no logging system enabled, log level is not specified. // Show all messages without marker - we just use the plain cout in this case. if (Debug::CurrentDebugLevel == Debug::NoLevel) return; std::cout << static_cast(level); } Log::~Log() { if (!mShouldLog) return; std::cout << std::endl; sLock.unlock(); } openmw-openmw-0.48.0/components/debug/debuglog.hpp000066400000000000000000000012511445372753700222070ustar00rootroot00000000000000#ifndef DEBUG_LOG_H #define DEBUG_LOG_H #include namespace Debug { enum Level { Error = 1, Warning = 2, Info = 3, Verbose = 4, Debug = 5, Marker = Debug, NoLevel = 6 // Do not filter messages in this case }; extern Level CurrentDebugLevel; } class Log { public: explicit Log(Debug::Level level); ~Log(); // Perfect forwarding wrappers to give the chain of objects to cout template Log& operator<<(T&& rhs) { if (mShouldLog) std::cout << std::forward(rhs); return *this; } private: const bool mShouldLog; }; #endif openmw-openmw-0.48.0/components/debug/gldebug.cpp000066400000000000000000000234721445372753700220340ustar00rootroot00000000000000// This file is based heavily on code from https://github.com/ThermalPixel/osgdemos/blob/master/osgdebug/EnableGLDebugOperation.cpp // The original licence is included below: /* Copyright (c) 2014, Andreas Klein All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ #include "gldebug.hpp" #include #include #include // OpenGL constants not provided by OSG: #include namespace Debug { void GL_APIENTRY debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { #ifdef GL_DEBUG_OUTPUT std::string srcStr; switch (source) { case GL_DEBUG_SOURCE_API: srcStr = "API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: srcStr = "WINDOW_SYSTEM"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: srcStr = "SHADER_COMPILER"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: srcStr = "THIRD_PARTY"; break; case GL_DEBUG_SOURCE_APPLICATION: srcStr = "APPLICATION"; break; case GL_DEBUG_SOURCE_OTHER: srcStr = "OTHER"; break; default: srcStr = "UNDEFINED"; break; } std::string typeStr; Level logSeverity = Warning; switch (type) { case GL_DEBUG_TYPE_ERROR: typeStr = "ERROR"; logSeverity = Error; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "DEPRECATED_BEHAVIOR"; logSeverity = Warning; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeStr = "UNDEFINED_BEHAVIOR"; logSeverity = Warning; break; case GL_DEBUG_TYPE_PORTABILITY: typeStr = "PORTABILITY"; logSeverity = Debug; break; case GL_DEBUG_TYPE_PERFORMANCE: typeStr = "PERFORMANCE"; logSeverity = Debug; break; case GL_DEBUG_TYPE_OTHER: typeStr = "OTHER"; logSeverity = Debug; break; default: typeStr = "UNDEFINED"; break; } Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; #endif } class PushDebugGroup { public: static std::unique_ptr sInstance; void (GL_APIENTRY * glPushDebugGroup) (GLenum source, GLuint id, GLsizei length, const GLchar * message); void (GL_APIENTRY * glPopDebugGroup) (void); bool valid() { return glPushDebugGroup && glPopDebugGroup; } }; std::unique_ptr PushDebugGroup::sInstance{ std::make_unique() }; EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) { } void EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) { #ifdef GL_DEBUG_OUTPUT OpenThreads::ScopedLock lock(mMutex); unsigned int contextID = graphicsContext->getState()->getContextID(); typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); GLDebugMessageControlFunction glDebugMessageControl = nullptr; GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) { osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPushDebugGroup, "glPushDebugGroup"); osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPopDebugGroup, "glPopDebugGroup"); } else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) { osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); } else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) { osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); } if (glDebugMessageCallback && glDebugMessageControl) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); glDebugMessageCallback(debugCallback, nullptr); Log(Info) << "OpenGL debug callback attached."; } else #endif Log(Error) << "Unable to attach OpenGL debug callback."; } bool shouldDebugOpenGL() { const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); if (!env) return false; std::string str(env); if (str.length() == 0) return true; return str.find("OFF") == std::string::npos && str.find('0') == std::string::npos && str.find("NO") == std::string::npos; } DebugGroup::DebugGroup(const std::string & message, GLuint id) #ifdef GL_DEBUG_OUTPUT : mSource(GL_DEBUG_SOURCE_APPLICATION) #else : mSource(0x824A) #endif , mId(id) , mMessage(message) { } DebugGroup::DebugGroup(const DebugGroup & debugGroup, const osg::CopyOp & copyop) : osg::StateAttribute(debugGroup, copyop) , mSource(debugGroup.mSource) , mId(debugGroup.mId) , mMessage(debugGroup.mMessage) { } void DebugGroup::apply(osg::State & state) const { if (!PushDebugGroup::sInstance->valid()) { Log(Error) << "OpenGL debug groups not supported on this system, or OPENMW_DEBUG_OPENGL environment variable not set."; return; } auto& attributeVec = state.getAttributeVec(this); auto& lastAppliedStack = sLastAppliedStack[state.getContextID()]; size_t firstNonMatch = 0; while (firstNonMatch < lastAppliedStack.size() && ((firstNonMatch < attributeVec.size() && lastAppliedStack[firstNonMatch] == attributeVec[firstNonMatch].first) || lastAppliedStack[firstNonMatch] == this)) firstNonMatch++; for (size_t i = lastAppliedStack.size(); i > firstNonMatch; --i) lastAppliedStack[i - 1]->pop(state); lastAppliedStack.resize(firstNonMatch); lastAppliedStack.reserve(attributeVec.size()); for (size_t i = firstNonMatch; i < attributeVec.size(); ++i) { const DebugGroup* group = static_cast(attributeVec[i].first); group->push(state); lastAppliedStack.push_back(group); } if (!(lastAppliedStack.back() == this)) { push(state); lastAppliedStack.push_back(this); } } int DebugGroup::compare(const StateAttribute & sa) const { COMPARE_StateAttribute_Types(DebugGroup, sa); COMPARE_StateAttribute_Parameter(mSource); COMPARE_StateAttribute_Parameter(mId); COMPARE_StateAttribute_Parameter(mMessage); return 0; } void DebugGroup::releaseGLObjects(osg::State * state) const { if (state) sLastAppliedStack.erase(state->getContextID()); else sLastAppliedStack.clear(); } bool DebugGroup::isValid() const { return mSource || mId || mMessage.length(); } void DebugGroup::push(osg::State & state) const { if (isValid()) PushDebugGroup::sInstance->glPushDebugGroup(mSource, mId, mMessage.size(), mMessage.c_str()); } void DebugGroup::pop(osg::State & state) const { if (isValid()) PushDebugGroup::sInstance->glPopDebugGroup(); } std::map> DebugGroup::sLastAppliedStack{}; } openmw-openmw-0.48.0/components/debug/gldebug.hpp000066400000000000000000000051111445372753700220270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DEBUG_GLDEBUG_H #define OPENMW_COMPONENTS_DEBUG_GLDEBUG_H #include namespace Debug { class EnableGLDebugOperation : public osg::GraphicsOperation { public: EnableGLDebugOperation(); void operator()(osg::GraphicsContext* graphicsContext) override; private: OpenThreads::Mutex mMutex; }; bool shouldDebugOpenGL(); /* Debug groups allow rendering to be annotated, making debugging via APITrace/CodeXL/NSight etc. much clearer. Because I've not thought of a quick and clean way of doing it without incurring a small performance cost, there are no uses of this class checked in. For now, add annotations locally when you need them. To use this class, add it to a StateSet just like any other StateAttribute. Prefer the string-only constructor. You'll need OPENMW_DEBUG_OPENGL set to true, or shouldDebugOpenGL() redefined to just return true as otherwise the extension function pointers won't get set up. That can maybe be cleaned up in the future. Beware that consecutive identical debug groups (i.e. pointers match) won't always get applied due to OSG thinking it's already applied them. Either avoid nesting the same object, add dummy groups so they're not consecutive, or ensure the leaf group isn't identical to its parent. */ class DebugGroup : public osg::StateAttribute { public: DebugGroup() : mSource(0) , mId(0) , mMessage("") {} DebugGroup(GLenum source, GLuint id, const std::string& message) : mSource(source) , mId(id) , mMessage(message) {} DebugGroup(const std::string& message, GLuint id = 0); DebugGroup(const DebugGroup& debugGroup, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); META_StateAttribute(Debug, DebugGroup, osg::StateAttribute::Type(101)); void apply(osg::State& state) const override; int compare(const StateAttribute& sa) const override; void releaseGLObjects(osg::State* state = nullptr) const override; virtual bool isValid() const; protected: virtual ~DebugGroup() = default; virtual void push(osg::State& state) const; virtual void pop(osg::State& state) const; GLenum mSource; GLuint mId; std::string mMessage; static std::map> sLastAppliedStack; friend EnableGLDebugOperation; }; } #endif openmw-openmw-0.48.0/components/detournavigator/000077500000000000000000000000001445372753700220365ustar00rootroot00000000000000openmw-openmw-0.48.0/components/detournavigator/agentbounds.hpp000066400000000000000000000013001445372753700250520ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_AGENTBOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_AGENTBOUNDS_H #include "collisionshapetype.hpp" #include #include namespace DetourNavigator { struct AgentBounds { CollisionShapeType mShapeType; osg::Vec3f mHalfExtents; }; inline auto tie(const AgentBounds& value) { return std::tie(value.mShapeType, value.mHalfExtents); } inline bool operator==(const AgentBounds& lhs, const AgentBounds& rhs) { return tie(lhs) == tie(rhs); } inline bool operator<(const AgentBounds& lhs, const AgentBounds& rhs) { return tie(lhs) < tie(rhs); } } #endif openmw-openmw-0.48.0/components/detournavigator/areatype.hpp000066400000000000000000000010131445372753700243540ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H #include namespace DetourNavigator { enum AreaType : unsigned char { AreaType_null = RC_NULL_AREA, AreaType_water, AreaType_door, AreaType_pathgrid, AreaType_ground = RC_WALKABLE_AREA, }; struct AreaCosts { float mWater = 1.0f; float mDoor = 2.0f; float mPathgrid = 1.0f; float mGround = 1.0f; }; } #endif openmw-openmw-0.48.0/components/detournavigator/asyncnavmeshupdater.cpp000066400000000000000000001016151445372753700266320ustar00rootroot00000000000000#include "asyncnavmeshupdater.hpp" #include "debug.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include "version.hpp" #include "serialization.hpp" #include "navmeshdbutils.hpp" #include "dbrefgeometryobject.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { namespace { int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } int getMinDistanceTo(const TilePosition& position, int maxDistance, const std::set>& pushedTiles, const std::set>& presentTiles) { int result = maxDistance; for (const auto& [agentBounds, tile] : pushedTiles) if (presentTiles.find(std::tie(agentBounds, tile)) == presentTiles.end()) result = std::min(result, getManhattanDistance(position, tile)); return result; } auto getPriority(const Job& job) noexcept { return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); } struct LessByJobPriority { bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getPriority(*lhs) < getPriority(*rhs); } }; void insertPrioritizedJob(JobIt job, std::deque& queue) { const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); queue.insert(it, job); } auto getDbPriority(const Job& job) noexcept { return std::make_tuple(static_cast>(job.mState), job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin); } struct LessByJobDbPriority { bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getDbPriority(*lhs) < getDbPriority(*rhs); } }; void insertPrioritizedDbJob(JobIt job, std::deque& queue) { const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority {}); queue.insert(it, job); } auto getAgentAndTile(const Job& job) noexcept { return std::make_tuple(job.mAgentBounds, job.mChangedTile); } std::unique_ptr makeDbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, const Settings& settings) { if (db == nullptr) return nullptr; return std::make_unique(updater, std::move(db), TileVersion(navMeshFormatVersion), settings.mRecast, settings.mWriteToNavMeshDb); } void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) { for (JobIt job : jobs) { job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) job->mChangeType = ChangeType::remove; } } std::size_t getNextJobId() { static std::atomic_size_t nextJobId {1}; return nextJobId.fetch_add(1); } } std::ostream& operator<<(std::ostream& stream, JobStatus value) { switch (value) { case JobStatus::Done: return stream << "JobStatus::Done"; case JobStatus::Fail: return stream << "JobStatus::Fail"; case JobStatus::MemoryCacheMiss: return stream << "JobStatus::MemoryCacheMiss"; } return stream << "JobStatus::" << static_cast>(value); } Job::Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime) : mId(getNextJobId()) , mAgentBounds(agentBounds) , mNavMeshCacheItem(std::move(navMeshCacheItem)) , mWorldspace(worldspace) , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) , mDistanceToPlayer(distanceToPlayer) , mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition {0, 0})) { } AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) , mDbWorker(makeDbWorker(*this, std::move(db), mSettings)) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); } AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { stop(); } void AsyncNavMeshUpdater::post(const AgentBounds& agentBounds, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, std::string_view worldspace, const std::map& changedTiles) { bool playerTileChanged = false; { auto locked = mPlayerTile.lock(); playerTileChanged = *locked != playerTile; *locked = playerTile; } if (!playerTileChanged && changedTiles.empty()) return; const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles); std::unique_lock lock(mMutex); if (playerTileChanged) updateJobs(mWaiting, playerTile, maxTiles); for (const auto& [changedTile, changeType] : changedTiles) { if (mPushed.emplace(agentBounds, changedTile).second) { const auto processTime = changeType == ChangeType::update ? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime); Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")" << " changedTile=(" << it->mChangedTile << ")"; if (playerTileChanged) mWaiting.push_back(it); else insertPrioritizedJob(it, mWaiting); } } if (playerTileChanged) std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority {}); Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; if (!mWaiting.empty()) mHasJob.notify_all(); lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) mDbWorker->updateJobs(playerTile, maxTiles); } void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { if (mSettings.get().mWaitUntilMinDistanceToPlayer == 0) return; listener.setLabel("#{Navigation:BuildingNavigationMesh}"); const std::size_t initialJobsLeft = getTotalJobs(); std::size_t maxProgress = initialJobsLeft + mThreads.size(); listener.setProgressRange(maxProgress); switch (waitConditionType) { case WaitConditionType::requiredTilesPresent: { const int minDistanceToPlayer = waitUntilJobsDoneForNotPresentTiles(initialJobsLeft, maxProgress, listener); if (minDistanceToPlayer < mSettings.get().mWaitUntilMinDistanceToPlayer) { mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); listener.setProgress(maxProgress); } break; } case WaitConditionType::allJobsDone: waitUntilAllJobsDone(); listener.setProgress(maxProgress); break; } } void AsyncNavMeshUpdater::stop() { mShouldStop = true; if (mDbWorker != nullptr) mDbWorker->stop(); std::unique_lock lock(mMutex); mWaiting.clear(); mHasJob.notify_all(); lock.unlock(); for (auto& thread : mThreads) if (thread.joinable()) thread.join(); } int AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener) { std::size_t prevJobsLeft = initialJobsLeft; std::size_t jobsDone = 0; std::size_t jobsLeft = 0; const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer; const TilePosition playerPosition = *mPlayerTile.lockConst(); int minDistanceToPlayer = 0; const auto isDone = [&] { jobsLeft = mJobs.size(); if (jobsLeft == 0) { minDistanceToPlayer = 0; return true; } minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles); return minDistanceToPlayer >= maxDistanceToPlayer; }; std::unique_lock lock(mMutex); while (!mDone.wait_for(lock, std::chrono::milliseconds(250), isDone)) { if (maxProgress < jobsLeft) { maxProgress = jobsLeft + mThreads.size(); listener.setProgressRange(maxProgress); listener.setProgress(jobsDone); } else if (jobsLeft < prevJobsLeft) { const std::size_t newJobsDone = prevJobsLeft - jobsLeft; jobsDone += newJobsDone; prevJobsLeft = jobsLeft; listener.increaseProgress(newJobsDone); } } return minDistanceToPlayer; } void AsyncNavMeshUpdater::waitUntilAllJobsDone() { { std::unique_lock lock(mMutex); mDone.wait(lock, [this] { return mJobs.size() == 0; }); } mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } AsyncNavMeshUpdater::Stats AsyncNavMeshUpdater::getStats() const { Stats result; { const std::lock_guard lock(mMutex); result.mJobs = mJobs.size(); result.mWaiting = mWaiting.size(); result.mPushed = mPushed.size(); } result.mProcessing = mProcessingTiles.lockConst()->size(); if (mDbWorker != nullptr) result.mDb = mDbWorker->getStats(); result.mCache = mNavMeshTilesCache.getStats(); result.mDbGetTileHits = mDbGetTileHits.load(std::memory_order_relaxed); return result; } void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out) { out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); if (stats.mDb.has_value()) { out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast(stats.mDb->mJobs)); if (stats.mDb->mGetTileCount > 0) out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast(stats.mDbGetTileHits) / static_cast(stats.mDb->mGetTileCount) * 100.0); } reportStats(stats.mCache, frameNumber, out); } void AsyncNavMeshUpdater::process() noexcept { Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id(); Misc::setCurrentThreadIdlePriority(); while (!mShouldStop) { try { if (JobIt job = getNextJob(); job != mJobs.end()) { const JobStatus status = processJob(*job); Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; switch (status) { case JobStatus::Done: unlockTile(job->mAgentBounds, job->mChangedTile); if (job->mGeneratedNavMeshData != nullptr) mDbWorker->enqueueJob(job); else removeJob(job); break; case JobStatus::Fail: repost(job); break; case JobStatus::MemoryCacheMiss: { mDbWorker->enqueueJob(job); break; } } } else cleanupLastUpdates(); } catch (const std::exception& e) { Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what(); } } Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); } JobStatus AsyncNavMeshUpdater::processJob(Job& job) { Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); if (!navMeshCacheItem) return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; navMeshCacheItem->lock()->removeTile(job.mChangedTile); return JobStatus::Done; } switch (job.mState) { case JobState::Initial: return processInitialJob(job, *navMeshCacheItem); case JobState::WithDbResult: return processJobWithDbResult(job, *navMeshCacheItem); } return JobStatus::Done; } JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) { Log(Debug::Debug) << "Processing initial job " << job.mId; std::shared_ptr recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile); if (recastMesh == nullptr) { Log(Debug::Debug) << "Null recast mesh for job " << job.mId; navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); return JobStatus::Done; } if (isEmpty(*recastMesh)) { Log(Debug::Debug) << "Empty bounds for job " << job.mId; navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); return JobStatus::Done; } NavMeshTilesCache::Value cachedNavMeshData = mNavMeshTilesCache.get(job.mAgentBounds, job.mChangedTile, *recastMesh); std::unique_ptr preparedNavMeshData; const PreparedNavMeshData* preparedNavMeshDataPtr = nullptr; if (cachedNavMeshData) { preparedNavMeshDataPtr = &cachedNavMeshData.get(); } else { if (job.mChangeType != ChangeType::update && mDbWorker != nullptr) { job.mRecastMesh = std::move(recastMesh); return JobStatus::MemoryCacheMiss; } preparedNavMeshData = prepareNavMeshTileData(*recastMesh, job.mChangedTile, job.mAgentBounds, mSettings.get().mRecast); if (preparedNavMeshData == nullptr) { Log(Debug::Debug) << "Null navmesh data for job " << job.mId; navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); return JobStatus::Done; } if (job.mChangeType == ChangeType::update) { preparedNavMeshDataPtr = preparedNavMeshData.get(); } else { cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentBounds, job.mChangedTile, *recastMesh, std::move(preparedNavMeshData)); preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); } } const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentBounds, job.mChangedTile, mSettings.get().mRecast)); return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh); } JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) { Log(Debug::Debug) << "Processing job with db result " << job.mId; std::unique_ptr preparedNavMeshData; bool generatedNavMeshData = false; if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == navMeshFormatVersion) { preparedNavMeshData = std::make_unique(); if (deserialize(job.mCachedTileData->mData, *preparedNavMeshData)) ++mDbGetTileHits; else preparedNavMeshData = nullptr; } if (preparedNavMeshData == nullptr) { preparedNavMeshData = prepareNavMeshTileData(*job.mRecastMesh, job.mChangedTile, job.mAgentBounds, mSettings.get().mRecast); generatedNavMeshData = true; } if (preparedNavMeshData == nullptr) { Log(Debug::Debug) << "Null navmesh data for job " << job.mId; navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); return JobStatus::Done; } auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentBounds, job.mChangedTile, *job.mRecastMesh, std::move(preparedNavMeshData)); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); assert (preparedNavMeshDataPtr != nullptr); const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentBounds, job.mChangedTile, mSettings.get().mRecast)); const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh); if (result == JobStatus::Done && job.mChangeType != ChangeType::update && mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData) job.mGeneratedNavMeshData = std::make_unique(*preparedNavMeshDataPtr); return result; } JobStatus AsyncNavMeshUpdater::handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh) { const Version navMeshVersion = navMeshCacheItem.lockConst()->getVersion(); mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, Version {recastMesh.getGeneration(), recastMesh.getRevision()}, navMeshVersion); if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) { const std::scoped_lock lock(mMutex); mPresentTiles.erase(std::make_tuple(job.mAgentBounds, job.mChangedTile)); } else if (isSuccess(status) && status != UpdateNavMeshStatus::ignored) { const std::scoped_lock lock(mMutex); mPresentTiles.insert(std::make_tuple(job.mAgentBounds, job.mChangedTile)); } writeDebugFiles(job, &recastMesh); return isSuccess(status) ? JobStatus::Done : JobStatus::Fail; } JobIt AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); bool shouldStop = false; const auto hasJob = [&] { shouldStop = mShouldStop; return shouldStop || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { if (mJobs.empty()) mDone.notify_all(); return mJobs.end(); } if (shouldStop) return mJobs.end(); const JobIt job = mWaiting.front(); mWaiting.pop_front(); if (job->mRecastMesh != nullptr) return job; if (!lockTile(job->mAgentBounds, job->mChangedTile)) { Log(Debug::Debug) << "Failed to lock tile by " << job->mId; ++job->mTryNumber; insertPrioritizedJob(job, mWaiting); return mJobs.end(); } if (job->mChangeType == ChangeType::update) mLastUpdates[getAgentAndTile(*job)] = std::chrono::steady_clock::now(); mPushed.erase(getAgentAndTile(*job)); return job; } void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const { std::string revision; std::string recastMeshRevision; std::string navMeshRevision; if ((mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) && (mSettings.get().mEnableRecastMeshFileNameRevision || mSettings.get().mEnableNavMeshFileNameRevision)) { revision = "." + std::to_string((std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point()).count()); if (mSettings.get().mEnableRecastMeshFileNameRevision) recastMeshRevision = revision; if (mSettings.get().mEnableNavMeshFileNameRevision) navMeshRevision = revision; } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings.get().mRecast); if (mSettings.get().mEnableWriteNavMeshToFile) if (const auto shared = job.mNavMeshCacheItem.lock()) writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } void AsyncNavMeshUpdater::repost(JobIt job) { unlockTile(job->mAgentBounds, job->mChangedTile); if (mShouldStop || job->mTryNumber > 2) return; const std::lock_guard lock(mMutex); if (mPushed.emplace(job->mAgentBounds, job->mChangedTile).second) { ++job->mTryNumber; insertPrioritizedJob(job, mWaiting); mHasJob.notify_all(); return; } mJobs.erase(job); } bool AsyncNavMeshUpdater::lockTile(const AgentBounds& agentBounds, const TilePosition& changedTile) { Log(Debug::Debug) << "Locking tile agent=" << agentBounds << " changedTile=(" << changedTile << ")"; return mProcessingTiles.lock()->emplace(agentBounds, changedTile).second; } void AsyncNavMeshUpdater::unlockTile(const AgentBounds& agentBounds, const TilePosition& changedTile) { auto locked = mProcessingTiles.lock(); locked->erase(std::tie(agentBounds, changedTile)); Log(Debug::Debug) << "Unlocked tile agent=" << agentBounds << " changedTile=(" << changedTile << ")"; if (locked->empty()) mProcessed.notify_all(); } std::size_t AsyncNavMeshUpdater::getTotalJobs() const { const std::scoped_lock lock(mMutex); return mJobs.size(); } void AsyncNavMeshUpdater::cleanupLastUpdates() { const auto now = std::chrono::steady_clock::now(); const std::lock_guard lock(mMutex); for (auto it = mLastUpdates.begin(); it != mLastUpdates.end();) { if (now - it->second > mSettings.get().mMinUpdateInterval) it = mLastUpdates.erase(it); else ++it; } } void AsyncNavMeshUpdater::enqueueJob(JobIt job) { Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); insertPrioritizedJob(job, mWaiting); mHasJob.notify_all(); } void AsyncNavMeshUpdater::removeJob(JobIt job) { Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); mJobs.erase(job); } void DbJobQueue::push(JobIt job) { const std::lock_guard lock(mMutex); insertPrioritizedDbJob(job, mJobs); mHasJob.notify_all(); } std::optional DbJobQueue::pop() { std::unique_lock lock(mMutex); mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); if (mJobs.empty()) return std::nullopt; const JobIt job = mJobs.front(); mJobs.pop_front(); return job; } void DbJobQueue::update(TilePosition playerTile, int maxTiles) { const std::lock_guard lock(mMutex); updateJobs(mJobs, playerTile, maxTiles); std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority {}); } void DbJobQueue::stop() { const std::lock_guard lock(mMutex); mJobs.clear(); mShouldStop = true; mHasJob.notify_all(); } std::size_t DbJobQueue::size() const { const std::lock_guard lock(mMutex); return mJobs.size(); } DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, const RecastSettings& recastSettings, bool writeToDb) : mUpdater(updater) , mRecastSettings(recastSettings) , mDb(std::move(db)) , mVersion(version) , mWriteToDb(writeToDb) , mNextTileId(mDb->getMaxTileId() + 1) , mNextShapeId(mDb->getMaxShapeId() + 1) , mThread([this] { run(); }) { } DbWorker::~DbWorker() { stop(); } void DbWorker::enqueueJob(JobIt job) { Log(Debug::Debug) << "Enqueueing db job " << job->mId << " by thread=" << std::this_thread::get_id(); mQueue.push(job); } DbWorker::Stats DbWorker::getStats() const { Stats result; result.mJobs = mQueue.size(); result.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed); return result; } void DbWorker::stop() { mShouldStop = true; mQueue.stop(); if (mThread.joinable()) mThread.join(); } void DbWorker::run() noexcept { while (!mShouldStop) { try { if (const auto job = mQueue.pop()) processJob(*job); } catch (const std::exception& e) { Log(Debug::Error) << "DbWorker exception: " << e.what(); } } } void DbWorker::processJob(JobIt job) { const auto process = [&] (auto f) { try { f(job); } catch (const std::exception& e) { Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what(); if (mWriteToDb) { const std::string_view message(e.what()); if (message.find("database or disk is full") != std::string_view::npos) { mWriteToDb = false; Log(Debug::Warning) << "Writes to navmeshdb are disabled because file size limit is reached or disk is full"; } else if (message.find("database is locked") != std::string_view::npos) { mWriteToDb = false; Log(Debug::Warning) << "Writes to navmeshdb are disabled to avoid concurrent writes from multiple processes"; } } } }; if (job->mGeneratedNavMeshData != nullptr) { process([&] (JobIt job) { processWritingJob(job); }); mUpdater.removeJob(job); return; } process([&] (JobIt job) { processReadingJob(job); }); job->mState = JobState::WithDbResult; mUpdater.enqueueJob(job); } void DbWorker::processReadingJob(JobIt job) { Log(Debug::Debug) << "Processing db read job " << job->mId; if (job->mInput.empty()) { Log(Debug::Debug) << "Serializing input for job " << job->mId; if (mWriteToDb) { const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); job->mInput = serialize(mRecastSettings, job->mAgentBounds, *job->mRecastMesh, objects); } else { const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v); }); if (!objects.has_value()) return; job->mInput = serialize(mRecastSettings, job->mAgentBounds, *job->mRecastMesh, *objects); } } job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput); ++mGetTileCount; } void DbWorker::processWritingJob(JobIt job) { if (!mWriteToDb) { Log(Debug::Debug) << "Ignored db write job " << job->mId; return; } Log(Debug::Debug) << "Processing db write job " << job->mId; if (job->mInput.empty()) { Log(Debug::Debug) << "Serializing input for job " << job->mId; const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); job->mInput = serialize(mRecastSettings, job->mAgentBounds, *job->mRecastMesh, objects); } if (const auto& cachedTileData = job->mCachedTileData) { Log(Debug::Debug) << "Update db tile by job " << job->mId; job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId; mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData)); return; } const auto cached = mDb->findTile(job->mWorldspace, job->mChangedTile, job->mInput); if (cached.has_value() && cached->mVersion == mVersion) { Log(Debug::Debug) << "Ignore existing db tile by job " << job->mId; return; } job->mGeneratedNavMeshData->mUserId = mNextTileId; Log(Debug::Debug) << "Insert db tile by job " << job->mId; mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); ++mNextTileId; } } openmw-openmw-0.48.0/components/detournavigator/asyncnavmeshupdater.hpp000066400000000000000000000155021445372753700266360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "navmeshcacheitem.hpp" #include "offmeshconnectionsmanager.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "waitconditiontype.hpp" #include "navmeshdb.hpp" #include "changetype.hpp" #include "agentbounds.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include class dtNavMesh; namespace Loading { class Listener; } namespace DetourNavigator { enum class JobState { Initial, WithDbResult, }; struct Job { const std::size_t mId; const AgentBounds mAgentBounds; const std::weak_ptr mNavMeshCacheItem; const std::string mWorldspace; const TilePosition mChangedTile; const std::chrono::steady_clock::time_point mProcessTime; unsigned mTryNumber = 0; ChangeType mChangeType; int mDistanceToPlayer; const int mDistanceToOrigin; JobState mState = JobState::Initial; std::vector mInput; std::shared_ptr mRecastMesh; std::optional mCachedTileData; std::unique_ptr mGeneratedNavMeshData; Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; enum class JobStatus { Done, Fail, MemoryCacheMiss, }; std::ostream& operator<<(std::ostream& stream, JobStatus value); class DbJobQueue { public: void push(JobIt job); std::optional pop(); void update(TilePosition playerTile, int maxTiles); void stop(); std::size_t size() const; private: mutable std::mutex mMutex; std::condition_variable mHasJob; std::deque mJobs; bool mShouldStop = false; }; class AsyncNavMeshUpdater; class DbWorker { public: struct Stats { std::size_t mJobs = 0; std::size_t mGetTileCount = 0; }; DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, const RecastSettings& recastSettings, bool writeToDb); ~DbWorker(); Stats getStats() const; void enqueueJob(JobIt job); void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } void stop(); private: AsyncNavMeshUpdater& mUpdater; const RecastSettings& mRecastSettings; const std::unique_ptr mDb; const TileVersion mVersion; bool mWriteToDb; TileId mNextTileId; ShapeId mNextShapeId; DbJobQueue mQueue; std::atomic_bool mShouldStop {false}; std::atomic_size_t mGetTileCount {0}; std::thread mThread; inline void run() noexcept; inline void processJob(JobIt job); inline void processReadingJob(JobIt job); inline void processWritingJob(JobIt job); }; class AsyncNavMeshUpdater { public: struct Stats { std::size_t mJobs = 0; std::size_t mWaiting = 0; std::size_t mPushed = 0; std::size_t mProcessing = 0; std::size_t mDbGetTileHits = 0; std::optional mDb; NavMeshTilesCache::Stats mCache; }; AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db); ~AsyncNavMeshUpdater(); void post(const AgentBounds& agentBounds, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, std::string_view worldspace, const std::map& changedTiles); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void stop(); Stats getStats() const; void enqueueJob(JobIt job); void removeJob(JobIt job); private: std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; std::atomic_bool mShouldStop; mutable std::mutex mMutex; std::condition_variable mHasJob; std::condition_variable mDone; std::condition_variable mProcessed; std::list mJobs; std::deque mWaiting; std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; std::vector mThreads; std::unique_ptr mDbWorker; std::atomic_size_t mDbGetTileHits {0}; void process() noexcept; JobStatus processJob(Job& job); inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh); JobIt getNextJob(); void postThreadJob(JobIt job, std::deque& queue); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; void repost(JobIt job); bool lockTile(const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(const AgentBounds& agentBounds, const TilePosition& changedTile); inline std::size_t getTotalJobs() const; void cleanupLastUpdates(); int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); void waitUntilAllJobsDone(); }; void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif openmw-openmw-0.48.0/components/detournavigator/bounds.hpp000066400000000000000000000005271445372753700240450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H #include namespace DetourNavigator { struct Bounds { osg::Vec3f mMin; osg::Vec3f mMax; }; inline bool isEmpty(const Bounds& value) { return value.mMin == value.mMax; } } #endif openmw-openmw-0.48.0/components/detournavigator/cachedrecastmeshmanager.cpp000066400000000000000000000062741445372753700273740ustar00rootroot00000000000000#include "cachedrecastmeshmanager.hpp" #include "debug.hpp" namespace DetourNavigator { CachedRecastMeshManager::CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation) : mImpl(bounds, generation) {} bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mImpl.addObject(id, shape, transform, areaType)) return false; mOutdatedCache = true; return true; } bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { if (!mImpl.updateObject(id, transform, areaType)) return false; mOutdatedCache = true; return true; } std::optional CachedRecastMeshManager::removeObject(const ObjectId id) { auto object = mImpl.removeObject(id); if (object) mOutdatedCache = true; return object; } bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { if (!mImpl.addWater(cellPosition, cellSize, level)) return false; mOutdatedCache = true; return true; } std::optional CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto water = mImpl.removeWater(cellPosition); if (water) mOutdatedCache = true; return water; } bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { if (!mImpl.addHeightfield(cellPosition, cellSize, shape)) return false; mOutdatedCache = true; return true; } std::optional CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const auto heightfield = mImpl.removeHeightfield(cellPosition); if (heightfield) mOutdatedCache = true; return heightfield; } std::shared_ptr CachedRecastMeshManager::getMesh() { bool outdated = true; if (!mOutdatedCache.compare_exchange_strong(outdated, false)) { std::shared_ptr cached = getCachedMesh(); if (cached != nullptr) return cached; } std::shared_ptr mesh = mImpl.getMesh(); *mCached.lock() = mesh; return mesh; } std::shared_ptr CachedRecastMeshManager::getCachedMesh() const { return *mCached.lockConst(); } std::shared_ptr CachedRecastMeshManager::getNewMesh() const { return mImpl.getMesh(); } bool CachedRecastMeshManager::isEmpty() const { return mImpl.isEmpty(); } void CachedRecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion) { mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); } Version CachedRecastMeshManager::getVersion() const { return mImpl.getVersion(); } } openmw-openmw-0.48.0/components/detournavigator/cachedrecastmeshmanager.hpp000066400000000000000000000031771445372753700274000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H #include "recastmeshmanager.hpp" #include "version.hpp" #include "heightfieldshape.hpp" #include #include namespace DetourNavigator { class CachedRecastMeshManager { public: explicit CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); std::optional removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh(); std::shared_ptr getCachedMesh() const; std::shared_ptr getNewMesh() const; bool isEmpty() const; void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); Version getVersion() const; private: RecastMeshManager mImpl; Misc::ScopeGuarded> mCached; std::atomic_bool mOutdatedCache {true}; }; } #endif openmw-openmw-0.48.0/components/detournavigator/changetype.hpp000066400000000000000000000004101445372753700246710ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H namespace DetourNavigator { enum class ChangeType { remove = 0, mixed = 1, add = 2, update = 3, }; } #endif openmw-openmw-0.48.0/components/detournavigator/collisionshapetype.cpp000066400000000000000000000011401445372753700264540ustar00rootroot00000000000000#include "collisionshapetype.hpp" #include #include namespace DetourNavigator { CollisionShapeType toCollisionShapeType(int value) { switch (static_cast(value)) { case CollisionShapeType::Aabb: case CollisionShapeType::RotatingBox: case CollisionShapeType::Cylinder: return static_cast(value); } std::string error("Invalid CollisionShapeType value: \""); error += value; error += '"'; throw std::invalid_argument(error); } } openmw-openmw-0.48.0/components/detournavigator/collisionshapetype.hpp000066400000000000000000000005611445372753700264670ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_COLLISIONSHAPETYPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_COLLISIONSHAPETYPE_H #include namespace DetourNavigator { enum class CollisionShapeType : std::uint8_t { Aabb = 0, RotatingBox = 1, Cylinder = 2, }; CollisionShapeType toCollisionShapeType(int value); } #endif openmw-openmw-0.48.0/components/detournavigator/dbrefgeometryobject.hpp000066400000000000000000000035761445372753700266070ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H #include "objecttransform.hpp" #include "recastmesh.hpp" #include #include #include #include #include #include #include namespace DetourNavigator { struct DbRefGeometryObject { std::int64_t mShapeId; ObjectTransform mObjectTransform; friend inline auto tie(const DbRefGeometryObject& v) { return std::tie(v.mShapeId, v.mObjectTransform); } friend inline bool operator<(const DbRefGeometryObject& l, const DbRefGeometryObject& r) { return tie(l) < tie(r); } }; template inline auto makeDbRefGeometryObjects(const std::vector& meshSources, ResolveMeshSource&& resolveMeshSource) -> std::conditional_t< Misc::isOptional>, std::optional>, std::vector > { std::vector result; result.reserve(meshSources.size()); for (const MeshSource& meshSource : meshSources) { const auto shapeId = resolveMeshSource(meshSource); if constexpr (Misc::isOptional>) { if (!shapeId.has_value()) return std::nullopt; result.push_back(DbRefGeometryObject {*shapeId, meshSource.mObjectTransform}); } else result.push_back(DbRefGeometryObject {shapeId, meshSource.mObjectTransform}); } std::sort(result.begin(), result.end()); return result; } } #endif openmw-openmw-0.48.0/components/detournavigator/debug.cpp000066400000000000000000000232541445372753700236360ustar00rootroot00000000000000#include "debug.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include #include #include #include #include #include #include #include #include namespace DetourNavigator { std::ostream& operator<<(std::ostream& stream, const TileBounds& value) { return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}"; } std::ostream& operator<<(std::ostream& stream, Status value) { #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(name) \ case Status::name: return stream << "DetourNavigator::Status::"#name; switch (value) { OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(Success) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(PartialPath) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(NavMeshNotFound) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(StartPolygonNotFound) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(EndPolygonNotFound) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(MoveAlongSurfaceFailed) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(FindPathOverPolygonsFailed) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(GetPolyHeightFailed) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(InitNavMeshQueryFailed) } #undef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE return stream << "DetourNavigator::Error::" << static_cast(value); } std::ostream& operator<<(std::ostream& s, const Water& v) { return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; } std::ostream& operator<<(std::ostream& s, const CellWater& v) { return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; } std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) { return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; } std::ostream& operator<<(std::ostream& s, const Heightfield& v) { s << "Heightfield {.mCellPosition=" << v.mCellPosition << ", .mCellSize=" << v.mCellSize << ", .mLength=" << static_cast(v.mLength) << ", .mMinHeight=" << v.mMinHeight << ", .mMaxHeight=" << v.mMaxHeight << ", .mHeights={"; for (float h : v.mHeights) s << h << ", "; s << "}"; return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; } std::ostream& operator<<(std::ostream& s, CollisionShapeType v) { switch (v) { case CollisionShapeType::Aabb: return s << "AgentShapeType::Aabb"; case CollisionShapeType::RotatingBox: return s << "AgentShapeType::RotatingBox"; case CollisionShapeType::Cylinder: return s << "AgentShapeType::Cylinder"; } return s << "AgentShapeType::" << static_cast>(v); } std::ostream& operator<<(std::ostream& s, const AgentBounds& v) { return s << "AgentBounds {" << v.mShapeType << ", " << v.mHalfExtents << "}"; } namespace { struct StatusString { dtStatus mStatus; std::string_view mString; }; } static constexpr std::array dtStatuses { StatusString {DT_FAILURE, "DT_FAILURE"}, StatusString {DT_SUCCESS, "DT_SUCCESS"}, StatusString {DT_IN_PROGRESS, "DT_IN_PROGRESS"}, StatusString {DT_WRONG_MAGIC, "DT_WRONG_MAGIC"}, StatusString {DT_WRONG_VERSION, "DT_WRONG_VERSION"}, StatusString {DT_OUT_OF_MEMORY, "DT_OUT_OF_MEMORY"}, StatusString {DT_INVALID_PARAM, "DT_INVALID_PARAM"}, StatusString {DT_BUFFER_TOO_SMALL, "DT_BUFFER_TOO_SMALL"}, StatusString {DT_OUT_OF_NODES, "DT_OUT_OF_NODES"}, StatusString {DT_PARTIAL_RESULT, "DT_PARTIAL_RESULT"}, }; std::ostream& operator<<(std::ostream& stream, const WriteDtStatus& value) { for (const auto& status : dtStatuses) if (value.mStatus & status.mStatus) stream << status.mString; return stream; } std::ostream& operator<<(std::ostream& stream, const Flag value) { switch (value) { case Flag_none: return stream << "none"; case Flag_walk: return stream << "walk"; case Flag_swim: return stream << "swim"; case Flag_openDoor: return stream << "openDoor"; case Flag_usePathgrid: return stream << "usePathgrid"; } return stream; } std::ostream& operator<<(std::ostream& stream, const WriteFlags& value) { if (value.mValue == Flag_none) { return stream << Flag_none; } else { bool first = true; for (const auto flag : {Flag_walk, Flag_swim, Flag_openDoor, Flag_usePathgrid}) { if (value.mValue & flag) { if (!first) stream << " | "; first = false; stream << flag; } } return stream; } } std::ostream& operator<<(std::ostream& stream, AreaType value) { switch (value) { case AreaType_null: return stream << "null"; case AreaType_water: return stream << "water"; case AreaType_door: return stream << "door"; case AreaType_pathgrid: return stream << "pathgrid"; case AreaType_ground: return stream << "ground"; } return stream << "unknown area type (" << static_cast>(value) << ")"; } std::ostream& operator<<(std::ostream& stream, ChangeType value) { switch (value) { case ChangeType::remove: return stream << "ChangeType::remove"; case ChangeType::mixed: return stream << "ChangeType::mixed"; case ChangeType::add: return stream << "ChangeType::add"; case ChangeType::update: return stream << "ChangeType::update"; } return stream << "ChangeType::" << static_cast(value); } void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const RecastSettings& settings) { const auto path = pathPrefix + "recastmesh" + revision + ".obj"; boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); file.precision(std::numeric_limits::max_exponent10); std::vector vertices = recastMesh.getMesh().getVertices(); for (std::size_t i = 0; i < vertices.size(); i += 3) { file << "v " << toNavMeshCoordinates(settings, vertices[i]) << ' ' << toNavMeshCoordinates(settings, vertices[i + 2]) << ' ' << toNavMeshCoordinates(settings, vertices[i + 1]) << '\n'; } std::size_t count = 0; for (int v : recastMesh.getMesh().getIndices()) { if (count % 3 == 0) { if (count != 0) file << '\n'; file << 'f'; } file << ' ' << (v + 1); ++count; } file << '\n'; } void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision) { const int navMeshSetMagic = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; const int navMeshSetVersion = 1; struct NavMeshSetHeader { int magic; int version; int numTiles; dtNavMeshParams params; }; struct NavMeshTileHeader { dtTileRef tileRef; int dataSize; }; const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin"; boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out | std::ios::binary); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); NavMeshSetHeader header; header.magic = navMeshSetMagic; header.version = navMeshSetVersion; header.numTiles = 0; for (int i = 0; i < navMesh.getMaxTiles(); ++i) { const auto tile = navMesh.getTile(i); if (!tile || !tile->header || !tile->dataSize) continue; header.numTiles++; } header.params = *navMesh.getParams(); using const_char_ptr = const char*; file.write(const_char_ptr(&header), sizeof(header)); for (int i = 0; i < navMesh.getMaxTiles(); ++i) { const auto tile = navMesh.getTile(i); if (!tile || !tile->header || !tile->dataSize) continue; NavMeshTileHeader tileHeader; tileHeader.tileRef = navMesh.getTileRef(tile); tileHeader.dataSize = tile->dataSize; file.write(const_char_ptr(&tileHeader), sizeof(tileHeader)); file.write(const_char_ptr(tile->data), tile->dataSize); } } } openmw-openmw-0.48.0/components/detournavigator/debug.hpp000066400000000000000000000033361445372753700236420ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H #include "tilebounds.hpp" #include "status.hpp" #include "recastmesh.hpp" #include "agentbounds.hpp" #include "flags.hpp" #include "areatype.hpp" #include "changetype.hpp" #include #include #include class dtNavMesh; namespace DetourNavigator { std::ostream& operator<<(std::ostream& stream, const TileBounds& value); std::ostream& operator<<(std::ostream& stream, Status value); std::ostream& operator<<(std::ostream& s, const Water& v); std::ostream& operator<<(std::ostream& s, const CellWater& v); std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v); std::ostream& operator<<(std::ostream& s, const Heightfield& v); std::ostream& operator<<(std::ostream& s, CollisionShapeType v); std::ostream& operator<<(std::ostream& s, const AgentBounds& v); struct WriteDtStatus { dtStatus mStatus; }; std::ostream& operator<<(std::ostream& stream, const WriteDtStatus& value); std::ostream& operator<<(std::ostream& stream, const Flag value); struct WriteFlags { Flags mValue; }; std::ostream& operator<<(std::ostream& stream, const WriteFlags& value); std::ostream& operator<<(std::ostream& stream, AreaType value); std::ostream& operator<<(std::ostream& stream, ChangeType value); class RecastMesh; struct RecastSettings; void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const RecastSettings& settings); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } #endif openmw-openmw-0.48.0/components/detournavigator/exceptions.hpp000066400000000000000000000011761445372753700247350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H #include namespace DetourNavigator { struct NavigatorException : std::runtime_error { NavigatorException(const std::string& message) : std::runtime_error(message) {} NavigatorException(const char* message) : std::runtime_error(message) {} }; struct InvalidArgument : std::invalid_argument { InvalidArgument(const std::string& message) : std::invalid_argument(message) {} InvalidArgument(const char* message) : std::invalid_argument(message) {} }; } #endif openmw-openmw-0.48.0/components/detournavigator/findrandompointaroundcircle.cpp000066400000000000000000000023641445372753700303350ustar00rootroot00000000000000#include "findrandompointaroundcircle.hpp" #include "settings.hpp" #include "findsmoothpath.hpp" #include #include #include namespace DetourNavigator { std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings, float(*prng)()) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) return std::optional(); dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); dtPolyRef startRef = findNearestPoly(navMeshQuery, queryFilter, start, halfExtents * 4); if (startRef == 0) return std::optional(); dtPolyRef resultRef = 0; osg::Vec3f resultPosition; navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter, prng, &resultRef, resultPosition.ptr()); if (resultRef == 0) return std::optional(); return std::optional(resultPosition); } } openmw-openmw-0.48.0/components/detournavigator/findrandompointaroundcircle.hpp000066400000000000000000000010331445372753700303320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDRANDOMPOINTAROUNDCIRCLE_H #include "flags.hpp" #include #include class dtNavMesh; namespace DetourNavigator { struct DetourSettings; std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings, float(*prng)()); } #endif openmw-openmw-0.48.0/components/detournavigator/findsmoothpath.cpp000066400000000000000000000124671445372753700256030ustar00rootroot00000000000000#include "findsmoothpath.hpp" #include #include #include namespace DetourNavigator { std::size_t fixupCorridor(std::vector& path, std::size_t pathSize, const std::vector& visited) { std::vector::const_reverse_iterator furthestVisited; // Find furthest common polygon. const auto begin = path.begin(); const auto end = path.begin() + pathSize; const std::reverse_iterator rbegin(end); const std::reverse_iterator rend(begin); const auto it = std::find_if(rbegin, rend, [&] (dtPolyRef pathValue) { const auto it = std::find(visited.rbegin(), visited.rend(), pathValue); if (it == visited.rend()) return false; furthestVisited = it; return true; }); // If no intersection found just return current path. if (it == rend) return pathSize; const auto furthestPath = it.base() - 1; // Concatenate paths. // visited: a_1 ... a_n x b_1 ... b_n // furthestVisited ^ // path: C x D E // ^ furthestPath ^ path.size() - (furthestVisited + 1 - visited.rbegin()) // result: x b_n ... b_1 D const std::size_t required = static_cast(furthestVisited + 1 - visited.rbegin()); const auto newEnd = std::copy(furthestPath + 1, std::min(begin + path.size(), end), begin + required); std::copy(visited.rbegin(), furthestVisited + 1, begin); return static_cast(newEnd - begin); } std::size_t fixupShortcuts(dtPolyRef* path, std::size_t pathSize, const dtNavMeshQuery& navQuery) { if (pathSize < 3) return pathSize; // Get connected polygons const dtMeshTile* tile = nullptr; const dtPoly* poly = nullptr; if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly))) return pathSize; const std::size_t maxNeis = 16; std::array neis; std::size_t nneis = 0; for (unsigned int k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) { const dtLink* link = &tile->links[k]; if (link->ref != 0) { if (nneis < maxNeis) neis[nneis++] = link->ref; } } // If any of the neighbour polygons is within the next few polygons // in the path, short cut to that polygon directly. const std::size_t maxLookAhead = 6; std::size_t cut = 0; for (std::size_t i = std::min(maxLookAhead, pathSize) - 1; i > 1 && cut == 0; i--) { for (std::size_t j = 0; j < nneis; j++) { if (path[i] == neis[j]) { cut = i; break; } } } if (cut <= 1) return pathSize; const std::ptrdiff_t offset = static_cast(cut) - 1; std::copy(path + offset, path + pathSize, path); return pathSize - offset; } std::optional getSteerTarget(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const float minTargetDist, const dtPolyRef* path, const std::size_t pathSize) { // Find steer target. SteerTarget result; constexpr int maxSteerPoints = 3; std::array steerPath; std::array steerPathFlags; std::array steerPathPolys; int nsteerPath = 0; const dtStatus status = navMeshQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path, static_cast(pathSize), steerPath.data(), steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, maxSteerPoints); if (dtStatusFailed(status)) return std::nullopt; assert(nsteerPath >= 0); if (!nsteerPath) return std::nullopt; // Find vertex far enough to steer to. std::size_t ns = 0; while (ns < static_cast(nsteerPath)) { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || !inRange(Misc::Convert::makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist)) break; ns++; } // Failed to find good point to steer to. if (ns >= static_cast(nsteerPath)) return std::nullopt; dtVcopy(result.mSteerPos.ptr(), &steerPath[ns * 3]); result.mSteerPos.y() = startPos[1]; result.mSteerPosFlag = steerPathFlags[ns]; result.mSteerPosRef = steerPathPolys[ns]; return result; } dtPolyRef findNearestPoly(const dtNavMeshQuery& query, const dtQueryFilter& filter, const osg::Vec3f& center, const osg::Vec3f& halfExtents) { dtPolyRef ref = 0; const dtStatus status = query.findNearestPoly(center.ptr(), halfExtents.ptr(), &filter, &ref, nullptr); if (!dtStatusSucceed(status)) return 0; return ref; } } openmw-openmw-0.48.0/components/detournavigator/findsmoothpath.hpp000066400000000000000000000272651445372753700256120ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #include "exceptions.hpp" #include "flags.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "status.hpp" #include "areatype.hpp" #include #include #include #include #include #include #include class dtNavMesh; namespace DetourNavigator { struct Settings; inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r) { return (osg::Vec2f(v1.x(), v1.z()) - osg::Vec2f(v2.x(), v2.z())).length() < r; } std::size_t fixupCorridor(std::vector& path, std::size_t pathSize, const std::vector& visited); // This function checks if the path has a small U-turn, that is, // a polygon further in the path is adjacent to the first polygon // in the path. If that happens, a shortcut is taken. // This can happen if the target (T) location is at tile boundary, // and we're (S) approaching it parallel to the tile edge. // The choice at the vertex can be arbitrary, // +---+---+ // |:::|:::| // +-S-+-T-+ // |:::| | <-- the step can end up in here, resulting U-turn path. // +---+---+ std::size_t fixupShortcuts(dtPolyRef* path, std::size_t pathSize, const dtNavMeshQuery& navQuery); struct SteerTarget { osg::Vec3f mSteerPos; unsigned char mSteerPosFlag; dtPolyRef mSteerPosRef; }; std::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const float minTargetDist, const dtPolyRef* path, const std::size_t pathSize); template class OutputTransformIterator { public: explicit OutputTransformIterator(OutputIterator& impl, const RecastSettings& settings) : mImpl(impl), mSettings(settings) { } OutputTransformIterator& operator *() { return *this; } OutputTransformIterator& operator ++() { ++mImpl.get(); return *this; } OutputTransformIterator operator ++(int) { const auto copy = *this; ++(*this); return copy; } OutputTransformIterator& operator =(const osg::Vec3f& value) { *mImpl.get() = fromNavMeshCoordinates(mSettings, value); return *this; } private: std::reference_wrapper mImpl; std::reference_wrapper mSettings; }; inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) { const auto status = value.init(&navMesh, maxNodes); return dtStatusSucceed(status); } dtPolyRef findNearestPoly(const dtNavMeshQuery& query, const dtQueryFilter& filter, const osg::Vec3f& center, const osg::Vec3f& halfExtents); struct MoveAlongSurfaceResult { osg::Vec3f mResultPos; std::vector mVisited; }; inline std::optional moveAlongSurface(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& filter, const std::size_t maxVisitedSize) { MoveAlongSurfaceResult result; result.mVisited.resize(maxVisitedSize); int visitedNumber = 0; const auto status = navMeshQuery.moveAlongSurface(startRef, startPos.ptr(), endPos.ptr(), &filter, result.mResultPos.ptr(), result.mVisited.data(), &visitedNumber, static_cast(maxVisitedSize)); if (!dtStatusSucceed(status)) return {}; assert(visitedNumber >= 0); assert(visitedNumber <= static_cast(maxVisitedSize)); result.mVisited.resize(static_cast(visitedNumber)); return {std::move(result)}; } inline std::optional findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, dtPolyRef* path, const std::size_t maxSize) { int pathLen = 0; const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, path, &pathLen, static_cast(maxSize)); if (!dtStatusSucceed(status)) return {}; assert(pathLen >= 0); assert(static_cast(pathLen) <= maxSize); return static_cast(pathLen); } template Status makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, const float stepSize, std::vector& polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, OutputIterator& out) { // Iterate over the path to find smooth path on the detail mesh surface. osg::Vec3f iterPos; navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), nullptr); osg::Vec3f targetPos; navMeshQuery.closestPointOnPoly(polygonPath[polygonPathSize - 1], end.ptr(), targetPos.ptr(), nullptr); constexpr float slop = 0.01f; *out++ = iterPos; std::size_t smoothPathSize = 1; // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. while (polygonPathSize > 0 && smoothPathSize < maxSmoothPathSize) { // Find location to steer towards. const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, slop, polygonPath.data(), polygonPathSize); if (!steerTarget) break; const bool endOfPath = bool(steerTarget->mSteerPosFlag & DT_STRAIGHTPATH_END); const bool offMeshConnection = bool(steerTarget->mSteerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); // Find movement delta. const osg::Vec3f delta = steerTarget->mSteerPos - iterPos; float len = delta.length(); // If the steer target is end of path or off-mesh link, do not move past the location. if ((endOfPath || offMeshConnection) && len < stepSize) len = 1; else len = stepSize / len; const osg::Vec3f moveTgt = iterPos + delta * len; const auto result = moveAlongSurface(navMeshQuery, polygonPath.front(), iterPos, moveTgt, filter, 16); if (!result) return Status::MoveAlongSurfaceFailed; polygonPathSize = fixupCorridor(polygonPath, polygonPathSize, result->mVisited); polygonPathSize = fixupShortcuts(polygonPath.data(), polygonPathSize, navMeshQuery); // Handle end of path and off-mesh links when close enough. if (endOfPath && inRange(result->mResultPos, steerTarget->mSteerPos, slop)) { // Reached end of path. iterPos = targetPos; *out++ = iterPos; ++smoothPathSize; break; } dtPolyRef polyRef = polygonPath.front(); osg::Vec3f polyPos = result->mResultPos; if (offMeshConnection && inRange(polyPos, steerTarget->mSteerPos, slop)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; std::size_t npos = 0; while (npos < polygonPathSize && polyRef != steerTarget->mSteerPosRef) { prevRef = polyRef; polyRef = polygonPath[npos]; ++npos; } if (npos > 0) { std::copy(polygonPath.begin() + npos, polygonPath.begin() + polygonPathSize, polygonPath.begin()); polygonPathSize -= npos; } // Reached off-mesh connection. osg::Vec3f startPos; osg::Vec3f endPos; // Handle the connection. if (dtStatusSucceed(navMesh.getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos.ptr(), endPos.ptr()))) { *out++ = startPos; ++smoothPathSize; // Hack to make the dotted path not visible during off-mesh connection. if (smoothPathSize & 1) { *out++ = startPos; ++smoothPathSize; } // Move position at the other side of the off-mesh link. polyPos = endPos; } } if (dtStatusFailed(navMeshQuery.getPolyHeight(polyRef, polyPos.ptr(), &iterPos.y()))) return Status::GetPolyHeightFailed; iterPos.x() = result->mResultPos.x(); iterPos.z() = result->mResultPos.z(); // Store results. *out++ = iterPos; ++smoothPathSize; } return Status::Success; } template Status findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const Settings& settings, float endTolerance, OutputIterator out) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mDetour.mMaxNavMeshQueryNodes)) return Status::InitNavMeshQueryFailed; dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); queryFilter.setAreaCost(AreaType_water, areaCosts.mWater); queryFilter.setAreaCost(AreaType_door, areaCosts.mDoor); queryFilter.setAreaCost(AreaType_pathgrid, areaCosts.mPathgrid); queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); constexpr float polyDistanceFactor = 4; const osg::Vec3f polyHalfExtents = halfExtents * polyDistanceFactor; const dtPolyRef startRef = findNearestPoly(navMeshQuery, queryFilter, start, polyHalfExtents); if (startRef == 0) return Status::StartPolygonNotFound; const dtPolyRef endRef = findNearestPoly(navMeshQuery, queryFilter, end, polyHalfExtents + osg::Vec3f(endTolerance, endTolerance, endTolerance)); if (endRef == 0) return Status::EndPolygonNotFound; std::vector polygonPath(settings.mDetour.mMaxPolygonPathSize); const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, polygonPath.data(), polygonPath.size()); if (!polygonPathSize.has_value()) return Status::FindPathOverPolygonsFailed; if (*polygonPathSize == 0) return Status::Success; const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef; auto outTransform = OutputTransformIterator(out, settings.mRecast); const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, polygonPath, *polygonPathSize, settings.mDetour.mMaxSmoothPathSize, outTransform); if (smoothStatus != Status::Success) return smoothStatus; return partialPath ? Status::PartialPath : Status::Success; } } #endif openmw-openmw-0.48.0/components/detournavigator/flags.hpp000066400000000000000000000005431445372753700236450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H namespace DetourNavigator { using Flags = unsigned short; enum Flag : Flags { Flag_none = 0, Flag_walk = 1 << 0, Flag_swim = 1 << 1, Flag_openDoor = 1 << 2, Flag_usePathgrid = 1 << 3, }; } #endif openmw-openmw-0.48.0/components/detournavigator/generatenavmeshtile.cpp000066400000000000000000000064431445372753700266030ustar00rootroot00000000000000#include "generatenavmeshtile.hpp" #include "dbrefgeometryobject.hpp" #include "makenavmesh.hpp" #include "offmeshconnectionsmanager.hpp" #include "preparednavmeshdata.hpp" #include "serialization.hpp" #include "settings.hpp" #include "tilecachedrecastmeshmanager.hpp" #include #include #include #include #include #include #include #include namespace DetourNavigator { namespace { struct Ignore { std::string_view mWorldspace; const TilePosition& mTilePosition; std::shared_ptr mConsumer; ~Ignore() noexcept { if (mConsumer != nullptr) mConsumer->ignore(mWorldspace, mTilePosition); } }; } GenerateNavMeshTile::GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, RecastMeshProvider recastMeshProvider, const AgentBounds& agentBounds, const DetourNavigator::Settings& settings, std::weak_ptr consumer) : mWorldspace(std::move(worldspace)) , mTilePosition(tilePosition) , mRecastMeshProvider(recastMeshProvider) , mAgentBounds(agentBounds) , mSettings(settings) , mConsumer(std::move(consumer)) {} void GenerateNavMeshTile::doWork() { impl(); } void GenerateNavMeshTile::impl() noexcept { const auto consumer = mConsumer.lock(); if (consumer == nullptr) return; try { Ignore ignore {mWorldspace, mTilePosition, consumer}; const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition); if (recastMesh == nullptr || isEmpty(*recastMesh)) return; const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), [&] (const MeshSource& v) { return consumer->resolveMeshSource(v); }); std::vector input = serialize(mSettings.mRecast, mAgentBounds, *recastMesh, objects); const std::optional info = consumer->find(mWorldspace, mTilePosition, input); if (info.has_value() && info->mVersion == navMeshFormatVersion) { consumer->identity(mWorldspace, mTilePosition, info->mTileId); ignore.mConsumer = nullptr; return; } const auto data = prepareNavMeshTileData(*recastMesh, mTilePosition, mAgentBounds, mSettings.mRecast); if (data == nullptr) return; if (info.has_value()) consumer->update(mWorldspace, mTilePosition, info->mTileId, navMeshFormatVersion, *data); else consumer->insert(mWorldspace, mTilePosition, navMeshFormatVersion, input, *data); ignore.mConsumer = nullptr; } catch (const std::exception& e) { Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace << "\" tile " << mTilePosition << ": " << e.what(); consumer->cancel(e.what()); } } } openmw-openmw-0.48.0/components/detournavigator/generatenavmeshtile.hpp000066400000000000000000000046551445372753700266130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H #include "recastmeshprovider.hpp" #include "tileposition.hpp" #include "agentbounds.hpp" #include #include #include #include #include #include #include #include namespace DetourNavigator { class OffMeshConnectionsManager; class RecastMesh; struct NavMeshTileConsumer; struct OffMeshConnection; struct PreparedNavMeshData; struct Settings; struct NavMeshTileInfo { std::int64_t mTileId; std::int64_t mVersion; }; struct NavMeshTileConsumer { virtual ~NavMeshTileConsumer() = default; virtual std::int64_t resolveMeshSource(const MeshSource& source) = 0; virtual std::optional find(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) = 0; virtual void ignore(std::string_view worldspace, const TilePosition& tilePosition) = 0; virtual void identity(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId) = 0; virtual void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) = 0; virtual void update(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; virtual void cancel(std::string_view reason) = 0; }; class GenerateNavMeshTile final : public SceneUtil::WorkItem { public: GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, RecastMeshProvider recastMeshProvider, const AgentBounds& agentBounds, const Settings& settings, std::weak_ptr consumer); void doWork() final; private: const std::string mWorldspace; const TilePosition mTilePosition; const RecastMeshProvider mRecastMeshProvider; const AgentBounds mAgentBounds; const Settings& mSettings; std::weak_ptr mConsumer; inline void impl() noexcept; }; } #endif openmw-openmw-0.48.0/components/detournavigator/gettilespositions.cpp000066400000000000000000000060571445372753700263420ustar00rootroot00000000000000#include "gettilespositions.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" #include "tilebounds.hpp" #include #include namespace DetourNavigator { TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, const osg::Vec2f& aabbMax, const RecastSettings& settings) { osg::Vec2f min = toNavMeshCoordinates(settings, aabbMin); osg::Vec2f max = toNavMeshCoordinates(settings, aabbMax); const float border = getBorderSize(settings); min -= osg::Vec2f(border, border); max += osg::Vec2f(border, border); TilePosition minTile = getTilePosition(settings, min); TilePosition maxTile = getTilePosition(settings, max); if (minTile.x() > maxTile.x()) std::swap(minTile.x(), maxTile.x()); if (minTile.y() > maxTile.y()) std::swap(minTile.y(), maxTile.y()); return {minTile, maxTile + osg::Vec2i(1, 1)}; } TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings) { const TileBounds bounds = makeObjectTileBounds(shape, transform); return makeTilesPositionsRange(bounds.mMin, bounds.mMax, settings); } TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings) { if (const auto intersection = getIntersection(bounds, makeObjectTileBounds(shape, transform))) return makeTilesPositionsRange(intersection->mMin, intersection->mMax, settings); return {}; } TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings) { const int halfCellSize = cellSize / 2; const btTransform transform(btMatrix3x3::getIdentity(), shift); btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); btVector3 aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); return makeTilesPositionsRange(Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax), settings); } TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept { const int beginX = std::max(a.mBegin.x(), b.mBegin.x()); const int endX = std::min(a.mEnd.x(), b.mEnd.x()); if (beginX > endX) return {}; const int beginY = std::max(a.mBegin.y(), b.mBegin.y()); const int endY = std::min(a.mEnd.y(), b.mEnd.y()); if (beginY > endY) return {}; return TilesPositionsRange {TilePosition(beginX, beginY), TilePosition(endX, endY)}; } } openmw-openmw-0.48.0/components/detournavigator/gettilespositions.hpp000066400000000000000000000035221445372753700263410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #include "tilebounds.hpp" #include "tileposition.hpp" #include "tilespositionsrange.hpp" class btVector3; class btTransform; class btCollisionShape; namespace osg { class Vec2f; } namespace DetourNavigator { struct RecastSettings; TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, const osg::Vec2f& aabbMax, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings); template inline void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) { for (int tileX = range.mBegin.x(); tileX < range.mEnd.x(); ++tileX) for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY) callback(TilePosition {tileX, tileY}); } inline bool isInTilesPositionsRange(int begin, int end, int coordinate) { return begin <= coordinate && coordinate < end; } inline bool isInTilesPositionsRange(const TilesPositionsRange& range, const TilePosition& position) { return isInTilesPositionsRange(range.mBegin.x(), range.mEnd.x(), position.x()) && isInTilesPositionsRange(range.mBegin.y(), range.mEnd.y(), position.y()); } TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; } #endif openmw-openmw-0.48.0/components/detournavigator/heightfieldshape.hpp000066400000000000000000000024361445372753700260510ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H #include #include #include #include namespace DetourNavigator { struct HeightfieldPlane { float mHeight; }; struct HeightfieldSurface { const float* mHeights; std::size_t mSize; float mMinHeight; float mMaxHeight; }; using HeightfieldShape = std::variant; inline btVector3 getHeightfieldShift(const HeightfieldPlane& v, const osg::Vec2i& cellPosition, int cellSize) { return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mHeight, v.mHeight); } inline btVector3 getHeightfieldShift(const HeightfieldSurface& v, const osg::Vec2i& cellPosition, int cellSize) { return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mMinHeight, v.mMaxHeight); } inline btVector3 getHeightfieldShift(const HeightfieldShape& v, const osg::Vec2i& cellPosition, int cellSize) { return std::visit([&] (const auto& w) { return getHeightfieldShift(w, cellPosition, cellSize); }, v); } } #endif openmw-openmw-0.48.0/components/detournavigator/makenavmesh.cpp000066400000000000000000000521471445372753700250520ustar00rootroot00000000000000#include "makenavmesh.hpp" #include "debug.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "sharednavmesh.hpp" #include "flags.hpp" #include "navmeshtilescache.hpp" #include "preparednavmeshdata.hpp" #include "navmeshdata.hpp" #include "recastmeshbuilder.hpp" #include "navmeshdb.hpp" #include "serialization.hpp" #include "dbrefgeometryobject.hpp" #include "navmeshdbutils.hpp" #include "recastparams.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { namespace { struct Rectangle { TileBounds mBounds; float mHeight; }; std::vector getOffMeshVerts(const std::vector& connections) { std::vector result; result.reserve(connections.size() * 6); const auto add = [&] (const osg::Vec3f& v) { result.push_back(v.x()); result.push_back(v.y()); result.push_back(v.z()); }; for (const auto& v : connections) { add(v.mStart); add(v.mEnd); } return result; } Flag getFlag(AreaType areaType) { switch (areaType) { case AreaType_null: return Flag_none; case AreaType_ground: return Flag_walk; case AreaType_water: return Flag_swim; case AreaType_door: return Flag_openDoor; case AreaType_pathgrid: return Flag_usePathgrid; } return Flag_none; } std::vector getOffMeshConAreas(const std::vector& connections) { std::vector result; result.reserve(connections.size()); std::transform(connections.begin(), connections.end(), std::back_inserter(result), [] (const OffMeshConnection& v) { return v.mAreaType; }); return result; } std::vector getOffMeshFlags(const std::vector& connections) { std::vector result; result.reserve(connections.size()); std::transform(connections.begin(), connections.end(), std::back_inserter(result), [] (const OffMeshConnection& v) { return getFlag(v.mAreaType); }); return result; } float getHeight(const RecastSettings& settings,const AgentBounds& agentBounds) { return getAgentHeight(agentBounds) * settings.mRecastScaleFactor; } float getMaxClimb(const RecastSettings& settings) { return settings.mMaxClimb * settings.mRecastScaleFactor; } float getRadius(const RecastSettings& settings, const AgentBounds& agentBounds) { return getAgentRadius(agentBounds) * settings.mRecastScaleFactor; } float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ) { return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ; } struct RecastParams { float mSampleDist = 0; float mSampleMaxError = 0; int mMaxEdgeLen = 0; int mWalkableClimb = 0; int mWalkableHeight = 0; int mWalkableRadius = 0; }; RecastParams makeRecastParams(const RecastSettings& settings, const AgentBounds& agentBounds) { RecastParams result; result.mWalkableHeight = static_cast(std::ceil(getHeight(settings, agentBounds) / settings.mCellHeight)); result.mWalkableClimb = static_cast(std::floor(getMaxClimb(settings) / settings.mCellHeight)); result.mWalkableRadius = static_cast(std::ceil(getRadius(settings, agentBounds) / settings.mCellSize)); result.mMaxEdgeLen = static_cast(std::round(static_cast(settings.mMaxEdgeLen) / settings.mCellSize)); result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist; result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError; return result; } void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ, const RecastSettings& settings, rcHeightfield& solid) { const int size = settings.mTileSize + settings.mBorderSize * 2; const int width = size; const int height = size; const float halfBoundsSize = size * settings.mCellSize * 0.5f; const osg::Vec2f shift = osg::Vec2f(tilePosition.x() + 0.5f, tilePosition.y() + 0.5f) * getTileSize(settings); const osg::Vec3f bmin(shift.x() - halfBoundsSize, minZ, shift.y() - halfBoundsSize); const osg::Vec3f bmax(shift.x() + halfBoundsSize, maxZ, shift.y() + halfBoundsSize); const auto result = rcCreateHeightfield(&context, solid, width, height, bmin.ptr(), bmax.ptr(), settings.mCellSize, settings.mCellHeight); if (!result) throw NavigatorException("Failed to create heightfield for navmesh"); } bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { std::vector areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end()); std::vector vertices = mesh.getVertices(); for (std::size_t i = 0; i < vertices.size(); i += 3) { for (std::size_t j = 0; j < 3; ++j) vertices[i + j] = toNavMeshCoordinates(settings, vertices[i + j]); std::swap(vertices[i + 1], vertices[i + 2]); } rcClearUnwalkableTriangles( &context, settings.mMaxSlope, vertices.data(), static_cast(mesh.getVerticesCount()), mesh.getIndices().data(), static_cast(areas.size()), areas.data() ); return rcRasterizeTriangles( &context, vertices.data(), static_cast(mesh.getVerticesCount()), mesh.getIndices().data(), areas.data(), static_cast(areas.size()), solid, params.mWalkableClimb ); } bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType, const RecastParams& params, rcHeightfield& solid) { const std::array vertices { rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), }; const std::array indices { 0, 1, 2, 0, 2, 3, }; const std::array areas {areaType, areaType}; return rcRasterizeTriangles( &context, vertices.data(), static_cast(vertices.size() / 3), indices.data(), areas.data(), static_cast(areas.size()), solid, params.mWalkableClimb ); } bool rasterizeTriangles(rcContext& context, float agentHalfExtentsZ, const std::vector& water, const RecastSettings& settings, const RecastParams& params, const TileBounds& realTileBounds, rcHeightfield& solid) { for (const CellWater& cellWater : water) { const TileBounds cellTileBounds = maxCellTileBounds(cellWater.mCellPosition, cellWater.mWater.mCellSize); if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) { const Rectangle rectangle { toNavMeshCoordinates(settings, *intersection), toNavMeshCoordinates(settings, getSwimLevel(settings, cellWater.mWater.mLevel, agentHalfExtentsZ)) }; if (!rasterizeTriangles(context, rectangle, AreaType_water, params, solid)) return false; } } return true; } bool rasterizeTriangles(rcContext& context, const TileBounds& realTileBounds, const std::vector& heightfields, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { for (const FlatHeightfield& heightfield : heightfields) { const TileBounds cellTileBounds = maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize); if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) { const Rectangle rectangle { toNavMeshCoordinates(settings, *intersection), toNavMeshCoordinates(settings, heightfield.mHeight) }; if (!rasterizeTriangles(context, rectangle, AreaType_ground, params, solid)) return false; } } return true; } bool rasterizeTriangles(rcContext& context, const std::vector& heightfields, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { for (const Heightfield& heightfield : heightfields) { const Mesh mesh = makeMesh(heightfield); if (!rasterizeTriangles(context, mesh, settings, params, solid)) return false; } return true; } bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, float agentHalfExtentsZ, const RecastMesh& recastMesh, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { const TileBounds realTileBounds = makeRealTileBoundsWithBorder(settings, tilePosition); return rasterizeTriangles(context, recastMesh.getMesh(), settings, params, solid) && rasterizeTriangles(context, agentHalfExtentsZ, recastMesh.getWater(), settings, params, realTileBounds, solid) && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, params, solid) && rasterizeTriangles(context, realTileBounds, recastMesh.getFlatHeightfields(), settings, params, solid); } void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, rcHeightfield& solid, rcCompactHeightfield& compact) { const auto result = rcBuildCompactHeightfield(&context, walkableHeight, walkableClimb, solid, compact); if (!result) throw NavigatorException("Failed to build compact heightfield for navmesh"); } void erodeWalkableArea(rcContext& context, int walkableRadius, rcCompactHeightfield& compact) { const auto result = rcErodeWalkableArea(&context, walkableRadius, compact); if (!result) throw NavigatorException("Failed to erode walkable area for navmesh"); } void buildDistanceField(rcContext& context, rcCompactHeightfield& compact) { const auto result = rcBuildDistanceField(&context, compact); if (!result) throw NavigatorException("Failed to build distance field for navmesh"); } void buildRegions(rcContext& context, rcCompactHeightfield& compact, const int borderSize, const int minRegionArea, const int mergeRegionArea) { const auto result = rcBuildRegions(&context, compact, borderSize, minRegionArea, mergeRegionArea); if (!result) throw NavigatorException("Failed to build distance field for navmesh"); } void buildContours(rcContext& context, rcCompactHeightfield& compact, const float maxError, const int maxEdgeLen, rcContourSet& contourSet, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES) { const auto result = rcBuildContours(&context, compact, maxError, maxEdgeLen, contourSet, buildFlags); if (!result) throw NavigatorException("Failed to build contours for navmesh"); } void buildPolyMesh(rcContext& context, rcContourSet& contourSet, const int maxVertsPerPoly, rcPolyMesh& polyMesh) { const auto result = rcBuildPolyMesh(&context, contourSet, maxVertsPerPoly, polyMesh); if (!result) throw NavigatorException("Failed to build poly mesh for navmesh"); } void buildPolyMeshDetail(rcContext& context, const rcPolyMesh& polyMesh, const rcCompactHeightfield& compact, const float sampleDist, const float sampleMaxError, rcPolyMeshDetail& polyMeshDetail) { const auto result = rcBuildPolyMeshDetail(&context, polyMesh, compact, sampleDist, sampleMaxError, polyMeshDetail); if (!result) throw NavigatorException("Failed to build detail poly mesh for navmesh"); } void setPolyMeshFlags(rcPolyMesh& polyMesh) { for (int i = 0; i < polyMesh.npolys; ++i) polyMesh.flags[i] = getFlag(static_cast(polyMesh.areas[i])); } bool fillPolyMesh(rcContext& context, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail) { rcCompactHeightfield compact; buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact); erodeWalkableArea(context, params.mWalkableRadius, compact); buildDistanceField(context, compact); buildRegions(context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea); rcContourSet contourSet; buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet); if (contourSet.nconts == 0) return false; buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh); buildPolyMeshDetail(context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail); setPolyMeshFlags(polyMesh); return true; } template unsigned long getMinValuableBitsNumber(const T value) { unsigned long power = 0; while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) ++power; return power; } std::pair getBoundsByZ(const RecastMesh& recastMesh, float agentHalfExtentsZ, const RecastSettings& settings) { float minZ = 0; float maxZ = 0; const std::vector& vertices = recastMesh.getMesh().getVertices(); for (std::size_t i = 0, n = vertices.size(); i < n; i += 3) { minZ = std::min(minZ, vertices[i + 2]); maxZ = std::max(maxZ, vertices[i + 2]); } for (const CellWater& water : recastMesh.getWater()) { const float swimLevel = getSwimLevel(settings, water.mWater.mLevel, agentHalfExtentsZ); minZ = std::min(minZ, swimLevel); maxZ = std::max(maxZ, swimLevel); } for (const Heightfield& heightfield : recastMesh.getHeightfields()) { if (heightfield.mHeights.empty()) continue; const auto [minHeight, maxHeight] = std::minmax_element(heightfield.mHeights.begin(), heightfield.mHeights.end()); minZ = std::min(minZ, *minHeight); maxZ = std::max(maxZ, *maxHeight); } for (const FlatHeightfield& heightfield : recastMesh.getFlatHeightfields()) { minZ = std::min(minZ, heightfield.mHeight); maxZ = std::max(maxZ, heightfield.mHeight); } return {minZ, maxZ}; } } } // namespace DetourNavigator namespace DetourNavigator { std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings) { rcContext context; const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentBounds.mHalfExtents.z(), settings); rcHeightfield solid; initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ), toNavMeshCoordinates(settings, maxZ), settings, solid); const RecastParams params = makeRecastParams(settings, agentBounds); if (!rasterizeTriangles(context, tilePosition, agentBounds.mHalfExtents.z(), recastMesh, settings, params, solid)) return nullptr; rcFilterLowHangingWalkableObstacles(&context, params.mWalkableClimb, solid); rcFilterLedgeSpans(&context, params.mWalkableHeight, params.mWalkableClimb, solid); rcFilterWalkableLowHeightSpans(&context, params.mWalkableHeight, solid); std::unique_ptr result = std::make_unique(); if (!fillPolyMesh(context, settings, params, solid, result->mPolyMesh, result->mPolyMeshDetail)) return nullptr; result->mCellSize = settings.mCellSize; result->mCellHeight = settings.mCellHeight; return result; } NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const AgentBounds& agentBounds, const TilePosition& tile, const RecastSettings& settings) { const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentBounds)); const std::vector offMeshConDir(offMeshConnections.size(), 0); const std::vector offMeshConAreas = getOffMeshConAreas(offMeshConnections); const std::vector offMeshConFlags = getOffMeshFlags(offMeshConnections); dtNavMeshCreateParams params; params.verts = data.mPolyMesh.verts; params.vertCount = data.mPolyMesh.nverts; params.polys = data.mPolyMesh.polys; params.polyAreas = data.mPolyMesh.areas; params.polyFlags = data.mPolyMesh.flags; params.polyCount = data.mPolyMesh.npolys; params.nvp = data.mPolyMesh.nvp; params.detailMeshes = data.mPolyMeshDetail.meshes; params.detailVerts = data.mPolyMeshDetail.verts; params.detailVertsCount = data.mPolyMeshDetail.nverts; params.detailTris = data.mPolyMeshDetail.tris; params.detailTriCount = data.mPolyMeshDetail.ntris; params.offMeshConVerts = offMeshConVerts.data(); params.offMeshConRad = offMeshConRad.data(); params.offMeshConDir = offMeshConDir.data(); params.offMeshConAreas = offMeshConAreas.data(); params.offMeshConFlags = offMeshConFlags.data(); params.offMeshConUserID = nullptr; params.offMeshConCount = static_cast(offMeshConnections.size()); params.walkableHeight = getHeight(settings, agentBounds); params.walkableRadius = getRadius(settings, agentBounds); params.walkableClimb = getMaxClimb(settings); rcVcopy(params.bmin, data.mPolyMesh.bmin); rcVcopy(params.bmax, data.mPolyMesh.bmax); params.cs = data.mCellSize; params.ch = data.mCellHeight; params.buildBvTree = true; params.userId = data.mUserId; params.tileX = tile.x(); params.tileY = tile.y(); params.tileLayer = 0; unsigned char* navMeshData; int navMeshDataSize; const auto navMeshDataCreated = dtCreateNavMeshData(¶ms, &navMeshData, &navMeshDataSize); if (!navMeshDataCreated) throw NavigatorException("Failed to create navmesh tile data"); return NavMeshData(navMeshData, navMeshDataSize); } NavMeshPtr makeEmptyNavMesh(const Settings& settings) { // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. const int polysAndTilesBits = 22; const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); if (polysBits >= polysAndTilesBits) throw InvalidArgument("Too many polygons per tile"); const auto tilesBits = polysAndTilesBits - polysBits; dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.maxTiles = 1 << tilesBits; params.maxPolys = 1 << polysBits; NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); if (navMesh == nullptr) throw NavigatorException("Failed to allocate navmesh"); const auto status = navMesh->init(¶ms); if (!dtStatusSucceed(status)) throw NavigatorException("Failed to init navmesh"); return navMesh; } } openmw-openmw-0.48.0/components/detournavigator/makenavmesh.hpp000066400000000000000000000036361445372753700250560ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #include "offmeshconnectionsmanager.hpp" #include "navmeshcacheitem.hpp" #include "tileposition.hpp" #include "sharednavmesh.hpp" #include "navmeshtilescache.hpp" #include "offmeshconnection.hpp" #include "navmeshdb.hpp" #include #include #include #include class dtNavMesh; struct rcConfig; namespace DetourNavigator { class RecastMesh; struct Settings; struct PreparedNavMeshData; struct NavMeshData; inline float getLength(const osg::Vec2i& value) { return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); } inline float getDistance(const TilePosition& lhs, const TilePosition& rhs) { return getLength(lhs - rhs); } inline bool shouldAddTile(const TilePosition& changedTile, const TilePosition& playerTile, int maxTiles) { const auto expectedTilesCount = std::ceil(osg::PI * osg::square(getDistance(changedTile, playerTile))); return expectedTilesCount <= maxTiles; } inline bool isEmpty(const RecastMesh& recastMesh) { return recastMesh.getMesh().getIndices().empty() && recastMesh.getWater().empty() && recastMesh.getHeightfields().empty() && recastMesh.getFlatHeightfields().empty(); } std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings); NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const AgentBounds& agentBounds, const TilePosition& tile, const RecastSettings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings); } #endif openmw-openmw-0.48.0/components/detournavigator/navigator.cpp000066400000000000000000000017221445372753700245360ustar00rootroot00000000000000#include "navigator.hpp" #include "navigatorimpl.hpp" #include "navigatorstub.hpp" #include "recastglobalallocator.hpp" #include namespace DetourNavigator { std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath) { DetourNavigator::RecastGlobalAllocator::init(); std::unique_ptr db; if (settings.mEnableNavMeshDiskCache) { try { db = std::make_unique(userDataPath + "/navmesh.db", settings.mMaxDbFileSize); } catch (const std::exception& e) { Log(Debug::Error) << e.what() << ", navigation mesh disk cache will be disabled"; } } return std::make_unique(settings, std::move(db)); } std::unique_ptr makeNavigatorStub() { return std::make_unique(); } } openmw-openmw-0.48.0/components/detournavigator/navigator.hpp000066400000000000000000000213341445372753700245440ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #include "objectid.hpp" #include "navmeshcacheitem.hpp" #include "recastmeshtiles.hpp" #include "waitconditiontype.hpp" #include "heightfieldshape.hpp" #include "objecttransform.hpp" #include #include namespace ESM { struct Cell; struct Pathgrid; } namespace Loading { class Listener; } namespace DetourNavigator { struct Settings; struct AgentBounds; struct ObjectShapes { osg::ref_ptr mShapeInstance; ObjectTransform mTransform; ObjectShapes(const osg::ref_ptr& shapeInstance, const ObjectTransform& transform) : mShapeInstance(shapeInstance) , mTransform(transform) { assert(mShapeInstance != nullptr); } }; struct DoorShapes : ObjectShapes { osg::Vec3f mConnectionStart; osg::Vec3f mConnectionEnd; DoorShapes(const osg::ref_ptr& shapeInstance, const ObjectTransform& transform, const osg::Vec3f& connectionStart, const osg::Vec3f& connectionEnd) : ObjectShapes(shapeInstance, transform) , mConnectionStart(connectionStart) , mConnectionEnd(connectionEnd) {} }; /** * @brief Top level interface of detournavigator component. Navigator allows to build a scene with navmesh and find * a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by * half extents. Each object has unique identifier and could be added, updated or removed. Water could be added once * for each world cell at given level of height. Navmesh builds asynchronously in separate threads. To start build * navmesh call update method. */ struct Navigator { virtual ~Navigator() = default; /** * @brief addAgent should be called for each agent even if all of them has same half extents. * @param agentBounds allows to setup bounding cylinder for each agent, for each different half extents * there is different navmesh. */ virtual void addAgent(const AgentBounds& agentBounds) = 0; /** * @brief removeAgent should be called for each agent even if all of them has same half extents * @param agentBounds allows determine which agent to remove */ virtual void removeAgent(const AgentBounds& agentBounds) = 0; /** * @brief setWorldspace should be called before adding object from new worldspace * @param worldspace */ virtual void setWorldspace(std::string_view worldspace) = 0; /** * @brief updateBounds should be called before adding object from loading cell * @param playerPosition corresponds to the bounds center */ virtual void updateBounds(const osg::Vec3f& playerPosition) = 0; /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects * @param shape members must live until object is updated by another shape removed from Navigator * @param transform allows to setup objects geometry according to its world state * @return true if object is added, false if there is already object with given id */ virtual bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief addObject is used to add doors. * @param id is used to distinguish different objects. * @param shape members must live until object is updated by another shape or removed from Navigator. * @param transform allows to setup objects geometry according to its world state. * @return true if object is added, false if there is already object with given id. */ virtual bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. * @param id is used to find object. * @param shape members must live until object is updated by another shape removed from Navigator. * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ virtual bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. * @param id is used to find object. * @param shape members must live until object is updated by another shape removed from Navigator. * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ virtual bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief removeObject to make it no more available at the scene. * @param id is used to find object. * @return true if object is removed, false if there is no object with given id. */ virtual bool removeObject(const ObjectId id) = 0; /** * @brief addWater is used to set water level at given world cell. * @param cellPosition allows to distinguish cells if there is many in current world. * @param cellSize set cell borders. std::numeric_limits::max() disables cell borders. * @param shift set global shift of cell center. * @return true if there was no water at given cell if cellSize != std::numeric_limits::max() or there is * at least single object is added to the scene, false if there is already water for given cell or there is no * any other objects. */ virtual bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) = 0; /** * @brief removeWater to make it no more available at the scene. * @param cellPosition allows to find cell. * @return true if there was water at given cell. */ virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; virtual bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) = 0; virtual bool removeHeightfield(const osg::Vec2i& cellPosition) = 0; virtual void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) = 0; virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0; /** * @brief update starts background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ virtual void update(const osg::Vec3f& playerPosition) = 0; /** * @brief updatePlayerPosition starts background navmesh update using current scene state only when player position has been changed. * @param playerPosition setup initial point to order build tiles of navmesh. */ virtual void updatePlayerPosition(const osg::Vec3f& playerPosition) = 0; /** * @brief disable navigator updates */ virtual void setUpdatesEnabled(bool enabled) = 0; /** * @brief wait locks thread until tiles are updated from last update call based on passed condition type. * @param waitConditionType defines when waiting will stop */ virtual void wait(Loading::Listener& listener, WaitConditionType waitConditionType) = 0; /** * @brief getNavMesh returns navmesh for specific agent half extents * @return navmesh */ virtual SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const = 0; /** * @brief getNavMeshes returns all current navmeshes * @return map of agent half extents to navmesh */ virtual std::map getNavMeshes() const = 0; virtual const Settings& getSettings() const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; virtual RecastMeshTiles getRecastMeshTiles() const = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0; }; std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath); std::unique_ptr makeNavigatorStub(); } #endif openmw-openmw-0.48.0/components/detournavigator/navigatorimpl.cpp000066400000000000000000000211041445372753700254140ustar00rootroot00000000000000#include "navigatorimpl.hpp" #include "debug.hpp" #include "settingsutils.hpp" #include #include #include #include namespace DetourNavigator { NavigatorImpl::NavigatorImpl(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mNavMeshManager(mSettings, std::move(db)) , mUpdatesEnabled(true) { } void NavigatorImpl::addAgent(const AgentBounds& agentBounds) { if (agentBounds.mHalfExtents.x() == 0.f || agentBounds.mHalfExtents.y() == 0.f || agentBounds.mHalfExtents.z() == 0.f) return; ++mAgents[agentBounds]; mNavMeshManager.addAgent(agentBounds); } void NavigatorImpl::removeAgent(const AgentBounds& agentBounds) { const auto it = mAgents.find(agentBounds); if (it == mAgents.end()) return; if (it->second > 0) --it->second; } void NavigatorImpl::setWorldspace(std::string_view worldspace) { mNavMeshManager.setWorldspace(worldspace); } void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition) { mNavMeshManager.updateBounds(playerPosition); } bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; } } return result; } bool NavigatorImpl::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { if (addObject(id, static_cast(shapes), transform)) { const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart); const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd); mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door); mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door); return true; } return false; } bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; } } return result; } bool NavigatorImpl::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { return updateObject(id, static_cast(shapes), transform); } bool NavigatorImpl::removeObject(const ObjectId id) { bool result = mNavMeshManager.removeObject(id); const auto avoid = mAvoidIds.find(id); if (avoid != mAvoidIds.end()) result = mNavMeshManager.removeObject(avoid->second) || result; const auto water = mWaterIds.find(id); if (water != mWaterIds.end()) result = mNavMeshManager.removeObject(water->second) || result; mNavMeshManager.removeOffMeshConnections(id); return result; } bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { return mNavMeshManager.addWater(cellPosition, cellSize, level); } bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) { return mNavMeshManager.removeWater(cellPosition); } bool NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { return mNavMeshManager.addHeightfield(cellPosition, cellSize, shape); } bool NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition) { return mNavMeshManager.removeHeightfield(cellPosition); } void NavigatorImpl::addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) { Misc::CoordinateConverter converter(&cell); for (auto& edge : pathgrid.mEdges) { const auto src = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV0])); const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1])); mNavMeshManager.addOffMeshConnection( ObjectId(&pathgrid), toNavMeshCoordinates(mSettings.mRecast, src), toNavMeshCoordinates(mSettings.mRecast, dst), AreaType_pathgrid ); } } void NavigatorImpl::removePathgrid(const ESM::Pathgrid& pathgrid) { mNavMeshManager.removeOffMeshConnections(ObjectId(&pathgrid)); } void NavigatorImpl::update(const osg::Vec3f& playerPosition) { if (!mUpdatesEnabled) return; removeUnusedNavMeshes(); for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) { const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; mNavMeshManager.updateBounds(playerPosition); update(playerPosition); mLastPlayerPosition = tilePosition; } void NavigatorImpl::setUpdatesEnabled(bool enabled) { mUpdatesEnabled = enabled; } void NavigatorImpl::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { mNavMeshManager.wait(listener, waitConditionType); } SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const AgentBounds& agentBounds) const { return mNavMeshManager.getNavMesh(agentBounds); } std::map NavigatorImpl::getNavMeshes() const { return mNavMeshManager.getNavMeshes(); } const Settings& NavigatorImpl::getSettings() const { return mSettings; } void NavigatorImpl::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mNavMeshManager.reportStats(frameNumber, stats); } RecastMeshTiles NavigatorImpl::getRecastMeshTiles() const { return mNavMeshManager.getRecastMeshTiles(); } void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) { updateId(id, avoidId, mWaterIds); } void NavigatorImpl::updateWaterShapeId(const ObjectId id, const ObjectId waterId) { updateId(id, waterId, mWaterIds); } void NavigatorImpl::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) { auto inserted = ids.insert(std::make_pair(id, updateId)); if (!inserted.second) { mNavMeshManager.removeObject(inserted.first->second); inserted.first->second = updateId; } } void NavigatorImpl::removeUnusedNavMeshes() { for (auto it = mAgents.begin(); it != mAgents.end();) { if (it->second == 0 && mNavMeshManager.reset(it->first)) it = mAgents.erase(it); else ++it; } } float NavigatorImpl::getMaxNavmeshAreaRealRadius() const { const auto& settings = getSettings(); return getRealTileSize(settings.mRecast) * getMaxNavmeshAreaRadius(settings); } } openmw-openmw-0.48.0/components/detournavigator/navigatorimpl.hpp000066400000000000000000000063761445372753700254370ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H #include "navigator.hpp" #include "navmeshmanager.hpp" #include #include namespace DetourNavigator { class NavigatorImpl final : public Navigator { public: /** * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. */ explicit NavigatorImpl(const Settings& settings, std::unique_ptr&& db); void addAgent(const AgentBounds& agentBounds) override; void removeAgent(const AgentBounds& agentBounds) override; void setWorldspace(std::string_view worldspace) override; void updateBounds(const osg::Vec3f& playerPosition) override; bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; bool removeObject(const ObjectId id) override; bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) override; bool removeWater(const osg::Vec2i& cellPosition) override; bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) override; bool removeHeightfield(const osg::Vec2i& cellPosition) override; void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) override; void removePathgrid(const ESM::Pathgrid& pathgrid) override; void update(const osg::Vec3f& playerPosition) override; void updatePlayerPosition(const osg::Vec3f& playerPosition) override; void setUpdatesEnabled(bool enabled) override; void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override; SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const override; std::map getNavMeshes() const override; const Settings& getSettings() const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; RecastMeshTiles getRecastMeshTiles() const override; float getMaxNavmeshAreaRealRadius() const override; private: Settings mSettings; NavMeshManager mNavMeshManager; bool mUpdatesEnabled; std::optional mLastPlayerPosition; std::map mAgents; std::unordered_map mAvoidIds; std::unordered_map mWaterIds; void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); void updateWaterShapeId(const ObjectId id, const ObjectId waterId); void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map& ids); void removeUnusedNavMeshes(); }; } #endif openmw-openmw-0.48.0/components/detournavigator/navigatorstub.hpp000066400000000000000000000064041445372753700254430ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H #include "navigator.hpp" #include "settings.hpp" namespace Loading { class Listener; } namespace DetourNavigator { class NavigatorStub final : public Navigator { public: NavigatorStub() = default; void addAgent(const AgentBounds& /*agentBounds*/) override {} void removeAgent(const AgentBounds& /*agentBounds*/) override {} void setWorldspace(std::string_view /*worldspace*/) override {} bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; } bool addObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; } bool updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; } bool updateObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; } bool removeObject(const ObjectId /*id*/) override { return false; } bool addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, float /*level*/) override { return false; } bool removeWater(const osg::Vec2i& /*cellPosition*/) override { return false; } bool addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const HeightfieldShape& /*height*/) override { return false; } bool removeHeightfield(const osg::Vec2i& /*cellPosition*/) override { return false; } void addPathgrid(const ESM::Cell& /*cell*/, const ESM::Pathgrid& /*pathgrid*/) override {} void removePathgrid(const ESM::Pathgrid& /*pathgrid*/) override {} void update(const osg::Vec3f& /*playerPosition*/) override {} void updateBounds(const osg::Vec3f& /*playerPosition*/) override {} void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; void setUpdatesEnabled(bool /*enabled*/) override {} void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} SharedNavMeshCacheItem getNavMesh(const AgentBounds& /*agentBounds*/) const override { return mEmptyNavMeshCacheItem; } std::map getNavMeshes() const override { return {}; } const Settings& getSettings() const override { return mDefaultSettings; } void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} RecastMeshTiles getRecastMeshTiles() const override { return {}; } float getMaxNavmeshAreaRealRadius() const override { return std::numeric_limits::max(); } private: Settings mDefaultSettings {}; SharedNavMeshCacheItem mEmptyNavMeshCacheItem; }; } #endif openmw-openmw-0.48.0/components/detournavigator/navigatorutils.cpp000066400000000000000000000034521445372753700256210ustar00rootroot00000000000000#include "navigatorutils.hpp" #include "findrandompointaroundcircle.hpp" #include "navigator.hpp" #include "raycast.hpp" namespace DetourNavigator { std::optional findRandomPointAroundCircle(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, float(*prng)()) { const auto navMesh = navigator.getNavMesh(agentBounds); if (!navMesh) return std::nullopt; const auto& settings = navigator.getSettings(); const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, maxRadius), includeFlags, settings.mDetour, prng); if (!result) return std::nullopt; return std::optional(fromNavMeshCoordinates(settings.mRecast, *result)); } std::optional raycast(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags) { const auto navMesh = navigator.getNavMesh(agentBounds); if (navMesh == nullptr) return std::nullopt; const auto& settings = navigator.getSettings(); const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags, settings.mDetour); if (!result) return std::nullopt; return fromNavMeshCoordinates(settings.mRecast, *result); } } openmw-openmw-0.48.0/components/detournavigator/navigatorutils.hpp000066400000000000000000000064701445372753700256310ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORUTILS_H #include "findsmoothpath.hpp" #include "flags.hpp" #include "settings.hpp" #include "navigator.hpp" #include namespace DetourNavigator { /** * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. * @param agentBounds allows to find navmesh for given actor. * @param start path from given point. * @param end path at given point. * @param includeFlags setup allowed surfaces for actor to walk. * @param out the beginning of the destination range. * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents * @return Output iterator to the element in the destination range, one past the last element of found path. * Equal to out if no path is found. */ template inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance, OutputIterator out) { static_assert( std::is_same< typename std::iterator_traits::iterator_category, std::output_iterator_tag >::value, "out is not an OutputIterator" ); const auto navMesh = navigator.getNavMesh(agentBounds); if (navMesh == nullptr) return Status::NavMeshNotFound; const auto settings = navigator.getSettings(); return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents), toNavMeshCoordinates(settings.mRecast, stepSize), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags, areaCosts, settings, endTolerance, out); } /** * @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location. * @param agentBounds allows to find navmesh for given actor. * @param start is a position where the search starts. * @param maxRadius limit maximum distance from start. * @param includeFlags setup allowed surfaces for actor to walk. * @return not empty optional with position if point is found and empty optional if point is not found. */ std::optional findRandomPointAroundCircle(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, float(*prng)()); /** * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. * @param agentBounds allows to find navmesh for given actor. * @param start of the line * @param end of the line * @param includeFlags setup allowed surfaces for actor to walk. * @return not empty optional with position if point is found and empty optional if point is not found. */ std::optional raycast(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags); } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshcacheitem.cpp000066400000000000000000000113411445372753700260460ustar00rootroot00000000000000#include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "navmeshtileview.hpp" #include "navmeshcacheitem.hpp" #include "navmeshdata.hpp" #include #include #include namespace { using DetourNavigator::TilePosition; bool removeTile(dtNavMesh& navMesh, const TilePosition& position) { const int layer = 0; const auto tileRef = navMesh.getTileRefAt(position.x(), position.y(), layer); if (tileRef == 0) return false; unsigned char** const data = nullptr; int* const dataSize = nullptr; return dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize)); } dtStatus addTile(dtNavMesh& navMesh, unsigned char* data, int size) { const int doNotTransferOwnership = 0; const dtTileRef lastRef = 0; dtTileRef* const result = nullptr; return navMesh.addTile(data, size, doNotTransferOwnership, lastRef, result); } } namespace DetourNavigator { std::ostream& operator<<(std::ostream& stream, UpdateNavMeshStatus value) { switch (value) { case UpdateNavMeshStatus::ignored: return stream << "ignore"; case UpdateNavMeshStatus::removed: return stream << "removed"; case UpdateNavMeshStatus::added: return stream << "add"; case UpdateNavMeshStatus::replaced: return stream << "replaced"; case UpdateNavMeshStatus::failed: return stream << "failed"; case UpdateNavMeshStatus::lost: return stream << "lost"; case UpdateNavMeshStatus::cached: return stream << "cached"; case UpdateNavMeshStatus::unchanged: return stream << "unchanged"; case UpdateNavMeshStatus::restored: return stream << "restored"; } return stream << "unknown(" << static_cast(value) << ")"; } const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position) { const int layer = 0; return navMesh.getTileAt(position.x(), position.y(), layer); } UpdateNavMeshStatus NavMeshCacheItem::updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached, NavMeshData&& navMeshData) { const dtMeshTile* currentTile = getTile(*mImpl, position); if (currentTile != nullptr && asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(navMeshData.mValue.get())) { return UpdateNavMeshStatus::ignored; } bool removed = ::removeTile(*mImpl, position); removed = mEmptyTiles.erase(position) > 0 || removed; const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize); if (dtStatusSucceed(addStatus)) { auto tile = mUsedTiles.find(position); if (tile == mUsedTiles.end()) { mUsedTiles.emplace_hint(tile, position, Tile {Version {mVersion.mRevision, 1}, std::move(cached), std::move(navMeshData)}); } else { ++tile->second.mVersion.mRevision; tile->second.mCached = std::move(cached); tile->second.mData = std::move(navMeshData); } ++mVersion.mRevision; return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); } else { if (removed) { mUsedTiles.erase(position); ++mVersion.mRevision; } return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult(); } } UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position) { bool removed = ::removeTile(*mImpl, position); removed = mEmptyTiles.erase(position) > 0 || removed; if (removed) { mUsedTiles.erase(position); ++mVersion.mRevision; } return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } UpdateNavMeshStatus NavMeshCacheItem::markAsEmpty(const TilePosition& position) { bool removed = ::removeTile(*mImpl, position); removed = mEmptyTiles.insert(position).second || removed; if (removed) { mUsedTiles.erase(position); ++mVersion.mRevision; } return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } bool NavMeshCacheItem::isEmptyTile(const TilePosition& position) const { return mEmptyTiles.find(position) != mEmptyTiles.end(); } } openmw-openmw-0.48.0/components/detournavigator/navmeshcacheitem.hpp000066400000000000000000000103431445372753700260540ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #include "sharednavmesh.hpp" #include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "navmeshdata.hpp" #include "version.hpp" #include #include #include #include struct dtMeshTile; namespace DetourNavigator { enum class UpdateNavMeshStatus : unsigned { ignored = 0, removed = 1 << 0, added = 1 << 1, replaced = removed | added, failed = 1 << 2, lost = removed | failed, cached = 1 << 3, unchanged = replaced | cached, restored = added | cached, }; inline bool isSuccess(UpdateNavMeshStatus value) { return (static_cast(value) & static_cast(UpdateNavMeshStatus::failed)) == 0; } std::ostream& operator<<(std::ostream& stream, UpdateNavMeshStatus value); class UpdateNavMeshStatusBuilder { public: UpdateNavMeshStatusBuilder() = default; explicit UpdateNavMeshStatusBuilder(UpdateNavMeshStatus value) : mResult(value) {} UpdateNavMeshStatusBuilder removed(bool value) { if (value) set(UpdateNavMeshStatus::removed); else unset(UpdateNavMeshStatus::removed); return *this; } UpdateNavMeshStatusBuilder added(bool value) { if (value) set(UpdateNavMeshStatus::added); else unset(UpdateNavMeshStatus::added); return *this; } UpdateNavMeshStatusBuilder failed(bool value) { if (value) set(UpdateNavMeshStatus::failed); else unset(UpdateNavMeshStatus::failed); return *this; } UpdateNavMeshStatusBuilder cached(bool value) { if (value) set(UpdateNavMeshStatus::cached); else unset(UpdateNavMeshStatus::cached); return *this; } UpdateNavMeshStatus getResult() const { return mResult; } private: UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored; void set(UpdateNavMeshStatus value) { mResult = static_cast(static_cast(mResult) | static_cast(value)); } void unset(UpdateNavMeshStatus value) { mResult = static_cast(static_cast(mResult) & ~static_cast(value)); } }; const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position); class NavMeshCacheItem { public: NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation) : mImpl(impl) , mVersion {generation, 0} { } const dtNavMesh& getImpl() const { return *mImpl; } const Version& getVersion() const { return mVersion; } UpdateNavMeshStatus updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached, NavMeshData&& navMeshData); UpdateNavMeshStatus removeTile(const TilePosition& position); UpdateNavMeshStatus markAsEmpty(const TilePosition& position); bool isEmptyTile(const TilePosition& position) const; template void forEachUsedTile(Function&& function) const { for (const auto& [position, tile] : mUsedTiles) if (const dtMeshTile* meshTile = getTile(*mImpl, position)) function(position, tile.mVersion, *meshTile); } private: struct Tile { Version mVersion; NavMeshTilesCache::Value mCached; NavMeshData mData; }; NavMeshPtr mImpl; Version mVersion; std::map mUsedTiles; std::set mEmptyTiles; }; using GuardedNavMeshCacheItem = Misc::ScopeGuarded; using SharedNavMeshCacheItem = std::shared_ptr; } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshdata.hpp000066400000000000000000000012441445372753700250430ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H #include #include namespace DetourNavigator { struct NavMeshDataValueDeleter { void operator ()(unsigned char* value) const { dtFree(value); } }; using NavMeshDataValue = std::unique_ptr; struct NavMeshData { NavMeshDataValue mValue; int mSize = 0; NavMeshData() = default; NavMeshData(unsigned char* value, int size) : mValue(value) , mSize(size) {} }; } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshdb.cpp000066400000000000000000000377651445372753700245330ustar00rootroot00000000000000#include "navmeshdb.hpp" #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { namespace { constexpr const char schema[] = R"( BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS tiles ( tile_id INTEGER PRIMARY KEY, revision INTEGER NOT NULL DEFAULT 1, worldspace TEXT NOT NULL, tile_position_x INTEGER NOT NULL, tile_position_y INTEGER NOT NULL, version INTEGER NOT NULL, input BLOB, data BLOB ); CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input ON tiles (worldspace, tile_position_x, tile_position_y, input); CREATE INDEX IF NOT EXISTS index_tiles_by_worldspace_and_tile_position ON tiles (worldspace, tile_position_x, tile_position_y); CREATE TABLE IF NOT EXISTS shapes ( shape_id INTEGER PRIMARY KEY, name TEXT NOT NULL, type INTEGER NOT NULL, hash BLOB NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS index_unique_shapes_by_name_and_type_and_hash ON shapes (name, type, hash); COMMIT; )"; constexpr std::string_view getMaxTileIdQuery = R"( SELECT max(tile_id) FROM tiles )"; constexpr std::string_view findTileQuery = R"( SELECT tile_id, version FROM tiles WHERE worldspace = :worldspace AND tile_position_x = :tile_position_x AND tile_position_y = :tile_position_y AND input = :input )"; constexpr std::string_view getTileDataQuery = R"( SELECT tile_id, version, data FROM tiles WHERE worldspace = :worldspace AND tile_position_x = :tile_position_x AND tile_position_y = :tile_position_y AND input = :input )"; constexpr std::string_view insertTileQuery = R"( INSERT INTO tiles ( tile_id, worldspace, version, tile_position_x, tile_position_y, input, data) VALUES (:tile_id, :worldspace, :version, :tile_position_x, :tile_position_y, :input, :data) )"; constexpr std::string_view updateTileQuery = R"( UPDATE tiles SET version = :version, data = :data, revision = revision + 1 WHERE tile_id = :tile_id )"; constexpr std::string_view deleteTilesAtQuery = R"( DELETE FROM tiles WHERE worldspace = :worldspace AND tile_position_x = :tile_position_x AND tile_position_y = :tile_position_y )"; constexpr std::string_view deleteTilesAtExceptQuery = R"( DELETE FROM tiles WHERE worldspace = :worldspace AND tile_position_x = :tile_position_x AND tile_position_y = :tile_position_y AND tile_id != :exclude_tile_id )"; constexpr std::string_view deleteTilesOutsideRangeQuery = R"( DELETE FROM tiles WHERE worldspace = :worldspace AND ( tile_position_x < :begin_tile_position_x OR tile_position_y < :begin_tile_position_y OR tile_position_x >= :end_tile_position_x OR tile_position_y >= :end_tile_position_y ) )"; constexpr std::string_view getMaxShapeIdQuery = R"( SELECT max(shape_id) FROM shapes )"; constexpr std::string_view findShapeIdQuery = R"( SELECT shape_id FROM shapes WHERE name = :name AND type = :type AND hash = :hash )"; constexpr std::string_view insertShapeQuery = R"( INSERT INTO shapes ( shape_id, name, type, hash) VALUES (:shape_id, :name, :type, :hash) )"; constexpr std::string_view vacuumQuery = R"( VACUUM; )"; struct GetPageSize { static std::string_view text() noexcept { return "pragma page_size;"; } static void bind(sqlite3&, sqlite3_stmt&) {} }; std::uint64_t getPageSize(sqlite3& db) { Sqlite3::Statement statement(db); std::uint64_t value = 0; request(db, statement, &value, 1); return value; } void setMaxPageCount(sqlite3& db, std::uint64_t value) { const auto query = Misc::StringUtils::format("pragma max_page_count = %lu;", value); if (const int ec = sqlite3_exec(&db, query.c_str(), nullptr, nullptr, nullptr); ec != SQLITE_OK) throw std::runtime_error("Failed set max page count: " + std::string(sqlite3_errmsg(&db))); } } std::ostream& operator<<(std::ostream& stream, ShapeType value) { switch (value) { case ShapeType::Collision: return stream << "collision"; case ShapeType::Avoid: return stream << "avoid"; } return stream << "unknown shape type (" << static_cast>(value) << ")"; } NavMeshDb::NavMeshDb(std::string_view path, std::uint64_t maxFileSize) : mDb(Sqlite3::makeDb(path, schema)) , mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {}) , mFindTile(*mDb, DbQueries::FindTile {}) , mGetTileData(*mDb, DbQueries::GetTileData {}) , mInsertTile(*mDb, DbQueries::InsertTile {}) , mUpdateTile(*mDb, DbQueries::UpdateTile {}) , mDeleteTilesAt(*mDb, DbQueries::DeleteTilesAt {}) , mDeleteTilesAtExcept(*mDb, DbQueries::DeleteTilesAtExcept {}) , mDeleteTilesOutsideRange(*mDb, DbQueries::DeleteTilesOutsideRange {}) , mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId {}) , mFindShapeId(*mDb, DbQueries::FindShapeId {}) , mInsertShape(*mDb, DbQueries::InsertShape {}) , mVacuum(*mDb, DbQueries::Vacuum {}) { const std::uint64_t dbPageSize = getPageSize(*mDb); if (dbPageSize == 0) throw std::runtime_error("NavMeshDb page size is zero"); setMaxPageCount(*mDb, maxFileSize / dbPageSize + static_cast((maxFileSize % dbPageSize) != 0)); } Sqlite3::Transaction NavMeshDb::startTransaction(Sqlite3::TransactionMode mode) { return Sqlite3::Transaction(*mDb, mode); } TileId NavMeshDb::getMaxTileId() { TileId tileId {0}; request(*mDb, mGetMaxTileId, &tileId, 1); return tileId; } std::optional NavMeshDb::findTile(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Tile result; auto row = std::tie(result.mTileId, result.mVersion); const std::vector compressedInput = Misc::compress(input); if (&row == request(*mDb, mFindTile, &row, 1, worldspace, tilePosition, compressedInput)) return {}; return result; } std::optional NavMeshDb::getTileData(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { TileData result; auto row = std::tie(result.mTileId, result.mVersion, result.mData); const std::vector compressedInput = Misc::compress(input); if (&row == request(*mDb, mGetTileData, &row, 1, worldspace, tilePosition, compressedInput)) return {}; result.mData = Misc::decompress(result.mData); return result; } int NavMeshDb::insertTile(TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data) { const std::vector compressedInput = Misc::compress(input); const std::vector compressedData = Misc::compress(data); return execute(*mDb, mInsertTile, tileId, worldspace, tilePosition, version, compressedInput, compressedData); } int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector& data) { const std::vector compressedData = Misc::compress(data); return execute(*mDb, mUpdateTile, tileId, version, compressedData); } int NavMeshDb::deleteTilesAt(std::string_view worldspace, const TilePosition& tilePosition) { return execute(*mDb, mDeleteTilesAt, worldspace, tilePosition); } int NavMeshDb::deleteTilesAtExcept(std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId) { return execute(*mDb, mDeleteTilesAtExcept, worldspace, tilePosition, excludeTileId); } int NavMeshDb::deleteTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) { return execute(*mDb, mDeleteTilesOutsideRange, worldspace, range); } ShapeId NavMeshDb::getMaxShapeId() { ShapeId shapeId {0}; request(*mDb, mGetMaxShapeId, &shapeId, 1); return shapeId; } std::optional NavMeshDb::findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { ShapeId shapeId; if (&shapeId == request(*mDb, mFindShapeId, &shapeId, 1, name, type, hash)) return {}; return shapeId; } int NavMeshDb::insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { return execute(*mDb, mInsertShape, shapeId, name, type, hash); } void NavMeshDb::vacuum() { execute(*mDb, mVacuum); } namespace DbQueries { std::string_view GetMaxTileId::text() noexcept { return getMaxTileIdQuery; } std::string_view FindTile::text() noexcept { return findTileQuery; } void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); Sqlite3::bindParameter(db, statement, ":input", input); } std::string_view GetTileData::text() noexcept { return getTileDataQuery; } void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); Sqlite3::bindParameter(db, statement, ":input", input); } std::string_view InsertTile::text() noexcept { return insertTileQuery; } void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data) { Sqlite3::bindParameter(db, statement, ":tile_id", tileId); Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); Sqlite3::bindParameter(db, statement, ":version", version); Sqlite3::bindParameter(db, statement, ":input", input); Sqlite3::bindParameter(db, statement, ":data", data); } std::string_view UpdateTile::text() noexcept { return updateTileQuery; } void UpdateTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, const std::vector& data) { Sqlite3::bindParameter(db, statement, ":tile_id", tileId); Sqlite3::bindParameter(db, statement, ":version", version); Sqlite3::bindParameter(db, statement, ":data", data); } std::string_view DeleteTilesAt::text() noexcept { return deleteTilesAtQuery; } void DeleteTilesAt::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); } std::string_view DeleteTilesAtExcept::text() noexcept { return deleteTilesAtExceptQuery; } void DeleteTilesAtExcept::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); Sqlite3::bindParameter(db, statement, ":exclude_tile_id", excludeTileId); } std::string_view DeleteTilesOutsideRange::text() noexcept { return deleteTilesOutsideRangeQuery; } void DeleteTilesOutsideRange::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilesPositionsRange& range) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); Sqlite3::bindParameter(db, statement, ":begin_tile_position_x", range.mBegin.x()); Sqlite3::bindParameter(db, statement, ":begin_tile_position_y", range.mBegin.y()); Sqlite3::bindParameter(db, statement, ":end_tile_position_x", range.mEnd.x()); Sqlite3::bindParameter(db, statement, ":end_tile_position_y", range.mEnd.y()); } std::string_view GetMaxShapeId::text() noexcept { return getMaxShapeIdQuery; } std::string_view FindShapeId::text() noexcept { return findShapeIdQuery; } void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { Sqlite3::bindParameter(db, statement, ":name", name); Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); Sqlite3::bindParameter(db, statement, ":hash", hash); } std::string_view InsertShape::text() noexcept { return insertShapeQuery; } void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { Sqlite3::bindParameter(db, statement, ":shape_id", shapeId); Sqlite3::bindParameter(db, statement, ":name", name); Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); Sqlite3::bindParameter(db, statement, ":hash", hash); } std::string_view Vacuum::text() noexcept { return vacuumQuery; } } } openmw-openmw-0.48.0/components/detournavigator/navmeshdb.hpp000066400000000000000000000151041445372753700245170ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H #include "tileposition.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct sqlite3; struct sqlite3_stmt; namespace DetourNavigator { using TileId = Misc::StrongTypedef; using TileRevision = Misc::StrongTypedef; using TileVersion = Misc::StrongTypedef; using ShapeId = Misc::StrongTypedef; struct Tile { TileId mTileId; TileVersion mVersion; }; struct TileData { TileId mTileId; TileVersion mVersion; std::vector mData; }; enum class ShapeType { Collision = 1, Avoid = 2, }; std::ostream& operator<<(std::ostream& stream, ShapeType value); namespace DbQueries { struct GetMaxTileId { static std::string_view text() noexcept; static void bind(sqlite3&, sqlite3_stmt&) {} }; struct FindTile { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); }; struct GetTileData { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); }; struct InsertTile { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data); }; struct UpdateTile { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, const std::vector& data); }; struct DeleteTilesAt { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition); }; struct DeleteTilesAtExcept { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId); }; struct DeleteTilesOutsideRange { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilesPositionsRange& range); }; struct GetMaxShapeId { static std::string_view text() noexcept; static void bind(sqlite3&, sqlite3_stmt&) {} }; struct FindShapeId { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); }; struct InsertShape { static std::string_view text() noexcept; static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); }; struct Vacuum { static std::string_view text() noexcept; static void bind(sqlite3&, sqlite3_stmt&) {} }; } class NavMeshDb { public: explicit NavMeshDb(std::string_view path, std::uint64_t maxFileSize); Sqlite3::Transaction startTransaction(Sqlite3::TransactionMode mode = Sqlite3::TransactionMode::Default); TileId getMaxTileId(); std::optional findTile(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); std::optional getTileData(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); int insertTile(TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data); int updateTile(TileId tileId, TileVersion version, const std::vector& data); int deleteTilesAt(std::string_view worldspace, const TilePosition& tilePosition); int deleteTilesAtExcept(std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId); int deleteTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range); ShapeId getMaxShapeId(); std::optional findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); int insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); void vacuum(); private: Sqlite3::Db mDb; Sqlite3::Statement mGetMaxTileId; Sqlite3::Statement mFindTile; Sqlite3::Statement mGetTileData; Sqlite3::Statement mInsertTile; Sqlite3::Statement mUpdateTile; Sqlite3::Statement mDeleteTilesAt; Sqlite3::Statement mDeleteTilesAtExcept; Sqlite3::Statement mDeleteTilesOutsideRange; Sqlite3::Statement mGetMaxShapeId; Sqlite3::Statement mFindShapeId; Sqlite3::Statement mInsertShape; Sqlite3::Statement mVacuum; }; } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshdbutils.cpp000066400000000000000000000047411445372753700256000ustar00rootroot00000000000000#include "navmeshdbutils.hpp" #include "navmeshdb.hpp" #include "recastmesh.hpp" #include #include #include #include namespace DetourNavigator { namespace { std::optional findShapeId(NavMeshDb& db, std::string_view name, ShapeType type, const std::string& hash) { const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; return db.findShapeId(name, type, hashData); } ShapeId getShapeId(NavMeshDb& db, std::string_view name, ShapeType type, const std::string& hash, ShapeId& nextShapeId) { const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; if (const auto existingShapeId = db.findShapeId(name, type, hashData)) return *existingShapeId; const ShapeId newShapeId = nextShapeId; db.insertShape(newShapeId, name, type, hashData); Log(Debug::Verbose) << "Added " << name << " " << type << " shape to navmeshdb with id " << newShapeId; ++nextShapeId; return newShapeId; } } ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId) { switch (source.mAreaType) { case AreaType_null: return getShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash, nextShapeId); case AreaType_ground: return getShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash, nextShapeId); default: Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType; assert(false); return ShapeId(0); } } std::optional resolveMeshSource(NavMeshDb& db, const MeshSource& source) { switch (source.mAreaType) { case AreaType_null: return findShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash); case AreaType_ground: return findShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash); default: Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType; return std::nullopt; } } } openmw-openmw-0.48.0/components/detournavigator/navmeshdbutils.hpp000066400000000000000000000006321445372753700256000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H #include "navmeshdb.hpp" #include namespace DetourNavigator { struct MeshSource; ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId); std::optional resolveMeshSource(NavMeshDb& db, const MeshSource& source); } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshmanager.cpp000066400000000000000000000322651445372753700255460ustar00rootroot00000000000000#include "navmeshmanager.hpp" #include "debug.hpp" #include "exceptions.hpp" #include "gettilespositions.hpp" #include "makenavmesh.hpp" #include "navmeshcacheitem.hpp" #include "settings.hpp" #include "waitconditiontype.hpp" #include #include #include #include #include #include namespace { using DetourNavigator::ChangeType; ChangeType addChangeType(const ChangeType current, const ChangeType add) { return current == add ? current : ChangeType::mixed; } /// Safely reset shared_ptr with definite underlying object destrutor call. /// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr. template bool resetIfUnique(std::shared_ptr& ptr) { const std::weak_ptr weak(ptr); ptr.reset(); if (auto shared = weak.lock()) { ptr = std::move(shared); return false; } return true; } } namespace DetourNavigator { namespace { TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) { const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); TileBounds result; result.mMin = center - osg::Vec2f(radius, radius); result.mMax = center + osg::Vec2f(radius, radius); return result; } } NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) , mOffMeshConnectionsManager(settings.mRecast) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)) {} void NavMeshManager::setWorldspace(std::string_view worldspace) { if (worldspace == mWorldspace) return; mRecastMeshManager.setWorldspace(worldspace); for (auto& [agent, cache] : mCache) cache = std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter); mWorldspace = worldspace; } void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition) { const TileBounds bounds = makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), mSettings.mMaxTilesNumber); const auto changedTiles = mRecastMeshManager.setBounds(bounds); for (const auto& [agent, cache] : mCache) { auto& tiles = mChangedTiles[agent]; for (const auto& [tilePosition, changeType] : changedTiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) tiles.emplace_hint(tile, tilePosition, changeType); else tile->second = addChangeType(tile->second, changeType); } } } bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.addObject(id, shape, transform, areaType, [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::add); }); } bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, [&] (const TilePosition& tile, ChangeType changeType) { addChangedTile(tile, changeType); }); } bool NavMeshManager::removeObject(const ObjectId id) { const auto object = mRecastMeshManager.removeObject(id); if (!object) return false; addChangedTiles(object->mShape, object->mTransform, ChangeType::remove); return true; } bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { if (!mRecastMeshManager.addWater(cellPosition, cellSize, level)) return false; const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); addChangedTiles(cellSize, shift, ChangeType::add); return true; } bool NavMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto water = mRecastMeshManager.removeWater(cellPosition); if (!water) return false; const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, water->mCellSize, water->mLevel)); addChangedTiles(water->mCellSize, shift, ChangeType::remove); return true; } bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape)) return false; const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); addChangedTiles(cellSize, shift, ChangeType::add); return true; } bool NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition); if (!heightfield) return false; const btVector3 shift = getHeightfieldShift(heightfield->mShape, cellPosition, heightfield->mCellSize); addChangedTiles(heightfield->mCellSize, shift, ChangeType::remove); return true; } void NavMeshManager::addAgent(const AgentBounds& agentBounds) { auto cached = mCache.find(agentBounds); if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentBounds, std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); Log(Debug::Debug) << "cache add for agent=" << agentBounds; } bool NavMeshManager::reset(const AgentBounds& agentBounds) { const auto it = mCache.find(agentBounds); if (it == mCache.end()) return true; if (!resetIfUnique(it->second)) return false; mCache.erase(agentBounds); mChangedTiles.erase(agentBounds); mPlayerTile.erase(agentBounds); mLastRecastMeshManagerRevision.erase(agentBounds); return true; } void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType) { mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType}); const auto startTilePosition = getTilePosition(mSettings.mRecast, start); const auto endTilePosition = getTilePosition(mSettings.mRecast, end); addChangedTile(startTilePosition, ChangeType::add); if (startTilePosition != endTilePosition) addChangedTile(endTilePosition, ChangeType::add); } void NavMeshManager::removeOffMeshConnections(const ObjectId id) { const auto changedTiles = mOffMeshConnectionsManager.remove(id); for (const auto& tile : changedTiles) addChangedTile(tile, ChangeType::update); } void NavMeshManager::update(const osg::Vec3f& playerPosition, const AgentBounds& agentBounds) { const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); auto& lastRevision = mLastRecastMeshManagerRevision[agentBounds]; auto lastPlayerTile = mPlayerTile.find(agentBounds); if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() && lastPlayerTile->second == playerTile) return; lastRevision = mRecastMeshManager.getRevision(); if (lastPlayerTile == mPlayerTile.end()) lastPlayerTile = mPlayerTile.insert(std::make_pair(agentBounds, playerTile)).first; else lastPlayerTile->second = playerTile; std::map tilesToPost; const auto cached = getCached(agentBounds); if (!cached) { std::ostringstream stream; stream << "Agent with half extents is not found: " << agentBounds; throw InvalidArgument(stream.str()); } const auto changedTiles = mChangedTiles.find(agentBounds); { const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); if (changedTiles != mChangedTiles.end()) { for (const auto& tile : changedTiles->second) if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0)) { auto tileToPost = tilesToPost.find(tile.first); if (tileToPost == tilesToPost.end()) tilesToPost.insert(tile); else tileToPost->second = addChangeType(tileToPost->second, tile.second); } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) return; const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); if (shouldAdd && !presentInNavMesh) tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); else recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); if (changedTiles != mChangedTiles.end()) changedTiles->second.clear(); Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << " playerTile=" << lastPlayerTile->second << " recastMeshManagerRevision=" << lastRevision; } void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { mAsyncNavMeshUpdater.wait(listener, waitConditionType); } SharedNavMeshCacheItem NavMeshManager::getNavMesh(const AgentBounds& agentBounds) const { return getCached(agentBounds); } std::map NavMeshManager::getNavMeshes() const { return mCache; } void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats); } RecastMeshTiles NavMeshManager::getRecastMeshTiles() const { std::vector tiles; mRecastMeshManager.forEachTile( [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); const std::string worldspace = mRecastMeshManager.getWorldspace(); RecastMeshTiles result; for (const TilePosition& tile : tiles) if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile)) result.emplace(tile, std::move(mesh)); return result; } void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { const auto bounds = mRecastMeshManager.getBounds(); getTilesPositions(makeTilesPositionsRange(shape, transform, bounds, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } void NavMeshManager::addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType) { if (cellSize == std::numeric_limits::max()) return; getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } void NavMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType) { for (const auto& cached : mCache) { auto& tiles = mChangedTiles[cached.first]; auto tile = tiles.find(tilePosition); if (tile == tiles.end()) tiles.insert(std::make_pair(tilePosition, changeType)); else tile->second = addChangeType(tile->second, changeType); } } SharedNavMeshCacheItem NavMeshManager::getCached(const AgentBounds& agentBounds) const { const auto cached = mCache.find(agentBounds); if (cached != mCache.end()) return cached->second; return SharedNavMeshCacheItem(); } } openmw-openmw-0.48.0/components/detournavigator/navmeshmanager.hpp000066400000000000000000000060711445372753700255470ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H #include "asyncnavmeshupdater.hpp" #include "cachedrecastmeshmanager.hpp" #include "offmeshconnectionsmanager.hpp" #include "recastmeshtiles.hpp" #include "waitconditiontype.hpp" #include "heightfieldshape.hpp" #include "agentbounds.hpp" #include #include #include class dtNavMesh; namespace DetourNavigator { class NavMeshManager { public: explicit NavMeshManager(const Settings& settings, std::unique_ptr&& db); void setWorldspace(std::string_view worldspace); void updateBounds(const osg::Vec3f& playerPosition); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool removeObject(const ObjectId id); void addAgent(const AgentBounds& agentBounds); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); bool removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); bool removeHeightfield(const osg::Vec2i& cellPosition); bool reset(const AgentBounds& agentBounds); void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType); void removeOffMeshConnections(const ObjectId id); void update(const osg::Vec3f& playerPosition, const AgentBounds& agentBounds); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const; std::map getNavMeshes() const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; RecastMeshTiles getRecastMeshTiles() const; private: const Settings& mSettings; std::string mWorldspace; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; AsyncNavMeshUpdater mAsyncNavMeshUpdater; std::map mCache; std::map> mChangedTiles; std::size_t mGenerationCounter = 0; std::map mPlayerTile; std::map mLastRecastMeshManagerRevision; void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); void addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const; }; } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshtilescache.cpp000066400000000000000000000106161445372753700262340ustar00rootroot00000000000000#include "navmeshtilescache.hpp" #include #include namespace DetourNavigator { NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), mHitCount(0), mGetCount(0) {} NavMeshTilesCache::Value NavMeshTilesCache::get(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh) { const std::lock_guard lock(mMutex); ++mGetCount; const auto tile = mValues.find(std::tie(agentBounds, changedTile, recastMesh)); if (tile == mValues.end()) return Value(); acquireItemUnsafe(tile->second); ++mHitCount; return Value(*this, tile->second); } NavMeshTilesCache::Value NavMeshTilesCache::set(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh, std::unique_ptr&& value) { const auto itemSize = sizeof(RecastMesh) + getSize(recastMesh) + (value == nullptr ? 0 : sizeof(PreparedNavMeshData) + getSize(*value)); const std::lock_guard lock(mMutex); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); RecastMeshData key {recastMesh.getMesh(), recastMesh.getWater(), recastMesh.getHeightfields(), recastMesh.getFlatHeightfields()}; const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentBounds, changedTile, std::move(key), itemSize); const auto emplaced = mValues.emplace(std::make_tuple(agentBounds, changedTile, std::cref(iterator->mRecastMeshData)), iterator); if (!emplaced.second) { mFreeItems.erase(iterator); acquireItemUnsafe(emplaced.first->second); ++mGetCount; ++mHitCount; return Value(*this, emplaced.first->second); } iterator->mPreparedNavMeshData = std::move(value); ++iterator->mUseCount; mUsedNavMeshDataSize += itemSize; mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); return Value(*this, iterator); } NavMeshTilesCache::Stats NavMeshTilesCache::getStats() const { Stats result; { const std::lock_guard lock(mMutex); result.mNavMeshCacheSize = mUsedNavMeshDataSize; result.mUsedNavMeshTiles = mBusyItems.size(); result.mCachedNavMeshTiles = mFreeItems.size(); result.mHitCount = mHitCount; result.mGetCount = mGetCount; } return result; } void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out) { out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast(stats.mNavMeshCacheSize)); out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast(stats.mUsedNavMeshTiles)); out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast(stats.mCachedNavMeshTiles)); if (stats.mGetCount > 0) out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() { const auto& item = mFreeItems.back(); const auto value = mValues.find(std::tie(item.mAgentBounds, item.mChangedTile, item.mRecastMeshData)); if (value == mValues.end()) return; mUsedNavMeshDataSize -= item.mSize; mFreeNavMeshDataSize -= item.mSize; mValues.erase(value); mFreeItems.pop_back(); } void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator) { if (++iterator->mUseCount > 1) return; mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); mFreeNavMeshDataSize -= iterator->mSize; } void NavMeshTilesCache::releaseItem(ItemIterator iterator) { if (--iterator->mUseCount > 0) return; const std::lock_guard lock(mMutex); mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); mFreeNavMeshDataSize += iterator->mSize; } } openmw-openmw-0.48.0/components/detournavigator/navmeshtilescache.hpp000066400000000000000000000115341445372753700262410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #include "preparednavmeshdata.hpp" #include "recastmesh.hpp" #include "tileposition.hpp" #include "agentbounds.hpp" #include #include #include #include #include #include #include namespace osg { class Stats; } namespace DetourNavigator { struct RecastMeshData { Mesh mMesh; std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; }; inline bool operator <(const RecastMeshData& lhs, const RecastMeshData& rhs) { return std::tie(lhs.mMesh, lhs.mWater, lhs.mHeightfields, lhs.mFlatHeightfields) < std::tie(rhs.mMesh, rhs.mWater, rhs.mHeightfields, rhs.mFlatHeightfields); } inline bool operator <(const RecastMeshData& lhs, const RecastMesh& rhs) { return std::tie(lhs.mMesh, lhs.mWater, lhs.mHeightfields, lhs.mFlatHeightfields) < std::tie(rhs.getMesh(), rhs.getWater(), rhs.getHeightfields(), rhs.getFlatHeightfields()); } inline bool operator <(const RecastMesh& lhs, const RecastMeshData& rhs) { return std::tie(lhs.getMesh(), lhs.getWater(), lhs.getHeightfields(), lhs.getFlatHeightfields()) < std::tie(rhs.mMesh, rhs.mWater, rhs.mHeightfields, rhs.mFlatHeightfields); } class NavMeshTilesCache { public: struct Item { std::atomic mUseCount; AgentBounds mAgentBounds; TilePosition mChangedTile; RecastMeshData mRecastMeshData; std::unique_ptr mPreparedNavMeshData; std::size_t mSize; Item(const AgentBounds& agentBounds, const TilePosition& changedTile, RecastMeshData&& recastMeshData, std::size_t size) : mUseCount(0) , mAgentBounds(agentBounds) , mChangedTile(changedTile) , mRecastMeshData(std::move(recastMeshData)) , mSize(size) {} }; using ItemIterator = std::list::iterator; class Value { public: Value() : mOwner(nullptr), mIterator() {} Value(NavMeshTilesCache& owner, ItemIterator iterator) : mOwner(&owner), mIterator(iterator) { } Value(const Value& other) = delete; Value(Value&& other) : mOwner(other.mOwner), mIterator(other.mIterator) { other.mOwner = nullptr; } ~Value() { if (mOwner) mOwner->releaseItem(mIterator); } Value& operator =(const Value& other) = delete; Value& operator =(Value&& other) { if (mOwner) mOwner->releaseItem(mIterator); mOwner = other.mOwner; mIterator = other.mIterator; other.mOwner = nullptr; return *this; } const PreparedNavMeshData& get() const { return *mIterator->mPreparedNavMeshData; } operator bool() const { return mOwner; } private: NavMeshTilesCache* mOwner; ItemIterator mIterator; }; struct Stats { std::size_t mNavMeshCacheSize; std::size_t mUsedNavMeshTiles; std::size_t mCachedNavMeshTiles; std::size_t mHitCount; std::size_t mGetCount; }; NavMeshTilesCache(const std::size_t maxNavMeshDataSize); Value get(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh); Value set(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh, std::unique_ptr&& value); Stats getStats() const; private: mutable std::mutex mMutex; std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; std::size_t mFreeNavMeshDataSize; std::size_t mHitCount; std::size_t mGetCount; std::list mBusyItems; std::list mFreeItems; std::map>, ItemIterator, std::less<>> mValues; void removeLeastRecentlyUsed(); void acquireItemUnsafe(ItemIterator iterator); void releaseItem(ItemIterator iterator); }; void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif openmw-openmw-0.48.0/components/detournavigator/navmeshtileview.cpp000066400000000000000000000131471445372753700257620ustar00rootroot00000000000000#include "navmeshtileview.hpp" #include "ref.hpp" #include #include #include #include #include #include inline bool operator==(const dtMeshHeader& lhs, const dtMeshHeader& rhs) noexcept { const auto makeTuple = [] (const dtMeshHeader& v) { using DetourNavigator::ArrayRef; return std::tuple( v.x, v.y, v.layer, v.userId, v.polyCount, v.vertCount, v.maxLinkCount, v.detailMeshCount, v.detailVertCount, v.detailTriCount, v.bvNodeCount, v.offMeshConCount, v.offMeshBase, v.walkableHeight, v.walkableRadius, v.walkableClimb, v.detailVertCount, ArrayRef(v.bmin), ArrayRef(v.bmax), v.bvQuantFactor ); }; return makeTuple(lhs) == makeTuple(rhs); } inline bool operator==(const dtPoly& lhs, const dtPoly& rhs) noexcept { const auto makeTuple = [] (const dtPoly& v) { using DetourNavigator::ArrayRef; return std::tuple(ArrayRef(v.verts), ArrayRef(v.neis), v.flags, v.vertCount, v.areaAndtype); }; return makeTuple(lhs) == makeTuple(rhs); } inline bool operator==(const dtPolyDetail& lhs, const dtPolyDetail& rhs) noexcept { const auto makeTuple = [] (const dtPolyDetail& v) { return std::tuple(v.vertBase, v.triBase, v.vertCount, v.triCount); }; return makeTuple(lhs) == makeTuple(rhs); } inline bool operator==(const dtBVNode& lhs, const dtBVNode& rhs) noexcept { const auto makeTuple = [] (const dtBVNode& v) { using DetourNavigator::ArrayRef; return std::tuple(ArrayRef(v.bmin), ArrayRef(v.bmax), v.i); }; return makeTuple(lhs) == makeTuple(rhs); } inline bool operator==(const dtOffMeshConnection& lhs, const dtOffMeshConnection& rhs) noexcept { const auto makeTuple = [] (const dtOffMeshConnection& v) { using DetourNavigator::ArrayRef; return std::tuple(ArrayRef(v.pos), v.rad, v.poly, v.flags, v.side, v.userId); }; return makeTuple(lhs) == makeTuple(rhs); } namespace DetourNavigator { NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data) { const dtMeshHeader* header = reinterpret_cast(data); if (header->magic != DT_NAVMESH_MAGIC) throw std::logic_error("Invalid navmesh magic"); if (header->version != DT_NAVMESH_VERSION) throw std::logic_error("Invalid navmesh version"); // Similar code to https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/Detour/Source/DetourNavMesh.cpp#L978-L996 const int headerSize = dtAlign4(sizeof(dtMeshHeader)); const int vertsSize = dtAlign4(sizeof(float) * 3 * header->vertCount); const int polysSize = dtAlign4(sizeof(dtPoly) * header->polyCount); const int linksSize = dtAlign4(sizeof(dtLink) * (header->maxLinkCount)); const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * header->detailMeshCount); const int detailVertsSize = dtAlign4(sizeof(float) * 3 * header->detailVertCount); const int detailTrisSize = dtAlign4(sizeof(unsigned char) * 4 * header->detailTriCount); const int bvtreeSize = dtAlign4(sizeof(dtBVNode) * header->bvNodeCount); const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection) * header->offMeshConCount); const unsigned char* ptr = data + headerSize; NavMeshTileConstView view; view.mHeader = header; view.mVerts = dtGetThenAdvanceBufferPointer(ptr, vertsSize); view.mPolys = dtGetThenAdvanceBufferPointer(ptr, polysSize); ptr += linksSize; view.mDetailMeshes = dtGetThenAdvanceBufferPointer(ptr, detailMeshesSize); view.mDetailVerts = dtGetThenAdvanceBufferPointer(ptr, detailVertsSize); view.mDetailTris = dtGetThenAdvanceBufferPointer(ptr, detailTrisSize); view.mBvTree = dtGetThenAdvanceBufferPointer(ptr, bvtreeSize); view.mOffMeshCons = dtGetThenAdvanceBufferPointer(ptr, offMeshLinksSize); return view; } NavMeshTileConstView asNavMeshTileConstView(const dtMeshTile& tile) { NavMeshTileConstView view; view.mHeader = tile.header; view.mPolys = tile.polys; view.mVerts = tile.verts; view.mDetailMeshes = tile.detailMeshes; view.mDetailVerts = tile.detailVerts; view.mDetailTris = tile.detailTris; view.mBvTree = tile.bvTree; view.mOffMeshCons = tile.offMeshCons; return view; } bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs) noexcept { using DetourNavigator::Ref; using DetourNavigator::Span; const auto makeTuple = [] (const DetourNavigator::NavMeshTileConstView& v) { return std::tuple( Ref(*v.mHeader), Span(v.mPolys, v.mHeader->polyCount), Span(v.mVerts, v.mHeader->vertCount), Span(v.mDetailMeshes, v.mHeader->detailMeshCount), Span(v.mDetailVerts, v.mHeader->detailVertCount), Span(v.mDetailTris, v.mHeader->detailTriCount), Span(v.mBvTree, v.mHeader->bvNodeCount), Span(v.mOffMeshCons, v.mHeader->offMeshConCount) ); }; return makeTuple(lhs) == makeTuple(rhs); } } openmw-openmw-0.48.0/components/detournavigator/navmeshtileview.hpp000066400000000000000000000015621445372753700257650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILEVIEW_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILEVIEW_H struct dtMeshHeader; struct dtPoly; struct dtPolyDetail; struct dtBVNode; struct dtOffMeshConnection; struct dtMeshTile; namespace DetourNavigator { struct NavMeshTileConstView { const dtMeshHeader* mHeader; const dtPoly* mPolys; const float* mVerts; const dtPolyDetail* mDetailMeshes; const float* mDetailVerts; const unsigned char* mDetailTris; const dtBVNode* mBvTree; const dtOffMeshConnection* mOffMeshCons; friend bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs) noexcept; }; NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data); NavMeshTileConstView asNavMeshTileConstView(const dtMeshTile& tile); } #endif openmw-openmw-0.48.0/components/detournavigator/objectid.hpp000066400000000000000000000021701445372753700243320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H #include #include namespace DetourNavigator { class ObjectId { public: template explicit ObjectId(T* value) noexcept : mValue(reinterpret_cast(value)) { } explicit ObjectId(std::size_t value) noexcept : mValue(value) { } std::size_t value() const noexcept { return mValue; } friend bool operator <(const ObjectId lhs, const ObjectId rhs) noexcept { return lhs.mValue < rhs.mValue; } friend bool operator ==(const ObjectId lhs, const ObjectId rhs) noexcept { return lhs.mValue == rhs.mValue; } private: std::size_t mValue; }; } namespace std { template <> struct hash { std::size_t operator ()(const DetourNavigator::ObjectId value) const noexcept { return value.value(); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/objecttransform.hpp000066400000000000000000000011101445372753700257420ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H #include #include namespace DetourNavigator { struct ObjectTransform { ESM::Position mPosition; float mScale; friend inline auto tie(const ObjectTransform& v) { return std::tie(v.mPosition, v.mScale); } friend inline bool operator<(const ObjectTransform& l, const ObjectTransform& r) { return tie(l) < tie(r); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/offmeshconnection.hpp000066400000000000000000000011011445372753700262470ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H #include "areatype.hpp" #include #include #include namespace DetourNavigator { struct OffMeshConnection { osg::Vec3f mStart; osg::Vec3f mEnd; AreaType mAreaType; }; inline bool operator<(const OffMeshConnection& lhs, const OffMeshConnection& rhs) { return std::tie(lhs.mStart, lhs.mEnd, lhs.mAreaType) < std::tie(rhs.mStart, rhs.mEnd, rhs.mAreaType); } } #endif openmw-openmw-0.48.0/components/detournavigator/offmeshconnectionsmanager.cpp000066400000000000000000000060251445372753700277720ustar00rootroot00000000000000#include "offmeshconnectionsmanager.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" #include "objectid.hpp" #include "offmeshconnection.hpp" #include #include #include namespace DetourNavigator { OffMeshConnectionsManager::OffMeshConnectionsManager(const RecastSettings& settings) : mSettings(settings) {} void OffMeshConnectionsManager::add(const ObjectId id, const OffMeshConnection& value) { const auto values = mValues.lock(); values->mById.insert(std::make_pair(id, value)); const auto startTilePosition = getTilePosition(mSettings, value.mStart); const auto endTilePosition = getTilePosition(mSettings, value.mEnd); values->mByTilePosition[startTilePosition].insert(id); if (startTilePosition != endTilePosition) values->mByTilePosition[endTilePosition].insert(id); } std::set OffMeshConnectionsManager::remove(const ObjectId id) { const auto values = mValues.lock(); const auto byId = values->mById.equal_range(id); if (byId.first == byId.second) return {}; std::set removed; std::for_each(byId.first, byId.second, [&] (const auto& v) { const auto startTilePosition = getTilePosition(mSettings, v.second.mStart); const auto endTilePosition = getTilePosition(mSettings, v.second.mEnd); removed.emplace(startTilePosition); if (startTilePosition != endTilePosition) removed.emplace(endTilePosition); }); for (const TilePosition& tilePosition : removed) { const auto it = values->mByTilePosition.find(tilePosition); if (it == values->mByTilePosition.end()) continue; it->second.erase(id); if (it->second.empty()) values->mByTilePosition.erase(it); } values->mById.erase(byId.first, byId.second); return removed; } std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) const { std::vector result; const auto values = mValues.lockConst(); const auto itByTilePosition = values->mByTilePosition.find(tilePosition); if (itByTilePosition == values->mByTilePosition.end()) return result; std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), [&] (const ObjectId v) { const auto byId = values->mById.equal_range(v); std::for_each(byId.first, byId.second, [&] (const auto& v) { if (getTilePosition(mSettings, v.second.mStart) == tilePosition || getTilePosition(mSettings, v.second.mEnd) == tilePosition) result.push_back(v.second); }); }); std::sort(result.begin(), result.end()); return result; } } openmw-openmw-0.48.0/components/detournavigator/offmeshconnectionsmanager.hpp000066400000000000000000000020211445372753700277670ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H #include "settings.hpp" #include "tileposition.hpp" #include "objectid.hpp" #include "offmeshconnection.hpp" #include #include #include #include #include namespace DetourNavigator { class OffMeshConnectionsManager { public: explicit OffMeshConnectionsManager(const RecastSettings& settings); void add(const ObjectId id, const OffMeshConnection& value); std::set remove(const ObjectId id); std::vector get(const TilePosition& tilePosition) const; private: struct Values { std::multimap mById; std::map> mByTilePosition; }; const RecastSettings& mSettings; Misc::ScopeGuarded mValues; }; } #endif openmw-openmw-0.48.0/components/detournavigator/oscillatingrecastmeshobject.cpp000066400000000000000000000042461445372753700303260ustar00rootroot00000000000000#include "oscillatingrecastmeshobject.hpp" #include "tilebounds.hpp" #include #include namespace DetourNavigator { namespace { void limitBy(btAABB& aabb, const TileBounds& bounds) { aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast(bounds.mMin.x()))); aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast(bounds.mMin.y()))); aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast(bounds.mMax.x()))); aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast(bounds.mMax.y()))); } } OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision) : mImpl(std::move(impl)) , mLastChangeRevision(lastChangeRevision) , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) { } OscillatingRecastMeshObject::OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision) : mImpl(impl) , mLastChangeRevision(lastChangeRevision) , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) { } bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision, const TileBounds& bounds) { const btTransform oldTransform = mImpl.getTransform(); if (!mImpl.update(transform, areaType)) return false; if (transform == oldTransform) return true; if (mLastChangeRevision != lastChangeRevision) { mLastChangeRevision = lastChangeRevision; // btAABB doesn't have copy-assignment operator const btAABB aabb = BulletHelpers::getAabb(mImpl.getShape(), transform); mAabb.m_min = aabb.m_min; mAabb.m_max = aabb.m_max; return true; } const btAABB currentAabb = mAabb; mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform)); limitBy(mAabb, bounds); return currentAabb != mAabb; } } openmw-openmw-0.48.0/components/detournavigator/oscillatingrecastmeshobject.hpp000066400000000000000000000017501445372753700303300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H #include "areatype.hpp" #include "recastmeshobject.hpp" #include "tilebounds.hpp" #include #include namespace DetourNavigator { class OscillatingRecastMeshObject { public: explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision); explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision); bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision, const TileBounds& bounds); const RecastMeshObject& getImpl() const { return mImpl; } private: RecastMeshObject mImpl; std::size_t mLastChangeRevision; btAABB mAabb; }; } #endif openmw-openmw-0.48.0/components/detournavigator/preparednavmeshdata.cpp000066400000000000000000000021551445372753700265630ustar00rootroot00000000000000#include "preparednavmeshdata.hpp" #include "preparednavmeshdatatuple.hpp" #include "recast.hpp" #include #include namespace { void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept { value.meshes = nullptr; value.verts = nullptr; value.tris = nullptr; value.nmeshes = 0; value.nverts = 0; value.ntris = 0; } } namespace DetourNavigator { PreparedNavMeshData::PreparedNavMeshData() noexcept { initPolyMeshDetail(mPolyMeshDetail); } PreparedNavMeshData::PreparedNavMeshData(const PreparedNavMeshData& other) : mUserId(other.mUserId) , mCellSize(other.mCellSize) , mCellHeight(other.mCellHeight) { copyPolyMesh(other.mPolyMesh, mPolyMesh); copyPolyMeshDetail(other.mPolyMeshDetail, mPolyMeshDetail); } PreparedNavMeshData::~PreparedNavMeshData() noexcept { freePolyMeshDetail(mPolyMeshDetail); } bool operator==(const PreparedNavMeshData& lhs, const PreparedNavMeshData& rhs) noexcept { return makeTuple(lhs) == makeTuple(rhs); } } openmw-openmw-0.48.0/components/detournavigator/preparednavmeshdata.hpp000066400000000000000000000027601445372753700265720ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H #include "recast.hpp" #include #include namespace DetourNavigator { struct PreparedNavMeshData { unsigned int mUserId = 0; float mCellSize = 0; float mCellHeight = 0; rcPolyMesh mPolyMesh; rcPolyMeshDetail mPolyMeshDetail; PreparedNavMeshData() noexcept; PreparedNavMeshData(const PreparedNavMeshData& other); ~PreparedNavMeshData() noexcept; friend bool operator==(const PreparedNavMeshData& lhs, const PreparedNavMeshData& rhs) noexcept; }; inline constexpr std::size_t getSize(const rcPolyMesh& value) noexcept { return getVertsLength(value) * sizeof(*value.verts) + getPolysLength(value) * sizeof(*value.polys) + getRegsLength(value) * sizeof(*value.regs) + getFlagsLength(value) * sizeof(*value.flags) + getAreasLength(value) * sizeof(*value.areas); } inline constexpr std::size_t getSize(const rcPolyMeshDetail& value) noexcept { return getMeshesLength(value) * sizeof(*value.meshes) + getVertsLength(value) * sizeof(*value.verts) + getTrisLength(value) * sizeof(*value.tris); } inline constexpr std::size_t getSize(const PreparedNavMeshData& value) noexcept { return getSize(value.mPolyMesh) + getSize(value.mPolyMeshDetail); } } #endif openmw-openmw-0.48.0/components/detournavigator/preparednavmeshdatatuple.hpp000066400000000000000000000033501445372753700276400ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATATUPLE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATATUPLE_H #include "preparednavmeshdata.hpp" #include "ref.hpp" #include "recast.hpp" #include #include inline bool operator==(const rcPolyMesh& lhs, const rcPolyMesh& rhs) noexcept { const auto makeTuple = [] (const rcPolyMesh& v) { using namespace DetourNavigator; return std::tuple( Span(v.verts, static_cast(getVertsLength(v))), Span(v.polys, static_cast(getPolysLength(v))), Span(v.regs, static_cast(getRegsLength(v))), Span(v.flags, static_cast(getFlagsLength(v))), Span(v.areas, static_cast(getAreasLength(v))), ArrayRef(v.bmin), ArrayRef(v.bmax), v.cs, v.ch, v.borderSize, v.maxEdgeError ); }; return makeTuple(lhs) == makeTuple(rhs); } inline bool operator==(const rcPolyMeshDetail& lhs, const rcPolyMeshDetail& rhs) noexcept { const auto makeTuple = [] (const rcPolyMeshDetail& v) { using namespace DetourNavigator; return std::tuple( Span(v.meshes, static_cast(getMeshesLength(v))), Span(v.verts, static_cast(getVertsLength(v))), Span(v.tris, static_cast(getTrisLength(v))) ); }; return makeTuple(lhs) == makeTuple(rhs); } namespace DetourNavigator { inline auto makeTuple(const PreparedNavMeshData& v) noexcept { return std::tuple( v.mUserId, v.mCellHeight, v.mCellSize, Ref(v.mPolyMesh), Ref(v.mPolyMeshDetail) ); } } #endif openmw-openmw-0.48.0/components/detournavigator/raycast.cpp000066400000000000000000000026751445372753700242220ustar00rootroot00000000000000#include "raycast.hpp" #include "settings.hpp" #include "findsmoothpath.hpp" #include #include #include namespace DetourNavigator { std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) return {}; dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); dtPolyRef ref = 0; if (dtStatus status = navMeshQuery.findNearestPoly(start.ptr(), halfExtents.ptr(), &queryFilter, &ref, nullptr); dtStatusFailed(status) || ref == 0) return {}; const unsigned options = 0; std::array path; dtRaycastHit hit; hit.path = path.data(); hit.maxPath = path.size(); if (dtStatus status = navMeshQuery.raycast(ref, start.ptr(), end.ptr(), &queryFilter, options, &hit); dtStatusFailed(status) || hit.pathCount == 0) return {}; osg::Vec3f hitPosition; if (dtStatus status = navMeshQuery.closestPointOnPoly(path[hit.pathCount - 1], end.ptr(), hitPosition.ptr(), nullptr); dtStatusFailed(status)) return {}; return hitPosition; } } openmw-openmw-0.48.0/components/detournavigator/raycast.hpp000066400000000000000000000007171445372753700242220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H #include "flags.hpp" #include #include class dtNavMesh; namespace DetourNavigator { struct DetourSettings; std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings); } #endif openmw-openmw-0.48.0/components/detournavigator/recast.cpp000066400000000000000000000050401445372753700240220ustar00rootroot00000000000000#include "recast.hpp" #include #include #include #include namespace DetourNavigator { void* permRecastAlloc(std::size_t size) { void* const result = rcAlloc(size, RC_ALLOC_PERM); if (result == nullptr) throw std::bad_alloc(); return result; } void permRecastAlloc(rcPolyMesh& value) { permRecastAlloc(value.verts, getVertsLength(value)); permRecastAlloc(value.polys, getPolysLength(value)); permRecastAlloc(value.regs, getRegsLength(value)); permRecastAlloc(value.flags, getFlagsLength(value)); permRecastAlloc(value.areas, getAreasLength(value)); } void permRecastAlloc(rcPolyMeshDetail& value) { try { permRecastAlloc(value.meshes, getMeshesLength(value)); permRecastAlloc(value.verts, getVertsLength(value)); permRecastAlloc(value.tris, getTrisLength(value)); } catch (...) { freePolyMeshDetail(value); throw; } } void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept { rcFree(value.meshes); rcFree(value.verts); rcFree(value.tris); } void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst) { dst.nverts = src.nverts; dst.npolys = src.npolys; dst.maxpolys = src.maxpolys; dst.nvp = src.nvp; rcVcopy(dst.bmin, src.bmin); rcVcopy(dst.bmax, src.bmax); dst.cs = src.cs; dst.ch = src.ch; dst.borderSize = src.borderSize; dst.maxEdgeError = src.maxEdgeError; permRecastAlloc(dst); std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); std::memcpy(dst.polys, src.polys, getPolysLength(src) * sizeof(*dst.polys)); std::memcpy(dst.regs, src.regs, getRegsLength(src) * sizeof(*dst.regs)); std::memcpy(dst.flags, src.flags, getFlagsLength(src) * sizeof(*dst.flags)); std::memcpy(dst.areas, src.areas, getAreasLength(src) * sizeof(*dst.areas)); } void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) { dst.nmeshes = src.nmeshes; dst.nverts = src.nverts; dst.ntris = src.ntris; permRecastAlloc(dst); std::memcpy(dst.meshes, src.meshes, getMeshesLength(src) * sizeof(*dst.meshes)); std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); std::memcpy(dst.tris, src.tris, getTrisLength(src) * sizeof(*dst.tris)); } } openmw-openmw-0.48.0/components/detournavigator/recast.hpp000066400000000000000000000037121445372753700240330ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #include #include #include #include namespace DetourNavigator { constexpr std::size_t getVertsLength(const rcPolyMesh& value) noexcept { return 3 * static_cast(value.nverts); } constexpr std::size_t getPolysLength(const rcPolyMesh& value) noexcept { return 2 * static_cast(value.maxpolys * value.nvp); } constexpr std::size_t getRegsLength(const rcPolyMesh& value) noexcept { return static_cast(value.maxpolys); } constexpr std::size_t getFlagsLength(const rcPolyMesh& value) noexcept { return static_cast(value.npolys); } constexpr std::size_t getAreasLength(const rcPolyMesh& value) noexcept { return static_cast(value.maxpolys); } constexpr std::size_t getMeshesLength(const rcPolyMeshDetail& value) noexcept { return 4 * static_cast(value.nmeshes); } constexpr std::size_t getVertsLength(const rcPolyMeshDetail& value) noexcept { return 3 * static_cast(value.nverts); } constexpr std::size_t getTrisLength(const rcPolyMeshDetail& value) noexcept { return 4 * static_cast(value.ntris); } void* permRecastAlloc(std::size_t size); template inline void permRecastAlloc(T*& values, std::size_t size) { static_assert(std::is_arithmetic_v); values = new (permRecastAlloc(size * sizeof(T))) T[size]; } void permRecastAlloc(rcPolyMesh& value); void permRecastAlloc(rcPolyMeshDetail& value); void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept; void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst); void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst); } #endif openmw-openmw-0.48.0/components/detournavigator/recastallocutils.hpp000066400000000000000000000040271445372753700261270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTALLOCUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTALLOCUTILS_H #include #include namespace DetourNavigator { static_assert(sizeof(std::size_t) == sizeof(void*), ""); enum BufferType : std::size_t { BufferType_perm, BufferType_temp, BufferType_unused, }; inline BufferType* tempPtrBufferType(void* ptr) { return reinterpret_cast(static_cast(ptr) + 1); } inline BufferType getTempPtrBufferType(void* ptr) { return *tempPtrBufferType(ptr); } inline void setTempPtrBufferType(void* ptr, BufferType value) { *tempPtrBufferType(ptr) = value; } inline void** tempPtrPrev(void* ptr) { return static_cast(ptr); } inline void* getTempPtrPrev(void* ptr) { return *tempPtrPrev(ptr); } inline void setTempPtrPrev(void* ptr, void* value) { *tempPtrPrev(ptr) = value; } inline void* getTempPtrDataPtr(void* ptr) { return reinterpret_cast(static_cast(ptr) + 2); } inline BufferType* dataPtrBufferType(void* dataPtr) { return reinterpret_cast(static_cast(dataPtr) - 1); } inline BufferType getDataPtrBufferType(void* dataPtr) { return *dataPtrBufferType(dataPtr); } inline void setDataPtrBufferType(void* dataPtr, BufferType value) { *dataPtrBufferType(dataPtr) = value; } inline void* getTempDataPtrStackPtr(void* dataPtr) { return static_cast(dataPtr) - 2; } inline void* getPermDataPtrHeapPtr(void* dataPtr) { return static_cast(dataPtr) - 1; } inline void setPermPtrBufferType(void* ptr, BufferType value) { *static_cast(ptr) = value; } inline void* getPermPtrDataPtr(void* ptr) { return static_cast(ptr) + 1; } } #endif openmw-openmw-0.48.0/components/detournavigator/recastglobalallocator.hpp000066400000000000000000000034731445372753700271210ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTGLOBALALLOCATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTGLOBALALLOCATOR_H #include "recasttempallocator.hpp" #include namespace DetourNavigator { class RecastGlobalAllocator { public: static void init() { instance(); } static void* alloc(size_t size, rcAllocHint hint) { void* result = nullptr; if (rcLikely(hint == RC_ALLOC_TEMP)) result = tempAllocator().alloc(size); if (rcUnlikely(!result)) result = allocPerm(size); return result; } static void free(void* ptr) { if (rcUnlikely(!ptr)) return; if (rcLikely(BufferType_temp == getDataPtrBufferType(ptr))) tempAllocator().free(ptr); else { assert(BufferType_perm == getDataPtrBufferType(ptr)); std::free(getPermDataPtrHeapPtr(ptr)); } } private: RecastGlobalAllocator() { rcAllocSetCustom(&RecastGlobalAllocator::alloc, &RecastGlobalAllocator::free); } static RecastGlobalAllocator& instance() { static RecastGlobalAllocator value; return value; } static RecastTempAllocator& tempAllocator() { static thread_local RecastTempAllocator value(1024ul * 1024ul); return value; } static void* allocPerm(size_t size) { const auto ptr = std::malloc(size + sizeof(std::size_t)); if (rcUnlikely(!ptr)) return ptr; setPermPtrBufferType(ptr, BufferType_perm); return getPermPtrDataPtr(ptr); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/recastmesh.cpp000066400000000000000000000026041445372753700247020ustar00rootroot00000000000000#include "recastmesh.hpp" #include "exceptions.hpp" #include namespace DetourNavigator { Mesh::Mesh(std::vector&& indices, std::vector&& vertices, std::vector&& areaTypes) { if (indices.size() / 3 != areaTypes.size()) throw InvalidArgument("Number of flags doesn't match number of triangles: triangles=" + std::to_string(indices.size() / 3) + ", areaTypes=" + std::to_string(areaTypes.size())); indices.shrink_to_fit(); vertices.shrink_to_fit(); areaTypes.shrink_to_fit(); mIndices = std::move(indices); mVertices = std::move(vertices); mAreaTypes = std::move(areaTypes); } RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, std::vector heightfields, std::vector flatHeightfields, std::vector meshSources) : mGeneration(generation) , mRevision(revision) , mMesh(std::move(mesh)) , mWater(std::move(water)) , mHeightfields(std::move(heightfields)) , mFlatHeightfields(std::move(flatHeightfields)) , mMeshSources(std::move(meshSources)) { mWater.shrink_to_fit(); mHeightfields.shrink_to_fit(); for (Heightfield& v : mHeightfields) v.mHeights.shrink_to_fit(); } } openmw-openmw-0.48.0/components/detournavigator/recastmesh.hpp000066400000000000000000000132451445372753700247120ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #include "areatype.hpp" #include "bounds.hpp" #include "tilebounds.hpp" #include "objecttransform.hpp" #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { class Mesh { public: Mesh(std::vector&& indices, std::vector&& vertices, std::vector&& areaTypes); const std::vector& getIndices() const noexcept { return mIndices; } const std::vector& getVertices() const noexcept { return mVertices; } const std::vector& getAreaTypes() const noexcept { return mAreaTypes; } std::size_t getVerticesCount() const noexcept { return mVertices.size() / 3; } std::size_t getTrianglesCount() const noexcept { return mAreaTypes.size(); } private: std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; friend inline bool operator<(const Mesh& lhs, const Mesh& rhs) noexcept { return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes) < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes); } friend inline std::size_t getSize(const Mesh& value) noexcept { return value.mIndices.size() * sizeof(int) + value.mVertices.size() * sizeof(float) + value.mAreaTypes.size() * sizeof(AreaType); } }; struct Water { int mCellSize; float mLevel; }; inline bool operator<(const Water& lhs, const Water& rhs) noexcept { const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); }; return tie(lhs) < tie(rhs); } struct CellWater { osg::Vec2i mCellPosition; Water mWater; }; inline bool operator<(const CellWater& lhs, const CellWater& rhs) noexcept { const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); }; return tie(lhs) < tie(rhs); } inline osg::Vec2f getWaterShift2d(const osg::Vec2i& cellPosition, int cellSize) { return osg::Vec2f((cellPosition.x() + 0.5f) * cellSize, (cellPosition.y() + 0.5f) * cellSize); } inline osg::Vec3f getWaterShift3d(const osg::Vec2i& cellPosition, int cellSize, float level) { return osg::Vec3f(getWaterShift2d(cellPosition, cellSize), level); } struct Heightfield { osg::Vec2i mCellPosition; int mCellSize; std::uint8_t mLength; float mMinHeight; float mMaxHeight; std::vector mHeights; std::size_t mOriginalSize; std::uint8_t mMinX; std::uint8_t mMinY; }; inline auto makeTuple(const Heightfield& v) noexcept { return std::tie(v.mCellPosition, v.mCellSize, v.mLength, v.mMinHeight, v.mMaxHeight, v.mHeights, v.mOriginalSize, v.mMinX, v.mMinY); } inline bool operator<(const Heightfield& lhs, const Heightfield& rhs) noexcept { return makeTuple(lhs) < makeTuple(rhs); } struct FlatHeightfield { osg::Vec2i mCellPosition; int mCellSize; float mHeight; }; inline bool operator<(const FlatHeightfield& lhs, const FlatHeightfield& rhs) noexcept { const auto tie = [] (const FlatHeightfield& v) { return std::tie(v.mCellPosition, v.mCellSize, v.mHeight); }; return tie(lhs) < tie(rhs); } struct MeshSource { osg::ref_ptr mShape; ObjectTransform mObjectTransform; AreaType mAreaType; }; class RecastMesh { public: RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, std::vector heightfields, std::vector flatHeightfields, std::vector sources); std::size_t getGeneration() const { return mGeneration; } std::size_t getRevision() const { return mRevision; } const Mesh& getMesh() const noexcept { return mMesh; } const std::vector& getWater() const { return mWater; } const std::vector& getHeightfields() const noexcept { return mHeightfields; } const std::vector& getFlatHeightfields() const noexcept { return mFlatHeightfields; } const std::vector& getMeshSources() const noexcept { return mMeshSources; } private: std::size_t mGeneration; std::size_t mRevision; Mesh mMesh; std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; std::vector mMeshSources; friend inline std::size_t getSize(const RecastMesh& value) noexcept { return getSize(value.mMesh) + value.mWater.size() * sizeof(CellWater) + value.mHeightfields.size() * sizeof(Heightfield) + std::accumulate(value.mHeightfields.begin(), value.mHeightfields.end(), std::size_t {0}, [] (std::size_t r, const Heightfield& v) { return r + v.mHeights.size() * sizeof(float); }) + value.mFlatHeightfields.size() * sizeof(FlatHeightfield); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/recastmeshbuilder.cpp000066400000000000000000000357701445372753700262630ustar00rootroot00000000000000#include "recastmeshbuilder.hpp" #include "debug.hpp" #include "exceptions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; namespace { RecastMeshTriangle makeRecastMeshTriangle(const btVector3* vertices, const AreaType areaType) { RecastMeshTriangle result; result.mAreaType = areaType; for (std::size_t i = 0; i < 3; ++i) result.mVertices[i] = Misc::Convert::toOsg(vertices[i]); return result; } float getHeightfieldScale(int cellSize, std::size_t dataSize) { return static_cast(cellSize) / (dataSize - 1); } bool isNan(const RecastMeshTriangle& triangle) { for (std::size_t i = 0; i < 3; ++i) if (std::isnan(triangle.mVertices[i].x()) || std::isnan(triangle.mVertices[i].y()) || std::isnan(triangle.mVertices[i].z())) return true; return false; } } Mesh makeMesh(std::vector&& triangles, const osg::Vec3f& shift) { std::vector uniqueVertices; uniqueVertices.reserve(3 * triangles.size()); for (const RecastMeshTriangle& triangle : triangles) for (const osg::Vec3f& vertex : triangle.mVertices) uniqueVertices.push_back(vertex); std::sort(uniqueVertices.begin(), uniqueVertices.end()); uniqueVertices.erase(std::unique(uniqueVertices.begin(), uniqueVertices.end()), uniqueVertices.end()); std::vector indices; indices.reserve(3 * triangles.size()); std::vector areaTypes; areaTypes.reserve(triangles.size()); for (const RecastMeshTriangle& triangle : triangles) { areaTypes.push_back(triangle.mAreaType); for (const osg::Vec3f& vertex : triangle.mVertices) { const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); assert(it != uniqueVertices.end()); assert(*it == vertex); indices.push_back(static_cast(it - uniqueVertices.begin())); } } triangles.clear(); std::vector vertices; vertices.reserve(3 * uniqueVertices.size()); for (const osg::Vec3f& vertex : uniqueVertices) { vertices.push_back(vertex.x() + shift.x()); vertices.push_back(vertex.y() + shift.y()); vertices.push_back(vertex.z() + shift.z()); } return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); } Mesh makeMesh(const Heightfield& heightfield) { using BulletHelpers::makeProcessTriangleCallback; using Misc::Convert::toOsg; constexpr int upAxis = 2; constexpr bool flipQuadEdges = false; #if BT_BULLET_VERSION < 310 std::vector heights(heightfield.mHeights.begin(), heightfield.mHeights.end()); btHeightfieldTerrainShape shape(static_cast(heightfield.mHeights.size() / heightfield.mLength), static_cast(heightfield.mLength), heights.data(), 1, heightfield.mMinHeight, heightfield.mMaxHeight, upAxis, PHY_FLOAT, flipQuadEdges ); #else btHeightfieldTerrainShape shape(static_cast(heightfield.mHeights.size() / heightfield.mLength), static_cast(heightfield.mLength), heightfield.mHeights.data(), heightfield.mMinHeight, heightfield.mMaxHeight, upAxis, flipQuadEdges); #endif const float scale = getHeightfieldScale(heightfield.mCellSize, heightfield.mOriginalSize); shape.setLocalScaling(btVector3(scale, scale, 1)); btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); std::vector triangles; auto callback = makeProcessTriangleCallback([&] (btVector3* vertices, int, int) { triangles.emplace_back(makeRecastMeshTriangle(vertices, AreaType_ground)); }); shape.processAllTriangles(&callback, aabbMin, aabbMax); const osg::Vec2f aabbShift = (osg::Vec2f(aabbMax.x(), aabbMax.y()) - osg::Vec2f(aabbMin.x(), aabbMin.y())) * 0.5; const osg::Vec2f tileShift = osg::Vec2f(heightfield.mMinX, heightfield.mMinY) * scale; const osg::Vec2f localShift = aabbShift + tileShift; const float cellSize = static_cast(heightfield.mCellSize); const osg::Vec3f cellShift( heightfield.mCellPosition.x() * cellSize, heightfield.mCellPosition.y() * cellSize, (heightfield.mMinHeight + heightfield.mMaxHeight) * 0.5f ); return makeMesh(std::move(triangles), cellShift + osg::Vec3f(localShift.x(), localShift.y(), 0)); } RecastMeshBuilder::RecastMeshBuilder(const TileBounds& bounds) noexcept : mBounds(bounds) { } void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType, osg::ref_ptr source, const ObjectTransform& objectTransform) { addObject(shape, transform, areaType); mSources.push_back(MeshSource {std::move(source), objectTransform, areaType}); } void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (shape.isCompound()) return addObject(static_cast(shape), transform, areaType); else if (shape.getShapeType() == TERRAIN_SHAPE_PROXYTYPE) return addObject(static_cast(shape), transform, areaType); else if (shape.isConcave()) return addObject(static_cast(shape), transform, areaType); else if (shape.getShapeType() == BOX_SHAPE_PROXYTYPE) return addObject(static_cast(shape), transform, areaType); std::ostringstream message; message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType()); throw InvalidArgument(message.str()); } void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType) { for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i), areaType); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, const AreaType areaType) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* vertices, int, int) { RecastMeshTriangle triangle = makeRecastMeshTriangle(vertices, areaType); std::reverse(triangle.mVertices.begin(), triangle.mVertices.end()); mTriangles.emplace_back(triangle); })); } void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const AreaType areaType) { addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* vertices, int, int) { mTriangles.emplace_back(makeRecastMeshTriangle(vertices, areaType)); })); } void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { constexpr std::array indices {{ 0, 2, 3, 3, 1, 0, 0, 4, 6, 6, 2, 0, 0, 1, 5, 5, 4, 0, 7, 5, 1, 1, 3, 7, 7, 3, 2, 2, 6, 7, 7, 6, 4, 4, 5, 7, }}; for (std::size_t i = 0; i < indices.size(); i += 3) { std::array vertices; for (std::size_t j = 0; j < 3; ++j) { btVector3 position; shape.getVertex(indices[i + j], position); vertices[j] = transform(position); } mTriangles.emplace_back(makeRecastMeshTriangle(vertices.data(), areaType)); } } void RecastMeshBuilder::addWater(const osg::Vec2i& cellPosition, const Water& water) { mWater.push_back(CellWater {cellPosition, water}); } void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height) { if (const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize))) mFlatHeightfields.emplace_back(FlatHeightfield {cellPosition, cellSize, height}); } void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights, std::size_t size, float minHeight, float maxHeight) { const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize)); if (!intersection.has_value()) return; const osg::Vec3f shift = Misc::Convert::toOsg(BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, minHeight, maxHeight)); const float stepSize = getHeightfieldScale(cellSize, size); const int halfCellSize = cellSize / 2; const auto local = [&] (float v, float shift) { return (v - shift + halfCellSize) / stepSize; }; const auto index = [&] (float v, int add) { return std::clamp(static_cast(v) + add, 0, size); }; const std::size_t minX = index(std::round(local(intersection->mMin.x(), shift.x())), -1); const std::size_t minY = index(std::round(local(intersection->mMin.y(), shift.y())), -1); const std::size_t maxX = index(std::round(local(intersection->mMax.x(), shift.x())), 1); const std::size_t maxY = index(std::round(local(intersection->mMax.y(), shift.y())), 1); const std::size_t endX = std::min(maxX + 1, size); const std::size_t endY = std::min(maxY + 1, size); const std::size_t sliceSize = (endX - minX) * (endY - minY); if (sliceSize == 0) return; std::vector tileHeights; tileHeights.reserve(sliceSize); for (std::size_t y = minY; y < endY; ++y) for (std::size_t x = minX; x < endX; ++x) tileHeights.push_back(heights[x + y * size]); Heightfield heightfield; heightfield.mCellPosition = cellPosition; heightfield.mCellSize = cellSize; heightfield.mLength = static_cast(endY - minY); heightfield.mMinHeight = minHeight; heightfield.mMaxHeight = maxHeight; heightfield.mHeights = std::move(tileHeights); heightfield.mOriginalSize = size; heightfield.mMinX = static_cast(minX); heightfield.mMinY = static_cast(minY); mHeightfields.push_back(std::move(heightfield)); } std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); return std::make_shared(generation, revision, std::move(mesh), std::move(mWater), std::move(mHeightfields), std::move(mFlatHeightfields), std::move(mSources)); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const btVector3 boundsMin(mBounds.mMin.x(), mBounds.mMin.y(), -std::numeric_limits::max() * std::numeric_limits::epsilon()); const btVector3 boundsMax(mBounds.mMax.x(), mBounds.mMax.y(), std::numeric_limits::max() * std::numeric_limits::epsilon()); auto wrapper = makeProcessTriangleCallback([&] (btVector3* triangle, int partId, int triangleIndex) { std::array transformed; for (std::size_t i = 0; i < 3; ++i) transformed[i] = transform(triangle[i]); if (TestTriangleAgainstAabb2(transformed.data(), boundsMin, boundsMax)) callback.processTriangle(transformed.data(), partId, triangleIndex); }); shape.processAllTriangles(&wrapper, aabbMin, aabbMax); } void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, btTriangleCallback&& callback) { using BulletHelpers::transformBoundingBox; btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); transformBoundingBox(transform, aabbMin, aabbMax); aabbMin.setX(std::max(static_cast(mBounds.mMin.x()), aabbMin.x())); aabbMin.setX(std::min(static_cast(mBounds.mMax.x()), aabbMin.x())); aabbMin.setY(std::max(static_cast(mBounds.mMin.y()), aabbMin.y())); aabbMin.setY(std::min(static_cast(mBounds.mMax.y()), aabbMin.y())); aabbMax.setX(std::max(static_cast(mBounds.mMin.x()), aabbMax.x())); aabbMax.setX(std::min(static_cast(mBounds.mMax.x()), aabbMax.x())); aabbMax.setY(std::max(static_cast(mBounds.mMin.y()), aabbMax.y())); aabbMax.setY(std::min(static_cast(mBounds.mMax.y()), aabbMax.y())); transformBoundingBox(transform.inverse(), aabbMin, aabbMax); auto wrapper = makeProcessTriangleCallback([&] (btVector3* triangle, int partId, int triangleIndex) { std::array transformed; for (std::size_t i = 0; i < 3; ++i) transformed[i] = transform(triangle[i]); callback.processTriangle(transformed.data(), partId, triangleIndex); }); shape.processAllTriangles(&wrapper, aabbMin, aabbMax); } } openmw-openmw-0.48.0/components/detournavigator/recastmeshbuilder.hpp000066400000000000000000000055021445372753700262560ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H #include "recastmesh.hpp" #include "tilebounds.hpp" #include #include #include #include #include #include #include class btBoxShape; class btCollisionShape; class btCompoundShape; class btConcaveShape; class btHeightfieldTerrainShape; class btTriangleCallback; namespace DetourNavigator { struct RecastMeshTriangle { AreaType mAreaType; std::array mVertices; friend inline bool operator<(const RecastMeshTriangle& lhs, const RecastMeshTriangle& rhs) { return std::tie(lhs.mAreaType, lhs.mVertices) < std::tie(rhs.mAreaType, rhs.mVertices); } }; class RecastMeshBuilder { public: explicit RecastMeshBuilder(const TileBounds& bounds) noexcept; void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType, osg::ref_ptr source, const ObjectTransform& objectTransform); void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btConcaveShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType); void addWater(const osg::Vec2i& cellPosition, const Water& water); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights, std::size_t size, float minHeight, float maxHeight); std::shared_ptr create(std::size_t generation, std::size_t revision) &&; private: const TileBounds mBounds; std::vector mTriangles; std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; std::vector mSources; inline void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, btTriangleCallback&& callback); }; Mesh makeMesh(std::vector&& triangles, const osg::Vec3f& shift = osg::Vec3f()); Mesh makeMesh(const Heightfield& heightfield); } #endif openmw-openmw-0.48.0/components/detournavigator/recastmeshmanager.cpp000066400000000000000000000144361445372753700262430ustar00rootroot00000000000000#include "recastmeshmanager.hpp" #include "recastmeshbuilder.hpp" #include "settings.hpp" #include "heightfieldshape.hpp" #include #include #include namespace { struct AddHeightfield { osg::Vec2i mCellPosition; int mCellSize; DetourNavigator::RecastMeshBuilder& mBuilder; void operator()(const DetourNavigator::HeightfieldSurface& v) { mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); } void operator()(DetourNavigator::HeightfieldPlane v) { mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight); } }; } namespace DetourNavigator { RecastMeshManager::RecastMeshManager(const TileBounds& bounds, std::size_t generation) : mGeneration(generation) , mTileBounds(bounds) { } bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { const std::lock_guard lock(mMutex); const auto object = mObjects.lower_bound(id); if (object != mObjects.end() && object->first == id) return false; mObjects.emplace_hint(object, id, OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); ++mRevision; return true; } bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return false; const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value() ? mLastNavMeshReportedChange->mRevision : mRevision; if (!object->second.update(transform, areaType, lastChangeRevision, mTileBounds)) return false; ++mRevision; return true; } std::optional RecastMeshManager::removeObject(const ObjectId id) { const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return std::nullopt; const RemovedRecastMeshObject result {object->second.getImpl().getShape(), object->second.getImpl().getTransform()}; mObjects.erase(object); ++mRevision; return result; } bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { const std::lock_guard lock(mMutex); if (!mWater.emplace(cellPosition, Water {cellSize, level}).second) return false; ++mRevision; return true; } std::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const std::lock_guard lock(mMutex); const auto water = mWater.find(cellPosition); if (water == mWater.end()) return std::nullopt; ++mRevision; Water result = water->second; mWater.erase(water); return result; } bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { const std::lock_guard lock(mMutex); if (!mHeightfields.emplace(cellPosition, SizedHeightfieldShape {cellSize, shape}).second) return false; ++mRevision; return true; } std::optional RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const std::lock_guard lock(mMutex); const auto it = mHeightfields.find(cellPosition); if (it == mHeightfields.end()) return std::nullopt; ++mRevision; auto result = std::make_optional(it->second); mHeightfields.erase(it); return result; } std::shared_ptr RecastMeshManager::getMesh() const { RecastMeshBuilder builder(mTileBounds); using Object = std::tuple< osg::ref_ptr, ObjectTransform, std::reference_wrapper, btTransform, AreaType >; std::vector objects; std::size_t revision; { const std::lock_guard lock(mMutex); for (const auto& [k, v] : mWater) builder.addWater(k, v); for (const auto& [cellPosition, v] : mHeightfields) std::visit(AddHeightfield {cellPosition, v.mCellSize, builder}, v.mShape); objects.reserve(mObjects.size()); for (const auto& [k, object] : mObjects) { const RecastMeshObject& impl = object.getImpl(); objects.emplace_back(impl.getInstance(), impl.getObjectTransform(), impl.getShape(), impl.getTransform(), impl.getAreaType()); } revision = mRevision; } for (const auto& [instance, objectTransform, shape, transform, areaType] : objects) builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform); return std::move(builder).create(mGeneration, revision); } bool RecastMeshManager::isEmpty() const { const std::lock_guard lock(mMutex); return mObjects.empty() && mWater.empty() && mHeightfields.empty(); } void RecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion) { if (recastMeshVersion.mGeneration != mGeneration) return; const std::lock_guard lock(mMutex); if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion) return; mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion}; if (!mLastNavMeshReportedChange.has_value() || mLastNavMeshReportedChange->mNavMeshVersion < mLastNavMeshReport->mNavMeshVersion) mLastNavMeshReportedChange = mLastNavMeshReport; } Version RecastMeshManager::getVersion() const { const std::lock_guard lock(mMutex); return Version {mGeneration, mRevision}; } } openmw-openmw-0.48.0/components/detournavigator/recastmeshmanager.hpp000066400000000000000000000045111445372753700262410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #include "oscillatingrecastmeshobject.hpp" #include "objectid.hpp" #include "version.hpp" #include "recastmesh.hpp" #include "heightfieldshape.hpp" #include #include #include #include #include #include class btCollisionShape; namespace DetourNavigator { struct Settings; class RecastMesh; struct RemovedRecastMeshObject { std::reference_wrapper mShape; btTransform mTransform; }; struct SizedHeightfieldShape { int mCellSize; HeightfieldShape mShape; }; class RecastMeshManager { public: explicit RecastMeshManager(const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); std::optional removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh() const; bool isEmpty() const; void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); Version getVersion() const; private: struct Report { std::size_t mRevision; Version mNavMeshVersion; }; const std::size_t mGeneration; const TileBounds mTileBounds; mutable std::mutex mMutex; std::size_t mRevision = 0; std::map mObjects; std::map mWater; std::map mHeightfields; std::optional mLastNavMeshReportedChange; std::optional mLastNavMeshReport; }; } #endif openmw-openmw-0.48.0/components/detournavigator/recastmeshobject.cpp000066400000000000000000000056321445372753700260750ustar00rootroot00000000000000#include "recastmeshobject.hpp" #include #include #include namespace DetourNavigator { namespace { bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType, std::vector& children) { assert(static_cast(shape.getNumChildShapes()) == children.size()); bool result = false; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) { assert(shape.getChildShape(i) == std::addressof(children[static_cast(i)].getShape())); result = children[static_cast(i)].update(shape.getChildTransform(i), areaType) || result; } return result; } std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType) { std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType); return result; } std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType) { if (shape.isCompound()) return makeChildrenObjects(static_cast(shape), areaType); return {}; } } ChildRecastMeshObject::ChildRecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) : mShape(shape) , mTransform(transform) , mAreaType(areaType) , mLocalScaling(shape.getLocalScaling()) , mChildren(makeChildrenObjects(shape, mAreaType)) { } bool ChildRecastMeshObject::update(const btTransform& transform, const AreaType areaType) { bool result = false; if (!(mTransform == transform)) { mTransform = transform; result = true; } if (mAreaType != areaType) { mAreaType = areaType; result = true; } if (!(mLocalScaling == mShape.get().getLocalScaling())) { mLocalScaling = mShape.get().getLocalScaling(); result = true; } if (mShape.get().isCompound()) result = updateCompoundObject(static_cast(mShape.get()), mAreaType, mChildren) || result; return result; } RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) : mInstance(shape.getInstance()) , mObjectTransform(shape.getObjectTransform()) , mImpl(shape.getShape(), transform, areaType) { } } openmw-openmw-0.48.0/components/detournavigator/recastmeshobject.hpp000066400000000000000000000055061445372753700261020ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #include "areatype.hpp" #include "objecttransform.hpp" #include #include #include #include #include #include class btCollisionShape; class btCompoundShape; namespace DetourNavigator { class CollisionShape { public: CollisionShape(osg::ref_ptr instance, const btCollisionShape& shape, const ObjectTransform& transform) : mInstance(std::move(instance)) , mShape(shape) , mObjectTransform(transform) {} const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mShape; } const ObjectTransform& getObjectTransform() const { return mObjectTransform; } private: osg::ref_ptr mInstance; std::reference_wrapper mShape; ObjectTransform mObjectTransform; }; class ChildRecastMeshObject { public: ChildRecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType); const btCollisionShape& getShape() const { return mShape; } const btTransform& getTransform() const { return mTransform; } AreaType getAreaType() const { return mAreaType; } private: std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; btVector3 mLocalScaling; std::vector mChildren; }; class RecastMeshObject { public: RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); } const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mImpl.getShape(); } const btTransform& getTransform() const { return mImpl.getTransform(); } AreaType getAreaType() const { return mImpl.getAreaType(); } const ObjectTransform& getObjectTransform() const { return mObjectTransform; } private: osg::ref_ptr mInstance; ObjectTransform mObjectTransform; ChildRecastMeshObject mImpl; }; } #endif openmw-openmw-0.48.0/components/detournavigator/recastmeshprovider.hpp000066400000000000000000000014231445372753700264600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H #include "tileposition.hpp" #include "recastmesh.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "version.hpp" #include #include namespace DetourNavigator { class RecastMesh; class RecastMeshProvider { public: RecastMeshProvider(TileCachedRecastMeshManager& impl) : mImpl(impl) {} std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const { return mImpl.get().getNewMesh(worldspace, tilePosition); } private: std::reference_wrapper mImpl; }; } #endif openmw-openmw-0.48.0/components/detournavigator/recastmeshtiles.hpp000066400000000000000000000005041445372753700257450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H #include "tileposition.hpp" #include #include namespace DetourNavigator { class RecastMesh; using RecastMeshTiles = std::map>; } #endif openmw-openmw-0.48.0/components/detournavigator/recastparams.hpp000066400000000000000000000017431445372753700252410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTPARAMS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTPARAMS_H #include "agentbounds.hpp" #include #include #include #include namespace DetourNavigator { inline float getAgentHeight(const AgentBounds& agentBounds) { return 2.0f * agentBounds.mHalfExtents.z(); } inline float getAgentRadius(const AgentBounds& agentBounds) { switch (agentBounds.mShapeType) { case CollisionShapeType::Aabb: return std::max(agentBounds.mHalfExtents.x(), agentBounds.mHalfExtents.y()) * std::sqrt(2); case CollisionShapeType::RotatingBox: return agentBounds.mHalfExtents.x(); case CollisionShapeType::Cylinder: return std::max(agentBounds.mHalfExtents.x(), agentBounds.mHalfExtents.y()); } assert(false && "Unsupported agent shape type"); return 0; } } #endif openmw-openmw-0.48.0/components/detournavigator/recasttempallocator.hpp000066400000000000000000000035461445372753700266270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTTEMPALLOCATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTTEMPALLOCATOR_H #include "recastallocutils.hpp" #include #include #include namespace DetourNavigator { class RecastTempAllocator { public: RecastTempAllocator(std::size_t capacity) : mStack(capacity), mTop(mStack.data()), mPrev(nullptr) {} void* alloc(std::size_t size) { std::size_t space = mStack.size() - getUsedSize(); void* top = mTop; const auto itemSize = 2 * sizeof(std::size_t) + size; if (rcUnlikely(!std::align(sizeof(std::size_t), itemSize, top, space))) return nullptr; setTempPtrBufferType(top, BufferType_temp); setTempPtrPrev(top, mPrev); mTop = static_cast(top) + itemSize; mPrev = static_cast(top); return getTempPtrDataPtr(top); } void free(void* ptr) { if (rcUnlikely(!ptr)) return; assert(BufferType_temp == getDataPtrBufferType(ptr)); if (!mPrev || getTempDataPtrStackPtr(ptr) != mPrev) { setDataPtrBufferType(ptr, BufferType_unused); return; } mTop = getTempDataPtrStackPtr(ptr); mPrev = getTempPtrPrev(mTop); while (mPrev && BufferType_unused == getTempPtrBufferType(mPrev)) { mTop = mPrev; mPrev = getTempPtrPrev(mTop); } return; } private: std::vector mStack; void* mTop; void* mPrev; std::size_t getUsedSize() const { return static_cast(static_cast(mTop) - mStack.data()); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/ref.hpp000066400000000000000000000025131445372753700233240ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_REF_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_REF_H #include #include #include namespace DetourNavigator { template struct Ref { T& mRef; constexpr explicit Ref(T& ref) noexcept : mRef(ref) {} friend bool operator==(const Ref& lhs, const Ref& rhs) { return lhs.mRef == rhs.mRef; } }; template struct ArrayRef { T (&mRef)[size]; constexpr explicit ArrayRef(T (&ref)[size]) noexcept : mRef(ref) {} friend bool operator==(const ArrayRef& lhs, const ArrayRef& rhs) { return std::equal(std::begin(lhs.mRef), std::end(lhs.mRef), std::begin(rhs.mRef)); } }; template struct Span { T* mBegin; T* mEnd; constexpr explicit Span(T* data, int size) noexcept : mBegin(data) , mEnd(data + static_cast(size)) {} friend bool operator==(const Span& lhs, const Span& rhs) { // size is already equal if headers are equal assert((lhs.mEnd - lhs.mBegin) == (rhs.mEnd - rhs.mBegin)); return std::equal(lhs.mBegin, lhs.mEnd, rhs.mBegin); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/serialization.cpp000066400000000000000000000251331445372753700254230ustar00rootroot00000000000000#include "serialization.hpp" #include "dbrefgeometryobject.hpp" #include "preparednavmeshdata.hpp" #include "recast.hpp" #include "recastmesh.hpp" #include "settings.hpp" #include "agentbounds.hpp" #include #include #include #include #include #include #include #include namespace DetourNavigator { namespace { template struct Format : Serialization::Format> { using Serialization::Format>::operator(); template void operator()(Visitor&& visitor, const osg::Vec2i& value) const { visitor(*this, value.ptr(), 2); } template void operator()(Visitor&& visitor, const osg::Vec2f& value) const { visitor(*this, value.ptr(), 2); } template void operator()(Visitor&& visitor, const osg::Vec3f& value) const { visitor(*this, value.ptr(), 3); } template void operator()(Visitor&& visitor, const Water& value) const { visitor(*this, value.mCellSize); visitor(*this, value.mLevel); } template void operator()(Visitor&& visitor, const CellWater& value) const { visitor(*this, value.mCellPosition); visitor(*this, value.mWater); } template void operator()(Visitor&& visitor, const RecastSettings& value) const { visitor(*this, value.mCellHeight); visitor(*this, value.mCellSize); visitor(*this, value.mDetailSampleDist); visitor(*this, value.mDetailSampleMaxError); visitor(*this, value.mMaxClimb); visitor(*this, value.mMaxSimplificationError); visitor(*this, value.mMaxSlope); visitor(*this, value.mRecastScaleFactor); visitor(*this, value.mSwimHeightScale); visitor(*this, value.mBorderSize); visitor(*this, value.mMaxEdgeLen); visitor(*this, value.mMaxVertsPerPoly); visitor(*this, value.mRegionMergeArea); visitor(*this, value.mRegionMinArea); visitor(*this, value.mTileSize); } template void operator()(Visitor&& visitor, const TileBounds& value) const { visitor(*this, value.mMin); visitor(*this, value.mMax); } template void operator()(Visitor&& visitor, const Heightfield& value) const { visitor(*this, value.mCellPosition); visitor(*this, value.mCellSize); visitor(*this, value.mLength); visitor(*this, value.mMinHeight); visitor(*this, value.mMaxHeight); visitor(*this, value.mHeights); visitor(*this, value.mOriginalSize); visitor(*this, value.mMinX); visitor(*this, value.mMinY); } template void operator()(Visitor&& visitor, const FlatHeightfield& value) const { visitor(*this, value.mCellPosition); visitor(*this, value.mCellSize); visitor(*this, value.mHeight); } template void operator()(Visitor&& visitor, const RecastMesh& value) const { visitor(*this, value.getWater()); visitor(*this, value.getHeightfields()); visitor(*this, value.getFlatHeightfields()); } template void operator()(Visitor&& visitor, const ESM::Position& value) const { visitor(*this, value.pos); visitor(*this, value.rot); } template void operator()(Visitor&& visitor, const ObjectTransform& value) const { visitor(*this, value.mPosition); visitor(*this, value.mScale); } template void operator()(Visitor&& visitor, const DbRefGeometryObject& value) const { visitor(*this, value.mShapeId); visitor(*this, value.mObjectTransform); } template void operator()(Visitor&& visitor, const RecastSettings& settings, const AgentBounds& agentBounds, const RecastMesh& recastMesh, const std::vector& dbRefGeometryObjects) const { visitor(*this, DetourNavigator::recastMeshMagic); visitor(*this, DetourNavigator::recastMeshVersion); visitor(*this, settings); visitor(*this, agentBounds); visitor(*this, recastMesh); visitor(*this, dbRefGeometryObjects); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, rcPolyMesh>> { visitor(*this, value.nverts); visitor(*this, value.npolys); visitor(*this, value.maxpolys); visitor(*this, value.nvp); visitor(*this, value.bmin); visitor(*this, value.bmax); visitor(*this, value.cs); visitor(*this, value.ch); visitor(*this, value.borderSize); visitor(*this, value.maxEdgeError); if constexpr (mode == Serialization::Mode::Read) { if (value.verts == nullptr) permRecastAlloc(value.verts, getVertsLength(value)); if (value.polys == nullptr) permRecastAlloc(value.polys, getPolysLength(value)); if (value.regs == nullptr) permRecastAlloc(value.regs, getRegsLength(value)); if (value.flags == nullptr) permRecastAlloc(value.flags, getFlagsLength(value)); if (value.areas == nullptr) permRecastAlloc(value.areas, getAreasLength(value)); } visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.polys, getPolysLength(value)); visitor(*this, value.regs, getRegsLength(value)); visitor(*this, value.flags, getFlagsLength(value)); visitor(*this, value.areas, getAreasLength(value)); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, rcPolyMeshDetail>> { visitor(*this, value.nmeshes); if constexpr (mode == Serialization::Mode::Read) if (value.meshes == nullptr) permRecastAlloc(value.meshes, getMeshesLength(value)); visitor(*this, value.meshes, getMeshesLength(value)); visitor(*this, value.nverts); if constexpr (mode == Serialization::Mode::Read) if (value.verts == nullptr) permRecastAlloc(value.verts, getVertsLength(value)); visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.ntris); if constexpr (mode == Serialization::Mode::Read) if (value.tris == nullptr) permRecastAlloc(value.tris, getTrisLength(value)); visitor(*this, value.tris, getTrisLength(value)); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, PreparedNavMeshData>> { if constexpr (mode == Serialization::Mode::Write) { visitor(*this, DetourNavigator::preparedNavMeshDataMagic); visitor(*this, DetourNavigator::preparedNavMeshDataVersion); } else { static_assert(mode == Serialization::Mode::Read); char magic[std::size(DetourNavigator::preparedNavMeshDataMagic)]; visitor(*this, magic); if (std::memcmp(magic, DetourNavigator::preparedNavMeshDataMagic, sizeof(magic)) != 0) throw std::runtime_error("Bad PreparedNavMeshData magic"); std::uint32_t version = 0; visitor(*this, version); if (version != DetourNavigator::preparedNavMeshDataVersion) throw std::runtime_error("Bad PreparedNavMeshData version"); } visitor(*this, value.mUserId); visitor(*this, value.mCellSize); visitor(*this, value.mCellHeight); visitor(*this, value.mPolyMesh); visitor(*this, value.mPolyMeshDetail); } template void operator()(Visitor&& visitor, const AgentBounds& value) const { visitor(*this, value.mShapeType); visitor(*this, value.mHalfExtents); } }; } } // namespace DetourNavigator namespace DetourNavigator { std::vector serialize(const RecastSettings& settings, const AgentBounds& agentBounds, const RecastMesh& recastMesh, const std::vector& dbRefGeometryObjects) { constexpr Format format; Serialization::SizeAccumulator sizeAccumulator; format(sizeAccumulator, settings, agentBounds, recastMesh, dbRefGeometryObjects); std::vector result(sizeAccumulator.value()); format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), settings, agentBounds, recastMesh, dbRefGeometryObjects); return result; } std::vector serialize(const PreparedNavMeshData& value) { constexpr Format format; Serialization::SizeAccumulator sizeAccumulator; format(sizeAccumulator, value); std::vector result(sizeAccumulator.value()); format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value); return result; } bool deserialize(const std::vector& data, PreparedNavMeshData& value) { try { constexpr Format format; format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value); return true; } catch (const std::exception&) { return false; } } } openmw-openmw-0.48.0/components/detournavigator/serialization.hpp000066400000000000000000000016741445372753700254340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H #include #include #include namespace DetourNavigator { class RecastMesh; struct DbRefGeometryObject; struct PreparedNavMeshData; struct RecastSettings; struct AgentBounds; constexpr char recastMeshMagic[] = {'r', 'c', 's', 't'}; constexpr std::uint32_t recastMeshVersion = 2; constexpr char preparedNavMeshDataMagic[] = {'p', 'n', 'a', 'v'}; constexpr std::uint32_t preparedNavMeshDataVersion = 1; std::vector serialize(const RecastSettings& settings, const AgentBounds& agentBounds, const RecastMesh& recastMesh, const std::vector& dbRefGeometryObjects); std::vector serialize(const PreparedNavMeshData& value); bool deserialize(const std::vector& data, PreparedNavMeshData& value); } #endif openmw-openmw-0.48.0/components/detournavigator/settings.cpp000066400000000000000000000106271445372753700244100ustar00rootroot00000000000000#include "settings.hpp" #include #include #include namespace DetourNavigator { RecastSettings makeRecastSettingsFromSettingsManager() { constexpr float epsilon = std::numeric_limits::epsilon(); RecastSettings result; result.mBorderSize = std::max(0, ::Settings::Manager::getInt("border size", "Navigator")); result.mCellHeight = std::max(epsilon, ::Settings::Manager::getFloat("cell height", "Navigator")); result.mCellSize = std::max(epsilon, ::Settings::Manager::getFloat("cell size", "Navigator")); result.mDetailSampleDist = std::max(0.0f, ::Settings::Manager::getFloat("detail sample dist", "Navigator")); result.mDetailSampleMaxError = std::max(0.0f, ::Settings::Manager::getFloat("detail sample max error", "Navigator")); result.mMaxClimb = Constants::sStepSizeUp; result.mMaxSimplificationError = std::max(0.0f, ::Settings::Manager::getFloat("max simplification error", "Navigator")); result.mMaxSlope = Constants::sMaxSlope; result.mRecastScaleFactor = std::max(epsilon, ::Settings::Manager::getFloat("recast scale factor", "Navigator")); result.mSwimHeightScale = 0; result.mMaxEdgeLen = std::max(0, ::Settings::Manager::getInt("max edge len", "Navigator")); result.mMaxVertsPerPoly = std::max(3, ::Settings::Manager::getInt("max verts per poly", "Navigator")); result.mRegionMergeArea = std::max(0, ::Settings::Manager::getInt("region merge area", "Navigator")); result.mRegionMinArea = std::max(0, ::Settings::Manager::getInt("region min area", "Navigator")); result.mTileSize = std::max(1, ::Settings::Manager::getInt("tile size", "Navigator")); return result; } DetourSettings makeDetourSettingsFromSettingsManager() { DetourSettings result; result.mMaxNavMeshQueryNodes = std::clamp(::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"), 1, 65535); result.mMaxPolys = std::clamp(::Settings::Manager::getInt("max polygons per tile", "Navigator"), 1, (1 << 22) - 1); result.mMaxPolygonPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max polygon path size", "Navigator"))); result.mMaxSmoothPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max smooth path size", "Navigator"))); return result; } Settings makeSettingsFromSettingsManager() { Settings result; result.mRecast = makeRecastSettingsFromSettingsManager(); result.mDetour = makeDetourSettingsFromSettingsManager(); result.mMaxTilesNumber = std::max(0, ::Settings::Manager::getInt("max tiles number", "Navigator")); result.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); result.mAsyncNavMeshUpdaterThreads = static_cast(std::max(0, ::Settings::Manager::getInt("async nav mesh updater threads", "Navigator"))); result.mMaxNavMeshTilesCacheSize = static_cast(std::max(std::int64_t {0}, ::Settings::Manager::getInt64("max nav mesh tiles cache size", "Navigator"))); result.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); result.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); result.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); result.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator"); result.mMaxDbFileSize = static_cast(::Settings::Manager::getInt64("max navmeshdb file size", "Navigator")); return result; } } openmw-openmw-0.48.0/components/detournavigator/settings.hpp000066400000000000000000000035551445372753700244170ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #include #include namespace DetourNavigator { struct RecastSettings { float mCellHeight = 0; float mCellSize = 0; float mDetailSampleDist = 0; float mDetailSampleMaxError = 0; float mMaxClimb = 0; float mMaxSimplificationError = 0; float mMaxSlope = 0; float mRecastScaleFactor = 0; float mSwimHeightScale = 0; int mBorderSize = 0; int mMaxEdgeLen = 0; int mMaxVertsPerPoly = 0; int mRegionMergeArea = 0; int mRegionMinArea = 0; int mTileSize = 0; }; struct DetourSettings { int mMaxPolys = 0; int mMaxNavMeshQueryNodes = 0; std::size_t mMaxPolygonPathSize = 0; std::size_t mMaxSmoothPathSize = 0; }; struct Settings { bool mEnableWriteRecastMeshToFile = false; bool mEnableWriteNavMeshToFile = false; bool mEnableRecastMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false; bool mEnableNavMeshDiskCache = false; bool mWriteToNavMeshDb = false; RecastSettings mRecast; DetourSettings mDetour; int mWaitUntilMinDistanceToPlayer = 0; int mMaxTilesNumber = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mMaxNavMeshTilesCacheSize = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; std::uint64_t mMaxDbFileSize = 0; }; inline constexpr std::int64_t navMeshFormatVersion = 2; RecastSettings makeRecastSettingsFromSettingsManager(); DetourSettings makeDetourSettingsFromSettingsManager(); Settings makeSettingsFromSettingsManager(); } #endif openmw-openmw-0.48.0/components/detournavigator/settingsutils.hpp000066400000000000000000000073721445372753700255010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #include "settings.hpp" #include "tilebounds.hpp" #include "tileposition.hpp" #include #include #include #include namespace DetourNavigator { inline float toNavMeshCoordinates(const RecastSettings& settings, float value) { return value * settings.mRecastScaleFactor; } inline osg::Vec2f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec2f position) { return position * settings.mRecastScaleFactor; } inline osg::Vec3f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { std::swap(position.y(), position.z()); return position * settings.mRecastScaleFactor; } inline TileBounds toNavMeshCoordinates(const RecastSettings& settings, const TileBounds& value) { return TileBounds { toNavMeshCoordinates(settings, value.mMin), toNavMeshCoordinates(settings, value.mMax) }; } inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) { return value / settings.mRecastScaleFactor; } inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; position *= factor; std::swap(position.y(), position.z()); return position; } inline float getTileSize(const RecastSettings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; } inline int getTilePosition(const RecastSettings& settings, float position) { const float v = std::floor(position / getTileSize(settings)); if (v < static_cast(std::numeric_limits::min())) return std::numeric_limits::min(); if (v > static_cast(std::numeric_limits::max() - 1)) return std::numeric_limits::max() - 1; return static_cast(v); } inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec2f& position) { return TilePosition(getTilePosition(settings, position.x()), getTilePosition(settings, position.y())); } inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return getTilePosition(settings, osg::Vec2f(position.x(), position.z())); } inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) { return TileBounds { osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings), osg::Vec2f(tilePosition.x() + 1, tilePosition.y() + 1) * getTileSize(settings), }; } inline float getBorderSize(const RecastSettings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; } inline float getRealTileSize(const RecastSettings& settings) { return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; } inline float getMaxNavmeshAreaRadius(const Settings& settings) { return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } inline TileBounds makeRealTileBoundsWithBorder(const RecastSettings& settings, const TilePosition& tilePosition) { TileBounds result = makeTileBounds(settings, tilePosition); const float border = getBorderSize(settings); result.mMin -= osg::Vec2f(border, border); result.mMax += osg::Vec2f(border, border); result.mMin /= settings.mRecastScaleFactor; result.mMax /= settings.mRecastScaleFactor; return result; } } #endif openmw-openmw-0.48.0/components/detournavigator/sharednavmesh.hpp000066400000000000000000000003631445372753700254010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #include class dtNavMesh; namespace DetourNavigator { using NavMeshPtr = std::shared_ptr; } #endif openmw-openmw-0.48.0/components/detournavigator/status.hpp000066400000000000000000000027231445372753700240760ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_STATUS_H namespace DetourNavigator { enum class Status { Success, PartialPath, NavMeshNotFound, StartPolygonNotFound, EndPolygonNotFound, MoveAlongSurfaceFailed, FindPathOverPolygonsFailed, GetPolyHeightFailed, InitNavMeshQueryFailed, }; constexpr const char* getMessage(Status value) { switch (value) { case Status::Success: return "success"; case Status::PartialPath: return "partial path is found"; case Status::NavMeshNotFound: return "navmesh is not found"; case Status::StartPolygonNotFound: return "polygon for start position is not found on navmesh"; case Status::EndPolygonNotFound: return "polygon for end position is not found on navmesh"; case Status::MoveAlongSurfaceFailed: return "move along surface on navmesh is failed"; case Status::FindPathOverPolygonsFailed: return "path over navmesh polygons is not found"; case Status::GetPolyHeightFailed: return "failed to get polygon height"; case Status::InitNavMeshQueryFailed: return "failed to init navmesh query"; } return "unknown error"; } } #endif openmw-openmw-0.48.0/components/detournavigator/tilebounds.hpp000066400000000000000000000040711445372753700247210ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #include #include #include #include #include #include #include #include namespace DetourNavigator { struct TileBounds { osg::Vec2f mMin; osg::Vec2f mMax; }; inline auto tie(const TileBounds& value) noexcept { return std::tie(value.mMin, value.mMax); } inline bool operator<(const TileBounds& lhs, const TileBounds& rhs) noexcept { return tie(lhs) < tie(rhs); } inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) noexcept { return tie(lhs) == tie(rhs); } inline bool operator !=(const TileBounds& lhs, const TileBounds& rhs) noexcept { return !(lhs == rhs); } inline std::optional getIntersection(const TileBounds& a, const TileBounds& b) noexcept { const float minX = std::max(a.mMin.x(), b.mMin.x()); const float maxX = std::min(a.mMax.x(), b.mMax.x()); if (minX > maxX) return std::nullopt; const float minY = std::max(a.mMin.y(), b.mMin.y()); const float maxY = std::min(a.mMax.y(), b.mMax.y()); if (minY > maxY) return std::nullopt; return TileBounds {osg::Vec2f(minX, minY), osg::Vec2f(maxX, maxY)}; } inline TileBounds maxCellTileBounds(const osg::Vec2i& position, int size) { return TileBounds { osg::Vec2f(position.x(), position.y()) * size, osg::Vec2f(position.x() + 1, position.y() + 1) * size }; } inline TileBounds makeObjectTileBounds(const btCollisionShape& shape, const btTransform& transform) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); return TileBounds {Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax)}; } } #endif openmw-openmw-0.48.0/components/detournavigator/tilecachedrecastmeshmanager.cpp000066400000000000000000000320471445372753700302470ustar00rootroot00000000000000#include "tilecachedrecastmeshmanager.hpp" #include "makenavmesh.hpp" #include "gettilespositions.hpp" #include "settingsutils.hpp" #include "changetype.hpp" #include #include #include #include #include namespace DetourNavigator { namespace { const TileBounds infiniteTileBounds { osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) }; } TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) , mBounds(infiniteTileBounds) , mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings)) {} TileBounds TileCachedRecastMeshManager::getBounds() const { return mBounds; } std::vector> TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) { std::vector> changedTiles; if (mBounds == bounds) return changedTiles; const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); if (mBounds != infiniteTileBounds) { const auto locked = mWorldspaceTiles.lock(); for (auto& object : mObjects) { const ObjectId id = object.first; ObjectData& data = object.second; const TilesPositionsRange objectRange = makeTilesPositionsRange(data.mShape.getShape(), data.mTransform, mSettings); const auto onOldTilePosition = [&] (const TilePosition& position) { if (isInTilesPositionsRange(newRange, position)) return; const auto it = data.mTiles.find(position); if (it == data.mTiles.end()) return; data.mTiles.erase(it); if (removeTile(id, position, locked->mTiles)) changedTiles.emplace_back(position, ChangeType::remove); }; getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); const auto onNewTilePosition = [&] (const TilePosition& position) { if (data.mTiles.find(position) != data.mTiles.end()) return; if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles)) { data.mTiles.insert(position); changedTiles.emplace_back(position, ChangeType::add); } }; getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition); } std::sort(changedTiles.begin(), changedTiles.end()); changedTiles.erase(std::unique(changedTiles.begin(), changedTiles.end()), changedTiles.end()); } if (!changedTiles.empty()) ++mRevision; mBounds = bounds; mRange = newRange; return changedTiles; } std::string TileCachedRecastMeshManager::getWorldspace() const { return mWorldspaceTiles.lockConst()->mWorldspace; } void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) { const auto locked = mWorldspaceTiles.lock(); if (locked->mWorldspace == worldspace) return; locked->mTiles.clear(); locked->mWorldspace = worldspace; } std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mObjects.find(id); if (object == mObjects.end()) return std::nullopt; std::optional result; { const auto locked = mWorldspaceTiles.lock(); for (const auto& tilePosition : object->second.mTiles) { const auto removed = removeTile(id, tilePosition, locked->mTiles); if (removed && !result) result = removed; } } mObjects.erase(object); if (result) ++mRevision; return result; } bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { const auto it = mWaterTilesPositions.find(cellPosition); if (it != mWaterTilesPositions.end()) return false; std::vector& tilesPositions = mWaterTilesPositions.emplace_hint( it, cellPosition, std::vector())->second; bool result = false; if (cellSize == std::numeric_limits::max()) { const auto locked = mWorldspaceTiles.lock(); for (auto& tile : locked->mTiles) { if (tile.second->addWater(cellPosition, cellSize, level)) { tilesPositions.push_back(tile.first); result = true; } } } else { const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), [&] (const TilePosition& tilePosition) { const auto locked = mWorldspaceTiles.lock(); auto tile = locked->mTiles.find(tilePosition); if (tile == locked->mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); tile = locked->mTiles.emplace_hint(tile, tilePosition, std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addWater(cellPosition, cellSize, level)) { tilesPositions.push_back(tilePosition); result = true; } }); } if (result) ++mRevision; return result; } std::optional TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto object = mWaterTilesPositions.find(cellPosition); if (object == mWaterTilesPositions.end()) return std::nullopt; std::optional result; for (const auto& tilePosition : object->second) { const auto locked = mWorldspaceTiles.lock(); const auto tile = locked->mTiles.find(tilePosition); if (tile == locked->mTiles.end()) continue; const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->isEmpty()) { locked->mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) result = tileResult; } mWaterTilesPositions.erase(object); if (result) ++mRevision; return result; } bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { const auto it = mHeightfieldTilesPositions.find(cellPosition); if (it != mHeightfieldTilesPositions.end()) return false; std::vector& tilesPositions = mHeightfieldTilesPositions.emplace_hint( it, cellPosition, std::vector())->second; const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); bool result = false; getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), [&] (const TilePosition& tilePosition) { const auto locked = mWorldspaceTiles.lock(); auto tile = locked->mTiles.find(tilePosition); if (tile == locked->mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); tile = locked->mTiles.emplace_hint(tile, tilePosition, std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addHeightfield(cellPosition, cellSize, shape)) { tilesPositions.push_back(tilePosition); result = true; } }); if (result) ++mRevision; return result; } std::optional TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const auto object = mHeightfieldTilesPositions.find(cellPosition); if (object == mHeightfieldTilesPositions.end()) return std::nullopt; std::optional result; for (const auto& tilePosition : object->second) { const auto locked = mWorldspaceTiles.lock(); const auto tile = locked->mTiles.find(tilePosition); if (tile == locked->mTiles.end()) continue; const auto tileResult = tile->second->removeHeightfield(cellPosition); if (tile->second->isEmpty()) { locked->mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) result = tileResult; } mHeightfieldTilesPositions.erase(object); if (result) ++mRevision; return result; } std::shared_ptr TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const { if (const auto manager = getManager(worldspace, tilePosition)) return manager->getMesh(); return nullptr; } std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const { if (const auto manager = getManager(worldspace, tilePosition)) return manager->getCachedMesh(); return nullptr; } std::shared_ptr TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const { if (const auto manager = getManager(worldspace, tilePosition)) return manager->getNewMesh(); return nullptr; } std::size_t TileCachedRecastMeshManager::getRevision() const { return mRevision; } void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const { const auto locked = mWorldspaceTiles.lockConst(); const auto it = locked->mTiles.find(tilePosition); if (it == locked->mTiles.end()) return; it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); tile = tiles.emplace_hint(tile, tilePosition, std::make_shared(tileBounds, mTilesGeneration)); } return tile->second->addObject(id, shape, transform, areaType); } bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); return tile != tiles.end() && tile->second->updateObject(id, transform, areaType); } std::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); if (tile == tiles.end()) return std::optional(); auto tileResult = tile->second->removeObject(id); if (tile->second->isEmpty()) { tiles.erase(tile); ++mTilesGeneration; } return tileResult; } std::shared_ptr TileCachedRecastMeshManager::getManager(std::string_view worldspace, const TilePosition& tilePosition) const { const auto locked = mWorldspaceTiles.lockConst(); if (locked->mWorldspace != worldspace) return nullptr; const auto it = locked->mTiles.find(tilePosition); if (it == locked->mTiles.end()) return nullptr; return it->second; } } openmw-openmw-0.48.0/components/detournavigator/tilecachedrecastmeshmanager.hpp000066400000000000000000000161401445372753700302500ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H #include "cachedrecastmeshmanager.hpp" #include "tileposition.hpp" #include "settingsutils.hpp" #include "gettilespositions.hpp" #include "version.hpp" #include "heightfieldshape.hpp" #include "changetype.hpp" #include #include #include #include #include #include namespace DetourNavigator { class TileCachedRecastMeshManager { public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); TileBounds getBounds() const; std::vector> setBounds(const TileBounds& bounds); std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); template bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { auto it = mObjects.find(id); if (it != mObjects.end()) return false; const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); const TilesPositionsRange range = getIntersection(mRange, objectRange); std::set tilesPositions; if (range.mBegin != range.mEnd) { const auto locked = mWorldspaceTiles.lock(); getTilesPositions(range, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) tilesPositions.insert(tilePosition); }); } it = mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)}); std::for_each(it->second.mTiles.begin(), it->second.mTiles.end(), std::forward(onChangedTile)); ++mRevision; return true; } template bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { const auto object = mObjects.find(id); if (object == mObjects.end()) return false; auto& data = object->second; bool changed = false; std::set newTiles; { const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); const TilesPositionsRange range = getIntersection(mRange, objectRange); const auto locked = mWorldspaceTiles.lock(); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (data.mTiles.find(tilePosition) != data.mTiles.end()) { newTiles.insert(tilePosition); if (updateTile(id, transform, areaType, tilePosition, locked->mTiles)) { onChangedTile(tilePosition, ChangeType::update); changed = true; } } else if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) { newTiles.insert(tilePosition); onChangedTile(tilePosition, ChangeType::add); changed = true; } }; getTilesPositions(range, onTilePosition); for (const auto& tile : data.mTiles) { if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, locked->mTiles)) { onChangedTile(tile, ChangeType::remove); changed = true; } } } if (changed) { data.mTiles = std::move(newTiles); ++mRevision; } return changed; } std::optional removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const; template void forEachTile(Function&& function) const { const auto& locked = mWorldspaceTiles.lockConst(); for (const auto& [tilePosition, recastMeshManager] : locked->mTiles) function(tilePosition, *recastMeshManager); } std::size_t getRevision() const; void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const; private: using TilesMap = std::map>; struct ObjectData { const CollisionShape mShape; const btTransform mTransform; const AreaType mAreaType; std::set mTiles; }; struct WorldspaceTiles { std::string mWorldspace; TilesMap mTiles; }; const RecastSettings& mSettings; TileBounds mBounds; TilesPositionsRange mRange; Misc::ScopeGuarded mWorldspaceTiles; std::unordered_map mObjects; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; std::size_t mRevision = 0; std::size_t mTilesGeneration = 0; bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); inline std::shared_ptr getManager(std::string_view worldspace, const TilePosition& tilePosition) const; }; } #endif openmw-openmw-0.48.0/components/detournavigator/tileposition.hpp000066400000000000000000000003241445372753700252700ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H #include namespace DetourNavigator { using TilePosition = osg::Vec2i; } #endif openmw-openmw-0.48.0/components/detournavigator/tilespositionsrange.hpp000066400000000000000000000004501445372753700266530ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILESPOSITIONSRANGE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILESPOSITIONSRANGE_H #include "tileposition.hpp" namespace DetourNavigator { struct TilesPositionsRange { TilePosition mBegin; TilePosition mEnd; }; } #endif openmw-openmw-0.48.0/components/detournavigator/version.hpp000066400000000000000000000017161445372753700242410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H #include #include namespace DetourNavigator { struct Version { std::size_t mGeneration = 0; std::size_t mRevision = 0; friend inline auto tie(const Version& value) { return std::tie(value.mGeneration, value.mRevision); } friend inline bool operator<(const Version& lhs, const Version& rhs) { return tie(lhs) < tie(rhs); } friend inline bool operator<=(const Version& lhs, const Version& rhs) { return tie(lhs) <= tie(rhs); } friend inline bool operator==(const Version& lhs, const Version& rhs) { return tie(lhs) == tie(rhs); } friend inline bool operator!=(const Version& lhs, const Version& rhs) { return !(lhs == rhs); } }; } #endif openmw-openmw-0.48.0/components/detournavigator/waitconditiontype.hpp000066400000000000000000000004041445372753700263220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H namespace DetourNavigator { enum class WaitConditionType { requiredTilesPresent, allJobsDone, }; } #endif openmw-openmw-0.48.0/components/doc.hpp000066400000000000000000000012271445372753700201010ustar00rootroot00000000000000// Note: This is not a regular source file. /// \defgroup components Components /// \namespace ESMS /// \ingroup components /// \brief ESM/ESP record store /// \namespace ESM /// \ingroup components /// \brief ESM/ESP records /// \namespace FileFinder /// \ingroup components /// \brief Linux/Windows-path resolving /// \namespace ToUTF /// \ingroup components /// \brief Text encoding /// \namespace Compiler /// \ingroup components /// \brief script compiler /// \namespace Interpreter /// \ingroup components /// \brief script interpreter // TODO put nif and nifogre in different namespaces (or merge them) // TODO put other components into namespaces openmw-openmw-0.48.0/components/esm/000077500000000000000000000000001445372753700174055ustar00rootroot00000000000000openmw-openmw-0.48.0/components/esm/attr.cpp000066400000000000000000000025601445372753700210660ustar00rootroot00000000000000#include "attr.hpp" using namespace ESM; const Attribute::AttributeID Attribute::sAttributeIds[Attribute::Length] = { Attribute::Strength, Attribute::Intelligence, Attribute::Willpower, Attribute::Agility, Attribute::Speed, Attribute::Endurance, Attribute::Personality, Attribute::Luck }; const std::string Attribute::sAttributeNames[Attribute::Length] = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck" }; const std::string Attribute::sGmstAttributeIds[Attribute::Length] = { "sAttributeStrength", "sAttributeIntelligence", "sAttributeWillpower", "sAttributeAgility", "sAttributeSpeed", "sAttributeEndurance", "sAttributePersonality", "sAttributeLuck" }; const std::string Attribute::sGmstAttributeDescIds[Attribute::Length] = { "sStrDesc", "sIntDesc", "sWilDesc", "sAgiDesc", "sSpdDesc", "sEndDesc", "sPerDesc", "sLucDesc" }; const std::string Attribute::sAttributeIcons[Attribute::Length] = { "icons\\k\\attribute_strength.dds", "icons\\k\\attribute_int.dds", "icons\\k\\attribute_wilpower.dds", "icons\\k\\attribute_agility.dds", "icons\\k\\attribute_speed.dds", "icons\\k\\attribute_endurance.dds", "icons\\k\\attribute_personality.dds", "icons\\k\\attribute_luck.dds" }; openmw-openmw-0.48.0/components/esm/attr.hpp000066400000000000000000000013171445372753700210720ustar00rootroot00000000000000#ifndef OPENMW_ESM_ATTR_H #define OPENMW_ESM_ATTR_H #include namespace ESM { /* * Attribute definitions */ struct Attribute { enum AttributeID { Strength = 0, Intelligence = 1, Willpower = 2, Agility = 3, Speed = 4, Endurance = 5, Personality = 6, Luck = 7, Length = 8 }; AttributeID mId; std::string mName, mDescription; static const AttributeID sAttributeIds[Length]; static const std::string sAttributeNames[Length]; static const std::string sGmstAttributeIds[Length]; static const std::string sGmstAttributeDescIds[Length]; static const std::string sAttributeIcons[Length]; }; } #endif openmw-openmw-0.48.0/components/esm/common.cpp000066400000000000000000000006071445372753700214040ustar00rootroot00000000000000#include "common.hpp" #include namespace ESM { std::string printName(const std::uint32_t typeId) { unsigned char typeName[4]; typeName[0] = typeId & 0xff; typeName[1] = (typeId >> 8) & 0xff; typeName[2] = (typeId >> 16) & 0xff; typeName[3] = (typeId >> 24) & 0xff; return std::string((char*)typeName, 4); } } openmw-openmw-0.48.0/components/esm/common.hpp000066400000000000000000000020601445372753700214040ustar00rootroot00000000000000#ifndef COMPONENT_ESM_COMMON_H #define COMPONENT_ESM_COMMON_H #include #include namespace ESM { #pragma pack(push, 1) union ESMVersion { float f; std::uint32_t ui; }; union TypeId { std::uint32_t value; char name[4]; // record type in ascii }; #pragma pack(pop) enum ESMVersions { VER_120 = 0x3f99999a, // TES3 VER_130 = 0x3fa66666, // TES3 VER_080 = 0x3f4ccccd, // TES4 VER_100 = 0x3f800000, // TES4 VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney VER_133 = 0x3faa3d71, // FONV HonestHearts VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 VER_170 = 0x3fd9999a // TES5 }; // Defines another files (esm or esp) that this file depends upon. struct MasterData { std::string name; std::uint64_t size; }; std::string printName(const std::uint32_t typeId); } #endif // COMPONENT_ESM_COMMON_H openmw-openmw-0.48.0/components/esm/defs.hpp000066400000000000000000000120351445372753700210400ustar00rootroot00000000000000#ifndef OPENMW_ESM_DEFS_H #define OPENMW_ESM_DEFS_H #include #include #include namespace ESM { struct TimeStamp { float mHour; int mDay; }; struct EpochTimeStamp { float mGameHour; int mDay; int mMonth; int mYear; }; // Pixel color value. Standard four-byte rr,gg,bb,aa format. typedef uint32_t Color; enum Specialization { SPC_Combat = 0, SPC_Magic = 1, SPC_Stealth = 2 }; enum RangeType { RT_Self = 0, RT_Touch = 1, RT_Target = 2 }; // Position and rotation struct Position { float pos[3]; // In radians float rot[3]; osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } friend inline bool operator<(const Position& l, const Position& r) { const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; return tuple(l) < tuple(r); } }; bool inline operator== (const Position& left, const Position& right) noexcept { return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; } bool inline operator!= (const Position& left, const Position& right) noexcept { return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; } template constexpr unsigned int fourCC(const char(&name)[len]) { static_assert(len == 5, "Constant must be 4 characters long. (Plus null terminator)"); return static_cast(name[0]) | (static_cast(name[1]) << 8) | (static_cast(name[2]) << 16) | (static_cast(name[3]) << 24); } enum RecNameInts : unsigned int { // Special values. Can not be used in any ESM. // Added to this enum to guarantee that the values don't collide with any records. REC_INTERNAL_PLAYER = 0, REC_INTERNAL_MARKER = 1, // format 0 / legacy REC_ACTI = fourCC("ACTI"), REC_ALCH = fourCC("ALCH"), REC_APPA = fourCC("APPA"), REC_ARMO = fourCC("ARMO"), REC_BODY = fourCC("BODY"), REC_BOOK = fourCC("BOOK"), REC_BSGN = fourCC("BSGN"), REC_CELL = fourCC("CELL"), REC_CLAS = fourCC("CLAS"), REC_CLOT = fourCC("CLOT"), REC_CNTC = fourCC("CNTC"), REC_CONT = fourCC("CONT"), REC_CREA = fourCC("CREA"), REC_CREC = fourCC("CREC"), REC_DIAL = fourCC("DIAL"), REC_DOOR = fourCC("DOOR"), REC_ENCH = fourCC("ENCH"), REC_FACT = fourCC("FACT"), REC_GLOB = fourCC("GLOB"), REC_GMST = fourCC("GMST"), REC_INFO = fourCC("INFO"), REC_INGR = fourCC("INGR"), REC_LAND = fourCC("LAND"), REC_LEVC = fourCC("LEVC"), REC_LEVI = fourCC("LEVI"), REC_LIGH = fourCC("LIGH"), REC_LOCK = fourCC("LOCK"), REC_LTEX = fourCC("LTEX"), REC_MGEF = fourCC("MGEF"), REC_MISC = fourCC("MISC"), REC_NPC_ = fourCC("NPC_"), REC_NPCC = fourCC("NPCC"), REC_PGRD = fourCC("PGRD"), REC_PROB = fourCC("PROB"), REC_RACE = fourCC("RACE"), REC_REGN = fourCC("REGN"), REC_REPA = fourCC("REPA"), REC_SCPT = fourCC("SCPT"), REC_SKIL = fourCC("SKIL"), REC_SNDG = fourCC("SNDG"), REC_SOUN = fourCC("SOUN"), REC_SPEL = fourCC("SPEL"), REC_SSCR = fourCC("SSCR"), REC_STAT = fourCC("STAT"), REC_WEAP = fourCC("WEAP"), // format 0 - saved games REC_SAVE = fourCC("SAVE"), REC_JOUR_LEGACY = fourCC("\xa4UOR"), // "\xa4UOR", rather than "JOUR", little oversight when magic numbers were // calculated by hand, needs to be supported for older files now REC_JOUR = fourCC("JOUR"), REC_QUES = fourCC("QUES"), REC_GSCR = fourCC("GSCR"), REC_PLAY = fourCC("PLAY"), REC_CSTA = fourCC("CSTA"), REC_GMAP = fourCC("GMAP"), REC_DIAS = fourCC("DIAS"), REC_WTHR = fourCC("WTHR"), REC_KEYS = fourCC("KEYS"), REC_DYNA = fourCC("DYNA"), REC_ASPL = fourCC("ASPL"), REC_ACTC = fourCC("ACTC"), REC_MPRJ = fourCC("MPRJ"), REC_PROJ = fourCC("PROJ"), REC_DCOU = fourCC("DCOU"), REC_MARK = fourCC("MARK"), REC_ENAB = fourCC("ENAB"), REC_CAM_ = fourCC("CAM_"), REC_STLN = fourCC("STLN"), REC_INPU = fourCC("INPU"), // format 1 REC_FILT = fourCC("FILT"), REC_DBGP = fourCC("DBGP"), ///< only used in project files REC_LUAL = fourCC("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon) // format 16 - Lua scripts in saved games REC_LUAM = fourCC("LUAM"), // LuaManager data // format 21 - Random state in saved games. REC_RAND = fourCC("RAND"), // Random state. }; /// Common subrecords enum SubRecNameInts { SREC_DELE = ESM::fourCC("DELE"), SREC_NAME = ESM::fourCC("NAME") }; } #endif openmw-openmw-0.48.0/components/esm/esmcommon.hpp000066400000000000000000000133271445372753700221210ustar00rootroot00000000000000#ifndef OPENMW_ESM_COMMON_H #define OPENMW_ESM_COMMON_H #include #include #include #include #include #include #include #include namespace ESM { enum Version { VER_12 = 0x3f99999a, VER_13 = 0x3fa66666 }; enum RecordFlag { // This flag exists, but is not used to determine if a record has been deleted while loading FLAG_Deleted = 0x00000020, FLAG_Persistent = 0x00000400, FLAG_Ignored = 0x00001000, FLAG_Blocked = 0x00002000 }; template struct FixedString { static_assert(capacity > 0); static constexpr std::size_t sCapacity = capacity; char mData[capacity]; FixedString() = default; template constexpr FixedString(const char (&value)[size]) noexcept : mData() { if constexpr (capacity == sizeof(std::uint32_t)) { static_assert(capacity == size || capacity + 1 == size); if constexpr (capacity + 1 == size) assert(value[capacity] == '\0'); for (std::size_t i = 0; i < capacity; ++i) mData[i] = value[i]; } else { const std::size_t length = std::min(capacity, size); for (std::size_t i = 0; i < length; ++i) mData[i] = value[i]; mData[std::min(capacity - 1, length)] = '\0'; } } constexpr explicit FixedString(std::uint32_t value) noexcept : mData() { static_assert(capacity == sizeof(std::uint32_t)); for (std::size_t i = 0; i < capacity; ++i) mData[i] = static_cast((value >> (i * std::numeric_limits::digits)) & std::numeric_limits::max()); } template constexpr explicit FixedString(T value) noexcept : FixedString(static_cast(value)) {} std::string_view toStringView() const noexcept { return std::string_view(mData, strnlen(mData, capacity)); } std::string toString() const { return std::string(toStringView()); } std::uint32_t toInt() const noexcept { static_assert(capacity == sizeof(std::uint32_t)); std::uint32_t value; std::memcpy(&value, mData, capacity); return value; } void clear() noexcept { std::memset(mData, 0, capacity); } void assign(std::string_view value) noexcept { if (value.empty()) { clear(); return; } if (value.size() < capacity) { if constexpr (capacity == sizeof(std::uint32_t)) std::memset(mData, 0, capacity); std::memcpy(mData, value.data(), value.size()); if constexpr (capacity != sizeof(std::uint32_t)) mData[value.size()] = '\0'; return; } std::memcpy(mData, value.data(), capacity); if constexpr (capacity != sizeof(std::uint32_t)) mData[capacity - 1] = '\0'; } FixedString& operator=(std::uint32_t value) noexcept { static_assert(capacity == sizeof(value)); std::memcpy(&mData, &value, capacity); return *this; } }; template >> inline bool operator==(const FixedString& lhs, const T* const& rhs) noexcept { for (std::size_t i = 0; i < capacity; ++i) { if (lhs.mData[i] != rhs[i]) return false; if (lhs.mData[i] == '\0') return true; } return rhs[capacity] == '\0'; } template inline bool operator==(const FixedString& lhs, const std::string& rhs) noexcept { return lhs == rhs.c_str(); } template inline bool operator==(const FixedString& lhs, const char (&rhs)[rhsSize]) noexcept { return strnlen(rhs, rhsSize) == strnlen(lhs.mData, capacity) && std::strncmp(lhs.mData, rhs, capacity) == 0; } inline bool operator==(const FixedString<4>& lhs, std::uint32_t rhs) noexcept { return lhs.toInt() == rhs; } inline bool operator==(const FixedString<4>& lhs, const FixedString<4>& rhs) noexcept { return lhs.toInt() == rhs.toInt(); } template inline bool operator!=(const FixedString& lhs, const Rhs& rhs) noexcept { return !(lhs == rhs); } using NAME = FixedString<4>; using NAME32 = FixedString<32>; using NAME64 = FixedString<64>; static_assert(std::is_standard_layout_v && std::is_trivial_v); static_assert(std::is_standard_layout_v && std::is_trivial_v); static_assert(std::is_standard_layout_v && std::is_trivial_v); static_assert(sizeof(NAME) == 4); static_assert(sizeof(NAME32) == 32); static_assert(sizeof(NAME64) == 64); /* This struct defines a file 'context' which can be saved and later restored by an ESMReader instance. It will save the position within a file, and when restored will let you read from that position as if you never left it. */ struct ESM_Context { std::string filename; uint32_t leftRec, leftSub; size_t leftFile; NAME recName, subName; // When working with multiple esX files, we will generate lists of all files that // actually contribute to a specific cell. Therefore, we need to store the index // of the file belonging to this contest. See CellStore::(list/load)refs for details. int index; std::vector parentFileIndices; // True if subName has been read but not used. bool subCached; // File position. Only used for stored contexts, not regularly // updated within the reader itself. size_t filePos; }; } #endif openmw-openmw-0.48.0/components/esm/format.cpp000066400000000000000000000023261445372753700214040ustar00rootroot00000000000000#include "format.hpp" #include #include #include namespace ESM { namespace { bool isValidFormat(std::uint32_t value) { return value == static_cast(Format::Tes3) || value == static_cast(Format::Tes4); } Format toFormat(std::uint32_t value) { if (!isValidFormat(value)) throw std::runtime_error("Invalid format: " + std::to_string(value)); return static_cast(value); } } Format readFormat(std::istream& stream) { std::uint32_t format = 0; stream.read(reinterpret_cast(&format), sizeof(format)); if (stream.gcount() != sizeof(format)) throw std::runtime_error("Not enough bytes to read file header"); return toFormat(format); } Format parseFormat(std::string_view value) { if (value.size() != sizeof(std::uint32_t)) throw std::logic_error("Invalid format value: " + std::string(value)); std::uint32_t format; std::memcpy(&format, value.data(), sizeof(std::uint32_t)); return toFormat(format); } } openmw-openmw-0.48.0/components/esm/format.hpp000066400000000000000000000005731445372753700214130ustar00rootroot00000000000000#ifndef COMPONENT_ESM_FORMAT_H #define COMPONENT_ESM_FORMAT_H #include "defs.hpp" #include #include #include namespace ESM { enum class Format : std::uint32_t { Tes3 = fourCC("TES3"), Tes4 = fourCC("TES4"), }; Format readFormat(std::istream& stream); Format parseFormat(std::string_view value); } #endif openmw-openmw-0.48.0/components/esm/luascripts.cpp000066400000000000000000000150321445372753700223030ustar00rootroot00000000000000#include "luascripts.hpp" #include "components/esm3/esmreader.hpp" #include "components/esm3/esmwriter.hpp" #include // List of all records, that are related to Lua. // // Records: // LUAL - LuaScriptsCfg - list of all scripts (in content files) // LUAM - MWLua::LuaManager (in saves) // // Subrecords: // LUAF - LuaScriptCfg::mFlags and ESM::RecNameInts list // LUAW - Start of MWLua::WorldView data // LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName) // LUAS - VFS path to a Lua script // LUAD - Serialized Lua variable // LUAT - MWLua::ScriptsContainer::Timer // LUAC - Name of a timer callback (string) // LUAR - Attach script to a specific record (LuaScriptCfg::PerRecordCfg) // LUAI - Attach script to a specific instance (LuaScriptCfg::PerRefCfg) void ESM::saveLuaBinaryData(ESMWriter& esm, const std::string& data) { if (data.empty()) return; esm.startSubRecord("LUAD"); esm.write(data.data(), data.size()); esm.endRecord("LUAD"); } std::string ESM::loadLuaBinaryData(ESMReader& esm) { std::string data; if (esm.isNextSub("LUAD")) { esm.getSubHeader(); data.resize(esm.getSubSize()); esm.getExact(data.data(), static_cast(data.size())); } return data; } static bool readBool(ESM::ESMReader& esm) { char c; esm.getT(c); return c != 0; } void ESM::LuaScriptsCfg::load(ESMReader& esm) { while (esm.isNextSub("LUAS")) { mScripts.emplace_back(); ESM::LuaScriptCfg& script = mScripts.back(); script.mScriptPath = esm.getHString(); esm.getSubNameIs("LUAF"); esm.getSubHeader(); if (esm.getSubSize() < 4 || (esm.getSubSize() % 4 != 0)) esm.fail("Incorrect LUAF size"); esm.getT(script.mFlags); script.mTypes.resize((esm.getSubSize() - 4) / 4); for (uint32_t& type : script.mTypes) esm.getT(type); script.mInitializationData = loadLuaBinaryData(esm); while (esm.isNextSub("LUAR")) { esm.getSubHeader(); script.mRecords.emplace_back(); ESM::LuaScriptCfg::PerRecordCfg& recordCfg = script.mRecords.back(); recordCfg.mRecordId.resize(esm.getSubSize() - 1); recordCfg.mAttach = readBool(esm); esm.getExact(recordCfg.mRecordId.data(), static_cast(recordCfg.mRecordId.size())); recordCfg.mInitializationData = loadLuaBinaryData(esm); } while (esm.isNextSub("LUAI")) { esm.getSubHeader(); script.mRefs.emplace_back(); ESM::LuaScriptCfg::PerRefCfg& refCfg = script.mRefs.back(); refCfg.mAttach = readBool(esm); esm.getT(refCfg.mRefnumIndex); esm.getT(refCfg.mRefnumContentFile); refCfg.mInitializationData = loadLuaBinaryData(esm); } } } void ESM::LuaScriptsCfg::adjustRefNums(const ESMReader& esm) { auto adjustRefNumFn = [&esm](int contentFile) -> int { if (contentFile == 0) return esm.getIndex(); else if (contentFile > 0 && contentFile <= static_cast(esm.getParentFileIndices().size())) return esm.getParentFileIndices()[contentFile - 1]; else throw std::runtime_error("Incorrect contentFile index"); }; lua_State* L = luaL_newstate(); LuaUtil::BasicSerializer serializer(adjustRefNumFn); auto adjustLuaData = [&](std::string& data) { if (data.empty()) return; sol::object luaData = LuaUtil::deserialize(L, data, &serializer); data = LuaUtil::serialize(luaData, &serializer); }; for (LuaScriptCfg& script : mScripts) { adjustLuaData(script.mInitializationData); for (LuaScriptCfg::PerRecordCfg& recordCfg : script.mRecords) adjustLuaData(recordCfg.mInitializationData); for (LuaScriptCfg::PerRefCfg& refCfg : script.mRefs) { adjustLuaData(refCfg.mInitializationData); refCfg.mRefnumContentFile = adjustRefNumFn(refCfg.mRefnumContentFile); } } lua_close(L); } void ESM::LuaScriptsCfg::save(ESMWriter& esm) const { for (const LuaScriptCfg& script : mScripts) { esm.writeHNString("LUAS", script.mScriptPath); esm.startSubRecord("LUAF"); esm.writeT(script.mFlags); for (uint32_t type : script.mTypes) esm.writeT(type); esm.endRecord("LUAF"); saveLuaBinaryData(esm, script.mInitializationData); for (const LuaScriptCfg::PerRecordCfg& recordCfg : script.mRecords) { esm.startSubRecord("LUAR"); esm.writeT(recordCfg.mAttach ? 1 : 0); esm.write(recordCfg.mRecordId.data(), recordCfg.mRecordId.size()); esm.endRecord("LUAR"); saveLuaBinaryData(esm, recordCfg.mInitializationData); } for (const LuaScriptCfg::PerRefCfg& refCfg : script.mRefs) { esm.startSubRecord("LUAI"); esm.writeT(refCfg.mAttach ? 1 : 0); esm.writeT(refCfg.mRefnumIndex); esm.writeT(refCfg.mRefnumContentFile); esm.endRecord("LUAI"); saveLuaBinaryData(esm, refCfg.mInitializationData); } } } void ESM::LuaScripts::load(ESMReader& esm) { while (esm.isNextSub("LUAS")) { std::string name = esm.getHString(); std::string data = loadLuaBinaryData(esm); std::vector timers; while (esm.isNextSub("LUAT")) { esm.getSubHeader(); LuaTimer timer; esm.getT(timer.mType); esm.getT(timer.mTime); timer.mCallbackName = esm.getHNString("LUAC"); timer.mCallbackArgument = loadLuaBinaryData(esm); timers.push_back(std::move(timer)); } mScripts.push_back({std::move(name), std::move(data), std::move(timers)}); } } void ESM::LuaScripts::save(ESMWriter& esm) const { for (const LuaScript& script : mScripts) { esm.writeHNString("LUAS", script.mScriptPath); saveLuaBinaryData(esm, script.mData); for (const LuaTimer& timer : script.mTimers) { esm.startSubRecord("LUAT"); esm.writeT(timer.mType); esm.writeT(timer.mTime); esm.endRecord("LUAT"); esm.writeHNString("LUAC", timer.mCallbackName); if (!timer.mCallbackArgument.empty()) saveLuaBinaryData(esm, timer.mCallbackArgument); } } } openmw-openmw-0.48.0/components/esm/luascripts.hpp000066400000000000000000000071071445372753700223140ustar00rootroot00000000000000#ifndef OPENMW_ESM_LUASCRIPTS_H #define OPENMW_ESM_LUASCRIPTS_H #include #include #include namespace ESM { class ESMReader; class ESMWriter; // LuaScriptCfg, LuaScriptsCfg are used in content files. struct LuaScriptCfg { using Flags = uint32_t; static constexpr Flags sGlobal = 1ull << 0; // start as a global script static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script static constexpr Flags sPlayer = 1ull << 2; // auto attach to players static constexpr Flags sMerge = 1ull << 3; // merge with configuration for this script from previous content files. std::string mScriptPath; // VFS path to the script. std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'. Flags mFlags; // bitwise OR of Flags. // Auto attach as a local script to objects of specific types (i.e. Container, Door, Activator, etc.) std::vector mTypes; // values are ESM::RecNameInts // Auto attach as a local script to objects with specific recordIds (i.e. specific door type, or an unique NPC) struct PerRecordCfg { bool mAttach; // true - attach, false - don't attach (overrides previous attach) std::string mRecordId; // Initialization data for this specific record. If empty than LuaScriptCfg::mInitializationData is used. std::string mInitializationData; }; std::vector mRecords; // Auto attach as a local script to specific objects by their Refnums. The reference must be defined in the same // content file as this LuaScriptCfg or in one of its deps. struct PerRefCfg { bool mAttach; // true - attach, false - don't attach (overrides previous attach) uint32_t mRefnumIndex; int32_t mRefnumContentFile; // Initialization data for this specific refnum. If empty than LuaScriptCfg::mInitializationData is used. std::string mInitializationData; }; std::vector mRefs; }; struct LuaScriptsCfg { std::vector mScripts; void load(ESMReader &esm); void adjustRefNums(const ESMReader &esm); void save(ESMWriter &esm) const; }; // LuaTimer, LuaScript, LuaScripts are used in saved game files. // Storage structure for LuaUtil::ScriptsContainer. These are not top-level records. // Used either for global scripts or for local scripts on a specific object. struct LuaTimer { enum class Type : bool { SIMULATION_TIME = 0, GAME_TIME = 1, }; Type mType; double mTime; std::string mCallbackName; std::string mCallbackArgument; // Serialized Lua table. It is a binary data. Can contain '\0'. }; struct LuaScript { std::string mScriptPath; std::string mData; // Serialized Lua table. It is a binary data. Can contain '\0'. std::vector mTimers; }; struct LuaScripts { std::vector mScripts; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; // Saves binary string `data` (can contain '\0') as LUAD record. void saveLuaBinaryData(ESM::ESMWriter& esm, const std::string& data); // Loads LUAD as binary string. If next subrecord is not LUAD, then returns an empty string. std::string loadLuaBinaryData(ESM::ESMReader& esm); } #endif openmw-openmw-0.48.0/components/esm/reader.cpp000066400000000000000000000050231445372753700213530ustar00rootroot00000000000000#include "reader.hpp" //#ifdef NDEBUG //#undef NDEBUG //#endif #include #include #include #include "components/esm3/esmreader.hpp" #include "components/esm4/reader.hpp" namespace ESM { Reader* Reader::getReader(const std::string &filename) { Files::IStreamPtr esmStream(Files::openConstrainedFileStream(filename)); std::uint32_t modVer = 0; // get the first 4 bytes of the record header only esmStream->read((char*)&modVer, sizeof(modVer)); if (esmStream->gcount() == sizeof(modVer)) { esmStream->seekg(0); if (modVer == ESM4::REC_TES4) { return new ESM4::Reader(std::move(esmStream), filename); } else { //return new ESM3::ESMReader(esmStream, filename); } } throw std::runtime_error("Unknown file format"); } bool Reader::getStringImpl(std::string& str, std::size_t size, std::istream& stream, const ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) { std::size_t newSize = size; if (encoder) { std::string input(size, '\0'); stream.read(input.data(), size); if (stream.gcount() == static_cast(size)) { encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str); return true; } } else { if (hasNull) newSize -= 1; // don't read the null terminator yet str.resize(newSize); // assumed C++11 stream.read(&str[0], newSize); if (static_cast(stream.gcount()) == newSize) { if (hasNull) { char ch; stream.read(&ch, 1); // read the null terminator assert (ch == '\0' && "ESM4::Reader::getString string is not terminated with a null"); } #if 0 else { // NOTE: normal ESMs don't but omwsave has locals or spells with null terminator assert (str[newSize - 1] != '\0' && "ESM4::Reader::getString string is unexpectedly terminated with a null"); } #endif return true; } } str.clear(); return false; // FIXME: throw instead? } } openmw-openmw-0.48.0/components/esm/reader.hpp000066400000000000000000000033171445372753700213640ustar00rootroot00000000000000#ifndef COMPONENT_ESM_READER_H #define COMPONENT_ESM_READER_H #include #include #include "common.hpp" // MasterData namespace ToUTF8 { class Utf8Encoder; } namespace ESM { class Reader { std::vector* mGlobalReaderList; public: virtual ~Reader() {} static Reader* getReader(const std::string& filename); void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} std::vector *getGlobalReaderList() {return mGlobalReaderList;} virtual inline bool isEsm4() const = 0; virtual inline bool hasMoreRecs() const = 0; virtual inline void setEncoder(const ToUTF8::StatelessUtf8Encoder* encoder) = 0; // used to check for dependencies e.g. CS::Editor::run() virtual inline const std::vector& getGameFiles() const = 0; // used by ContentSelector::ContentModel::addFiles() virtual inline const std::string getAuthor() const = 0; virtual inline const std::string getDesc() const = 0; virtual inline int getFormat() const = 0; virtual inline std::string getFileName() const = 0; // used by CSMWorld::Data::startLoading() and getTotalRecords() for loading progress bar virtual inline int getRecordCount() const = 0; virtual void setModIndex(std::uint32_t index) = 0; // used by CSMWorld::Data::getTotalRecords() virtual void close() = 0; protected: bool getStringImpl(std::string& str, std::size_t size, std::istream& stream, const ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); }; } #endif // COMPONENT_ESM_READER_H openmw-openmw-0.48.0/components/esm/records.hpp000066400000000000000000000034251445372753700215630ustar00rootroot00000000000000#ifndef OPENMW_ESM_RECORDS_H #define OPENMW_ESM_RECORDS_H #include "defs.hpp" #include "components/esm3/loadacti.hpp" #include "components/esm3/loadalch.hpp" #include "components/esm3/loadappa.hpp" #include "components/esm3/loadarmo.hpp" #include "components/esm3/loadbody.hpp" #include "components/esm3/loadbook.hpp" #include "components/esm3/loadbsgn.hpp" #include "components/esm3/loadcell.hpp" #include "components/esm3/loadclas.hpp" #include "components/esm3/loadclot.hpp" #include "components/esm3/loadcont.hpp" #include "components/esm3/loadcrea.hpp" #include "components/esm3/loadinfo.hpp" #include "components/esm3/loaddial.hpp" #include "components/esm3/loaddoor.hpp" #include "components/esm3/loadench.hpp" #include "components/esm3/loadfact.hpp" #include "components/esm3/loadglob.hpp" #include "components/esm3/loadgmst.hpp" #include "components/esm3/loadingr.hpp" #include "components/esm3/loadland.hpp" #include "components/esm3/loadlevlist.hpp" #include "components/esm3/loadligh.hpp" #include "components/esm3/loadlock.hpp" #include "components/esm3/loadrepa.hpp" #include "components/esm3/loadprob.hpp" #include "components/esm3/loadltex.hpp" #include "components/esm3/loadmgef.hpp" #include "components/esm3/loadmisc.hpp" #include "components/esm3/loadnpc.hpp" #include "components/esm3/loadpgrd.hpp" #include "components/esm3/loadrace.hpp" #include "components/esm3/loadregn.hpp" #include "components/esm3/loadscpt.hpp" #include "components/esm3/loadskil.hpp" #include "components/esm3/loadsndg.hpp" #include "components/esm3/loadsoun.hpp" #include "components/esm3/loadspel.hpp" #include "components/esm3/loadsscr.hpp" #include "components/esm3/loadstat.hpp" #include "components/esm3/loadweap.hpp" // Special records which are not loaded from ESM #include "components/esm/attr.hpp" #endif openmw-openmw-0.48.0/components/esm/util.hpp000066400000000000000000000014551445372753700211000ustar00rootroot00000000000000#ifndef OPENMW_ESM_UTIL_H #define OPENMW_ESM_UTIL_H #include #include namespace ESM { // format 0, savegames only struct Quaternion { float mValues[4]; Quaternion() = default; Quaternion(const osg::Quat& q) { mValues[0] = q.w(); mValues[1] = q.x(); mValues[2] = q.y(); mValues[3] = q.z(); } operator osg::Quat () const { return osg::Quat(mValues[1], mValues[2], mValues[3], mValues[0]); } }; struct Vector3 { float mValues[3]; Vector3() = default; Vector3(const osg::Vec3f& v) { mValues[0] = v.x(); mValues[1] = v.y(); mValues[2] = v.z(); } operator osg::Vec3f () const { return osg::Vec3f(mValues[0], mValues[1], mValues[2]); } }; } #endif openmw-openmw-0.48.0/components/esm3/000077500000000000000000000000001445372753700174705ustar00rootroot00000000000000openmw-openmw-0.48.0/components/esm3/activespells.cpp000066400000000000000000000077531445372753700227060ustar00rootroot00000000000000#include "activespells.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { namespace { void saveImpl(ESMWriter& esm, const std::vector& spells, NAME tag) { for (const auto& params : spells) { esm.writeHNString (tag, params.mId); esm.writeHNT ("CAST", params.mCasterActorId); esm.writeHNString ("DISP", params.mDisplayName); esm.writeHNT ("TYPE", params.mType); if(params.mItem.isSet()) params.mItem.save(esm, true, "ITEM"); if(params.mWorsenings >= 0) { esm.writeHNT ("WORS", params.mWorsenings); esm.writeHNT ("TIME", params.mNextWorsening); } for (auto& effect : params.mEffects) { esm.writeHNT ("MGEF", effect.mEffectId); if (effect.mArg != -1) esm.writeHNT ("ARG_", effect.mArg); esm.writeHNT ("MAGN", effect.mMagnitude); esm.writeHNT ("MAGN", effect.mMinMagnitude); esm.writeHNT ("MAGN", effect.mMaxMagnitude); esm.writeHNT ("DURA", effect.mDuration); esm.writeHNT ("EIND", effect.mEffectIndex); esm.writeHNT ("LEFT", effect.mTimeLeft); esm.writeHNT ("FLAG", effect.mFlags); } } } void loadImpl(ESMReader& esm, std::vector& spells, NAME tag) { int format = esm.getFormat(); while (esm.isNextSub(tag)) { ActiveSpells::ActiveSpellParams params; params.mId = esm.getHString(); esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); params.mItem.unset(); if (format < 17) params.mType = ActiveSpells::Type_Temporary; else { esm.getHNT (params.mType, "TYPE"); if(esm.peekNextSub("ITEM")) params.mItem.load(esm, true, "ITEM"); } if(esm.isNextSub("WORS")) { esm.getHT(params.mWorsenings); esm.getHNT(params.mNextWorsening, "TIME"); } else params.mWorsenings = -1; // spell casting timestamp, no longer used if (esm.isNextSub("TIME")) esm.skipHSub(); while (esm.isNextSub("MGEF")) { ActiveEffect effect; esm.getHT(effect.mEffectId); effect.mArg = -1; esm.getHNOT(effect.mArg, "ARG_"); esm.getHNT (effect.mMagnitude, "MAGN"); if (format < 17) { effect.mMinMagnitude = effect.mMagnitude; effect.mMaxMagnitude = effect.mMagnitude; } else { esm.getHNT (effect.mMinMagnitude, "MAGN"); esm.getHNT (effect.mMaxMagnitude, "MAGN"); } esm.getHNT (effect.mDuration, "DURA"); effect.mEffectIndex = -1; esm.getHNOT (effect.mEffectIndex, "EIND"); if (format < 9) effect.mTimeLeft = effect.mDuration; else esm.getHNT (effect.mTimeLeft, "LEFT"); if (format < 17) effect.mFlags = ActiveEffect::Flag_None; else esm.getHNT (effect.mFlags, "FLAG"); params.mEffects.push_back(effect); } spells.emplace_back(params); } } } } namespace ESM { void ActiveSpells::save(ESMWriter &esm) const { saveImpl(esm, mSpells, "ID__"); saveImpl(esm, mQueue, "QID_"); } void ActiveSpells::load(ESMReader &esm) { loadImpl(esm, mSpells, "ID__"); loadImpl(esm, mQueue, "QID_"); } } openmw-openmw-0.48.0/components/esm3/activespells.hpp000066400000000000000000000033041445372753700226770ustar00rootroot00000000000000#ifndef OPENMW_ESM_ACTIVESPELLS_H #define OPENMW_ESM_ACTIVESPELLS_H #include "cellref.hpp" #include "components/esm/defs.hpp" #include "effectlist.hpp" #include #include namespace ESM { class ESMReader; class ESMWriter; // Parameters of an effect concerning lasting effects. // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. struct ActiveEffect { enum Flags { Flag_None = 0, Flag_Applied = 1 << 0, Flag_Remove = 1 << 1, Flag_Ignore_Resistances = 1 << 2, Flag_Ignore_Reflect = 1 << 3, Flag_Ignore_SpellAbsorption = 1 << 4 }; int mEffectId; float mMagnitude; float mMinMagnitude; float mMaxMagnitude; int mArg; // skill or attribute float mDuration; float mTimeLeft; int mEffectIndex; int mFlags; }; // format 0, saved games only struct ActiveSpells { enum EffectType { Type_Temporary, Type_Ability, Type_Enchantment, Type_Permanent, Type_Consumable }; struct ActiveSpellParams { std::string mId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; RefNum mItem; EffectType mType; int mWorsenings; TimeStamp mNextWorsening; }; std::vector mSpells; std::vector mQueue; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/aipackage.cpp000066400000000000000000000046271445372753700221120ustar00rootroot00000000000000#include "aipackage.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void AIData::blank() { mHello = mFight = mFlee = mAlarm = mU1 = mU2 = mU3 = 0; mServices = 0; } void AIPackageList::add(ESMReader &esm) { AIPackage pack; if (esm.retSubName() == AI_CNDT) { if (mList.empty()) { esm.fail("AIPackge with an AI_CNDT applying to no cell."); } else { mList.back().mCellName = esm.getHString(); } } else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; esm.getHExact(&pack.mWander, 14); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; esm.getHExact(&pack.mTravel, 16); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; esm.getHExact(&pack.mTarget, 48); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; esm.getHExact(&pack.mActivate, 33); mList.push_back(pack); } else { // not AI package related data, so leave return; } } void AIPackageList::save(ESMWriter &esm) const { typedef std::vector::const_iterator PackageIter; for (PackageIter it = mList.begin(); it != mList.end(); ++it) { switch (it->mType) { case AI_Wander: esm.writeHNT("AI_W", it->mWander, sizeof(it->mWander)); break; case AI_Travel: esm.writeHNT("AI_T", it->mTravel, sizeof(it->mTravel)); break; case AI_Activate: esm.writeHNT("AI_A", it->mActivate, sizeof(it->mActivate)); break; case AI_Escort: case AI_Follow: { const NAME name = (it->mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); esm.writeHNOCString("CNDT", it->mCellName); break; } default: break; } } } } openmw-openmw-0.48.0/components/esm3/aipackage.hpp000066400000000000000000000042761445372753700221170ustar00rootroot00000000000000#ifndef OPENMW_ESM_AIPACKAGE_H #define OPENMW_ESM_AIPACKAGE_H #include #include #include "components/esm/esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; #pragma pack(push) #pragma pack(1) struct AIData { unsigned short mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] char mU1, mU2, mU3; // Unknown values int mServices; // See the Services enum void blank(); ///< Set record to default state (does not touch the ID). }; // 12 bytes struct AIWander { short mDistance; short mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; unsigned char mShouldRepeat; }; struct AITravel { float mX, mY, mZ; unsigned char mShouldRepeat; unsigned char mPadding[3]; }; struct AITarget { float mX, mY, mZ; short mDuration; NAME32 mId; unsigned char mShouldRepeat; unsigned char mPadding; }; struct AIActivate { NAME32 mName; unsigned char mShouldRepeat; }; #pragma pack(pop) enum { AI_Wander = 0x575f4941, AI_Travel = 0x545f4941, AI_Follow = 0x465f4941, AI_Escort = 0x455f4941, AI_Activate = 0x415f4941, AI_CNDT = 0x54444e43 }; /// \note Used for storaging packages in a single container /// w/o manual memory allocation accordingly to policy standards struct AIPackage { int mType; // Anonymous union union { AIWander mWander; AITravel mTravel; AITarget mTarget; AIActivate mActivate; }; /// \note for AITarget only, placed here to stick with union, /// overhead should be not so awful std::string mCellName; }; struct AIPackageList { std::vector mList; /// Add a single AIPackage, assumes subrecord name was already read void add(ESMReader &esm); void save(ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/aisequence.cpp000066400000000000000000000213611445372753700223210ustar00rootroot00000000000000#include "aisequence.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include #include namespace ESM { namespace AiSequence { void AiWander::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); esm.getHNT(mDurationData, "STAR"); // was mStartTime mStoredInitialActorPosition = false; if (esm.isNextSub("POS_")) { mStoredInitialActorPosition = true; esm.getHT(mInitialActorPosition); } } void AiWander::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("STAR", mDurationData); if (mStoredInitialActorPosition) esm.writeHNT ("POS_", mInitialActorPosition); } void AiTravel::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); esm.getHNOT (mHidden, "HIDD"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("HIDD", mHidden); if(mRepeat) esm.writeHNT("REPT", mRepeat); } void AiEscort::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); mTargetActorId = -1; esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); if(esm.getFormat() < 18) { // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. // The exact value of mDuration only matters for repeating packages. // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); } } void AiEscort::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNString ("TARG", mTargetId); esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); if(mRepeat) esm.writeHNT("REPT", mRepeat); } void AiFollow::load(ESMReader &esm) { esm.getHNT (mData, "DATA"); mTargetId = esm.getHNString("TARG"); mTargetActorId = -1; esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); mCommanded = false; esm.getHNOT (mCommanded, "CMND"); mActive = false; esm.getHNOT (mActive, "ACTV"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); if(esm.getFormat() < 18) { // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. // The exact value of mDuration only matters for repeating packages. // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); } } void AiFollow::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNString("TARG", mTargetId); esm.writeHNT ("TAID", mTargetActorId); esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); esm.writeHNT ("CMND", mCommanded); if (mActive) esm.writeHNT("ACTV", mActive); if(mRepeat) esm.writeHNT("REPT", mRepeat); } void AiActivate::load(ESMReader &esm) { mTargetId = esm.getHNString("TARG"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); } void AiActivate::save(ESMWriter &esm) const { esm.writeHNString("TARG", mTargetId); if(mRepeat) esm.writeHNT("REPT", mRepeat); } void AiCombat::load(ESMReader &esm) { esm.getHNT (mTargetActorId, "TARG"); } void AiCombat::save(ESMWriter &esm) const { esm.writeHNT ("TARG", mTargetActorId); } void AiPursue::load(ESMReader &esm) { esm.getHNT (mTargetActorId, "TARG"); } void AiPursue::save(ESMWriter &esm) const { esm.writeHNT ("TARG", mTargetActorId); } void AiSequence::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) { esm.writeHNT ("AIPK", it->mType); switch (it->mType) { case Ai_Wander: static_cast(*it->mPackage).save(esm); break; case Ai_Travel: static_cast(*it->mPackage).save(esm); break; case Ai_Escort: static_cast(*it->mPackage).save(esm); break; case Ai_Follow: static_cast(*it->mPackage).save(esm); break; case Ai_Activate: static_cast(*it->mPackage).save(esm); break; case Ai_Combat: static_cast(*it->mPackage).save(esm); break; case Ai_Pursue: static_cast(*it->mPackage).save(esm); break; default: break; } } esm.writeHNT ("LAST", mLastAiPackage); } void AiSequence::load(ESMReader &esm) { int count = 0; while (esm.isNextSub("AIPK")) { int type; esm.getHT(type); mPackages.emplace_back(); mPackages.back().mType = type; switch (type) { case Ai_Wander: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); ++count; break; } case Ai_Travel: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); ++count; break; } case Ai_Escort: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); ++count; break; } case Ai_Follow: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); ++count; break; } case Ai_Activate: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); ++count; break; } case Ai_Combat: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); break; } case Ai_Pursue: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = std::move(ptr); break; } default: return; } } esm.getHNOT (mLastAiPackage, "LAST"); if(count > 1 && esm.getFormat() < 18) { for(auto& pkg : mPackages) { if(pkg.mType == Ai_Wander) static_cast(*pkg.mPackage).mData.mShouldRepeat = true; else if(pkg.mType == Ai_Travel) static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Escort) static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Follow) static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Activate) static_cast(*pkg.mPackage).mRepeat = true; } } } } } openmw-openmw-0.48.0/components/esm3/aisequence.hpp000066400000000000000000000065721445372753700223350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_AISEQUENCE_H #define OPENMW_COMPONENTS_ESM_AISEQUENCE_H #include #include #include #include "components/esm/defs.hpp" #include "components/esm/util.hpp" namespace ESM { class ESMReader; class ESMWriter; namespace AiSequence { // format 0, saved games only // As opposed to AiPackageList, this stores the "live" version of AI packages. enum AiPackages { Ai_Wander = fourCC("WAND"), Ai_Travel = fourCC("TRAV"), Ai_Escort = fourCC("ESCO"), Ai_Follow = fourCC("FOLL"), Ai_Activate = fourCC("ACTI"), Ai_Combat = fourCC("COMB"), Ai_Pursue = fourCC("PURS") }; struct AiPackage { virtual ~AiPackage() {} }; #pragma pack(push,1) struct AiWanderData { short mDistance; short mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; unsigned char mShouldRepeat; }; struct AiWanderDuration { float mRemainingDuration; int unused; }; struct AiTravelData { float mX, mY, mZ; }; struct AiEscortData { float mX, mY, mZ; short mDuration; }; #pragma pack(pop) struct AiWander : AiPackage { AiWanderData mData; AiWanderDuration mDurationData; // was TimeStamp mStartTime bool mStoredInitialActorPosition; Vector3 mInitialActorPosition; /// \todo add more AiWander state void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiTravel : AiPackage { AiTravelData mData; bool mHidden; bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiEscort : AiPackage { AiEscortData mData; int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiFollow : AiPackage { AiEscortData mData; int mTargetActorId; std::string mTargetId; std::string mCellId; float mRemainingDuration; bool mAlwaysFollow; bool mCommanded; bool mActive; bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiActivate : AiPackage { std::string mTargetId; bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiCombat : AiPackage { int mTargetActorId; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiPursue : AiPackage { int mTargetActorId; void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct AiPackageContainer { int mType; std::unique_ptr mPackage; }; struct AiSequence { AiSequence() { mLastAiPackage = -1; } std::vector mPackages; int mLastAiPackage; void load (ESMReader &esm); void save (ESMWriter &esm) const; private: AiSequence(const AiSequence&); AiSequence& operator=(const AiSequence&); }; } } #endif openmw-openmw-0.48.0/components/esm3/animationstate.cpp000066400000000000000000000026351445372753700232220ustar00rootroot00000000000000#include "animationstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { bool AnimationState::empty() const { return mScriptedAnims.empty(); } void AnimationState::load(ESMReader& esm) { mScriptedAnims.clear(); while (esm.isNextSub("ANIS")) { ScriptedAnimation anim; anim.mGroup = esm.getHString(); esm.getHNOT(anim.mTime, "TIME"); esm.getHNOT(anim.mAbsolute, "ABST"); esm.getSubNameIs("COUN"); // workaround bug in earlier version where size_t was used esm.getSubHeader(); if (esm.getSubSize() == 8) esm.getT(anim.mLoopCount); else { uint32_t loopcount; esm.getT(loopcount); anim.mLoopCount = (uint64_t) loopcount; } mScriptedAnims.push_back(anim); } } void AnimationState::save(ESMWriter& esm) const { for (ScriptedAnimations::const_iterator iter = mScriptedAnims.begin(); iter != mScriptedAnims.end(); ++iter) { esm.writeHNString("ANIS", iter->mGroup); if (iter->mTime > 0) esm.writeHNT("TIME", iter->mTime); if (iter->mAbsolute) esm.writeHNT("ABST", iter->mAbsolute); esm.writeHNT("COUN", iter->mLoopCount); } } } openmw-openmw-0.48.0/components/esm3/animationstate.hpp000066400000000000000000000013671445372753700232300ustar00rootroot00000000000000#ifndef OPENMW_ESM_ANIMATIONSTATE_H #define OPENMW_ESM_ANIMATIONSTATE_H #include #include #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct AnimationState { struct ScriptedAnimation { ScriptedAnimation() : mTime(0.f), mAbsolute(false), mLoopCount(0) {} std::string mGroup; float mTime; bool mAbsolute; uint64_t mLoopCount; }; typedef std::vector ScriptedAnimations; ScriptedAnimations mScriptedAnims; bool empty() const; void load(ESMReader& esm); void save(ESMWriter& esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/cellid.cpp000066400000000000000000000025741445372753700214400ustar00rootroot00000000000000#include "cellid.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { const std::string CellId::sDefaultWorldspace = "sys::default"; void CellId::load (ESMReader &esm) { mWorldspace = esm.getHNString ("SPAC"); if (esm.isNextSub ("CIDX")) { esm.getHTSized<8>(mIndex); mPaged = true; } else mPaged = false; } void CellId::save (ESMWriter &esm) const { esm.writeHNString ("SPAC", mWorldspace); if (mPaged) esm.writeHNT ("CIDX", mIndex, 8); } bool operator== (const CellId& left, const CellId& right) { return left.mWorldspace==right.mWorldspace && left.mPaged==right.mPaged && (!left.mPaged || (left.mIndex.mX==right.mIndex.mX && left.mIndex.mY==right.mIndex.mY)); } bool operator!= (const CellId& left, const CellId& right) { return !(left==right); } bool operator < (const CellId& left, const CellId& right) { if (left.mPaged < right.mPaged) return true; if (left.mPaged > right.mPaged) return false; if (left.mPaged) { if (left.mIndex.mX < right.mIndex.mX) return true; if (left.mIndex.mX > right.mIndex.mX) return false; if (left.mIndex.mY < right.mIndex.mY) return true; if (left.mIndex.mY > right.mIndex.mY) return false; } return left.mWorldspace < right.mWorldspace; } } openmw-openmw-0.48.0/components/esm3/cellid.hpp000066400000000000000000000012311445372753700214320ustar00rootroot00000000000000#ifndef OPENMW_ESM_CELLID_H #define OPENMW_ESM_CELLID_H #include namespace ESM { class ESMReader; class ESMWriter; struct CellId { struct CellIndex { int mX; int mY; }; std::string mWorldspace; CellIndex mIndex; bool mPaged; static const std::string sDefaultWorldspace; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; bool operator== (const CellId& left, const CellId& right); bool operator!= (const CellId& left, const CellId& right); bool operator< (const CellId& left, const CellId& right); } #endif openmw-openmw-0.48.0/components/esm3/cellref.cpp000066400000000000000000000206341445372753700216150ustar00rootroot00000000000000#include "cellref.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { namespace { template void loadIdImpl(ESMReader& esm, bool wideRefNum, CellRef& cellRef) { // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, // because any item can theoretically be moved by a script. if (esm.isNextSub("NAM0")) esm.skipHSub(); if constexpr (load) { cellRef.blank(); cellRef.mRefNum.load (esm, wideRefNum); cellRef.mRefID = esm.getHNOString("NAME"); if (cellRef.mRefID.empty()) Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); } else { RefNum {}.load(esm, wideRefNum); esm.skipHNOString("NAME"); } } template void loadDataImpl(ESMReader &esm, bool &isDeleted, CellRef& cellRef) { const auto getHStringOrSkip = [&] (std::string& value) { if constexpr (load) value = esm.getHString(); else esm.skipHString(); }; const auto getHTOrSkip = [&] (auto& value) { if constexpr (load) esm.getHT(value); else esm.skipHT>(); }; if constexpr (load) isDeleted = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("UNAM"): getHTOrSkip(cellRef.mReferenceBlocked); break; case fourCC("XSCL"): getHTOrSkip(cellRef.mScale); if constexpr (load) cellRef.mScale = std::clamp(cellRef.mScale, 0.5f, 2.0f); break; case fourCC("ANAM"): getHStringOrSkip(cellRef.mOwner); break; case fourCC("BNAM"): getHStringOrSkip(cellRef.mGlobalVariable); break; case fourCC("XSOL"): getHStringOrSkip(cellRef.mSoul); break; case fourCC("CNAM"): getHStringOrSkip(cellRef.mFaction); break; case fourCC("INDX"): getHTOrSkip(cellRef.mFactionRank); break; case fourCC("XCHG"): getHTOrSkip(cellRef.mEnchantmentCharge); break; case fourCC("INTV"): getHTOrSkip(cellRef.mChargeInt); break; case fourCC("NAM9"): getHTOrSkip(cellRef.mGoldValue); break; case fourCC("DODT"): getHTOrSkip(cellRef.mDoorDest); if constexpr (load) cellRef.mTeleport = true; break; case fourCC("DNAM"): getHStringOrSkip(cellRef.mDestCell); break; case fourCC("FLTV"): getHTOrSkip(cellRef.mLockLevel); break; case fourCC("KNAM"): getHStringOrSkip(cellRef.mKey); break; case fourCC("TNAM"): getHStringOrSkip(cellRef.mTrap); break; case fourCC("DATA"): if constexpr (load) esm.getHTSized<24>(cellRef.mPos); else esm.skipHTSized<24, decltype(cellRef.mPos)>(); break; case fourCC("NAM0"): { esm.skipHSub(); break; } case SREC_DELE: esm.skipHSub(); if constexpr (load) isDeleted = true; break; default: esm.cacheSubName(); isLoaded = true; break; } } if constexpr (load) { if (cellRef.mLockLevel == 0 && !cellRef.mKey.empty()) { cellRef.mLockLevel = UnbreakableLock; cellRef.mTrap.clear(); } } } } void RefNum::load(ESMReader& esm, bool wide, NAME tag) { if (wide) esm.getHNTSized<8>(*this, tag); else esm.getHNT(mIndex, tag); } void RefNum::save(ESMWriter &esm, bool wide, NAME tag) const { if (wide) esm.writeHNT (tag, *this, 8); else { if (isSet() && !hasContentFile()) Log(Debug::Error) << "Generated RefNum can not be saved in 32bit format"; int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff)<<24); esm.writeHNT (tag, refNum, 4); } } void CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); loadData(esm, isDeleted); } void CellRef::loadId (ESMReader& esm, bool wideRefNum) { loadIdImpl(esm, wideRefNum, *this); } void CellRef::loadData(ESMReader &esm, bool &isDeleted) { loadDataImpl(esm, isDeleted, *this); } void CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); esm.writeHNCString("NAME", mRefID); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (mScale != 1.0) { esm.writeHNT("XSCL", std::clamp(mScale, 0.5f, 2.0f)); } if (!inInventory) esm.writeHNOCString("ANAM", mOwner); esm.writeHNOCString("BNAM", mGlobalVariable); esm.writeHNOCString("XSOL", mSoul); if (!inInventory) { esm.writeHNOCString("CNAM", mFaction); if (mFactionRank != -2) { esm.writeHNT("INDX", mFactionRank); } } if (mEnchantmentCharge != -1) esm.writeHNT("XCHG", mEnchantmentCharge); if (mChargeInt != -1) esm.writeHNT("INTV", mChargeInt); if (mGoldValue > 1) esm.writeHNT("NAM9", mGoldValue); if (!inInventory && mTeleport) { esm.writeHNT("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } if (!inInventory && mLockLevel != 0) { esm.writeHNT("FLTV", mLockLevel); } if (!inInventory) { esm.writeHNOCString ("KNAM", mKey); esm.writeHNOCString ("TNAM", mTrap); } if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); if (!inInventory) esm.writeHNT("DATA", mPos, 24); } void CellRef::blank() { mRefNum.unset(); mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); mSoul.clear(); mFaction.clear(); mFactionRank = -2; mChargeInt = -1; mChargeIntRemainder = 0.0f; mEnchantmentCharge = -1; mGoldValue = 1; mDestCell.clear(); mLockLevel = 0; mKey.clear(); mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; mDoorDest.rot[i] = 0; mPos.pos[i] = 0; mPos.rot[i] = 0; } } void skipLoadCellRef(ESMReader& esm, bool wideRefNum) { CellRef cellRef; loadIdImpl(esm, wideRefNum, cellRef); bool isDeleted; loadDataImpl(esm, isDeleted, cellRef); } } openmw-openmw-0.48.0/components/esm3/cellref.hpp000066400000000000000000000111611445372753700216150ustar00rootroot00000000000000#ifndef OPENMW_ESM_CELLREF_H #define OPENMW_ESM_CELLREF_H #include #include #include "components/esm/defs.hpp" #include "components/esm/esmcommon.hpp" namespace ESM { class ESMWriter; class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); struct RefNum { unsigned int mIndex; int mContentFile; void load(ESMReader& esm, bool wide = false, NAME tag = "FRMR"); void save(ESMWriter &esm, bool wide = false, NAME tag = "FRMR") const; inline bool hasContentFile() const { return mContentFile >= 0; } inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } inline void unset() { *this = {0, -1}; } }; /* Cell reference. This represents ONE object (of many) inside the cell. The cell references are not loaded as part of the normal loading process, but are rather loaded later on demand when we are setting up a specific cell. */ class CellRef { public: // Reference number // Note: Currently unused for items in containers RefNum mRefNum; std::string mRefID; // ID of object being referenced float mScale; // Scale applied to mesh // The NPC that owns this object (and will get angry if you steal it) std::string mOwner; // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed // even if it has an Owner field. // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. std::string mGlobalVariable; // ID of creature trapped in this soul gem std::string mSoul; // The faction that owns this object (and will get angry if // you take it and are not a faction member) std::string mFaction; // PC faction rank required to use the item. Sometimes is -1, which means "any rank". int mFactionRank; // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. // For lights it is remaining time. // This could be -1 if the charge was not touched yet (i.e. full). union { int mChargeInt; // Used by everything except lights float mChargeFloat; // Used only by lights }; float mChargeIntRemainder; // Stores amount of charge not subtracted from mChargeInt // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; // This is 5 for Gold_005 references, 100 for Gold_100 and so on. int mGoldValue; // For doors - true if this door teleports to somewhere else, false // if it should open through animation. bool mTeleport; // Teleport location for the door, if this is a teleporting door. Position mDoorDest; // Destination cell for doors (optional) std::string mDestCell; // Lock level for doors and containers int mLockLevel; std::string mKey, mTrap; // Key and trap ID names, if any // This corresponds to the "Reference Blocked" checkbox in the construction set, // which prevents editing that reference. // -1 is not blocked, otherwise it is blocked. signed char mReferenceBlocked; // Position and rotation of this object within the cell Position mPos; /// Calls loadId and loadData void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false); void loadId (ESMReader& esm, bool wideRefNum = false); /// Implicitly called by load void loadData (ESMReader& esm, bool &isDeleted); void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); }; void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false); inline bool operator== (const RefNum& left, const RefNum& right) { return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; } inline bool operator< (const RefNum& left, const RefNum& right) { if (left.mIndexright.mIndex) return false; return left.mContentFile namespace ESM { void CreatureStats::load (ESMReader &esm) { bool intFallback = esm.getFormat() < 11; for (int i=0; i<8; ++i) mAttributes[i].load (esm, intFallback); for (int i=0; i<3; ++i) mDynamic[i].load (esm); mGoldPool = 0; esm.getHNOT (mGoldPool, "GOLD"); mTradeTime.mDay = 0; mTradeTime.mHour = 0; esm.getHNOT (mTradeTime, "TIME"); int flags = 0; mDead = false; mDeathAnimationFinished = false; mDied = false; mMurdered = false; mTalkedTo = false; mAlarmed = false; mAttacked = false; mKnockdown = false; mKnockdownOneFrame = false; mKnockdownOverOneFrame = false; mHitRecovery = false; mBlock = false; mRecalcDynamicStats = false; if (esm.getFormat() < 8) { esm.getHNOT (mDead, "DEAD"); esm.getHNOT (mDeathAnimationFinished, "DFNT"); if (esm.getFormat() < 3 && mDead) mDeathAnimationFinished = true; esm.getHNOT (mDied, "DIED"); esm.getHNOT (mMurdered, "MURD"); if (esm.isNextSub("FRHT")) esm.skipHSub(); // Friendly hits, no longer used esm.getHNOT (mTalkedTo, "TALK"); esm.getHNOT (mAlarmed, "ALRM"); esm.getHNOT (mAttacked, "ATKD"); if (esm.isNextSub("HOST")) esm.skipHSub(); // Hostile, no longer used if (esm.isNextSub("ATCK")) esm.skipHSub(); // attackingOrSpell, no longer used esm.getHNOT (mKnockdown, "KNCK"); esm.getHNOT (mKnockdownOneFrame, "KNC1"); esm.getHNOT (mKnockdownOverOneFrame, "KNCO"); esm.getHNOT (mHitRecovery, "HITR"); esm.getHNOT (mBlock, "BLCK"); } else { esm.getHNOT(flags, "AFLG"); mDead = flags & Dead; mDeathAnimationFinished = flags & DeathAnimationFinished; mDied = flags & Died; mMurdered = flags & Murdered; mTalkedTo = flags & TalkedTo; mAlarmed = flags & Alarmed; mAttacked = flags & Attacked; mKnockdown = flags & Knockdown; mKnockdownOneFrame = flags & KnockdownOneFrame; mKnockdownOverOneFrame = flags & KnockdownOverOneFrame; mHitRecovery = flags & HitRecovery; mBlock = flags & Block; mRecalcDynamicStats = flags & RecalcDynamicStats; } mMovementFlags = 0; esm.getHNOT (mMovementFlags, "MOVE"); if (esm.isNextSub("ASTR")) esm.skipHSub(); // attackStrength, no longer used mFallHeight = 0; esm.getHNOT (mFallHeight, "FALL"); mLastHitObject = esm.getHNOString ("LHIT"); mLastHitAttemptObject = esm.getHNOString ("LHAT"); if (esm.getFormat() < 8) esm.getHNOT (mRecalcDynamicStats, "CALC"); mDrawState = 0; esm.getHNOT (mDrawState, "DRAW"); mLevel = 1; esm.getHNOT (mLevel, "LEVL"); mActorId = -1; esm.getHNOT (mActorId, "ACID"); mDeathAnimation = -1; esm.getHNOT (mDeathAnimation, "DANM"); mTimeOfDeath.mDay = 0; mTimeOfDeath.mHour = 0; esm.getHNOT (mTimeOfDeath, "DTIM"); mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); mMagicEffects.load(esm); if (esm.getFormat() < 17) { while (esm.isNextSub("SUMM")) { int magicEffect; esm.getHT(magicEffect); std::string source = esm.getHNOString("SOUR"); int effectIndex = -1; esm.getHNOT (effectIndex, "EIND"); int actorId; esm.getHNT (actorId, "ACID"); mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; mSummonedCreatures.emplace(magicEffect, actorId); } } else { while (esm.isNextSub("SUMM")) { int magicEffect; esm.getHT(magicEffect); int actorId; esm.getHNT (actorId, "ACID"); mSummonedCreatures.emplace(magicEffect, actorId); } } while (esm.isNextSub("GRAV")) { int actorId; esm.getHT(actorId); mSummonGraveyard.push_back(actorId); } mHasAiSettings = false; esm.getHNOT(mHasAiSettings, "AISE"); if (mHasAiSettings) { for (int i=0; i<4; ++i) mAiSettings[i].load(esm); } while (esm.isNextSub("CORP")) { std::string id = esm.getHString(); CorprusStats stats; esm.getHNT(stats.mWorsenings, "WORS"); esm.getHNT(stats.mNextWorsening, "TIME"); mCorprusSpells[id] = stats; } if(esm.getFormat() <= 18) mMissingACDT = mGoldPool == std::numeric_limits::min(); else { mMissingACDT = false; esm.getHNOT(mMissingACDT, "NOAC"); } } void CreatureStats::save (ESMWriter &esm) const { for (int i=0; i<8; ++i) mAttributes[i].save (esm); for (int i=0; i<3; ++i) mDynamic[i].save (esm); if (mGoldPool) esm.writeHNT("GOLD", mGoldPool); if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0) esm.writeHNT ("TIME", mTradeTime); int flags = 0; if (mDead) flags |= Dead; if (mDeathAnimationFinished) flags |= DeathAnimationFinished; if (mDied) flags |= Died; if (mMurdered) flags |= Murdered; if (mTalkedTo) flags |= TalkedTo; if (mAlarmed) flags |= Alarmed; if (mAttacked) flags |= Attacked; if (mKnockdown) flags |= Knockdown; if (mKnockdownOneFrame) flags |= KnockdownOneFrame; if (mKnockdownOverOneFrame) flags |= KnockdownOverOneFrame; if (mHitRecovery) flags |= HitRecovery; if (mBlock) flags |= Block; if (mRecalcDynamicStats) flags |= RecalcDynamicStats; if (flags) esm.writeHNT ("AFLG", flags); if (mMovementFlags) esm.writeHNT ("MOVE", mMovementFlags); if (mFallHeight) esm.writeHNT ("FALL", mFallHeight); if (!mLastHitObject.empty()) esm.writeHNString ("LHIT", mLastHitObject); if (!mLastHitAttemptObject.empty()) esm.writeHNString ("LHAT", mLastHitAttemptObject); if (mDrawState) esm.writeHNT ("DRAW", mDrawState); if (mLevel != 1) esm.writeHNT ("LEVL", mLevel); if (mActorId != -1) esm.writeHNT ("ACID", mActorId); if (mDeathAnimation != -1) esm.writeHNT ("DANM", mDeathAnimation); if (mTimeOfDeath.mHour != 0 || mTimeOfDeath.mDay != 0) esm.writeHNT ("DTIM", mTimeOfDeath); mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); mMagicEffects.save(esm); for (const auto& [effectId, actorId] : mSummonedCreatures) { esm.writeHNT ("SUMM", effectId); esm.writeHNT ("ACID", actorId); } for (int key : mSummonGraveyard) { esm.writeHNT ("GRAV", key); } esm.writeHNT("AISE", mHasAiSettings); if (mHasAiSettings) { for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } if (mMissingACDT) esm.writeHNT("NOAC", mMissingACDT); } void CreatureStats::blank() { mTradeTime.mHour = 0; mTradeTime.mDay = 0; mGoldPool = 0; mActorId = -1; mHasAiSettings = false; mDead = false; mDeathAnimationFinished = false; mDied = false; mMurdered = false; mTalkedTo = false; mAlarmed = false; mAttacked = false; mKnockdown = false; mKnockdownOneFrame = false; mKnockdownOverOneFrame = false; mHitRecovery = false; mBlock = false; mMovementFlags = 0; mFallHeight = 0.f; mRecalcDynamicStats = false; mDrawState = 0; mDeathAnimation = -1; mLevel = 1; mCorprusSpells.clear(); mMissingACDT = false; } } openmw-openmw-0.48.0/components/esm3/creaturestats.hpp000066400000000000000000000051161445372753700230750ustar00rootroot00000000000000#ifndef OPENMW_ESM_CREATURESTATS_H #define OPENMW_ESM_CREATURESTATS_H #include #include #include #include "statstate.hpp" #include "components/esm/defs.hpp" #include "components/esm/attr.hpp" #include "spellstate.hpp" #include "activespells.hpp" #include "magiceffects.hpp" #include "aisequence.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct CreatureStats { struct CorprusStats { int mWorsenings[Attribute::Length]; TimeStamp mNextWorsening; }; StatState mAttributes[Attribute::Length]; StatState mDynamic[3]; MagicEffects mMagicEffects; AiSequence::AiSequence mAiSequence; bool mHasAiSettings; StatState mAiSettings[4]; std::map mSummonedCreatureMap; std::multimap mSummonedCreatures; std::vector mSummonGraveyard; TimeStamp mTradeTime; int mGoldPool; int mActorId; //int mHitAttemptActorId; enum Flags { Dead = 0x0001, DeathAnimationFinished = 0x0002, Died = 0x0004, Murdered = 0x0008, TalkedTo = 0x0010, Alarmed = 0x0020, Attacked = 0x0040, Knockdown = 0x0080, KnockdownOneFrame = 0x0100, KnockdownOverOneFrame = 0x0200, HitRecovery = 0x0400, Block = 0x0800, RecalcDynamicStats = 0x1000 }; bool mDead; bool mDeathAnimationFinished; bool mDied; bool mMurdered; bool mTalkedTo; bool mAlarmed; bool mAttacked; bool mKnockdown; bool mKnockdownOneFrame; bool mKnockdownOverOneFrame; bool mHitRecovery; bool mBlock; unsigned int mMovementFlags; float mFallHeight; std::string mLastHitObject; std::string mLastHitAttemptObject; bool mRecalcDynamicStats; int mDrawState; signed char mDeathAnimation; TimeStamp mTimeOfDeath; int mLevel; bool mMissingACDT; std::map mCorprusSpells; SpellState mSpells; ActiveSpells mActiveSpells; /// Initialize to default state void blank(); void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/custommarkerstate.cpp000066400000000000000000000007421445372753700237540ustar00rootroot00000000000000#include "custommarkerstate.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void CustomMarker::save(ESMWriter &esm) const { esm.writeHNT("POSX", mWorldX); esm.writeHNT("POSY", mWorldY); mCell.save(esm); if (!mNote.empty()) esm.writeHNString("NOTE", mNote); } void CustomMarker::load(ESMReader &esm) { esm.getHNT(mWorldX, "POSX"); esm.getHNT(mWorldY, "POSY"); mCell.load(esm); mNote = esm.getHNOString("NOTE"); } } openmw-openmw-0.48.0/components/esm3/custommarkerstate.hpp000066400000000000000000000010201445372753700237470ustar00rootroot00000000000000#ifndef OPENMW_ESM_CUSTOMMARKERSTATE_H #define OPENMW_ESM_CUSTOMMARKERSTATE_H #include "cellid.hpp" namespace ESM { // format 0, saved games only struct CustomMarker { float mWorldX; float mWorldY; CellId mCell; std::string mNote; bool operator == (const CustomMarker& other) const { return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY; } void load (ESMReader& reader); void save (ESMWriter& writer) const; }; } #endif openmw-openmw-0.48.0/components/esm3/debugprofile.cpp000066400000000000000000000025441445372753700226500ustar00rootroot00000000000000#include "debugprofile.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void DebugProfile::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); break; case fourCC("DESC"): mDescription = esm.getHString(); break; case fourCC("SCRP"): mScriptText = esm.getHString(); break; case fourCC("FLAG"): esm.getHT(mFlags); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void DebugProfile::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); } void DebugProfile::blank() { mDescription.clear(); mScriptText.clear(); mFlags = 0; } } openmw-openmw-0.48.0/components/esm3/debugprofile.hpp000066400000000000000000000016621445372753700226550ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_DEBUGPROFILE_H #define COMPONENTS_ESM_DEBUGPROFILE_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct DebugProfile { constexpr static RecNameInts sRecordId = REC_DBGP; enum Flags { Flag_Default = 1, // add to newly opened scene subviews Flag_BypassNewGame = 2, // bypass regular game startup Flag_Global = 4 // make available from main menu (i.e. not location specific) }; unsigned int mRecordFlags; std::string mId; std::string mDescription; std::string mScriptText; unsigned int mFlags; void load (ESMReader& esm, bool &isDeleted); void save (ESMWriter& esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID). void blank(); }; } #endif openmw-openmw-0.48.0/components/esm3/dialoguestate.cpp000066400000000000000000000027251445372753700230340ustar00rootroot00000000000000#include "dialoguestate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void DialogueState::load (ESMReader &esm) { while (esm.isNextSub ("TOPI")) mKnownTopics.push_back (esm.getHString()); while (esm.isNextSub ("FACT")) { std::string faction = esm.getHString(); while (esm.isNextSub("REA2")) { std::string faction2 = esm.getHString(); int reaction; esm.getHNT(reaction, "INTV"); mChangedFactionReaction[faction][faction2] = reaction; } // no longer used while (esm.isNextSub ("REAC")) { esm.skipHSub(); esm.getSubName(); esm.skipHSub(); } } } void DialogueState::save (ESMWriter &esm) const { for (std::vector::const_iterator iter (mKnownTopics.begin()); iter!=mKnownTopics.end(); ++iter) { esm.writeHNString ("TOPI", *iter); } for (std::map >::const_iterator iter = mChangedFactionReaction.begin(); iter != mChangedFactionReaction.end(); ++iter) { esm.writeHNString ("FACT", iter->first); for (std::map::const_iterator reactIter = iter->second.begin(); reactIter != iter->second.end(); ++reactIter) { esm.writeHNString ("REA2", reactIter->first); esm.writeHNT ("INTV", reactIter->second); } } } } openmw-openmw-0.48.0/components/esm3/dialoguestate.hpp000066400000000000000000000010561445372753700230350ustar00rootroot00000000000000#ifndef OPENMW_ESM_DIALOGUESTATE_H #define OPENMW_ESM_DIALOGUESTATE_H #include #include #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct DialogueState { // must be lower case topic IDs std::vector mKnownTopics; // must be lower case faction IDs std::map > mChangedFactionReaction; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/doorstate.cpp000066400000000000000000000015501445372753700222010ustar00rootroot00000000000000#include "doorstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include namespace ESM { void DoorState::load(ESMReader &esm) { ObjectState::load(esm); mDoorState = 0; esm.getHNOT (mDoorState, "ANIM"); if (mDoorState < 0 || mDoorState > 2) Log(Debug::Warning) << "Dropping invalid door state (" << mDoorState << ") for door \"" << mRef.mRefID << "\""; } void DoorState::save(ESMWriter &esm, bool inInventory) const { ObjectState::save(esm, inInventory); if (mDoorState < 0 || mDoorState > 2) { Log(Debug::Warning) << "Dropping invalid door state (" << mDoorState << ") for door \"" << mRef.mRefID << "\""; return; } if (mDoorState != 0) esm.writeHNT ("ANIM", mDoorState); } } openmw-openmw-0.48.0/components/esm3/doorstate.hpp000066400000000000000000000010511445372753700222020ustar00rootroot00000000000000#ifndef OPENMW_ESM_DOORSTATE_H #define OPENMW_ESM_DOORSTATE_H #include "objectstate.hpp" namespace ESM { // format 0, saved games only struct DoorState final : public ObjectState { int mDoorState = 0; void load (ESMReader &esm) override; void save (ESMWriter &esm, bool inInventory = false) const override; DoorState& asDoorState() override { return *this; } const DoorState& asDoorState() const override { return *this; } }; } #endif openmw-openmw-0.48.0/components/esm3/effectlist.cpp000066400000000000000000000010451445372753700223240ustar00rootroot00000000000000#include "effectlist.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void EffectList::load(ESMReader &esm) { mList.clear(); while (esm.isNextSub("ENAM")) { add(esm); } } void EffectList::add(ESMReader &esm) { ENAMstruct s; esm.getHTSized<24>(s); mList.push_back(s); } void EffectList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("ENAM", *it, 24); } } } // end namespace openmw-openmw-0.48.0/components/esm3/effectlist.hpp000066400000000000000000000020561445372753700223340ustar00rootroot00000000000000#ifndef OPENMW_ESM_EFFECTLIST_H #define OPENMW_ESM_EFFECTLIST_H #include namespace ESM { class ESMReader; class ESMWriter; #pragma pack(push) #pragma pack(1) /** Defines a spell effect. Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item enchantments) records */ struct ENAMstruct { // Magical effect, hard-coded ID short mEffectID; // Which skills/attributes are affected (for restore/drain spells // etc.) signed char mSkill, mAttribute; // -1 if N/A // Other spell parameters int mRange; // 0 - self, 1 - touch, 2 - target (RangeType enum) int mArea, mDuration, mMagnMin, mMagnMax; }; #pragma pack(pop) /// EffectList, ENAM subrecord struct EffectList { std::vector mList; /// Load one effect, assumes subrecord name was already read void add(ESMReader &esm); /// Load all effects void load(ESMReader &esm); void save(ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/esmreader.cpp000066400000000000000000000235451445372753700221540ustar00rootroot00000000000000#include "esmreader.hpp" #include "readerscache.hpp" #include #include #include #include #include namespace ESM { using namespace Misc; ESM_Context ESMReader::getContext() { // Update the file position before returning mCtx.filePos = mEsm->tellg(); return mCtx; } ESMReader::ESMReader() : mRecordFlags(0) , mBuffer(50*1024) , mEncoder(nullptr) , mFileSize(0) { clearCtx(); mCtx.index = 0; } void ESMReader::restoreContext(const ESM_Context &rc) { // Reopen the file if necessary if (mCtx.filename != rc.filename) openRaw(rc.filename); // Copy the data mCtx = rc; // Make sure we seek to the right place mEsm->seekg(mCtx.filePos); } void ESMReader::close() { mEsm.reset(); clearCtx(); mHeader.blank(); } void ESMReader::clearCtx() { mCtx.filename.clear(); mCtx.leftFile = 0; mCtx.leftRec = 0; mCtx.leftSub = 0; mCtx.subCached = false; mCtx.recName.clear(); mCtx.subName.clear(); } std::string ESMReader::getMaybeFixedStringSize(std::size_t size) { if (mHeader.mFormat > 22) { std::uint32_t storedSize = 0; getT(storedSize); if (storedSize > mCtx.leftSub) fail("String does not fit subrecord (" + std::to_string(storedSize) + " > " + std::to_string(mCtx.leftSub) + ")"); size = static_cast(storedSize); } return std::string(getStringView(size)); } std::string_view ESMReader::getStringView(std::size_t size) { if (mBuffer.size() <= size) // Add some extra padding to reduce the chance of having to resize // again later. mBuffer.resize(3 * size); // And make sure the string is zero terminated mBuffer[size] = 0; // read ESM data char* ptr = mBuffer.data(); getExact(ptr, size); size = strnlen(ptr, size); // Convert to UTF8 and return if (mEncoder != nullptr) return mEncoder->getUtf8(std::string_view(ptr, size)); return std::string_view(ptr, size); } void ESMReader::resolveParentFileIndices(ReadersCache& readers) { mCtx.parentFileIndices.clear(); for (const Header::MasterData &mast : getGameFiles()) { const std::string& fname = mast.name; int index = getIndex(); for (int i = 0; i < getIndex(); i++) { const ESM::ReadersCache::BusyItem reader = readers.get(static_cast(i)); if (reader->getFileSize() == 0) continue; // Content file in non-ESM format const std::string& candidate = reader->getName(); std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; break; } } mCtx.parentFileIndices.push_back(index); } } void ESMReader::openRaw(std::unique_ptr&& stream, std::string_view name) { close(); mEsm = std::move(stream); mCtx.filename = name; mEsm->seekg(0, mEsm->end); mCtx.leftFile = mFileSize = mEsm->tellg(); mEsm->seekg(0, mEsm->beg); } void ESMReader::openRaw(std::string_view filename) { openRaw(Files::openBinaryInputFileStream(std::string(filename)), filename); } void ESMReader::open(std::unique_ptr&& stream, const std::string &name) { openRaw(std::move(stream), name); if (getRecName() != "TES3") fail("Not a valid Morrowind file"); getRecHeader(); mHeader.load (*this); } void ESMReader::open(const std::string &file) { open(Files::openBinaryInputFileStream(file), file); } std::string ESMReader::getHNOString(NAME name) { if (isNextSub(name)) return getHString(); return ""; } void ESMReader::skipHNOString(NAME name) { if (isNextSub(name)) skipHString(); } std::string ESMReader::getHNString(NAME name) { getSubNameIs(name); return getHString(); } std::string ESMReader::getHString() { getSubHeader(); // Hack to make MultiMark.esp load. Zero-length strings do not // occur in any of the official mods, but MultiMark makes use of // them. For some reason, they break the rules, and contain a byte // (value 0) even if the header says there is no data. If // Morrowind accepts it, so should we. if (mCtx.leftSub == 0 && hasMoreSubs() && !mEsm->peek()) { // Skip the following zero byte mCtx.leftRec--; char c; getT(c); return std::string(); } return getString(mCtx.leftSub); } void ESMReader::skipHString() { getSubHeader(); // Hack to make MultiMark.esp load. Zero-length strings do not // occur in any of the official mods, but MultiMark makes use of // them. For some reason, they break the rules, and contain a byte // (value 0) even if the header says there is no data. If // Morrowind accepts it, so should we. if (mCtx.leftSub == 0 && hasMoreSubs() && !mEsm->peek()) { // Skip the following zero byte mCtx.leftRec--; skipT(); return; } skip(mCtx.leftSub); } void ESMReader::getHExact(void*p, int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) reportSubSizeMismatch(size, mCtx.leftSub); getExact(p, size); } // Read the given number of bytes from a named subrecord void ESMReader::getHNExact(void*p, int size, NAME name) { getSubNameIs(name); getHExact(p, size); } // Get the next subrecord name and check if it matches the parameter void ESMReader::getSubNameIs(NAME name) { getSubName(); if (mCtx.subName != name) fail("Expected subrecord " + name.toString() + " but got " + mCtx.subName.toString()); } bool ESMReader::isNextSub(NAME name) { if (!hasMoreSubs()) return false; getSubName(); // If the name didn't match, then mark the it as 'cached' so it's // available for the next call to getSubName. mCtx.subCached = (mCtx.subName != name); // If subCached is false, then subName == name. return !mCtx.subCached; } bool ESMReader::peekNextSub(NAME name) { if (!hasMoreSubs()) return false; getSubName(); mCtx.subCached = true; return mCtx.subName == name; } // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() { // If the name has already been read, do nothing if (mCtx.subCached) { mCtx.subCached = false; return; } // reading the subrecord data anyway. const std::size_t subNameSize = decltype(mCtx.subName)::sCapacity; getExact(mCtx.subName.mData, static_cast(subNameSize)); mCtx.leftRec -= static_cast(subNameSize); } void ESMReader::skipHSub() { getSubHeader(); skip(mCtx.leftSub); } void ESMReader::skipHSubSize(int size) { skipHSub(); if (static_cast (mCtx.leftSub) != size) reportSubSizeMismatch(mCtx.leftSub, size); } void ESMReader::skipHSubUntil(NAME name) { while (hasMoreSubs() && !isNextSub(name)) { mCtx.subCached = false; skipHSub(); } if (hasMoreSubs()) mCtx.subCached = true; } void ESMReader::getSubHeader() { if (mCtx.leftRec < sizeof(mCtx.leftSub)) fail("End of record while reading sub-record header"); // Get subrecord size getT(mCtx.leftSub); mCtx.leftRec -= sizeof(mCtx.leftSub); // Adjust number of record bytes left if (mCtx.leftRec < mCtx.leftSub) fail("Record size is larger than rest of file"); mCtx.leftRec -= mCtx.leftSub; } NAME ESMReader::getRecName() { if (!hasMoreRecs()) fail("No more records, getRecName() failed"); getName(mCtx.recName); mCtx.leftFile -= decltype(mCtx.recName)::sCapacity; // Make sure we don't carry over any old cached subrecord // names. This can happen in some cases when we skip parts of a // record. mCtx.subCached = false; return mCtx.recName; } void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) { // General error checking if (mCtx.leftFile < 3 * sizeof(uint32_t)) fail("End of file while reading record header"); if (mCtx.leftRec) fail("Previous record contains unread bytes"); getUint(mCtx.leftRec); getUint(flags);// This header entry is always zero getUint(flags); mCtx.leftFile -= 3 * sizeof(uint32_t); // Check that sizes add up if (mCtx.leftFile < mCtx.leftRec) reportSubSizeMismatch(mCtx.leftFile, mCtx.leftRec); // Adjust number of bytes mCtx.left in file mCtx.leftFile -= mCtx.leftRec; } /************************************************************************* * * Lowest level data reading and misc methods * *************************************************************************/ std::string ESMReader::getString(int size) { size_t s = size; if (mBuffer.size() <= s) // Add some extra padding to reduce the chance of having to resize // again later. mBuffer.resize(3*s); // And make sure the string is zero terminated mBuffer[s] = 0; // read ESM data char *ptr = mBuffer.data(); getExact(ptr, size); size = static_cast(strnlen(ptr, size)); // Convert to UTF8 and return if (mEncoder) return std::string(mEncoder->getUtf8(std::string_view(ptr, size))); return std::string (ptr, size); } [[noreturn]] void ESMReader::fail(const std::string &msg) { std::stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toStringView(); ss << "\n Subrecord: " << mCtx.subName.toStringView(); if (mEsm.get()) ss << "\n Offset: 0x" << std::hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } } openmw-openmw-0.48.0/components/esm3/esmreader.hpp000066400000000000000000000224451445372753700221570ustar00rootroot00000000000000#ifndef OPENMW_ESM_READER_H #define OPENMW_ESM_READER_H #include #include #include #include #include #include #include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" namespace ESM { class ReadersCache; class ESMReader { public: ESMReader(); /************************************************************************* * * Information retrieval * *************************************************************************/ int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string& getAuthor() const { return mHeader.mData.author; } const std::string& getDesc() const { return mHeader.mData.desc; } const std::vector &getGameFiles() const { return mHeader.mMaster; } const Header& getHeader() const { return mHeader; } int getFormat() const { return mHeader.mFormat; }; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } const std::string& getName() const { return mCtx.filename; }; bool isOpen() const { return mEsm != nullptr; } std::string getMaybeFixedStringSize(std::size_t size); std::string_view getStringView(std::size_t size); /************************************************************************* * * Opening and closing * *************************************************************************/ /** Save the current file position and information in a ESM_Context struct */ ESM_Context getContext(); /** Restore a previously saved context */ void restoreContext(const ESM_Context &rc); /** Close the file, resets all information. After calling close() the structure may be reused to load a new file. */ void close(); /// Raw opening. Opens the file and sets everything up but doesn't /// parse the header. void openRaw(std::unique_ptr&& stream, std::string_view name); /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. void open(std::unique_ptr&& stream, const std::string &name); void open(const std::string &file); void openRaw(std::string_view filename); /// Get the current position in the file. Make sure that the file has been opened! size_t getFileOffset() const { return mEsm->tellg(); }; // This is a quick hack for multiple esm/esp files. Each plugin introduces its own // terrain palette, but ESMReader does not pass a reference to the correct plugin // to the individual load() methods. This hack allows to pass this reference // indirectly to the load() method. void setIndex(const int index) { mCtx.index = index;} int getIndex() const {return mCtx.index;} // Assign parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This is required for correct adjustRefNum() results // as required for handling moved, deleted and edited CellRefs. /// @note Does not validate. void resolveParentFileIndices(ReadersCache& readers); const std::vector& getParentFileIndices() const { return mCtx.parentFileIndices; } /************************************************************************* * * Medium-level reading shortcuts * *************************************************************************/ // Read data of a given type, stored in a subrecord of a given name template void getHNT(X &x, NAME name) { getSubNameIs(name); getHT(x); } // Optional version of getHNT template void getHNOT(X &x, NAME name) { if(isNextSub(name)) getHT(x); } // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template void getHNTSized(X &x, NAME name) { static_assert(sizeof(X) == size); getHNT(x, name); } template void getHNOTSized(X &x, NAME name) { static_assert(sizeof(X) == size); getHNOT(x, name); } // Get data of a given type/size, including subrecord header template void getHT(X &x) { getSubHeader(); if (mCtx.leftSub != sizeof(X)) reportSubSizeMismatch(sizeof(X), mCtx.leftSub); getT(x); } template void skipHT() { getSubHeader(); if (mCtx.leftSub != sizeof(T)) reportSubSizeMismatch(sizeof(T), mCtx.leftSub); skipT(); } // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template void getHTSized(X &x) { static_assert(sizeof(X) == size); getHT(x); } template void skipHTSized() { static_assert(sizeof(T) == size); skipHT(); } // Read a string by the given name if it is the next record. std::string getHNOString(NAME name); void skipHNOString(NAME name); // Read a string with the given sub-record name std::string getHNString(NAME name); // Read a string, including the sub-record header (but not the name) std::string getHString(); void skipHString(); // Read the given number of bytes from a subrecord void getHExact(void*p, int size); // Read the given number of bytes from a named subrecord void getHNExact(void*p, int size, NAME name); /************************************************************************* * * Low level sub-record methods * *************************************************************************/ // Get the next subrecord name and check if it matches the parameter void getSubNameIs(NAME name); /** Checks if the next sub record name matches the parameter. If it does, it is read into 'subName' just as if getSubName() was called. If not, the read name will still be available for future calls to getSubName(), isNextSub() and getSubNameIs(). */ bool isNextSub(NAME name); bool peekNextSub(NAME name); // Store the current subrecord name for the next call of getSubName() void cacheSubName() {mCtx.subCached = true; }; // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); // Skip current sub record, including header (but not including // name.) void skipHSub(); // Skip sub record and check its size void skipHSubSize(int size); // Skip all subrecords until the given subrecord or no more subrecords remaining void skipHSubUntil(NAME name); /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. */ void getSubHeader(); /************************************************************************* * * Low level record methods * *************************************************************************/ // Get the next record name NAME getRecName(); // Skip the rest of this record. Assumes the name and header have // already been read void skipRecord(); /* Read record header. This updatesleftFile BEYOND the data that follows the header, ie beyond the entire record. You should use leftRec to orient yourself inside the record itself. */ void getRecHeader() { getRecHeader(mRecordFlags); } void getRecHeader(uint32_t &flags); bool hasMoreRecs() const { return mCtx.leftFile > 0; } bool hasMoreSubs() const { return mCtx.leftRec > 0; } /************************************************************************* * * Lowest level data reading and misc methods * *************************************************************************/ template void getT(X &x) { getExact(&x, sizeof(X)); } template void skipT() { skip(sizeof(T)); } void getExact(void* x, int size) { mEsm->read((char*)x, size); } void getName(NAME &name) { getT(name); } void getUint(uint32_t &u) { getT(u); } // Read the next 'size' bytes and return them as a string. Converts // them from native encoding to UTF8 in the process. std::string getString(int size); void skip(std::size_t bytes) { char buffer[4096]; if (bytes > std::size(buffer)) mEsm->seekg(getFileOffset() + bytes); else mEsm->read(buffer, bytes); } /// Used for error handling [[noreturn]] void fail(const std::string &msg); /// Sets font encoder for ESM strings void setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; }; /// Get record flags of last record unsigned int getRecordFlags() { return mRecordFlags; } size_t getFileSize() const { return mFileSize; } private: [[noreturn]] void reportSubSizeMismatch(size_t want, size_t got) { fail("record size mismatch, requested " + std::to_string(want) + ", got" + std::to_string(got)); } void clearCtx(); std::unique_ptr mEsm; ESM_Context mCtx; unsigned int mRecordFlags; // Special file signifier (see SpecialFile enum above) // Buffer for ESM strings std::vector mBuffer; Header mHeader; ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; }; } #endif openmw-openmw-0.48.0/components/esm3/esmwriter.cpp000066400000000000000000000117251445372753700222230ustar00rootroot00000000000000#include "esmwriter.hpp" #include #include #include #include namespace ESM { ESMWriter::ESMWriter() : mRecords() , mStream(nullptr) , mHeaderPos() , mEncoder(nullptr) , mRecordCount(0) , mCounting(true) , mHeader() {} unsigned int ESMWriter::getVersion() const { return mHeader.mData.version; } void ESMWriter::setVersion(unsigned int ver) { mHeader.mData.version = ver; } void ESMWriter::setType(int type) { mHeader.mData.type = type; } void ESMWriter::setAuthor(const std::string& auth) { mHeader.mData.author.assign (auth); } void ESMWriter::setDescription(const std::string& desc) { mHeader.mData.desc.assign (desc); } void ESMWriter::setRecordCount (int count) { mHeader.mData.records = count; } void ESMWriter::setFormat (int format) { mHeader.mFormat = format; } void ESMWriter::clearMaster() { mHeader.mMaster.clear(); } void ESMWriter::addMaster(const std::string& name, uint64_t size) { Header::MasterData d; d.name = name; d.size = size; mHeader.mMaster.push_back(d); } void ESMWriter::save(std::ostream& file) { mRecordCount = 0; mRecords.clear(); mCounting = true; mStream = &file; startRecord("TES3", 0); mHeader.save (*this); endRecord("TES3"); } void ESMWriter::close() { if (!mRecords.empty()) throw std::runtime_error ("Unclosed record remaining"); } void ESMWriter::startRecord(NAME name, uint32_t flags) { mRecordCount++; writeName(name); RecordData rec; rec.name = name; rec.position = mStream->tellp(); rec.size = 0; writeT(0); // Size goes here writeT(0); // Unused header? writeT(flags); mRecords.push_back(rec); assert(mRecords.back().size == 0); } void ESMWriter::startRecord (uint32_t name, uint32_t flags) { startRecord(NAME(name), flags); } void ESMWriter::startSubRecord(NAME name) { // Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. assert (mRecords.size() <= 1); writeName(name); RecordData rec; rec.name = name; rec.position = mStream->tellp(); rec.size = 0; writeT(0); // Size goes here mRecords.push_back(rec); assert(mRecords.back().size == 0); } void ESMWriter::endRecord(NAME name) { RecordData rec = mRecords.back(); assert(rec.name == name); mRecords.pop_back(); mStream->seekp(rec.position); mCounting = false; write (reinterpret_cast (&rec.size), sizeof(uint32_t)); mCounting = true; mStream->seekp(0, std::ios::end); } void ESMWriter::endRecord (uint32_t name) { endRecord(NAME(name)); } void ESMWriter::writeHNString(NAME name, const std::string& data) { startSubRecord(name); writeHString(data); endRecord(name); } void ESMWriter::writeHNString(NAME name, const std::string& data, size_t size) { assert(data.size() <= size); startSubRecord(name); writeHString(data); if (data.size() < size) { for (size_t i = data.size(); i < size; ++i) write("\0",1); } endRecord(name); } void ESMWriter::writeFixedSizeString(const std::string& data, int size) { std::string string; if (!data.empty()) string = mEncoder ? mEncoder->getLegacyEnc(data) : data; string.resize(size); write(string.c_str(), string.size()); } void ESMWriter::writeHString(const std::string& data) { if (data.size() == 0) write("\0", 1); else { // Convert to UTF8 and return const std::string_view string = mEncoder != nullptr ? mEncoder->getLegacyEnc(data) : data; write(string.data(), string.size()); } } void ESMWriter::writeHCString(const std::string& data) { writeHString(data); if (data.size() > 0 && data[data.size()-1] != '\0') write("\0", 1); } void ESMWriter::writeName(NAME name) { write(name.mData, NAME::sCapacity); } void ESMWriter::write(const char* data, size_t size) { if (mCounting && !mRecords.empty()) { for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) it->size += static_cast(size); } mStream->write(data, size); } void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } } openmw-openmw-0.48.0/components/esm3/esmwriter.hpp000066400000000000000000000113171445372753700222250ustar00rootroot00000000000000#ifndef OPENMW_ESM_WRITER_H #define OPENMW_ESM_WRITER_H #include #include #include #include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" namespace ToUTF8 { class Utf8Encoder; } namespace ESM { class ESMWriter { struct RecordData { NAME name; std::streampos position; uint32_t size; }; public: ESMWriter(); unsigned int getVersion() const; // Set various header data (Header::Data). All of the below functions must be called before writing, // otherwise this data will be left uninitialized. void setVersion(unsigned int ver = 0x3fa66666); void setType(int type); void setEncoder(ToUTF8::Utf8Encoder *encoding); void setAuthor(const std::string& author); void setDescription(const std::string& desc); void setHeader(const Header& value) { mHeader = value; } // Set the record count for writing it in the file header void setRecordCount (int count); // Counts how many records we have actually written. // It is a good idea to compare this with the value you wrote into the header (setRecordCount) // It should be the record count you set + 1 (1 additional record for the TES3 header) int getRecordCount() { return mRecordCount; } void setFormat (int format); void clearMaster(); void addMaster(const std::string& name, uint64_t size); void save(std::ostream& file); ///< Start saving a file by writing the TES3 header. void close(); ///< \note Does not close the stream. void writeHNString(NAME name, const std::string& data); void writeHNString(NAME name, const std::string& data, size_t size); void writeHNCString(NAME name, const std::string& data) { startSubRecord(name); writeHCString(data); endRecord(name); } void writeHNOString(NAME name, const std::string& data) { if (!data.empty()) writeHNString(name, data); } void writeHNOCString(NAME name, const std::string& data) { if (!data.empty()) writeHNCString(name, data); } template void writeHNT(NAME name, const T& data) { startSubRecord(name); writeT(data); endRecord(name); } template void writeHNT(NAME name, const T (&data)[size]) { startSubRecord(name); writeT(data); endRecord(name); } // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. void writeHNT(NAME name, const std::string& data) = delete; void writeT(NAME data) = delete; template void writeHNT(NAME name, const T (&data)[size], int) = delete; template void writeHNT(NAME name, const T& data, int size) { startSubRecord(name); writeT(data, size); endRecord(name); } template void writeT(const T& data) { static_assert(!std::is_pointer_v); write(reinterpret_cast(&data), sizeof(T)); } template void writeT(const T (&data)[size]) { write(reinterpret_cast(data), size * sizeof(T)); } template void writeT(const T& data, size_t size) { static_assert(!std::is_pointer_v); write((char*)&data, size); } void startRecord(NAME name, uint32_t flags = 0); void startRecord(uint32_t name, uint32_t flags = 0); /// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. void startSubRecord(NAME name); void endRecord(NAME name); void endRecord(uint32_t name); void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); void writeName(NAME data); void write(const char* data, size_t size); private: std::list mRecords; std::ostream* mStream; std::streampos mHeaderPos; ToUTF8::Utf8Encoder* mEncoder; int mRecordCount; bool mCounting; Header mHeader; }; } #endif openmw-openmw-0.48.0/components/esm3/filter.cpp000066400000000000000000000023461445372753700214660ustar00rootroot00000000000000#include "filter.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Filter::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().toInt(); switch (name) { case SREC_NAME: mId = esm.getHString(); break; case fourCC("FILT"): mFilter = esm.getHString(); break; case fourCC("DESC"): mDescription = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void Filter::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } void Filter::blank() { mRecordFlags = 0; mFilter.clear(); mDescription.clear(); } } openmw-openmw-0.48.0/components/esm3/filter.hpp000066400000000000000000000011461445372753700214700ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_FILTER_H #define COMPONENTS_ESM_FILTER_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Filter { constexpr static RecNameInts sRecordId = REC_FILT; unsigned int mRecordFlags; std::string mId; std::string mDescription; std::string mFilter; void load (ESMReader& esm, bool &isDeleted); void save (ESMWriter& esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/fogstate.cpp000066400000000000000000000050401445372753700220070ustar00rootroot00000000000000#include "fogstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include #include #include #include "savedgame.hpp" namespace ESM { namespace { void convertFogOfWar(std::vector& imageData) { if (imageData.empty()) { return; } osgDB::ReaderWriter* tgaReader = osgDB::Registry::instance()->getReaderWriterForExtension("tga"); if (!tgaReader) { Log(Debug::Error) << "Error: Unable to load fog, can't find a tga ReaderWriter"; return; } Files::IMemStream in(&imageData[0], imageData.size()); osgDB::ReaderWriter::ReadResult result = tgaReader->readImage(in); if (!result.success()) { Log(Debug::Error) << "Error: Failed to read fog: " << result.message() << " code " << result.status(); return; } osgDB::ReaderWriter* pngWriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!pngWriter) { Log(Debug::Error) << "Error: Unable to write fog, can't find a png ReaderWriter"; return; } std::ostringstream ostream; osgDB::ReaderWriter::WriteResult png = pngWriter->writeImage(*result.getImage(), ostream); if (!png.success()) { Log(Debug::Error) << "Error: Unable to write fog: " << png.message() << " code " << png.status(); return; } std::string str = ostream.str(); imageData = std::vector(str.begin(), str.end()); } } void FogState::load (ESMReader &esm) { esm.getHNOT(mBounds, "BOUN"); esm.getHNOT(mNorthMarkerAngle, "ANGL"); int dataFormat = esm.getFormat(); while (esm.isNextSub("FTEX")) { esm.getSubHeader(); FogTexture tex; esm.getT(tex.mX); esm.getT(tex.mY); size_t imageSize = esm.getSubSize()-sizeof(int)*2; tex.mImageData.resize(imageSize); esm.getExact(&tex.mImageData[0], imageSize); if (dataFormat < 7) convertFogOfWar(tex.mImageData); mFogTextures.push_back(tex); } } void FogState::save (ESMWriter &esm, bool interiorCell) const { if (interiorCell) { esm.writeHNT("BOUN", mBounds); esm.writeHNT("ANGL", mNorthMarkerAngle); } for (std::vector::const_iterator it = mFogTextures.begin(); it != mFogTextures.end(); ++it) { esm.startSubRecord("FTEX"); esm.writeT(it->mX); esm.writeT(it->mY); esm.write(&it->mImageData[0], it->mImageData.size()); esm.endRecord("FTEX"); } } } openmw-openmw-0.48.0/components/esm3/fogstate.hpp000066400000000000000000000013371445372753700220210ustar00rootroot00000000000000#ifndef OPENMW_ESM_FOGSTATE_H #define OPENMW_ESM_FOGSTATE_H #include namespace ESM { class ESMReader; class ESMWriter; struct FogTexture { int mX, mY; // Only used for interior cells std::vector mImageData; }; // format 0, saved games only // Fog of war state struct FogState { // Only used for interior cells float mNorthMarkerAngle; struct Bounds { float mMinX; float mMinY; float mMaxX; float mMaxY; } mBounds; std::vector mFogTextures; void load (ESMReader &esm); void save (ESMWriter &esm, bool interiorCell) const; }; } #endif openmw-openmw-0.48.0/components/esm3/globalmap.cpp000066400000000000000000000016531445372753700221370ustar00rootroot00000000000000#include "globalmap.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void GlobalMap::load (ESMReader &esm) { esm.getHNT(mBounds, "BNDS"); esm.getSubNameIs("DATA"); esm.getSubHeader(); mImageData.resize(esm.getSubSize()); esm.getExact(&mImageData[0], mImageData.size()); while (esm.isNextSub("MRK_")) { esm.getSubHeader(); CellId cell; esm.getT(cell.first); esm.getT(cell.second); mMarkers.insert(cell); } } void GlobalMap::save (ESMWriter &esm) const { esm.writeHNT("BNDS", mBounds); esm.startSubRecord("DATA"); esm.write(&mImageData[0], mImageData.size()); esm.endRecord("DATA"); for (std::set::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) { esm.startSubRecord("MRK_"); esm.writeT(it->first); esm.writeT(it->second); esm.endRecord("MRK_"); } } } openmw-openmw-0.48.0/components/esm3/globalmap.hpp000066400000000000000000000014271445372753700221430ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H #define OPENMW_COMPONENTS_ESM_GLOBALMAP_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only ///< \brief An image containing the explored areas on the global map. struct GlobalMap { constexpr static RecNameInts sRecordId = REC_GMAP; // The minimum and maximum cell coordinates struct Bounds { int mMinX, mMaxX, mMinY, mMaxY; }; Bounds mBounds; std::vector mImageData; typedef std::pair CellId; std::set mMarkers; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/globalscript.cpp000066400000000000000000000013561445372753700226660ustar00rootroot00000000000000#include "globalscript.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void GlobalScript::load (ESMReader &esm) { mId = esm.getHNString ("NAME"); mLocals.load (esm); mRunning = 0; esm.getHNOT (mRunning, "RUN_"); mTargetRef.unset(); mTargetId = esm.getHNOString ("TARG"); if (esm.peekNextSub("FRMR")) mTargetRef.load(esm, true, "FRMR"); } void GlobalScript::save (ESMWriter &esm) const { esm.writeHNString ("NAME", mId); mLocals.save (esm); if (mRunning) esm.writeHNT ("RUN_", mRunning); if (!mTargetId.empty()) { esm.writeHNOString ("TARG", mTargetId); if (mTargetRef.isSet()) mTargetRef.save (esm, true, "FRMR"); } } } openmw-openmw-0.48.0/components/esm3/globalscript.hpp000066400000000000000000000010661445372753700226710ustar00rootroot00000000000000#ifndef OPENMW_ESM_GLOBALSCRIPT_H #define OPENMW_ESM_GLOBALSCRIPT_H #include "locals.hpp" #include "cellref.hpp" namespace ESM { class ESMReader; class ESMWriter; /// \brief Storage structure for global script state (only used in saved games) struct GlobalScript { std::string mId; /// \note must be lowercase Locals mLocals; int mRunning; std::string mTargetId; // for targeted scripts RefNum mTargetRef; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/inventorystate.cpp000066400000000000000000000110711445372753700232720ustar00rootroot00000000000000#include "inventorystate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include namespace ESM { void InventoryState::load (ESMReader &esm) { // obsolete int index = 0; while (esm.isNextSub ("IOBJ")) { int unused; // no longer used esm.getHT(unused); ObjectState state; // obsolete if (esm.isNextSub("SLOT")) { int slot; esm.getHT(slot); mEquipmentSlots[index] = slot; } state.mRef.loadId(esm, true); state.load (esm); if (state.mCount == 0) continue; mItems.push_back (state); ++index; } int itemsCount = 0; esm.getHNOT(itemsCount, "ICNT"); for (int i = 0; i < itemsCount; i++) { ObjectState state; state.mRef.loadId(esm, true); state.load (esm); if (state.mCount == 0) continue; mItems.push_back (state); } //Next item is Levelled item while (esm.isNextSub("LEVM")) { //Get its name std::string id = esm.getHString(); int count; std::string parentGroup; //Then get its count esm.getHNT (count, "COUN"); //Old save formats don't have information about parent group; check for that if(esm.isNextSub("LGRP")) //Newest saves contain parent group parentGroup = esm.getHString(); mLevelledItemMap[std::make_pair(id, parentGroup)] = count; } while (esm.isNextSub("MAGI")) { std::string id = esm.getHString(); std::vector > params; while (esm.isNextSub("RAND")) { float rand, multiplier; esm.getHT (rand); esm.getHNT (multiplier, "MULT"); params.emplace_back(rand, multiplier); } mPermanentMagicEffectMagnitudes[id] = params; } while (esm.isNextSub("EQUI")) { esm.getSubHeader(); int equipIndex; esm.getT(equipIndex); int slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } if (esm.isNextSub("EQIP")) { esm.getSubHeader(); int slotsCount = 0; esm.getT(slotsCount); for (int i = 0; i < slotsCount; i++) { int equipIndex; esm.getT(equipIndex); int slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } } mSelectedEnchantItem = -1; esm.getHNOT(mSelectedEnchantItem, "SELE"); // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities for(const auto& entry : mLevelledItemMap) { const std::string& id = entry.first.first; const int count = entry.second; for(auto& item : mItems) { if(item.mCount == count && Misc::StringUtils::ciEqual(id, item.mRef.mRefID)) item.mCount = -count; } } } void InventoryState::save (ESMWriter &esm) const { int itemsCount = static_cast(mItems.size()); if (itemsCount > 0) { esm.writeHNT ("ICNT", itemsCount); for (const ObjectState& state : mItems) { state.save (esm, true); } } for (std::map, int>::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) { esm.writeHNString ("LEVM", it->first.first); esm.writeHNT ("COUN", it->second); esm.writeHNString("LGRP", it->first.second); } for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) { esm.writeHNString("MAGI", it->first); const std::vector >& params = it->second; for (std::vector >::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt) { esm.writeHNT ("RAND", pIt->first); esm.writeHNT ("MULT", pIt->second); } } int slotsCount = static_cast(mEquipmentSlots.size()); if (slotsCount > 0) { esm.startSubRecord("EQIP"); esm.writeT(slotsCount); for (std::map::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it) { esm.writeT(it->first); esm.writeT(it->second); } esm.endRecord("EQIP"); } if (mSelectedEnchantItem != -1) esm.writeHNT ("SELE", mSelectedEnchantItem); } } openmw-openmw-0.48.0/components/esm3/inventorystate.hpp000066400000000000000000000016351445372753700233040ustar00rootroot00000000000000#ifndef OPENMW_ESM_INVENTORYSTATE_H #define OPENMW_ESM_INVENTORYSTATE_H #include #include "objectstate.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only /// \brief State for inventories and containers struct InventoryState { std::vector mItems; // std::map mEquipmentSlots; std::map, int> mLevelledItemMap; typedef std::map > > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; int mSelectedEnchantItem; // For inventories only InventoryState() : mSelectedEnchantItem(-1) {} virtual ~InventoryState() {} virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/journalentry.cpp000066400000000000000000000017161445372753700227350ustar00rootroot00000000000000#include "journalentry.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void JournalEntry::load (ESMReader &esm) { esm.getHNOT (mType, "JETY"); mTopic = esm.getHNString ("YETO"); mInfo = esm.getHNString ("YEIN"); mText = esm.getHNString ("TEXT"); if (mType==Type_Journal) { esm.getHNT (mDay, "JEDA"); esm.getHNT (mMonth, "JEMO"); esm.getHNT (mDayOfMonth, "JEDM"); } else if (mType==Type_Topic) mActorName = esm.getHNOString("ACT_"); } void JournalEntry::save (ESMWriter &esm) const { esm.writeHNT ("JETY", mType); esm.writeHNString ("YETO", mTopic); esm.writeHNString ("YEIN", mInfo); esm.writeHNString ("TEXT", mText); if (mType==Type_Journal) { esm.writeHNT ("JEDA", mDay); esm.writeHNT ("JEMO", mMonth); esm.writeHNT ("JEDM", mDayOfMonth); } else if (mType==Type_Topic) esm.writeHNString ("ACT_", mActorName); } } openmw-openmw-0.48.0/components/esm3/journalentry.hpp000066400000000000000000000013501445372753700227340ustar00rootroot00000000000000#ifndef OPENMW_ESM_JOURNALENTRY_H #define OPENMW_ESM_JOURNALENTRY_H #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct JournalEntry { enum Type { Type_Journal = 0, Type_Topic = 1, Type_Quest = 2 }; int mType; std::string mTopic; std::string mInfo; std::string mText; std::string mActorName; // Could also be Actor ID to allow switching of localisation, but since mText is plaintext anyway... int mDay; // time stamp int mMonth; int mDayOfMonth; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/loadacti.cpp000066400000000000000000000032501445372753700217540ustar00rootroot00000000000000#include "loadacti.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Activator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); } void Activator::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); } void Activator::blank() { mRecordFlags = 0; mName.clear(); mScript.clear(); mModel.clear(); } } openmw-openmw-0.48.0/components/esm3/loadacti.hpp000066400000000000000000000012601445372753700217600ustar00rootroot00000000000000#ifndef OPENMW_ESM_ACTI_H #define OPENMW_ESM_ACTI_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Activator { constexpr static RecNameInts sRecordId = REC_ACTI; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Activator"; } unsigned int mRecordFlags; std::string mId, mName, mScript, mModel; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadalch.cpp000066400000000000000000000046651445372753700217560ustar00rootroot00000000000000#include "loadalch.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Potion::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("TEXT"): // not ITEX here for some reason mIcon = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("ALDT"): esm.getHTSized<12>(mData); hasData = true; break; case fourCC("ENAM"): mEffects.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing ALDT subrecord"); } void Potion::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("FNAM", mName); esm.writeHNT("ALDT", mData, 12); mEffects.save(esm); } void Potion::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mAutoCalc = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); mEffects.mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadalch.hpp000066400000000000000000000016101445372753700217460ustar00rootroot00000000000000#ifndef OPENMW_ESM_ALCH_H #define OPENMW_ESM_ALCH_H #include #include "effectlist.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Alchemy item (potions) */ struct Potion { constexpr static RecNameInts sRecordId = REC_ALCH; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Potion"; } struct ALDTstruct { float mWeight; int mValue; int mAutoCalc; }; ALDTstruct mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadappa.cpp000066400000000000000000000043471445372753700217650ustar00rootroot00000000000000#include "loadappa.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Apparatus::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("AADT"): esm.getHT(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing AADT subrecord"); } void Apparatus::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); esm.writeHNT("AADT", mData, 16); esm.writeHNOCString("SCRI", mScript); esm.writeHNCString("ITEX", mIcon); } void Apparatus::blank() { mRecordFlags = 0; mData.mType = 0; mData.mQuality = 0; mData.mWeight = 0; mData.mValue = 0; mModel.clear(); mIcon.clear(); mScript.clear(); mName.clear(); } } openmw-openmw-0.48.0/components/esm3/loadappa.hpp000066400000000000000000000017401445372753700217640ustar00rootroot00000000000000#ifndef OPENMW_ESM_APPA_H #define OPENMW_ESM_APPA_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Alchemist apparatus */ struct Apparatus { constexpr static RecNameInts sRecordId = REC_APPA; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Apparatus"; } enum AppaType { MortarPestle = 0, Alembic = 1, Calcinator = 2, Retort = 3 }; struct AADTstruct { int mType; float mQuality; float mWeight; int mValue; }; AADTstruct mData; unsigned int mRecordFlags; std::string mId, mModel, mIcon, mScript, mName; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadarmo.cpp000066400000000000000000000066161445372753700220030ustar00rootroot00000000000000#include "loadarmo.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void PartReferenceList::add(ESMReader &esm) { PartReference pr; esm.getHT(pr.mPart); // The INDX byte pr.mMale = esm.getHNOString("BNAM"); pr.mFemale = esm.getHNOString("CNAM"); mParts.push_back(pr); } void PartReferenceList::load(ESMReader &esm) { mParts.clear(); while (esm.isNextSub("INDX")) { add(esm); } } void PartReferenceList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mParts.begin(); it != mParts.end(); ++it) { esm.writeHNT("INDX", it->mPart); esm.writeHNOString("BNAM", it->mMale); esm.writeHNOString("CNAM", it->mFemale); } } void Armor::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mParts.mParts.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("AODT"): esm.getHTSized<24>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case fourCC("ENAM"): mEnchant = esm.getHString(); break; case fourCC("INDX"): mParts.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing AODT subrecord"); } void Armor::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNT("AODT", mData, 24); esm.writeHNOCString("ITEX", mIcon); mParts.save(esm); esm.writeHNOCString("ENAM", mEnchant); } void Armor::blank() { mRecordFlags = 0; mData.mType = 0; mData.mWeight = 0; mData.mValue = 0; mData.mHealth = 0; mData.mEnchant = 0; mData.mArmor = 0; mParts.mParts.clear(); mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); mEnchant.clear(); } } openmw-openmw-0.48.0/components/esm3/loadarmo.hpp000066400000000000000000000043551445372753700220060ustar00rootroot00000000000000#ifndef OPENMW_ESM_ARMO_H #define OPENMW_ESM_ARMO_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; enum PartReferenceType { PRT_Head = 0, PRT_Hair = 1, PRT_Neck = 2, PRT_Cuirass = 3, PRT_Groin = 4, PRT_Skirt = 5, PRT_RHand = 6, PRT_LHand = 7, PRT_RWrist = 8, PRT_LWrist = 9, PRT_Shield = 10, PRT_RForearm = 11, PRT_LForearm = 12, PRT_RUpperarm = 13, PRT_LUpperarm = 14, PRT_RFoot = 15, PRT_LFoot = 16, PRT_RAnkle = 17, PRT_LAnkle = 18, PRT_RKnee = 19, PRT_LKnee = 20, PRT_RLeg = 21, PRT_LLeg = 22, PRT_RPauldron = 23, PRT_LPauldron = 24, PRT_Weapon = 25, PRT_Tail = 26, PRT_Count = 27 }; // Reference to body parts struct PartReference { unsigned char mPart; // possible values [0, 26] std::string mMale, mFemale; }; // A list of references to body parts struct PartReferenceList { std::vector mParts; /// Load one part, assumes the subrecord name was already read void add(ESMReader &esm); /// TODO: remove this method. The ESM format does not guarantee that all Part subrecords follow one another. void load(ESMReader &esm); void save(ESMWriter &esm) const; }; struct Armor { constexpr static RecNameInts sRecordId = REC_ARMO; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Armor"; } enum Type { Helmet = 0, Cuirass = 1, LPauldron = 2, RPauldron = 3, Greaves = 4, Boots = 5, LGauntlet = 6, RGauntlet = 7, Shield = 8, LBracer = 9, RBracer = 10 }; struct AODTstruct { int mType; float mWeight; int mValue, mHealth, mEnchant, mArmor; }; AODTstruct mData; PartReferenceList mParts; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript, mEnchant; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadbody.cpp000066400000000000000000000036041445372753700217740ustar00rootroot00000000000000#include "loadbody.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void BodyPart::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mRace = esm.getHString(); break; case fourCC("BYDT"): esm.getHTSized<4>(mData); hasData = true; break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing BYDT subrecord"); } void BodyPart::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mRace); esm.writeHNT("BYDT", mData, 4); } void BodyPart::blank() { mRecordFlags = 0; mData.mPart = 0; mData.mVampire = 0; mData.mFlags = 0; mData.mType = 0; mModel.clear(); mRace.clear(); } } openmw-openmw-0.48.0/components/esm3/loadbody.hpp000066400000000000000000000026711445372753700220040ustar00rootroot00000000000000#ifndef OPENMW_ESM_BODY_H #define OPENMW_ESM_BODY_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct BodyPart { constexpr static RecNameInts sRecordId = REC_BODY; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "BodyPart"; } enum MeshPart { MP_Head = 0, MP_Hair = 1, MP_Neck = 2, MP_Chest = 3, MP_Groin = 4, MP_Hand = 5, MP_Wrist = 6, MP_Forearm = 7, MP_Upperarm = 8, MP_Foot = 9, MP_Ankle = 10, MP_Knee = 11, MP_Upperleg = 12, MP_Clavicle = 13, MP_Tail = 14, MP_Count = 15 }; enum Flags { BPF_Female = 1, BPF_NotPlayable = 2 }; enum MeshType { MT_Skin = 0, MT_Clothing = 1, MT_Armor = 2 }; struct BYDTstruct { unsigned char mPart; // mesh part unsigned char mVampire; // boolean unsigned char mFlags; unsigned char mType; // mesh type }; BYDTstruct mData; unsigned int mRecordFlags; std::string mId, mModel, mRace; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadbook.cpp000066400000000000000000000051541445372753700217730ustar00rootroot00000000000000#include "loadbook.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Book::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("BKDT"): esm.getHTSized<20>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case fourCC("ENAM"): mEnchant = esm.getHString(); break; case fourCC("TEXT"): mText = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing BKDT subrecord"); } void Book::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("BKDT", mData, 20); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOString("TEXT", mText); esm.writeHNOCString("ENAM", mEnchant); } void Book::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mIsScroll = 0; mData.mSkillId = 0; mData.mEnchant = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); mEnchant.clear(); mText.clear(); } } openmw-openmw-0.48.0/components/esm3/loadbook.hpp000066400000000000000000000016011445372753700217710ustar00rootroot00000000000000#ifndef OPENMW_ESM_BOOK_H #define OPENMW_ESM_BOOK_H #include #include "components/esm/defs.hpp" namespace ESM { /* * Books, magic scrolls, notes and so on */ class ESMReader; class ESMWriter; struct Book { constexpr static RecNameInts sRecordId = REC_BOOK; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Book"; } struct BKDTstruct { float mWeight; int mValue, mIsScroll, mSkillId, mEnchant; }; BKDTstruct mData; std::string mName, mModel, mIcon, mScript, mEnchant, mText; unsigned int mRecordFlags; std::string mId; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadbsgn.cpp000066400000000000000000000036001445372753700217640ustar00rootroot00000000000000#include "loadbsgn.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void BirthSign::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("TNAM"): mTexture = esm.getHString(); break; case fourCC("DESC"): mDescription = esm.getHString(); break; case fourCC("NPCS"): mPowers.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); } void BirthSign::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); mPowers.save(esm); } void BirthSign::blank() { mRecordFlags = 0; mName.clear(); mDescription.clear(); mTexture.clear(); mPowers.mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadbsgn.hpp000066400000000000000000000014621445372753700217750ustar00rootroot00000000000000#ifndef OPENMW_ESM_BSGN_H #define OPENMW_ESM_BSGN_H #include #include "spelllist.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct BirthSign { constexpr static RecNameInts sRecordId = REC_BSGN; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "BirthSign"; } unsigned int mRecordFlags; std::string mId, mName, mDescription, mTexture; // List of powers and abilities that come with this birth sign. SpellList mPowers; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadcell.cpp000066400000000000000000000231171445372753700217570ustar00rootroot00000000000000#include "loadcell.hpp" #include #include #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "cellid.hpp" namespace ESM { namespace { ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum void adjustRefNum (RefNum& refNum, const ESMReader& reader) { unsigned int local = (refNum.mIndex & 0xff000000) >> 24; // If we have an index value that does not make sense, assume that it was an addition // by the present plugin (but a faulty one) if (local && local <= reader.getParentFileIndices().size()) { // If the most significant 8 bits are used, then this reference already exists. // In this case, do not spawn a new reference, but overwrite the old one. refNum.mIndex &= 0x00ffffff; // delete old plugin ID refNum.mContentFile = reader.getParentFileIndices()[local-1]; } else { // This is an addition by the present plugin. Set the corresponding plugin index. refNum.mContentFile = reader.getIndex(); } } } } namespace ESM { // Some overloaded compare operators. bool operator== (const MovedCellRef& ref, const RefNum& refNum) { return ref.mRefNum == refNum; } bool operator== (const CellRef& ref, const RefNum& refNum) { return ref.mRefNum == refNum; } void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) { loadNameAndData(esm, isDeleted); loadCell(esm, saveContext); } void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) { isDeleted = false; blank(); bool hasData = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mName = esm.getHString(); break; case fourCC("DATA"): esm.getHTSized<12>(mData); hasData = true; break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.cacheSubName(); isLoaded = true; break; } } if (!hasData) esm.fail("Missing DATA subrecord"); mCellId.mPaged = !(mData.mFlags & Interior); if (mCellId.mPaged) { mCellId.mWorldspace = CellId::sDefaultWorldspace; mCellId.mIndex.mX = mData.mX; mCellId.mIndex.mY = mData.mY; } else { mCellId.mWorldspace = Misc::StringUtils::lowerCase (mName); mCellId.mIndex.mX = 0; mCellId.mIndex.mY = 0; } } void Cell::loadCell(ESMReader &esm, bool saveContext) { bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("INTV"): int waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; break; case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); mWaterInt = false; if(!std::isfinite(waterLevel)) { if(!overriding) mWater = std::numeric_limits::max(); Log(Debug::Warning) << "Warning: Encountered invalid water level in cell " << mName << " defined in " << esm.getContext().filename; } else mWater = waterLevel; break; case fourCC("AMBI"): esm.getHT(mAmbi); mHasAmbi = true; break; case fourCC("RGNN"): mRegion = esm.getHString(); break; case fourCC("NAM5"): esm.getHT(mMapColor); break; case fourCC("NAM0"): esm.getHT(mRefNumCounter); break; default: esm.cacheSubName(); isLoaded = true; break; } } if (saveContext) { mContextList.push_back(esm.getContext()); esm.skipRecord(); } } void Cell::postLoad(ESMReader &esm) { // Save position of the cell references and move on mContextList.push_back(esm.getContext()); esm.skipRecord(); } void Cell::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mName); esm.writeHNT("DATA", mData, 12); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (mData.mFlags & Interior) { if (mWaterInt) { int water = (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); esm.writeHNT("INTV", water); } else { esm.writeHNT("WHGT", mWater); } if (mData.mFlags & QuasiEx) esm.writeHNOCString("RGNN", mRegion); else { // Try to avoid saving ambient lighting information when it's unnecessary. // This is to fix black lighting in resaved cell records that lack this information. if (mHasAmbi) esm.writeHNT("AMBI", mAmbi, 16); } } else { esm.writeHNOCString("RGNN", mRegion); if (mMapColor != 0) esm.writeHNT("NAM5", mMapColor); } } void Cell::saveTempMarker(ESMWriter &esm, int tempCount) const { if (tempCount != 0) esm.writeHNT("NAM0", tempCount); } void Cell::restore(ESMReader &esm, int iCtx) const { esm.restoreContext(mContextList.at (iCtx)); } std::string Cell::getDescription() const { if (mData.mFlags & Interior) return mName; std::string cellGrid = "(" + std::to_string(mData.mX) + ", " + std::to_string(mData.mY) + ")"; if (!mName.empty()) return mName + ' ' + cellGrid; // FIXME: should use sDefaultCellname GMST instead, but it's not available in this scope std::string region = !mRegion.empty() ? mRegion : "Wilderness"; return region + ' ' + cellGrid; } bool Cell::getNextRef(ESMReader& esm, CellRef& ref, bool& isDeleted) { isDeleted = false; // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) return false; // MVRF are FRMR are present in pairs. MVRF indicates that following FRMR describes moved CellRef. // This function has to skip all moved CellRefs therefore read all such pairs to ignored values. while (esm.isNextSub("MVRF")) { MovedCellRef movedCellRef; esm.getHT(movedCellRef.mRefNum.mIndex); esm.getHNOT(movedCellRef.mTarget, "CNDT"); CellRef skippedCellRef; if (!esm.peekNextSub("FRMR")) return false; bool skippedDeleted; skippedCellRef.load(esm, skippedDeleted); } if (esm.peekNextSub("FRMR")) { ref.load (esm, isDeleted); // TODO: should count the number of temp refs and validate the number // Identify references belonging to a parent file and adapt the ID accordingly. adjustRefNum (ref.mRefNum, esm); return true; } return false; } bool Cell::getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved, GetNextRefMode mode) { deleted = false; moved = false; if (!esm.hasMoreSubs()) return false; if (esm.isNextSub("MVRF")) { moved = true; getNextMVRF(esm, movedCellRef); } if (!esm.peekNextSub("FRMR")) return false; if ((!moved && mode == GetNextRefMode::LoadOnlyMoved) || (moved && mode == GetNextRefMode::LoadOnlyNotMoved)) { skipLoadCellRef(esm); return true; } cellRef.load(esm, deleted); adjustRefNum(cellRef.mRefNum, esm); return true; } bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) { esm.getHT(mref.mRefNum.mIndex); esm.getHNOT(mref.mTarget, "CNDT"); adjustRefNum (mref.mRefNum, esm); return true; } void Cell::blank() { mName.clear(); mRegion.clear(); mWater = 0; mWaterInt = false; mMapColor = 0; mRefNumCounter = 0; mData.mFlags = 0; mData.mX = 0; mData.mY = 0; mHasAmbi = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; mAmbi.mFogDensity = 0; } const CellId& Cell::getCellId() const { return mCellId; } } openmw-openmw-0.48.0/components/esm3/loadcell.hpp000066400000000000000000000142741445372753700217700ustar00rootroot00000000000000#ifndef OPENMW_ESM_CELL_H #define OPENMW_ESM_CELL_H #include #include #include #include "components/esm/esmcommon.hpp" #include "components/esm/defs.hpp" #include "cellref.hpp" #include "cellid.hpp" namespace MWWorld { class ESMStore; } namespace ESM { class ESMReader; class ESMWriter; /* Moved cell reference tracking object. This mainly stores the target cell of the reference, so we can easily know where it has been moved when another plugin tries to move it independently. Unfortunately, we need to implement this here. */ class MovedCellRef { public: RefNum mRefNum; // Coordinates of target exterior cell int mTarget[2]; // The content file format does not support moving objects to an interior cell. // The save game format does support moving to interior cells, but uses a different mechanism // (see the MovedRefTracker implementation in MWWorld::CellStore for more details). }; /// Overloaded compare operator used to search inside a list of cell refs. bool operator==(const MovedCellRef& ref, const RefNum& refNum); bool operator==(const CellRef& ref, const RefNum& refNum); typedef std::list MovedCellRefTracker; typedef std::list > CellRefTracker; struct CellRefTrackerPredicate { RefNum mRefNum; CellRefTrackerPredicate(const RefNum& refNum) : mRefNum(refNum) {} bool operator() (const std::pair& refdelPair) { return refdelPair.first == mRefNum; } }; /* Cells hold data about objects, creatures, statics (rocks, walls, buildings) and landscape (for exterior cells). Cells frequently also has other associated LAND and PGRD records. Combined, all this data can be huge, and we cannot load it all at startup. Instead, the strategy we use is to remember the file position of each cell (using ESMReader::getContext()) and jumping back into place whenever we need to load a given cell. */ struct Cell { constexpr static RecNameInts sRecordId = REC_CELL; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Cell"; } enum class GetNextRefMode { LoadAll, LoadOnlyMoved, LoadOnlyNotMoved, }; enum Flags { Interior = 0x01, // Interior cell HasWater = 0x02, // Does this cell have a water surface NoSleep = 0x04, // Is it allowed to sleep here (without a bed) QuasiEx = 0x80 // Behave like exterior (Tribunal+), with // skybox and weather }; struct DATAstruct { int mFlags {0}; int mX {0}, mY {0}; }; struct AMBIstruct { Color mAmbient {0}, mSunlight {0}, mFog {0}; float mFogDensity {0.f}; }; Cell() : mName(""), mRegion(""), mHasAmbi(true), mWater(0), mWaterInt(false), mMapColor(0), mRefNumCounter(0) {} // Interior cells are indexed by this (it's the 'id'), for exterior // cells it is optional. std::string mName; // Optional region name for exterior and quasi-exterior cells. std::string mRegion; std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; CellId mCellId; AMBIstruct mAmbi; bool mHasAmbi; float mWater; // Water level bool mWaterInt; int mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. // as that would collide with refs when a content file is upgraded. int mRefNumCounter; // References "leased" from another cell (i.e. a different cell // introduced this ref, and it has been moved here by a plugin) CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references) void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references void save(ESMWriter &esm, bool isDeleted = false) const; void saveTempMarker(ESMWriter &esm, int tempCount) const; bool isExterior() const { return !(mData.mFlags & Interior); } int getGridX() const { return mData.mX; } int getGridY() const { return mData.mY; } bool hasWater() const { return ((mData.mFlags&HasWater) != 0) || isExterior(); } bool hasAmbient() const { return mHasAmbi; } void setHasAmbient(bool hasAmbi) { mHasAmbi = hasAmbi; } // Restore the given reader to the stored position. Will try to open // the file matching the stored file name. If you want to read from // somewhere other than the file system, you need to pre-open the // ESMReader, and the filename must match the stored filename // exactly. void restore(ESMReader &esm, int iCtx) const; std::string getDescription() const; ///< Return a short string describing the cell (mostly used for debugging/logging purpose) /* Get the next reference in this cell, if any. Returns false when there are no more references in the cell. All fields of the CellRef struct are overwritten. You can safely reuse one memory location without blanking it between calls. */ static bool getNextRef(ESMReader& esm, CellRef& ref, bool& deleted); static bool getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved, GetNextRefMode mode = GetNextRefMode::LoadAll); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. */ static bool getNextMVRF(ESMReader &esm, MovedCellRef &mref); void blank(); ///< Set record to default state (does not touch the ID/index). const CellId& getCellId() const; }; } #endif openmw-openmw-0.48.0/components/esm3/loadclas.cpp000066400000000000000000000056451445372753700217700ustar00rootroot00000000000000#include "loadclas.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { const Class::Specialization Class::sSpecializationIds[3] = { Class::Combat, Class::Magic, Class::Stealth }; const char *Class::sGmstSpecializationIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; int& Class::CLDTstruct::getSkill (int index, bool major) { if (index<0 || index>=5) throw std::logic_error ("skill index out of range"); return mSkills[index][major ? 1 : 0]; } int Class::CLDTstruct::getSkill (int index, bool major) const { if (index<0 || index>=5) throw std::logic_error ("skill index out of range"); return mSkills[index][major ? 1 : 0]; } void Class::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("CLDT"): esm.getHTSized<60>(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; break; case fourCC("DESC"): mDescription = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing CLDT subrecord"); } void Class::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); } void Class::blank() { mRecordFlags = 0; mName.clear(); mDescription.clear(); mData.mAttribute[0] = mData.mAttribute[1] = 0; mData.mSpecialization = 0; mData.mIsPlayable = 0; mData.mCalc = 0; for (int i=0; i<5; ++i) for (int i2=0; i2<2; ++i2) mData.mSkills[i][i2] = 0; } } openmw-openmw-0.48.0/components/esm3/loadclas.hpp000066400000000000000000000041461445372753700217700ustar00rootroot00000000000000#ifndef OPENMW_ESM_CLAS_H #define OPENMW_ESM_CLAS_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Character class definitions */ // These flags tells us which items should be auto-calculated for this // class struct Class { constexpr static RecNameInts sRecordId = REC_CLAS; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Class"; } enum AutoCalc { Weapon = 0x00001, Armor = 0x00002, Clothing = 0x00004, Books = 0x00008, Ingredient = 0x00010, Lockpick = 0x00020, Probe = 0x00040, Lights = 0x00080, Apparatus = 0x00100, Repair = 0x00200, Misc = 0x00400, Spells = 0x00800, MagicItems = 0x01000, Potions = 0x02000, Training = 0x04000, Spellmaking = 0x08000, Enchanting = 0x10000, RepairItem = 0x20000 }; enum Specialization { Combat = 0, Magic = 1, Stealth = 2 }; static const Specialization sSpecializationIds[3]; static const char *sGmstSpecializationIds[3]; struct CLDTstruct { int mAttribute[2]; // Attributes that get class bonus int mSpecialization; // 0 = Combat, 1 = Magic, 2 = Stealth int mSkills[5][2]; // Minor and major skills. int mIsPlayable; // 0x0001 - Playable class // I have no idea how to autocalculate these items... int mCalc; int& getSkill (int index, bool major); ///< Throws an exception for invalid values of \a index. int getSkill (int index, bool major) const; ///< Throws an exception for invalid values of \a index. }; // 60 bytes unsigned int mRecordFlags; std::string mId, mName, mDescription; CLDTstruct mData; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadclot.cpp000066400000000000000000000051521445372753700220000ustar00rootroot00000000000000#include "loadclot.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Clothing::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mParts.mParts.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("CTDT"): esm.getHTSized<12>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case fourCC("ENAM"): mEnchant = esm.getHString(); break; case fourCC("INDX"): mParts.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } void Clothing::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CTDT", mData, 12); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); mParts.save(esm); esm.writeHNOCString("ENAM", mEnchant); } void Clothing::blank() { mRecordFlags = 0; mData.mType = 0; mData.mWeight = 0; mData.mValue = 0; mData.mEnchant = 0; mParts.mParts.clear(); mName.clear(); mModel.clear(); mIcon.clear(); mEnchant.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadclot.hpp000066400000000000000000000022071445372753700220030ustar00rootroot00000000000000#ifndef OPENMW_ESM_CLOT_H #define OPENMW_ESM_CLOT_H #include #include "loadarmo.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Clothing */ struct Clothing { constexpr static RecNameInts sRecordId = REC_CLOT; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Clothing"; } enum Type { Pants = 0, Shoes = 1, Shirt = 2, Belt = 3, Robe = 4, RGlove = 5, LGlove = 6, Skirt = 7, Ring = 8, Amulet = 9 }; struct CTDTstruct { int mType; float mWeight; unsigned short mValue; unsigned short mEnchant; }; CTDTstruct mData; PartReferenceList mParts; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadcont.cpp000066400000000000000000000064461445372753700220110ustar00rootroot00000000000000#include "loadcont.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void InventoryList::add(ESMReader &esm) { esm.getSubHeader(); ContItem ci; esm.getT(ci.mCount); ci.mItem.assign(esm.getString(32)); mList.push_back(ci); } void InventoryList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.startSubRecord("NPCO"); esm.writeT(it->mCount); esm.writeFixedSizeString(it->mItem, 32); esm.endRecord("NPCO"); } } void Container::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mInventory.mList.clear(); bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("CNDT"): esm.getHTSized<4>(mWeight); hasWeight = true; break; case fourCC("FLAG"): esm.getHTSized<4>(mFlags); if (mFlags & 0xf4) esm.fail("Unknown flags"); if (!(mFlags & 0x8)) esm.fail("Flag 8 not set"); hasFlags = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("NPCO"): mInventory.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasWeight && !isDeleted) esm.fail("Missing CNDT subrecord"); if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } void Container::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CNDT", mWeight, 4); esm.writeHNT("FLAG", mFlags, 4); esm.writeHNOCString("SCRI", mScript); mInventory.save(esm); } void Container::blank() { mRecordFlags = 0; mName.clear(); mModel.clear(); mScript.clear(); mWeight = 0; mFlags = 0x8; // set default flag value mInventory.mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadcont.hpp000066400000000000000000000025011445372753700220020ustar00rootroot00000000000000#ifndef OPENMW_ESM_CONT_H #define OPENMW_ESM_CONT_H #include #include #include "components/esm/esmcommon.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Container definition */ struct ContItem { int mCount{0}; std::string mItem; }; /// InventoryList, NPCO subrecord struct InventoryList { std::vector mList; /// Load one item, assumes subrecord name is already read void add(ESMReader &esm); void save(ESMWriter &esm) const; }; struct Container { constexpr static RecNameInts sRecordId = REC_CONT; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Container"; } enum Flags { Organic = 1, // Objects cannot be placed in this container Respawn = 2, // Respawns after 4 months Unknown = 8 }; unsigned int mRecordFlags; std::string mId, mName, mModel, mScript; float mWeight; // Not sure, might be max total weight allowed? int mFlags; InventoryList mInventory; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadcrea.cpp000066400000000000000000000120411445372753700217440ustar00rootroot00000000000000#include "loadcrea.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Creature::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mAiPackage.mList.clear(); mInventory.mList.clear(); mSpells.mList.clear(); mTransport.mList.clear(); mScale = 1.f; mAiData.blank(); mAiData.mFight = 90; mAiData.mFlee = 20; bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("CNAM"): mOriginal = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("NPDT"): esm.getHTSized<96>(mData); hasNpdt = true; break; case fourCC("FLAG"): int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; case fourCC("XSCL"): esm.getHT(mScale); break; case fourCC("NPCO"): mInventory.add(esm); break; case fourCC("NPCS"): mSpells.add(esm); break; case fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; case fourCC("DODT"): case fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: case AI_Activate: case AI_Escort: case AI_Follow: case AI_Travel: case AI_CNDT: mAiPackage.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; case fourCC("INDX"): // seems to occur only in .ESS files, unsure of purpose int index; esm.getHT(index); Log(Debug::Warning) << "Creature::load: Unhandled INDX " << index; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } void Creature::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNT("NPDT", mData, 96); esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } mInventory.save(esm); mSpells.save(esm); esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); mTransport.save(esm); mAiPackage.save(esm); } void Creature::blank() { mRecordFlags = 0; mData.mType = 0; mData.mLevel = 0; mData.mStrength = mData.mIntelligence = mData.mWillpower = mData.mAgility = mData.mSpeed = mData.mEndurance = mData.mPersonality = mData.mLuck = 0; mData.mHealth = mData.mMana = mData.mFatigue = 0; mData.mSoul = 0; mData.mCombat = mData.mMagic = mData.mStealth = 0; for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mBloodType = 0; mFlags = 0; mScale = 1.f; mModel.clear(); mName.clear(); mScript.clear(); mOriginal.clear(); mInventory.mList.clear(); mSpells.mList.clear(); mAiData.blank(); mAiData.mFight = 90; mAiData.mFlee = 20; mAiPackage.mList.clear(); mTransport.mList.clear(); } const std::vector& Creature::getTransport() const { return mTransport.mList; } } openmw-openmw-0.48.0/components/esm3/loadcrea.hpp000066400000000000000000000050601445372753700217540ustar00rootroot00000000000000#ifndef OPENMW_ESM_CREA_H #define OPENMW_ESM_CREA_H #include #include "loadcont.hpp" #include "spelllist.hpp" #include "aipackage.hpp" #include "transport.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Creature definition * */ struct Creature { constexpr static RecNameInts sRecordId = REC_CREA; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Creature"; } // Default is 0x48? enum Flags { Bipedal = 0x01, Respawn = 0x02, Weapon = 0x04, // Has weapon and shield Base = 0x08, // This flag is set for every actor in Bethesda ESMs Swims = 0x10, Flies = 0x20, // Don't know what happens if several Walks = 0x40, // of these are set Essential = 0x80 }; enum Type { Creatures = 0, Daedra = 1, Undead = 2, Humanoid = 3 }; struct NPDTstruct { int mType; // For creatures we obviously have to use ints, not shorts and // bytes like we use for NPCs.... this file format just makes so // much sense! (Still, _much_ easier to decode than the NIFs.) int mLevel; int mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; int mHealth, mMana, mFatigue; // Stats int mSoul; // The creatures soul value (used with soul gems.) // Creatures have generalized combat, magic and stealth stats which substitute for // the specific skills (in the same way as specializations). int mCombat, mMagic, mStealth; int mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3 int mGold; }; // 96 byte NPDTstruct mData; int mBloodType; unsigned char mFlags; float mScale; unsigned int mRecordFlags; std::string mId, mModel, mName, mScript; std::string mOriginal; // Base creature that this is a modification of InventoryList mInventory; SpellList mSpells; AIData mAiData; AIPackageList mAiPackage; Transport mTransport; const std::vector& getTransport() const; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loaddial.cpp000066400000000000000000000065401445372753700217520ustar00rootroot00000000000000#include "loaddial.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Dialogue::load(ESMReader &esm, bool &isDeleted) { loadId(esm); loadData(esm, isDeleted); } void Dialogue::loadId(ESMReader &esm) { mId = esm.getHNString("NAME"); } void Dialogue::loadData(ESMReader &esm, bool &isDeleted) { isDeleted = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("DATA"): { esm.getSubHeader(); int size = esm.getSubSize(); if (size == 1) { esm.getT(mType); } else { esm.skip(size); } break; } case SREC_DELE: esm.skipHSub(); mType = Unknown; isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void Dialogue::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); } else { esm.writeHNT("DATA", mType); } } void Dialogue::blank() { mInfo.clear(); } void Dialogue::readInfo(ESMReader &esm, bool merge) { DialInfo info; bool isDeleted = false; info.load(esm, isDeleted); if (!merge || mInfo.empty()) { mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); return; } LookupMap::iterator lookup = mLookup.find(info.mId); if (lookup != mLookup.end()) { auto it = lookup->second.first; if (it->mPrev == info.mPrev) { *it = info; lookup->second.second = isDeleted; return; } // Since the new version of this record has a different prev linked list connection, we need to re-insert // the record mInfo.erase(it); mLookup.erase(lookup); } if (!info.mPrev.empty()) { lookup = mLookup.find(info.mPrev); if (lookup != mLookup.end()) { auto it = lookup->second.first; mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); } else mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); } else mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); } void Dialogue::clearDeletedInfos() { LookupMap::const_iterator current = mLookup.begin(); LookupMap::const_iterator end = mLookup.end(); for (; current != end; ++current) { if (current->second.second) { mInfo.erase(current->second.first); } } mLookup.clear(); } } openmw-openmw-0.48.0/components/esm3/loaddial.hpp000066400000000000000000000035251445372753700217570ustar00rootroot00000000000000#ifndef OPENMW_ESM_DIAL_H #define OPENMW_ESM_DIAL_H #include #include #include #include "components/esm/defs.hpp" #include "loadinfo.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Dialogue topic and journal entries. The actual data is contained in * the INFO records following the DIAL. */ struct Dialogue { constexpr static RecNameInts sRecordId = REC_DIAL; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Dialogue"; } enum Type { Topic = 0, Voice = 1, Greeting = 2, Persuasion = 3, Journal = 4, Unknown = -1 // Used for deleted dialogues }; std::string mId; signed char mType; typedef std::list InfoContainer; // Parameters: Info ID, (Info iterator, Deleted flag) typedef std::map > LookupMap; InfoContainer mInfo; // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; void load(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Dialogue record void loadId(ESMReader &esm); ///< Loads NAME sub-record of Dialogue record void loadData(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Dialogue record, except NAME sub-record void save(ESMWriter &esm, bool isDeleted = false) const; /// Remove all INFOs that are deleted void clearDeletedInfos(); /// Read the next info record /// @param merge Merge with existing list, or just push each record to the end of the list? void readInfo (ESMReader& esm, bool merge); void blank(); ///< Set record to default state (does not touch the ID and does not change the type). }; } #endif openmw-openmw-0.48.0/components/esm3/loaddoor.cpp000066400000000000000000000040351445372753700220010ustar00rootroot00000000000000#include "loaddoor.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Door::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("SNAM"): mOpenSound = esm.getHString(); break; case fourCC("ANAM"): mCloseSound = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); } void Door::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SNAM", mOpenSound); esm.writeHNOCString("ANAM", mCloseSound); } void Door::blank() { mRecordFlags = 0; mName.clear(); mModel.clear(); mScript.clear(); mOpenSound.clear(); mCloseSound.clear(); } } openmw-openmw-0.48.0/components/esm3/loaddoor.hpp000066400000000000000000000012771445372753700220130ustar00rootroot00000000000000#ifndef OPENMW_ESM_DOOR_H #define OPENMW_ESM_DOOR_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Door { constexpr static RecNameInts sRecordId = REC_DOOR; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Door"; } unsigned int mRecordFlags; std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadench.cpp000066400000000000000000000033551445372753700217570ustar00rootroot00000000000000#include "loadench.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Enchantment::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("ENDT"): esm.getHTSized<16>(mData); hasData = true; break; case fourCC("ENAM"): mEffects.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing ENDT subrecord"); } void Enchantment::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNT("ENDT", mData, 16); mEffects.save(esm); } void Enchantment::blank() { mRecordFlags = 0; mData.mType = 0; mData.mCost = 0; mData.mCharge = 0; mData.mFlags = 0; mEffects.mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadench.hpp000066400000000000000000000020411445372753700217530ustar00rootroot00000000000000#ifndef OPENMW_ESM_ENCH_H #define OPENMW_ESM_ENCH_H #include #include "components/esm/defs.hpp" #include "effectlist.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Enchantments */ struct Enchantment { constexpr static RecNameInts sRecordId = REC_ENCH; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Enchantment"; } enum Type { CastOnce = 0, WhenStrikes = 1, WhenUsed = 2, ConstantEffect = 3 }; enum Flags { Autocalc = 0x01 }; struct ENDTstruct { int mType; int mCost; int mCharge; int mFlags; }; unsigned int mRecordFlags; std::string mId; ENDTstruct mData; EffectList mEffects; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadfact.cpp000066400000000000000000000071671445372753700217640ustar00rootroot00000000000000#include "loadfact.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { int& Faction::FADTstruct::getSkill (int index, bool ignored) { if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; } int Faction::FADTstruct::getSkill (int index, bool ignored) const { if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; } void Faction::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mReactions.clear(); for (int i=0;i<10;++i) mRanks[i].clear(); int rankCounter = 0; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("RNAM"): if (rankCounter >= 10) esm.fail("Rank out of range"); mRanks[rankCounter++] = esm.getHString(); break; case fourCC("FADT"): esm.getHTSized<240>(mData); if (mData.mIsHidden > 1) esm.fail("Unknown flag!"); hasData = true; break; case fourCC("ANAM"): { std::string faction = esm.getHString(); int reaction; esm.getHNT(reaction, "INTV"); mReactions[faction] = reaction; break; } case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing FADT subrecord"); } void Faction::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); for (int i = 0; i < 10; i++) { if (mRanks[i].empty()) break; esm.writeHNString("RNAM", mRanks[i], 32); } esm.writeHNT("FADT", mData, 240); for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) { esm.writeHNString("ANAM", it->first); esm.writeHNT("INTV", it->second); } } void Faction::blank() { mRecordFlags = 0; mName.clear(); mData.mAttribute[0] = mData.mAttribute[1] = 0; mData.mIsHidden = 0; for (int i=0; i<10; ++i) { mData.mRankData[i].mAttribute1 = mData.mRankData[i].mAttribute2 = 0; mData.mRankData[i].mPrimarySkill = mData.mRankData[i].mFavouredSkill = 0; mData.mRankData[i].mFactReaction = 0; mRanks[i].clear(); } for (int i=0; i<7; ++i) mData.mSkills[i] = 0; mReactions.clear(); } } openmw-openmw-0.48.0/components/esm3/loadfact.hpp000066400000000000000000000035441445372753700217640ustar00rootroot00000000000000#ifndef OPENMW_ESM_FACT_H #define OPENMW_ESM_FACT_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Faction definitions */ // Requirements for each rank struct RankData { int mAttribute1, mAttribute2; // Attribute level // Skill level (faction skills given in // skillID below.) You need one skill at // level 'mPrimarySkill' and two skills at level // 'mFavouredSkill' to advance to this rank. int mPrimarySkill, mFavouredSkill; int mFactReaction; // Reaction from faction members }; struct Faction { constexpr static RecNameInts sRecordId = REC_FACT; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Faction"; } unsigned int mRecordFlags; std::string mId, mName; struct FADTstruct { // Which attributes we like int mAttribute[2]; RankData mRankData[10]; int mSkills[7]; // IDs of skills this faction require // Each element will either contain an Skill index, or -1. int mIsHidden; // 1 - hidden from player int& getSkill (int index, bool ignored = false); ///< Throws an exception for invalid values of \a index. int getSkill (int index, bool ignored = false) const; ///< Throws an exception for invalid values of \a index. }; // 240 bytes FADTstruct mData; // std::map mReactions; // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadglob.cpp000066400000000000000000000017621445372753700217650ustar00rootroot00000000000000#include "loadglob.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Global::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString ("NAME"); if (esm.isNextSub ("DELE")) { esm.skipHSub(); isDeleted = true; } else { mValue.read (esm, Variant::Format_Global); } } void Global::save (ESMWriter &esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); if (isDeleted) { esm.writeHNCString ("DELE", ""); } else { mValue.write (esm, Variant::Format_Global); } } void Global::blank() { mRecordFlags = 0; mValue.setType (VT_None); } bool operator== (const Global& left, const Global& right) { return left.mId==right.mId && left.mValue==right.mValue; } } openmw-openmw-0.48.0/components/esm3/loadglob.hpp000066400000000000000000000014361445372753700217700ustar00rootroot00000000000000#ifndef OPENMW_ESM_GLOB_H #define OPENMW_ESM_GLOB_H #include #include "variant.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Global script variables */ struct Global { constexpr static RecNameInts sRecordId = REC_GLOB; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Global"; } unsigned int mRecordFlags; std::string mId; Variant mValue; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; bool operator== (const Global& left, const Global& right); } #endif openmw-openmw-0.48.0/components/esm3/loadgmst.cpp000066400000000000000000000014461445372753700220130ustar00rootroot00000000000000#include "loadgmst.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void GameSetting::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString("NAME"); mValue.read (esm, Variant::Format_Gmst); } void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNCString("NAME", mId); mValue.write (esm, Variant::Format_Gmst); } void GameSetting::blank() { mRecordFlags = 0; mValue.setType (VT_None); } bool operator== (const GameSetting& left, const GameSetting& right) { return left.mValue==right.mValue; } } openmw-openmw-0.48.0/components/esm3/loadgmst.hpp000066400000000000000000000014601445372753700220140ustar00rootroot00000000000000#ifndef OPENMW_ESM_GMST_H #define OPENMW_ESM_GMST_H #include #include "variant.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Game setting * */ struct GameSetting { constexpr static RecNameInts sRecordId = REC_GMST; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "GameSetting"; } unsigned int mRecordFlags; std::string mId; Variant mValue; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; bool operator== (const GameSetting& left, const GameSetting& right); } #endif openmw-openmw-0.48.0/components/esm3/loadinfo.cpp000066400000000000000000000106141445372753700217710ustar00rootroot00000000000000#include "loadinfo.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void DialInfo::load(ESMReader &esm, bool &isDeleted) { mId = esm.getHNString("INAM"); isDeleted = false; mQuestStatus = QS_None; mFactionLess = false; mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("DATA"): esm.getHTSized<12>(mData); break; case fourCC("ONAM"): mActor = esm.getHString(); break; case fourCC("RNAM"): mRace = esm.getHString(); break; case fourCC("CNAM"): mClass = esm.getHString(); break; case fourCC("FNAM"): { mFaction = esm.getHString(); if (mFaction == "FFFF") { mFactionLess = true; } break; } case fourCC("ANAM"): mCell = esm.getHString(); break; case fourCC("DNAM"): mPcFaction = esm.getHString(); break; case fourCC("SNAM"): mSound = esm.getHString(); break; case SREC_NAME: mResponse = esm.getHString(); break; case fourCC("SCVR"): { SelectStruct ss; ss.mSelectRule = esm.getHString(); ss.mValue.read(esm, Variant::Format_Info); mSelects.push_back(ss); break; } case fourCC("BNAM"): mResultScript = esm.getHString(); break; case fourCC("QSTN"): mQuestStatus = QS_Name; esm.skipRecord(); break; case fourCC("QSTF"): mQuestStatus = QS_Finished; esm.skipRecord(); break; case fourCC("QSTR"): mQuestStatus = QS_Restart; esm.skipRecord(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } } void DialInfo::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNT("DATA", mData, 12); esm.writeHNOCString("ONAM", mActor); esm.writeHNOCString("RNAM", mRace); esm.writeHNOCString("CNAM", mClass); esm.writeHNOCString("FNAM", mFaction); esm.writeHNOCString("ANAM", mCell); esm.writeHNOCString("DNAM", mPcFaction); esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) { esm.writeHNString("SCVR", it->mSelectRule); it->mValue.write (esm, Variant::Format_Info); } esm.writeHNOString("BNAM", mResultScript); switch(mQuestStatus) { case QS_Name: esm.writeHNT("QSTN",'\1'); break; case QS_Finished: esm.writeHNT("QSTF", '\1'); break; case QS_Restart: esm.writeHNT("QSTR", '\1'); break; default: break; } } void DialInfo::blank() { mData = {}; mSelects.clear(); mPrev.clear(); mNext.clear(); mActor.clear(); mRace.clear(); mClass.clear(); mFaction.clear(); mPcFaction.clear(); mCell.clear(); mSound.clear(); mResponse.clear(); mResultScript.clear(); mFactionLess = false; mQuestStatus = QS_None; } } openmw-openmw-0.48.0/components/esm3/loadinfo.hpp000066400000000000000000000056471445372753700220100ustar00rootroot00000000000000#ifndef OPENMW_ESM_INFO_H #define OPENMW_ESM_INFO_H #include #include #include "components/esm/defs.hpp" #include "variant.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Dialogue information. A series of these follow after DIAL records, * and form a linked list of dialogue items. */ struct DialInfo { constexpr static RecNameInts sRecordId = REC_INFO; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "DialInfo"; } enum Gender { Male = 0, Female = 1, NA = -1 }; struct DATAstruct { int mUnknown1 = 0; union { int mDisposition = 0; // Used for dialogue responses int mJournalIndex; // Used for journal entries }; signed char mRank = -1; // Rank of NPC signed char mGender = Gender::NA; // See Gender enum signed char mPCrank = -1; // Player rank signed char mUnknown2 = 0; }; // 12 bytes DATAstruct mData; // The rules for whether or not we will select this dialog item. struct SelectStruct { std::string mSelectRule; // This has a complicated format Variant mValue; }; // Journal quest indices (introduced with the quest system in Tribunal) enum QuestStatus { QS_None = 0, QS_Name = 1, QS_Finished = 2, QS_Restart = 3 }; // Rules for when to include this item in the final list of options // visible to the player. std::vector mSelects; // Id of this, previous and next INFO items std::string mId, mPrev, mNext; // Various references used in determining when to select this item. std::string mActor, mRace, mClass, mFaction, mPcFaction, mCell; // Sound and text associated with this item std::string mSound, mResponse; // Result script (uncompiled) to run whenever this dialog item is // selected std::string mResultScript; // ONLY include this item the NPC is not part of any faction. bool mFactionLess; // Status of this quest item QuestStatus mQuestStatus; // Hexadecimal versions of the various subrecord names. enum SubNames { REC_ONAM = 0x4d414e4f, REC_RNAM = 0x4d414e52, REC_CNAM = 0x4d414e43, REC_FNAM = 0x4d414e46, REC_ANAM = 0x4d414e41, REC_DNAM = 0x4d414e44, REC_SNAM = 0x4d414e53, REC_NAME = 0x454d414e, REC_SCVR = 0x52564353, REC_BNAM = 0x4d414e42, REC_QSTN = 0x4e545351, REC_QSTF = 0x46545351, REC_QSTR = 0x52545351, REC_DELE = 0x454c4544 }; void load(ESMReader &esm, bool &isDeleted); ///< Loads Info record void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadingr.cpp000066400000000000000000000061021445372753700217720ustar00rootroot00000000000000#include "loadingr.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Ingredient::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("IRDT"): esm.getHTSized<56>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records for (int i=0; i<4; ++i) { if (mData.mEffectID[i] != 85 && mData.mEffectID[i] != 22 && mData.mEffectID[i] != 17 && mData.mEffectID[i] != 79 && mData.mEffectID[i] != 74) { mData.mAttributes[i] = -1; } // is this relevant in cycle from 0 to 4? if (mData.mEffectID[i] != 89 && mData.mEffectID[i] != 26 && mData.mEffectID[i] != 21 && mData.mEffectID[i] != 83 && mData.mEffectID[i] != 78) { mData.mSkills[i] = -1; } } } void Ingredient::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } void Ingredient::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; for (int i=0; i<4; ++i) { mData.mEffectID[i] = 0; mData.mSkills[i] = 0; mData.mAttributes[i] = 0; } mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadingr.hpp000066400000000000000000000017461445372753700220100ustar00rootroot00000000000000#ifndef OPENMW_ESM_INGR_H #define OPENMW_ESM_INGR_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Alchemy ingredient */ struct Ingredient { constexpr static RecNameInts sRecordId = REC_INGR; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Ingredient"; } struct IRDTstruct { float mWeight; int mValue; int mEffectID[4]; // Effect, 0 or -1 means none int mSkills[4]; // SkillEnum related to effect int mAttributes[4]; // Attribute related to effect }; IRDTstruct mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadland.cpp000066400000000000000000000305421445372753700217560ustar00rootroot00000000000000#include "loadland.hpp" #include #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { Land::Land() : mFlags(0) , mX(0) , mY(0) , mDataTypes(0) , mLandData(nullptr) { } void transposeTextureData(const uint16_t *in, uint16_t *out) { int readPos = 0; //bit ugly, but it works for ( int y1 = 0; y1 < 4; y1++ ) for ( int x1 = 0; x1 < 4; x1++ ) for ( int y2 = 0; y2 < 4; y2++) for ( int x2 = 0; x2 < 4; x2++ ) out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; } Land::~Land() { delete mLandData; } void Land::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasLocation = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("INTV"): esm.getSubHeader(); if (esm.getSubSize() != 8) esm.fail("Subrecord size is not equal to 8"); esm.getT(mX); esm.getT(mY); hasLocation = true; break; case fourCC("DATA"): esm.getHT(mFlags); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.cacheSubName(); isLoaded = true; break; } } if (!hasLocation) esm.fail("Missing INTV subrecord"); mContext = esm.getContext(); mLandData = nullptr; std::fill(std::begin(mWnam), std::end(mWnam), 0); // Skip the land data here. Load it when the cell is loaded. while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("VNML"): esm.skipHSub(); mDataTypes |= DATA_VNML; break; case fourCC("VHGT"): esm.skipHSub(); mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): esm.getHExact(mWnam, sizeof(mWnam)); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): esm.skipHSub(); mDataTypes |= DATA_VCLR; break; case fourCC("VTEX"): esm.skipHSub(); mDataTypes |= DATA_VTEX; break; default: esm.fail("Unknown subrecord"); break; } } } void Land::save(ESMWriter &esm, bool isDeleted) const { esm.startSubRecord("INTV"); esm.writeT(mX); esm.writeT(mY); esm.endRecord("INTV"); esm.writeHNT("DATA", mFlags); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (mLandData) { if (mDataTypes & Land::DATA_VNML) { esm.writeHNT("VNML", mLandData->mNormals); } if (mDataTypes & Land::DATA_VHGT) { VHGT offsets; offsets.mHeightOffset = mLandData->mHeights[0] / HEIGHT_SCALE; offsets.mUnk1 = mLandData->mUnk1; offsets.mUnk2 = mLandData->mUnk2; float prevY = mLandData->mHeights[0]; int number = 0; // avoid multiplication for (int i = 0; i < LAND_SIZE; ++i) { float diff = (mLandData->mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); float prevX = prevY = mLandData->mHeights[number]; ++number; for (int j = 1; j < LAND_SIZE; ++j) { diff = (mLandData->mHeights[number] - prevX) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); prevX = mLandData->mHeights[number]; ++number; } } esm.writeHNT("VHGT", offsets, sizeof(VHGT)); } if (mDataTypes & Land::DATA_WNAM) { // Generate WNAM record signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE]; constexpr float max = std::numeric_limits::max(); constexpr float min = std::numeric_limits::min(); constexpr float vertMult = static_cast(Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) { for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) { float height = mLandData->mHeights[int(row * vertMult) * Land::LAND_SIZE + int(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; height = std::clamp(height, min, max); wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); } } esm.writeHNT("WNAM", wnam); } if (mDataTypes & Land::DATA_VCLR) { esm.writeHNT("VCLR", mLandData->mColours); } if (mDataTypes & Land::DATA_VTEX) { uint16_t vtex[LAND_NUM_TEXTURES]; transposeTextureData(mLandData->mTextures, vtex); esm.writeHNT("VTEX", vtex); } } } void Land::blank() { setPlugin(0); std::fill(std::begin(mWnam), std::end(mWnam), 0); if (!mLandData) mLandData = new LandData; mLandData->mHeightOffset = 0; std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; for (int i = 0; i < LAND_NUM_VERTS; ++i) { mLandData->mNormals[i*3+0] = 0; mLandData->mNormals[i*3+1] = 0; mLandData->mNormals[i*3+2] = 127; } std::fill(std::begin(mLandData->mTextures), std::end(mLandData->mTextures), 0); std::fill(std::begin(mLandData->mColours), std::end(mLandData->mColours), 255); mLandData->mUnk1 = 0; mLandData->mUnk2 = 0; mLandData->mDataLoaded = Land::DATA_VNML | Land::DATA_VHGT | Land::DATA_WNAM | Land::DATA_VCLR | Land::DATA_VTEX; mDataTypes = mLandData->mDataLoaded; // No file associated with the land now mContext.filename.clear(); } void Land::loadData(int flags, LandData* target) const { // Create storage if nothing is loaded if (!target && !mLandData) { mLandData = new LandData; } if (!target) target = mLandData; // Try to load only available data flags = flags & mDataTypes; // Return if all required data is loaded if ((target->mDataLoaded & flags) == flags) { return; } // Copy data to target if no file if (mContext.filename.empty()) { // Make sure there is data, and that it doesn't point to the same object. if (mLandData && mLandData != target) *target = *mLandData; return; } ESMReader reader; reader.restoreContext(mContext); if (reader.isNextSub("VNML")) { condLoad(reader, flags, target->mDataLoaded, DATA_VNML, target->mNormals, sizeof(target->mNormals)); } if (reader.isNextSub("VHGT")) { VHGT vhgt; if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) { target->mMinHeight = std::numeric_limits::max(); target->mMaxHeight = -std::numeric_limits::max(); float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; target->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE > target->mMaxHeight) target->mMaxHeight = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE < target->mMinHeight) target->mMinHeight = rowOffset * HEIGHT_SCALE; float colOffset = rowOffset; for (int x = 1; x < LAND_SIZE; x++) { colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; target->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; if (colOffset * HEIGHT_SCALE > target->mMaxHeight) target->mMaxHeight = colOffset * HEIGHT_SCALE; if (colOffset * HEIGHT_SCALE < target->mMinHeight) target->mMinHeight = colOffset * HEIGHT_SCALE; } } target->mUnk1 = vhgt.mUnk1; target->mUnk2 = vhgt.mUnk2; } } if (reader.isNextSub("WNAM")) reader.skipHSub(); if (reader.isNextSub("VCLR")) condLoad(reader, flags, target->mDataLoaded, DATA_VCLR, target->mColours, 3 * LAND_NUM_VERTS); if (reader.isNextSub("VTEX")) { uint16_t vtex[LAND_NUM_TEXTURES]; if (condLoad(reader, flags, target->mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) { transposeTextureData(vtex, target->mTextures); } } } void Land::unloadData() const { if (mLandData) { delete mLandData; mLandData = nullptr; } } bool Land::condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { reader.getHExact(ptr, size); targetFlags |= dataFlag; return true; } reader.skipHSubSize(size); return false; } bool Land::isDataLoaded(int flags) const { return mLandData && (mLandData->mDataLoaded & flags) == flags; } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mContext (land.mContext), mDataTypes (land.mDataTypes), mLandData (land.mLandData ? new LandData (*land.mLandData) : nullptr) { std::copy(land.mWnam, land.mWnam + LAND_GLOBAL_MAP_LOD_SIZE, mWnam); } Land& Land::operator= (const Land& land) { Land tmp(land); swap(tmp); return *this; } void Land::swap (Land& land) { std::swap (mFlags, land.mFlags); std::swap (mX, land.mX); std::swap (mY, land.mY); std::swap (mContext, land.mContext); std::swap (mDataTypes, land.mDataTypes); std::swap (mLandData, land.mLandData); std::swap (mWnam, land.mWnam); } const Land::LandData *Land::getLandData (int flags) const { if (!(flags & mDataTypes)) return nullptr; loadData (flags); return mLandData; } const Land::LandData *Land::getLandData() const { return mLandData; } Land::LandData *Land::getLandData() { return mLandData; } void Land::add (int flags) { if (!mLandData) mLandData = new LandData; mDataTypes |= flags; mLandData->mDataLoaded |= flags; } void Land::remove (int flags) { mDataTypes &= ~flags; if (mLandData) { mLandData->mDataLoaded &= ~flags; if (!mLandData->mDataLoaded) { delete mLandData; mLandData = nullptr; } } } } openmw-openmw-0.48.0/components/esm3/loadland.hpp000066400000000000000000000123341445372753700217620ustar00rootroot00000000000000#ifndef OPENMW_ESM_LAND_H #define OPENMW_ESM_LAND_H #include #include #include "components/esm/defs.hpp" #include "components/esm/esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Landscape data. */ struct Land { constexpr static RecNameInts sRecordId = REC_LAND; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Land"; } Land(); ~Land(); int mFlags; // Only first four bits seem to be used, don't know what // they mean. int mX, mY; // Map coordinates. // Plugin index, used to reference the correct material palette. int getPlugin() const { return mContext.index; } void setPlugin(int index) { mContext.index = index; } // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. // In the editor, there may not be a file associated with the Land, // in which case the filename will be empty. ESM_Context mContext; int mDataTypes; enum { DATA_VNML = 1, DATA_VHGT = 2, DATA_WNAM = 4, DATA_VCLR = 8, DATA_VTEX = 16 }; // default height to use in case there is no Land record static constexpr int DEFAULT_HEIGHT = -2048; // number of vertices per side static constexpr int LAND_SIZE = 65; // cell terrain size in world coords static constexpr int REAL_SIZE = Constants::CellSizeInUnits; // total number of vertices static constexpr int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; static constexpr int HEIGHT_SCALE = 8; //number of textures per side of land static constexpr int LAND_TEXTURE_SIZE = 16; //total number of textures per land static constexpr int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; static constexpr int LAND_GLOBAL_MAP_LOD_SIZE = 81; static constexpr int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; #pragma pack(push,1) struct VHGT { float mHeightOffset; int8_t mHeightData[LAND_NUM_VERTS]; short mUnk1; char mUnk2; }; #pragma pack(pop) typedef signed char VNML; struct LandData { LandData() : mHeightOffset(0) , mMinHeight(0) , mMaxHeight(0) , mUnk1(0) , mUnk2(0) , mDataLoaded(0) { } // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; float mMinHeight; float mMaxHeight; // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; // 2D array of texture indices. An index can be used to look up an LandTexture, // but to do so you must subtract 1 from the index first! // An index of 0 indicates the default texture. uint16_t mTextures[LAND_NUM_TEXTURES]; // 24-bit RGB color for each vertex unsigned char mColours[3 * LAND_NUM_VERTS]; // ??? short mUnk1; uint8_t mUnk2; int mDataLoaded; }; // low-LOD heightmap (used for rendering the global map) signed char mWnam[LAND_GLOBAL_MAP_LOD_SIZE]; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); /** * Actually loads data into target * If target is nullptr, assumed target is mLandData */ void loadData(int flags, LandData* target = nullptr) const; /** * Frees memory allocated for mLandData */ void unloadData() const; /// Check if given data type is loaded bool isDataLoaded(int flags) const; /// Sets the flags and creates a LandData if needed void setDataLoaded(int flags); Land (const Land& land); Land& operator= (const Land& land); void swap (Land& land); /// Return land data with at least the data types specified in \a flags loaded (if they /// are available). Will return a 0-pointer if there is no data for any of the /// specified types. const LandData *getLandData (int flags) const; /// Return land data without loading first anything. Can return a 0-pointer. const LandData *getLandData() const; /// Return land data without loading first anything. Can return a 0-pointer. LandData *getLandData(); /// \attention Must not be called on objects that aren't fully loaded. /// /// \note Added data fields will be uninitialised void add (int flags); /// \attention Must not be called on objects that aren't fully loaded. void remove (int flags); private: /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const; mutable LandData *mLandData; }; } #endif openmw-openmw-0.48.0/components/esm3/loadlevlist.cpp000066400000000000000000000064621445372753700225260ustar00rootroot00000000000000#include "loadlevlist.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void LevelledListBase::load(ESMReader& esm, NAME recName, bool& isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasList = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("DATA"): esm.getHT(mFlags); break; case fourCC("NNAM"): esm.getHT(mChanceNone); break; case fourCC("INDX"): { int length = 0; esm.getHT(length); mList.resize(length); // If this levelled list was already loaded by a previous content file, // we overwrite the list. Merging lists should probably be left to external tools, // with the limited amount of information there is in the records, all merging methods // will be flawed in some way. For a proper fix the ESM format would have to be changed // to actually track list changes instead of including the whole list for every file // that does something with that list. for (size_t i = 0; i < mList.size(); i++) { LevelItem &li = mList[i]; li.mId = esm.getHNString(recName); esm.getHNT(li.mLevel, "INTV"); } hasList = true; break; } case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: { if (!hasList) { // Original engine ignores rest of the record, even if there are items following mList.clear(); esm.skipRecord(); } else { esm.fail("Unknown subrecord"); } break; } } } if (!hasName) esm.fail("Missing NAME subrecord"); } void LevelledListBase::save(ESMWriter& esm, NAME recName, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNCString(recName, it->mId); esm.writeHNT("INTV", it->mLevel); } } void LevelledListBase::blank() { mRecordFlags = 0; mFlags = 0; mChanceNone = 0; mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadlevlist.hpp000066400000000000000000000053211445372753700225240ustar00rootroot00000000000000#ifndef OPENMW_ESM_LEVLISTS_H #define OPENMW_ESM_LEVLISTS_H #include #include #include #include namespace ESM { class ESMReader; class ESMWriter; /* * Levelled lists. Since these have identical layout, I only bothered * to implement it once. * * We should later implement the ability to merge levelled lists from * several files. */ struct LevelledListBase { int mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) unsigned int mRecordFlags; std::string mId; struct LevelItem { std::string mId; short mLevel; }; std::vector mList; void load(ESMReader& esm, NAME recName, bool& isDeleted); void save(ESMWriter& esm, NAME recName, bool isDeleted) const; void blank(); ///< Set record to default state (does not touch the ID). }; template struct CustomLevelledListBase : LevelledListBase { void load(ESMReader &esm, bool& isDeleted) { LevelledListBase::load(esm, Base::sRecName, isDeleted); } void save(ESMWriter &esm, bool isDeleted = false) const { LevelledListBase::save(esm, Base::sRecName, isDeleted); } }; struct CreatureLevList : CustomLevelledListBase { /// Record name used to read references. static constexpr NAME sRecName {"CNAM"}; static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVC; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "CreatureLevList"; } enum Flags { AllLevels = 0x01 // Calculate from all levels <= player // level, not just the closest below // player. }; }; struct ItemLevList : CustomLevelledListBase { /// Record name used to read references. static constexpr NAME sRecName {"INAM"}; static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVI; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "ItemLevList"; } enum Flags { Each = 0x01, // Select a new item each time this // list is instantiated, instead of // giving several identical items // (used when a container has more // than one instance of one levelled // list.) AllLevels = 0x02 // Calculate from all levels <= player // level, not just the closest below // player. }; }; } #endif openmw-openmw-0.48.0/components/esm3/loadligh.cpp000066400000000000000000000047141445372753700217650ustar00rootroot00000000000000#include "loadligh.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Light::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case fourCC("LHDT"): esm.getHTSized<24>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("SNAM"): mSound = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing LHDT subrecord"); } void Light::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); esm.writeHNT("LHDT", mData, 24); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SNAM", mSound); } void Light::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mTime = 0; mData.mRadius = 0; mData.mColor = 0; mData.mFlags = 0; mSound.clear(); mScript.clear(); mModel.clear(); mIcon.clear(); mName.clear(); } } openmw-openmw-0.48.0/components/esm3/loadligh.hpp000066400000000000000000000026721445372753700217730ustar00rootroot00000000000000#ifndef OPENMW_ESM_LIGH_H #define OPENMW_ESM_LIGH_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Lights. Includes static light sources and also carryable candles * and torches. */ struct Light { constexpr static RecNameInts sRecordId = REC_LIGH; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Light"; } enum Flags { Dynamic = 0x001, Carry = 0x002, // Can be carried Negative = 0x004, // Negative light - i.e. darkness Flicker = 0x008, Fire = 0x010, OffDefault = 0x020, // Off by default - does not burn while placed in a cell, but can burn when equipped by an NPC FlickerSlow = 0x040, Pulse = 0x080, PulseSlow = 0x100 }; struct LHDTstruct { float mWeight; int mValue; int mTime; // Duration int mRadius; unsigned int mColor; // 4-byte rgba value int mFlags; }; // Size = 24 bytes LHDTstruct mData; unsigned int mRecordFlags; std::string mSound, mScript, mModel, mIcon, mName, mId; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadlock.cpp000066400000000000000000000043571445372753700217750ustar00rootroot00000000000000#include "loadlock.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Lockpick::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("LKDT"): esm.getHTSized<16>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing LKDT subrecord"); } void Lockpick::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("LKDT", mData, 16); esm.writeHNOString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } void Lockpick::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadlock.hpp000066400000000000000000000015101445372753700217660ustar00rootroot00000000000000#ifndef OPENMW_ESM_LOCK_H #define OPENMW_ESM_LOCK_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Lockpick { constexpr static RecNameInts sRecordId = REC_LOCK; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Lockpick"; } struct Data { float mWeight; int mValue; float mQuality; int mUses; }; // Size = 16 Data mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadltex.cpp000066400000000000000000000031001445372753700220020ustar00rootroot00000000000000#include "loadltex.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void LandTexture::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; bool hasName = false; bool hasIndex = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("INTV"): esm.getHT(mIndex); hasIndex = true; break; case fourCC("DATA"): mTexture = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasIndex) esm.fail("Missing INTV subrecord"); } void LandTexture::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); esm.writeHNT("INTV", mIndex); esm.writeHNCString("DATA", mTexture); if (isDeleted) { esm.writeHNString("DELE", "", 3); } } void LandTexture::blank() { mId.clear(); mTexture.clear(); } } openmw-openmw-0.48.0/components/esm3/loadltex.hpp000066400000000000000000000020241445372753700220130ustar00rootroot00000000000000#ifndef OPENMW_ESM_LTEX_H #define OPENMW_ESM_LTEX_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Texture used for texturing landscape. * They are indexed by 'num', but still use 'id' to override base records. * Original editor even does not allow to create new records with existing ID's. * TODO: currently OpenMW-CS does not allow to override LTEX records at all. */ struct LandTexture { constexpr static RecNameInts sRecordId = REC_LTEX; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "LandTexture"; } // mId is merely a user friendly name for the texture in the editor. std::string mId, mTexture; int mIndex; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; /// Sets the record to the default state. Does not touch the index. Does touch mID. void blank(); }; } #endif openmw-openmw-0.48.0/components/esm3/loadmgef.cpp000066400000000000000000000461231445372753700217600ustar00rootroot00000000000000#include "loadmgef.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { namespace { static const char *sIds[MagicEffect::Length] = { "WaterBreathing", "SwiftSwim", "WaterWalking", "Shield", "FireShield", "LightningShield", "FrostShield", "Burden", "Feather", "Jump", "Levitate", "SlowFall", "Lock", "Open", "FireDamage", "ShockDamage", "FrostDamage", "DrainAttribute", "DrainHealth", "DrainMagicka", "DrainFatigue", "DrainSkill", "DamageAttribute", "DamageHealth", "DamageMagicka", "DamageFatigue", "DamageSkill", "Poison", "WeaknessToFire", "WeaknessToFrost", "WeaknessToShock", "WeaknessToMagicka", "WeaknessToCommonDisease", "WeaknessToBlightDisease", "WeaknessToCorprusDisease", "WeaknessToPoison", "WeaknessToNormalWeapons", "DisintegrateWeapon", "DisintegrateArmor", "Invisibility", "Chameleon", "Light", "Sanctuary", "NightEye", "Charm", "Paralyze", "Silence", "Blind", "Sound", "CalmHumanoid", "CalmCreature", "FrenzyHumanoid", "FrenzyCreature", "DemoralizeHumanoid", "DemoralizeCreature", "RallyHumanoid", "RallyCreature", "Dispel", "Soultrap", "Telekinesis", "Mark", "Recall", "DivineIntervention", "AlmsiviIntervention", "DetectAnimal", "DetectEnchantment", "DetectKey", "SpellAbsorption", "Reflect", "CureCommonDisease", "CureBlightDisease", "CureCorprusDisease", "CurePoison", "CureParalyzation", "RestoreAttribute", "RestoreHealth", "RestoreMagicka", "RestoreFatigue", "RestoreSkill", "FortifyAttribute", "FortifyHealth", "FortifyMagicka", "FortifyFatigue", "FortifySkill", "FortifyMaximumMagicka", "AbsorbAttribute", "AbsorbHealth", "AbsorbMagicka", "AbsorbFatigue", "AbsorbSkill", "ResistFire", "ResistFrost", "ResistShock", "ResistMagicka", "ResistCommonDisease", "ResistBlightDisease", "ResistCorprusDisease", "ResistPoison", "ResistNormalWeapons", "ResistParalysis", "RemoveCurse", "TurnUndead", "SummonScamp", "SummonClannfear", "SummonDaedroth", "SummonDremora", "SummonAncestralGhost", "SummonSkeletalMinion", "SummonBonewalker", "SummonGreaterBonewalker", "SummonBonelord", "SummonWingedTwilight", "SummonHunger", "SummonGoldenSaint", "SummonFlameAtronach", "SummonFrostAtronach", "SummonStormAtronach", "FortifyAttack", "CommandCreature", "CommandHumanoid", "BoundDagger", "BoundLongsword", "BoundMace", "BoundBattleAxe", "BoundSpear", "BoundLongbow", "ExtraSpell", "BoundCuirass", "BoundHelm", "BoundBoots", "BoundShield", "BoundGloves", "Corprus", "Vampirism", "SummonCenturionSphere", "SunDamage", "StuntedMagicka", // Tribunal only "SummonFabricant", // Bloodmoon only "SummonWolf", "SummonBear", "SummonBonewolf", "SummonCreature04", "SummonCreature05" }; const int NumberOfHardcodedFlags = 143; const int HardcodedFlags[NumberOfHardcodedFlags] = { 0x11c8, 0x11c0, 0x11c8, 0x11e0, 0x11e0, 0x11e0, 0x11e0, 0x11d0, 0x11c0, 0x11c0, 0x11e0, 0x11c0, 0x11184, 0x11184, 0x1f0, 0x1f0, 0x1f0, 0x11d2, 0x11f0, 0x11d0, 0x11d0, 0x11d1, 0x1d2, 0x1f0, 0x1d0, 0x1d0, 0x1d1, 0x1f0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x1d0, 0x1d0, 0x11c8, 0x31c0, 0x11c0, 0x11c0, 0x11c0, 0x1180, 0x11d8, 0x11d8, 0x11d0, 0x11d0, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11c4, 0x111b8, 0x1040, 0x104c, 0x104c, 0x104c, 0x104c, 0x1040, 0x1040, 0x1040, 0x11c0, 0x11c0, 0x1cc, 0x1cc, 0x1cc, 0x1cc, 0x1cc, 0x1c2, 0x1c0, 0x1c0, 0x1c0, 0x1c1, 0x11c2, 0x11c0, 0x11c0, 0x11c0, 0x11c1, 0x11c0, 0x21192, 0x20190, 0x20190, 0x20190, 0x21191, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x1c0, 0x11190, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x11c0, 0x1180, 0x1180, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1188, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1048, 0x104c, 0x1048, 0x40, 0x11c8, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048 }; } } namespace ESM { void MagicEffect::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) mRecordFlags = esm.getRecordFlags(); esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); esm.getHNTSized<36>(mData, "MEDT"); if (esm.getFormat() == 0) { // don't allow mods to change fixed flags in the legacy format mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight); if (mIndex>=0 && mIndex static std::map effects; if (effects.empty()) { effects[DisintegrateArmor] = Sanctuary; effects[DisintegrateWeapon] = Sanctuary; for (int i = DrainAttribute; i <= DamageSkill; ++i) effects[i] = ResistMagicka; for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i) effects[i] = ResistMagicka; for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) effects[i] = ResistMagicka; effects[Burden] = ResistMagicka; effects[Charm] = ResistMagicka; effects[Silence] = ResistMagicka; effects[Blind] = ResistMagicka; effects[Sound] = ResistMagicka; for (int i=0; i<2; ++i) { effects[CalmHumanoid+i] = ResistMagicka; effects[FrenzyHumanoid+i] = ResistMagicka; effects[DemoralizeHumanoid+i] = ResistMagicka; effects[RallyHumanoid+i] = ResistMagicka; } effects[TurnUndead] = ResistMagicka; effects[FireDamage] = ResistFire; effects[FrostDamage] = ResistFrost; effects[ShockDamage] = ResistShock; effects[Vampirism] = ResistCommonDisease; effects[Corprus] = ResistCorprusDisease; effects[Poison] = ResistPoison; effects[Paralyze] = ResistParalysis; } if (effects.find(effect) != effects.end()) return effects[effect]; else return -1; } short MagicEffect::getWeaknessEffect(short effect) { static std::map effects; if (effects.empty()) { for (int i = DrainAttribute; i <= DamageSkill; ++i) effects[i] = WeaknessToMagicka; for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i) effects[i] = WeaknessToMagicka; for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) effects[i] = WeaknessToMagicka; effects[Burden] = WeaknessToMagicka; effects[Charm] = WeaknessToMagicka; effects[Silence] = WeaknessToMagicka; effects[Blind] = WeaknessToMagicka; effects[Sound] = WeaknessToMagicka; for (int i=0; i<2; ++i) { effects[CalmHumanoid+i] = WeaknessToMagicka; effects[FrenzyHumanoid+i] = WeaknessToMagicka; effects[DemoralizeHumanoid+i] = WeaknessToMagicka; effects[RallyHumanoid+i] = WeaknessToMagicka; } effects[TurnUndead] = WeaknessToMagicka; effects[FireDamage] = WeaknessToFire; effects[FrostDamage] = WeaknessToFrost; effects[ShockDamage] = WeaknessToShock; effects[Vampirism] = WeaknessToCommonDisease; effects[Corprus] = WeaknessToCorprusDisease; effects[Poison] = WeaknessToPoison; effects[Paralyze] = -1; } if (effects.find(effect) != effects.end()) return effects[effect]; else return -1; } static std::map genNameMap() { // Map effect ID to GMST name // http://www.uesp.net/morrow/hints/mweffects.shtml std::map names; names[85] ="sEffectAbsorbAttribute"; names[88] ="sEffectAbsorbFatigue"; names[86] ="sEffectAbsorbHealth"; names[87] ="sEffectAbsorbSpellPoints"; names[89] ="sEffectAbsorbSkill"; names[63] ="sEffectAlmsiviIntervention"; names[47] ="sEffectBlind"; names[123] ="sEffectBoundBattleAxe"; names[129] ="sEffectBoundBoots"; names[127] ="sEffectBoundCuirass"; names[120] ="sEffectBoundDagger"; names[131] ="sEffectBoundGloves"; names[128] ="sEffectBoundHelm"; names[125] ="sEffectBoundLongbow"; names[126] ="sEffectExtraSpell"; names[121] ="sEffectBoundLongsword"; names[122] ="sEffectBoundMace"; names[130] ="sEffectBoundShield"; names[124] ="sEffectBoundSpear"; names[7] ="sEffectBurden"; names[50] ="sEffectCalmCreature"; names[49] ="sEffectCalmHumanoid"; names[40] ="sEffectChameleon"; names[44] ="sEffectCharm"; names[118] ="sEffectCommandCreatures"; names[119] ="sEffectCommandHumanoids"; names[132] ="sEffectCorpus"; // NB this typo. (bethesda made it) names[70] ="sEffectCureBlightDisease"; names[69] ="sEffectCureCommonDisease"; names[71] ="sEffectCureCorprusDisease"; names[73] ="sEffectCureParalyzation"; names[72] ="sEffectCurePoison"; names[22] ="sEffectDamageAttribute"; names[25] ="sEffectDamageFatigue"; names[23] ="sEffectDamageHealth"; names[24] ="sEffectDamageMagicka"; names[26] ="sEffectDamageSkill"; names[54] ="sEffectDemoralizeCreature"; names[53] ="sEffectDemoralizeHumanoid"; names[64] ="sEffectDetectAnimal"; names[65] ="sEffectDetectEnchantment"; names[66] ="sEffectDetectKey"; names[38] ="sEffectDisintegrateArmor"; names[37] ="sEffectDisintegrateWeapon"; names[57] ="sEffectDispel"; names[62] ="sEffectDivineIntervention"; names[17] ="sEffectDrainAttribute"; names[20] ="sEffectDrainFatigue"; names[18] ="sEffectDrainHealth"; names[19] ="sEffectDrainSpellpoints"; names[21] ="sEffectDrainSkill"; names[8] ="sEffectFeather"; names[14] ="sEffectFireDamage"; names[4] ="sEffectFireShield"; names[117] ="sEffectFortifyAttackBonus"; names[79] ="sEffectFortifyAttribute"; names[82] ="sEffectFortifyFatigue"; names[80] ="sEffectFortifyHealth"; names[81] ="sEffectFortifySpellpoints"; names[84] ="sEffectFortifyMagickaMultiplier"; names[83] ="sEffectFortifySkill"; names[52] ="sEffectFrenzyCreature"; names[51] ="sEffectFrenzyHumanoid"; names[16] ="sEffectFrostDamage"; names[6] ="sEffectFrostShield"; names[39] ="sEffectInvisibility"; names[9] ="sEffectJump"; names[10] ="sEffectLevitate"; names[41] ="sEffectLight"; names[5] ="sEffectLightningShield"; names[12] ="sEffectLock"; names[60] ="sEffectMark"; names[43] ="sEffectNightEye"; names[13] ="sEffectOpen"; names[45] ="sEffectParalyze"; names[27] ="sEffectPoison"; names[56] ="sEffectRallyCreature"; names[55] ="sEffectRallyHumanoid"; names[61] ="sEffectRecall"; names[68] ="sEffectReflect"; names[100] ="sEffectRemoveCurse"; names[95] ="sEffectResistBlightDisease"; names[94] ="sEffectResistCommonDisease"; names[96] ="sEffectResistCorprusDisease"; names[90] ="sEffectResistFire"; names[91] ="sEffectResistFrost"; names[93] ="sEffectResistMagicka"; names[98] ="sEffectResistNormalWeapons"; names[99] ="sEffectResistParalysis"; names[97] ="sEffectResistPoison"; names[92] ="sEffectResistShock"; names[74] ="sEffectRestoreAttribute"; names[77] ="sEffectRestoreFatigue"; names[75] ="sEffectRestoreHealth"; names[76] ="sEffectRestoreSpellPoints"; names[78] ="sEffectRestoreSkill"; names[42] ="sEffectSanctuary"; names[3] ="sEffectShield"; names[15] ="sEffectShockDamage"; names[46] ="sEffectSilence"; names[11] ="sEffectSlowFall"; names[58] ="sEffectSoultrap"; names[48] ="sEffectSound"; names[67] ="sEffectSpellAbsorption"; names[136] ="sEffectStuntedMagicka"; names[106] ="sEffectSummonAncestralGhost"; names[110] ="sEffectSummonBonelord"; names[108] ="sEffectSummonLeastBonewalker"; names[134] ="sEffectSummonCenturionSphere"; names[103] ="sEffectSummonClannfear"; names[104] ="sEffectSummonDaedroth"; names[105] ="sEffectSummonDremora"; names[114] ="sEffectSummonFlameAtronach"; names[115] ="sEffectSummonFrostAtronach"; names[113] ="sEffectSummonGoldenSaint"; names[109] ="sEffectSummonGreaterBonewalker"; names[112] ="sEffectSummonHunger"; names[102] ="sEffectSummonScamp"; names[107] ="sEffectSummonSkeletalMinion"; names[116] ="sEffectSummonStormAtronach"; names[111] ="sEffectSummonWingedTwilight"; names[135] ="sEffectSunDamage"; names[1] ="sEffectSwiftSwim"; names[59] ="sEffectTelekinesis"; names[101] ="sEffectTurnUndead"; names[133] ="sEffectVampirism"; names[0] ="sEffectWaterBreathing"; names[2] ="sEffectWaterWalking"; names[33] ="sEffectWeaknesstoBlightDisease"; names[32] ="sEffectWeaknesstoCommonDisease"; names[34] ="sEffectWeaknesstoCorprusDisease"; names[28] ="sEffectWeaknesstoFire"; names[29] ="sEffectWeaknesstoFrost"; names[31] ="sEffectWeaknesstoMagicka"; names[36] ="sEffectWeaknesstoNormalWeapons"; names[35] ="sEffectWeaknesstoPoison"; names[30] ="sEffectWeaknesstoShock"; // bloodmoon names[138] ="sEffectSummonCreature01"; names[139] ="sEffectSummonCreature02"; names[140] ="sEffectSummonCreature03"; names[141] ="sEffectSummonCreature04"; names[142] ="sEffectSummonCreature05"; // tribunal names[137] ="sEffectSummonFabricant"; return names; } const std::map MagicEffect::sNames = genNameMap(); const std::string &MagicEffect::effectIdToString(short effectID) { std::map::const_iterator name = sNames.find(effectID); if(name == sNames.end()) throw std::runtime_error(std::string("Unimplemented effect ID ")+std::to_string(effectID)); return name->second; } class FindSecond { std::string_view mName; public: FindSecond(std::string_view name) : mName(name) { } bool operator()(const std::pair &item) const { if(Misc::StringUtils::ciEqual(item.second, mName)) return true; return false; } }; short MagicEffect::effectStringToId(std::string_view effect) { std::map::const_iterator name; name = std::find_if(sNames.begin(), sNames.end(), FindSecond(effect)); if(name == sNames.end()) throw std::runtime_error("Unimplemented effect " + std::string(effect)); return name->first; } MagicEffect::MagnitudeDisplayType MagicEffect::getMagnitudeDisplayType() const { if ( mData.mFlags & NoMagnitude ) return MDT_None; if ( mIndex == 84 ) return MDT_TimesInt; if ( mIndex == 59 || ( mIndex >= 64 && mIndex <= 66) ) return MDT_Feet; if ( mIndex == 118 || mIndex == 119 ) return MDT_Level; if ( ( mIndex >= 28 && mIndex <= 36 ) || ( mIndex >= 90 && mIndex <= 99 ) || mIndex == 40 || mIndex == 47 || mIndex == 57 || mIndex == 68 ) return MDT_Percentage; return MDT_Points; } void MagicEffect::blank() { mRecordFlags = 0; mData.mSchool = 0; mData.mBaseCost = 0; mData.mFlags = 0; mData.mRed = 0; mData.mGreen = 0; mData.mBlue = 0; mData.mSpeed = 0; mIcon.clear(); mParticle.clear(); mCasting.clear(); mHit.clear(); mArea.clear(); mBolt.clear(); mCastSound.clear(); mBoltSound.clear(); mHitSound.clear(); mAreaSound.clear(); mDescription.clear(); } std::string MagicEffect::indexToId (int index) { std::ostringstream stream; if (index!=-1) { stream << "#"; if (index<100) { stream << "0"; if (index<10) stream << "0"; } stream << index; if (index>=0 && index #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct MagicEffect { constexpr static RecNameInts sRecordId = REC_MGEF; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "MagicEffect"; } unsigned int mRecordFlags; std::string mId; enum Flags { // Originally fixed flags (HardcodedFlags array consists of just these) TargetSkill = 0x1, // Affects a specific skill, which is specified elsewhere in the effect structure. TargetAttribute = 0x2, // Affects a specific attribute, which is specified elsewhere in the effect structure. NoDuration = 0x4, // Has no duration. Only runs effect once on cast. NoMagnitude = 0x8, // Has no magnitude. Harmful = 0x10, // Counts as a negative effect. Interpreted as useful for attack, and is treated as a bad effect in alchemy. ContinuousVfx = 0x20, // The effect's hit particle VFX repeats for the full duration of the spell, rather than occuring once on hit. CastSelf = 0x40, // Allows range - cast on self. CastTouch = 0x80, // Allows range - cast on touch. CastTarget = 0x100, // Allows range - cast on target. AppliedOnce = 0x1000, // An effect that is applied once it lands, instead of continuously. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second. Stealth = 0x2000, // Unused NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. IllegalDaedra = 0x8000, // Unused Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. // Originally modifiable flags AllowSpellmaking = 0x200, // Can be used for spellmaking AllowEnchanting = 0x400, // Can be used for enchanting NegativeLight = 0x800 // Unused }; enum MagnitudeDisplayType { MDT_None, MDT_Feet, MDT_Level, MDT_Percentage, MDT_Points, MDT_TimesInt }; struct MEDTstruct { int mSchool; // SpellSchool, see defs.hpp float mBaseCost; int mFlags; // Glow color for enchanted items with this effect int mRed, mGreen, mBlue; float mUnknown1; // Called "Size X" in CS float mSpeed; // Speed of fired projectile float mUnknown2; // Called "Size Cap" in CS }; // 36 bytes static const std::map sNames; static const std::string &effectIdToString(short effectID); static short effectStringToId(std::string_view effect); /// Returns the effect that provides resistance against \a effect (or -1 if there's none) static short getResistanceEffect(short effect); /// Returns the effect that induces weakness against \a effect (or -1 if there's none) static short getWeaknessEffect(short effect); MagnitudeDisplayType getMagnitudeDisplayType() const; MEDTstruct mData; std::string mIcon, mParticle; // Textures std::string mCasting, mHit, mArea; // Static std::string mBolt; // Weapon std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds std::string mDescription; // Index of this magical effect. Corresponds to one of the // hard-coded effects in the original engine: // 0-136 in Morrowind // 137 in Tribunal // 138-140 in Bloodmoon (also changes 64?) // 141-142 are summon effects introduced in bloodmoon, but not used // there. They can be redefined in mods by setting the name in GMST // sEffectSummonCreature04/05 creature id in // sMagicCreature04ID/05ID. int mIndex; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID/index). void blank(); enum Effects { WaterBreathing = 0, SwiftSwim = 1, WaterWalking = 2, Shield = 3, FireShield = 4, LightningShield = 5, FrostShield = 6, Burden = 7, Feather = 8, Jump = 9, Levitate = 10, SlowFall = 11, Lock = 12, Open = 13, FireDamage = 14, ShockDamage = 15, FrostDamage = 16, DrainAttribute = 17, DrainHealth = 18, DrainMagicka = 19, DrainFatigue = 20, DrainSkill = 21, DamageAttribute = 22, DamageHealth = 23, DamageMagicka = 24, DamageFatigue = 25, DamageSkill = 26, Poison = 27, WeaknessToFire = 28, WeaknessToFrost = 29, WeaknessToShock = 30, WeaknessToMagicka = 31, WeaknessToCommonDisease = 32, WeaknessToBlightDisease = 33, WeaknessToCorprusDisease = 34, WeaknessToPoison = 35, WeaknessToNormalWeapons = 36, DisintegrateWeapon = 37, DisintegrateArmor = 38, Invisibility = 39, Chameleon = 40, Light = 41, Sanctuary = 42, NightEye = 43, Charm = 44, Paralyze = 45, Silence = 46, Blind = 47, Sound = 48, CalmHumanoid = 49, CalmCreature = 50, FrenzyHumanoid = 51, FrenzyCreature = 52, DemoralizeHumanoid = 53, DemoralizeCreature = 54, RallyHumanoid = 55, RallyCreature = 56, Dispel = 57, Soultrap = 58, Telekinesis = 59, Mark = 60, Recall = 61, DivineIntervention = 62, AlmsiviIntervention = 63, DetectAnimal = 64, DetectEnchantment = 65, DetectKey = 66, SpellAbsorption = 67, Reflect = 68, CureCommonDisease = 69, CureBlightDisease = 70, CureCorprusDisease = 71, CurePoison = 72, CureParalyzation = 73, RestoreAttribute = 74, RestoreHealth = 75, RestoreMagicka = 76, RestoreFatigue = 77, RestoreSkill = 78, FortifyAttribute = 79, FortifyHealth = 80, FortifyMagicka= 81, FortifyFatigue = 82, FortifySkill = 83, FortifyMaximumMagicka = 84, AbsorbAttribute = 85, AbsorbHealth = 86, AbsorbMagicka = 87, AbsorbFatigue = 88, AbsorbSkill = 89, ResistFire = 90, ResistFrost = 91, ResistShock = 92, ResistMagicka = 93, ResistCommonDisease = 94, ResistBlightDisease = 95, ResistCorprusDisease = 96, ResistPoison = 97, ResistNormalWeapons = 98, ResistParalysis = 99, RemoveCurse = 100, TurnUndead = 101, SummonScamp = 102, SummonClannfear = 103, SummonDaedroth = 104, SummonDremora = 105, SummonAncestralGhost = 106, SummonSkeletalMinion = 107, SummonBonewalker = 108, SummonGreaterBonewalker = 109, SummonBonelord = 110, SummonWingedTwilight = 111, SummonHunger = 112, SummonGoldenSaint = 113, SummonFlameAtronach = 114, SummonFrostAtronach = 115, SummonStormAtronach = 116, FortifyAttack = 117, CommandCreature = 118, CommandHumanoid = 119, BoundDagger = 120, BoundLongsword = 121, BoundMace = 122, BoundBattleAxe = 123, BoundSpear = 124, BoundLongbow = 125, ExtraSpell = 126, BoundCuirass = 127, BoundHelm = 128, BoundBoots = 129, BoundShield = 130, BoundGloves = 131, Corprus = 132, Vampirism = 133, SummonCenturionSphere = 134, SunDamage = 135, StuntedMagicka = 136, // Tribunal only SummonFabricant = 137, // Bloodmoon only SummonWolf = 138, SummonBear = 139, SummonBonewolf = 140, SummonCreature04 = 141, SummonCreature05 = 142, Length }; static std::string indexToId (int index); }; } #endif openmw-openmw-0.48.0/components/esm3/loadmisc.cpp000066400000000000000000000043431445372753700217730ustar00rootroot00000000000000#include "loadmisc.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("MCDT"): esm.getHTSized<12>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing MCDT subrecord"); } void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("MCDT", mData, 12); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } void Miscellaneous::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mIsKey = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadmisc.hpp000066400000000000000000000021401445372753700217710ustar00rootroot00000000000000#ifndef OPENMW_ESM_MISC_H #define OPENMW_ESM_MISC_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Misc inventory items, basically things that have no use but can be * carried, bought and sold. It also includes keys. */ struct Miscellaneous { constexpr static RecNameInts sRecordId = REC_MISC; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Miscellaneous"; } struct MCDTstruct { float mWeight; int mValue; int mIsKey; // There are many keys in Morrowind.esm that has this // set to 0. TODO: Check what this field corresponds to // in the editor. }; MCDTstruct mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadnpc.cpp000066400000000000000000000163641445372753700216260ustar00rootroot00000000000000#include "loadnpc.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void NPC::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mSpells.mList.clear(); mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); mAiData.blank(); mAiData.mHello = mAiData.mFight = mAiData.mFlee = 30; bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("RNAM"): mRace = esm.getHString(); break; case fourCC("CNAM"): mClass = esm.getHString(); break; case fourCC("ANAM"): mFaction = esm.getHString(); break; case fourCC("BNAM"): mHead = esm.getHString(); break; case fourCC("KNAM"): mHair = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); if (esm.getSubSize() == 52) { mNpdtType = NPC_DEFAULT; esm.getExact(&mNpdt, 52); } else if (esm.getSubSize() == 12) { //Reading into temporary NPDTstruct12 object NPDTstruct12 npdt12; mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; esm.getExact(&npdt12, 12); //Clearing the mNdpt struct to initialize all values blankNpdt(); //Swiching to an internal representation mNpdt.mLevel = npdt12.mLevel; mNpdt.mDisposition = npdt12.mDisposition; mNpdt.mReputation = npdt12.mReputation; mNpdt.mRank = npdt12.mRank; mNpdt.mGold = npdt12.mGold; } else esm.fail("NPC_NPDT must be 12 or 52 bytes long"); break; case fourCC("FLAG"): hasFlags = true; int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; break; case fourCC("NPCS"): mSpells.add(esm); break; case fourCC("NPCO"): mInventory.add(esm); break; case fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; case fourCC("DODT"): case fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: case AI_Activate: case AI_Escort: case AI_Follow: case AI_Travel: case AI_CNDT: mAiPackage.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } void NPC::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNCString("RNAM", mRace); esm.writeHNCString("CNAM", mClass); esm.writeHNCString("ANAM", mFaction); esm.writeHNCString("BNAM", mHead); esm.writeHNCString("KNAM", mHair); esm.writeHNOCString("SCRI", mScript); if (mNpdtType == NPC_DEFAULT) { esm.writeHNT("NPDT", mNpdt, 52); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { NPDTstruct12 npdt12; npdt12.mLevel = mNpdt.mLevel; npdt12.mDisposition = mNpdt.mDisposition; npdt12.mReputation = mNpdt.mReputation; npdt12.mRank = mNpdt.mRank; npdt12.mUnknown1 = 0; npdt12.mUnknown2 = 0; npdt12.mUnknown3 = 0; npdt12.mGold = mNpdt.mGold; esm.writeHNT("NPDT", npdt12, 12); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); mInventory.save(esm); mSpells.save(esm); esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); mTransport.save(esm); mAiPackage.save(esm); } bool NPC::isMale() const { return (mFlags & Female) == 0; } void NPC::setIsMale(bool value) { mFlags |= Female; if (value) { mFlags ^= Female; } } void NPC::blank() { mRecordFlags = 0; mNpdtType = NPC_DEFAULT; blankNpdt(); mBloodType = 0; mFlags = 0; mInventory.mList.clear(); mSpells.mList.clear(); mAiData.blank(); mAiData.mHello = mAiData.mFight = mAiData.mFlee = 30; mTransport.mList.clear(); mAiPackage.mList.clear(); mName.clear(); mModel.clear(); mRace.clear(); mClass.clear(); mFaction.clear(); mScript.clear(); mHair.clear(); mHead.clear(); } void NPC::blankNpdt() { mNpdt.mLevel = 0; mNpdt.mStrength = mNpdt.mIntelligence = mNpdt.mWillpower = mNpdt.mAgility = mNpdt.mSpeed = mNpdt.mEndurance = mNpdt.mPersonality = mNpdt.mLuck = 0; for (int i=0; i< Skill::Length; ++i) mNpdt.mSkills[i] = 0; mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; mNpdt.mDisposition = 0; mNpdt.mUnknown1 = 0; mNpdt.mRank = 0; mNpdt.mUnknown2 = 0; mNpdt.mGold = 0; } int NPC::getFactionRank() const { if (mFaction.empty()) return -1; else return mNpdt.mRank; } const std::vector& NPC::getTransport() const { return mTransport.mList; } } openmw-openmw-0.48.0/components/esm3/loadnpc.hpp000066400000000000000000000066251445372753700216320ustar00rootroot00000000000000#ifndef OPENMW_ESM_NPC_H #define OPENMW_ESM_NPC_H #include #include #include "components/esm/defs.hpp" #include "loadcont.hpp" #include "aipackage.hpp" #include "spelllist.hpp" #include "loadskil.hpp" #include "transport.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * NPC definition */ struct NPC { constexpr static RecNameInts sRecordId = REC_NPC_; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "NPC"; } // Services enum Services { // This merchant buys: Weapon = 0x00001, Armor = 0x00002, Clothing = 0x00004, Books = 0x00008, Ingredients = 0x00010, Picks = 0x00020, Probes = 0x00040, Lights = 0x00080, Apparatus = 0x00100, RepairItem = 0x00200, Misc = 0x00400, Potions = 0x02000, AllItems = Weapon|Armor|Clothing|Books|Ingredients|Picks|Probes|Lights|Apparatus|RepairItem|Misc|Potions, // Other services Spells = 0x00800, MagicItems = 0x01000, Training = 0x04000, Spellmaking = 0x08000, Enchanting = 0x10000, Repair = 0x20000 }; enum Flags { Female = 0x01, Essential = 0x02, Respawn = 0x04, Base = 0x08, Autocalc = 0x10 }; enum NpcType { NPC_WITH_AUTOCALCULATED_STATS = 12, NPC_DEFAULT = 52 }; #pragma pack(push) #pragma pack(1) struct NPDTstruct52 { short mLevel; unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; // mSkill can grow up to 200, it must be unsigned unsigned char mSkills[Skill::Length]; char mUnknown1; unsigned short mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; char mUnknown2; int mGold; }; // 52 bytes //Structure for autocalculated characters. // This is only used for load and save operations. struct NPDTstruct12 { short mLevel; // see above unsigned char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; int mGold; }; // 12 bytes #pragma pack(pop) unsigned char mNpdtType; //Worth noting when saving the struct: // Although we might read a NPDTstruct12 in, we use NPDTstruct52 internally NPDTstruct52 mNpdt; int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank int mBloodType; unsigned char mFlags; InventoryList mInventory; SpellList mSpells; AIData mAiData; Transport mTransport; const std::vector& getTransport() const; AIPackageList mAiPackage; unsigned int mRecordFlags; std::string mId, mName, mModel, mRace, mClass, mFaction, mScript; // body parts std::string mHair, mHead; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; bool isMale() const; void setIsMale(bool value); void blank(); ///< Set record to default state (does not touch the ID). /// Resets the mNpdt object void blankNpdt(); }; } #endif openmw-openmw-0.48.0/components/esm3/loadpgrd.cpp000066400000000000000000000141211445372753700217670ustar00rootroot00000000000000#include "loadpgrd.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { mX = static_cast(rhs[0]); mY = static_cast(rhs[1]); mZ = static_cast(rhs[2]); mAutogenerated = 0; mConnectionNum = 0; mUnknown = 0; return *this; } Pathgrid::Point::Point(const float rhs[3]) : mX(static_cast(rhs[0])), mY(static_cast(rhs[1])), mZ(static_cast(rhs[2])), mAutogenerated(0), mConnectionNum(0), mUnknown(0) { } Pathgrid::Point::Point():mX(0),mY(0),mZ(0),mAutogenerated(0), mConnectionNum(0),mUnknown(0) { } void Pathgrid::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mPoints.clear(); mEdges.clear(); // keep track of total connections so we can reserve edge vector size int edgeCount = 0; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mCell = esm.getHString(); break; case fourCC("DATA"): esm.getHTSized<12>(mData); hasData = true; break; case fourCC("PGRP"): { esm.getSubHeader(); int size = esm.getSubSize(); // Check that the sizes match up. Size = 16 * s2 (path points) if (size != static_cast (sizeof(Point) * mData.mS2)) esm.fail("Path point subrecord size mismatch"); else { int pointCount = mData.mS2; mPoints.reserve(pointCount); for (int i = 0; i < pointCount; ++i) { Point p; esm.getExact(&p, sizeof(Point)); mPoints.push_back(p); edgeCount += p.mConnectionNum; } } break; } case fourCC("PGRC"): { esm.getSubHeader(); int size = esm.getSubSize(); if (size % sizeof(int) != 0) esm.fail("PGRC size not a multiple of 4"); else { int rawConnNum = size / sizeof(int); std::vector rawConnections; rawConnections.reserve(rawConnNum); for (int i = 0; i < rawConnNum; ++i) { int currentValue; esm.getT(currentValue); rawConnections.push_back(currentValue); } std::vector::const_iterator rawIt = rawConnections.begin(); int pointIndex = 0; mEdges.reserve(edgeCount); for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) { unsigned char connectionNum = (*it).mConnectionNum; if (rawConnections.end() - rawIt < connectionNum) esm.fail("Not enough connections"); for (int i = 0; i < connectionNum; ++i) { Edge edge; edge.mV0 = pointIndex; edge.mV1 = *rawIt; ++rawIt; mEdges.push_back(edge); } } } break; } case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasData) esm.fail("Missing DATA subrecord"); } void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { // Correct connection count and sort edges by point // Can probably be optimized PointList correctedPoints = mPoints; std::vector sortedEdges; sortedEdges.reserve(mEdges.size()); for (size_t point = 0; point < correctedPoints.size(); ++point) { correctedPoints[point].mConnectionNum = 0; for (EdgeList::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) { if (static_cast(it->mV0) == point) { sortedEdges.push_back(it->mV1); ++correctedPoints[point].mConnectionNum; } } } // Save esm.writeHNCString("NAME", mCell); esm.writeHNT("DATA", mData, 12); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (!correctedPoints.empty()) { esm.startSubRecord("PGRP"); for (PointList::const_iterator it = correctedPoints.begin(); it != correctedPoints.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRP"); } if (!sortedEdges.empty()) { esm.startSubRecord("PGRC"); for (std::vector::const_iterator it = sortedEdges.begin(); it != sortedEdges.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRC"); } } void Pathgrid::blank() { mCell.clear(); mData.mX = 0; mData.mY = 0; mData.mS1 = 0; mData.mS2 = 0; mPoints.clear(); mEdges.clear(); } } openmw-openmw-0.48.0/components/esm3/loadpgrd.hpp000066400000000000000000000032711445372753700220000ustar00rootroot00000000000000#ifndef OPENMW_ESM_PGRD_H #define OPENMW_ESM_PGRD_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Path grid. */ struct Pathgrid { constexpr static RecNameInts sRecordId = REC_PGRD; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Pathgrid"; } struct DATAstruct { int mX, mY; // Grid location, matches cell for exterior cells short mS1; // ?? Usually but not always a power of 2. Doesn't seem // to have any relation to the size of PGRC. short mS2; // Number of path points. }; // 12 bytes struct Point // path grid point { int mX, mY, mZ; // Location of point unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point short mUnknown; Point& operator=(const float[3]); Point(const float[3]); Point(); Point(int x, int y, int z) : mX(x), mY(y), mZ(z) , mAutogenerated(0), mConnectionNum(0), mUnknown(0) {} }; // 16 bytes struct Edge // path grid edge { int mV0, mV1; // index of points connected with this edge }; // 8 bytes std::string mCell; // Cell name DATAstruct mData; typedef std::vector PointList; PointList mPoints; typedef std::vector EdgeList; EdgeList mEdges; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; } #endif openmw-openmw-0.48.0/components/esm3/loadprob.cpp000066400000000000000000000043461445372753700220050ustar00rootroot00000000000000#include "loadprob.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Probe::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("PBDT"): esm.getHTSized<16>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing PBDT subrecord"); } void Probe::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("PBDT", mData, 16); esm.writeHNOString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } void Probe::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadprob.hpp000066400000000000000000000015041445372753700220030ustar00rootroot00000000000000#ifndef OPENMW_ESM_PROBE_H #define OPENMW_ESM_PROBE_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Probe { constexpr static RecNameInts sRecordId = REC_PROB; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Probe"; } struct Data { float mWeight; int mValue; float mQuality; int mUses; }; // Size = 16 Data mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadrace.cpp000066400000000000000000000050511445372753700217470ustar00rootroot00000000000000#include "loadrace.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; } float Race::MaleFemaleF::getValue (bool male) const { return male ? mMale : mFemale; } void Race::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("RADT"): esm.getHTSized<140>(mData); hasData = true; break; case fourCC("DESC"): mDescription = esm.getHString(); break; case fourCC("NPCS"): mPowers.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing RADT subrecord"); } void Race::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); } void Race::blank() { mRecordFlags = 0; mName.clear(); mDescription.clear(); mPowers.mList.clear(); for (int i=0; i<7; ++i) { mData.mBonus[i].mSkill = -1; mData.mBonus[i].mBonus = 0; } for (int i=0; i<8; ++i) mData.mAttributeValues[i].mMale = mData.mAttributeValues[i].mFemale = 1; mData.mHeight.mMale = mData.mHeight.mFemale = 1; mData.mWeight.mMale = mData.mWeight.mFemale = 1; mData.mFlags = 0; } } openmw-openmw-0.48.0/components/esm3/loadrace.hpp000066400000000000000000000031111445372753700217470ustar00rootroot00000000000000#ifndef OPENMW_ESM_RACE_H #define OPENMW_ESM_RACE_H #include #include "spelllist.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Race definition */ struct Race { constexpr static RecNameInts sRecordId = REC_RACE; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Race"; } struct SkillBonus { int mSkill; // SkillEnum int mBonus; }; struct MaleFemale { int mMale, mFemale; int getValue (bool male) const; }; struct MaleFemaleF { float mMale, mFemale; float getValue (bool male) const; }; enum Flags { Playable = 0x01, Beast = 0x02 }; struct RADTstruct { // List of skills that get a bonus SkillBonus mBonus[7]; // Attribute values for male/female MaleFemale mAttributeValues[8]; // The actual eye level height (in game units) is (probably) given // as 'height' times 128. This has not been tested yet. MaleFemaleF mHeight, mWeight; int mFlags; // 0x1 - playable, 0x2 - beast race }; // Size = 140 bytes RADTstruct mData; unsigned int mRecordFlags; std::string mId, mName, mDescription; SpellList mPowers; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadregn.cpp000066400000000000000000000065371445372753700220020ustar00rootroot00000000000000#include "loadregn.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Region::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("WEAT"): { esm.getSubHeader(); // Cold weather not included before 1.3 if (esm.getSubSize() == sizeof(mData)) { esm.getT(mData); } else if (esm.getSubSize() == sizeof(mData) - 2) { mData.mSnow = 0; mData.mBlizzard = 0; esm.getExact(&mData, sizeof(mData) - 2); } else { esm.fail("Don't know what to do in this version"); } break; } case fourCC("BNAM"): mSleepList = esm.getHString(); break; case fourCC("CNAM"): esm.getHT(mMapColor); break; case fourCC("SNAM"): { esm.getSubHeader(); SoundRef sr; sr.mSound.assign(esm.getString(32)); esm.getT(sr.mChance); mSoundList.push_back(sr); break; } case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); } void Region::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) esm.writeHNT("WEAT", mData, sizeof(mData) - 2); else esm.writeHNT("WEAT", mData); esm.writeHNOCString("BNAM", mSleepList); esm.writeHNT("CNAM", mMapColor); for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) { esm.startSubRecord("SNAM"); esm.writeFixedSizeString(it->mSound, 32); esm.writeT(it->mChance); esm.endRecord("SNAM"); } } void Region::blank() { mRecordFlags = 0; mData.mClear = mData.mCloudy = mData.mFoggy = mData.mOvercast = mData.mRain = mData.mThunder = mData.mAsh = mData.mBlight = mData.mSnow = mData.mBlizzard = 0; mMapColor = 0; mName.clear(); mSleepList.clear(); mSoundList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadregn.hpp000066400000000000000000000025561445372753700220040ustar00rootroot00000000000000#ifndef OPENMW_ESM_REGN_H #define OPENMW_ESM_REGN_H #include #include #include "components/esm/defs.hpp" #include "components/esm/esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Region data */ struct Region { constexpr static RecNameInts sRecordId = REC_REGN; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Region"; } #pragma pack(push) #pragma pack(1) struct WEATstruct { // These are probabilities that add up to 100 unsigned char mClear, mCloudy, mFoggy, mOvercast, mRain, mThunder, mAsh, mBlight, mSnow, mBlizzard; }; // 10 bytes #pragma pack(pop) // Reference to a sound that is played randomly in this region struct SoundRef { std::string mSound; unsigned char mChance; }; WEATstruct mData; int mMapColor; // RGBA unsigned int mRecordFlags; // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. std::string mId, mName, mSleepList; std::vector mSoundList; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadrepa.cpp000066400000000000000000000043511445372753700217660ustar00rootroot00000000000000#include "loadrepa.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Repair::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("RIDT"): esm.getHTSized<16>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing RIDT subrecord"); } void Repair::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("RIDT", mData, 16); esm.writeHNOString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } void Repair::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mQuality = 0; mData.mUses = 0; mName.clear(); mModel.clear(); mIcon.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadrepa.hpp000066400000000000000000000015041445372753700217700ustar00rootroot00000000000000#ifndef OPENMW_ESM_REPA_H #define OPENMW_ESM_REPA_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Repair { constexpr static RecNameInts sRecordId = REC_REPA; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Repair"; } struct Data { float mWeight; int mValue; int mUses; float mQuality; }; // Size = 16 Data mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadscpt.cpp000066400000000000000000000143241445372753700220110ustar00rootroot00000000000000#include "loadscpt.hpp" #include #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Script::loadSCVR(ESMReader &esm) { int s = mData.mStringTableSize; std::vector tmp (s); // not using getHExact, vanilla doesn't seem to mind unused bytes at the end esm.getSubHeader(); int left = esm.getSubSize(); if (left < s) esm.fail("SCVR string list is smaller than specified"); esm.getExact(tmp.data(), s); if (left > s) esm.skip(left-s); // skip the leftover junk // Set up the list of variable names mVarNames.resize(mData.mNumShorts + mData.mNumLongs + mData.mNumFloats); // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. char* str = tmp.data(); if (tmp.empty()) { if (mVarNames.size() > 0) Log(Debug::Warning) << "SCVR with no variable names"; return; } // Support '\r' terminated strings like vanilla. See Bug #1324. std::replace(tmp.begin(), tmp.end(), '\r', '\0'); // Avoid heap corruption if (tmp.back() != '\0') { tmp.emplace_back('\0'); std::stringstream ss; ss << "Malformed string table"; ss << "\n File: " << esm.getName(); ss << "\n Record: " << esm.getContext().recName.toStringView(); ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); str = tmp.data(); } const auto tmpEnd = tmp.data() + tmp.size(); for (size_t i = 0; i < mVarNames.size(); i++) { mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; if (str >= tmpEnd) { if(str > tmpEnd) { // SCVR subrecord is unused and variable names are determined // from the script source, so an overflow is not fatal. std::stringstream ss; ss << "String table overflow"; ss << "\n File: " << esm.getName(); ss << "\n Record: " << esm.getContext().recName.toStringView(); ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); } // Get rid of empty strings in the list. mVarNames.resize(i+1); break; } } } void Script::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mVarNames.clear(); bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("SCHD"): { esm.getSubHeader(); mId = esm.getString(32); esm.getT(mData); hasHeader = true; break; } case fourCC("SCVR"): // list of local variables loadSCVR(esm); break; case fourCC("SCDT"): { // compiled script esm.getSubHeader(); uint32_t subSize = esm.getSubSize(); if (subSize != static_cast(mData.mScriptDataSize)) { std::stringstream ss; ss << "Script data size defined in SCHD subrecord does not match size of SCDT subrecord"; ss << "\n File: " << esm.getName(); ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); } mScriptData.resize(subSize); esm.getExact(mScriptData.data(), mScriptData.size()); break; } case fourCC("SCTX"): mScriptText = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasHeader) esm.fail("Missing SCHD subrecord"); } void Script::save(ESMWriter &esm, bool isDeleted) const { std::string varNameString; if (!mVarNames.empty()) for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) varNameString.append(*it); esm.startSubRecord("SCHD"); esm.writeFixedSizeString(mId, 32); esm.writeT(mData, 20); esm.endRecord("SCHD"); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) { esm.writeHCString(*it); } esm.endRecord("SCVR"); } esm.startSubRecord("SCDT"); esm.write(reinterpret_cast(mScriptData.data()), mData.mScriptDataSize); esm.endRecord("SCDT"); esm.writeHNOString("SCTX", mScriptText); } void Script::blank() { mRecordFlags = 0; mData.mNumShorts = mData.mNumLongs = mData.mNumFloats = 0; mData.mScriptDataSize = 0; mData.mStringTableSize = 0; mVarNames.clear(); mScriptData.clear(); if (mId.find ("::")!=std::string::npos) mScriptText = "Begin \"" + mId + "\"\n\nEnd " + mId + "\n"; else mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n"; } } openmw-openmw-0.48.0/components/esm3/loadscpt.hpp000066400000000000000000000031451445372753700220150ustar00rootroot00000000000000#ifndef OPENMW_ESM_SCPT_H #define OPENMW_ESM_SCPT_H #include #include #include "components/esm/defs.hpp" #include "components/esm/esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Script definitions */ class Script { public: constexpr static RecNameInts sRecordId = REC_SCPT; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Script"; } struct SCHDstruct { /// Data from script-precompling in the editor. /// \warning Do not use them. OpenCS currently does not precompile scripts. int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; }; struct SCHD { std::string mName; Script::SCHDstruct mData; }; unsigned int mRecordFlags; std::string mId; SCHDstruct mData; /// Variable names generated by script-precompiling in the editor. /// \warning Do not use this field. OpenCS currently does not precompile scripts. std::vector mVarNames; /// Bytecode generated from script-precompiling in the editor. /// \warning Do not use this field. OpenCS currently does not precompile scripts. std::vector mScriptData; /// Script source code std::string mScriptText; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). private: void loadSCVR(ESMReader &esm); }; } #endif openmw-openmw-0.48.0/components/esm3/loadskil.cpp000066400000000000000000000117251445372753700220040ustar00rootroot00000000000000#include "loadskil.hpp" #include #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { const std::string Skill::sSkillNames[Length] = { "Block", "Armorer", "Mediumarmor", "Heavyarmor", "Bluntweapon", "Longblade", "Axe", "Spear", "Athletics", "Enchant", "Destruction", "Alteration", "Illusion", "Conjuration", "Mysticism", "Restoration", "Alchemy", "Unarmored", "Security", "Sneak", "Acrobatics", "Lightarmor", "Shortblade", "Marksman", "Mercantile", "Speechcraft", "Handtohand", }; const std::string Skill::sSkillNameIds[Length] = { "sSkillBlock", "sSkillArmorer", "sSkillMediumarmor", "sSkillHeavyarmor", "sSkillBluntweapon", "sSkillLongblade", "sSkillAxe", "sSkillSpear", "sSkillAthletics", "sSkillEnchant", "sSkillDestruction", "sSkillAlteration", "sSkillIllusion", "sSkillConjuration", "sSkillMysticism", "sSkillRestoration", "sSkillAlchemy", "sSkillUnarmored", "sSkillSecurity", "sSkillSneak", "sSkillAcrobatics", "sSkillLightarmor", "sSkillShortblade", "sSkillMarksman", "sSkillMercantile", "sSkillSpeechcraft", "sSkillHandtohand", }; const std::string Skill::sIconNames[Length] = { "combat_block.dds", "combat_armor.dds", "combat_mediumarmor.dds", "combat_heavyarmor.dds", "combat_blunt.dds", "combat_longblade.dds", "combat_axe.dds", "combat_spear.dds", "combat_athletics.dds", "magic_enchant.dds", "magic_destruction.dds", "magic_alteration.dds", "magic_illusion.dds", "magic_conjuration.dds", "magic_mysticism.dds", "magic_restoration.dds", "magic_alchemy.dds", "magic_unarmored.dds", "stealth_security.dds", "stealth_sneak.dds", "stealth_acrobatics.dds", "stealth_lightarmor.dds", "stealth_shortblade.dds", "stealth_marksman.dds", "stealth_mercantile.dds", "stealth_speechcraft.dds", "stealth_handtohand.dds", }; const std::array Skill::sSkillIds = {{ Block, Armorer, MediumArmor, HeavyArmor, BluntWeapon, LongBlade, Axe, Spear, Athletics, Enchant, Destruction, Alteration, Illusion, Conjuration, Mysticism, Restoration, Alchemy, Unarmored, Security, Sneak, Acrobatics, LightArmor, ShortBlade, Marksman, Mercantile, Speechcraft, HandToHand }}; void Skill::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) mRecordFlags = esm.getRecordFlags(); bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("INDX"): esm.getHT(mIndex); hasIndex = true; break; case fourCC("SKDT"): esm.getHTSized<24>(mData); hasData = true; break; case fourCC("DESC"): mDescription = esm.getHString(); break; default: esm.fail("Unknown subrecord"); } } if (!hasIndex) esm.fail("Missing INDX"); if (!hasData) esm.fail("Missing SKDT"); // create an ID from the index and the name (only used in the editor and likely to change in the // future) mId = indexToId (mIndex); } void Skill::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); esm.writeHNOString("DESC", mDescription); } void Skill::blank() { mRecordFlags = 0; mData.mAttribute = 0; mData.mSpecialization = 0; mData.mUseValue[0] = mData.mUseValue[1] = mData.mUseValue[2] = mData.mUseValue[3] = 1.0; mDescription.clear(); } std::string Skill::indexToId (int index) { std::ostringstream stream; if (index!=-1) { stream << "#"; if (index<10) stream << "0"; stream << index; if (index>=0 && index #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Skill information * */ struct Skill { constexpr static RecNameInts sRecordId = REC_SKIL; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Skill"; } unsigned int mRecordFlags; std::string mId; struct SKDTstruct { int mAttribute; // see defs.hpp int mSpecialization;// 0 - Combat, 1 - Magic, 2 - Stealth float mUseValue[4]; // How much skill improves through use. Meaning // of each field depends on what skill this // is. We should document this better later. }; // Total size: 24 bytes SKDTstruct mData; // Skill index. Skils don't have an id ("NAME") like most records, // they only have a numerical index that matches one of the // hard-coded skills in the game. int mIndex; std::string mDescription; enum SkillEnum { Block = 0, Armorer = 1, MediumArmor = 2, HeavyArmor = 3, BluntWeapon = 4, LongBlade = 5, Axe = 6, Spear = 7, Athletics = 8, Enchant = 9, Destruction = 10, Alteration = 11, Illusion = 12, Conjuration = 13, Mysticism = 14, Restoration = 15, Alchemy = 16, Unarmored = 17, Security = 18, Sneak = 19, Acrobatics = 20, LightArmor = 21, ShortBlade = 22, Marksman = 23, Mercantile = 24, Speechcraft = 25, HandToHand = 26, Length }; static const std::string sSkillNames[Length]; static const std::string sSkillNameIds[Length]; static const std::string sIconNames[Length]; static const std::array sSkillIds; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). static std::string indexToId (int index); }; } #endif openmw-openmw-0.48.0/components/esm3/loadsndg.cpp000066400000000000000000000035771445372753700220030ustar00rootroot00000000000000#include "loadsndg.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("DATA"): esm.getHTSized<4>(mType); hasData = true; break; case fourCC("CNAM"): mCreature = esm.getHString(); break; case fourCC("SNAM"): mSound = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing DATA subrecord"); } void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); } void SoundGenerator::blank() { mRecordFlags = 0; mType = LeftFoot; mCreature.clear(); mSound.clear(); } } openmw-openmw-0.48.0/components/esm3/loadsndg.hpp000066400000000000000000000016311445372753700217750ustar00rootroot00000000000000#ifndef OPENMW_ESM_SNDG_H #define OPENMW_ESM_SNDG_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Sound generator. This describes the sounds a creature make. */ struct SoundGenerator { constexpr static RecNameInts sRecordId = REC_SNDG; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "SoundGenerator"; } enum Type { LeftFoot = 0, RightFoot = 1, SwimLeft = 2, SwimRight = 3, Moan = 4, Roar = 5, Scream = 6, Land = 7 }; // Type int mType; unsigned int mRecordFlags; std::string mId, mCreature, mSound; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; } #endif openmw-openmw-0.48.0/components/esm3/loadsoun.cpp000066400000000000000000000032741445372753700220260ustar00rootroot00000000000000#include "loadsoun.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Sound::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mSound = esm.getHString(); break; case fourCC("DATA"): esm.getHTSized<3>(mData); hasData = true; break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing DATA subrecord"); } void Sound::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } void Sound::blank() { mRecordFlags = 0; mSound.clear(); mData.mVolume = 128; mData.mMinRange = 0; mData.mMaxRange = 255; } } openmw-openmw-0.48.0/components/esm3/loadsoun.hpp000066400000000000000000000013751445372753700220330ustar00rootroot00000000000000#ifndef OPENMW_ESM_SOUN_H #define OPENMW_ESM_SOUN_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct SOUNstruct { unsigned char mVolume, mMinRange, mMaxRange; }; struct Sound { constexpr static RecNameInts sRecordId = REC_SOUN; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Sound"; } SOUNstruct mData; unsigned int mRecordFlags; std::string mId, mSound; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadspel.cpp000066400000000000000000000037111445372753700220010ustar00rootroot00000000000000#include "loadspel.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Spell::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("SPDT"): esm.getHTSized<12>(mData); hasData = true; break; case fourCC("ENAM"): ENAMstruct s; esm.getHTSized<24>(s); mEffects.mList.push_back(s); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing SPDT subrecord"); } void Spell::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); } void Spell::blank() { mRecordFlags = 0; mData.mType = 0; mData.mCost = 0; mData.mFlags = 0; mName.clear(); mEffects.mList.clear(); } } openmw-openmw-0.48.0/components/esm3/loadspel.hpp000066400000000000000000000026131445372753700220060ustar00rootroot00000000000000#ifndef OPENMW_ESM_SPEL_H #define OPENMW_ESM_SPEL_H #include #include "effectlist.hpp" #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; struct Spell { constexpr static RecNameInts sRecordId = REC_SPEL; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Spell"; } enum SpellType { ST_Spell = 0, // Normal spell, must be cast and costs mana ST_Ability = 1, // Inert ability, always in effect ST_Blight = 2, // Blight disease ST_Disease = 3, // Common disease ST_Curse = 4, // Curse (?) ST_Power = 5 // Power, can use once a day }; enum Flags { F_Autocalc = 1, // Can be selected by NPC spells auto-calc F_PCStart = 2, // Can be selected by player spells auto-calc F_Always = 4 // Casting always succeeds }; struct SPDTstruct { int mType; // SpellType int mCost; // Mana cost int mFlags; // Flags }; SPDTstruct mData; unsigned int mRecordFlags; std::string mId, mName; EffectList mEffects; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). }; } #endif openmw-openmw-0.48.0/components/esm3/loadsscr.cpp000066400000000000000000000027231445372753700220120ustar00rootroot00000000000000#include "loadsscr.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void StartScript::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("DATA"): mData = esm.getHString(); hasData = true; break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME"); if (!hasData && !isDeleted) esm.fail("Missing DATA"); } void StartScript::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); } else { esm.writeHNString("DATA", mData); } } void StartScript::blank() { mRecordFlags = 0; mData.clear(); } } openmw-openmw-0.48.0/components/esm3/loadsscr.hpp000066400000000000000000000017031445372753700220140ustar00rootroot00000000000000#ifndef OPENMW_ESM_SSCR_H #define OPENMW_ESM_SSCR_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* Startup script. I think this is simply a 'main' script that is run from the begining. The SSCR records contain a DATA identifier which is totally useless (TODO: don't remember what it contains exactly, document it below later.), and a NAME which is simply a script reference. */ struct StartScript { constexpr static RecNameInts sRecordId = REC_SSCR; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "StartScript"; } std::string mData; unsigned int mRecordFlags; std::string mId; // Load a record and add it to the list void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; } #endif openmw-openmw-0.48.0/components/esm3/loadstat.cpp000066400000000000000000000027121445372753700220110ustar00rootroot00000000000000#include "loadstat.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Static::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); //bool isBlocked = (mRecordFlags & FLAG_Blocked) != 0; //bool isPersistent = (mRecordFlags & FLAG_Persistent) != 0; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); } void Static::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); } else { esm.writeHNCString("MODL", mModel); } } void Static::blank() { mRecordFlags = 0; mModel.clear(); } } openmw-openmw-0.48.0/components/esm3/loadstat.hpp000066400000000000000000000022531445372753700220160ustar00rootroot00000000000000#ifndef OPENMW_ESM_STAT_H #define OPENMW_ESM_STAT_H #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Definition of static object. * * A stat record is basically just a reference to a nif file. Some * esps seem to contain copies of the STAT entries from the esms, and * the esms themselves contain several identical entries. Perhaps all * statics referenced in a file is also put in the file? Since we are * only reading files it doesn't much matter to us, but it would if we * were writing our own ESM/ESPs. You can check some files later when * you decode the CELL blocks, if you want to test this hypothesis. */ struct Static { constexpr static RecNameInts sRecordId = REC_STAT; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Static"; } unsigned int mRecordFlags; std::string mId, mModel; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; } #endif openmw-openmw-0.48.0/components/esm3/loadtes3.cpp000066400000000000000000000037041445372753700217160ustar00rootroot00000000000000#include "loadtes3.hpp" #include "components/esm/esmcommon.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void Header::blank() { mData.version = VER_13; mData.type = 0; mData.author.clear(); mData.desc.clear(); mData.records = 0; mFormat = CurrentFormat; mMaster.clear(); } void Header::load (ESMReader &esm) { if (esm.isNextSub ("FORM")) { esm.getHT (mFormat); if (mFormat<0) esm.fail ("invalid format code"); } else mFormat = 0; if (esm.isNextSub("HEDR")) { esm.getSubHeader(); esm.getT(mData.version); esm.getT(mData.type); mData.author.assign(esm.getMaybeFixedStringSize(32)); mData.desc.assign(esm.getMaybeFixedStringSize(256)); esm.getT(mData.records); } while (esm.isNextSub ("MAST")) { MasterData m; m.name = esm.getHString(); esm.getHNT(m.size, "DATA"); mMaster.push_back (m); } if (esm.isNextSub("GMDT")) { esm.getHT(mGameData); } if (esm.isNextSub("SCRD")) { esm.getSubHeader(); mSCRD.resize(esm.getSubSize()); if (!mSCRD.empty()) esm.getExact(mSCRD.data(), mSCRD.size()); } if (esm.isNextSub("SCRS")) { esm.getSubHeader(); mSCRS.resize(esm.getSubSize()); if (!mSCRS.empty()) esm.getExact(mSCRS.data(), mSCRS.size()); } } void Header::save (ESMWriter &esm) { if (mFormat>0) esm.writeHNT ("FORM", mFormat); esm.startSubRecord("HEDR"); esm.writeT(mData.version); esm.writeT(mData.type); esm.writeFixedSizeString(mData.author, 32); esm.writeFixedSizeString(mData.desc, 256); esm.writeT(mData.records); esm.endRecord("HEDR"); for (const Header::MasterData& data : mMaster) { esm.writeHNCString ("MAST", data.name); esm.writeHNT ("DATA", data.size); } } } openmw-openmw-0.48.0/components/esm3/loadtes3.hpp000066400000000000000000000031751445372753700217250ustar00rootroot00000000000000#ifndef COMPONENT_ESM_TES3_H #define COMPONENT_ESM_TES3_H #include #include "components/esm/esmcommon.hpp" namespace ESM { class ESMReader; class ESMWriter; #pragma pack(push) #pragma pack(1) struct Data { /* File format version. This is actually a float, the supported versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ unsigned int version; int type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description int records; // Number of records }; struct GMDT { float mCurrentHealth; float mMaximumHealth; float mHour; unsigned char unknown1[12]; NAME64 mCurrentCell; unsigned char unknown2[4]; NAME32 mPlayerName; }; #pragma pack(pop) /// \brief File header record struct Header { static constexpr int CurrentFormat = 1; // most recent known format // Defines another files (esm or esp) that this file depends upon. struct MasterData { std::string name; uint64_t size; }; GMDT mGameData; // Used in .ess savegames only std::vector mSCRD; // Used in .ess savegames only, unknown std::vector mSCRS; // Used in .ess savegames only, screenshot Data mData; int mFormat; std::vector mMaster; void blank(); void load (ESMReader &esm); void save (ESMWriter &esm); }; } #endif openmw-openmw-0.48.0/components/esm3/loadweap.cpp000066400000000000000000000052411445372753700217720ustar00rootroot00000000000000#include "loadweap.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include "components/esm/defs.hpp" namespace ESM { void Weapon::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getHString(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("WPDT"): esm.getHTSized<32>(mData); hasData = true; break; case fourCC("SCRI"): mScript = esm.getHString(); break; case fourCC("ITEX"): mIcon = esm.getHString(); break; case fourCC("ENAM"): mEnchant = esm.getHString(); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; default: esm.fail("Unknown subrecord"); } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) esm.fail("Missing WPDT subrecord"); } void Weapon::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("WPDT", mData, 32); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCString("ENAM", mEnchant); } void Weapon::blank() { mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; mData.mType = 0; mData.mHealth = 0; mData.mSpeed = 0; mData.mReach = 0; mData.mEnchant = 0; mData.mChop[0] = mData.mChop[1] = 0; mData.mSlash[0] = mData.mSlash[1] = 0; mData.mThrust[0] = mData.mThrust[1] = 0; mData.mFlags = 0; mName.clear(); mModel.clear(); mIcon.clear(); mEnchant.clear(); mScript.clear(); } } openmw-openmw-0.48.0/components/esm3/loadweap.hpp000066400000000000000000000044011445372753700217740ustar00rootroot00000000000000#ifndef OPENMW_ESM_WEAP_H #define OPENMW_ESM_WEAP_H #include #include "loadskil.hpp" namespace ESM { class ESMReader; class ESMWriter; /* * Weapon definition */ struct Weapon { constexpr static RecNameInts sRecordId = REC_WEAP; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Weapon"; } enum Type { PickProbe = -4, HandToHand = -3, Spell = -2, None = -1, ShortBladeOneHand = 0, LongBladeOneHand = 1, LongBladeTwoHand = 2, BluntOneHand = 3, BluntTwoClose = 4, BluntTwoWide = 5, SpearTwoWide = 6, AxeOneHand = 7, AxeTwoHand = 8, MarksmanBow = 9, MarksmanCrossbow = 10, MarksmanThrown = 11, Arrow = 12, Bolt = 13 }; enum AttackType { AT_Chop, AT_Slash, AT_Thrust }; enum Flags { Magical = 0x01, Silver = 0x02 }; #pragma pack(push) #pragma pack(1) struct WPDTstruct { float mWeight; int mValue; short mType; unsigned short mHealth; float mSpeed, mReach; unsigned short mEnchant; // Enchantment points. The real value is mEnchant/10.f unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max int mFlags; }; // 32 bytes #pragma pack(pop) WPDTstruct mData; unsigned int mRecordFlags; std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). }; struct WeaponType { enum Flags { TwoHanded = 0x01, HasHealth = 0x02 }; enum Class { Melee = 0, Ranged = 1, Thrown = 2, Ammo = 3 }; //std::string mDisplayName; // TODO: will be needed later for editor std::string mShortGroup; std::string mLongGroup; std::string mSoundId; std::string mAttachBone; std::string mSheathingBone; Skill::SkillEnum mSkill; Class mWeaponClass; int mAmmoType; int mFlags; }; } #endif openmw-openmw-0.48.0/components/esm3/locals.cpp000066400000000000000000000012041445372753700214460ustar00rootroot00000000000000#include "locals.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Locals::load (ESMReader &esm) { while (esm.isNextSub ("LOCA")) { std::string id = esm.getHString(); Variant value; value.read (esm, Variant::Format_Local); mVariables.emplace_back (id, value); } } void Locals::save (ESMWriter &esm) const { for (std::vector >::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { esm.writeHNString ("LOCA", iter->first); iter->second.write (esm, Variant::Format_Local); } } } openmw-openmw-0.48.0/components/esm3/locals.hpp000066400000000000000000000007621445372753700214630ustar00rootroot00000000000000#ifndef OPENMW_ESM_LOCALS_H #define OPENMW_ESM_LOCALS_H #include #include #include "variant.hpp" namespace ESM { class ESMReader; class ESMWriter; /// \brief Storage structure for local variables (only used in saved games) /// /// \note This is not a top-level record. struct Locals { std::vector > mVariables; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/magiceffects.cpp000066400000000000000000000013031445372753700226110ustar00rootroot00000000000000#include "magiceffects.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void MagicEffects::save(ESMWriter &esm) const { for (const auto& [key, params] : mEffects) { esm.writeHNT("EFID", key); esm.writeHNT("BASE", params.first); esm.writeHNT("MODI", params.second); } } void MagicEffects::load(ESMReader &esm) { while (esm.isNextSub("EFID")) { int id; std::pair params; esm.getHT(id); esm.getHNT(params.first, "BASE"); if(esm.getFormat() < 17) params.second = 0.f; else esm.getHNT(params.second, "MODI"); mEffects.emplace(id, params); } } } openmw-openmw-0.48.0/components/esm3/magiceffects.hpp000066400000000000000000000025261445372753700226260ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_MAGICEFFECTS_H #define COMPONENTS_ESM_MAGICEFFECTS_H #include #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct MagicEffects { // std::map> mEffects; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; struct SummonKey { SummonKey(int effectId, const std::string& sourceId, int index): mEffectId(effectId), mSourceId(sourceId), mEffectIndex(index) {} bool operator==(const SummonKey &other) const { return mEffectId == other.mEffectId && mSourceId == other.mSourceId && mEffectIndex == other.mEffectIndex; } bool operator<(const SummonKey &other) const { if (mEffectId < other.mEffectId) return true; if (mEffectId > other.mEffectId) return false; if (mSourceId < other.mSourceId) return true; if (mSourceId > other.mSourceId) return false; return mEffectIndex < other.mEffectIndex; } int mEffectId; std::string mSourceId; int mEffectIndex; }; } #endif openmw-openmw-0.48.0/components/esm3/mappings.cpp000066400000000000000000000100611445372753700220100ustar00rootroot00000000000000#include "mappings.hpp" #include namespace ESM { BodyPart::MeshPart getMeshPart(PartReferenceType type) { switch(type) { case PRT_Head: return BodyPart::MP_Head; case PRT_Hair: return BodyPart::MP_Hair; case PRT_Neck: return BodyPart::MP_Neck; case PRT_Cuirass: return BodyPart::MP_Chest; case PRT_Groin: return BodyPart::MP_Groin; case PRT_RHand: return BodyPart::MP_Hand; case PRT_LHand: return BodyPart::MP_Hand; case PRT_RWrist: return BodyPart::MP_Wrist; case PRT_LWrist: return BodyPart::MP_Wrist; case PRT_RForearm: return BodyPart::MP_Forearm; case PRT_LForearm: return BodyPart::MP_Forearm; case PRT_RUpperarm: return BodyPart::MP_Upperarm; case PRT_LUpperarm: return BodyPart::MP_Upperarm; case PRT_RFoot: return BodyPart::MP_Foot; case PRT_LFoot: return BodyPart::MP_Foot; case PRT_RAnkle: return BodyPart::MP_Ankle; case PRT_LAnkle: return BodyPart::MP_Ankle; case PRT_RKnee: return BodyPart::MP_Knee; case PRT_LKnee: return BodyPart::MP_Knee; case PRT_RLeg: return BodyPart::MP_Upperleg; case PRT_LLeg: return BodyPart::MP_Upperleg; case PRT_Tail: return BodyPart::MP_Tail; default: throw std::runtime_error("PartReferenceType " + std::to_string(type) + " not associated with a mesh part"); } } std::string getBoneName(PartReferenceType type) { switch(type) { case PRT_Head: return "head"; case PRT_Hair: return "head"; // This is purposeful. case PRT_Neck: return "neck"; case PRT_Cuirass: return "chest"; case PRT_Groin: return "groin"; case PRT_Skirt: return "groin"; case PRT_RHand: return "right hand"; case PRT_LHand: return "left hand"; case PRT_RWrist: return "right wrist"; case PRT_LWrist: return "left wrist"; case PRT_Shield: return "shield bone"; case PRT_RForearm: return "right forearm"; case PRT_LForearm: return "left forearm"; case PRT_RUpperarm: return "right upper arm"; case PRT_LUpperarm: return "left upper arm"; case PRT_RFoot: return "right foot"; case PRT_LFoot: return "left foot"; case PRT_RAnkle: return "right ankle"; case PRT_LAnkle: return "left ankle"; case PRT_RKnee: return "right knee"; case PRT_LKnee: return "left knee"; case PRT_RLeg: return "right upper leg"; case PRT_LLeg: return "left upper leg"; case PRT_RPauldron: return "right clavicle"; case PRT_LPauldron: return "left clavicle"; case PRT_Weapon: return "weapon bone"; case PRT_Tail: return "tail"; default: throw std::runtime_error("unknown PartReferenceType"); } } std::string getMeshFilter(PartReferenceType type) { switch(type) { case PRT_Hair: return "hair"; default: return getBoneName(type); } } } openmw-openmw-0.48.0/components/esm3/mappings.hpp000066400000000000000000000005431445372753700220210ustar00rootroot00000000000000#ifndef OPENMW_ESM_MAPPINGS_H #define OPENMW_ESM_MAPPINGS_H #include #include #include namespace ESM { BodyPart::MeshPart getMeshPart(PartReferenceType type); std::string getBoneName(PartReferenceType type); std::string getMeshFilter(PartReferenceType type); } #endif openmw-openmw-0.48.0/components/esm3/npcstate.cpp000066400000000000000000000011641445372753700220170ustar00rootroot00000000000000#include "npcstate.hpp" namespace ESM { void NpcState::load (ESMReader &esm) { ObjectState::load (esm); if (mHasCustomState) { mInventory.load (esm); mNpcStats.load (esm); mCreatureStats.load (esm); } } void NpcState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); if (mHasCustomState) { mInventory.save (esm); mNpcStats.save (esm); mCreatureStats.save (esm); } } void NpcState::blank() { ObjectState::blank(); mNpcStats.blank(); mCreatureStats.blank(); mHasCustomState = true; } } openmw-openmw-0.48.0/components/esm3/npcstate.hpp000066400000000000000000000014061445372753700220230ustar00rootroot00000000000000#ifndef OPENMW_ESM_NPCSTATE_H #define OPENMW_ESM_NPCSTATE_H #include "objectstate.hpp" #include "inventorystate.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" namespace ESM { // format 0, saved games only struct NpcState final : public ObjectState { InventoryState mInventory; NpcStats mNpcStats; CreatureStats mCreatureStats; /// Initialize to default state void blank() override; void load (ESMReader &esm) override; void save (ESMWriter &esm, bool inInventory = false) const override; NpcState& asNpcState() override { return *this; } const NpcState& asNpcState() const override { return *this; } }; } #endif openmw-openmw-0.48.0/components/esm3/npcstats.cpp000066400000000000000000000121621445372753700220350ustar00rootroot00000000000000#include #include "npcstats.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} void NpcStats::load (ESMReader &esm) { while (esm.isNextSub ("FACT")) { std::string id = esm.getHString(); Faction faction; int expelled = 0; esm.getHNOT (expelled, "FAEX"); if (expelled) faction.mExpelled = true; esm.getHNOT (faction.mRank, "FARA"); esm.getHNOT (faction.mReputation, "FARE"); mFactions.insert (std::make_pair (id, faction)); } mDisposition = 0; esm.getHNOT (mDisposition, "DISP"); bool intFallback = esm.getFormat() < 11; for (int i=0; i<27; ++i) mSkills[i].load (esm, intFallback); mWerewolfDeprecatedData = false; if (esm.getFormat() < 8 && esm.peekNextSub("STBA")) { // we have deprecated werewolf skills, stored interleaved // Load into one big vector, then remove every 2nd value mWerewolfDeprecatedData = true; std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); for (int i=0; i<27; ++i) { StatState skill; skill.load(esm, intFallback); skills.push_back(skill); } int i=0; for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) { if (i%2 == 1) it = skills.erase(it); else ++it; } assert(skills.size() == 27); std::copy(skills.begin(), skills.end(), mSkills); } // No longer used bool hasWerewolfAttributes = false; esm.getHNOT (hasWerewolfAttributes, "HWAT"); if (hasWerewolfAttributes) { StatState dummy; for (int i=0; i<8; ++i) dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; } mIsWerewolf = false; esm.getHNOT (mIsWerewolf, "WOLF"); mBounty = 0; esm.getHNOT (mBounty, "BOUN"); mReputation = 0; esm.getHNOT (mReputation, "REPU"); mWerewolfKills = 0; esm.getHNOT (mWerewolfKills, "WKIL"); // No longer used if (esm.isNextSub("PROF")) esm.skipHSub(); // int profit // No longer used if (esm.isNextSub("ASTR")) esm.skipHSub(); // attackStrength mLevelProgress = 0; esm.getHNOT (mLevelProgress, "LPRO"); for (int i = 0; i < 8; ++i) mSkillIncrease[i] = 0; esm.getHNOT (mSkillIncrease, "INCR"); for (int i=0; i<3; ++i) mSpecIncreases[i] = 0; esm.getHNOT (mSpecIncreases, "SPEC"); while (esm.isNextSub ("USED")) mUsedIds.push_back (esm.getHString()); mTimeToStartDrowning = 0; esm.getHNOT (mTimeToStartDrowning, "DRTI"); // No longer used float lastDrowningHit = 0; esm.getHNOT (lastDrowningHit, "DRLH"); // No longer used float levelHealthBonus = 0; esm.getHNOT (levelHealthBonus, "LVLH"); mCrimeId = -1; esm.getHNOT (mCrimeId, "CRID"); } void NpcStats::save (ESMWriter &esm) const { for (std::map::const_iterator iter (mFactions.begin()); iter!=mFactions.end(); ++iter) { esm.writeHNString ("FACT", iter->first); if (iter->second.mExpelled) { int expelled = 1; esm.writeHNT ("FAEX", expelled); } if (iter->second.mRank >= 0) esm.writeHNT ("FARA", iter->second.mRank); if (iter->second.mReputation) esm.writeHNT ("FARE", iter->second.mReputation); } if (mDisposition) esm.writeHNT ("DISP", mDisposition); for (int i=0; i<27; ++i) mSkills[i].save (esm); if (mIsWerewolf) esm.writeHNT ("WOLF", mIsWerewolf); if (mBounty) esm.writeHNT ("BOUN", mBounty); if (mReputation) esm.writeHNT ("REPU", mReputation); if (mWerewolfKills) esm.writeHNT ("WKIL", mWerewolfKills); if (mLevelProgress) esm.writeHNT ("LPRO", mLevelProgress); bool saveSkillIncreases = false; for (int i = 0; i < 8; ++i) { if (mSkillIncrease[i] != 0) { saveSkillIncreases = true; break; } } if (saveSkillIncreases) esm.writeHNT ("INCR", mSkillIncrease); if (mSpecIncreases[0] != 0 || mSpecIncreases[1] != 0 || mSpecIncreases[2] != 0) esm.writeHNT ("SPEC", mSpecIncreases); for (std::vector::const_iterator iter (mUsedIds.begin()); iter!=mUsedIds.end(); ++iter) esm.writeHNString ("USED", *iter); if (mTimeToStartDrowning) esm.writeHNT ("DRTI", mTimeToStartDrowning); if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); } void NpcStats::blank() { mWerewolfDeprecatedData = false; mIsWerewolf = false; mDisposition = 0; mBounty = 0; mReputation = 0; mWerewolfKills = 0; mLevelProgress = 0; for (int i=0; i<8; ++i) mSkillIncrease[i] = 0; for (int i=0; i<3; ++i) mSpecIncreases[i] = 0; mTimeToStartDrowning = 20; mCrimeId = -1; } } openmw-openmw-0.48.0/components/esm3/npcstats.hpp000066400000000000000000000020301445372753700220330ustar00rootroot00000000000000#ifndef OPENMW_ESM_NPCSTATS_H #define OPENMW_ESM_NPCSTATS_H #include #include #include #include "statstate.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct NpcStats { struct Faction { bool mExpelled; int mRank; int mReputation; Faction(); }; bool mIsWerewolf; bool mWerewolfDeprecatedData; std::map mFactions; // lower case IDs int mDisposition; StatState mSkills[27]; int mBounty; int mReputation; int mWerewolfKills; int mLevelProgress; int mSkillIncrease[8]; int mSpecIncreases[3]; std::vector mUsedIds; // lower case IDs float mTimeToStartDrowning; int mCrimeId; /// Initialize to default state void blank(); void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/objectstate.cpp000066400000000000000000000103261445372753700225050ustar00rootroot00000000000000#include "objectstate.hpp" #include #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); bool isDeleted; mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); if (mHasLocals) mLocals.load (esm); mLuaScripts.load(esm); mEnabled = 1; esm.getHNOT (mEnabled, "ENAB"); mCount = 1; esm.getHNOT (mCount, "COUN"); if(esm.isNextSub("POS_")) { std::array pos; esm.getHT(pos); memcpy(mPosition.pos, pos.data(), sizeof(float) * 3); memcpy(mPosition.rot, pos.data() + 3, sizeof(float) * 3); } else mPosition = mRef.mPos; if (esm.isNextSub("LROT")) esm.skipHSub(); // local rotation, no longer used mFlags = 0; esm.getHNOT (mFlags, "FLAG"); // obsolete int unused; esm.getHNOT(unused, "LTIM"); mAnimationState.load(esm); // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files mHasCustomState = true; esm.getHNOT (mHasCustomState, "HCUS"); } void ObjectState::save (ESMWriter &esm, bool inInventory) const { mRef.save (esm, true, inInventory); if (mHasLocals) { esm.writeHNT ("HLOC", mHasLocals); mLocals.save (esm); } mLuaScripts.save(esm); if (!mEnabled && !inInventory) esm.writeHNT ("ENAB", mEnabled); if (mCount!=1) esm.writeHNT ("COUN", mCount); if (!inInventory && mPosition != mRef.mPos) { std::array pos; memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); esm.writeHNT ("POS_", pos, 24); } if (mFlags != 0) esm.writeHNT ("FLAG", mFlags); mAnimationState.save(esm); if (!mHasCustomState) esm.writeHNT ("HCUS", false); } void ObjectState::blank() { mRef.blank(); mHasLocals = 0; mEnabled = false; mCount = 1; for (int i=0;i<3;++i) { mPosition.pos[i] = 0; mPosition.rot[i] = 0; } mFlags = 0; mHasCustomState = true; } const NpcState& ObjectState::asNpcState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } NpcState& ObjectState::asNpcState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } const CreatureState& ObjectState::asCreatureState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } CreatureState& ObjectState::asCreatureState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } const ContainerState& ObjectState::asContainerState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } ContainerState& ObjectState::asContainerState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } const DoorState& ObjectState::asDoorState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } DoorState& ObjectState::asDoorState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } const CreatureLevListState& ObjectState::asCreatureLevListState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } CreatureLevListState& ObjectState::asCreatureLevListState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } ObjectState::~ObjectState() {} } openmw-openmw-0.48.0/components/esm3/objectstate.hpp000066400000000000000000000036771445372753700225250ustar00rootroot00000000000000#ifndef OPENMW_ESM_OBJECTSTATE_H #define OPENMW_ESM_OBJECTSTATE_H #include #include #include "cellref.hpp" #include "locals.hpp" #include "components/esm/luascripts.hpp" #include "animationstate.hpp" namespace ESM { class ESMReader; class ESMWriter; struct ContainerState; struct CreatureLevListState; struct CreatureState; struct DoorState; struct NpcState; // format 0, saved games only ///< \brief Save state for objects, that do not use custom data struct ObjectState { CellRef mRef; unsigned char mHasLocals; Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; int mCount; Position mPosition; unsigned int mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; unsigned int mVersion; AnimationState mAnimationState; ObjectState() : mHasLocals(0), mEnabled(0), mCount(0) , mFlags(0), mHasCustomState(true), mVersion(0) {} /// @note Does not load the CellRef ID, it should already be loaded before calling this method virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; virtual /// Initialize to default state void blank(); virtual ~ObjectState(); virtual const NpcState& asNpcState() const; virtual NpcState& asNpcState(); virtual const CreatureState& asCreatureState() const; virtual CreatureState& asCreatureState(); virtual const ContainerState& asContainerState() const; virtual ContainerState& asContainerState(); virtual const DoorState& asDoorState() const; virtual DoorState& asDoorState(); virtual const CreatureLevListState& asCreatureLevListState() const; virtual CreatureLevListState& asCreatureLevListState(); }; } #endif openmw-openmw-0.48.0/components/esm3/player.cpp000066400000000000000000000061611445372753700214740ustar00rootroot00000000000000#include "player.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void Player::load (ESMReader &esm) { mObject.mRef.loadId(esm, true); mObject.load (esm); mCellId.load (esm); esm.getHNTSized<12>(mLastKnownExteriorPosition, "LKEP"); if (esm.isNextSub ("MARK")) { mHasMark = true; esm.getHTSized<24>(mMarkedPosition); mMarkedCell.load (esm); } else mHasMark = false; // Automove, no longer used. if (esm.isNextSub("AMOV")) esm.skipHSub(); mBirthsign = esm.getHNString ("SIGN"); mCurrentCrimeId = -1; esm.getHNOT (mCurrentCrimeId, "CURD"); mPaidCrimeId = -1; esm.getHNOT (mPaidCrimeId, "PAYD"); bool checkPrevItems = true; while (checkPrevItems) { std::string boundItemId = esm.getHNOString("BOUN"); std::string prevItemId = esm.getHNOString("PREV"); if (!boundItemId.empty()) mPreviousItems[boundItemId] = prevItemId; else checkPrevItems = false; } if(esm.getFormat() < 19) { bool intFallback = esm.getFormat() < 11; bool clearModified = esm.getFormat() < 17 && !mObject.mNpcStats.mIsWerewolf; if (esm.hasMoreSubs()) { for (int i=0; i attribute; attribute.load(esm, intFallback); if (clearModified) attribute.mMod = 0.f; mSaveAttributes[i] = attribute.mBase + attribute.mMod - attribute.mDamage; if (mObject.mNpcStats.mIsWerewolf) mObject.mCreatureStats.mAttributes[i] = attribute; } for (int i=0; i skill; skill.load(esm, intFallback); if (clearModified) skill.mMod = 0.f; mSaveSkills[i] = skill.mBase + skill.mMod - skill.mDamage; if (mObject.mNpcStats.mIsWerewolf) { if(i == Skill::Acrobatics) mSetWerewolfAcrobatics = mObject.mNpcStats.mSkills[i].mBase != skill.mBase; mObject.mNpcStats.mSkills[i] = skill; } } } } else { mSetWerewolfAcrobatics = false; esm.getHNT(mSaveAttributes, "WWAT"); esm.getHNT(mSaveSkills, "WWSK"); } } void Player::save (ESMWriter &esm) const { mObject.save (esm); mCellId.save (esm); esm.writeHNT ("LKEP", mLastKnownExteriorPosition); if (mHasMark) { esm.writeHNT ("MARK", mMarkedPosition, 24); mMarkedCell.save (esm); } esm.writeHNString ("SIGN", mBirthsign); esm.writeHNT ("CURD", mCurrentCrimeId); esm.writeHNT ("PAYD", mPaidCrimeId); for (PreviousItems::const_iterator it=mPreviousItems.begin(); it != mPreviousItems.end(); ++it) { esm.writeHNString ("BOUN", it->first); esm.writeHNString ("PREV", it->second); } esm.writeHNT("WWAT", mSaveAttributes); esm.writeHNT("WWSK", mSaveSkills); } } openmw-openmw-0.48.0/components/esm3/player.hpp000066400000000000000000000017441445372753700215030ustar00rootroot00000000000000#ifndef OPENMW_ESM_PLAYER_H #define OPENMW_ESM_PLAYER_H #include #include "npcstate.hpp" #include "cellid.hpp" #include "components/esm/defs.hpp" #include "loadskil.hpp" #include "components/esm/attr.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct Player { NpcState mObject; CellId mCellId; float mLastKnownExteriorPosition[3]; unsigned char mHasMark; bool mSetWerewolfAcrobatics; Position mMarkedPosition; CellId mMarkedCell; std::string mBirthsign; int mCurrentCrimeId; int mPaidCrimeId; float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/projectilestate.cpp000066400000000000000000000036141445372753700234010ustar00rootroot00000000000000#include "projectilestate.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void BaseProjectileState::save(ESMWriter &esm) const { esm.writeHNString ("ID__", mId); esm.writeHNT ("VEC3", mPosition); esm.writeHNT ("QUAT", mOrientation); esm.writeHNT ("ACTO", mActorId); } void BaseProjectileState::load(ESMReader &esm) { mId = esm.getHNString("ID__"); esm.getHNT (mPosition, "VEC3"); esm.getHNT (mOrientation, "QUAT"); esm.getHNT (mActorId, "ACTO"); } void MagicBoltState::save(ESMWriter &esm) const { BaseProjectileState::save(esm); esm.writeHNString ("SPEL", mSpellId); esm.writeHNT ("SPED", mSpeed); esm.writeHNT ("SLOT", mSlot); } void MagicBoltState::load(ESMReader &esm) { BaseProjectileState::load(esm); mSpellId = esm.getHNString("SPEL"); if (esm.isNextSub("SRCN")) // for backwards compatibility esm.skipHSub(); EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); if(esm.getFormat() < 17) mSlot = 0; else esm.getHNT(mSlot, "SLOT"); if (esm.isNextSub("STCK")) // for backwards compatibility esm.skipHSub(); if (esm.isNextSub("SOUN")) // for backwards compatibility esm.skipHSub(); } void ProjectileState::save(ESMWriter &esm) const { BaseProjectileState::save(esm); esm.writeHNString ("BOW_", mBowId); esm.writeHNT ("VEL_", mVelocity); esm.writeHNT ("STR_", mAttackStrength); } void ProjectileState::load(ESMReader &esm) { BaseProjectileState::load(esm); mBowId = esm.getHNString ("BOW_"); esm.getHNT (mVelocity, "VEL_"); mAttackStrength = 1.f; esm.getHNOT(mAttackStrength, "STR_"); } } openmw-openmw-0.48.0/components/esm3/projectilestate.hpp000066400000000000000000000016621445372753700234070ustar00rootroot00000000000000#ifndef OPENMW_ESM_PROJECTILESTATE_H #define OPENMW_ESM_PROJECTILESTATE_H #include #include #include #include "effectlist.hpp" #include "components/esm/util.hpp" namespace ESM { // format 0, savegames only struct BaseProjectileState { std::string mId; Vector3 mPosition; Quaternion mOrientation; int mActorId; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; struct MagicBoltState : public BaseProjectileState { std::string mSpellId; float mSpeed; int mSlot; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; struct ProjectileState : public BaseProjectileState { std::string mBowId; Vector3 mVelocity; float mAttackStrength; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/queststate.cpp000066400000000000000000000006361445372753700224030ustar00rootroot00000000000000#include "queststate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void QuestState::load (ESMReader &esm) { mTopic = esm.getHNString ("YETO"); esm.getHNOT (mState, "QSTA"); esm.getHNOT (mFinished, "QFIN"); } void QuestState::save (ESMWriter &esm) const { esm.writeHNString ("YETO", mTopic); esm.writeHNT ("QSTA", mState); esm.writeHNT ("QFIN", mFinished); } } openmw-openmw-0.48.0/components/esm3/queststate.hpp000066400000000000000000000006201445372753700224010ustar00rootroot00000000000000#ifndef OPENMW_ESM_QUESTSTATE_H #define OPENMW_ESM_QUESTSTATE_H #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct QuestState { std::string mTopic; // lower case id int mState; unsigned char mFinished; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/quickkeys.cpp000066400000000000000000000020151445372753700222020ustar00rootroot00000000000000#include "quickkeys.hpp" #include "esmwriter.hpp" #include "esmreader.hpp" namespace ESM { void QuickKeys::load(ESMReader &esm) { if (esm.isNextSub("KEY_")) esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader while (esm.isNextSub("TYPE")) { int keyType; esm.getHT(keyType); std::string id; id = esm.getHNString("ID__"); QuickKey key; key.mType = keyType; key.mId = id; mKeys.push_back(key); if (esm.isNextSub("KEY_")) esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader } } void QuickKeys::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) { esm.writeHNT("TYPE", it->mType); esm.writeHNString("ID__", it->mId); } } } openmw-openmw-0.48.0/components/esm3/quickkeys.hpp000066400000000000000000000007141445372753700222130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_QUICKKEYS_H #define OPENMW_COMPONENTS_ESM_QUICKKEYS_H #include #include namespace ESM { class ESMReader; class ESMWriter; struct QuickKeys { struct QuickKey { int mType; std::string mId; // Spell or Item ID }; std::vector mKeys; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/readerscache.cpp000066400000000000000000000050121445372753700226030ustar00rootroot00000000000000#include "readerscache.hpp" #include namespace ESM { ReadersCache::BusyItem::BusyItem(ReadersCache& owner, std::list::iterator item) noexcept : mOwner(owner) , mItem(item) {} ReadersCache::BusyItem::~BusyItem() noexcept { mOwner.releaseItem(mItem); } ReadersCache::ReadersCache(std::size_t capacity) : mCapacity(capacity) {} ReadersCache::BusyItem ReadersCache::get(std::size_t index) { const auto indexIt = mIndex.find(index); std::list::iterator it; if (indexIt == mIndex.end()) { closeExtraReaders(); it = mBusyItems.emplace(mBusyItems.end()); mIndex.emplace(index, it); } else { switch (indexIt->second->mState) { case State::Busy: throw std::logic_error("ESMReader at index " + std::to_string(index) + " is busy"); case State::Free: it = indexIt->second; mBusyItems.splice(mBusyItems.end(), mFreeItems, it); break; case State::Closed: closeExtraReaders(); it = indexIt->second; if (it->mName.has_value()) { it->mReader.open(*it->mName); it->mName.reset(); } mBusyItems.splice(mBusyItems.end(), mClosedItems, it); break; } it->mState = State::Busy; } return BusyItem(*this, it); } void ReadersCache::closeExtraReaders() { while (!mFreeItems.empty() && mBusyItems.size() + mFreeItems.size() + 1 > mCapacity) { const auto it = mFreeItems.begin(); if (it->mReader.isOpen()) { it->mName = it->mReader.getName(); it->mReader.close(); } mClosedItems.splice(mClosedItems.end(), mFreeItems, it); it->mState = State::Closed; } } void ReadersCache::releaseItem(std::list::iterator it) noexcept { assert(it->mState == State::Busy); if (it->mReader.isOpen()) { mFreeItems.splice(mFreeItems.end(), mBusyItems, it); it->mState = State::Free; } else { mClosedItems.splice(mClosedItems.end(), mBusyItems, it); it->mState = State::Closed; } } } openmw-openmw-0.48.0/components/esm3/readerscache.hpp000066400000000000000000000033731445372753700226200ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM3_READERSCACHE_H #define OPENMW_COMPONENTS_ESM3_READERSCACHE_H #include "esmreader.hpp" #include #include #include #include #include namespace ESM { class ReadersCache { private: enum class State { Busy, Free, Closed, }; struct Item { State mState = State::Busy; ESMReader mReader; std::optional mName; Item() = default; }; public: class BusyItem { public: explicit BusyItem(ReadersCache& owner, std::list::iterator item) noexcept; BusyItem(const BusyItem& other) = delete; ~BusyItem() noexcept; BusyItem& operator=(const BusyItem& other) = delete; ESMReader& operator*() const noexcept { return mItem->mReader; } ESMReader* operator->() const noexcept { return &mItem->mReader; } private: ReadersCache& mOwner; std::list::iterator mItem; }; explicit ReadersCache(std::size_t capacity = 100); BusyItem get(std::size_t index); private: const std::size_t mCapacity; std::map::iterator> mIndex; std::list mBusyItems; std::list mFreeItems; std::list mClosedItems; inline void closeExtraReaders(); inline void releaseItem(std::list::iterator it) noexcept; }; } #endif openmw-openmw-0.48.0/components/esm3/savedgame.cpp000066400000000000000000000031571445372753700221360ustar00rootroot00000000000000#include "savedgame.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { int SavedGame::sCurrentFormat = 21; void SavedGame::load (ESMReader &esm) { mPlayerName = esm.getHNString("PLNA"); esm.getHNOT (mPlayerLevel, "PLLE"); mPlayerClassId = esm.getHNOString("PLCL"); // Erase RefId type if (esm.getFormat() >= 22 && !mPlayerClassId.empty()) mPlayerClassId = mPlayerClassId.substr(1); mPlayerClassName = esm.getHNOString("PLCN"); mPlayerCell = esm.getHNString("PLCE"); esm.getHNTSized<16>(mInGameTime, "TSTM"); esm.getHNT (mTimePlayed, "TIME"); mDescription = esm.getHNString ("DESC"); while (esm.isNextSub ("DEPE")) mContentFiles.push_back (esm.getHString()); esm.getSubNameIs("SCRN"); esm.getSubHeader(); mScreenshot.resize(esm.getSubSize()); esm.getExact(mScreenshot.data(), mScreenshot.size()); } void SavedGame::save (ESMWriter &esm) const { esm.writeHNString ("PLNA", mPlayerName); esm.writeHNT ("PLLE", mPlayerLevel); if (!mPlayerClassId.empty()) esm.writeHNString ("PLCL", mPlayerClassId); else esm.writeHNString ("PLCN", mPlayerClassName); esm.writeHNString ("PLCE", mPlayerCell); esm.writeHNT ("TSTM", mInGameTime, 16); esm.writeHNT ("TIME", mTimePlayed); esm.writeHNString ("DESC", mDescription); for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) esm.writeHNString ("DEPE", *iter); esm.startSubRecord("SCRN"); esm.write(&mScreenshot[0], mScreenshot.size()); esm.endRecord("SCRN"); } } openmw-openmw-0.48.0/components/esm3/savedgame.hpp000066400000000000000000000017661445372753700221470ustar00rootroot00000000000000#ifndef OPENMW_ESM_SAVEDGAME_H #define OPENMW_ESM_SAVEDGAME_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct SavedGame { constexpr static RecNameInts sRecordId = REC_SAVE; static int sCurrentFormat; std::vector mContentFiles; std::string mPlayerName; int mPlayerLevel; // ID of class std::string mPlayerClassId; // Name of the class. When using a custom class, the ID is not really meaningful prior // to loading the savegame, so the name is stored separately. std::string mPlayerClassName; std::string mPlayerCell; EpochTimeStamp mInGameTime; double mTimePlayed; std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/spelllist.cpp000066400000000000000000000011551445372753700222110ustar00rootroot00000000000000#include "spelllist.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void SpellList::add(ESMReader &esm) { mList.push_back(esm.getHString()); } void SpellList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNString("NPCS", *it, 32); } } bool SpellList::exists(const std::string &spell) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) if (Misc::StringUtils::ciEqual(*it, spell)) return true; return false; } } openmw-openmw-0.48.0/components/esm3/spelllist.hpp000066400000000000000000000011711445372753700222140ustar00rootroot00000000000000#ifndef OPENMW_ESM_SPELLLIST_H #define OPENMW_ESM_SPELLLIST_H #include #include namespace ESM { class ESMReader; class ESMWriter; /** A list of references to spells and spell effects. This is shared between the records BSGN, NPC and RACE. NPCS subrecord. */ struct SpellList { std::vector mList; /// Is this spell ID in mList? bool exists(const std::string& spell) const; /// Load one spell, assumes the subrecord name was already read void add(ESMReader &esm); void save(ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/spellstate.cpp000066400000000000000000000056531445372753700223650ustar00rootroot00000000000000#include "spellstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void SpellState::load(ESMReader &esm) { if(esm.getFormat() < 17) { while (esm.isNextSub("SPEL")) { std::string id = esm.getHString(); SpellParams state; while (esm.isNextSub("INDX")) { int index; esm.getHT(index); float magnitude; esm.getHNT(magnitude, "RAND"); state.mEffectRands[index] = magnitude; } while (esm.isNextSub("PURG")) { int index; esm.getHT(index); state.mPurgedEffects.insert(index); } mSpellParams[id] = state; mSpells.emplace_back(id); } } else { while (esm.isNextSub("SPEL")) mSpells.emplace_back(esm.getHString()); } // Obsolete while (esm.isNextSub("PERM")) { std::string spellId = esm.getHString(); std::vector permEffectList; while (true) { ESM_Context restorePoint = esm.getContext(); if (!esm.isNextSub("EFID")) break; PermanentSpellEffectInfo info; esm.getHT(info.mId); if (esm.isNextSub("BASE")) { esm.restoreContext(restorePoint); return; } else esm.getHNT(info.mArg, "ARG_"); esm.getHNT(info.mMagnitude, "MAGN"); permEffectList.push_back(info); } mPermanentSpellEffects[spellId] = permEffectList; } // Obsolete while (esm.isNextSub("CORP")) { std::string id = esm.getHString(); CorprusStats stats; esm.getHNT(stats.mWorsenings, "WORS"); esm.getHNT(stats.mNextWorsening, "TIME"); mCorprusSpells[id] = stats; } while (esm.isNextSub("USED")) { std::string id = esm.getHString(); TimeStamp time; esm.getHNT(time, "TIME"); mUsedPowers[id] = time; } mSelectedSpell = esm.getHNOString("SLCT"); } void SpellState::save(ESMWriter &esm) const { for (const std::string& spell : mSpells) esm.writeHNString("SPEL", spell); for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) { esm.writeHNString("USED", it->first); esm.writeHNT("TIME", it->second); } if (!mSelectedSpell.empty()) esm.writeHNString("SLCT", mSelectedSpell); } } openmw-openmw-0.48.0/components/esm3/spellstate.hpp000066400000000000000000000023511445372753700223620ustar00rootroot00000000000000#ifndef OPENMW_ESM_SPELLSTATE_H #define OPENMW_ESM_SPELLSTATE_H #include #include #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; // NOTE: spell ids must be lower case struct SpellState { struct CorprusStats { int mWorsenings; TimeStamp mNextWorsening; }; struct PermanentSpellEffectInfo { int mId; int mArg; float mMagnitude; }; struct SpellParams { std::map mEffectRands; // std::set mPurgedEffects; // indices of purged effects }; std::vector mSpells; // FIXME: obsolete, used only for old saves std::map mSpellParams; std::map > mPermanentSpellEffects; std::map mCorprusSpells; std::map mUsedPowers; std::string mSelectedSpell; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/statstate.cpp000066400000000000000000000034061445372753700222130ustar00rootroot00000000000000#include "statstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { template StatState::StatState() : mBase(0), mMod(0), mCurrent(0), mDamage(0), mProgress(0) {} template void StatState::load(ESMReader &esm, bool intFallback) { // We changed stats values from integers to floats; ensure backwards compatibility if (intFallback) { int base = 0; esm.getHNT(base, "STBA"); mBase = static_cast(base); int mod = 0; esm.getHNOT(mod, "STMO"); mMod = static_cast(mod); int current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); int oldDamage = 0; esm.getHNOT(oldDamage, "STDA"); mDamage = static_cast(oldDamage); } else { mBase = 0; esm.getHNT(mBase, "STBA"); mMod = 0; esm.getHNOT(mMod, "STMO"); mCurrent = 0; esm.getHNOT(mCurrent, "STCU"); mDamage = 0; esm.getHNOT(mDamage, "STDF"); mProgress = 0; } esm.getHNOT(mDamage, "STDF"); mProgress = 0; esm.getHNOT(mProgress, "STPR"); } template void StatState::save(ESMWriter &esm) const { esm.writeHNT("STBA", mBase); if (mMod != 0) esm.writeHNT("STMO", mMod); if (mCurrent) esm.writeHNT("STCU", mCurrent); if (mDamage) esm.writeHNT("STDF", mDamage); if (mProgress) esm.writeHNT("STPR", mProgress); } template struct StatState; template struct StatState; } openmw-openmw-0.48.0/components/esm3/statstate.hpp000066400000000000000000000011371445372753700222170ustar00rootroot00000000000000#ifndef OPENMW_ESM_STATSTATE_H #define OPENMW_ESM_STATSTATE_H namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only template struct StatState { T mBase; T mMod; // Note: can either be the modifier, or the modified value. // A bit inconsistent, but we can't fix this without breaking compatibility. T mCurrent; float mDamage; float mProgress; StatState(); void load (ESMReader &esm, bool intFallback = false); void save (ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/stolenitems.cpp000066400000000000000000000027411445372753700225460ustar00rootroot00000000000000#include "stolenitems.hpp" #include #include namespace ESM { void StolenItems::write(ESMWriter &esm) const { for (StolenItemsMap::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) { esm.writeHNString("NAME", it->first); for (std::map, int>::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt) { if (ownerIt->first.second) esm.writeHNString("FNAM", ownerIt->first.first); else esm.writeHNString("ONAM", ownerIt->first.first); esm.writeHNT("COUN", ownerIt->second); } } } void StolenItems::load(ESMReader &esm) { while (esm.isNextSub("NAME")) { std::string itemid = esm.getHString(); std::map, int> ownerMap; while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { const bool isFaction = (esm.retSubName() == "FNAM"); std::string owner = esm.getHString(); int count; esm.getHNT(count, "COUN"); ownerMap.emplace(std::make_pair(std::move(owner), isFaction), count); } mStolenItems.insert_or_assign(std::move(itemid), std::move(ownerMap)); } } } openmw-openmw-0.48.0/components/esm3/stolenitems.hpp000066400000000000000000000007361445372753700225550ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_STOLENITEMS_H #define OPENMW_COMPONENTS_ESM_STOLENITEMS_H #include #include namespace ESM { class ESMReader; class ESMWriter; // format 0, saved games only struct StolenItems { typedef std::map, int> > StolenItemsMap; StolenItemsMap mStolenItems; void load(ESMReader& esm); void write(ESMWriter& esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/transport.cpp000066400000000000000000000020341445372753700222270ustar00rootroot00000000000000#include "transport.hpp" #include #include #include namespace ESM { void Transport::add(ESMReader &esm) { if (esm.retSubName().toInt() == fourCC("DODT")) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } else if (esm.retSubName().toInt() == fourCC("DNAM")) { const std::string name = esm.getHString(); if (mList.empty()) Log(Debug::Warning) << "Encountered DNAM record without DODT record, skipped."; else mList.back().mCellName = name; } } void Transport::save(ESMWriter &esm) const { typedef std::vector::const_iterator DestIter; for (DestIter it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); esm.writeHNOCString("DNAM", it->mCellName); } } } openmw-openmw-0.48.0/components/esm3/transport.hpp000066400000000000000000000011751445372753700222410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESM_TRANSPORT_H #define OPENMW_COMPONENTS_ESM_TRANSPORT_H #include #include #include "components/esm/defs.hpp" namespace ESM { class ESMReader; class ESMWriter; /// List of travel service destination. Shared by CREA and NPC_ records. struct Transport { struct Dest { Position mPos; std::string mCellName; }; std::vector mList; /// Load one destination, assumes the subrecord name was already read void add(ESMReader &esm); void save(ESMWriter &esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3/variant.cpp000066400000000000000000000137601445372753700216470ustar00rootroot00000000000000#include "variant.hpp" #include #include #include "esmreader.hpp" #include "variantimp.hpp" #include "components/esm/defs.hpp" namespace ESM { namespace { constexpr uint32_t STRV = fourCC("STRV"); constexpr uint32_t INTV = fourCC("INTV"); constexpr uint32_t FLTV = fourCC("FLTV"); constexpr uint32_t STTV = fourCC("STTV"); template struct GetValue { constexpr T operator()(int value) const { return static_cast(value); } constexpr T operator()(float value) const { return static_cast(value); } template constexpr T operator()(const V&) const { if constexpr (orDefault) return T {}; else throw std::runtime_error("cannot convert variant"); } }; template struct SetValue { T mValue; explicit SetValue(T value) : mValue(value) {} void operator()(int& value) const { value = static_cast(mValue); } void operator()(float& value) const { value = static_cast(mValue); } template void operator()(V&) const { throw std::runtime_error("cannot convert variant"); } }; } const std::string& Variant::getString() const { return std::get(mData); } int Variant::getInteger() const { return std::visit(GetValue{}, mData); } float Variant::getFloat() const { return std::visit(GetValue{}, mData); } void Variant::read (ESMReader& esm, Format format) { // type VarType type = VT_Unknown; if (format==Format_Global) { std::string typeId = esm.getHNString ("FNAM"); if (typeId == "s") type = VT_Short; else if (typeId == "l") type = VT_Long; else if (typeId == "f") type = VT_Float; else esm.fail ("illegal global variable type " + typeId); } else if (format==Format_Gmst) { if (!esm.hasMoreSubs()) { type = VT_None; } else { esm.getSubName(); NAME name = esm.retSubName(); if (name==STRV) { type = VT_String; } else if (name==INTV) { type = VT_Int; } else if (name==FLTV) { type = VT_Float; } else esm.fail ("invalid subrecord: " + name.toString()); } } else if (format == Format_Info) { esm.getSubName(); NAME name = esm.retSubName(); if (name==INTV) { type = VT_Int; } else if (name==FLTV) { type = VT_Float; } else esm.fail ("invalid subrecord: " + name.toString()); } else if (format == Format_Local) { esm.getSubName(); NAME name = esm.retSubName(); if (name==INTV) { type = VT_Int; } else if (name==FLTV) { type = VT_Float; } else if (name==STTV) { type = VT_Short; } else esm.fail ("invalid subrecord: " + name.toString()); } setType (type); std::visit(ReadESMVariantValue {esm, format, mType}, mData); } void Variant::write (ESMWriter& esm, Format format) const { if (mType==VT_Unknown) { throw std::runtime_error ("can not serialise variant of unknown type"); } else if (mType==VT_None) { if (format==Format_Global) throw std::runtime_error ("can not serialise variant of type none to global format"); if (format==Format_Info) throw std::runtime_error ("can not serialise variant of type none to info format"); if (format==Format_Local) throw std::runtime_error ("can not serialise variant of type none to local format"); // nothing to do here for GMST format } else std::visit(WriteESMVariantValue {esm, format, mType}, mData); } void Variant::write (std::ostream& stream) const { switch (mType) { case VT_Unknown: stream << "variant unknown"; break; case VT_None: stream << "variant none"; break; case VT_Short: stream << "variant short: " << std::get(mData); break; case VT_Int: stream << "variant int: " << std::get(mData); break; case VT_Long: stream << "variant long: " << std::get(mData); break; case VT_Float: stream << "variant float: " << std::get(mData); break; case VT_String: stream << "variant string: \"" << std::get(mData) << "\""; break; } } void Variant::setType (VarType type) { if (type!=mType) { switch (type) { case VT_Unknown: case VT_None: mData = std::monostate {}; break; case VT_Short: case VT_Int: case VT_Long: mData = std::visit(GetValue{}, mData); break; case VT_Float: mData = std::visit(GetValue{}, mData); break; case VT_String: mData = std::string {}; break; } mType = type; } } void Variant::setString (const std::string& value) { std::get(mData) = value; } void Variant::setString (std::string&& value) { std::get(mData) = std::move(value); } void Variant::setInteger (int value) { std::visit(SetValue(value), mData); } void Variant::setFloat (float value) { std::visit(SetValue(value), mData); } std::ostream& operator<< (std::ostream& stream, const Variant& value) { value.write (stream); return stream; } } openmw-openmw-0.48.0/components/esm3/variant.hpp000066400000000000000000000055151445372753700216530ustar00rootroot00000000000000#ifndef OPENMW_ESM_VARIANT_H #define OPENMW_ESM_VARIANT_H #include #include #include #include namespace ESM { class ESMReader; class ESMWriter; enum VarType { VT_Unknown = 0, VT_None, VT_Short, // stored as a float, kinda VT_Int, VT_Long, // stored as a float VT_Float, VT_String }; class Variant { VarType mType; std::variant mData; public: enum Format { Format_Global, Format_Gmst, Format_Info, Format_Local // local script variables in save game files }; Variant() : mType (VT_None), mData (std::monostate{}) {} explicit Variant(const std::string& value) : mType(VT_String), mData(value) {} explicit Variant(std::string&& value) : mType(VT_String), mData(std::move(value)) {} explicit Variant(int value) : mType(VT_Long), mData(value) {} explicit Variant(float value) : mType(VT_Float), mData(value) {} VarType getType() const { return mType; } const std::string& getString() const; ///< Will throw an exception, if value can not be represented as a string. int getInteger() const; ///< Will throw an exception, if value can not be represented as an integer (implicit /// casting of float values is permitted). float getFloat() const; ///< Will throw an exception, if value can not be represented as a float value. void read (ESMReader& esm, Format format); void write (ESMWriter& esm, Format format) const; void write (std::ostream& stream) const; ///< Write in text format. void setType (VarType type); void setString (const std::string& value); ///< Will throw an exception, if type is not compatible with string. void setString (std::string&& value); ///< Will throw an exception, if type is not compatible with string. void setInteger (int value); ///< Will throw an exception, if type is not compatible with integer. void setFloat (float value); ///< Will throw an exception, if type is not compatible with float. friend bool operator==(const Variant& left, const Variant& right) { return std::tie(left.mType, left.mData) == std::tie(right.mType, right.mData); } friend bool operator!=(const Variant& left, const Variant& right) { return !(left == right); } }; std::ostream& operator<<(std::ostream& stream, const Variant& value); } #endif openmw-openmw-0.48.0/components/esm3/variantimp.cpp000066400000000000000000000113101445372753700223420ustar00rootroot00000000000000#include "variantimp.hpp" #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out) { if (type!=VT_String) throw std::logic_error ("not a string type"); if (format==Variant::Format_Global) esm.fail ("global variables of type string not supported"); if (format==Variant::Format_Info) esm.fail ("info variables of type string not supported"); if (format==Variant::Format_Local) esm.fail ("local variables of type string not supported"); // GMST out = esm.getHString(); } void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, const std::string& in) { if (type!=VT_String) throw std::logic_error ("not a string type"); if (format==Variant::Format_Global) throw std::runtime_error ("global variables of type string not supported"); if (format==Variant::Format_Info) throw std::runtime_error ("info variables of type string not supported"); if (format==Variant::Format_Local) throw std::runtime_error ("local variables of type string not supported"); // GMST esm.writeHNString("STRV", in); } void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); if (format==Variant::Format_Global) { float value; esm.getHNT (value, "FLTV"); if (type==VT_Short) if (std::isnan(value)) out = 0; else out = static_cast (value); else if (type==VT_Long) out = static_cast (value); else esm.fail ("unsupported global variable integer type"); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info) { if (type!=VT_Int) { std::ostringstream stream; stream << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info") << " variable integer type"; esm.fail (stream.str()); } esm.getHT(out); } else if (format==Variant::Format_Local) { if (type==VT_Short) { short value; esm.getHT(value); out = value; } else if (type==VT_Int) { esm.getHT(out); } else esm.fail("unsupported local variable integer type"); } } void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); if (format==Variant::Format_Global) { if (type==VT_Short || type==VT_Long) { float value = static_cast(in); esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l"); esm.writeHNT ("FLTV", value); } else throw std::runtime_error ("unsupported global variable integer type"); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info) { if (type!=VT_Int) { std::ostringstream stream; stream << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info") << " variable integer type"; throw std::runtime_error (stream.str()); } esm.writeHNT("INTV", in); } else if (format==Variant::Format_Local) { if (type==VT_Short) esm.writeHNT("STTV", static_cast(in)); else if (type == VT_Int) esm.writeHNT("INTV", in); else throw std::runtime_error("unsupported local variable integer type"); } } void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out) { if (type!=VT_Float) throw std::logic_error ("not a float type"); if (format==Variant::Format_Global) { esm.getHNT(out, "FLTV"); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { esm.getHT(out); } } void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, float in) { if (type!=VT_Float) throw std::logic_error ("not a float type"); if (format==Variant::Format_Global) { esm.writeHNString ("FNAM", "f"); esm.writeHNT("FLTV", in); } else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { esm.writeHNT("FLTV", in); } } } openmw-openmw-0.48.0/components/esm3/variantimp.hpp000066400000000000000000000034311445372753700223540ustar00rootroot00000000000000#ifndef OPENMW_ESM_VARIANTIMP_H #define OPENMW_ESM_VARIANTIMP_H #include #include #include "variant.hpp" namespace ESM { void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, std::string& value); void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value); void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value); struct ReadESMVariantValue { std::reference_wrapper mReader; Variant::Format mFormat; VarType mType; ReadESMVariantValue(ESMReader& reader, Variant::Format format, VarType type) : mReader(reader), mFormat(format), mType(type) {} void operator()(std::monostate) const {} template void operator()(T& value) const { readESMVariantValue(mReader.get(), mFormat, mType, value); } }; struct WriteESMVariantValue { std::reference_wrapper mWriter; Variant::Format mFormat; VarType mType; WriteESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type) : mWriter(writer), mFormat(format), mType(type) {} void operator()(std::monostate) const {} template void operator()(const T& value) const { writeESMVariantValue(mWriter.get(), mFormat, mType, value); } }; } #endif openmw-openmw-0.48.0/components/esm3/weatherstate.cpp000066400000000000000000000053521445372753700227010ustar00rootroot00000000000000#include "weatherstate.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { namespace { constexpr NAME currentRegionRecord = "CREG"; constexpr NAME timePassedRecord = "TMPS"; constexpr NAME fastForwardRecord = "FAST"; constexpr NAME weatherUpdateTimeRecord = "WUPD"; constexpr NAME transitionFactorRecord = "TRFC"; constexpr NAME currentWeatherRecord = "CWTH"; constexpr NAME nextWeatherRecord = "NWTH"; constexpr NAME queuedWeatherRecord = "QWTH"; constexpr NAME regionNameRecord = "RGNN"; constexpr NAME regionWeatherRecord = "RGNW"; constexpr NAME regionChanceRecord = "RGNC"; } } namespace ESM { void WeatherState::load(ESMReader& esm) { mCurrentRegion = esm.getHNString(currentRegionRecord); esm.getHNT(mTimePassed, timePassedRecord); esm.getHNT(mFastForward, fastForwardRecord); esm.getHNT(mWeatherUpdateTime, weatherUpdateTimeRecord); esm.getHNT(mTransitionFactor, transitionFactorRecord); esm.getHNT(mCurrentWeather, currentWeatherRecord); esm.getHNT(mNextWeather, nextWeatherRecord); esm.getHNT(mQueuedWeather, queuedWeatherRecord); while (esm.isNextSub(regionNameRecord)) { std::string regionID = esm.getHString(); RegionWeatherState region; esm.getHNT(region.mWeather, regionWeatherRecord); while (esm.isNextSub(regionChanceRecord)) { char chance; esm.getHT(chance); region.mChances.push_back(chance); } mRegions.insert(std::make_pair(regionID, region)); } } void WeatherState::save(ESMWriter& esm) const { esm.writeHNCString(currentRegionRecord, mCurrentRegion); esm.writeHNT(timePassedRecord, mTimePassed); esm.writeHNT(fastForwardRecord, mFastForward); esm.writeHNT(weatherUpdateTimeRecord, mWeatherUpdateTime); esm.writeHNT(transitionFactorRecord, mTransitionFactor); esm.writeHNT(currentWeatherRecord, mCurrentWeather); esm.writeHNT(nextWeatherRecord, mNextWeather); esm.writeHNT(queuedWeatherRecord, mQueuedWeather); std::map::const_iterator it = mRegions.begin(); for(; it != mRegions.end(); ++it) { esm.writeHNCString(regionNameRecord, it->first.c_str()); esm.writeHNT(regionWeatherRecord, it->second.mWeather); for(size_t i = 0; i < it->second.mChances.size(); ++i) { esm.writeHNT(regionChanceRecord, it->second.mChances[i]); } } } } openmw-openmw-0.48.0/components/esm3/weatherstate.hpp000066400000000000000000000013461445372753700227050ustar00rootroot00000000000000#ifndef OPENMW_ESM_WEATHERSTATE_H #define OPENMW_ESM_WEATHERSTATE_H #include #include #include namespace ESM { class ESMReader; class ESMWriter; struct RegionWeatherState { int mWeather; std::vector mChances; }; struct WeatherState { std::string mCurrentRegion; float mTimePassed; bool mFastForward; float mWeatherUpdateTime; float mTransitionFactor; int mCurrentWeather; int mNextWeather; int mQueuedWeather; std::map mRegions; void load(ESMReader& esm); void save(ESMWriter& esm) const; }; } #endif openmw-openmw-0.48.0/components/esm3terrain/000077500000000000000000000000001445372753700210555ustar00rootroot00000000000000openmw-openmw-0.48.0/components/esm3terrain/storage.cpp000066400000000000000000000551251445372753700232350ustar00rootroot00000000000000#include "storage.hpp" #include #include #include #include #include #include #include namespace ESMTerrain { class LandCache { public: typedef std::map, osg::ref_ptr > Map; Map mMap; }; LandObject::LandObject() : mLand(nullptr) , mLoadFlags(0) { } LandObject::LandObject(const ESM::Land *land, int loadFlags) : mLand(land) , mLoadFlags(loadFlags) { mLand->loadData(mLoadFlags, &mData); } LandObject::LandObject(const LandObject ©, const osg::CopyOp ©op) : mLand(nullptr) , mLoadFlags(0) { } LandObject::~LandObject() { } const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : mVFS(vfs) , mNormalMapPattern(normalMapPattern) , mNormalHeightMapPattern(normalHeightMapPattern) , mAutoUseNormalMaps(autoUseNormalMaps) , mSpecularMapPattern(specularMapPattern) , mAutoUseSpecularMaps(autoUseSpecularMaps) { } bool Storage::getMinMaxHeights(float size, const osg::Vec2f ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); int cellX = static_cast(std::floor(origin.x())); int cellY = static_cast(std::floor(origin.y())); int startRow = (origin.x() - cellX) * ESM::Land::LAND_SIZE; int startColumn = (origin.y() - cellY) * ESM::Land::LAND_SIZE; int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; osg::ref_ptr land = getLand (cellX, cellY); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; if (data) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); for (int row=startRow; rowmHeights[col*ESM::Land::LAND_SIZE+row]; if (h > max) max = h; if (h < min) min = h; } } return true; } min = defaultHeight; max = defaultHeight; return false; } void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) { while (col >= ESM::Land::LAND_SIZE-1) { ++cellY; col -= ESM::Land::LAND_SIZE-1; } while (row >= ESM::Land::LAND_SIZE-1) { ++cellX; row -= ESM::Land::LAND_SIZE-1; } while (col < 0) { --cellY; col += ESM::Land::LAND_SIZE-1; } while (row < 0) { --cellX; row += ESM::Land::LAND_SIZE-1; } const LandObject* land = getLand(cellX, cellY, cache); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : nullptr; if (data) { normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalize(); } else normal = osg::Vec3f(0,0,1); } void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache) { osg::Vec3f n1,n2,n3,n4; fixNormal(n1, cellX, cellY, col+1, row, cache); fixNormal(n2, cellX, cellY, col-1, row, cache); fixNormal(n3, cellX, cellY, col, row+1, cache); fixNormal(n4, cellX, cellY, col, row-1, cache); normal = (n1+n2+n3+n4); normal.normalize(); } void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) { if (col == ESM::Land::LAND_SIZE-1) { ++cellY; col = 0; } if (row == ESM::Land::LAND_SIZE-1) { ++cellX; row = 0; } const LandObject* land = getLand(cellX, cellY, cache); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : nullptr; if (data) { color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; } else { color.r() = 255; color.g() = 255; color.b() = 255; } } void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, osg::ref_ptr colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = static_cast(1) << lodLevel; osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); int startCellX = static_cast(std::floor(origin.x())); int startCellY = static_cast(std::floor(origin.y())); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); positions->resize(numVerts*numVerts); normals->resize(numVerts*numVerts); colours->resize(numVerts*numVerts); osg::Vec3f normal; osg::Vec4ub color; float vertY = 0; float vertX = 0; LandCache cache; bool alteration = useAlteration(); float vertY_ = 0; // of current cell corner for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const LandObject* land = getLand(cellX, cellY, cache); const ESM::Land::LandData *heightData = nullptr; const ESM::Land::LandData *normalData = nullptr; const ESM::Land::LandData *colourData = nullptr; if (land) { heightData = land->getData(ESM::Land::DATA_VHGT); normalData = land->getData(ESM::Land::DATA_VNML); colourData = land->getData(ESM::Land::DATA_VCLR); } int rowStart = 0; int colStart = 0; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell // This is only relevant if we're creating a chunk spanning multiple cells if (vertY_ != 0) colStart += increment; if (vertX_ != 0) rowStart += increment; // Only relevant for chunks smaller than (contained in) one cell rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; int rowEnd = std::min(static_cast(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); int colEnd = std::min(static_cast(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); vertY = vertY_; for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); assert(col >= 0 && col < ESM::Land::LAND_SIZE); assert (vertX < numVerts); assert (vertY < numVerts); float height = defaultHeight; if (heightData) height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; if (alteration) height += getAlteredHeight(col, row); (*positions)[static_cast(vertX*numVerts + vertY)] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, height); if (normalData) { for (int i=0; i<3; ++i) normal[i] = normalData->mNormals[srcArrayIndex+i]; normal.normalize(); } else normal = osg::Vec3f(0,0,1); // Normals apparently don't connect seamlessly between cells if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixNormal(normal, cellX, cellY, col, row, cache); // some corner normals appear to be complete garbage (z < 0) if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) averageNormal(normal, cellX, cellY, col, row, cache); assert(normal.z() > 0); (*normals)[static_cast(vertX*numVerts + vertY)] = normal; if (colourData) { for (int i=0; i<3; ++i) color[i] = colourData->mColours[srcArrayIndex+i]; } else { color.r() = 255; color.g() = 255; color.b() = 255; } if (alteration) adjustColor(col, row, heightData, color); //Does nothing by default, override in OpenMW-CS // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixColour(color, cellX, cellY, col, row, cache); color.a() = 255; (*colours)[static_cast(vertX*numVerts + vertY)] = color; ++vertX; } ++vertY; } vertX_ = vertX; } vertY_ = vertY; assert(vertX_ == numVerts); // Ensure we covered whole area } assert(vertY_ == numVerts); // Ensure we covered whole area } Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache& cache) { // For the first/last row/column, we need to get the texture from the neighbour cell // to get consistent blending at the borders --x; if (x < 0) { --cellX; x += ESM::Land::LAND_TEXTURE_SIZE; } while (x >= ESM::Land::LAND_TEXTURE_SIZE) { ++cellX; x -= ESM::Land::LAND_TEXTURE_SIZE; } while (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? { ++cellY; y -= ESM::Land::LAND_TEXTURE_SIZE; } assert(xgetData(ESM::Land::DATA_VTEX) : nullptr; if (data) { int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin return std::make_pair(tex, land->getPlugin()); } return std::make_pair(0,0); } std::string Storage::getTextureName(UniqueTextureId id) { static constexpr char defaultTexture[] = "textures\\_land_default.dds"; if (id.first == 0) return defaultTexture; // Not sure if the default texture really is hardcoded? // NB: All vtex ids are +1 compared to the ltex ids const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); if (!ltex) { Log(Debug::Warning) << "Warning: Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead"; return defaultTexture; } // this is needed due to MWs messed up texture handling std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS); return texture; } void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, ImageVector &blendmaps, std::vector &layerList) { osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); int cellX = static_cast(std::floor(origin.x())); int cellY = static_cast(std::floor(origin.y())); int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell int rowStart = (origin.x() - cellX) * realTextureSize; int colStart = (origin.y() - cellY) * realTextureSize; const int blendmapSize = (realTextureSize-1) * chunkSize + 1; // We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla const int imageScaleFactor = 2; const int blendmapImageSize = blendmapSize * imageScaleFactor; LandCache cache; std::map textureIndicesMap; for (int y=0; y::iterator found = textureIndicesMap.find(id); if (found == textureIndicesMap.end()) { unsigned int layerIndex = layerList.size(); Terrain::LayerInfo info = getLayerInfo(getTextureName(id)); // look for existing diffuse map, which may be present when several plugins use the same texture for (unsigned int i=0; i= layerList.size()) { osg::ref_ptr image (new osg::Image); image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); memset(pData, 0, image->getTotalDataSize()); blendmaps.emplace_back(image); layerList.emplace_back(info); } } unsigned int layerIndex = found->second; unsigned char* pData = blendmaps[layerIndex]->data(); int realY = (blendmapSize - y - 1)*imageScaleFactor; int realX = x*imageScaleFactor; pData[((realY+0)*blendmapImageSize + realX + 0)] = 255; pData[((realY+1)*blendmapImageSize + realX + 0)] = 255; pData[((realY+0)*blendmapImageSize + realX + 1)] = 255; pData[((realY+1)*blendmapImageSize + realX + 1)] = 255; } } if (blendmaps.size() == 1) blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } float Storage::getHeightAt(const osg::Vec3f &worldPos) { int cellX = static_cast(std::floor(worldPos.x() / float(Constants::CellSizeInUnits))); int cellY = static_cast(std::floor(worldPos.y() / float(Constants::CellSizeInUnits))); osg::ref_ptr land = getLand(cellX, cellY); if (!land) return defaultHeight; const ESM::Land::LandData* data = land->getData(ESM::Land::DATA_VHGT); if (!data) return defaultHeight; // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Normalized position in the cell float nX = (worldPos.x() - (cellX * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); float nY = (worldPos.y() - (cellY * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); // get left / bottom points (rounded down) float factor = ESM::Land::LAND_SIZE - 1.0f; float invFactor = 1.0f / factor; int startX = static_cast(nX * factor); int startY = static_cast(nY * factor); int endX = startX + 1; int endY = startY + 1; endX = std::min(endX, ESM::Land::LAND_SIZE-1); endY = std::min(endY, ESM::Land::LAND_SIZE-1); // now get points in terrain space (effectively rounding them to boundaries) float startXTS = startX * invFactor; float startYTS = startY * invFactor; float endXTS = endX * invFactor; float endYTS = endY * invFactor; // get parametric from start coord to next point float xParam = (nX - startXTS) * factor; float yParam = (nY - startYTS) * factor; /* For even / odd tri strip rows, triangles are this shape: even odd 3---2 3---2 | / | | \ | 0---1 0---1 */ // Build all 4 positions in normalized cell space, using point-sampled height osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / float(Constants::CellSizeInUnits)); osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / float(Constants::CellSizeInUnits)); osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / float(Constants::CellSizeInUnits)); osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / float(Constants::CellSizeInUnits)); // define this plane in terrain space osg::Plane plane; // FIXME: deal with differing triangle alignment if (true) { // odd row bool secondTri = ((1.0 - yParam) > xParam); if (secondTri) plane = osg::Plane(v0, v1, v3); else plane = osg::Plane(v1, v2, v3); } /* else { // even row bool secondTri = (yParam > xParam); if (secondTri) plane.redefine(v0, v2, v3); else plane.redefine(v0, v1, v2); } */ // Solve plane equation for z return (-plane.getNormal().x() * nX -plane.getNormal().y() * nY - plane[3]) / plane.getNormal().z() * Constants::CellSizeInUnits; } const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) { LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); if (found != cache.mMap.end()) return found->second; else { found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; return found->second; } } void Storage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const { } float Storage::getAlteredHeight(int col, int row) const { return 0; } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { std::lock_guard lock(mLayerInfoMutex); // Already have this cached? std::map::iterator found = mLayerInfoMap.find(texture); if (found != mLayerInfoMap.end()) return found->second; Terrain::LayerInfo info; info.mParallax = false; info.mSpecular = false; info.mDiffuseMap = texture; if (mAutoUseNormalMaps) { std::string texture_ = texture; Misc::StringUtils::replaceLast(texture_, ".", mNormalHeightMapPattern + "."); if (mVFS->exists(texture_)) { info.mNormalMap = texture_; info.mParallax = true; } else { texture_ = texture; Misc::StringUtils::replaceLast(texture_, ".", mNormalMapPattern + "."); if (mVFS->exists(texture_)) info.mNormalMap = texture_; } } if (mAutoUseSpecularMaps) { std::string texture_ = texture; Misc::StringUtils::replaceLast(texture_, ".", mSpecularMapPattern + "."); if (mVFS->exists(texture_)) { info.mDiffuseMap = texture_; info.mSpecular = true; } } mLayerInfoMap[texture] = info; return info; } float Storage::getCellWorldSize() { return static_cast(ESM::Land::REAL_SIZE); } int Storage::getCellVertices() { return ESM::Land::LAND_SIZE; } int Storage::getBlendmapScale(float chunkSize) { return ESM::Land::LAND_TEXTURE_SIZE*chunkSize; } } openmw-openmw-0.48.0/components/esm3terrain/storage.hpp000066400000000000000000000150201445372753700232300ustar00rootroot00000000000000#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H #include #include #include #include #include namespace VFS { class Manager; } namespace ESMTerrain { class LandCache; /// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use class LandObject : public osg::Object { public: LandObject(); LandObject(const ESM::Land* land, int loadFlags); LandObject(const LandObject& copy, const osg::CopyOp& copyop); virtual ~LandObject(); META_Object(ESMTerrain, LandObject) inline const ESM::Land::LandData* getData(int flags) const { if ((mData.mDataLoaded & flags) != flags) return nullptr; return &mData; } inline int getPlugin() const { return mLand->getPlugin(); } private: const ESM::Land* mLand; int mLoadFlags; ESM::Land::LandData mData; }; /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// into the terrain component, converting it on the fly as needed. class Storage : public Terrain::Storage { public: Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); // Not implemented in this class, because we need different Store implementations for game and editor virtual osg::ref_ptr getLand (int cellX, int cellY)= 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; /// Get bounds of the whole terrain in cell units void getBounds(float& minX, float& maxX, float& minY, float& maxY) override = 0; /// Get the minimum and maximum heights of a terrain region. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units /// @param min min height will be stored here /// @param max max height will be stored here /// @return true if there was data available for this terrain chunk bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) override; /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). /// The specified positions should be in local space, i.e. relative to the center of the terrain chunk. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units /// @param positions buffer to write vertices /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, osg::ref_ptr colours) override; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. /// @note May be called from background threads. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector& layerList) override; float getHeightAt (const osg::Vec3f& worldPos) override; /// Get the transformation factor for mapping cell units to world units. float getCellWorldSize() override; /// Get the number of vertices on one side for each cell. Should be (power of two)+1 int getCellVertices() override; int getBlendmapScale(float chunkSize) override; float getVertexHeight (const ESM::Land::LandData* data, int x, int y) { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); return data->mHeights[y * ESM::Land::LAND_SIZE + x]; } private: const VFS::Manager* mVFS; inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); virtual bool useAlteration() const { return false; } virtual void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const; virtual float getAlteredHeight(int col, int row) const; // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair typedef std::pair UniqueTextureId; inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&); std::string getTextureName (UniqueTextureId id); std::map mLayerInfoMap; std::mutex mLayerInfoMutex; std::string mNormalMapPattern; std::string mNormalHeightMapPattern; bool mAutoUseNormalMaps; std::string mSpecularMapPattern; bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); }; } #endif openmw-openmw-0.48.0/components/esm4/000077500000000000000000000000001445372753700174715ustar00rootroot00000000000000openmw-openmw-0.48.0/components/esm4/actor.hpp000066400000000000000000000113361445372753700213160ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ACTOR_H #define ESM4_ACTOR_H #include #include "formid.hpp" namespace ESM4 { #pragma pack(push, 1) struct AIData // NPC_, CREA { std::uint8_t aggression; std::uint8_t confidence; std::uint8_t energyLevel; std::uint8_t responsibility; std::uint32_t aiFlags; std::uint8_t trainSkill; std::uint8_t trainLevel; std::uint16_t unknown; }; struct AttributeValues { std::uint8_t strength; std::uint8_t intelligence; std::uint8_t willpower; std::uint8_t agility; std::uint8_t speed; std::uint8_t endurance; std::uint8_t personality; std::uint8_t luck; }; struct ACBS_TES4 { std::uint32_t flags; std::uint16_t baseSpell; std::uint16_t fatigue; std::uint16_t barterGold; std::int16_t levelOrOffset; std::uint16_t calcMin; std::uint16_t calcMax; std::uint32_t padding1; std::uint32_t padding2; }; struct ACBS_FO3 { std::uint32_t flags; std::uint16_t fatigue; std::uint16_t barterGold; std::int16_t levelOrMult; std::uint16_t calcMinlevel; std::uint16_t calcMaxlevel; std::uint16_t speedMultiplier; float karma; std::int16_t dispositionBase; std::uint16_t templateFlags; }; struct ACBS_TES5 { std::uint32_t flags; std::uint16_t magickaOffset; std::uint16_t staminaOffset; std::uint16_t levelOrMult; // TODO: check if int16_t std::uint16_t calcMinlevel; std::uint16_t calcMaxlevel; std::uint16_t speedMultiplier; std::uint16_t dispositionBase; // TODO: check if int16_t std::uint16_t templateFlags; std::uint16_t healthOffset; std::uint16_t bleedoutOverride; }; union ActorBaseConfig { ACBS_TES4 tes4; ACBS_FO3 fo3; ACBS_TES5 tes5; }; struct ActorFaction { FormId faction; std::int8_t rank; std::uint8_t unknown1; std::uint8_t unknown2; std::uint8_t unknown3; }; #pragma pack(pop) struct BodyTemplate // TES5 { // 0x00000001 - Head // 0x00000002 - Hair // 0x00000004 - Body // 0x00000008 - Hands // 0x00000010 - Forearms // 0x00000020 - Amulet // 0x00000040 - Ring // 0x00000080 - Feet // 0x00000100 - Calves // 0x00000200 - Shield // 0x00000400 - Tail // 0x00000800 - Long Hair // 0x00001000 - Circlet // 0x00002000 - Ears // 0x00004000 - Body AddOn 3 // 0x00008000 - Body AddOn 4 // 0x00010000 - Body AddOn 5 // 0x00020000 - Body AddOn 6 // 0x00040000 - Body AddOn 7 // 0x00080000 - Body AddOn 8 // 0x00100000 - Decapitate Head // 0x00200000 - Decapitate // 0x00400000 - Body AddOn 9 // 0x00800000 - Body AddOn 10 // 0x01000000 - Body AddOn 11 // 0x02000000 - Body AddOn 12 // 0x04000000 - Body AddOn 13 // 0x08000000 - Body AddOn 14 // 0x10000000 - Body AddOn 15 // 0x20000000 - Body AddOn 16 // 0x40000000 - Body AddOn 17 // 0x80000000 - FX01 std::uint32_t bodyPart; std::uint8_t flags; std::uint8_t unknown1; // probably padding std::uint8_t unknown2; // probably padding std::uint8_t unknown3; // probably padding std::uint32_t type; // 0 = light, 1 = heavy, 2 = none (cloth?) }; } #endif // ESM4_ACTOR_H openmw-openmw-0.48.0/components/esm4/common.cpp000066400000000000000000000064601445372753700214730ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "common.hpp" #include #include #include #include #include "formid.hpp" namespace ESM4 { const char *sGroupType[] = { "Record Type", "World Child", "Interior Cell", "Interior Sub Cell", "Exterior Cell", "Exterior Sub Cell", "Cell Child", "Topic Child", "Cell Persistent Child", "Cell Temporary Child", "Cell Visible Dist Child", "Unknown" }; std::string printLabel(const GroupLabel& label, const std::uint32_t type) { std::ostringstream ss; ss << std::string(sGroupType[std::min(type, (uint32_t)11)]); // avoid out of range switch (type) { case ESM4::Grp_RecordType: { ss << ": " << std::string((char*)label.recordType, 4); break; } case ESM4::Grp_ExteriorCell: case ESM4::Grp_ExteriorSubCell: { //short x, y; //y = label & 0xff; //x = (label >> 16) & 0xff; ss << ": grid (x, y) " << std::dec << label.grid[1] << ", " << label.grid[0]; break; } case ESM4::Grp_InteriorCell: case ESM4::Grp_InteriorSubCell: { ss << ": block 0x" << std::hex << label.value; break; } case ESM4::Grp_WorldChild: case ESM4::Grp_CellChild: case ESM4::Grp_TopicChild: case ESM4::Grp_CellPersistentChild: case ESM4::Grp_CellTemporaryChild: case ESM4::Grp_CellVisibleDistChild: { ss << ": FormId 0x" << formIdToString(label.value); break; } default: break; } return ss.str(); } void gridToString(std::int16_t x, std::int16_t y, std::string& str) { char buf[6+6+2+1]; // longest signed 16 bit number is 6 characters (-32768) int res = snprintf(buf, 6+6+2+1, "#%d %d", x, y); if (res > 0 && res < 6+6+2+1) str.assign(buf); else throw std::runtime_error("possible buffer overflow while converting grid"); } } openmw-openmw-0.48.0/components/esm4/common.hpp000066400000000000000000001105001445372753700214670ustar00rootroot00000000000000/* Copyright (C) 2015-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_COMMON_H #define ESM4_COMMON_H #include #include #include #include "formid.hpp" namespace ESM4 { using ESM::fourCC; // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format enum RecordTypes { REC_AACT = fourCC("AACT"), // Action REC_ACHR = fourCC("ACHR"), // Actor Reference REC_ACTI = fourCC("ACTI"), // Activator REC_ADDN = fourCC("ADDN"), // Addon Node REC_ALCH = fourCC("ALCH"), // Potion REC_AMMO = fourCC("AMMO"), // Ammo REC_ANIO = fourCC("ANIO"), // Animated Object REC_APPA = fourCC("APPA"), // Apparatus (probably unused) REC_ARMA = fourCC("ARMA"), // Armature (Model) REC_ARMO = fourCC("ARMO"), // Armor REC_ARTO = fourCC("ARTO"), // Art Object REC_ASPC = fourCC("ASPC"), // Acoustic Space REC_ASTP = fourCC("ASTP"), // Association Type REC_AVIF = fourCC("AVIF"), // Actor Values/Perk Tree Graphics REC_BOOK = fourCC("BOOK"), // Book REC_BPTD = fourCC("BPTD"), // Body Part Data REC_CAMS = fourCC("CAMS"), // Camera Shot REC_CELL = fourCC("CELL"), // Cell REC_CLAS = fourCC("CLAS"), // Class REC_CLFM = fourCC("CLFM"), // Color REC_CLMT = fourCC("CLMT"), // Climate REC_CLOT = fourCC("CLOT"), // Clothing REC_COBJ = fourCC("COBJ"), // Constructible Object (recipes) REC_COLL = fourCC("COLL"), // Collision Layer REC_CONT = fourCC("CONT"), // Container REC_CPTH = fourCC("CPTH"), // Camera Path REC_CREA = fourCC("CREA"), // Creature REC_CSTY = fourCC("CSTY"), // Combat Style REC_DEBR = fourCC("DEBR"), // Debris REC_DIAL = fourCC("DIAL"), // Dialog Topic REC_DLBR = fourCC("DLBR"), // Dialog Branch REC_DLVW = fourCC("DLVW"), // Dialog View REC_DOBJ = fourCC("DOBJ"), // Default Object Manager REC_DOOR = fourCC("DOOR"), // Door REC_DUAL = fourCC("DUAL"), // Dual Cast Data (possibly unused) REC_ECZN = fourCC("ECZN"), // Encounter Zone REC_EFSH = fourCC("EFSH"), // Effect Shader REC_ENCH = fourCC("ENCH"), // Enchantment REC_EQUP = fourCC("EQUP"), // Equip Slot (flag-type values) REC_EXPL = fourCC("EXPL"), // Explosion REC_EYES = fourCC("EYES"), // Eyes REC_FACT = fourCC("FACT"), // Faction REC_FLOR = fourCC("FLOR"), // Flora REC_FLST = fourCC("FLST"), // Form List (non-levelled list) REC_FSTP = fourCC("FSTP"), // Footstep REC_FSTS = fourCC("FSTS"), // Footstep Set REC_FURN = fourCC("FURN"), // Furniture REC_GLOB = fourCC("GLOB"), // Global Variable REC_GMST = fourCC("GMST"), // Game Setting REC_GRAS = fourCC("GRAS"), // Grass REC_GRUP = fourCC("GRUP"), // Form Group REC_HAIR = fourCC("HAIR"), // Hair REC_HAZD = fourCC("HAZD"), // Hazard REC_HDPT = fourCC("HDPT"), // Head Part REC_IDLE = fourCC("IDLE"), // Idle Animation REC_IDLM = fourCC("IDLM"), // Idle Marker REC_IMAD = fourCC("IMAD"), // Image Space Modifier REC_IMGS = fourCC("IMGS"), // Image Space REC_INFO = fourCC("INFO"), // Dialog Topic Info REC_INGR = fourCC("INGR"), // Ingredient REC_IPCT = fourCC("IPCT"), // Impact Data REC_IPDS = fourCC("IPDS"), // Impact Data Set REC_KEYM = fourCC("KEYM"), // Key REC_KYWD = fourCC("KYWD"), // Keyword REC_LAND = fourCC("LAND"), // Land REC_LCRT = fourCC("LCRT"), // Location Reference Type REC_LCTN = fourCC("LCTN"), // Location REC_LGTM = fourCC("LGTM"), // Lighting Template REC_LIGH = fourCC("LIGH"), // Light REC_LSCR = fourCC("LSCR"), // Load Screen REC_LTEX = fourCC("LTEX"), // Land Texture REC_LVLC = fourCC("LVLC"), // Leveled Creature REC_LVLI = fourCC("LVLI"), // Leveled Item REC_LVLN = fourCC("LVLN"), // Leveled Actor REC_LVSP = fourCC("LVSP"), // Leveled Spell REC_MATO = fourCC("MATO"), // Material Object REC_MATT = fourCC("MATT"), // Material Type REC_MESG = fourCC("MESG"), // Message REC_MGEF = fourCC("MGEF"), // Magic Effect REC_MISC = fourCC("MISC"), // Misc. Object REC_MOVT = fourCC("MOVT"), // Movement Type REC_MSTT = fourCC("MSTT"), // Movable Static REC_MUSC = fourCC("MUSC"), // Music Type REC_MUST = fourCC("MUST"), // Music Track REC_NAVI = fourCC("NAVI"), // Navigation (master data) REC_NAVM = fourCC("NAVM"), // Nav Mesh REC_NOTE = fourCC("NOTE"), // Note REC_NPC_ = fourCC("NPC_"), // Actor (NPC, Creature) REC_OTFT = fourCC("OTFT"), // Outfit REC_PACK = fourCC("PACK"), // AI Package REC_PERK = fourCC("PERK"), // Perk REC_PGRE = fourCC("PGRE"), // Placed grenade REC_PHZD = fourCC("PHZD"), // Placed hazard REC_PROJ = fourCC("PROJ"), // Projectile REC_QUST = fourCC("QUST"), // Quest REC_RACE = fourCC("RACE"), // Race / Creature type REC_REFR = fourCC("REFR"), // Object Reference REC_REGN = fourCC("REGN"), // Region (Audio/Weather) REC_RELA = fourCC("RELA"), // Relationship REC_REVB = fourCC("REVB"), // Reverb Parameters REC_RFCT = fourCC("RFCT"), // Visual Effect REC_SBSP = fourCC("SBSP"), // Subspace (TES4 only?) REC_SCEN = fourCC("SCEN"), // Scene REC_SCPT = fourCC("SCPT"), // Script REC_SCRL = fourCC("SCRL"), // Scroll REC_SGST = fourCC("SGST"), // Sigil Stone REC_SHOU = fourCC("SHOU"), // Shout REC_SLGM = fourCC("SLGM"), // Soul Gem REC_SMBN = fourCC("SMBN"), // Story Manager Branch Node REC_SMEN = fourCC("SMEN"), // Story Manager Event Node REC_SMQN = fourCC("SMQN"), // Story Manager Quest Node REC_SNCT = fourCC("SNCT"), // Sound Category REC_SNDR = fourCC("SNDR"), // Sound Reference REC_SOPM = fourCC("SOPM"), // Sound Output Model REC_SOUN = fourCC("SOUN"), // Sound REC_SPEL = fourCC("SPEL"), // Spell REC_SPGD = fourCC("SPGD"), // Shader Particle Geometry REC_STAT = fourCC("STAT"), // Static REC_TACT = fourCC("TACT"), // Talking Activator REC_TERM = fourCC("TERM"), // Terminal REC_TES4 = fourCC("TES4"), // Plugin info REC_TREE = fourCC("TREE"), // Tree REC_TXST = fourCC("TXST"), // Texture Set REC_VTYP = fourCC("VTYP"), // Voice Type REC_WATR = fourCC("WATR"), // Water Type REC_WEAP = fourCC("WEAP"), // Weapon REC_WOOP = fourCC("WOOP"), // Word Of Power REC_WRLD = fourCC("WRLD"), // World Space REC_WTHR = fourCC("WTHR"), // Weather REC_ACRE = fourCC("ACRE"), // Placed Creature (TES4 only?) REC_PGRD = fourCC("PGRD"), // Pathgrid (TES4 only?) REC_ROAD = fourCC("ROAD"), // Road (TES4 only?) REC_IMOD = fourCC("IMOD"), // Item Mod REC_PWAT = fourCC("PWAT"), // Placeable Water REC_SCOL = fourCC("SCOL"), // Static Collection REC_CCRD = fourCC("CCRD"), // Caravan Card REC_CMNY = fourCC("CMNY"), // Caravan Money REC_ALOC = fourCC("ALOC"), // Audio Location Controller REC_MSET = fourCC("MSET") // Media Set }; enum SubRecordTypes { SUB_HEDR = fourCC("HEDR"), SUB_CNAM = fourCC("CNAM"), SUB_SNAM = fourCC("SNAM"), // TES4 only? SUB_MAST = fourCC("MAST"), SUB_DATA = fourCC("DATA"), SUB_ONAM = fourCC("ONAM"), SUB_INTV = fourCC("INTV"), SUB_INCC = fourCC("INCC"), SUB_OFST = fourCC("OFST"), // TES4 only? SUB_DELE = fourCC("DELE"), // TES4 only? SUB_DNAM = fourCC("DNAM"), SUB_EDID = fourCC("EDID"), SUB_FULL = fourCC("FULL"), SUB_LTMP = fourCC("LTMP"), SUB_MHDT = fourCC("MHDT"), SUB_MNAM = fourCC("MNAM"), SUB_MODL = fourCC("MODL"), SUB_NAM0 = fourCC("NAM0"), SUB_NAM2 = fourCC("NAM2"), SUB_NAM3 = fourCC("NAM3"), SUB_NAM4 = fourCC("NAM4"), SUB_NAM9 = fourCC("NAM9"), SUB_NAMA = fourCC("NAMA"), SUB_PNAM = fourCC("PNAM"), SUB_RNAM = fourCC("RNAM"), SUB_TNAM = fourCC("TNAM"), SUB_UNAM = fourCC("UNAM"), SUB_WCTR = fourCC("WCTR"), SUB_WNAM = fourCC("WNAM"), SUB_XEZN = fourCC("XEZN"), SUB_XLCN = fourCC("XLCN"), SUB_XXXX = fourCC("XXXX"), SUB_ZNAM = fourCC("ZNAM"), SUB_MODT = fourCC("MODT"), SUB_ICON = fourCC("ICON"), // TES4 only? SUB_NVER = fourCC("NVER"), SUB_NVMI = fourCC("NVMI"), SUB_NVPP = fourCC("NVPP"), SUB_NVSI = fourCC("NVSI"), SUB_NVNM = fourCC("NVNM"), SUB_NNAM = fourCC("NNAM"), SUB_XCLC = fourCC("XCLC"), SUB_XCLL = fourCC("XCLL"), SUB_TVDT = fourCC("TVDT"), SUB_XCGD = fourCC("XCGD"), SUB_LNAM = fourCC("LNAM"), SUB_XCLW = fourCC("XCLW"), SUB_XNAM = fourCC("XNAM"), SUB_XCLR = fourCC("XCLR"), SUB_XWCS = fourCC("XWCS"), SUB_XWCN = fourCC("XWCN"), SUB_XWCU = fourCC("XWCU"), SUB_XCWT = fourCC("XCWT"), SUB_XOWN = fourCC("XOWN"), SUB_XILL = fourCC("XILL"), SUB_XWEM = fourCC("XWEM"), SUB_XCCM = fourCC("XCCM"), SUB_XCAS = fourCC("XCAS"), SUB_XCMO = fourCC("XCMO"), SUB_XCIM = fourCC("XCIM"), SUB_XCMT = fourCC("XCMT"), // TES4 only? SUB_XRNK = fourCC("XRNK"), // TES4 only? SUB_XGLB = fourCC("XGLB"), // TES4 only? SUB_VNML = fourCC("VNML"), SUB_VHGT = fourCC("VHGT"), SUB_VCLR = fourCC("VCLR"), SUA_BTXT = fourCC("BTXT"), SUB_ATXT = fourCC("ATXT"), SUB_VTXT = fourCC("VTXT"), SUB_VTEX = fourCC("VTEX"), SUB_HNAM = fourCC("HNAM"), SUB_GNAM = fourCC("GNAM"), SUB_RCLR = fourCC("RCLR"), SUB_RPLI = fourCC("RPLI"), SUB_RPLD = fourCC("RPLD"), SUB_RDAT = fourCC("RDAT"), SUB_RDMD = fourCC("RDMD"), // TES4 only? SUB_RDSD = fourCC("RDSD"), // TES4 only? SUB_RDGS = fourCC("RDGS"), // TES4 only? SUB_RDMO = fourCC("RDMO"), SUB_RDSA = fourCC("RDSA"), SUB_RDWT = fourCC("RDWT"), SUB_RDOT = fourCC("RDOT"), SUB_RDMP = fourCC("RDMP"), SUB_MODB = fourCC("MODB"), SUB_OBND = fourCC("OBND"), SUB_MODS = fourCC("MODS"), SUB_NAME = fourCC("NAME"), SUB_XMRK = fourCC("XMRK"), SUB_FNAM = fourCC("FNAM"), SUB_XSCL = fourCC("XSCL"), SUB_XTEL = fourCC("XTEL"), SUB_XTRG = fourCC("XTRG"), SUB_XSED = fourCC("XSED"), SUB_XLOD = fourCC("XLOD"), SUB_XPCI = fourCC("XPCI"), SUB_XLOC = fourCC("XLOC"), SUB_XESP = fourCC("XESP"), SUB_XLCM = fourCC("XLCM"), SUB_XRTM = fourCC("XRTM"), SUB_XACT = fourCC("XACT"), SUB_XCNT = fourCC("XCNT"), SUB_VMAD = fourCC("VMAD"), SUB_XPRM = fourCC("XPRM"), SUB_XMBO = fourCC("XMBO"), SUB_XPOD = fourCC("XPOD"), SUB_XRMR = fourCC("XRMR"), SUB_INAM = fourCC("INAM"), SUB_SCHR = fourCC("SCHR"), SUB_XLRM = fourCC("XLRM"), SUB_XRGD = fourCC("XRGD"), SUB_XRDS = fourCC("XRDS"), SUB_XEMI = fourCC("XEMI"), SUB_XLIG = fourCC("XLIG"), SUB_XALP = fourCC("XALP"), SUB_XNDP = fourCC("XNDP"), SUB_XAPD = fourCC("XAPD"), SUB_XAPR = fourCC("XAPR"), SUB_XLIB = fourCC("XLIB"), SUB_XLKR = fourCC("XLKR"), SUB_XLRT = fourCC("XLRT"), SUB_XCVL = fourCC("XCVL"), SUB_XCVR = fourCC("XCVR"), SUB_XCZA = fourCC("XCZA"), SUB_XCZC = fourCC("XCZC"), SUB_XFVC = fourCC("XFVC"), SUB_XHTW = fourCC("XHTW"), SUB_XIS2 = fourCC("XIS2"), SUB_XMBR = fourCC("XMBR"), SUB_XCCP = fourCC("XCCP"), SUB_XPWR = fourCC("XPWR"), SUB_XTRI = fourCC("XTRI"), SUB_XATR = fourCC("XATR"), SUB_XPRD = fourCC("XPRD"), SUB_XPPA = fourCC("XPPA"), SUB_PDTO = fourCC("PDTO"), SUB_XLRL = fourCC("XLRL"), SUB_QNAM = fourCC("QNAM"), SUB_COCT = fourCC("COCT"), SUB_COED = fourCC("COED"), SUB_CNTO = fourCC("CNTO"), SUB_SCRI = fourCC("SCRI"), SUB_BNAM = fourCC("BNAM"), SUB_BMDT = fourCC("BMDT"), SUB_MOD2 = fourCC("MOD2"), SUB_MOD3 = fourCC("MOD3"), SUB_MOD4 = fourCC("MOD4"), SUB_MO2B = fourCC("MO2B"), SUB_MO3B = fourCC("MO3B"), SUB_MO4B = fourCC("MO4B"), SUB_MO2T = fourCC("MO2T"), SUB_MO3T = fourCC("MO3T"), SUB_MO4T = fourCC("MO4T"), SUB_ANAM = fourCC("ANAM"), SUB_ENAM = fourCC("ENAM"), SUB_ICO2 = fourCC("ICO2"), SUB_ACBS = fourCC("ACBS"), SUB_SPLO = fourCC("SPLO"), SUB_AIDT = fourCC("AIDT"), SUB_PKID = fourCC("PKID"), SUB_HCLR = fourCC("HCLR"), SUB_FGGS = fourCC("FGGS"), SUB_FGGA = fourCC("FGGA"), SUB_FGTS = fourCC("FGTS"), SUB_KFFZ = fourCC("KFFZ"), SUB_PFIG = fourCC("PFIG"), SUB_PFPC = fourCC("PFPC"), SUB_XHRS = fourCC("XHRS"), SUB_XMRC = fourCC("XMRC"), SUB_SNDD = fourCC("SNDD"), SUB_SNDX = fourCC("SNDX"), SUB_DESC = fourCC("DESC"), SUB_ENIT = fourCC("ENIT"), SUB_EFID = fourCC("EFID"), SUB_EFIT = fourCC("EFIT"), SUB_SCIT = fourCC("SCIT"), SUB_SOUL = fourCC("SOUL"), SUB_SLCP = fourCC("SLCP"), SUB_CSCR = fourCC("CSCR"), SUB_CSDI = fourCC("CSDI"), SUB_CSDC = fourCC("CSDC"), SUB_NIFZ = fourCC("NIFZ"), SUB_CSDT = fourCC("CSDT"), SUB_NAM1 = fourCC("NAM1"), SUB_NIFT = fourCC("NIFT"), SUB_LVLD = fourCC("LVLD"), SUB_LVLF = fourCC("LVLF"), SUB_LVLO = fourCC("LVLO"), SUB_BODT = fourCC("BODT"), SUB_YNAM = fourCC("YNAM"), SUB_DEST = fourCC("DEST"), SUB_DMDL = fourCC("DMDL"), SUB_DMDS = fourCC("DMDS"), SUB_DMDT = fourCC("DMDT"), SUB_DSTD = fourCC("DSTD"), SUB_DSTF = fourCC("DSTF"), SUB_KNAM = fourCC("KNAM"), SUB_KSIZ = fourCC("KSIZ"), SUB_KWDA = fourCC("KWDA"), SUB_VNAM = fourCC("VNAM"), SUB_SDSC = fourCC("SDSC"), SUB_MO2S = fourCC("MO2S"), SUB_MO4S = fourCC("MO4S"), SUB_BOD2 = fourCC("BOD2"), SUB_BAMT = fourCC("BAMT"), SUB_BIDS = fourCC("BIDS"), SUB_ETYP = fourCC("ETYP"), SUB_BMCT = fourCC("BMCT"), SUB_MICO = fourCC("MICO"), SUB_MIC2 = fourCC("MIC2"), SUB_EAMT = fourCC("EAMT"), SUB_EITM = fourCC("EITM"), SUB_SCTX = fourCC("SCTX"), SUB_XLTW = fourCC("XLTW"), SUB_XMBP = fourCC("XMBP"), SUB_XOCP = fourCC("XOCP"), SUB_XRGB = fourCC("XRGB"), SUB_XSPC = fourCC("XSPC"), SUB_XTNM = fourCC("XTNM"), SUB_ATKR = fourCC("ATKR"), SUB_CRIF = fourCC("CRIF"), SUB_DOFT = fourCC("DOFT"), SUB_DPLT = fourCC("DPLT"), SUB_ECOR = fourCC("ECOR"), SUB_ATKD = fourCC("ATKD"), SUB_ATKE = fourCC("ATKE"), SUB_FTST = fourCC("FTST"), SUB_HCLF = fourCC("HCLF"), SUB_NAM5 = fourCC("NAM5"), SUB_NAM6 = fourCC("NAM6"), SUB_NAM7 = fourCC("NAM7"), SUB_NAM8 = fourCC("NAM8"), SUB_PRKR = fourCC("PRKR"), SUB_PRKZ = fourCC("PRKZ"), SUB_SOFT = fourCC("SOFT"), SUB_SPCT = fourCC("SPCT"), SUB_TINC = fourCC("TINC"), SUB_TIAS = fourCC("TIAS"), SUB_TINI = fourCC("TINI"), SUB_TINV = fourCC("TINV"), SUB_TPLT = fourCC("TPLT"), SUB_VTCK = fourCC("VTCK"), SUB_SHRT = fourCC("SHRT"), SUB_SPOR = fourCC("SPOR"), SUB_XHOR = fourCC("XHOR"), SUB_CTDA = fourCC("CTDA"), SUB_CRDT = fourCC("CRDT"), SUB_FNMK = fourCC("FNMK"), SUB_FNPR = fourCC("FNPR"), SUB_WBDT = fourCC("WBDT"), SUB_QUAL = fourCC("QUAL"), SUB_INDX = fourCC("INDX"), SUB_ATTR = fourCC("ATTR"), SUB_MTNM = fourCC("MTNM"), SUB_UNES = fourCC("UNES"), SUB_TIND = fourCC("TIND"), SUB_TINL = fourCC("TINL"), SUB_TINP = fourCC("TINP"), SUB_TINT = fourCC("TINT"), SUB_TIRS = fourCC("TIRS"), SUB_PHWT = fourCC("PHWT"), SUB_AHCF = fourCC("AHCF"), SUB_AHCM = fourCC("AHCM"), SUB_HEAD = fourCC("HEAD"), SUB_MPAI = fourCC("MPAI"), SUB_MPAV = fourCC("MPAV"), SUB_DFTF = fourCC("DFTF"), SUB_DFTM = fourCC("DFTM"), SUB_FLMV = fourCC("FLMV"), SUB_FTSF = fourCC("FTSF"), SUB_FTSM = fourCC("FTSM"), SUB_MTYP = fourCC("MTYP"), SUB_PHTN = fourCC("PHTN"), SUB_RNMV = fourCC("RNMV"), SUB_RPRF = fourCC("RPRF"), SUB_RPRM = fourCC("RPRM"), SUB_SNMV = fourCC("SNMV"), SUB_SPED = fourCC("SPED"), SUB_SWMV = fourCC("SWMV"), SUB_WKMV = fourCC("WKMV"), SUB_LLCT = fourCC("LLCT"), SUB_IDLF = fourCC("IDLF"), SUB_IDLA = fourCC("IDLA"), SUB_IDLC = fourCC("IDLC"), SUB_IDLT = fourCC("IDLT"), SUB_DODT = fourCC("DODT"), SUB_TX00 = fourCC("TX00"), SUB_TX01 = fourCC("TX01"), SUB_TX02 = fourCC("TX02"), SUB_TX03 = fourCC("TX03"), SUB_TX04 = fourCC("TX04"), SUB_TX05 = fourCC("TX05"), SUB_TX06 = fourCC("TX06"), SUB_TX07 = fourCC("TX07"), SUB_BPND = fourCC("BPND"), SUB_BPTN = fourCC("BPTN"), SUB_BPNN = fourCC("BPNN"), SUB_BPNT = fourCC("BPNT"), SUB_BPNI = fourCC("BPNI"), SUB_RAGA = fourCC("RAGA"), SUB_QSTI = fourCC("QSTI"), SUB_QSTR = fourCC("QSTR"), SUB_QSDT = fourCC("QSDT"), SUB_SCDA = fourCC("SCDA"), SUB_SCRO = fourCC("SCRO"), SUB_QSTA = fourCC("QSTA"), SUB_CTDT = fourCC("CTDT"), SUB_SCHD = fourCC("SCHD"), SUB_TCLF = fourCC("TCLF"), SUB_TCLT = fourCC("TCLT"), SUB_TRDT = fourCC("TRDT"), SUB_TPIC = fourCC("TPIC"), SUB_PKDT = fourCC("PKDT"), SUB_PSDT = fourCC("PSDT"), SUB_PLDT = fourCC("PLDT"), SUB_PTDT = fourCC("PTDT"), SUB_PGRP = fourCC("PGRP"), SUB_PGRR = fourCC("PGRR"), SUB_PGRI = fourCC("PGRI"), SUB_PGRL = fourCC("PGRL"), SUB_PGAG = fourCC("PGAG"), SUB_FLTV = fourCC("FLTV"), SUB_XHLT = fourCC("XHLT"), // Unofficial Oblivion Patch SUB_XCHG = fourCC("XCHG"), // thievery.exp SUB_ITXT = fourCC("ITXT"), SUB_MO5T = fourCC("MO5T"), SUB_MOD5 = fourCC("MOD5"), SUB_MDOB = fourCC("MDOB"), SUB_SPIT = fourCC("SPIT"), SUB_PTDA = fourCC("PTDA"), // TES5 SUB_PFOR = fourCC("PFOR"), // TES5 SUB_PFO2 = fourCC("PFO2"), // TES5 SUB_PRCB = fourCC("PRCB"), // TES5 SUB_PKCU = fourCC("PKCU"), // TES5 SUB_PKC2 = fourCC("PKC2"), // TES5 SUB_CITC = fourCC("CITC"), // TES5 SUB_CIS1 = fourCC("CIS1"), // TES5 SUB_CIS2 = fourCC("CIS2"), // TES5 SUB_TIFC = fourCC("TIFC"), // TES5 SUB_ALCA = fourCC("ALCA"), // TES5 SUB_ALCL = fourCC("ALCL"), // TES5 SUB_ALCO = fourCC("ALCO"), // TES5 SUB_ALDN = fourCC("ALDN"), // TES5 SUB_ALEA = fourCC("ALEA"), // TES5 SUB_ALED = fourCC("ALED"), // TES5 SUB_ALEQ = fourCC("ALEQ"), // TES5 SUB_ALFA = fourCC("ALFA"), // TES5 SUB_ALFC = fourCC("ALFC"), // TES5 SUB_ALFD = fourCC("ALFD"), // TES5 SUB_ALFE = fourCC("ALFE"), // TES5 SUB_ALFI = fourCC("ALFI"), // TES5 SUB_ALFL = fourCC("ALFL"), // TES5 SUB_ALFR = fourCC("ALFR"), // TES5 SUB_ALID = fourCC("ALID"), // TES5 SUB_ALLS = fourCC("ALLS"), // TES5 SUB_ALNA = fourCC("ALNA"), // TES5 SUB_ALNT = fourCC("ALNT"), // TES5 SUB_ALPC = fourCC("ALPC"), // TES5 SUB_ALRT = fourCC("ALRT"), // TES5 SUB_ALSP = fourCC("ALSP"), // TES5 SUB_ALST = fourCC("ALST"), // TES5 SUB_ALUA = fourCC("ALUA"), // TES5 SUB_FLTR = fourCC("FLTR"), // TES5 SUB_QTGL = fourCC("QTGL"), // TES5 SUB_TWAT = fourCC("TWAT"), // TES5 SUB_XIBS = fourCC("XIBS"), // FO3 SUB_REPL = fourCC("REPL"), // FO3 SUB_BIPL = fourCC("BIPL"), // FO3 SUB_MODD = fourCC("MODD"), // FO3 SUB_MOSD = fourCC("MOSD"), // FO3 SUB_MO3S = fourCC("MO3S"), // FO3 SUB_XCET = fourCC("XCET"), // FO3 SUB_LVLG = fourCC("LVLG"), // FO3 SUB_NVCI = fourCC("NVCI"), // FO3 SUB_NVVX = fourCC("NVVX"), // FO3 SUB_NVTR = fourCC("NVTR"), // FO3 SUB_NVCA = fourCC("NVCA"), // FO3 SUB_NVDP = fourCC("NVDP"), // FO3 SUB_NVGD = fourCC("NVGD"), // FO3 SUB_NVEX = fourCC("NVEX"), // FO3 SUB_XHLP = fourCC("XHLP"), // FO3 SUB_XRDO = fourCC("XRDO"), // FO3 SUB_XAMT = fourCC("XAMT"), // FO3 SUB_XAMC = fourCC("XAMC"), // FO3 SUB_XRAD = fourCC("XRAD"), // FO3 SUB_XORD = fourCC("XORD"), // FO3 SUB_XCLP = fourCC("XCLP"), // FO3 SUB_NEXT = fourCC("NEXT"), // FO3 SUB_QOBJ = fourCC("QOBJ"), // FO3 SUB_POBA = fourCC("POBA"), // FO3 SUB_POCA = fourCC("POCA"), // FO3 SUB_POEA = fourCC("POEA"), // FO3 SUB_PKDD = fourCC("PKDD"), // FO3 SUB_PKD2 = fourCC("PKD2"), // FO3 SUB_PKPT = fourCC("PKPT"), // FO3 SUB_PKED = fourCC("PKED"), // FO3 SUB_PKE2 = fourCC("PKE2"), // FO3 SUB_PKAM = fourCC("PKAM"), // FO3 SUB_PUID = fourCC("PUID"), // FO3 SUB_PKW3 = fourCC("PKW3"), // FO3 SUB_PTD2 = fourCC("PTD2"), // FO3 SUB_PLD2 = fourCC("PLD2"), // FO3 SUB_PKFD = fourCC("PKFD"), // FO3 SUB_IDLB = fourCC("IDLB"), // FO3 SUB_XDCR = fourCC("XDCR"), // FO3 SUB_DALC = fourCC("DALC"), // FO3 SUB_IMPS = fourCC("IMPS"), // FO3 Anchorage SUB_IMPF = fourCC("IMPF"), // FO3 Anchorage SUB_XATO = fourCC("XATO"), // FONV SUB_INFC = fourCC("INFC"), // FONV SUB_INFX = fourCC("INFX"), // FONV SUB_TDUM = fourCC("TDUM"), // FONV SUB_TCFU = fourCC("TCFU"), // FONV SUB_DAT2 = fourCC("DAT2"), // FONV SUB_RCIL = fourCC("RCIL"), // FONV SUB_MMRK = fourCC("MMRK"), // FONV SUB_SCRV = fourCC("SCRV"), // FONV SUB_SCVR = fourCC("SCVR"), // FONV SUB_SLSD = fourCC("SLSD"), // FONV SUB_XSRF = fourCC("XSRF"), // FONV SUB_XSRD = fourCC("XSRD"), // FONV SUB_WMI1 = fourCC("WMI1"), // FONV SUB_RDID = fourCC("RDID"), // FONV SUB_RDSB = fourCC("RDSB"), // FONV SUB_RDSI = fourCC("RDSI"), // FONV SUB_BRUS = fourCC("BRUS"), // FONV SUB_VATS = fourCC("VATS"), // FONV SUB_VANM = fourCC("VANM"), // FONV SUB_MWD1 = fourCC("MWD1"), // FONV SUB_MWD2 = fourCC("MWD2"), // FONV SUB_MWD3 = fourCC("MWD3"), // FONV SUB_MWD4 = fourCC("MWD4"), // FONV SUB_MWD5 = fourCC("MWD5"), // FONV SUB_MWD6 = fourCC("MWD6"), // FONV SUB_MWD7 = fourCC("MWD7"), // FONV SUB_WMI2 = fourCC("WMI2"), // FONV SUB_WMI3 = fourCC("WMI3"), // FONV SUB_WMS1 = fourCC("WMS1"), // FONV SUB_WMS2 = fourCC("WMS2"), // FONV SUB_WNM1 = fourCC("WNM1"), // FONV SUB_WNM2 = fourCC("WNM2"), // FONV SUB_WNM3 = fourCC("WNM3"), // FONV SUB_WNM4 = fourCC("WNM4"), // FONV SUB_WNM5 = fourCC("WNM5"), // FONV SUB_WNM6 = fourCC("WNM6"), // FONV SUB_WNM7 = fourCC("WNM7"), // FONV SUB_JNAM = fourCC("JNAM"), // FONV SUB_EFSD = fourCC("EFSD"), // FONV DeadMoney }; enum MagicEffectID { // Alteration EFI_BRDN = fourCC("BRDN"), EFI_FTHR = fourCC("FTHR"), EFI_FISH = fourCC("FISH"), EFI_FRSH = fourCC("FRSH"), EFI_OPEN = fourCC("OPNN"), EFI_SHLD = fourCC("SHLD"), EFI_LISH = fourCC("LISH"), EFI_WABR = fourCC("WABR"), EFI_WAWA = fourCC("WAWA"), // Conjuration EFI_BABO = fourCC("BABO"), // Bound Boots EFI_BACU = fourCC("BACU"), // Bound Cuirass EFI_BAGA = fourCC("BAGA"), // Bound Gauntlets EFI_BAGR = fourCC("BAGR"), // Bound Greaves EFI_BAHE = fourCC("BAHE"), // Bound Helmet EFI_BASH = fourCC("BASH"), // Bound Shield EFI_BWAX = fourCC("BWAX"), // Bound Axe EFI_BWBO = fourCC("BWBO"), // Bound Bow EFI_BWDA = fourCC("BWDA"), // Bound Dagger EFI_BWMA = fourCC("BWMA"), // Bound Mace EFI_BWSW = fourCC("BWSW"), // Bound Sword EFI_Z001 = fourCC("Z001"), // Summon Rufio's Ghost EFI_Z002 = fourCC("Z002"), // Summon Ancestor Guardian EFI_Z003 = fourCC("Z003"), // Summon Spiderling EFI_Z005 = fourCC("Z005"), // Summon Bear EFI_ZCLA = fourCC("ZCLA"), // Summon Clannfear EFI_ZDAE = fourCC("ZDAE"), // Summon Daedroth EFI_ZDRE = fourCC("ZDRE"), // Summon Dremora EFI_ZDRL = fourCC("ZDRL"), // Summon Dremora Lord EFI_ZFIA = fourCC("ZFIA"), // Summon Flame Atronach EFI_ZFRA = fourCC("ZFRA"), // Summon Frost Atronach EFI_ZGHO = fourCC("ZGHO"), // Summon Ghost EFI_ZHDZ = fourCC("ZHDZ"), // Summon Headless Zombie EFI_ZLIC = fourCC("ZLIC"), // Summon Lich EFI_ZSCA = fourCC("ZSCA"), // Summon Scamp EFI_ZSKE = fourCC("ZSKE"), // Summon Skeleton EFI_ZSKA = fourCC("ZSKA"), // Summon Skeleton Guardian EFI_ZSKH = fourCC("ZSKH"), // Summon Skeleton Hero EFI_ZSKC = fourCC("ZSKC"), // Summon Skeleton Champion EFI_ZSPD = fourCC("ZSPD"), // Summon Spider Daedra EFI_ZSTA = fourCC("ZSTA"), // Summon Storm Atronach EFI_ZWRA = fourCC("ZWRA"), // Summon Faded Wraith EFI_ZWRL = fourCC("ZWRL"), // Summon Gloom Wraith EFI_ZXIV = fourCC("ZXIV"), // Summon Xivilai EFI_ZZOM = fourCC("ZZOM"), // Summon Zombie EFI_TURN = fourCC("TURN"), // Turn Undead // Destruction EFI_DGAT = fourCC("DGAT"), // Damage Attribute EFI_DGFA = fourCC("DGFA"), // Damage Fatigue EFI_DGHE = fourCC("DGHE"), // Damage Health EFI_DGSP = fourCC("DGSP"), // Damage Magicka EFI_DIAR = fourCC("DIAR"), // Disintegrate Armor EFI_DIWE = fourCC("DIWE"), // Disintegrate Weapon EFI_DRAT = fourCC("DRAT"), // Drain Attribute EFI_DRFA = fourCC("DRFA"), // Drain Fatigue EFI_DRHE = fourCC("DRHE"), // Drain Health EFI_DRSP = fourCC("DRSP"), // Drain Magicka EFI_DRSK = fourCC("DRSK"), // Drain Skill EFI_FIDG = fourCC("FIDG"), // Fire Damage EFI_FRDG = fourCC("FRDG"), // Frost Damage EFI_SHDG = fourCC("SHDG"), // Shock Damage EFI_WKDI = fourCC("WKDI"), // Weakness to Disease EFI_WKFI = fourCC("WKFI"), // Weakness to Fire EFI_WKFR = fourCC("WKFR"), // Weakness to Frost EFI_WKMA = fourCC("WKMA"), // Weakness to Magic EFI_WKNW = fourCC("WKNW"), // Weakness to Normal Weapons EFI_WKPO = fourCC("WKPO"), // Weakness to Poison EFI_WKSH = fourCC("WKSH"), // Weakness to Shock // Illusion EFI_CALM = fourCC("CALM"), // Calm EFI_CHML = fourCC("CHML"), // Chameleon EFI_CHRM = fourCC("CHRM"), // Charm EFI_COCR = fourCC("COCR"), // Command Creature EFI_COHU = fourCC("COHU"), // Command Humanoid EFI_DEMO = fourCC("DEMO"), // Demoralize EFI_FRNZ = fourCC("FRNZ"), // Frenzy EFI_INVI = fourCC("INVI"), // Invisibility EFI_LGHT = fourCC("LGHT"), // Light EFI_NEYE = fourCC("NEYE"), // Night-Eye EFI_PARA = fourCC("PARA"), // Paralyze EFI_RALY = fourCC("RALY"), // Rally EFI_SLNC = fourCC("SLNC"), // Silence // Mysticism EFI_DTCT = fourCC("DTCT"), // Detect Life EFI_DSPL = fourCC("DSPL"), // Dispel EFI_REDG = fourCC("REDG"), // Reflect Damage EFI_RFLC = fourCC("RFLC"), // Reflect Spell EFI_STRP = fourCC("STRP"), // Soul Trap EFI_SABS = fourCC("SABS"), // Spell Absorption EFI_TELE = fourCC("TELE"), // Telekinesis // Restoration EFI_ABAT = fourCC("ABAT"), // Absorb Attribute EFI_ABFA = fourCC("ABFA"), // Absorb Fatigue EFI_ABHe = fourCC("ABHe"), // Absorb Health EFI_ABSP = fourCC("ABSP"), // Absorb Magicka EFI_ABSK = fourCC("ABSK"), // Absorb Skill EFI_1400 = fourCC("1400"), // Cure Disease EFI_CUPA = fourCC("CUPA"), // Cure Paralysis EFI_CUPO = fourCC("CUPO"), // Cure Poison EFI_FOAT = fourCC("FOAT"), // Fortify Attribute EFI_FOFA = fourCC("FOFA"), // Fortify Fatigue EFI_FOHE = fourCC("FOHE"), // Fortify Health EFI_FOSP = fourCC("FOSP"), // Fortify Magicka EFI_FOSK = fourCC("FOSK"), // Fortify Skill EFI_RSDI = fourCC("RSDI"), // Resist Disease EFI_RSFI = fourCC("RSFI"), // Resist Fire EFI_RSFR = fourCC("RSFR"), // Resist Frost EFI_RSMA = fourCC("RSMA"), // Resist Magic EFI_RSNW = fourCC("RSNW"), // Resist Normal Weapons EFI_RSPA = fourCC("RSPA"), // Resist Paralysis EFI_RSPO = fourCC("RSPO"), // Resist Poison EFI_RSSH = fourCC("RSSH"), // Resist Shock EFI_REAT = fourCC("REAT"), // Restore Attribute EFI_REFA = fourCC("REFA"), // Restore Fatigue EFI_REHE = fourCC("REHE"), // Restore Health EFI_RESP = fourCC("RESP"), // Restore Magicka // Effects EFI_LOCK = fourCC("LOCK"), // Lock Lock EFI_SEFF = fourCC("SEFF"), // Script Effect EFI_Z020 = fourCC("Z020"), // Summon 20 Extra EFI_MYHL = fourCC("MYHL"), // Summon Mythic Dawn Helmet EFI_MYTH = fourCC("MYTH"), // Summon Mythic Dawn Armor EFI_REAN = fourCC("REAN"), // Reanimate EFI_DISE = fourCC("DISE"), // Disease Info EFI_POSN = fourCC("POSN"), // Poison Info EFI_DUMY = fourCC("DUMY"), // Mehrunes Dagon Custom Effect EFI_STMA = fourCC("STMA"), // Stunted Magicka EFI_SUDG = fourCC("SUDG"), // Sun Damage EFI_VAMP = fourCC("VAMP"), // Vampirism EFI_DARK = fourCC("DARK"), // Darkness EFI_RSWD = fourCC("RSWD") // Resist Water Damage }; // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups enum GroupType { Grp_RecordType = 0, Grp_WorldChild = 1, Grp_InteriorCell = 2, Grp_InteriorSubCell = 3, Grp_ExteriorCell = 4, Grp_ExteriorSubCell = 5, Grp_CellChild = 6, Grp_TopicChild = 7, Grp_CellPersistentChild = 8, Grp_CellTemporaryChild = 9, Grp_CellVisibleDistChild = 10 }; // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records enum RecordFlag { Rec_ESM = 0x00000001, // (TES4 record only) Master (ESM) file. Rec_Deleted = 0x00000020, // Deleted Rec_Constant = 0x00000040, // Constant Rec_HiddenLMap = 0x00000040, // (REFR) Hidden From Local Map (Needs Confirmation: Related to shields) Rec_Localized = 0x00000080, // (TES4 record only) Is localized. This will make Skyrim load the // .STRINGS, .DLSTRINGS, and .ILSTRINGS files associated with the mod. // If this flag is not set, lstrings are treated as zstrings. Rec_FireOff = 0x00000080, // (PHZD) Turn off fire Rec_UpdateAnim = 0x00000100, // Must Update Anims Rec_NoAccess = 0x00000100, // (REFR) Inaccessible Rec_Hidden = 0x00000200, // (REFR) Hidden from local map Rec_StartDead = 0x00000200, // (ACHR) Starts dead /(REFR) MotionBlurCastsShadows Rec_Persistent = 0x00000400, // Quest item / Persistent reference Rec_DispMenu = 0x00000400, // (LSCR) Displays in Main Menu Rec_Disabled = 0x00000800, // Initially disabled Rec_Ignored = 0x00001000, // Ignored Rec_VisDistant = 0x00008000, // Visible when distant Rec_RandAnim = 0x00010000, // (ACTI) Random Animation Start Rec_Danger = 0x00020000, // (ACTI) Dangerous / Off limits (Interior cell) // Dangerous Can't be set withough Ignore Object Interaction Rec_Compressed = 0x00040000, // Data is compressed Rec_CanNotWait = 0x00080000, // Can't wait Rec_IgnoreObj = 0x00100000, // (ACTI) Ignore Object Interaction // Ignore Object Interaction Sets Dangerous Automatically Rec_Marker = 0x00800000, // Is Marker Rec_Obstacle = 0x02000000, // (ACTI) Obstacle / (REFR) No AI Acquire Rec_NavMFilter = 0x04000000, // NavMesh Gen - Filter Rec_NavMBBox = 0x08000000, // NavMesh Gen - Bounding Box Rec_ExitToTalk = 0x10000000, // (FURN) Must Exit to Talk Rec_Refected = 0x10000000, // (REFR) Reflected By Auto Water Rec_ChildUse = 0x20000000, // (FURN/IDLM) Child Can Use Rec_NoHavok = 0x20000000, // (REFR) Don't Havok Settle Rec_NavMGround = 0x40000000, // NavMesh Gen - Ground Rec_NoRespawn = 0x40000000, // (REFR) NoRespawn Rec_MultiBound = 0x80000000 // (REFR) MultiBound }; #pragma pack(push, 1) // NOTE: the label field of a group is not reliable (http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format) union GroupLabel { std::uint32_t value; // formId, blockNo or raw int representation of type char recordType[4]; // record type in ascii std::int16_t grid[2]; // grid y, x (note the reverse order) }; struct GroupTypeHeader { std::uint32_t typeId; std::uint32_t groupSize; // includes the 24 bytes (20 for TES4) of header (i.e. this struct) GroupLabel label; // format based on type std::int32_t type; std::uint16_t stamp; // & 0xff for day, & 0xff00 for months since Dec 2002 (i.e. 1 = Jan 2003) std::uint16_t unknown; std::uint16_t version; // not in TES4 std::uint16_t unknown2; // not in TES4 }; struct RecordTypeHeader { std::uint32_t typeId; std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header std::uint32_t flags; FormId id; std::uint32_t revision; std::uint16_t version; // not in TES4 std::uint16_t unknown; // not in TES4 }; union RecordHeader { struct GroupTypeHeader group; struct RecordTypeHeader record; }; struct SubRecordHeader { std::uint32_t typeId; std::uint16_t dataSize; }; // Grid, CellGrid and Vertex are shared by NVMI(NAVI) and NVNM(NAVM) struct Grid { std::int16_t x; std::int16_t y; }; union CellGrid { FormId cellId; Grid grid; }; struct Vertex { float x; float y; float z; }; #pragma pack(pop) // For pretty printing GroupHeader labels std::string printLabel(const GroupLabel& label, const std::uint32_t type); void gridToString(std::int16_t x, std::int16_t y, std::string& str); } #endif // ESM4_COMMON_H openmw-openmw-0.48.0/components/esm4/dialogue.hpp000066400000000000000000000027721445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_DIALOGUE_H #define ESM4_DIALOGUE_H namespace ESM4 { enum DialType { DTYP_Topic = 0, DTYP_Conversation = 1, DTYP_Combat = 2, DTYP_Persuation = 3, DTYP_Detection = 4, DTYP_Service = 5, DTYP_Miscellaneous = 6, // below FO3/FONV DTYP_Radio = 7 }; } #endif // ESM4_DIALOGUE_H openmw-openmw-0.48.0/components/esm4/effect.hpp000066400000000000000000000034301445372753700214360ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_EFFECT_H #define ESM4_EFFECT_H #include #include "formid.hpp" namespace ESM4 { #pragma pack(push, 1) union EFI_Label { std::uint32_t value; char effect[4]; }; struct ScriptEffect { FormId formId; // Script effect (Magic effect must be SEFF) std::int32_t school; // Magic school. See Magic schools for more information. EFI_Label visualEffect; // Visual effect name or 0x00000000 if None std::uint8_t flags; // 0x01 = Hostile std::uint8_t unknown1; std::uint8_t unknown2; std::uint8_t unknown3; }; #pragma pack(pop) } #endif // ESM4_EFFECT_H openmw-openmw-0.48.0/components/esm4/formid.cpp000066400000000000000000000044031445372753700214560ustar00rootroot00000000000000/* Copyright (C) 2016, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au */ #include "formid.hpp" #include #include #include #include // strtol #include // LONG_MIN, LONG_MAX for gcc #include namespace ESM4 { void formIdToString(FormId formId, std::string& str) { char buf[8+1]; int res = snprintf(buf, 8+1, "%08X", formId); if (res > 0 && res < 8+1) str.assign(buf); else throw std::runtime_error("Possible buffer overflow while converting formId"); } std::string formIdToString(FormId formId) { std::string str; formIdToString(formId, str); return str; } bool isFormId(const std::string& str, FormId *id) { if (str.size() != 8) return false; char *tmp; errno = 0; unsigned long val = strtol(str.c_str(), &tmp, 16); if (tmp == str.c_str() || *tmp != '\0' || ((val == (unsigned long)LONG_MIN || val == (unsigned long)LONG_MAX) && errno == ERANGE)) return false; if (id != nullptr) *id = static_cast(val); return true; } FormId stringToFormId(const std::string& str) { if (str.size() != 8) throw std::out_of_range("StringToFormId: incorrect string size"); return static_cast(std::stoul(str, nullptr, 16)); } } openmw-openmw-0.48.0/components/esm4/formid.hpp000066400000000000000000000024171445372753700214660ustar00rootroot00000000000000/* Copyright (C) 2016 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au */ #ifndef ESM4_FORMID_H #define ESM4_FORMID_H #include #include namespace ESM4 { typedef std::uint32_t FormId; void formIdToString(FormId formId, std::string& str); std::string formIdToString(FormId formId); bool isFormId(const std::string& str, FormId *id = nullptr); FormId stringToFormId(const std::string& str); } #endif // ESM4_FORMID_H openmw-openmw-0.48.0/components/esm4/inventory.hpp000066400000000000000000000031731445372753700222430ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_INVENTORY_H #define ESM4_INVENTORY_H #include #include "formid.hpp" namespace ESM4 { #pragma pack(push, 1) // LVLC, LVLI struct LVLO { std::int16_t level; std::uint16_t unknown; // sometimes missing FormId item; std::int16_t count; std::uint16_t unknown2; // sometimes missing }; struct InventoryItem // NPC_, CREA, CONT { FormId item; std::uint32_t count; }; #pragma pack(pop) } #endif // ESM4_INVENTORY_H openmw-openmw-0.48.0/components/esm4/lighting.hpp000066400000000000000000000055741445372753700220220ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LIGHTING_H #define ESM4_LIGHTING_H #include #include namespace ESM4 { #pragma pack(push, 1) // guesses only for TES4 struct Lighting { // | Aichan Prison values std::uint32_t ambient; // | 16 17 19 00 (RGBA) std::uint32_t directional; // | 00 00 00 00 (RGBA) std::uint32_t fogColor; // | 1D 1B 16 00 (RGBA) float fogNear; // Fog Near | 00 00 00 00 = 0.f float fogFar; // Fog Far | 00 80 3B 45 = 3000.f std::int32_t rotationXY; // rotation xy | 00 00 00 00 = 0 std::int32_t rotationZ; // rotation z | 00 00 00 00 = 0 float fogDirFade; // Fog dir fade | 00 00 80 3F = 1.f float fogClipDist; // Fog clip dist | 00 80 3B 45 = 3000.f float fogPower = std::numeric_limits::max(); }; struct Lighting_TES5 { std::uint32_t ambient; std::uint32_t directional; std::uint32_t fogColor; float fogNear; float fogFar; std::int32_t rotationXY; std::int32_t rotationZ; float fogDirFade; float fogClipDist; float fogPower; std::uint32_t unknown1; std::uint32_t unknown2; std::uint32_t unknown3; std::uint32_t unknown4; std::uint32_t unknown5; std::uint32_t unknown6; std::uint32_t unknown7; std::uint32_t unknown8; std::uint32_t fogColorFar; float fogMax; float LightFadeStart; float LightFadeEnd; std::uint32_t padding; }; #pragma pack(pop) } #endif // ESM4_LIGHTING_H openmw-openmw-0.48.0/components/esm4/loadachr.cpp000066400000000000000000000100051445372753700217460ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadachr.hpp" #include //#include #include "reader.hpp" //#include "writer.hpp" void ESM4::ActorCharacter::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?) while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; case ESM4::SUB_DATA: reader.get(mPlacement); break; case ESM4::SUB_XSCL: reader.get(mScale); break; case ESM4::SUB_XOWN: reader.get(mOwner); break; case ESM4::SUB_XESP: { reader.get(mEsp); reader.adjustFormId(mEsp.parent); break; } case ESM4::SUB_XRGD: // ragdoll case ESM4::SUB_XRGB: // ragdoll biped { //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } case ESM4::SUB_XHRS: // horse formId case ESM4::SUB_XMRC: // merchant container formId // TES5 case ESM4::SUB_XAPD: // activation parent case ESM4::SUB_XAPR: // active parent case ESM4::SUB_XEZN: // encounter zone case ESM4::SUB_XHOR: case ESM4::SUB_XLCM: // levelled creature case ESM4::SUB_XLCN: // location case ESM4::SUB_XLKR: // location route? case ESM4::SUB_XLRT: // location type // case ESM4::SUB_XPRD: case ESM4::SUB_XPPA: case ESM4::SUB_INAM: case ESM4::SUB_PDTO: // case ESM4::SUB_XIS2: case ESM4::SUB_XPCI: // formId case ESM4::SUB_XLOD: case ESM4::SUB_VMAD: case ESM4::SUB_XLRL: // Unofficial Skyrim Patch case ESM4::SUB_XRDS: // FO3 case ESM4::SUB_XIBS: // FO3 case ESM4::SUB_SCHR: // FO3 case ESM4::SUB_TNAM: // FO3 case ESM4::SUB_XATO: // FONV { //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::ActorCharacter::save(ESM4::Writer& writer) const //{ //} //void ESM4::ActorCharacter::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadachr.hpp000066400000000000000000000041171445372753700217620ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ACHR_H #define ESM4_ACHR_H #include #include "reference.hpp" // FormId, Placement, EnableParent namespace ESM4 { class Reader; class Writer; struct ActorCharacter { FormId mParent; // cell formId, from the loading sequence // NOTE: for exterior cells it will be the dummy cell FormId FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; FormId mBaseObj; Placement mPlacement; float mScale = 1.0f; FormId mOwner; FormId mGlobal; bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) EnableParent mEsp; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ACHR_H openmw-openmw-0.48.0/components/esm4/loadacre.cpp000066400000000000000000000066101445372753700217520ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadacre.hpp" #include //#include #include "reader.hpp" //#include "writer.hpp" void ESM4::ActorCreature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; case ESM4::SUB_DATA: reader.get(mPlacement); break; case ESM4::SUB_XSCL: reader.get(mScale); break; case ESM4::SUB_XESP: { reader.get(mEsp); reader.adjustFormId(mEsp.parent); break; } case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; case ESM4::SUB_XGLB: reader.get(mGlobal); break; // FIXME: formId? case ESM4::SUB_XRNK: reader.get(mFactionRank); break; case ESM4::SUB_XRGD: // ragdoll case ESM4::SUB_XRGB: // ragdoll biped { // seems to occur only for dead bodies, e.g. DeadMuffy, DeadDogVicious //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } case ESM4::SUB_XLKR: // FO3 case ESM4::SUB_XLCM: // FO3 case ESM4::SUB_XEZN: // FO3 case ESM4::SUB_XMRC: // FO3 case ESM4::SUB_XAPD: // FO3 case ESM4::SUB_XAPR: // FO3 case ESM4::SUB_XRDS: // FO3 case ESM4::SUB_XPRD: // FO3 case ESM4::SUB_XATO: // FONV { //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::ActorCreature::save(ESM4::Writer& writer) const //{ //} //void ESM4::ActorCreature::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadacre.hpp000066400000000000000000000036521445372753700217620ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ACRE_H #define ESM4_ACRE_H #include #include "reference.hpp" // FormId, Placement, EnableParent namespace ESM4 { class Reader; class Writer; struct ActorCreature { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; FormId mBaseObj; Placement mPlacement; float mScale = 1.0f; FormId mOwner; FormId mGlobal; std::uint32_t mFactionRank; bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) EnableParent mEsp; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ACRE_H openmw-openmw-0.48.0/components/esm4/loadacti.cpp000066400000000000000000000063661445372753700217700ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadacti.hpp" #include #include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::Activator::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_SNAM: reader.getFormId(mLoopingSound); break; case ESM4::SUB_VNAM: reader.getFormId(mActivationSound); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV case ESM4::SUB_RNAM: reader.getFormId(mRadioStation); break; case ESM4::SUB_XATO: reader.getZString(mActivationPrompt); break; // FONV case ESM4::SUB_MODT: case ESM4::SUB_MODS: case ESM4::SUB_DEST: case ESM4::SUB_DMDL: case ESM4::SUB_DMDS: case ESM4::SUB_DMDT: case ESM4::SUB_DSTD: case ESM4::SUB_DSTF: case ESM4::SUB_FNAM: case ESM4::SUB_KNAM: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_OBND: case ESM4::SUB_PNAM: case ESM4::SUB_VMAD: case ESM4::SUB_WNAM: { //std::cout << "ACTI " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ACTI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Activator::save(ESM4::Writer& writer) const //{ //} //void ESM4::Activator::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadacti.hpp000066400000000000000000000036361445372753700217720ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ACTI_H #define ESM4_ACTI_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Activator { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; FormId mScriptId; FormId mLoopingSound; // SOUN FormId mActivationSound; // SOUN float mBoundRadius; FormId mRadioTemplate; // SOUN FormId mRadioStation; // TACT std::string mActivationPrompt; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ACTI_H openmw-openmw-0.48.0/components/esm4/loadalch.cpp000066400000000000000000000071631445372753700217530ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadalch.hpp" #include #include //#include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::Potion::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_SCIT: { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } case ESM4::SUB_ENIT: { if (subHdr.dataSize == 8) // TES4 { reader.get(&mItem, 8); mItem.withdrawl = 0; mItem.sound = 0; break; } reader.get(mItem); reader.adjustFormId(mItem.withdrawl); reader.adjustFormId(mItem.sound); break; } case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_EFID: case ESM4::SUB_EFIT: case ESM4::SUB_CTDA: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_MODS: case ESM4::SUB_OBND: case ESM4::SUB_ETYP: // FO3 { //std::cout << "ALCH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ALCH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Potion::save(ESM4::Writer& writer) const //{ //} //void ESM4::Potion::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadalch.hpp000066400000000000000000000044021445372753700217510ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ALCH_H #define ESM4_ALCH_H #include #include #include "effect.hpp" // FormId, ScriptEffect namespace ESM4 { class Reader; class Writer; struct Potion { #pragma pack(push, 1) struct Data { float weight; }; struct EnchantedItem { std::int32_t value; std::uint32_t flags; FormId withdrawl; float chanceAddition; FormId sound; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory std::string mMiniIcon; // inventory FormId mPickUpSound; FormId mDropSound; FormId mScriptId; ScriptEffect mEffect; float mBoundRadius; Data mData; EnchantedItem mItem; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ALCH_H openmw-openmw-0.48.0/components/esm4/loadaloc.cpp000066400000000000000000000127271445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadaloc.hpp" #include #include //#include // FIXME: for debugging only //#include // FIXME: for debugging only //#include // FIXME //#include "formid.hpp" // FIXME: #include "reader.hpp" //#include "writer.hpp" void ESM4::MediaLocationController::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_GNAM: { FormId id; reader.getFormId(id); mBattleSets.push_back(id); break; } case ESM4::SUB_LNAM: { FormId id; reader.getFormId(id); mLocationSets.push_back(id); break; } case ESM4::SUB_YNAM: { FormId id; reader.getFormId(id); mEnemySets.push_back(id); break; } case ESM4::SUB_HNAM: { FormId id; reader.getFormId(id); mNeutralSets.push_back(id); break; } case ESM4::SUB_XNAM: { FormId id; reader.getFormId(id); mFriendSets.push_back(id); break; } case ESM4::SUB_ZNAM: { FormId id; reader.getFormId(id); mAllySets.push_back(id); break; } case ESM4::SUB_RNAM: reader.getFormId(mConditionalFaction); break; case ESM4::SUB_NAM1: { reader.get(mMediaFlags); std::uint8_t flags = mMediaFlags.loopingOptions; mMediaFlags.loopingOptions = (flags & 0xF0) >> 4; mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data break; } case ESM4::SUB_NAM4: reader.get(mLocationDelay); break; case ESM4::SUB_NAM7: reader.get(mRetriggerDelay); break; case ESM4::SUB_NAM5: reader.get(mDayStart); break; case ESM4::SUB_NAM6: reader.get(mNightStart); break; case ESM4::SUB_NAM2: // always 0? 4 bytes case ESM4::SUB_NAM3: // always 0? 4 bytes case ESM4::SUB_FNAM: // always 0? 4 bytes { #if 0 boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); reader.get(&mDataBuf[0], subHdr.dataSize); std::ostringstream ss; ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; for (std::size_t i = 0; i < subHdr.dataSize; ++i) { //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char //ss << (char)(mDataBuf[i]) << " "; //else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); if ((i & 0x000f) == 0xf) // wrap around ss << "\n"; else if (i < subHdr.dataSize-1) ss << " "; } std::cout << ss.str() << std::endl; #else //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); #endif break; } default: //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); throw std::runtime_error("ESM4::ALOC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::MediaLocationController::save(ESM4::Writer& writer) const //{ //} //void ESM4::MediaLocationController::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadaloc.hpp000066400000000000000000000050661445372753700217670ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ALOC_H #define ESM4_ALOC_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; #pragma pack(push, 1) struct MLC_Flags { // use day/night transition: 0 = loop, 1 = random, 2 = retrigger, 3 = none // use defaults (6:00/23:54): 4 = loop, 5 = random, 6 = retrigger, 7 = none std::uint8_t loopingOptions; // 0 = neutral, 1 = enemy, 2 = ally, 3 = friend, 4 = location, 5 = none std::uint8_t factionNotFound; // WARN: overwriting whatever is in this std::uint16_t unknown; // padding? }; #pragma pack(pop) struct MediaLocationController { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::vector mBattleSets; std::vector mLocationSets; std::vector mEnemySets; std::vector mNeutralSets; std::vector mFriendSets; std::vector mAllySets; MLC_Flags mMediaFlags; FormId mConditionalFaction; float mLocationDelay; float mRetriggerDelay; std::uint32_t mDayStart; std::uint32_t mNightStart; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ALOC_H openmw-openmw-0.48.0/components/esm4/loadammo.cpp000066400000000000000000000111321445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadammo.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Ammunition::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 16) // FO3 has 13 bytes even though VER_094 { FormId projectile; reader.get(projectile); // FIXME: add to mData reader.get(mData.flags); reader.get(mData.weight); float damageInFloat; reader.get(damageInFloat); // FIXME: add to mData } else if (isFONV || subHdr.dataSize == 13) { reader.get(mData.speed); std::uint8_t flags; reader.get(flags); mData.flags = flags; static std::uint8_t dummy; reader.get(dummy); reader.get(dummy); reader.get(dummy); reader.get(mData.value); reader.get(mData.clipRounds); } else // TES4 { reader.get(mData.speed); reader.get(mData.flags); reader.get(mData.value); reader.get(mData.weight); reader.get(mData.damage); } break; } case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_OBND: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_ONAM: // FO3 case ESM4::SUB_DAT2: // FONV case ESM4::SUB_QNAM: // FONV case ESM4::SUB_RCIL: // FONV case ESM4::SUB_SCRI: // FONV { //std::cout << "AMMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::AMMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Ammunition::save(ESM4::Writer& writer) const //{ //} //void ESM4::Ammunition::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadammo.hpp000066400000000000000000000045351445372753700220020ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_AMMO_H #define ESM4_AMMO_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Ammunition { struct Data // FIXME: TES5 projectile, damage (float) { float speed; std::uint32_t flags; std::uint32_t value; // gold float weight; std::uint16_t damage; std::uint8_t clipRounds; // only in FO3/FONV Data() : speed(0.f), flags(0), value(0), weight(0.f), damage(0), clipRounds(0) {} }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mText; std::string mIcon; // inventory std::string mMiniIcon; // inventory FormId mPickUpSound; FormId mDropSound; float mBoundRadius; std::uint16_t mEnchantmentPoints; FormId mEnchantment; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_AMMO_H openmw-openmw-0.48.0/components/esm4/loadanio.cpp000066400000000000000000000046271445372753700217740ustar00rootroot00000000000000/* Copyright (C) 2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadanio.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::AnimObject::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_BNAM: reader.getZString(mUnloadEvent); break; case ESM4::SUB_DATA: reader.getFormId(mIdleAnim); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: // TES5 only case ESM4::SUB_MODS: // TES5 only { //std::cout << "ANIO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ANIO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::AnimObject::save(ESM4::Writer& writer) const //{ //} //void ESM4::AnimObject::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadanio.hpp000066400000000000000000000034011445372753700217660ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ANIO_H #define ESM4_ANIO_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct AnimObject { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; float mBoundRadius; FormId mIdleAnim; // only in TES4 std::string mUnloadEvent; // only in TES5 void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ANIO_H openmw-openmw-0.48.0/components/esm4/loadappa.cpp000066400000000000000000000061151445372753700217610ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadappa.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Apparatus::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) { reader.get(mData.value); reader.get(mData.weight); } else { reader.get(mData.type); reader.get(mData.value); reader.get(mData.weight); reader.get(mData.quality); } break; } case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_MODT: case ESM4::SUB_OBND: case ESM4::SUB_QUAL: { //std::cout << "APPA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::APPA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Apparatus::save(ESM4::Writer& writer) const //{ //} //void ESM4::Apparatus::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadappa.hpp000066400000000000000000000040471445372753700217700ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_APPA_H #define ESM4_APPA_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Apparatus { struct Data { std::uint8_t type; // 0 = Mortar and Pestle, 1 = Alembic, 2 = Calcinator, 3 = Retort std::uint32_t value; // gold float weight; float quality; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mText; std::string mIcon; // inventory float mBoundRadius; FormId mScriptId; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_APPA_H openmw-openmw-0.48.0/components/esm4/loadarma.cpp000066400000000000000000000124571445372753700217660ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadarma.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::ArmorAddon::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MOD2: reader.getZString(mModelMale); break; case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; case ESM4::SUB_MOD4: case ESM4::SUB_MOD5: { std::string model; reader.getZString(model); //std::cout << mEditorId << " " << ESM::printName(subHdr.typeId) << " " << model << std::endl; break; } case ESM4::SUB_NAM0: reader.getFormId(mTextureMale); break; case ESM4::SUB_NAM1: reader.getFormId(mTextureFemale); break; case ESM4::SUB_RNAM: reader.getFormId(mRacePrimary); break; case ESM4::SUB_MODL: { if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 { FormId formId; reader.getFormId(formId); mRaces.push_back(formId); } else reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV break; } case ESM4::SUB_BODT: // body template { reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); reader.get(mBodyTemplate.unknown1); // probably padding reader.get(mBodyTemplate.unknown2); // probably padding reader.get(mBodyTemplate.unknown3); // probably padding reader.get(mBodyTemplate.type); break; } case ESM4::SUB_BOD2: // TES5 { reader.get(mBodyTemplate.bodyPart); mBodyTemplate.flags = 0; mBodyTemplate.unknown1 = 0; // probably padding mBodyTemplate.unknown2 = 0; // probably padding mBodyTemplate.unknown3 = 0; // probably padding reader.get(mBodyTemplate.type); break; } case ESM4::SUB_DNAM: case ESM4::SUB_MO2T: // FIXME: should group with MOD2 case ESM4::SUB_MO2S: // FIXME: should group with MOD2 case ESM4::SUB_MO3T: // FIXME: should group with MOD3 case ESM4::SUB_MO3S: // FIXME: should group with MOD3 case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3 case ESM4::SUB_MO4T: // FIXME: should group with MOD4 case ESM4::SUB_MO4S: // FIXME: should group with MOD4 case ESM4::SUB_MO5T: case ESM4::SUB_NAM2: // txst formid male case ESM4::SUB_NAM3: // txst formid female case ESM4::SUB_SNDD: // footset sound formid case ESM4::SUB_BMDT: // FO3 case ESM4::SUB_DATA: // FO3 case ESM4::SUB_ETYP: // FO3 case ESM4::SUB_FULL: // FO3 case ESM4::SUB_ICO2: // FO3 // female case ESM4::SUB_ICON: // FO3 // male case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL case ESM4::SUB_OBND: // FO3 { //std::cout << "ARMA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ARMA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::ArmorAddon::save(ESM4::Writer& writer) const //{ //} //void ESM4::ArmorAddon::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadarma.hpp000066400000000000000000000036201445372753700217630ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ARMA_H #define ESM4_ARMA_H #include #include #include #include "formid.hpp" #include "actor.hpp" // BodyTemplate namespace ESM4 { class Reader; class Writer; struct ArmorAddon { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModelMale; std::string mModelFemale; FormId mTextureMale; FormId mTextureFemale; FormId mRacePrimary; std::vector mRaces; // TES5 only BodyTemplate mBodyTemplate; // TES5 void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ARMA_H openmw-openmw-0.48.0/components/esm4/loadarmo.cpp000066400000000000000000000163631445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadarmo.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::Armor::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 8) // FO3 has 12 bytes even though VER_094 { reader.get(mData.value); reader.get(mData.weight); mIsFO3 = true; } else if (mIsFONV || subHdr.dataSize == 12) { reader.get(mData.value); reader.get(mData.health); reader.get(mData.weight); } else { reader.get(mData); // TES4 mIsTES4 = true; } break; } case ESM4::SUB_MODL: // seems only for Dawnguard/Dragonborn? { //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) if (subHdr.dataSize == 4) // FO3 has zstring even though VER_094 { FormId formId; reader.getFormId(formId); mAddOns.push_back(formId); } else { if (!reader.getZString(mModelMale)) throw std::runtime_error ("ARMO MODL data read error"); } break; } case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld);break; case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; case ESM4::SUB_ICON: reader.getZString(mIconMale); break; case ESM4::SUB_MICO: reader.getZString(mMiniIconMale); break; case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; case ESM4::SUB_MIC2: reader.getZString(mMiniIconFemale); break; case ESM4::SUB_BMDT: { if (subHdr.dataSize == 8) // FO3 { reader.get(mArmorFlags); reader.get(mGeneralFlags); mGeneralFlags &= 0x000000ff; mGeneralFlags |= TYPE_FO3; } else // TES4 { reader.get(mArmorFlags); mGeneralFlags = (mArmorFlags & 0x00ff0000) >> 16; mGeneralFlags |= TYPE_TES4; } break; } case ESM4::SUB_BODT: { reader.get(mArmorFlags); uint32_t flags = 0; if (subHdr.dataSize == 12) reader.get(flags); reader.get(mGeneralFlags); // skill mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) if (subHdr.dataSize == 12) mGeneralFlags |= (flags & 0x0000000f) << 3; mGeneralFlags |= TYPE_TES5; break; } case ESM4::SUB_BOD2: { reader.get(mArmorFlags); reader.get(mGeneralFlags); mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) mGeneralFlags |= TYPE_TES5; break; } case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_MO2B: case ESM4::SUB_MO3B: case ESM4::SUB_MO4B: case ESM4::SUB_MO2T: case ESM4::SUB_MO2S: case ESM4::SUB_MO3T: case ESM4::SUB_MO4T: case ESM4::SUB_MO4S: case ESM4::SUB_OBND: case ESM4::SUB_RNAM: // race formid case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_TNAM: case ESM4::SUB_DNAM: case ESM4::SUB_BAMT: case ESM4::SUB_BIDS: case ESM4::SUB_ETYP: case ESM4::SUB_BMCT: case ESM4::SUB_EAMT: case ESM4::SUB_EITM: case ESM4::SUB_VMAD: case ESM4::SUB_REPL: // FO3 case ESM4::SUB_BIPL: // FO3 case ESM4::SUB_MODD: // FO3 case ESM4::SUB_MOSD: // FO3 case ESM4::SUB_MODS: // FO3 case ESM4::SUB_MO3S: // FO3 case ESM4::SUB_BNAM: // FONV case ESM4::SUB_SNAM: // FONV { //std::cout << "ARMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ARMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //if ((mArmorFlags&0xffff) == 0x02) // only hair //std::cout << "only hair " << mEditorId << std::endl; } //void ESM4::Armor::save(ESM4::Writer& writer) const //{ //} //void ESM4::Armor::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadarmo.hpp000066400000000000000000000146421445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ARMO_H #define ESM4_ARMO_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Armor { // WARN: TES4 Armorflags still has the general flags high bits enum ArmorFlags { TES4_Head = 0x00000001, TES4_Hair = 0x00000002, TES4_UpperBody = 0x00000004, TES4_LowerBody = 0x00000008, TES4_Hands = 0x00000010, TES4_Feet = 0x00000020, TES4_RightRing = 0x00000040, TES4_LeftRing = 0x00000080, TES4_Amulet = 0x00000100, TES4_Weapon = 0x00000200, TES4_BackWeapon = 0x00000400, TES4_SideWeapon = 0x00000800, TES4_Quiver = 0x00001000, TES4_Shield = 0x00002000, TES4_Torch = 0x00004000, TES4_Tail = 0x00008000, // FO3_Head = 0x00000001, FO3_Hair = 0x00000002, FO3_UpperBody = 0x00000004, FO3_LeftHand = 0x00000008, FO3_RightHand = 0x00000010, FO3_Weapon = 0x00000020, FO3_PipBoy = 0x00000040, FO3_Backpack = 0x00000080, FO3_Necklace = 0x00000100, FO3_Headband = 0x00000200, FO3_Hat = 0x00000400, FO3_EyeGlasses = 0x00000800, FO3_NoseRing = 0x00001000, FO3_Earrings = 0x00002000, FO3_Mask = 0x00004000, FO3_Choker = 0x00008000, FO3_MouthObject = 0x00010000, FO3_BodyAddOn1 = 0x00020000, FO3_BodyAddOn2 = 0x00040000, FO3_BodyAddOn3 = 0x00080000, // TES5_Head = 0x00000001, TES5_Hair = 0x00000002, TES5_Body = 0x00000004, TES5_Hands = 0x00000008, TES5_Forearms = 0x00000010, TES5_Amulet = 0x00000020, TES5_Ring = 0x00000040, TES5_Feet = 0x00000080, TES5_Calves = 0x00000100, TES5_Shield = 0x00000200, TES5_Tail = 0x00000400, TES5_LongHair = 0x00000800, TES5_Circlet = 0x00001000, TES5_Ears = 0x00002000, TES5_BodyAddOn3 = 0x00004000, TES5_BodyAddOn4 = 0x00008000, TES5_BodyAddOn5 = 0x00010000, TES5_BodyAddOn6 = 0x00020000, TES5_BodyAddOn7 = 0x00040000, TES5_BodyAddOn8 = 0x00080000, TES5_DecapHead = 0x00100000, TES5_Decapitate = 0x00200000, TES5_BodyAddOn9 = 0x00400000, TES5_BodyAddOn10 = 0x00800000, TES5_BodyAddOn11 = 0x01000000, TES5_BodyAddOn12 = 0x02000000, TES5_BodyAddOn13 = 0x04000000, TES5_BodyAddOn14 = 0x08000000, TES5_BodyAddOn15 = 0x10000000, TES5_BodyAddOn16 = 0x20000000, TES5_BodyAddOn17 = 0x40000000, TES5_FX01 = 0x80000000 }; enum GeneralFlags { TYPE_TES4 = 0x1000, TYPE_FO3 = 0x2000, TYPE_TES5 = 0x3000, TYPE_FONV = 0x4000, // TES4_HideRings = 0x0001, TES4_HideAmulet = 0x0002, TES4_NonPlayable = 0x0040, TES4_HeavyArmor = 0x0080, // FO3_PowerArmor = 0x0020, FO3_NonPlayable = 0x0040, FO3_HeavyArmor = 0x0080, // TES5_LightArmor = 0x0000, TES5_HeavyArmor = 0x0001, TES5_None = 0x0002, TES5_ModVoice = 0x0004, // note bit shift TES5_NonPlayable = 0x0040 // note bit shift }; #pragma pack(push, 1) struct Data { std::uint16_t armor; // only in TES4? std::uint32_t value; std::uint32_t health; // not in TES5? float weight; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details bool mIsTES4; // TODO: check that these match the general flags bool mIsFO3; bool mIsFONV; std::string mEditorId; std::string mFullName; std::string mModelMale; std::string mModelMaleWorld; std::string mModelFemale; std::string mModelFemaleWorld; std::string mText; std::string mIconMale; std::string mMiniIconMale; std::string mIconFemale; std::string mMiniIconFemale; FormId mPickUpSound; FormId mDropSound; std::string mModel; // FIXME: for OpenCS float mBoundRadius; std::uint32_t mArmorFlags; std::uint32_t mGeneralFlags; FormId mScriptId; std::uint16_t mEnchantmentPoints; FormId mEnchantment; std::vector mAddOns; // TES5 ARMA Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ARMO_H openmw-openmw-0.48.0/components/esm4/loadaspc.cpp000066400000000000000000000056471445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadaspc.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::AcousticSpace::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_ANAM: reader.get(mEnvironmentType); break; case ESM4::SUB_SNAM: { FormId id; reader.getFormId(id); mAmbientLoopSounds.push_back(id); break; } case ESM4::SUB_RDAT: reader.getFormId(mSoundRegion); break; case ESM4::SUB_INAM: reader.get(mIsInterior); break; case ESM4::SUB_WNAM: // usually 0 for FONV (maybe # of close Actors for Walla to trigger) { std::uint32_t dummy; reader.get(dummy); //std::cout << "WNAM " << mEditorId << " " << dummy << std::endl; break; } case ESM4::SUB_BNAM: // TES5 reverb formid case ESM4::SUB_OBND: { //std::cout << "ASPC " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::ASPC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::AcousticSpace::save(ESM4::Writer& writer) const //{ //} //void ESM4::AcousticSpace::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadaspc.hpp000066400000000000000000000036051445372753700217740ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ASPC_H #define ESM4_ASPC_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct AcousticSpace { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::uint32_t mEnvironmentType; // 0 Dawn (5:00 start), 1 Afternoon (8:00), 2 Dusk (18:00), 3 Night (20:00) std::vector mAmbientLoopSounds; FormId mSoundRegion; std::uint32_t mIsInterior; // if true only use mAmbientLoopSounds[0] void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ASPC_H openmw-openmw-0.48.0/components/esm4/loadbook.cpp000066400000000000000000000074241445372753700217760ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadbook.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Book::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; //std::uint32_t esmVer = reader.esmVersion(); // currently unused while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_DATA: { reader.get(mData.flags); //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 16) // FO3 has 10 bytes even though VER_094 { static std::uint8_t dummy; reader.get(mData.type); reader.get(dummy); reader.get(dummy); reader.get(mData.teaches); } else { reader.get(mData.bookSkill); } reader.get(mData.value); reader.get(mData.weight); break; } case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; // TODO: does this exist? case ESM4::SUB_MODT: case ESM4::SUB_OBND: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_CNAM: case ESM4::SUB_INAM: case ESM4::SUB_VMAD: { //std::cout << "BOOK " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::BOOK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Book::save(ESM4::Writer& writer) const //{ //} //void ESM4::Book::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadbook.hpp000066400000000000000000000063361445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_BOOK_H #define ESM4_BOOK_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Book { enum Flags { Flag_Scroll = 0x0001, Flag_NoTake = 0x0002 }; enum BookSkill // for TES4 only { BookSkill_None = -1, BookSkill_Armorer = 0, BookSkill_Athletics = 1, BookSkill_Blade = 2, BookSkill_Block = 3, BookSkill_Blunt = 4, BookSkill_HandToHand = 5, BookSkill_HeavyArmor = 6, BookSkill_Alchemy = 7, BookSkill_Alteration = 8, BookSkill_Conjuration = 9, BookSkill_Destruction = 10, BookSkill_Illusion = 11, BookSkill_Mysticism = 12, BookSkill_Restoration = 13, BookSkill_Acrobatics = 14, BookSkill_LightArmor = 15, BookSkill_Marksman = 16, BookSkill_Mercantile = 17, BookSkill_Security = 18, BookSkill_Sneak = 19, BookSkill_Speechcraft = 20 }; struct Data { std::uint8_t flags; std::uint8_t type; // TES5 only std::uint32_t teaches; // TES5 only std::int8_t bookSkill; // not in TES5 std::uint32_t value; float weight; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; float mBoundRadius; std::string mText; FormId mScriptId; std::string mIcon; std::uint16_t mEnchantmentPoints; FormId mEnchantment; Data mData; FormId mPickUpSound; FormId mDropSound; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_BOOK_H openmw-openmw-0.48.0/components/esm4/loadbptd.cpp000066400000000000000000000070761445372753700220000ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadbptd.hpp" #include #include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::BodyPartData::BodyPart::clear() { mPartName.clear(); mNodeName.clear(); mVATSTarget.clear(); mIKStartNode.clear(); std::memset(&mData, 0, sizeof(BPND)); mLimbReplacementModel.clear(); mGoreEffectsTarget.clear(); } void ESM4::BodyPartData::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; BodyPart bodyPart; bodyPart.clear(); while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_BPTN: reader.getLocalizedString(bodyPart.mPartName); break; case ESM4::SUB_BPNN: reader.getZString(bodyPart.mNodeName); break; case ESM4::SUB_BPNT: reader.getZString(bodyPart.mVATSTarget); break; case ESM4::SUB_BPNI: reader.getZString(bodyPart.mIKStartNode); break; case ESM4::SUB_BPND: reader.get(bodyPart.mData); break; case ESM4::SUB_NAM1: reader.getZString(bodyPart.mLimbReplacementModel); break; case ESM4::SUB_NAM4: // FIXME: assumed occurs last { reader.getZString(bodyPart.mGoreEffectsTarget); // target bone mBodyParts.push_back(bodyPart); // FIXME: possible without copying? bodyPart.clear(); break; } case ESM4::SUB_NAM5: case ESM4::SUB_RAGA: // ragdoll case ESM4::SUB_MODS: case ESM4::SUB_MODT: { //std::cout << "BPTD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::BPTD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //if (mEditorId == "DefaultBodyPartData") //std::cout << "BPTD: " << mEditorId << std::endl; // FIXME: testing only } //void ESM4::BodyPartData::save(ESM4::Writer& writer) const //{ //} //void ESM4::BodyPartData::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadbptd.hpp000066400000000000000000000066501445372753700220020ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_BPTD_H #define ESM4_BPTD_H #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct BodyPartData { #pragma pack(push, 1) struct BPND { float damageMult; // Severable // IK Data // IK Data - Biped Data // Explodable // IK Data - Is Head // IK Data - Headtracking // To Hit Chance - Absolute std::uint8_t flags; // Torso // Head // Eye // LookAt // Fly Grab // Saddle std::uint8_t partType; std::uint8_t healthPercent; std::int8_t actorValue; //(Actor Values) std::uint8_t toHitChance; std::uint8_t explExplosionChance; // % std::uint16_t explDebrisCount; FormId explDebris; FormId explExplosion; float trackingMaxAngle; float explDebrisScale; std::int32_t sevDebrisCount; FormId sevDebris; FormId sevExplosion; float sevDebrisScale; //Struct - Gore Effects Positioning float transX; float transY; float transZ; float rotX; float rotY; float rotZ; FormId sevImpactDataSet; FormId explImpactDataSet; uint8_t sevDecalCount; uint8_t explDecalCount; uint16_t Unknown; float limbReplacementScale; }; #pragma pack(pop) struct BodyPart { std::string mPartName; std::string mNodeName; std::string mVATSTarget; std::string mIKStartNode; BPND mData; std::string mLimbReplacementModel; std::string mGoreEffectsTarget; void clear(); }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::vector mBodyParts; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_BPTD_H openmw-openmw-0.48.0/components/esm4/loadcell.cpp000066400000000000000000000222261445372753700217600ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadcell.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include #include // FLT_MAX for gcc #include // FIXME: debug only #include "reader.hpp" //#include "writer.hpp" // TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) // // But, for external cells we may be scanning the whole record since we don't know if there is // going to be an EDID subrecord. And the vast majority of cells are these kinds. // // So perhaps some testing needs to be done to see if scanning and skipping takes // longer/shorter/same as loading the subrecords. void ESM4::Cell::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mParent = reader.currWorld(); reader.clearCellGrid(); // clear until XCLC FIXME: somehow do this automatically? // Sometimes cell 0,0 does not have an XCLC sub record (e.g. ToddLand 000009BF) // To workaround this issue put a default value if group is "exterior sub cell" and its // grid from label is "0 0". Note the reversed X/Y order (no matter since they're both 0 // anyway). if (reader.grp().type == ESM4::Grp_ExteriorSubCell && reader.grp().label.grid[1] == 0 && reader.grp().label.grid[0] == 0) { ESM4::CellGrid currCellGrid; currCellGrid.grid.x = 0; currCellGrid.grid.y = 0; reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid true } // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading // cell child groups if we are loading them after restoring the context // (may be easier to update the context before saving?) reader.setCurrCell(mFormId); // save for LAND (and other children) to access later std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: { if (!reader.getZString(mEditorId)) throw std::runtime_error ("CELL EDID data read error"); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "CELL Editor ID: " << mEditorId << std::endl; #endif break; } case ESM4::SUB_XCLC: { //(X, Y) grid location of the cell followed by flags. Always in //exterior cells and never in interior cells. // // int32 - X // int32 - Y // uint32 - flags (high bits look random) // // 0x1 - Force Hide Land Quad 1 // 0x2 - Force Hide Land Quad 2 // 0x4 - Force Hide Land Quad 3 // 0x8 - Force Hide Land Quad 4 uint32_t flags; reader.get(mX); reader.get(mY); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl; std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl; std::cout << padding << "CELL X " << std::dec << mX << ", Y " << mY << std::endl; #endif if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) if (subHdr.dataSize == 12) reader.get(flags); // not in Obvlivion, nor FO3/FONV // Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children) // Note that grids only apply for external cells. For interior cells use the cell's formid. ESM4::CellGrid currCell; currCell.grid.x = (int16_t)mX; currCell.grid.y = (int16_t)mY; reader.setCurrCellGrid(currCell); break; } case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) if (subHdr.dataSize == 2) reader.get(mCellFlags); else { assert(subHdr.dataSize == 1 && "CELL unexpected DATA flag size"); reader.get(&mCellFlags, sizeof(std::uint8_t)); } else { reader.get((std::uint8_t&)mCellFlags); // 8 bits in Obvlivion } #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl; #endif break; } case ESM4::SUB_XCLR: // for exterior cells { mRegions.resize(subHdr.dataSize/sizeof(FormId)); for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) { reader.getFormId(*it); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "region: " << std::hex << *it << std::endl; #endif } break; } case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; // Oblivion only? case ESM4::SUB_XCCM: reader.getFormId(mClimate); break; case ESM4::SUB_XCWT: reader.getFormId(mWater); break; case ESM4::SUB_XCLW: reader.get(mWaterHeight); break; case ESM4::SUB_XCLL: { if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) { if (subHdr.dataSize == 40) // FO3/FONV reader.get(mLighting); else if (subHdr.dataSize == 92) // TES5 { reader.get(mLighting); reader.skipSubRecordData(52); // FIXME } else reader.skipSubRecordData(); } else reader.get(&mLighting, 36); // TES4 break; } case ESM4::SUB_XCMT: reader.get(mMusicType); break; // Oblivion only? case ESM4::SUB_LTMP: reader.getFormId(mLightingTemplate); break; case ESM4::SUB_LNAM: reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP case ESM4::SUB_XCMO: reader.getFormId(mMusic); break; case ESM4::SUB_XCAS: reader.getFormId(mAcousticSpace); break; case ESM4::SUB_TVDT: case ESM4::SUB_MHDT: case ESM4::SUB_XCGD: case ESM4::SUB_XNAM: case ESM4::SUB_XLCN: case ESM4::SUB_XWCS: case ESM4::SUB_XWCU: case ESM4::SUB_XWCN: case ESM4::SUB_XCIM: case ESM4::SUB_XEZN: case ESM4::SUB_XWEM: case ESM4::SUB_XILL: case ESM4::SUB_XRNK: // Oblivion only? case ESM4::SUB_XCET: // FO3 case ESM4::SUB_IMPF: // FO3 Zeta { //std::cout << "CELL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::CELL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Cell::save(ESM4::Writer& writer) const //{ //} void ESM4::Cell::blank() { } openmw-openmw-0.48.0/components/esm4/loadcell.hpp000066400000000000000000000067521445372753700217730ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CELL_H #define ESM4_CELL_H #include #include #include #include "formid.hpp" #include "lighting.hpp" namespace ESM4 { class Reader; class Writer; struct ReaderContext; struct CellGroup; typedef std::uint32_t FormId; enum CellFlags // TES4 TES5 { // ----------------------- ------------------------------------ CELL_Interior = 0x0001, // Can't travel from here Interior CELL_HasWater = 0x0002, // Has water (Int) Has Water (Int) CELL_NoTravel = 0x0004, // not Can't Travel From Here(Int only) CELL_HideLand = 0x0008, // Force hide land (Ext) No LOD Water // Oblivion interior (Int) CELL_Public = 0x0020, // Public place Public Area CELL_HandChgd = 0x0040, // Hand changed Hand Changed CELL_QuasiExt = 0x0080, // Behave like exterior Show Sky CELL_SkyLight = 0x0100 // Use Sky Lighting }; // Unlike TES3, multiple cells can have the same exterior co-ordinates. // The cells need to be organised under world spaces. struct Cell { FormId mParent; // world formId (for grouping cells), from the loading sequence FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::uint16_t mCellFlags; // TES5 can also be 8 bits std::int32_t mX; std::int32_t mY; FormId mOwner; FormId mGlobal; FormId mClimate; FormId mWater; float mWaterHeight; std::vector mRegions; Lighting mLighting; FormId mLightingTemplate; // FO3/FONV std::uint32_t mLightingTemplateFlags; // FO3/FONV FormId mMusic; // FO3/FONV FormId mAcousticSpace; // FO3/FONV // TES4: 0 = default, 1 = public, 2 = dungeon // FO3/FONV have more types (not sure how they are used, however) std::uint8_t mMusicType; CellGroup *mCellGroup; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; void blank(); }; } #endif // ESM4_CELL_H openmw-openmw-0.48.0/components/esm4/loadclas.cpp000066400000000000000000000046501445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadclas.hpp" //#ifdef NDEBUG // FIXME: debugging only //#undef NDEBUG //#endif //#include #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Class::load(ESM4::Reader& reader) { //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? mFormId = reader.hdr().record.id; mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DESC: reader.getLocalizedString(mDesc); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_DATA: { //std::cout << "CLAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::CLAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Class::save(ESM4::Writer& writer) const //{ //} //void ESM4::Class::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadclas.hpp000066400000000000000000000034211445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CLAS_H #define ESM4_CLAS_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Class { struct Data { std::uint32_t attr; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mDesc; std::string mIcon; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& reader) const; //void blank(); }; } #endif // ESM4_CLAS_H openmw-openmw-0.48.0/components/esm4/loadclfm.cpp000066400000000000000000000050141445372753700217560ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadclfm.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Colour::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_CNAM: { reader.get(mColour.red); reader.get(mColour.green); reader.get(mColour.blue); reader.get(mColour.custom); break; } case ESM4::SUB_FNAM: { reader.get(mPlayable); break; } default: //std::cout << "CLFM " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); throw std::runtime_error("ESM4::CLFM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Colour::save(ESM4::Writer& writer) const //{ //} //void ESM4::Colour::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadclfm.hpp000066400000000000000000000035531445372753700217710ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CLFM_H #define ESM4_CLFM_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; // FIXME: duplicate with Npc struct ColourRGB { std::uint8_t red; std::uint8_t green; std::uint8_t blue; std::uint8_t custom; // alpha? }; struct Colour { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; ColourRGB mColour; std::uint32_t mPlayable; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_CLFM_H openmw-openmw-0.48.0/components/esm4/loadclot.cpp000066400000000000000000000065331445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadclot.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Clothing::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_BMDT: reader.get(mClothingFlags); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODL: reader.getZString(mModelMale); break; case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld); break; case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; case ESM4::SUB_ICON: reader.getZString(mIconMale); break; case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; case ESM4::SUB_MODT: case ESM4::SUB_MO2B: case ESM4::SUB_MO3B: case ESM4::SUB_MO4B: case ESM4::SUB_MO2T: case ESM4::SUB_MO3T: case ESM4::SUB_MO4T: { //std::cout << "CLOT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::CLOT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //if ((mClothingFlags&0xffff) == 0x02) // only hair //std::cout << "only hair " << mEditorId << std::endl; } //void ESM4::Clothing::save(ESM4::Writer& writer) const //{ //} //void ESM4::Clothing::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadclot.hpp000066400000000000000000000044461445372753700220130ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CLOT_H #define ESM4_CLOT_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Clothing { #pragma pack(push, 1) struct Data { std::uint32_t value; // gold float weight; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModelMale; std::string mModelMaleWorld; std::string mModelFemale; std::string mModelFemaleWorld; std::string mIconMale; // texture std::string mIconFemale; // texture std::string mModel; // FIXME: for OpenCS float mBoundRadius; std::uint32_t mClothingFlags; // see Armor::ArmorFlags for the values FormId mScriptId; std::uint16_t mEnchantmentPoints; FormId mEnchantment; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_CLOT_H openmw-openmw-0.48.0/components/esm4/loadcont.cpp000066400000000000000000000066141445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadcont.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Container::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { reader.get(mDataFlags); reader.get(mWeight); break; } case ESM4::SUB_CNTO: { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; case ESM4::SUB_QNAM: reader.getFormId(mCloseSound); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_MODS: // TES5 only case ESM4::SUB_VMAD: // TES5 only case ESM4::SUB_OBND: // TES5 only case ESM4::SUB_COCT: // TES5 only case ESM4::SUB_COED: // TES5 only case ESM4::SUB_DEST: // FONV case ESM4::SUB_DSTD: // FONV case ESM4::SUB_DSTF: // FONV case ESM4::SUB_DMDL: // FONV case ESM4::SUB_DMDT: // FONV case ESM4::SUB_RNAM: // FONV { //std::cout << "CONT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::CONT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Container::save(ESM4::Writer& writer) const //{ //} //void ESM4::Container::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadcont.hpp000066400000000000000000000036761445372753700220210ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CONT_H #define ESM4_CONT_H #include #include #include #include "formid.hpp" #include "inventory.hpp" // InventoryItem namespace ESM4 { class Reader; class Writer; struct Container { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; float mBoundRadius; unsigned char mDataFlags; float mWeight; FormId mOpenSound; FormId mCloseSound; FormId mScriptId; // TES4 only std::vector mInventory; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_CONT_H openmw-openmw-0.48.0/components/esm4/loadcrea.cpp000066400000000000000000000161071445372753700217540ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadcrea.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include #include #include #include #include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::Creature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_CNTO: { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } case ESM4::SUB_SPLO: { FormId id; reader.getFormId(id); mSpell.push_back(id); break; } case ESM4::SUB_PKID: { FormId id; reader.getFormId(id); mAIPackages.push_back(id); break; } case ESM4::SUB_SNAM: { reader.get(mFaction); reader.adjustFormId(mFaction.faction); break; } case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_AIDT: { if (subHdr.dataSize == 20) // FO3 reader.skipSubRecordData(); else reader.get(mAIData); // 12 bytes break; } case ESM4::SUB_ACBS: { //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) if (subHdr.dataSize == 24) reader.get(mBaseConfig); else reader.get(&mBaseConfig, 16); // TES4 break; } case ESM4::SUB_DATA: { if (subHdr.dataSize == 17) // FO3 reader.skipSubRecordData(); else reader.get(mData); break; } case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; case ESM4::SUB_CSDI: reader.getFormId(mSound); break; case ESM4::SUB_CSDC: reader.get(mSoundChance); break; case ESM4::SUB_BNAM: reader.get(mBaseScale); break; case ESM4::SUB_TNAM: reader.get(mTurningSpeed); break; case ESM4::SUB_WNAM: reader.get(mFootWeight); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_NAM0: reader.getZString(mBloodSpray); break; case ESM4::SUB_NAM1: reader.getZString(mBloodDecal); break; case ESM4::SUB_NIFZ: { std::string str; if (!reader.getZString(str)) throw std::runtime_error ("CREA NIFZ data read error"); std::stringstream ss(str); std::string file; while (std::getline(ss, file, '\0')) // split the strings mNif.push_back(file); break; } case ESM4::SUB_NIFT: { if (subHdr.dataSize != 4) // FIXME: FO3 { reader.skipSubRecordData(); break; } assert(subHdr.dataSize == 4 && "CREA NIFT datasize error"); std::uint32_t nift; reader.get(nift); if (nift) std::cout << "CREA NIFT " << mFormId << ", non-zero " << nift << std::endl; break; } case ESM4::SUB_KFFZ: { std::string str; if (!reader.getZString(str)) throw std::runtime_error ("CREA KFFZ data read error"); std::stringstream ss(str); std::string file; while (std::getline(ss, file, '\0')) // split the strings mKf.push_back(file); break; } case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; // FO3 case ESM4::SUB_PNAM: // FO3/FONV/TES5 { FormId bodyPart; reader.get(bodyPart); mBodyParts.push_back(bodyPart); break; } case ESM4::SUB_MODT: case ESM4::SUB_RNAM: case ESM4::SUB_CSDT: case ESM4::SUB_OBND: // FO3 case ESM4::SUB_EAMT: // FO3 case ESM4::SUB_VTCK: // FO3 case ESM4::SUB_NAM4: // FO3 case ESM4::SUB_NAM5: // FO3 case ESM4::SUB_CNAM: // FO3 case ESM4::SUB_LNAM: // FO3 case ESM4::SUB_EITM: // FO3 case ESM4::SUB_DEST: // FO3 case ESM4::SUB_DSTD: // FO3 case ESM4::SUB_DSTF: // FO3 case ESM4::SUB_DMDL: // FO3 case ESM4::SUB_DMDT: // FO3 case ESM4::SUB_COED: // FO3 { //std::cout << "CREA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::CREA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Creature::save(ESM4::Writer& writer) const //{ //} //void ESM4::Creature::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadcrea.hpp000066400000000000000000000113221445372753700217530ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_CREA_H #define ESM4_CREA_H #include #include #include #include "actor.hpp" #include "inventory.hpp" namespace ESM4 { class Reader; class Writer; struct Creature { enum ACBS_TES4 { TES4_Essential = 0x000002, TES4_WeapAndShield = 0x000004, TES4_Respawn = 0x000008, TES4_PCLevelOffset = 0x000080, TES4_NoLowLevelProc = 0x000200, TES4_NoHead = 0x008000, // different meaning to npc_ TES4_NoRightArm = 0x010000, TES4_NoLeftArm = 0x020000, TES4_NoCombatWater = 0x040000, TES4_NoShadow = 0x080000, TES4_NoCorpseCheck = 0x100000 // opposite of npc_ }; enum ACBS_FO3 { FO3_Biped = 0x00000001, FO3_Essential = 0x00000002, FO3_Weap_Shield = 0x00000004, FO3_Respawn = 0x00000008, FO3_CanSwim = 0x00000010, FO3_CanFly = 0x00000020, FO3_CanWalk = 0x00000040, FO3_PCLevelMult = 0x00000080, FO3_NoLowLevelProc = 0x00000200, FO3_NoBloodSpray = 0x00000800, FO3_NoBloodDecal = 0x00001000, FO3_NoHead = 0x00008000, FO3_NoRightArm = 0x00010000, FO3_NoLeftArm = 0x00020000, FO3_NoWaterCombat = 0x00040000, FO3_NoShadow = 0x00080000, FO3_NoVATSMelee = 0x00100000, FO3_AllowPCDialog = 0x00200000, FO3_NoOpenDoors = 0x00400000, FO3_Immobile = 0x00800000, FO3_TiltFrontBack = 0x01000000, FO3_TiltLeftRight = 0x02000000, FO3_NoKnockdown = 0x04000000, FO3_NotPushable = 0x08000000, FO3_AllowPickpoket = 0x10000000, FO3_IsGhost = 0x20000000, FO3_NoRotateHead = 0x40000000, FO3_Invulnerable = 0x80000000 }; #pragma pack(push, 1) struct Data { std::uint8_t unknown; std::uint8_t combat; std::uint8_t magic; std::uint8_t stealth; std::uint16_t soul; std::uint16_t health; std::uint16_t unknown2; std::uint16_t damage; AttributeValues attribs; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; FormId mDeathItem; std::vector mSpell; FormId mScriptId; AIData mAIData; std::vector mAIPackages; ActorBaseConfig mBaseConfig; ActorFaction mFaction; Data mData; FormId mCombatStyle; FormId mSoundBase; FormId mSound; std::uint8_t mSoundChance; float mBaseScale; float mTurningSpeed; float mFootWeight; std::string mBloodSpray; std::string mBloodDecal; float mBoundRadius; std::vector mNif; // NIF filenames, get directory from mModel std::vector mKf; std::vector mInventory; FormId mBaseTemplate; // FO3/FONV std::vector mBodyParts; // FO3/FONV void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_CREA_H openmw-openmw-0.48.0/components/esm4/loaddial.cpp000066400000000000000000000073011445372753700217470ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loaddial.hpp" #include #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Dialogue::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mTopicName); break; case ESM4::SUB_QSTI: { FormId questId; reader.getFormId(questId); mQuests.push_back(questId); break; } case ESM4::SUB_QSTR: // Seems never used in TES4 { FormId questRem; reader.getFormId(questRem); mQuestsRemoved.push_back(questRem); break; } case ESM4::SUB_DATA: { if (subHdr.dataSize == 4) // TES5 { std::uint8_t dummy; reader.get(dummy); if (dummy != 0) mDoAllBeforeRepeat = true; } reader.get(mDialType); // TES4/FO3/FONV/TES5 if (subHdr.dataSize >= 2) // FO3/FONV/TES5 reader.get(mDialFlags); if (subHdr.dataSize >= 3) // TES5 reader.skipSubRecordData(1); // unknown break; } case ESM4::SUB_PNAM: reader.get(mPriority); break; // FO3/FONV case ESM4::SUB_TDUM: reader.getZString(mTextDumb); break; // FONV case ESM4::SUB_SCRI: case ESM4::SUB_INFC: // FONV info connection case ESM4::SUB_INFX: // FONV info index case ESM4::SUB_QNAM: // TES5 case ESM4::SUB_BNAM: // TES5 case ESM4::SUB_SNAM: // TES5 case ESM4::SUB_TIFC: // TES5 { //std::cout << "DIAL " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::DIAL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Dialogue::save(ESM4::Writer& writer) const //{ //} //void ESM4::Dialogue::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loaddial.hpp000066400000000000000000000040021445372753700217470ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_DIAL_H #define ESM4_DIAL_H #include #include #include #include "formid.hpp" #include "dialogue.hpp" // DialType namespace ESM4 { class Reader; class Writer; struct Dialogue { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::vector mQuests; std::vector mQuestsRemoved; // FONV only? std::string mTopicName; std::string mTextDumb; // FIXME: temp name bool mDoAllBeforeRepeat; // TES5 only std::uint8_t mDialType; // DialType std::uint8_t mDialFlags; // FO3/FONV: 0x1 rumours, 0x2 top-level float mPriority; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_DIAL_H openmw-openmw-0.48.0/components/esm4/loaddobj.cpp000066400000000000000000000112351445372753700217550ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loaddobj.hpp" #include #include //#include // FIXME: for debugging only //#include "formid.hpp" #include "reader.hpp" //#include "writer.hpp" void ESM4::DefaultObj::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; // "DefaultObjectManager" case ESM4::SUB_DATA: { reader.getFormId(mData.stimpack); reader.getFormId(mData.superStimpack); reader.getFormId(mData.radX); reader.getFormId(mData.radAway); reader.getFormId(mData.morphine); reader.getFormId(mData.perkParalysis); reader.getFormId(mData.playerFaction); reader.getFormId(mData.mysteriousStrangerNPC); reader.getFormId(mData.mysteriousStrangerFaction); reader.getFormId(mData.defaultMusic); reader.getFormId(mData.battleMusic); reader.getFormId(mData.deathMusic); reader.getFormId(mData.successMusic); reader.getFormId(mData.levelUpMusic); reader.getFormId(mData.playerVoiceMale); reader.getFormId(mData.playerVoiceMaleChild); reader.getFormId(mData.playerVoiceFemale); reader.getFormId(mData.playerVoiceFemaleChild); reader.getFormId(mData.eatPackageDefaultFood); reader.getFormId(mData.everyActorAbility); reader.getFormId(mData.drugWearsOffImageSpace); // below FONV only if (subHdr.dataSize == 136) // FONV 136/4 = 34 formid { reader.getFormId(mData.doctorsBag); reader.getFormId(mData.missFortuneNPC); reader.getFormId(mData.missFortuneFaction); reader.getFormId(mData.meltdownExplosion); reader.getFormId(mData.unarmedForwardPA); reader.getFormId(mData.unarmedBackwardPA); reader.getFormId(mData.unarmedLeftPA); reader.getFormId(mData.unarmedRightPA); reader.getFormId(mData.unarmedCrouchPA); reader.getFormId(mData.unarmedCounterPA); reader.getFormId(mData.spotterEffect); reader.getFormId(mData.itemDetectedEfect); reader.getFormId(mData.cateyeMobileEffectNYI); } break; } case ESM4::SUB_DNAM: { //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); throw std::runtime_error("ESM4::DOBJ::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::DefaultObj::save(ESM4::Writer& writer) const //{ //} //void ESM4::DefaultObj::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loaddobj.hpp000066400000000000000000000054651445372753700217720ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_DOBJ_H #define ESM4_DOBJ_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Defaults { FormId stimpack; FormId superStimpack; FormId radX; FormId radAway; FormId morphine; FormId perkParalysis; FormId playerFaction; FormId mysteriousStrangerNPC; FormId mysteriousStrangerFaction; FormId defaultMusic; FormId battleMusic; FormId deathMusic; FormId successMusic; FormId levelUpMusic; FormId playerVoiceMale; FormId playerVoiceMaleChild; FormId playerVoiceFemale; FormId playerVoiceFemaleChild; FormId eatPackageDefaultFood; FormId everyActorAbility; FormId drugWearsOffImageSpace; // below FONV only FormId doctorsBag; FormId missFortuneNPC; FormId missFortuneFaction; FormId meltdownExplosion; FormId unarmedForwardPA; FormId unarmedBackwardPA; FormId unarmedLeftPA; FormId unarmedRightPA; FormId unarmedCrouchPA; FormId unarmedCounterPA; FormId spotterEffect; FormId itemDetectedEfect; FormId cateyeMobileEffectNYI; }; struct DefaultObj { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; Defaults mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_DOBJ_H openmw-openmw-0.48.0/components/esm4/loaddoor.cpp000066400000000000000000000057541445372753700220130ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loaddoor.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Door::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; case ESM4::SUB_ANAM: reader.getFormId(mCloseSound); break; case ESM4::SUB_BNAM: reader.getFormId(mLoopSound); break; case ESM4::SUB_FNAM: reader.get(mDoorFlags); break; case ESM4::SUB_TNAM: reader.getFormId(mRandomTeleport); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_MODS: case ESM4::SUB_OBND: case ESM4::SUB_VMAD: case ESM4::SUB_DEST: // FO3 case ESM4::SUB_DSTD: // FO3 case ESM4::SUB_DSTF: // FO3 case ESM4::SUB_DMDL: // FO3 case ESM4::SUB_DMDT: // FO3 { //std::cout << "DOOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::DOOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Door::save(ESM4::Writer& writer) const //{ //} //void ESM4::Door::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loaddoor.hpp000066400000000000000000000041541445372753700220110ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_DOOR_H #define ESM4_DOOR_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Door { enum Flags { Flag_OblivionGate = 0x01, Flag_AutomaticDoor = 0x02, Flag_Hidden = 0x04, Flag_MinimalUse = 0x08 }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; float mBoundRadius; std::uint8_t mDoorFlags; FormId mScriptId; FormId mOpenSound; // SNDR for TES5, SOUN for others FormId mCloseSound; // SNDR for TES5, SOUN for others FormId mLoopSound; FormId mRandomTeleport; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_DOOR_H openmw-openmw-0.48.0/components/esm4/loadeyes.cpp000066400000000000000000000040301445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadeyes.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Eyes::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_DATA: reader.get(mData); break; default: throw std::runtime_error("ESM4::EYES::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Eyes::save(ESM4::Writer& writer) const //{ //} //void ESM4::Eyes::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadeyes.hpp000066400000000000000000000035001445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_EYES_H #define ESM4_EYES_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Eyes { #pragma pack(push, 1) struct Data { std::uint8_t flags; // 0x01 = playable? }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mIcon; // texture Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_EYES_H openmw-openmw-0.48.0/components/esm4/loadflor.cpp000066400000000000000000000053601445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadflor.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Flora::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_PFIG: reader.getFormId(mIngredient); break; case ESM4::SUB_PFPC: reader.get(mPercentHarvest); break; case ESM4::SUB_SNAM: reader.getFormId(mSound); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_MODS: case ESM4::SUB_FNAM: case ESM4::SUB_OBND: case ESM4::SUB_PNAM: case ESM4::SUB_RNAM: case ESM4::SUB_VMAD: { //std::cout << "FLOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::FLOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Flora::save(ESM4::Writer& writer) const //{ //} //void ESM4::Flora::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadflor.hpp000066400000000000000000000041161445372753700220060ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_FLOR_H #define ESM4_FLOR_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Flora { #pragma pack(push, 1) struct Production { std::uint8_t spring; std::uint8_t summer; std::uint8_t autumn; std::uint8_t winter; Production() : spring(0), summer(0), autumn(0), winter(0) {} }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; float mBoundRadius; FormId mScriptId; FormId mIngredient; FormId mSound; Production mPercentHarvest; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_FLOR_H openmw-openmw-0.48.0/components/esm4/loadflst.cpp000066400000000000000000000045601445372753700220120ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadflst.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::FormIdList::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_LNAM: { FormId formId; reader.getFormId(formId); mObjects.push_back(formId); break; } default: //std::cout << "FLST " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); throw std::runtime_error("ESM4::FLST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //std::cout << "flst " << mEditorId << " " << mObjects.size() << std::endl; // FIXME } //void ESM4::FormIdList::save(ESM4::Writer& writer) const //{ //} //void ESM4::FormIdList::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadflst.hpp000066400000000000000000000032201445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_FLST_H #define ESM4_FLST_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct FormIdList { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::vector mObjects; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_FLST_H openmw-openmw-0.48.0/components/esm4/loadfurn.cpp000066400000000000000000000057261445372753700220210ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadfurn.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Furniture::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MNAM: reader.get(mActiveMarkerFlags); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_DEST: case ESM4::SUB_DSTD: case ESM4::SUB_DSTF: case ESM4::SUB_ENAM: case ESM4::SUB_FNAM: case ESM4::SUB_FNMK: case ESM4::SUB_FNPR: case ESM4::SUB_KNAM: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_MODS: case ESM4::SUB_NAM0: case ESM4::SUB_OBND: case ESM4::SUB_PNAM: case ESM4::SUB_VMAD: case ESM4::SUB_WBDT: case ESM4::SUB_XMRK: { //std::cout << "FURN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::FURN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Furniture::save(ESM4::Writer& writer) const //{ //} //void ESM4::Furniture::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadfurn.hpp000066400000000000000000000033771445372753700220260ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_FURN_H #define ESM4_FURN_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Furniture { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; float mBoundRadius; FormId mScriptId; std::uint32_t mActiveMarkerFlags; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_FURN_H openmw-openmw-0.48.0/components/esm4/loadglob.cpp000066400000000000000000000046631445372753700217710ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadglob.hpp" #include #include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::GlobalVariable::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FNAM: reader.get(mType); break; case ESM4::SUB_FLTV: reader.get(mValue); break; case ESM4::SUB_FULL: case ESM4::SUB_MODL: case ESM4::SUB_MODB: case ESM4::SUB_ICON: case ESM4::SUB_DATA: case ESM4::SUB_OBND: // TES5 case ESM4::SUB_VMAD: // TES5 { //std::cout << "GLOB " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::GLOB::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::GlobalVariable::save(ESM4::Writer& writer) const //{ //} //void ESM4::GlobalVariable::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadglob.hpp000066400000000000000000000032161445372753700217670ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_GLOB_H #define ESM4_GLOB_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct GlobalVariable { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::uint8_t mType; float mValue; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_GLOB_H openmw-openmw-0.48.0/components/esm4/loadgras.cpp000066400000000000000000000044341445372753700217760ustar00rootroot00000000000000/* Copyright (C) 2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadgras.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Grass::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_OBND: { //std::cout << "GRAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::GRAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Grass::save(ESM4::Writer& writer) const //{ //} //void ESM4::Grass::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadgras.hpp000066400000000000000000000052731445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_GRAS_H #define ESM4_GRAS_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Grass { #pragma pack(push, 1) // unused fields are probably packing struct Data { std::uint8_t density; std::uint8_t minSlope; std::uint8_t maxSlope; std::uint8_t unused; std::uint16_t distanceFromWater; std::uint16_t unused2; /* 1 Above - At Least 2 Above - At Most 3 Below - At Least 4 Below - At Most 5 Either - At Least 6 Either - At Most 7 Either - At Most Above 8 Either - At Most Below */ std::uint32_t waterDistApplication; float positionRange; float heightRange; float colorRange; float wavePeriod; /* 0x01 Vertex Lighting 0x02 Uniform Scaling 0x04 Fit to Slope */ std::uint8_t flags; std::uint8_t unused3; std::uint16_t unused4; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; float mBoundRadius; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_GRAS_H openmw-openmw-0.48.0/components/esm4/loadgrup.hpp000066400000000000000000000160621445372753700220240ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_GRUP_H #define ESM4_GRUP_H #include #include #include "common.hpp" // GroupLabel namespace ESM4 { // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups // // Type | Info | // ------+--------------------------------------+------------------- // 2 | Interior Cell Block | // 3 | Interior Cell Sub-Block | // R | CELL | // 6 | Cell Childen | // 8 | Persistent children | // R | REFR, ACHR, ACRE | // 10 | Visible distant children | // R | REFR, ACHR, ACRE | // 9 | Temp Children | // R | PGRD | // R | REFR, ACHR, ACRE | // | | // 0 | Top (Type) | // R | WRLD | // 1 | World Children | // R | ROAD | // R | CELL | // 6 | Cell Childen | // 8 | Persistent children | // R | REFR, ACHR, ACRE | // 10 | Visible distant children | // R | REFR, ACHR, ACRE | // 9 | Temp Children | // R | PGRD | // R | REFR, ACHR, ACRE | // 4 | Exterior World Block | // 5 | Exterior World Sub-block | // R | CELL | // 6 | Cell Childen | // 8 | Persistent children | // R | REFR, ACHR, ACRE | // 10 | Visible distant children | // R | REFR, ACHR, ACRE | // 9 | Temp Children | // R | LAND | // R | PGRD | // R | REFR, ACHR, ACRE | // struct WorldGroup { FormId mWorld; // WRLD record for this group // occurs only after World Child (type 1) // since GRUP label may not be reliable, need to keep the formid of the current WRLD in // the reader's context FormId mRoad; std::vector mCells; // FIXME should this be CellGroup* instead? }; // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/CELL // // The block and subblock groups for an interior cell are determined by the last two decimal // digits of the lower 3 bytes of the cell form ID (the modindex is not included in the // calculation). For example, for form ID 0x000CF2=3314, the block is 4 and the subblock is 1. // // The block and subblock groups for an exterior cell are determined by the X-Y coordinates of // the cell. Each block contains 16 subblocks (4x4) and each subblock contains 64 cells (8x8). // So each block contains 1024 cells (32x32). // // NOTE: There may be many CELL records in one subblock struct CellGroup { FormId mCell; // CELL record for this cell group int mCellModIndex; // from which file to get the CELL record (e.g. may have been updated) // For retrieving parent group size (for lazy loading or skipping) and sub-block number / grid // NOTE: There can be more than one file that adds/modifies records to this cell group // // Use Case 1: To quickly get only the visble when distant records: // // - Find the FormId of the CELL (maybe WRLD/X/Y grid lookup or from XTEL of a REFR) // - search a map of CELL FormId to CellGroup // - load CELL and its child groups (or load the visible distant only, or whatever) // // Use Case 2: Scan the files but don't load CELL or cell group // // - Load referenceables and other records up front, updating them as required // - Don't load CELL, LAND, PGRD or ROAD (keep FormId's and file index, and file // context then skip the rest of the group) // std::vector mHeaders; // FIXME: is this needed? // FIXME: should these be pairs? i.e. so that we know from which file // the formid came (it may have been updated by a mod) // but does it matter? the record itself keeps track of whether it is base, // added or modified anyway // FIXME: should these be maps? e.g. std::map // or vector for storage with a corresponding map of index? // cache (modindex adjusted) formId's of children // FIXME: also need file index + file context of all those that has type 8 GRUP GroupTypeHeader mHdrPersist; std::vector mPersistent; // REFR, ACHR, ACRE std::vector mdelPersistent; // FIXME: also need file index + file context of all those that has type 10 GRUP GroupTypeHeader mHdrVisDist; std::vector mVisibleDist; // REFR, ACHR, ACRE std::vector mdelVisibleDist; // FIXME: also need file index + file context of all those that has type 9 GRUP GroupTypeHeader mHdrTemp; FormId mLand; // if present, assume only one LAND per exterior CELL FormId mPgrd; // if present, seems to be the first record after LAND in Temp Cell Child GRUP std::vector mTemporary; // REFR, ACHR, ACRE std::vector mdelTemporary; // need to keep modindex and context for lazy loading (of all the files that contribute // to this group) }; } #endif // ESM4_GRUP_H openmw-openmw-0.48.0/components/esm4/loadhair.cpp000066400000000000000000000046721445372753700217710ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadhair.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Hair::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: { //std::cout << "HAIR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::HAIR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Hair::save(ESM4::Writer& writer) const //{ //} //void ESM4::Hair::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadhair.hpp000066400000000000000000000036601445372753700217720ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_HAIR #define ESM4_HAIR #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Hair { #pragma pack(push, 1) struct Data { std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; // mesh std::string mIcon; // texture float mBoundRadius; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_HAIR openmw-openmw-0.48.0/components/esm4/loadhdpt.cpp000066400000000000000000000065601445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadhdpt.hpp" #include #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::HeadPart::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::optional type; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_HNAM: reader.getFormId(mAdditionalPart); break; case ESM4::SUB_NAM0: // TES5 { std::uint32_t value; reader.get(value); type = value; break; } case ESM4::SUB_NAM1: // TES5 { std::string file; reader.getZString(file); if (!type.has_value()) throw std::runtime_error("Failed to read ESM4 HDPT record: subrecord NAM0 does not precede subrecord NAM1: file type is unknown"); if (*type >= mTriFile.size()) throw std::runtime_error("Failed to read ESM4 HDPT record: invalid file type: " + std::to_string(*type)); mTriFile[*type] = std::move(file); break; } case ESM4::SUB_TNAM: reader.getFormId(mBaseTexture); break; case ESM4::SUB_PNAM: case ESM4::SUB_MODS: case ESM4::SUB_MODT: case ESM4::SUB_RNAM: { //std::cout << "HDPT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::HDPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::HeadPart::save(ESM4::Writer& writer) const //{ //} //void ESM4::HeadPart::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadhdpt.hpp000066400000000000000000000034361445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_HDPT_H #define ESM4_HDPT_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct HeadPart { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::uint8_t mData; FormId mAdditionalPart; std::array mTriFile; FormId mBaseTexture; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_HDPT_H openmw-openmw-0.48.0/components/esm4/loadidle.cpp000066400000000000000000000046561445372753700217650ustar00rootroot00000000000000/* Copyright (C) 2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadidle.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::IdleAnimation::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_DNAM: reader.getZString(mCollision); break; case ESM4::SUB_ENAM: reader.getZString(mEvent); break; case ESM4::SUB_ANAM: { reader.get(mParent); reader.get(mPrevious); break; } case ESM4::SUB_CTDA: // formId case ESM4::SUB_DATA: // formId { //std::cout << "IDLE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::IDLE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::IdleAnimation::save(ESM4::Writer& writer) const //{ //} //void ESM4::IdleAnimation::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadidle.hpp000066400000000000000000000033601445372753700217610ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_IDLE_H #define ESM4_IDLE_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct IdleAnimation { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mCollision; std::string mEvent; FormId mParent; // IDLE or AACT FormId mPrevious; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_IDLE_H openmw-openmw-0.48.0/components/esm4/loadidlm.cpp000066400000000000000000000062261445372753700217700ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadidlm.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::IdleMarker::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_IDLF: reader.get(mIdleFlags); break; case ESM4::SUB_IDLC: { if (subHdr.dataSize != 1) // FO3 can have 4? { reader.skipSubRecordData(); break; } reader.get(mIdleCount); break; } case ESM4::SUB_IDLT: reader.get(mIdleTimer); break; case ESM4::SUB_IDLA: { bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; if (esmVer == ESM::VER_094 || isFONV) // FO3? 4 or 8 bytes { reader.skipSubRecordData(); break; } mIdleAnim.resize(mIdleCount); for (unsigned int i = 0; i < static_cast(mIdleCount); ++i) reader.get(mIdleAnim.at(i)); break; } case ESM4::SUB_OBND: // object bounds { //std::cout << "IDLM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::IDLM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::IdleMarker::save(ESM4::Writer& writer) const //{ //} //void ESM4::IdleMarker::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadidlm.hpp000066400000000000000000000033751445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_IDLM_H #define ESM4_IDLM_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct IdleMarker { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; std::uint8_t mIdleFlags; std::uint8_t mIdleCount; float mIdleTimer; std::vector mIdleAnim; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_IDLM_H openmw-openmw-0.48.0/components/esm4/loadimod.cpp000066400000000000000000000053131445372753700217670ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadimod.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::ItemMod::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_OBND: case ESM4::SUB_FULL: case ESM4::SUB_MODL: case ESM4::SUB_ICON: case ESM4::SUB_MICO: case ESM4::SUB_SCRI: case ESM4::SUB_DESC: case ESM4::SUB_YNAM: case ESM4::SUB_ZNAM: case ESM4::SUB_DATA: { //std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); //throw std::runtime_error("ESM4::IMOD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::ItemMod::save(ESM4::Writer& writer) const //{ //} //void ESM4::ItemMod::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadimod.hpp000066400000000000000000000032421445372753700217730ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_IMOD_H #define ESM4_IMOD_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct ItemMod { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_IMOD_H openmw-openmw-0.48.0/components/esm4/loadinfo.cpp000066400000000000000000000170111445372753700217700ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadinfo.hpp" #include #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::DialogInfo::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code static ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_QSTI: reader.getFormId(mQuest); break; // FormId quest id case ESM4::SUB_SNDD: reader.getFormId(mSound); break; // FO3 (not used in FONV?) case ESM4::SUB_TRDT: { if (subHdr.dataSize == 16) // TES4 reader.get(&mResponseData, 16); else if (subHdr.dataSize == 20) // FO3 reader.get(&mResponseData, 20); else // FO3/FONV { reader.get(mResponseData); if (mResponseData.sound) reader.adjustFormId(mResponseData.sound); } break; } case ESM4::SUB_NAM1: reader.getZString(mResponse); break; // response text case ESM4::SUB_NAM2: reader.getZString(mNotes); break; // actor notes case ESM4::SUB_NAM3: reader.getZString(mEdits); break; // not in TES4 case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 reader.get(&mTargetCondition, 24); else if (subHdr.dataSize == 20) // FO3 reader.get(&mTargetCondition, 20); else if (subHdr.dataSize == 28) { reader.get(mTargetCondition); // FO3/FONV if (mTargetCondition.reference) reader.adjustFormId(mTargetCondition.reference); } else // TES5 { reader.get(&mTargetCondition, 20); if (subHdr.dataSize == 36) reader.getFormId(mParam3); reader.get(mTargetCondition.runOn); reader.get(mTargetCondition.reference); if (mTargetCondition.reference) reader.adjustFormId(mTargetCondition.reference); reader.skipSubRecordData(4); // unknown } break; } case ESM4::SUB_SCHR: { if (!ignore) reader.get(mScript.scriptHeader); else reader.skipSubRecordData(); // TODO: does the second one ever used? break; } case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; case ESM4::SUB_SLSD: { localVar.clear(); reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); // WARN: assumes SCVR will follow immediately break; } case ESM4::SUB_SCVR: // assumed always pair with SLSD { reader.getZString(localVar.variableName); mScript.localVarData.push_back(localVar); break; } case ESM4::SUB_SCRV: { std::uint32_t index; reader.get(index); mScript.localRefVarIndex.push_back(index); break; } case ESM4::SUB_NEXT: // FO3/FONV marker for next script header { ignore = true; break; } case ESM4::SUB_DATA: // always 3 for TES4 ? { if (subHdr.dataSize == 4) // FO3/FONV { reader.get(mDialType); reader.get(mNextSpeaker); reader.get(mInfoFlags); } else reader.skipSubRecordData(); // FIXME break; } case ESM4::SUB_NAME: // FormId add topic (not always present) case ESM4::SUB_CTDT: // older version of CTDA? 20 bytes case ESM4::SUB_SCHD: // 28 bytes case ESM4::SUB_TCLT: // FormId choice case ESM4::SUB_TCLF: // FormId case ESM4::SUB_PNAM: // TES4 DLC case ESM4::SUB_TPIC: // TES4 DLC case ESM4::SUB_ANAM: // FO3 speaker formid case ESM4::SUB_DNAM: // FO3 speech challenge case ESM4::SUB_KNAM: // FO3 formid case ESM4::SUB_LNAM: // FONV case ESM4::SUB_TCFU: // FONV case ESM4::SUB_TIFC: // TES5 case ESM4::SUB_TWAT: // TES5 case ESM4::SUB_CIS2: // TES5 case ESM4::SUB_CNAM: // TES5 case ESM4::SUB_ENAM: // TES5 case ESM4::SUB_EDID: // TES5 case ESM4::SUB_VMAD: // TES5 case ESM4::SUB_BNAM: // TES5 case ESM4::SUB_SNAM: // TES5 case ESM4::SUB_ONAM: // TES5 case ESM4::SUB_QNAM: // TES5 for mScript case ESM4::SUB_RNAM: // TES5 { //std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); //throw std::runtime_error("ESM4::INFO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::DialogInfo::save(ESM4::Writer& writer) const //{ //} //void ESM4::DialogInfo::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadinfo.hpp000066400000000000000000000052101445372753700217730ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_INFO_H #define ESM4_INFO_H #include #include #include "formid.hpp" #include "script.hpp" // TargetCondition #include "dialogue.hpp" // DialType namespace ESM4 { class Reader; class Writer; enum InfoFlag { INFO_Goodbye = 0x0001, INFO_Random = 0x0002, INFO_SayOnce = 0x0004, INFO_RunImmediately = 0x0008, INFO_InfoRefusal = 0x0010, INFO_RandomEnd = 0x0020, INFO_RunForRumors = 0x0040, INFO_SpeechChallenge = 0x0080, INFO_SayOnceADay = 0x0100, INFO_AlwaysDarken = 0x0200 }; struct DialogInfo { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; // FIXME: no such record for INFO, but keep here to avoid extra work for now FormId mQuest; FormId mSound; // unused? TargetResponseData mResponseData; std::string mResponse; std::string mNotes; std::string mEdits; std::uint8_t mDialType; // DialType std::uint8_t mNextSpeaker; std::uint16_t mInfoFlags; // see above enum TargetCondition mTargetCondition; FormId mParam3; // TES5 only ScriptDefinition mScript; // FIXME: ignoring the second one after the NEXT sub-record void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_INFO_H openmw-openmw-0.48.0/components/esm4/loadingr.cpp000066400000000000000000000077011445372753700220010ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadingr.hpp" #include #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Ingredient::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: { if (mFullName.empty()) { reader.getLocalizedString(mFullName); break; } else // in TES4 subsequent FULL records are script effect names { // FIXME: should be part of a struct? std::string scriptEffectName; if (!reader.getZString(scriptEffectName)) throw std::runtime_error ("INGR FULL data read error"); mScriptEffect.push_back(scriptEffectName); break; } } case ESM4::SUB_DATA: { //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 reader.get(mData); else reader.get(mData.weight); break; } case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_ENIT: reader.get(mEnchantment); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_SCIT: { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } case ESM4::SUB_MODT: case ESM4::SUB_MODS: // Dragonborn only? case ESM4::SUB_EFID: case ESM4::SUB_EFIT: case ESM4::SUB_OBND: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_VMAD: case ESM4::SUB_YNAM: case ESM4::SUB_ZNAM: case ESM4::SUB_ETYP: // FO3 { //std::cout << "INGR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::INGR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Ingredient::save(ESM4::Writer& writer) const //{ //} //void ESM4::Ingredient::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadingr.hpp000066400000000000000000000042451445372753700220060ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_INGR_H #define ESM4_INGR_H #include #include #include "effect.hpp" namespace ESM4 { class Reader; class Writer; struct Ingredient { #pragma pack(push, 1) struct Data { std::uint32_t value; float weight; }; struct ENIT { std::uint32_t value; std::uint32_t flags; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory float mBoundRadius; std::vector mScriptEffect; // FIXME: prob. should be in a struct FormId mScriptId; ScriptEffect mEffect; ENIT mEnchantment; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_INGR_H openmw-openmw-0.48.0/components/esm4/loadkeym.cpp000066400000000000000000000054651445372753700220140ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadkeym.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Key::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_OBND: case ESM4::SUB_VMAD: { //std::cout << "KEYM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::KEYM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Key::save(ESM4::Writer& writer) const //{ //} //void ESM4::Key::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadkeym.hpp000066400000000000000000000040151445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_KEYM_H #define ESM4_KEYM_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Key { #pragma pack(push, 1) struct Data { std::uint32_t value; // gold float weight; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory std::string mMiniIcon; // inventory FormId mPickUpSound; FormId mDropSound; float mBoundRadius; FormId mScriptId; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_KEYM_H openmw-openmw-0.48.0/components/esm4/loadland.cpp000066400000000000000000000212341445372753700217550ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadland.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include #include // FIXME: debug only #include "reader.hpp" //#include "writer.hpp" // overlap north // // 32 // 31 // 30 // overlap . // west . // . // 2 // 1 // 0 // 0 1 2 ... 30 31 32 // // overlap south // void ESM4::Land::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mDataTypes = 0; TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT //std::map uniqueTextures; // FIXME: for temp testing only while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_DATA: { reader.get(mLandFlags); break; } case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 { reader.get(mVertNorm); mDataTypes |= LAND_VNML; break; } case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { #if 0 reader.get(mHeightMap.heightOffset); reader.get(mHeightMap.gradientData); reader.get(mHeightMap.unknown1); reader.get(mHeightMap.unknown2); #endif reader.get(mHeightMap); mDataTypes |= LAND_VHGT; break; } case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 { reader.get(mVertColr); mDataTypes |= LAND_VCLR; break; } case ESM4::SUA_BTXT: { BTXT base; if (reader.getExact(base)) { assert(base.quadrant < 4 && "base texture quadrant index error"); reader.adjustFormId(base.formId); mTextures[base.quadrant].base = std::move(base); #if 0 std::cout << "Base Texture formid: 0x" << std::hex << mTextures[base.quadrant].base.formId << ", quad " << std::dec << (int)base.quadrant << std::endl; #endif } break; } case ESM4::SUB_ATXT: { if (currentAddQuad != -1) { // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << std::endl; mTextures[currentAddQuad].layers.push_back(layer); } reader.get(layer.texture); reader.adjustFormId(layer.texture.formId); assert(layer.texture.quadrant < 4 && "additional texture quadrant index error"); #if 0 FormId txt = layer.texture.formId; std::map::iterator lb = uniqueTextures.lower_bound(txt); if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) { lb->second += 1; } else uniqueTextures.insert(lb, std::make_pair(txt, 1)); #endif #if 0 std::cout << "Additional Texture formId: 0x" << std::hex << layer.texture.formId << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; std::cout << "Additional Texture layer: " << std::dec << (int)layer.texture.layerIndex << std::endl; #endif currentAddQuad = layer.texture.quadrant; break; } case ESM4::SUB_VTXT: { assert(currentAddQuad != -1 && "VTXT without ATXT found"); int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT); assert((reader.subRecordHeader().dataSize % sizeof(ESM4::Land::VTXT)) == 0 && "ESM4::LAND VTXT data size error"); if (count) { layer.data.resize(count); std::vector::iterator it = layer.data.begin(); for (;it != layer.data.end(); ++it) { reader.get(*it); // FIXME: debug only //std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; } } mTextures[currentAddQuad].layers.push_back(layer); // Assumed that the layers are added in the correct sequence // FIXME: Knights.esp doesn't seem to observe this - investigate more //assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 //&& "additional texture layer index error"); currentAddQuad = -1; layer.data.clear(); // FIXME: debug only //std::cout << "VTXT: count " << std::dec << count << std::endl; break; } case ESM4::SUB_VTEX: // only in Oblivion? { int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId); assert((reader.subRecordHeader().dataSize % sizeof(FormId)) == 0 && "ESM4::LAND VTEX data size error"); if (count) { mIds.resize(count); for (std::vector::iterator it = mIds.begin(); it != mIds.end(); ++it) { reader.getFormId(*it); // FIXME: debug only //std::cout << "VTEX: " << std::hex << *it << std::endl; } } break; } default: throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } if (currentAddQuad != -1) { // FIXME: not sure if it happens here as well std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad " << (int)layer.texture.quadrant << std::endl; mTextures[currentAddQuad].layers.push_back(layer); } bool missing = false; for (int i = 0; i < 4; ++i) { if (mTextures[i].base.formId == 0) { //std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; //std::cout << "layers " << mTextures[i].layers.size() << std::endl; // NOTE: can't set the default here since FO3/FONV may have different defaults //mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds missing = true; } //else //{ // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; // std::cout << "layers " << mTextures[i].layers.size() << std::endl; //} } // at least one of the quadrants do not have a base texture, return without setting the flag if (!missing) mDataTypes |= LAND_VTEX; } //void ESM4::Land::save(ESM4::Writer& writer) const //{ //} //void ESM4::Land::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadland.hpp000066400000000000000000000077741445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LAND_H #define ESM4_LAND_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; struct Land { enum { LAND_VNML = 1, LAND_VHGT = 2, LAND_WNAM = 4, // only in TES3? LAND_VCLR = 8, LAND_VTEX = 16 }; // number of vertices per side static const int VERTS_PER_SIDE = 33; // cell terrain size in world coords static const int REAL_SIZE = 4096; // total number of vertices static const int LAND_NUM_VERTS = VERTS_PER_SIDE * VERTS_PER_SIDE; static const int HEIGHT_SCALE = 8; // number of textures per side of a land quadrant // (for TES4 - based on vanilla observations) static const int QUAD_TEXTURE_PER_SIDE = 6; #pragma pack(push,1) struct VHGT { float heightOffset; std::int8_t gradientData[VERTS_PER_SIDE * VERTS_PER_SIDE]; std::uint16_t unknown1; unsigned char unknown2; }; struct BTXT { FormId formId; std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::uint8_t unknown1; std::uint16_t unknown2; }; struct ATXT { FormId formId; std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::uint8_t unknown; std::uint16_t layerIndex; // texture layer, 0..7 }; struct VTXT { std::uint16_t position; // 0..288 (17x17 grid) std::uint8_t unknown1; std::uint8_t unknown2; float opacity; }; #pragma pack(pop) struct TxtLayer { ATXT texture; std::vector data; // alpha data }; struct Texture { BTXT base; std::vector layers; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mLandFlags; // from DATA subrecord // FIXME: lazy loading not yet implemented int mDataTypes; // which data types are loaded signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord signed char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord VHGT mHeightMap; Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids virtual void load(Reader& reader); //virtual void save(Writer& writer) const; //void blank(); }; } #endif // ESM4_LAND_H openmw-openmw-0.48.0/components/esm4/loadlgtm.cpp000066400000000000000000000055331445372753700220060ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadlgtm.hpp" #include #include // FLT_MAX for gcc //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::LightingTemplate::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_DATA: { if (subHdr.dataSize == 36) // TES4 reader.get(&mLighting, 36); if (subHdr.dataSize == 40) // FO3/FONV reader.get(mLighting); else if (subHdr.dataSize == 92) // TES5 { reader.get(mLighting); reader.skipSubRecordData(52); // FIXME } else reader.skipSubRecordData(); // throw? break; } case ESM4::SUB_DALC: // TES5 { //std::cout << "LGTM " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::LGTM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::LightingTemplate::save(ESM4::Writer& writer) const //{ //} //void ESM4::LightingTemplate::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadlgtm.hpp000066400000000000000000000034021445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_LGTM_H #define ESM4_LGTM_H #include #include #include "formid.hpp" #include "lighting.hpp" namespace ESM4 { class Reader; class Writer; typedef std::uint32_t FormId; struct LightingTemplate { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; Lighting mLighting; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LGTM_H openmw-openmw-0.48.0/components/esm4/loadligh.cpp000066400000000000000000000100071445372753700217560ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadligh.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Light::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { // FIXME: TES4 might be uint32 as well, need to check if (isFONV || (esmVer == ESM::VER_094 && subHdr.dataSize == 32)/*FO3*/) { reader.get(mData.time); // uint32 } else reader.get(mData.duration); // float reader.get(mData.radius); reader.get(mData.colour); reader.get(mData.flags); //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 48) { reader.get(mData.falloff); reader.get(mData.FOV); reader.get(mData.nearClip); reader.get(mData.frequency); reader.get(mData.intensityAmplitude); reader.get(mData.movementAmplitude); } else if (subHdr.dataSize == 32) // TES4 { reader.get(mData.falloff); reader.get(mData.FOV); } reader.get(mData.value); reader.get(mData.weight); break; } case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_SNAM: reader.getFormId(mSound); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_FNAM: reader.get(mFade); break; case ESM4::SUB_MODT: case ESM4::SUB_OBND: case ESM4::SUB_VMAD: // Dragonborn only? { //std::cout << "LIGH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::LIGH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Light::save(ESM4::Writer& writer) const //{ //} //void ESM4::Light::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadligh.hpp000066400000000000000000000055601445372753700217730ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LIGH_H #define ESM4_LIGH_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Light { struct Data { std::uint32_t time; // FO/FONV only float duration = -1; std::uint32_t radius; std::uint32_t colour; // RGBA // flags: // 0x00000001 = Dynamic // 0x00000002 = Can be Carried // 0x00000004 = Negative // 0x00000008 = Flicker // 0x00000020 = Off By Default // 0x00000040 = Flicker Slow // 0x00000080 = Pulse // 0x00000100 = Pulse Slow // 0x00000200 = Spot Light // 0x00000400 = Spot Shadow std::int32_t flags; float falloff = 1.f; float FOV = 90; // FIXME: FOV in degrees or radians? float nearClip; // TES5 only float frequency; // TES5 only float intensityAmplitude; // TES5 only float movementAmplitude; // TES5 only std::uint32_t value; // gold float weight; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; float mBoundRadius; FormId mScriptId; FormId mSound; float mFade; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LIGH_H openmw-openmw-0.48.0/components/esm4/loadltex.cpp000066400000000000000000000066361445372753700220240ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadltex.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include //#include // FIXME: debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::LandTexture::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_HNAM: { if (isFONV) { reader.skipSubRecordData(); // FIXME: skip FONV for now break; } if ((reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) && subHdr.dataSize == 2) // FO3 is VER_094 but dataSize 3 { //assert(subHdr.dataSize == 2 && "LTEX unexpected HNAM size"); reader.get(mHavokFriction); reader.get(mHavokRestitution); } else { assert(subHdr.dataSize == 3 && "LTEX unexpected HNAM size"); reader.get(mHavokMaterial); reader.get(mHavokFriction); reader.get(mHavokRestitution); } break; } case ESM4::SUB_ICON: reader.getZString(mTextureFile); break; // Oblivion only? case ESM4::SUB_SNAM: reader.get(mTextureSpecular); break; case ESM4::SUB_GNAM: reader.getFormId(mGrass); break; case ESM4::SUB_TNAM: reader.getFormId(mTexture); break; // TES5 only case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5 only default: throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::LandTexture::save(ESM4::Writer& writer) const //{ //} //void ESM4::LandTexture::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadltex.hpp000066400000000000000000000037511445372753700220240ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LTEX_H #define ESM4_LTEX_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct LandTexture { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::uint8_t mHavokFriction; std::uint8_t mHavokRestitution; std::uint8_t mTextureSpecular; // default 30 FormId mGrass; // ------ TES4 only ----- std::string mTextureFile; std::uint8_t mHavokMaterial; // ------ TES5 only ----- FormId mTexture; FormId mMaterial; // ---------------------- void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LTEX_H openmw-openmw-0.48.0/components/esm4/loadlvlc.cpp000066400000000000000000000101711445372753700217750ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadlvlc.hpp" #include //#include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::LevelledCreature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_TNAM: reader.getFormId(mTemplate); break; case ESM4::SUB_LVLD: reader.get(mChanceNone); break; case ESM4::SUB_LVLF: reader.get(mLvlCreaFlags); break; case ESM4::SUB_LVLO: { static LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) { reader.get(lvlo.level); reader.get(lvlo.item); reader.get(lvlo.count); //std::cout << "LVLC " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item //<< ", count " << lvlo.count << std::endl; // FIXME: seems to happen only once, don't add to mLvlObject // LVLC TesKvatchCreature LVLO lev 1, item 1393819648, count 2 // 0x0001, 0x5314 0000, 0x0002 break; } else throw std::runtime_error("ESM4::LVLC::load - " + mEditorId + " LVLO size error"); } else reader.get(lvlo); reader.adjustFormId(lvlo.item); mLvlObject.push_back(lvlo); break; } case ESM4::SUB_OBND: // FO3 { //std::cout << "LVLC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::LVLC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } bool ESM4::LevelledCreature::calcAllLvlLessThanPlayer() const { if (mHasLvlCreaFlags) return (mLvlCreaFlags & 0x01) != 0; else return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess } bool ESM4::LevelledCreature::calcEachItemInCount() const { if (mHasLvlCreaFlags) return (mLvlCreaFlags & 0x02) != 0; else return true; // FIXME: just a guess } std::int8_t ESM4::LevelledCreature::chanceNone() const { if (mHasLvlCreaFlags) return mChanceNone; else return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess } //void ESM4::LevelledCreature::save(ESM4::Writer& writer) const //{ //} //void ESM4::LevelledCreature::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadlvlc.hpp000066400000000000000000000037051445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LVLC_H #define ESM4_LVLC_H #include #include #include "formid.hpp" #include "inventory.hpp" namespace ESM4 { class Reader; class Writer; struct LevelledCreature { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; FormId mScriptId; FormId mTemplate; std::int8_t mChanceNone; bool mHasLvlCreaFlags; std::uint8_t mLvlCreaFlags; std::vector mLvlObject; bool calcAllLvlLessThanPlayer() const; bool calcEachItemInCount() const; std::int8_t chanceNone() const; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LVLC_H openmw-openmw-0.48.0/components/esm4/loadlvli.cpp000066400000000000000000000104061445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadlvli.hpp" #include #include // FIXME: for debugging #include "reader.hpp" //#include "writer.hpp" void ESM4::LevelledItem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_LVLD: reader.get(mChanceNone); break; case ESM4::SUB_LVLF: reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_LVLO: { static LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) { reader.get(lvlo.level); reader.get(lvlo.item); reader.get(lvlo.count); // std::cout << "LVLI " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item // << ", count " << lvlo.count << std::endl; break; } else throw std::runtime_error("ESM4::LVLI::load - " + mEditorId + " LVLO size error"); } else reader.get(lvlo); reader.adjustFormId(lvlo.item); mLvlObject.push_back(lvlo); break; } case ESM4::SUB_LLCT: case ESM4::SUB_OBND: // FO3/FONV case ESM4::SUB_COED: // FO3/FONV case ESM4::SUB_LVLG: // FO3/FONV { //std::cout << "LVLI " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::LVLI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } // FIXME: testing //if (mHasLvlItemFlags && mChanceNone >= 90) //std::cout << "LVLI " << mEditorId << " chance none " << int(mChanceNone) << std::endl; } bool ESM4::LevelledItem::calcAllLvlLessThanPlayer() const { if (mHasLvlItemFlags) return (mLvlItemFlags & 0x01) != 0; else return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess } bool ESM4::LevelledItem::calcEachItemInCount() const { if (mHasLvlItemFlags) return (mLvlItemFlags & 0x02) != 0; else return mData != 0; } std::int8_t ESM4::LevelledItem::chanceNone() const { if (mHasLvlItemFlags) return mChanceNone; else return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess } bool ESM4::LevelledItem::useAll() const { if (mHasLvlItemFlags) return (mLvlItemFlags & 0x04) != 0; else return false; } //void ESM4::LevelledItem::save(ESM4::Writer& writer) const //{ //} //void ESM4::LevelledItem::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadlvli.hpp000066400000000000000000000037171445372753700220200ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LVLI_H #define ESM4_LVLI_H #include #include #include "formid.hpp" #include "inventory.hpp" // LVLO namespace ESM4 { class Reader; class Writer; struct LevelledItem { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::int8_t mChanceNone; bool mHasLvlItemFlags; std::uint8_t mLvlItemFlags; std::uint8_t mData; std::vector mLvlObject; bool calcAllLvlLessThanPlayer() const; bool calcEachItemInCount() const; bool useAll() const; std::int8_t chanceNone() const; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LVLI_H openmw-openmw-0.48.0/components/esm4/loadlvln.cpp000066400000000000000000000073641445372753700220220ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadlvln.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::LevelledNpc::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; //std::uint32_t esmVer = reader.esmVersion(); // currently unused while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_LLCT: reader.get(mListCount); break; case ESM4::SUB_LVLD: reader.get(mChanceNone); break; case ESM4::SUB_LVLF: reader.get(mLvlActorFlags); break; case ESM4::SUB_LVLO: { static LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) { reader.get(lvlo.level); reader.get(lvlo.item); reader.get(lvlo.count); break; } else throw std::runtime_error("ESM4::LVLN::load - " + mEditorId + " LVLO size error"); } // else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) // { // std::uint32_t level; // reader.get(level); // lvlo.level = static_cast(level); // reader.get(lvlo.item); // std::uint32_t count; // reader.get(count); // lvlo.count = static_cast(count); // } else reader.get(lvlo); reader.adjustFormId(lvlo.item); mLvlObject.push_back(lvlo); break; } case ESM4::SUB_COED: // owner case ESM4::SUB_OBND: // object bounds case ESM4::SUB_MODT: // model texture data { //std::cout << "LVLN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::LVLN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::LevelledNpc::save(ESM4::Writer& writer) const //{ //} //void ESM4::LevelledNpc::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadlvln.hpp000066400000000000000000000040501445372753700220140ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_LVLN_H #define ESM4_LVLN_H #include #include #include "formid.hpp" #include "inventory.hpp" // LVLO namespace ESM4 { class Reader; class Writer; struct LevelledNpc { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; std::int8_t mChanceNone; std::uint8_t mLvlActorFlags; std::uint8_t mListCount; std::vector mLvlObject; inline bool calcAllLvlLessThanPlayer() const { return (mLvlActorFlags & 0x01) != 0; } inline bool calcEachItemInCount() const { return (mLvlActorFlags & 0x02) != 0; } inline std::int8_t chanceNone() const { return mChanceNone; } void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_LVLN_H openmw-openmw-0.48.0/components/esm4/loadmato.cpp000066400000000000000000000043341445372753700220010ustar00rootroot00000000000000/* Copyright (C) 2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadmato.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Material::load(ESM4::Reader& reader) { //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? mFormId = reader.hdr().record.id; mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_DNAM: case ESM4::SUB_DATA: { //std::cout << "MATO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::MATO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Material::save(ESM4::Writer& writer) const //{ //} //void ESM4::Material::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadmato.hpp000066400000000000000000000031751445372753700220100ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_MATO_H #define ESM4_MATO_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Material { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_MATO_H openmw-openmw-0.48.0/components/esm4/loadmisc.cpp000066400000000000000000000056161445372753700220000ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadmisc.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::MiscItem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_MODS: case ESM4::SUB_OBND: case ESM4::SUB_VMAD: case ESM4::SUB_RNAM: // FONV { //std::cout << "MISC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::MISC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::MiscItem::save(ESM4::Writer& writer) const //{ //} //void ESM4::MiscItem::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadmisc.hpp000066400000000000000000000040241445372753700217750ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_MISC_H #define ESM4_MISC_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct MiscItem { #pragma pack(push, 1) struct Data { std::uint32_t value; // gold float weight; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory std::string mMiniIcon; // inventory FormId mPickUpSound; FormId mDropSound; float mBoundRadius; FormId mScriptId; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_MISC_H openmw-openmw-0.48.0/components/esm4/loadmset.cpp000066400000000000000000000076031445372753700220130ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadmset.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::MediaSet::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mFullName); break; case ESM4::SUB_NAM1: reader.get(mSetType); break; case ESM4::SUB_PNAM: reader.get(mEnabled); break; case ESM4::SUB_NAM2: reader.getZString(mSet2); break; case ESM4::SUB_NAM3: reader.getZString(mSet3); break; case ESM4::SUB_NAM4: reader.getZString(mSet4); break; case ESM4::SUB_NAM5: reader.getZString(mSet5); break; case ESM4::SUB_NAM6: reader.getZString(mSet6); break; case ESM4::SUB_NAM7: reader.getZString(mSet7); break; case ESM4::SUB_HNAM: reader.getFormId(mSoundIntro); break; case ESM4::SUB_INAM: reader.getFormId(mSoundOutro); break; case ESM4::SUB_NAM8: reader.get(mLevel8); break; case ESM4::SUB_NAM9: reader.get(mLevel9); break; case ESM4::SUB_NAM0: reader.get(mLevel0); break; case ESM4::SUB_ANAM: reader.get(mLevelA); break; case ESM4::SUB_BNAM: reader.get(mLevelB); break; case ESM4::SUB_CNAM: reader.get(mLevelC); break; case ESM4::SUB_JNAM: reader.get(mBoundaryDayOuter); break; case ESM4::SUB_KNAM: reader.get(mBoundaryDayMiddle); break; case ESM4::SUB_LNAM: reader.get(mBoundaryDayInner); break; case ESM4::SUB_MNAM: reader.get(mBoundaryNightOuter); break; case ESM4::SUB_NNAM: reader.get(mBoundaryNightMiddle); break; case ESM4::SUB_ONAM: reader.get(mBoundaryNightInner); break; case ESM4::SUB_DNAM: reader.get(mTime1); break; case ESM4::SUB_ENAM: reader.get(mTime2); break; case ESM4::SUB_FNAM: reader.get(mTime3); break; case ESM4::SUB_GNAM: reader.get(mTime4); break; case ESM4::SUB_DATA: { //std::cout << "MSET " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::MSET::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::MediaSet::save(ESM4::Writer& writer) const //{ //} //void ESM4::MediaSet::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadmset.hpp000066400000000000000000000061161445372753700220160ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_MSET_H #define ESM4_MSET_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct MediaSet { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; // -1 none, 0 battle, 1 location, 2 dungeon, 3 incidental // Battle - intro (HNAM), loop (NAM2), outro (INAM) // Location - day outer (NAM2), day middle (NAM3), day inner (NAM4), // night outer (NAM5), night middle (NAM6), night inner (NAM7) // Dungeon - intro (HNAM), battle (NAM2), explore (NAM3), suspence (NAM4), outro (INAM) // Incidental - daytime (HNAM), nighttime (INAM) std::int32_t mSetType = -1; // 0x01 day outer, 0x02 day middle, 0x04 day inner // 0x08 night outer, 0x10 night middle, 0x20 night inner std::uint8_t mEnabled; // for location float mBoundaryDayOuter; // % float mBoundaryDayMiddle; // % float mBoundaryDayInner; // % float mBoundaryNightOuter; // % float mBoundaryNightMiddle; // % float mBoundaryNightInner; // % // start at 2 to reduce confusion std::string mSet2; // NAM2 std::string mSet3; // NAM3 std::string mSet4; // NAM4 std::string mSet5; // NAM5 std::string mSet6; // NAM6 std::string mSet7; // NAM7 float mLevel8; // dB float mLevel9; // dB float mLevel0; // dB float mLevelA; // dB float mLevelB; // dB float mLevelC; // dB float mTime1; float mTime2; float mTime3; float mTime4; FormId mSoundIntro; // HNAM FormId mSoundOutro; // INAM void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_MSET_H openmw-openmw-0.48.0/components/esm4/loadmstt.cpp000066400000000000000000000052551445372753700220330ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadmstt.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::MovableStatic::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_SNAM: reader.get(mLoopingSound); break; case ESM4::SUB_DEST: // destruction data case ESM4::SUB_OBND: // object bounds case ESM4::SUB_MODT: // model texture data case ESM4::SUB_DMDL: case ESM4::SUB_DMDT: case ESM4::SUB_DSTD: case ESM4::SUB_DSTF: case ESM4::SUB_MODS: case ESM4::SUB_FULL: case ESM4::SUB_MODB: { //std::cout << "MSTT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::MSTT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::MovableStatic::save(ESM4::Writer& writer) const //{ //} //void ESM4::MovableStatic::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadmstt.hpp000066400000000000000000000032441445372753700220340ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_MSTT_H #define ESM4_MSTT_H #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct MovableStatic { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; std::int8_t mData; FormId mLoopingSound; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_MSTT_H openmw-openmw-0.48.0/components/esm4/loadmusc.cpp000066400000000000000000000052221445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadmusc.hpp" #include //#include // FIXME: for debugging only //#include "formid.hpp" #include "reader.hpp" //#include "writer.hpp" void ESM4::Music::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FNAM: reader.getZString(mMusicFile); //std::cout << "music: " << /*formIdToString(mFormId)*/mEditorId << " " << mMusicFile << std::endl; break; case ESM4::SUB_ANAM: // FONV float (attenuation in db? loop if positive?) case ESM4::SUB_WNAM: // TES5 case ESM4::SUB_PNAM: // TES5 case ESM4::SUB_TNAM: // TES5 { //std::cout << "MUSC " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::MUSC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Music::save(ESM4::Writer& writer) const //{ //} //void ESM4::Music::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadmusc.hpp000066400000000000000000000033001445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_MUSC_H #define ESM4_MUSC_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Music { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mMusicFile; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_MUSC_H openmw-openmw-0.48.0/components/esm4/loadnavi.cpp000066400000000000000000000326241445372753700220010ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadnavi.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include #include // FIXME: debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) { reader.get(minX); reader.get(minY); reader.get(minZ); reader.get(maxX); reader.get(maxY); reader.get(maxZ); std::uint32_t count; reader.get(count); // countTriangle; if (count) { triangles.resize(count); //std::cout << "NVMI island triangles " << std::dec << count << std::endl; // FIXME for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) { reader.get(*it); } } reader.get(count); // countVertex; if (count) { verticies.resize(count); for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) { reader.get(*it); // FIXME: debugging only #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "NVMI vert " << std::dec << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; #endif } } } void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) { std::uint32_t count; reader.get(formId); reader.get(flags); reader.get(x); reader.get(y); reader.get(z); // FIXME: for debugging only #if 0 std::string padding; if (flags == ESM4::FLG_Modified) padding.insert(0, 2, '-'); else if (flags == ESM4::FLG_Unmodified) padding.insert(0, 4, '.'); padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "NVMI formId: 0x" << std::hex << formId << std::endl; std::cout << padding << "NVMI flags: " << std::hex << flags << std::endl; std::cout << padding << "NVMI center: " << std::dec << x << ", " << y << ", " << z << std::endl; #endif reader.get(flagPrefMerges); reader.get(count); // countMerged; if (count) { //std::cout << "NVMI countMerged " << std::dec << count << std::endl; formIdMerged.resize(count); for (std::vector::iterator it = formIdMerged.begin(); it != formIdMerged.end(); ++it) { reader.get(*it); } } reader.get(count); // countPrefMerged; if (count) { //std::cout << "NVMI countPrefMerged " << std::dec << count << std::endl; formIdPrefMerged.resize(count); for (std::vector::iterator it = formIdPrefMerged.begin(); it != formIdPrefMerged.end(); ++it) { reader.get(*it); } } reader.get(count); // countLinkedDoors; if (count) { //std::cout << "NVMI countLinkedDoors " << std::dec << count << std::endl; linkedDoors.resize(count); for (std::vector::iterator it = linkedDoors.begin(); it != linkedDoors.end(); ++it) { reader.get(*it); } } unsigned char island; reader.get(island); if (island) { Navigation::IslandInfo island2; island2.load(reader); islandInfo.push_back(island2); // Maybe don't use a vector for just one entry? } else if (flags == FLG_Island) // FIXME: debug only std::cerr << "nvmi no island but has 0x20 flag" << std::endl; reader.get(locationMarker); reader.get(worldSpaceId); //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) { reader.get(cellGrid.grid.y); // NOTE: reverse order reader.get(cellGrid.grid.x); // FIXME: debugging only #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == ESM4::FLG_Morrowind) std::cout << padding << "NVMI MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; else std::cout << padding << "NVMI SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; #endif } else { reader.get(cellGrid.cellId); #if 0 if (worldSpaceId == 0) // interior std::cout << "NVMI Interior: cellId " << std::hex << cellGrid.cellId << std::endl; else std::cout << "NVMI FormID: cellId " << std::hex << cellGrid.cellId << std::endl; #endif } } // NVPP data seems to be organised this way (total is 0x64 = 100) // // (0) total | 0x1 | formid (index 0) | count | formid's // (1) | count | formid's // (2) | count | formid's // (3) | count | formid's // (4) | count | formid's // (5) | count | formid's // (6) | count | formid's // (7) | count | formid's // (8) | count | formid's // (9) | count | formid's // (10) | 0x1 | formid (index 1) | count | formid's // (11) | count | formid's // (12) | count | formid's // (13) | count | formid's // (14) | count | formid's // (15) | count | formid's // ... // // (88) | count | formid's // (89) | count | formid's // // Here the pattern changes (final count is 0xa = 10) // // (90) | 0x1 | formid (index 9) | count | formid | index // (91) | formid | index // (92) | formid | index // (93) | formid | index // (94) | formid | index // (95) | formid | index // (96) | formid | index // (97) | formid | index // (98) | formid | index // (99) | formid | index // // Note that the index values are not sequential, i.e. the first index value // (i.e. row 90) for Update.esm is 2. // // Also note that there's no list of formid's following the final node (index 9) // // The same 10 formids seem to be used for the indices, but not necessarily // with the same index value (but only Update.esm differs?) // // formid cellid X Y Editor ID other formids in same X,Y S U D D // -------- ------ --- --- --------------------------- ---------------------------- - - - - // 00079bbf 9639 5 -4 WhiterunExterior17 00079bc3 0 6 0 0 // 0010377b 8ed5 6 24 DawnstarWesternMineExterior 1 1 1 1 // 000a3f44 9577 -22 2 RoriksteadEdge 2 9 2 2 // 00100f4b 8ea2 26 25 WinterholdExterior01 00100f4a, 00100f49 3 3 3 3 // 00103120 bc8e 42 -22 (near Riften) 4 2 4 4 // 00105e9a 929d -18 24 SolitudeExterior03 5 0 5 5 // 001030cb 7178 -40 1 SalviusFarmExterior01 (east of Markarth) 6 8 6 6 // 00098776 980b 4 -19 HelgenExterior 000cce3d 7 5 7 7 // 000e88cc 93de -9 14 (near Morthal) 0010519e, 0010519d, 000e88d2 8 7 8 8 // 000b87df b51d 33 5 WindhelmAttackStart05 9 4 9 9 // void ESM4::Navigation::load(ESM4::Reader& reader) { //mFormId = reader.hdr().record.id; //mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: // seems to be unused? { if (!reader.getZString(mEditorId)) throw std::runtime_error ("NAVI EDID data read error"); break; } case ESM4::SUB_NVPP: { std::uint32_t total; std::uint32_t count; reader.get(total); if (!total) { reader.get(count); // throw away break; } total -= 10; // HACK std::uint32_t node; for (std::uint32_t i = 0; i < total; ++i) { std::vector preferredPaths; reader.get(count); if (count == 1) { reader.get(node); reader.get(count); } if (count) { preferredPaths.resize(count); for (std::vector::iterator it = preferredPaths.begin(); it != preferredPaths.end(); ++it) { reader.get(*it); } } mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); #if 0 std::cout << "node " << std::hex << node // FIXME: debugging only << ", count " << count << ", i " << std::dec << i << std::endl; #endif } reader.get(count); assert(count == 1 && "expected separator"); reader.get(node); // HACK std::vector preferredPaths; mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); // empty #if 0 std::cout << "node " << std::hex << node // FIXME: debugging only << ", count " << 0 << std::endl; #endif reader.get(count); // HACK assert(count == 10 && "expected 0xa"); std::uint32_t index; for (std::uint32_t i = 0; i < count; ++i) { reader.get(node); reader.get(index); #if 0 std::cout << "node " << std::hex << node // FIXME: debugging only << ", index " << index << ", i " << std::dec << total+i << std::endl; #endif //std::pair::iterator, bool> res = mPathIndexMap.insert(std::make_pair(node, index)); // FIXME: this throws if more than one file is being loaded //if (!res.second) //throw std::runtime_error ("node already exists in the preferred path index map"); } break; } case ESM4::SUB_NVER: { std::uint32_t version; // always the same? (0x0c) reader.get(version); // TODO: store this or use it for merging? //std::cout << "NAVI version " << std::dec << version << std::endl; break; } case ESM4::SUB_NVMI: // multiple { if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) { reader.skipSubRecordData(); // FIXME: FO3/FONV have different form of NavMeshInfo break; } //std::cout << "\nNVMI start" << std::endl; NavMeshInfo nvmi; nvmi.load(reader); mNavMeshInfo.push_back (nvmi); break; } case ESM4::SUB_NVSI: // from Dawnguard onwards case ESM4::SUB_NVCI: // FO3 { reader.skipSubRecordData(); // FIXME: break; } default: { throw std::runtime_error("ESM4::NAVI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } } //void ESM4::Navigation::save(ESM4::Writer& writer) const //{ //} //void ESM4::Navigation::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadnavi.hpp000066400000000000000000000061711445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_NAVI_H #define ESM4_NAVI_H #include #include #include #include "common.hpp" // CellGrid, Vertex namespace ESM4 { class Reader; class Writer; struct Navigation { #pragma pack(push,1) struct DoorRef { std::uint32_t unknown; FormId formId; }; struct Triangle { std::uint16_t vertexIndex0; std::uint16_t vertexIndex1; std::uint16_t vertexIndex2; }; #pragma pack(pop) struct IslandInfo { float minX; float minY; float minZ; float maxX; float maxY; float maxZ; std::vector triangles; std::vector verticies; void load(ESM4::Reader& reader); }; enum Flags // NVMI island flags (not certain) { FLG_Island = 0x00000020, FLG_Modified = 0x00000000, // not island FLG_Unmodified = 0x00000040 // not island }; struct NavMeshInfo { FormId formId; std::uint32_t flags; // center point of the navmesh float x; float y; float z; std::uint32_t flagPrefMerges; std::vector formIdMerged; std::vector formIdPrefMerged; std::vector linkedDoors; std::vector islandInfo; std::uint32_t locationMarker; FormId worldSpaceId; CellGrid cellGrid; void load(ESM4::Reader& reader); }; std::string mEditorId; std::vector mNavMeshInfo; std::vector > > mPreferredPaths; std::map mPathIndexMap; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_NAVI_H openmw-openmw-0.48.0/components/esm4/loadnavm.cpp000066400000000000000000000210441445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadnavm.hpp" #include #include #include #include // FIXME: debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) { //std::cout << "start: divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; //"this 0x" << this << std::endl; // FIXME std::uint32_t count; reader.get(unknownNVER); reader.get(unknownLCTN); reader.get(worldSpaceId); //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) { // ^ // Y | X Y Index // | 0,0 0 // 1 |23 0,1 1 // 0 |01 1,0 2 // +--- 1,1 3 // 01 -> // X // // e.g. Dagonfel X:13,14,15,16 Y:43,44,45,46 (Morrowind X:7 Y:22) // // Skywind: -4,-3 -2,-1 0,1 2,3 4,5 6,7 // Morrowind: -2 -1 0 1 2 3 // // Formula seems to be floor(Skywind coord / 2) // reader.get(cellGrid.grid.y); // NOTE: reverse order reader.get(cellGrid.grid.x); // FIXME: debugging only #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == ESM4::FLG_Morrowind) std::cout << padding << "NVNM MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; else std::cout << padding << "NVNM SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; #endif } else { reader.get(cellGrid.cellId); #if 0 std::string padding; // FIXME padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == 0) // interior std::cout << padding << "NVNM Interior: cellId " << std::hex << cellGrid.cellId << std::endl; else std::cout << padding << "NVNM FormID: cellId " << std::hex << cellGrid.cellId << std::endl; #endif } reader.get(count); // numVerticies if (count) { verticies.resize(count); for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) { reader.get(*it); // FIXME: debugging only #if 0 //if (reader.hdr().record.id == 0x2004ecc) // FIXME std::cout << "nvnm vert " << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; #endif } } reader.get(count); // numTriangles; if (count) { triangles.resize(count); for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) { reader.get(*it); } } reader.get(count); // numExtConn; if (count) { extConns.resize(count); for (std::vector::iterator it = extConns.begin(); it != extConns.end(); ++it) { reader.get(*it); // FIXME: debugging only #if 0 std::cout << "nvnm ext 0x" << std::hex << (*it).navMesh << std::endl; #endif } } reader.get(count); // numDoorTriangles; if (count) { doorTriangles.resize(count); for (std::vector::iterator it = doorTriangles.begin(); it != doorTriangles.end(); ++it) { reader.get(*it); } } reader.get(count); // numCoverTriangles; if (count) { coverTriangles.resize(count); for (std::vector::iterator it = coverTriangles.begin(); it != coverTriangles.end(); ++it) { reader.get(*it); } } // abs((maxX - minX) / divisor) = Max X Distance reader.get(divisor); // FIXME: latest over-writes old reader.get(maxXDist); // FIXME: update with formula reader.get(maxYDist); reader.get(minX); // FIXME: use std::min reader.get(minY); reader.get(minZ); reader.get(maxX); reader.get(maxY); reader.get(maxZ); // FIXME: should check remaining size here // there are divisor^2 segments, each segment is a vector of triangle indices for (unsigned int i = 0; i < divisor*divisor; ++i) { reader.get(count); // NOTE: count may be zero std::vector indices; indices.resize(count); for (std::vector::iterator it = indices.begin(); it != indices.end(); ++it) { reader.get(*it); } triSegments.push_back(indices); } assert(triSegments.size() == divisor*divisor && "tiangle segments size is not the square of divisor"); #if 0 if (triSegments.size() != divisor*divisor) std::cout << "divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; "this 0x" << this << std::endl; #endif } void ESM4::NavMesh::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; mFlags = reader.hdr().record.flags; //std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME std::uint32_t subSize = 0; // for XXXX sub record // FIXME: debugging only #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "NAVM flags 0x" << std::hex << reader.hdr().record.flags << std::endl; std::cout << padding << "NAVM id 0x" << std::hex << reader.hdr().record.id << std::endl; #endif while (reader.getSubRecordHeader()) { switch (reader.subRecordHeader().typeId) { case ESM4::SUB_NVNM: { NVNMstruct nvnm; nvnm.load(reader); mData.push_back(nvnm); // FIXME try swap break; } case ESM4::SUB_ONAM: case ESM4::SUB_PNAM: case ESM4::SUB_NNAM: { if (subSize) { reader.skipSubRecordData(subSize); // special post XXXX reader.updateRecordRead(subSize); // WARNING: manual update subSize = 0; } else //const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip break; } case ESM4::SUB_XXXX: { reader.get(subSize); break; } case ESM4::SUB_NVER: // FO3 case ESM4::SUB_DATA: // FO3 case ESM4::SUB_NVVX: // FO3 case ESM4::SUB_NVTR: // FO3 case ESM4::SUB_NVCA: // FO3 case ESM4::SUB_NVDP: // FO3 case ESM4::SUB_NVGD: // FO3 case ESM4::SUB_NVEX: // FO3 case ESM4::SUB_EDID: // FO3 { reader.skipSubRecordData(); // FIXME: break; } default: throw std::runtime_error("ESM4::NAVM::load - Unknown subrecord " + ESM::printName(reader.subRecordHeader().typeId)); } } //std::cout << "num nvnm " << std::dec << mData.size() << std::endl; // FIXME } //void ESM4::NavMesh::save(ESM4::Writer& writer) const //{ //} void ESM4::NavMesh::blank() { } openmw-openmw-0.48.0/components/esm4/loadnavm.hpp000066400000000000000000000063441445372753700220120ustar00rootroot00000000000000/* Copyright (C) 2015, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_NAVM_H #define ESM4_NAVM_H #include #include #include "common.hpp" // CellGrid, Vertex namespace ESM4 { class Reader; class Writer; struct NavMesh { #pragma pack(push,1) struct Triangle { std::uint16_t vertexIndex0; std::uint16_t vertexIndex1; std::uint16_t vertexIndex2; std::uint16_t edge0; std::uint16_t edge1; std::uint16_t edge2; std::uint16_t coverMarker; std::uint16_t coverFlags; }; struct ExtConnection { std::uint32_t unknown; FormId navMesh; std::uint16_t triangleIndex; }; struct DoorTriangle { std::uint16_t triangleIndex; std::uint32_t unknown; FormId doorRef; }; #pragma pack(pop) struct NVNMstruct { std::uint32_t unknownNVER; std::uint32_t unknownLCTN; FormId worldSpaceId; CellGrid cellGrid; std::vector verticies; std::vector triangles; std::vector extConns; std::vector doorTriangles; std::vector coverTriangles; std::uint32_t divisor; float maxXDist; float maxYDist; float minX; float minY; float minZ; float maxX; float maxY; float maxZ; // there are divisor^2 segments, each segment is a vector of triangle indices std::vector > triSegments; void load(ESM4::Reader& esm); }; std::vector mData; // Up to 4 skywind cells in one Morrowind cell FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; void blank(); }; } #endif // ESM4_NAVM_H openmw-openmw-0.48.0/components/esm4/loadnote.cpp000066400000000000000000000047641445372753700220150ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadnote.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::Note::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_DATA: case ESM4::SUB_MODB: case ESM4::SUB_ONAM: case ESM4::SUB_SNAM: case ESM4::SUB_TNAM: case ESM4::SUB_XNAM: case ESM4::SUB_OBND: { //std::cout << "NOTE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::NOTE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Note::save(ESM4::Writer& writer) const //{ //} //void ESM4::Note::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadnote.hpp000066400000000000000000000032551445372753700220140ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_NOTE_H #define ESM4_NOTE_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Note { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_NOTE_H openmw-openmw-0.48.0/components/esm4/loadnpc.cpp000066400000000000000000000261671445372753700216310ustar00rootroot00000000000000/* Copyright (C) 2016-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadnpc.hpp" #include #include #include // getline #include // NOTE: for testing only #include // NOTE: for testing only #include // NOTE: for testing only //#include //#include #include #include "formid.hpp" // NOTE: for testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::Npc::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); mIsTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; //mIsTES5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; // WARN: FO3 is also VER_094 while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; // not for TES5, see Race case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_CNTO: { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); break; } case ESM4::SUB_SPLO: { FormId id; reader.getFormId(id); mSpell.push_back(id); break; } case ESM4::SUB_PKID: { FormId id; reader.getFormId(id); mAIPackages.push_back(id); break; } case ESM4::SUB_SNAM: { reader.get(mFaction); reader.adjustFormId(mFaction.faction); break; } case ESM4::SUB_RNAM: reader.getFormId(mRace); break; case ESM4::SUB_CNAM: reader.getFormId(mClass); break; case ESM4::SUB_HNAM: reader.getFormId(mHair); break; // not for TES5 case ESM4::SUB_ENAM: reader.getFormId(mEyes); break; // case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; // case ESM4::SUB_AIDT: { if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) { reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip break; } reader.get(mAIData); // TES4 break; } case ESM4::SUB_ACBS: { //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) if (subHdr.dataSize == 24) reader.get(mBaseConfig); else reader.get(&mBaseConfig, 16); // TES4 break; } case ESM4::SUB_DATA: { if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) { if (subHdr.dataSize != 0) // FIXME FO3 reader.skipSubRecordData(); break; // zero length } reader.get(&mData, 33); // FIXME: check packing break; } case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; case ESM4::SUB_CSDI: reader.getFormId(mSound); break; case ESM4::SUB_CSDC: reader.get(mSoundChance); break; case ESM4::SUB_WNAM: { if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) reader.get(mWornArmor); else reader.get(mFootWeight); break; } case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_KFFZ: { std::string str; if (!reader.getZString(str)) throw std::runtime_error ("NPC_ KFFZ data read error"); // Seems to be only below 3, and only happens 3 times while loading TES4: // Forward_SheogorathWithCane.kf // TurnLeft_SheogorathWithCane.kf // TurnRight_SheogorathWithCane.kf std::stringstream ss(str); std::string file; while (std::getline(ss, file, '\0')) // split the strings mKf.push_back(file); break; } case ESM4::SUB_LNAM: reader.get(mHairLength); break; case ESM4::SUB_HCLR: { reader.get(mHairColour.red); reader.get(mHairColour.green); reader.get(mHairColour.blue); reader.get(mHairColour.custom); break; } case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; case ESM4::SUB_FGGS: { mSymShapeModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymShapeModeCoefficients.at(i)); break; } case ESM4::SUB_FGGA: { mAsymShapeModeCoefficients.resize(30); for (std::size_t i = 0; i < 30; ++i) reader.get(mAsymShapeModeCoefficients.at(i)); break; } case ESM4::SUB_FGTS: { mSymTextureModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymTextureModeCoefficients.at(i)); break; } case ESM4::SUB_FNAM: { reader.get(mFgRace); //std::cout << "race " << mEditorId << " " << mRace << std::endl; // FIXME //std::cout << "fg race " << mEditorId << " " << mFgRace << std::endl; // FIXME break; } case ESM4::SUB_PNAM: // FO3/FONV/TES5 { FormId headPart; reader.getFormId(headPart); mHeadParts.push_back(headPart); break; } case ESM4::SUB_HCLF: // TES5 hair colour { reader.getFormId(mHairColourId); break; } case ESM4::SUB_COCT: // TES5 { std::uint32_t count; reader.get(count); break; } case ESM4::SUB_DOFT: reader.getFormId(mDefaultOutfit); break; case ESM4::SUB_SOFT: reader.getFormId(mSleepOutfit); break; case ESM4::SUB_DPLT: reader.getFormId(mDefaultPkg); break; // AI package list case ESM4::SUB_DEST: case ESM4::SUB_DSTD: case ESM4::SUB_DSTF: { #if 1 boost::scoped_array dataBuf(new unsigned char[subHdr.dataSize]); reader.get(&dataBuf[0], subHdr.dataSize); std::ostringstream ss; ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; for (std::size_t i = 0; i < subHdr.dataSize; ++i) { if (dataBuf[i] > 64 && dataBuf[i] < 91) // looks like printable ascii char ss << (char)(dataBuf[i]) << " "; else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(dataBuf[i]); if ((i & 0x000f) == 0xf) // wrap around ss << "\n"; else if (i < (size_t)(subHdr.dataSize-1)) // quiesce gcc ss << " "; } std::cout << ss.str() << std::endl; #else //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); #endif break; } case ESM4::SUB_NAM6: // height mult case ESM4::SUB_NAM7: // weight mult case ESM4::SUB_ATKR: case ESM4::SUB_CRIF: case ESM4::SUB_CSDT: case ESM4::SUB_DNAM: case ESM4::SUB_ECOR: case ESM4::SUB_ANAM: case ESM4::SUB_ATKD: case ESM4::SUB_ATKE: case ESM4::SUB_FTST: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_NAM5: case ESM4::SUB_NAM8: case ESM4::SUB_NAM9: case ESM4::SUB_NAMA: case ESM4::SUB_OBND: case ESM4::SUB_PRKR: case ESM4::SUB_PRKZ: case ESM4::SUB_QNAM: case ESM4::SUB_SPCT: case ESM4::SUB_TIAS: case ESM4::SUB_TINC: case ESM4::SUB_TINI: case ESM4::SUB_TINV: case ESM4::SUB_VMAD: case ESM4::SUB_VTCK: case ESM4::SUB_GNAM: case ESM4::SUB_SHRT: case ESM4::SUB_SPOR: case ESM4::SUB_EAMT: // FO3 case ESM4::SUB_NAM4: // FO3 case ESM4::SUB_COED: // FO3 { //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::NPC_::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Npc::save(ESM4::Writer& writer) const //{ //} //void ESM4::Npc::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadnpc.hpp000066400000000000000000000207221445372753700216250ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_NPC__H #define ESM4_NPC__H #include #include #include #include "actor.hpp" #include "inventory.hpp" namespace ESM4 { class Reader; class Writer; struct Npc { enum ACBS_TES4 { TES4_Female = 0x000001, TES4_Essential = 0x000002, TES4_Respawn = 0x000008, TES4_AutoCalcStats = 0x000010, TES4_PCLevelOffset = 0x000080, TES4_NoLowLevelProc = 0x000200, TES4_NoRumors = 0x002000, TES4_Summonable = 0x004000, TES4_NoPersuasion = 0x008000, // different meaning to crea TES4_CanCorpseCheck = 0x100000 // opposite of crea }; enum ACBS_FO3 { FO3_Female = 0x00000001, FO3_Essential = 0x00000002, FO3_PresetFace = 0x00000004, // Is CharGen Face Preset FO3_Respawn = 0x00000008, FO3_AutoCalcStats = 0x00000010, FO3_PCLevelMult = 0x00000080, FO3_UseTemplate = 0x00000100, FO3_NoLowLevelProc = 0x00000200, FO3_NoBloodSpray = 0x00000800, FO3_NoBloodDecal = 0x00001000, FO3_NoVATSMelee = 0x00100000, FO3_AnyRace = 0x00400000, FO3_AutoCalcServ = 0x00800000, FO3_NoKnockdown = 0x04000000, FO3_NotPushable = 0x08000000, FO3_NoRotateHead = 0x40000000 }; enum ACBS_TES5 { TES5_Female = 0x00000001, TES5_Essential = 0x00000002, TES5_PresetFace = 0x00000004, // Is CharGen Face Preset TES5_Respawn = 0x00000008, TES5_AutoCalcStats = 0x00000010, TES5_Unique = 0x00000020, TES5_NoStealth = 0x00000040, // Doesn't affect stealth meter TES5_PCLevelMult = 0x00000080, //TES5_Unknown = 0x00000100, // Audio template? TES5_Protected = 0x00000800, TES5_Summonable = 0x00004000, TES5_NoBleeding = 0x00010000, TES5_Owned = 0x00040000, // owned/follow? (Horses, Atronachs, NOT Shadowmere) TES5_GenderAnim = 0x00080000, // Opposite Gender Anims TES5_SimpleActor = 0x00100000, TES5_LoopedScript = 0x00200000, // AAvenicci, Arcadia, Silvia, Afflicted, TortureVictims TES5_LoopedAudio = 0x10000000, // AAvenicci, Arcadia, Silvia, DA02 Cultists, Afflicted, TortureVictims TES5_IsGhost = 0x20000000, // Ghost/non-interactable (Ghosts, Nocturnal) TES5_Invulnerable = 0x80000000 }; enum Template_Flags { TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, // voice type, death item; Sounds tab; Animation tab; Character Gen tabs TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, // speed, bleedout, class TES5_UseFactions = 0x0004, // both factions and assigned crime faction TES5_UseSpellList = 0x0008, // both spells and perks TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and // gift filter TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; // rest of tab controlled by Def Pack List TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item // -- but not death item TES5_UseScript = 0x0200, TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, // events, and data) TES5_UseKeywords = 0x1000 }; #pragma pack(push, 1) struct SkillValues { std::uint8_t armorer; std::uint8_t athletics; std::uint8_t blade; std::uint8_t block; std::uint8_t blunt; std::uint8_t handToHand; std::uint8_t heavyArmor; std::uint8_t alchemy; std::uint8_t alteration; std::uint8_t conjuration; std::uint8_t destruction; std::uint8_t illusion; std::uint8_t mysticism; std::uint8_t restoration; std::uint8_t acrobatics; std::uint8_t lightArmor; std::uint8_t marksman; std::uint8_t mercantile; std::uint8_t security; std::uint8_t sneak; std::uint8_t speechcraft; }; struct HairColour { std::uint8_t red; std::uint8_t green; std::uint8_t blue; std::uint8_t custom; // alpha? }; struct Data { SkillValues skills; std::uint32_t health; AttributeValues attribs; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details bool mIsTES4; bool mIsFONV; std::string mEditorId; std::string mFullName; std::string mModel; // skeleton model (can be a marker in FO3/FONV) FormId mRace; FormId mClass; FormId mHair; // not for TES5, see mHeadParts FormId mEyes; std::vector mHeadParts; // FO3/FONV/TES5 float mHairLength; HairColour mHairColour; // TES4/FO3/FONV FormId mHairColourId; // TES5 FormId mDeathItem; std::vector mSpell; FormId mScriptId; AIData mAIData; std::vector mAIPackages; // seems to be in priority order, 0 = highest priority ActorBaseConfig mBaseConfig; // union ActorFaction mFaction; Data mData; FormId mCombatStyle; FormId mSoundBase; FormId mSound; std::uint8_t mSoundChance; float mFootWeight; float mBoundRadius; std::vector mKf; // filenames only, get directory path from mModel std::vector mInventory; FormId mBaseTemplate; // FO3/FONV/TES5 FormId mWornArmor; // TES5 only? FormId mDefaultOutfit; // TES5 OTFT FormId mSleepOutfit; // TES5 OTFT FormId mDefaultPkg; std::vector mSymShapeModeCoefficients; // size 0 or 50 std::vector mAsymShapeModeCoefficients; // size 0 or 30 std::vector mSymTextureModeCoefficients; // size 0 or 50 std::int16_t mFgRace; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_NPC__H openmw-openmw-0.48.0/components/esm4/loadotft.cpp000066400000000000000000000046751445372753700220250ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadotft.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Outfit::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_INAM: { std::size_t numObj = subHdr.dataSize / sizeof(FormId); for (std::size_t i = 0; i < numObj; ++i) { FormId formId; reader.getFormId(formId); mInventory.push_back(formId); } break; } default: //std::cout << "OTFT " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); throw std::runtime_error("ESM4::OTFT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Outfit::save(ESM4::Writer& writer) const //{ //} //void ESM4::Outfit::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadotft.hpp000066400000000000000000000032161445372753700220200ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_OTFT_H #define ESM4_OTFT_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Outfit { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::vector mInventory; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_OTFT_H openmw-openmw-0.48.0/components/esm4/loadpack.cpp000066400000000000000000000144361445372753700217630ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadpack.hpp" #include #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::AIPackage::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_PKDT: { if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) { //std::cout << "skip fallout" << mEditorId << std::endl; // FIXME reader.get(mData.flags); mData.type = 0; // FIXME } else if (subHdr.dataSize != sizeof(mData)) reader.skipSubRecordData(); // FIXME: FO3 else reader.get(mData); break; } case ESM4::SUB_PSDT: //reader.get(mSchedule); break; { if (subHdr.dataSize != sizeof(mSchedule)) reader.skipSubRecordData(); // FIXME: else reader.get(mSchedule); // TES4 break; } case ESM4::SUB_PLDT: { if (subHdr.dataSize != sizeof(mLocation)) reader.skipSubRecordData(); // FIXME: else { reader.get(mLocation); // TES4 if (mLocation.type != 5) reader.adjustFormId(mLocation.location); } break; } case ESM4::SUB_PTDT: { if (subHdr.dataSize != sizeof(mTarget)) reader.skipSubRecordData(); // FIXME: FO3 else { reader.get(mTarget); // TES4 if (mLocation.type != 2) reader.adjustFormId(mTarget.target); } break; } case ESM4::SUB_CTDA: { if (subHdr.dataSize != sizeof(CTDA)) { reader.skipSubRecordData(); // FIXME: FO3 break; } static CTDA condition; reader.get(condition); // FIXME: how to "unadjust" if not FormId? //adjustFormId(condition.param1); //adjustFormId(condition.param2); mConditions.push_back(condition); break; } case ESM4::SUB_CTDT: // always 20 for TES4 case ESM4::SUB_TNAM: // FO3 case ESM4::SUB_INAM: // FO3 case ESM4::SUB_CNAM: // FO3 case ESM4::SUB_SCHR: // FO3 case ESM4::SUB_POBA: // FO3 case ESM4::SUB_POCA: // FO3 case ESM4::SUB_POEA: // FO3 case ESM4::SUB_SCTX: // FO3 case ESM4::SUB_SCDA: // FO3 case ESM4::SUB_SCRO: // FO3 case ESM4::SUB_IDLA: // FO3 case ESM4::SUB_IDLC: // FO3 case ESM4::SUB_IDLF: // FO3 case ESM4::SUB_IDLT: // FO3 case ESM4::SUB_PKDD: // FO3 case ESM4::SUB_PKD2: // FO3 case ESM4::SUB_PKPT: // FO3 case ESM4::SUB_PKED: // FO3 case ESM4::SUB_PKE2: // FO3 case ESM4::SUB_PKAM: // FO3 case ESM4::SUB_PUID: // FO3 case ESM4::SUB_PKW3: // FO3 case ESM4::SUB_PTD2: // FO3 case ESM4::SUB_PLD2: // FO3 case ESM4::SUB_PKFD: // FO3 case ESM4::SUB_SLSD: // FO3 case ESM4::SUB_SCVR: // FO3 case ESM4::SUB_SCRV: // FO3 case ESM4::SUB_IDLB: // FO3 case ESM4::SUB_ANAM: // TES5 case ESM4::SUB_BNAM: // TES5 case ESM4::SUB_FNAM: // TES5 case ESM4::SUB_PNAM: // TES5 case ESM4::SUB_QNAM: // TES5 case ESM4::SUB_UNAM: // TES5 case ESM4::SUB_XNAM: // TES5 case ESM4::SUB_PDTO: // TES5 case ESM4::SUB_PTDA: // TES5 case ESM4::SUB_PFOR: // TES5 case ESM4::SUB_PFO2: // TES5 case ESM4::SUB_PRCB: // TES5 case ESM4::SUB_PKCU: // TES5 case ESM4::SUB_PKC2: // TES5 case ESM4::SUB_CITC: // TES5 case ESM4::SUB_CIS1: // TES5 case ESM4::SUB_CIS2: // TES5 case ESM4::SUB_VMAD: // TES5 case ESM4::SUB_TPIC: // TES5 { //std::cout << "PACK " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::PACK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::AIPackage::save(ESM4::Writer& writer) const //{ //} //void ESM4::AIPackage::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadpack.hpp000066400000000000000000000063411445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_PACK_H #define ESM4_PACK_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct AIPackage { #pragma pack(push, 1) struct PKDT // data { std::uint32_t flags; std::int32_t type; }; struct PSDT // schedule { std::uint8_t month; // Any = 0xff std::uint8_t dayOfWeek; // Any = 0xff std::uint8_t date; // Any = 0 std::uint8_t time; // Any = 0xff std::uint32_t duration; }; struct PLDT // location { std::int32_t type = 0xff; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj type, 0xff = no location data FormId location; // uint32_t if type = 5 std::int32_t radius; }; struct PTDT // target { std::int32_t type = 0xff; // 0 = specific ref, 1 = obj id, 2 = obj type, 0xff = no target data FormId target; // uint32_t if type = 2 std::int32_t distance; }; // NOTE: param1/param2 can be FormId or number, but assume FormId so that adjustFormId // can be called struct CTDA { std::uint8_t condition; std::uint8_t unknown1; // probably padding std::uint8_t unknown2; // probably padding std::uint8_t unknown3; // probably padding float compValue; std::int32_t fnIndex; FormId param1; FormId param2; std::uint32_t unknown4; // probably padding }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; PKDT mData; PSDT mSchedule; PLDT mLocation; PTDT mTarget; std::vector mConditions; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_PACK_H openmw-openmw-0.48.0/components/esm4/loadpgrd.cpp000066400000000000000000000136331445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadpgrd.hpp" #include //#include // FIXME: for debugging only //#include // FIXME: for debugging only //#include // FIXME for debugging only #include "formid.hpp" // FIXME: for mEditorId workaround #include "reader.hpp" //#include "writer.hpp" void ESM4::Pathgrid::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_PGRP: { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); if (numNodes != std::size_t(mData)) // keep gcc quiet throw std::runtime_error("ESM4::PGRD::load numNodes mismatch"); mNodes.resize(numNodes); for (std::size_t i = 0; i < numNodes; ++i) { reader.get(mNodes.at(i)); if (int(mNodes.at(i).z) % 2 == 0) mNodes.at(i).priority = 0; else mNodes.at(i).priority = 1; } break; } case ESM4::SUB_PGRR: { static PGRR link; for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet { for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) { link.startNode = std::int16_t(i); reader.get(link.endNode); if (link.endNode == -1) continue; // ICMarketDistrictTheBestDefenseBasement doesn't have a PGRR sub-record // CELL formId 00049E2A // PGRD formId 000304B7 //if (mFormId == 0x0001C2C8) //std::cout << link.startNode << "," << link.endNode << std::endl; mLinks.push_back(link); } } break; } case ESM4::SUB_PGRI: { std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); mForeign.resize(numForeign); for (std::size_t i = 0; i < numForeign; ++i) { reader.get(mForeign.at(i)); // mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) } break; } case ESM4::SUB_PGRL: { static PGRL objLink; reader.get(objLink.object); // object linkedNode std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); objLink.linkedNodes.resize(numNodes); for (std::size_t i = 0; i < numNodes; ++i) reader.get(objLink.linkedNodes.at(i)); mObjects.push_back(objLink); break; } case ESM4::SUB_PGAG: { #if 0 boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); reader.get(&mDataBuf[0], subHdr.dataSize); std::ostringstream ss; ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; for (std::size_t i = 0; i < subHdr.dataSize; ++i) { //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char //ss << (char)(mDataBuf[i]) << " "; //else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); if ((i & 0x000f) == 0xf) // wrap around ss << "\n"; else if (i < subHdr.dataSize-1) ss << " "; } std::cout << ss.str() << std::endl; #else //std::cout << "PGRD " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); #endif break; } default: throw std::runtime_error("ESM4::PGRD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Pathgrid::save(ESM4::Writer& writer) const //{ //} //void ESM4::Pathgrid::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadpgrd.hpp000066400000000000000000000050771445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2020 - 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_PGRD_H #define ESM4_PGRD_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Pathgrid { #pragma pack(push, 1) struct PGRP { float x; float y; float z; std::uint8_t numLinks; std::uint8_t priority; // probably padding, repurposing std::uint16_t unknown; // probably padding }; struct PGRR { std::int16_t startNode; std::int16_t endNode; }; struct PGRI { std::int32_t localNode; float x; // foreign float y; // foreign float z; // foreign }; #pragma pack(pop) struct PGRL { FormId object; std::vector linkedNodes; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; // FIXME: no such record for PGRD, but keep here to avoid extra work for now std::int16_t mData; // number of nodes std::vector mNodes; std::vector mLinks; std::vector mForeign; std::vector mObjects; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_PGRD_H openmw-openmw-0.48.0/components/esm4/loadpgre.cpp000066400000000000000000000063551445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadpgre.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::PlacedGrenade::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_NAME: case ESM4::SUB_XEZN: case ESM4::SUB_XRGD: case ESM4::SUB_XRGB: case ESM4::SUB_XPRD: case ESM4::SUB_XPPA: case ESM4::SUB_INAM: case ESM4::SUB_TNAM: case ESM4::SUB_XOWN: case ESM4::SUB_XRNK: case ESM4::SUB_XCNT: case ESM4::SUB_XRDS: case ESM4::SUB_XHLP: case ESM4::SUB_XPWR: case ESM4::SUB_XDCR: case ESM4::SUB_XLKR: case ESM4::SUB_XCLP: case ESM4::SUB_XAPD: case ESM4::SUB_XAPR: case ESM4::SUB_XATO: case ESM4::SUB_XESP: case ESM4::SUB_XEMI: case ESM4::SUB_XMBR: case ESM4::SUB_XIBS: case ESM4::SUB_XSCL: case ESM4::SUB_DATA: { //std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); //throw std::runtime_error("ESM4::PGRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::PlacedGrenade::save(ESM4::Writer& writer) const //{ //} //void ESM4::PlacedGrenade::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadpgre.hpp000066400000000000000000000032501445372753700217770ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_PGRE_H #define ESM4_PGRE_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct PlacedGrenade { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_PGRE_H openmw-openmw-0.48.0/components/esm4/loadpwat.cpp000066400000000000000000000047711445372753700220210ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadpwat.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::PlaceableWater::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_OBND: case ESM4::SUB_MODL: case ESM4::SUB_DNAM: { //std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); //throw std::runtime_error("ESM4::PWAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::PlaceableWater::save(ESM4::Writer& writer) const //{ //} //void ESM4::PlaceableWater::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadpwat.hpp000066400000000000000000000032511445372753700220160ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_PWAT_H #define ESM4_PWAT_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct PlaceableWater { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_PWAT_H openmw-openmw-0.48.0/components/esm4/loadqust.cpp000066400000000000000000000142061445372753700220340ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadqust.hpp" #include #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::Quest::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getZString(mQuestName); break; case ESM4::SUB_ICON: reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) case ESM4::SUB_DATA: { if (subHdr.dataSize == 2) // TES4 { reader.get(&mData, 2); mData.questDelay = 0.f; // unused in TES4 but keep it clean //if ((mData.flags & Flag_StartGameEnabled) != 0) //std::cout << "start quest " << mEditorId << std::endl; } else reader.get(mData); // FO3 break; } case ESM4::SUB_SCRI: reader.get(mQuestScript); break; case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 { TargetCondition cond; reader.get(&cond, 24); cond.reference = 0; // unused in TES4 but keep it clean mTargetConditions.push_back(cond); } else if (subHdr.dataSize == 28) { TargetCondition cond; reader.get(cond); // FO3/FONV if (cond.reference) reader.adjustFormId(cond.reference); mTargetConditions.push_back(cond); } else { // one record with size 20: EDID GenericSupMutBehemoth reader.skipSubRecordData(); // FIXME } // FIXME: support TES5 break; } case ESM4::SUB_SCHR: reader.get(mScript.scriptHeader); break; case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; case ESM4::SUB_INDX: case ESM4::SUB_QSDT: case ESM4::SUB_CNAM: case ESM4::SUB_QSTA: case ESM4::SUB_NNAM: // FO3 case ESM4::SUB_QOBJ: // FO3 case ESM4::SUB_NAM0: // FO3 case ESM4::SUB_ANAM: // TES5 case ESM4::SUB_DNAM: // TES5 case ESM4::SUB_ENAM: // TES5 case ESM4::SUB_FNAM: // TES5 case ESM4::SUB_NEXT: // TES5 case ESM4::SUB_ALCA: // TES5 case ESM4::SUB_ALCL: // TES5 case ESM4::SUB_ALCO: // TES5 case ESM4::SUB_ALDN: // TES5 case ESM4::SUB_ALEA: // TES5 case ESM4::SUB_ALED: // TES5 case ESM4::SUB_ALEQ: // TES5 case ESM4::SUB_ALFA: // TES5 case ESM4::SUB_ALFC: // TES5 case ESM4::SUB_ALFD: // TES5 case ESM4::SUB_ALFE: // TES5 case ESM4::SUB_ALFI: // TES5 case ESM4::SUB_ALFL: // TES5 case ESM4::SUB_ALFR: // TES5 case ESM4::SUB_ALID: // TES5 case ESM4::SUB_ALLS: // TES5 case ESM4::SUB_ALNA: // TES5 case ESM4::SUB_ALNT: // TES5 case ESM4::SUB_ALPC: // TES5 case ESM4::SUB_ALRT: // TES5 case ESM4::SUB_ALSP: // TES5 case ESM4::SUB_ALST: // TES5 case ESM4::SUB_ALUA: // TES5 case ESM4::SUB_CIS2: // TES5 case ESM4::SUB_CNTO: // TES5 case ESM4::SUB_COCT: // TES5 case ESM4::SUB_ECOR: // TES5 case ESM4::SUB_FLTR: // TES5 case ESM4::SUB_KNAM: // TES5 case ESM4::SUB_KSIZ: // TES5 case ESM4::SUB_KWDA: // TES5 case ESM4::SUB_QNAM: // TES5 case ESM4::SUB_QTGL: // TES5 case ESM4::SUB_SPOR: // TES5 case ESM4::SUB_VMAD: // TES5 case ESM4::SUB_VTCK: // TES5 { //std::cout << "QUST " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::QUST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //if (mEditorId == "DAConversations") //std::cout << mEditorId << std::endl; } //void ESM4::Quest::save(ESM4::Writer& writer) const //{ //} //void ESM4::Quest::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadqust.hpp000066400000000000000000000044511445372753700220420ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_QUST_H #define ESM4_QUST_H #include #include "formid.hpp" #include "script.hpp" // TargetCondition, ScriptDefinition namespace ESM4 { class Reader; class Writer; #pragma pack(push, 1) struct QuestData { std::uint8_t flags; // Quest_Flags std::uint8_t priority; std::uint16_t padding; // FO3 float questDelay; // FO3 }; #pragma pack(pop) struct Quest { // NOTE: these values are for TES4 enum Quest_Flags { Flag_StartGameEnabled = 0x01, Flag_AllowRepeatConvTopic = 0x04, Flag_AllowRepeatStages = 0x08 }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mQuestName; std::string mFileName; // texture file FormId mQuestScript; QuestData mData; std::vector mTargetConditions; ScriptDefinition mScript; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_QUST_H openmw-openmw-0.48.0/components/esm4/loadrace.cpp000066400000000000000000000635101445372753700217540ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadrace.hpp" #include #include #include // FIXME: for debugging only #include // FIXME: for debugging only #include "formid.hpp" #include "reader.hpp" //#include "writer.hpp" void ESM4::Race::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; bool isFO3 = false; bool isMale = false; int curr_part = -1; // 0 = head, 1 = body, 2 = egt, 3 = hkx std::uint32_t currentIndex = 0xffffffff; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); //std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; switch (subHdr.typeId) { case ESM4::SUB_EDID: { reader.getZString(mEditorId); // TES4 // Sheogorath 0x0005308E // GoldenSaint 0x0001208F // DarkSeducer 0x0001208E // VampireRace 0x00000019 // Dremora 0x00038010 // Argonian 0x00023FE9 // Nord 0x000224FD // Breton 0x000224FC // WoodElf 0x000223C8 // Khajiit 0x000223C7 // DarkElf 0x000191C1 // Orc 0x000191C0 // HighElf 0x00019204 // Redguard 0x00000D43 // Imperial 0x00000907 break; } case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DESC: { if (subHdr.dataSize == 1) // FO3? { reader.skipSubRecordData(); break; } reader.getLocalizedString(mDesc); break; } case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) { FormId magic; reader.getFormId(magic); mBonusSpells.push_back(magic); // std::cout << "RACE " << printName(subHdr.typeId) << " " << formIdToString(magic) << std::endl; break; } case ESM4::SUB_DATA: // ?? different length for TES5 { // DATA:size 128 // 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 // 9a 99 99 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f // 48 89 10 00 00 00 40 41 00 00 00 00 00 00 48 43 // 00 00 48 43 00 00 80 3f 9a 99 19 3f 00 00 00 40 // 01 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00 // ff ff ff ff 00 00 00 00 00 00 20 41 00 00 a0 40 // 00 00 a0 40 00 00 80 42 ff ff ff ff 00 00 00 00 // 00 00 00 00 9a 99 99 3e 00 00 a0 40 02 00 00 00 #if 0 unsigned char mDataBuf[256/*bufSize*/]; reader.get(&mDataBuf[0], subHdr.dataSize); std::ostringstream ss; ss << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; for (unsigned int i = 0; i < subHdr.dataSize; ++i) { //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) //ss << (char)(mDataBuf[i]) << " "; //else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); if ((i & 0x000f) == 0xf) ss << "\n"; else if (i < 256/*bufSize*/-1) ss << " "; } std::cout << ss.str() << std::endl; #else if (subHdr.dataSize == 36) // TES4/FO3/FONV { if (!isTES4 && !isFONV && !mIsTES5) isFO3 = true; std::uint8_t skill; std::uint8_t bonus; for (unsigned int i = 0; i < 8; ++i) { reader.get(skill); reader.get(bonus); mSkillBonus[static_cast(skill)] = bonus; } reader.get(mHeightMale); reader.get(mHeightFemale); reader.get(mWeightMale); reader.get(mWeightFemale); reader.get(mRaceFlags); } else if (subHdr.dataSize >= 128 && subHdr.dataSize <= 164) // TES5 { mIsTES5 = true; std::uint8_t skill; std::uint8_t bonus; for (unsigned int i = 0; i < 7; ++i) { reader.get(skill); reader.get(bonus); mSkillBonus[static_cast(skill)] = bonus; } std::uint16_t unknown; reader.get(unknown); reader.get(mHeightMale); reader.get(mHeightFemale); reader.get(mWeightMale); reader.get(mWeightFemale); reader.get(mRaceFlags); // FIXME float dummy; reader.get(dummy); // starting health reader.get(dummy); // starting magicka reader.get(dummy); // starting stamina reader.get(dummy); // base carry weight reader.get(dummy); // base mass reader.get(dummy); // accleration rate reader.get(dummy); // decleration rate uint32_t dummy2; reader.get(dummy2); // size reader.get(dummy2); // head biped object reader.get(dummy2); // hair biped object reader.get(dummy); // injured health % (0.f..1.f) reader.get(dummy2); // shield biped object reader.get(dummy); // health regen reader.get(dummy); // magicka regen reader.get(dummy); // stamina regen reader.get(dummy); // unarmed damage reader.get(dummy); // unarmed reach reader.get(dummy2); // body biped object reader.get(dummy); // aim angle tolerance reader.get(dummy2); // unknown reader.get(dummy); // angular accleration rate reader.get(dummy); // angular tolerance reader.get(dummy2); // flags if (subHdr.dataSize > 128) { reader.get(dummy2); // unknown 1 reader.get(dummy2); // unknown 2 reader.get(dummy2); // unknown 3 reader.get(dummy2); // unknown 4 reader.get(dummy2); // unknown 5 reader.get(dummy2); // unknown 6 reader.get(dummy2); // unknown 7 reader.get(dummy2); // unknown 8 reader.get(dummy2); // unknown 9 } } else { reader.skipSubRecordData(); std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; } #endif break; } case ESM4::SUB_DNAM: { reader.get(mDefaultHair[0]); // male reader.get(mDefaultHair[1]); // female break; } case ESM4::SUB_CNAM: // Only in TES4? // CNAM SNAM VNAM // Sheogorath 0x0 0000 98 2b 10011000 00101011 // Golden Saint 0x3 0011 26 46 00100110 01000110 // Dark Seducer 0xC 1100 df 55 11011111 01010101 // Vampire Race 0x0 0000 77 44 01110111 10001000 // Dremora 0x7 0111 bf 32 10111111 00110010 // Argonian 0x0 0000 dc 3c 11011100 00111100 // Nord 0x5 0101 b6 03 10110110 00000011 // Breton 0x5 0101 48 1d 01001000 00011101 00000000 00000907 (Imperial) // Wood Elf 0xD 1101 2e 4a 00101110 01001010 00019204 00019204 (HighElf) // khajiit 0x5 0101 54 5b 01010100 01011011 00023FE9 00023FE9 (Argonian) // Dark Elf 0x0 0000 72 54 01110010 01010100 00019204 00019204 (HighElf) // Orc 0xC 1100 74 09 01110100 00001001 000224FD 000224FD (Nord) // High Elf 0xF 1111 e6 21 11100110 00100001 // Redguard 0xD 1101 a9 61 10101001 01100001 // Imperial 0xD 1101 8e 35 10001110 00110101 { reader.skipSubRecordData(); break; } case ESM4::SUB_PNAM: reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f case ESM4::SUB_UNAM: reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f case ESM4::SUB_ATTR: // Only in TES4? { if (subHdr.dataSize == 2) // FO3? { reader.skipSubRecordData(); break; } reader.get(mAttribMale.strength); reader.get(mAttribMale.intelligence); reader.get(mAttribMale.willpower); reader.get(mAttribMale.agility); reader.get(mAttribMale.speed); reader.get(mAttribMale.endurance); reader.get(mAttribMale.personality); reader.get(mAttribMale.luck); reader.get(mAttribFemale.strength); reader.get(mAttribFemale.intelligence); reader.get(mAttribFemale.willpower); reader.get(mAttribFemale.agility); reader.get(mAttribFemale.speed); reader.get(mAttribFemale.endurance); reader.get(mAttribFemale.personality); reader.get(mAttribFemale.luck); break; } // [0..9]-> ICON // NAM0 -> INDX -> MODL --+ // ^ -> MODB | // | | // +-------------+ // case ESM4::SUB_NAM0: // start marker head data /* 1 */ { curr_part = 0; // head part if (isFO3 || isFONV) { mHeadParts.resize(8); mHeadPartsFemale.resize(8); } else if (isTES4) mHeadParts.resize(9); // assumed based on Construction Set else { mHeadPartIdsMale.resize(5); mHeadPartIdsFemale.resize(5); } currentIndex = 0xffffffff; break; } case ESM4::SUB_INDX: { reader.get(currentIndex); // FIXME: below check is rather useless //if (headpart) //{ // if (currentIndex > 8) // throw std::runtime_error("ESM4::RACE::load - too many head part " + currentIndex); //} //else // bodypart //{ // if (currentIndex > 4) // throw std::runtime_error("ESM4::RACE::load - too many body part " + currentIndex); //} break; } case ESM4::SUB_MODL: { if (curr_part == 0) // head part { if (isMale || isTES4) reader.getZString(mHeadParts[currentIndex].mesh); else reader.getZString(mHeadPartsFemale[currentIndex].mesh); // TODO: check TES4 // TES5 keeps head part formid in mHeadPartIdsMale and mHeadPartIdsFemale } else if (curr_part == 1) // body part { if (isMale) reader.getZString(mBodyPartsMale[currentIndex].mesh); else reader.getZString(mBodyPartsFemale[currentIndex].mesh); // TES5 seems to have no body parts at all, instead keep EGT models } else if (curr_part == 2) // egt { //std::cout << mEditorId << " egt " << currentIndex << std::endl; // FIXME reader.skipSubRecordData(); // FIXME TES5 egt } else { //std::cout << mEditorId << " hkx " << currentIndex << std::endl; // FIXME reader.skipSubRecordData(); // FIXME TES5 hkx } break; } case ESM4::SUB_MODB: reader.skipSubRecordData(); break; // always 0x0000? case ESM4::SUB_ICON: { if (curr_part == 0) // head part { if (isMale || isTES4) reader.getZString(mHeadParts[currentIndex].texture); else reader.getZString(mHeadPartsFemale[currentIndex].texture); // TODO: check TES4 } else if (curr_part == 1) // body part { if (isMale) reader.getZString(mBodyPartsMale[currentIndex].texture); else reader.getZString(mBodyPartsFemale[currentIndex].texture); } else reader.skipSubRecordData(); // FIXME TES5 break; } // case ESM4::SUB_NAM1: // start marker body data /* 4 */ { if (isFO3 || isFONV) { curr_part = 1; // body part mBodyPartsMale.resize(4); mBodyPartsFemale.resize(4); } else if (isTES4) { curr_part = 1; // body part mBodyPartsMale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail mBodyPartsFemale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail } else // TES5 curr_part = 2; // for TES5 NAM1 indicates the start of EGT model if (isTES4) currentIndex = 4; // FIXME: argonian tail mesh without preceeding INDX else currentIndex = 0xffffffff; break; } case ESM4::SUB_MNAM: isMale = true; break; /* 2, 5, 7 */ case ESM4::SUB_FNAM: isMale = false; break; /* 3, 6, 8 */ // case ESM4::SUB_HNAM: { std::size_t numHairChoices = subHdr.dataSize / sizeof(FormId); mHairChoices.resize(numHairChoices); for (unsigned int i = 0; i < numHairChoices; ++i) reader.get(mHairChoices.at(i)); break; } case ESM4::SUB_ENAM: { std::size_t numEyeChoices = subHdr.dataSize / sizeof(FormId); mEyeChoices.resize(numEyeChoices); for (unsigned int i = 0; i < numEyeChoices; ++i) reader.get(mEyeChoices.at(i)); break; } case ESM4::SUB_FGGS: { if (isMale || isTES4) { mSymShapeModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymShapeModeCoefficients.at(i)); } else { mSymShapeModeCoeffFemale.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymShapeModeCoeffFemale.at(i)); } break; } case ESM4::SUB_FGGA: { if (isMale || isTES4) { mAsymShapeModeCoefficients.resize(30); for (std::size_t i = 0; i < 30; ++i) reader.get(mAsymShapeModeCoefficients.at(i)); } else { mAsymShapeModeCoeffFemale.resize(30); for (std::size_t i = 0; i < 30; ++i) reader.get(mAsymShapeModeCoeffFemale.at(i)); } break; } case ESM4::SUB_FGTS: { if (isMale || isTES4) { mSymTextureModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymTextureModeCoefficients.at(i)); } else { mSymTextureModeCoeffFemale.resize(50); for (std::size_t i = 0; i < 50; ++i) reader.get(mSymTextureModeCoeffFemale.at(i)); } break; } // case ESM4::SUB_SNAM: //skipping...2 // only in TES4? { reader.skipSubRecordData(); break; } case ESM4::SUB_XNAM: { FormId race; std::int32_t adjustment; reader.get(race); reader.get(adjustment); mDisposition[race] = adjustment; break; } case ESM4::SUB_VNAM: { if (subHdr.dataSize == 8) // TES4 { reader.get(mVNAM[0]); // For TES4 seems to be 2 race formids reader.get(mVNAM[1]); } else if (subHdr.dataSize == 4) // TES5 { // equipment type flags meant to be uint32 ??? GLOB reference? shows up in // SCRO in sript records and CTDA in INFO records uint32_t dummy; reader.get(dummy); } else { reader.skipSubRecordData(); std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; } break; } // case ESM4::SUB_ANAM: // TES5 { if (isMale) reader.getZString(mModelMale); else reader.getZString(mModelFemale); break; } case ESM4::SUB_KSIZ: reader.get(mNumKeywords); break; case ESM4::SUB_KWDA: { std::uint32_t formid; for (unsigned int i = 0; i < mNumKeywords; ++i) reader.getFormId(formid); break; } // case ESM4::SUB_WNAM: // ARMO FormId { reader.getFormId(mSkin); //std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME break; } case ESM4::SUB_BODT: // body template { reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); reader.get(mBodyTemplate.unknown1); // probably padding reader.get(mBodyTemplate.unknown2); // probably padding reader.get(mBodyTemplate.unknown3); // probably padding reader.get(mBodyTemplate.type); break; } case ESM4::SUB_BOD2: // TES5 { reader.get(mBodyTemplate.bodyPart); mBodyTemplate.flags = 0; mBodyTemplate.unknown1 = 0; // probably padding mBodyTemplate.unknown2 = 0; // probably padding mBodyTemplate.unknown3 = 0; // probably padding reader.get(mBodyTemplate.type); break; } case ESM4::SUB_HEAD: // TES5 { FormId formId; reader.getFormId(formId); // FIXME: no order? head, mouth, eyes, brow, hair if (isMale) mHeadPartIdsMale[currentIndex] = formId; else mHeadPartIdsFemale[currentIndex] = formId; //std::cout << mEditorId << (isMale ? " male head " : " female head ") //<< formIdToString(formId) << " " << currentIndex << std::endl; // FIXME break; } case ESM4::SUB_NAM3: // start of hkx model { curr_part = 3; // for TES5 NAM3 indicates the start of hkx model break; } // Not sure for what this is used - maybe to indicate which slots are in use? e.g.: // // ManakinRace HEAD // ManakinRace Hair // ManakinRace BODY // ManakinRace Hands // ManakinRace Forearms // ManakinRace Amulet // ManakinRace Ring // ManakinRace Feet // ManakinRace Calves // ManakinRace SHIELD // ManakinRace // ManakinRace LongHair // ManakinRace Circlet // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace DecapitateHead // ManakinRace Decapitate // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace // ManakinRace FX0 case ESM4::SUB_NAME: // TES5 biped object names (x32) { std::string name; reader.getZString(name); //std::cout << mEditorId << " " << name << std::endl; break; } case ESM4::SUB_MTNM: // movement type case ESM4::SUB_ATKD: // attack data case ESM4::SUB_ATKE: // attach event case ESM4::SUB_GNAM: // body part data case ESM4::SUB_NAM4: // material type case ESM4::SUB_NAM5: // unarmed impact? case ESM4::SUB_LNAM: // close loot sound case ESM4::SUB_QNAM: // equipment slot formid case ESM4::SUB_HCLF: // default hair colour case ESM4::SUB_UNES: // unarmed equipment slot formid case ESM4::SUB_TINC: case ESM4::SUB_TIND: case ESM4::SUB_TINI: case ESM4::SUB_TINL: case ESM4::SUB_TINP: case ESM4::SUB_TINT: case ESM4::SUB_TINV: case ESM4::SUB_TIRS: case ESM4::SUB_PHWT: case ESM4::SUB_AHCF: case ESM4::SUB_AHCM: case ESM4::SUB_MPAI: case ESM4::SUB_MPAV: case ESM4::SUB_DFTF: case ESM4::SUB_DFTM: case ESM4::SUB_FLMV: case ESM4::SUB_FTSF: case ESM4::SUB_FTSM: case ESM4::SUB_MTYP: case ESM4::SUB_NAM7: case ESM4::SUB_NAM8: case ESM4::SUB_PHTN: case ESM4::SUB_RNAM: case ESM4::SUB_RNMV: case ESM4::SUB_RPRF: case ESM4::SUB_RPRM: case ESM4::SUB_SNMV: case ESM4::SUB_SPCT: case ESM4::SUB_SPED: case ESM4::SUB_SWMV: case ESM4::SUB_WKMV: // case ESM4::SUB_YNAM: // FO3 case ESM4::SUB_NAM2: // FO3 case ESM4::SUB_VTCK: // FO3 case ESM4::SUB_MODT: // FO3 case ESM4::SUB_MODD: // FO3 case ESM4::SUB_ONAM: // FO3 { //std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::RACE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Race::save(ESM4::Writer& writer) const //{ //} //void ESM4::Race::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadrace.hpp000066400000000000000000000133471445372753700217640ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_RACE #define ESM4_RACE #include #include #include #include #include "formid.hpp" #include "actor.hpp" // AttributeValues, BodyTemplate namespace ESM4 { class Reader; class Writer; typedef std::uint32_t FormId; struct Race { #pragma pack(push, 1) struct Data { std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed }; #pragma pack(pop) enum SkillIndex { Skill_Armorer = 0x0C, Skill_Athletics = 0x0D, Skill_Blade = 0x0E, Skill_Block = 0x0F, Skill_Blunt = 0x10, Skill_HandToHand = 0x11, Skill_HeavyArmor = 0x12, Skill_Alchemy = 0x13, Skill_Alteration = 0x14, Skill_Conjuration = 0x15, Skill_Destruction = 0x16, Skill_Illusion = 0x17, Skill_Mysticism = 0x18, Skill_Restoration = 0x19, Skill_Acrobatics = 0x1A, Skill_LightArmor = 0x1B, Skill_Marksman = 0x1C, Skill_Mercantile = 0x1D, Skill_Security = 0x1E, Skill_Sneak = 0x1F, Skill_Speechcraft = 0x20, Skill_None = 0xFF, Skill_Unknown = 0x00 }; enum HeadPartIndex // TES4 { Head = 0, EarMale = 1, EarFemale = 2, Mouth = 3, TeethLower = 4, TeethUpper = 5, Tongue = 6, EyeLeft = 7, // no texture EyeRight = 8, // no texture NumHeadParts = 9 }; enum BodyPartIndex // TES4 { UpperBody = 0, LowerBody = 1, Hands = 2, Feet = 3, Tail = 4, NumBodyParts = 5 }; struct BodyPart { std::string mesh; // can be empty for arms, hands, etc std::string texture; // can be empty e.g. eye left, eye right }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details bool mIsTES5; std::string mEditorId; std::string mFullName; std::string mDesc; std::string mModelMale; // TES5 skeleton (in TES4 skeleton is found in npc_) std::string mModelFemale; // TES5 skeleton (in TES4 skeleton is found in npc_) AttributeValues mAttribMale; AttributeValues mAttribFemale; std::map mSkillBonus; // DATA float mHeightMale = 1.0f; float mHeightFemale = 1.0f; float mWeightMale = 1.0f; float mWeightFemale = 1.0f; std::uint32_t mRaceFlags; // 0x0001 = playable? std::vector mHeadParts; // see HeadPartIndex std::vector mHeadPartsFemale; // see HeadPartIndex std::vector mBodyPartsMale; // see BodyPartIndex std::vector mBodyPartsFemale; // see BodyPartIndex std::vector mEyeChoices; // texture only std::vector mHairChoices; // not for TES5 float mFaceGenMainClamp; float mFaceGenFaceClamp; std::vector mSymShapeModeCoefficients; // should be 50 std::vector mSymShapeModeCoeffFemale; // should be 50 std::vector mAsymShapeModeCoefficients; // should be 30 std::vector mAsymShapeModeCoeffFemale; // should be 30 std::vector mSymTextureModeCoefficients; // should be 50 std::vector mSymTextureModeCoeffFemale; // should be 50 std::map mDisposition; // race adjustments std::vector mBonusSpells; // race ability/power std::array mVNAM; // don't know what these are; 1 or 2 RACE FormIds std::array mDefaultHair; // male/female (HAIR FormId for TES4) std::uint32_t mNumKeywords; FormId mSkin; // TES5 BodyTemplate mBodyTemplate; // TES5 // FIXME: there's no fixed order? // head, mouth, eyes, brow, hair std::vector mHeadPartIdsMale; // TES5 std::vector mHeadPartIdsFemale; // TES5 void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_RACE openmw-openmw-0.48.0/components/esm4/loadrefr.cpp000066400000000000000000000313321445372753700217750ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadrefr.hpp" #include #include // FIXME: debug only #include "reader.hpp" //#include "writer.hpp" void ESM4::Reference::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mParent = reader.currCell(); // NOTE: only for persistent refs? // TODO: Let the engine apply this? Saved games? //mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; FormId mid; FormId sid; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_NAME: { reader.getFormId(mBaseObj); #if 0 if (mFlags & ESM4::Rec_Disabled) std::cout << "REFR disable at start " << formIdToString(mFormId) << " baseobj " << formIdToString(mBaseObj) << " " << (mEditorId.empty() ? "" : mEditorId) << std::endl; // FIXME #endif //if (mBaseObj == 0x20) // e.g. FO3 mFormId == 0x0007E90F //if (mBaseObj == 0x17) //std::cout << mEditorId << std::endl; break; } case ESM4::SUB_DATA: reader.get(mPlacement); break; case ESM4::SUB_XSCL: reader.get(mScale); break; case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; case ESM4::SUB_XRNK: reader.get(mFactionRank); break; case ESM4::SUB_XESP: { reader.get(mEsp); reader.adjustFormId(mEsp.parent); //std::cout << "REFR parent: " << formIdToString(mEsp.parent) << " ref " << formIdToString(mFormId) //<< ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME break; } case ESM4::SUB_XTEL: { reader.getFormId(mDoor.destDoor); reader.get(mDoor.destPos); if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) reader.get(mDoor.flags); // not in Obvlivion //std::cout << "REFR dest door: " << formIdToString(mDoor.destDoor) << std::endl;// FIXME break; } case ESM4::SUB_XSED: { // 1 or 4 bytes if (subHdr.dataSize == 1) { uint8_t data; reader.get(data); //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; break; } else if (subHdr.dataSize == 4) { uint32_t data; reader.get(data); //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; break; } //std::cout << "REFR XSED dataSize: " << subHdr.dataSize << std::endl;// FIXME reader.skipSubRecordData(); break; } case ESM4::SUB_XLOD: { // 12 bytes if (subHdr.dataSize == 12) { float data, data2, data3; reader.get(data); reader.get(data2); reader.get(data3); //bool hasVisibleWhenDistantFlag = (mFlags & 0x00008000) != 0; // currently unused // some are trees, e.g. 000E03B6, mBaseObj 00022F32, persistent, visible when distant // some are doors, e.g. 000270F7, mBaseObj 000CD338, persistent, initially disabled // (this particular one is an Oblivion Gate) //std::cout << "REFR XLOD " << std::hex << (int)data << " " << (int)data2 << " " << (int)data3 << std::endl; break; } //std::cout << "REFR XLOD dataSize: " << subHdr.dataSize << std::endl;// FIXME reader.skipSubRecordData(); break; } case ESM4::SUB_XACT: { if (subHdr.dataSize == 4) { uint32_t data; reader.get(data); //std::cout << "REFR XACT " << std::hex << (int)data << std::endl; break; } //std::cout << "REFR XACT dataSize: " << subHdr.dataSize << std::endl;// FIXME reader.skipSubRecordData(); break; } case ESM4::SUB_XRTM: // formId { // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" // e.g. some are doors (prob. quest related) // MS94OblivionGateRef XRTM : 00097C88 // MQ11SkingradGate XRTM : 00064583 // MQ11ChorrolGate XRTM : 00188770 // MQ11LeyawiinGate XRTM : 0018AD7C // MQ11AnvilGate XRTM : 0018D452 // MQ11BravilGate XRTM : 0018AE1B // e.g. some are XMarkerHeading // XRTM : 000A4DD7 in OblivionRDCavesMiddleA05 (maybe spawn points?) FormId marker; reader.getFormId(marker); //std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME break; } case ESM4::SUB_TNAM: //reader.get(mMapMarker); break; { if (subHdr.dataSize != sizeof(mMapMarker)) //reader.skipSubRecordData(); // FIXME: FO3 reader.getFormId(mid); else reader.get(mMapMarker); // TES4 break; } case ESM4::SUB_XMRK: mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" case ESM4::SUB_FNAM: { //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } case ESM4::SUB_XTRG: // formId { reader.getFormId(mTargetRef); //std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME break; } case ESM4::SUB_CNAM: reader.getFormId(mAudioLocation); break; // FONV case ESM4::SUB_XRDO: // FO3 { reader.get(mRadio.rangeRadius); reader.get(mRadio.broadcastRange); reader.get(mRadio.staticPercentage); reader.getFormId(mRadio.posReference); break; } case ESM4::SUB_SCRO: // FO3 { reader.getFormId(sid); //if (mFormId == 0x0016b74B) //std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME break; } case ESM4::SUB_XLOC: { mIsLocked = true; std::int8_t dummy; // FIXME: very poor code reader.get(mLockLevel); reader.get(dummy); reader.get(dummy); reader.get(dummy); reader.getFormId(mKey); reader.get(dummy); // flag? reader.get(dummy); reader.get(dummy); reader.get(dummy); if (subHdr.dataSize == 16) reader.skipSubRecordData(4); // Oblivion (sometimes), flag? else if (subHdr.dataSize == 20) // Skyrim, FO3 reader.skipSubRecordData(8); // flag? break; } // lighting case ESM4::SUB_LNAM: // lighting template formId case ESM4::SUB_XLIG: // struct, FOV, fade, etc case ESM4::SUB_XEMI: // LIGH formId case ESM4::SUB_XRDS: // Radius or Radiance case ESM4::SUB_XRGB: case ESM4::SUB_XRGD: // tangent data? case ESM4::SUB_XALP: // alpha cutoff // case ESM4::SUB_XPCI: // formId case ESM4::SUB_XLCM: case ESM4::SUB_XCNT: case ESM4::SUB_ONAM: case ESM4::SUB_VMAD: case ESM4::SUB_XPRM: case ESM4::SUB_INAM: case ESM4::SUB_PDTO: case ESM4::SUB_SCHR: case ESM4::SUB_SCTX: case ESM4::SUB_XAPD: case ESM4::SUB_XAPR: case ESM4::SUB_XCVL: case ESM4::SUB_XCZA: case ESM4::SUB_XCZC: case ESM4::SUB_XEZN: case ESM4::SUB_XFVC: case ESM4::SUB_XHTW: case ESM4::SUB_XIS2: case ESM4::SUB_XLCN: case ESM4::SUB_XLIB: case ESM4::SUB_XLKR: case ESM4::SUB_XLRM: case ESM4::SUB_XLRT: case ESM4::SUB_XLTW: case ESM4::SUB_XMBO: case ESM4::SUB_XMBP: case ESM4::SUB_XMBR: case ESM4::SUB_XNDP: case ESM4::SUB_XOCP: case ESM4::SUB_XPOD: case ESM4::SUB_XPPA: case ESM4::SUB_XPRD: case ESM4::SUB_XPWR: case ESM4::SUB_XRMR: case ESM4::SUB_XSPC: case ESM4::SUB_XTNM: case ESM4::SUB_XTRI: case ESM4::SUB_XWCN: case ESM4::SUB_XWCU: case ESM4::SUB_XATR: // Dawnguard only? case ESM4::SUB_XHLT: // Unofficial Oblivion Patch case ESM4::SUB_XCHG: // thievery.exp case ESM4::SUB_XHLP: // FO3 case ESM4::SUB_XAMT: // FO3 case ESM4::SUB_XAMC: // FO3 case ESM4::SUB_XRAD: // FO3 case ESM4::SUB_XIBS: // FO3 case ESM4::SUB_XORD: // FO3 case ESM4::SUB_XCLP: // FO3 case ESM4::SUB_SCDA: // FO3 case ESM4::SUB_RCLR: // FO3 case ESM4::SUB_BNAM: // FONV case ESM4::SUB_MMRK: // FONV case ESM4::SUB_MNAM: // FONV case ESM4::SUB_NNAM: // FONV case ESM4::SUB_XATO: // FONV case ESM4::SUB_SCRV: // FONV case ESM4::SUB_SCVR: // FONV case ESM4::SUB_SLSD: // FONV case ESM4::SUB_XSRF: // FONV case ESM4::SUB_XSRD: // FONV case ESM4::SUB_WMI1: // FONV case ESM4::SUB_XLRL: // Unofficial Skyrim Patch { //if (mFormId == 0x0007e90f) // XPRM XPOD //if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::REFR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } //if (mFormId == 0x0016B74B) // base is TACT vCasinoUltraLuxeRadio in cell ULCasino //std::cout << "REFR SCRO " << formIdToString(sid) << std::endl; } //void ESM4::Reference::save(ESM4::Writer& writer) const //{ //} void ESM4::Reference::blank() { } openmw-openmw-0.48.0/components/esm4/loadrefr.hpp000066400000000000000000000066101445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_REFR_H #define ESM4_REFR_H #include #include "reference.hpp" // FormId, Placement, EnableParent namespace ESM4 { class Reader; class Writer; enum MapMarkerType { Map_None = 0x00, // ? Map_Camp = 0x01, Map_Cave = 0x02, Map_City = 0x03, Map_ElvenRuin = 0x04, Map_FortRuin = 0x05, Map_Mine = 0x06, Map_Landmark = 0x07, Map_Tavern = 0x08, Map_Settlement = 0x09, Map_DaedricShrine = 0x0A, Map_OblivionGate = 0x0B, Map_Unknown = 0x0C // ? (door icon) }; struct TeleportDest { FormId destDoor; Placement destPos; std::uint32_t flags; // 0x01 no alarm (only in TES5) }; struct RadioStationData { float rangeRadius; // 0 radius, 1 everywhere, 2 worldspace and linked int, 3 linked int, 4 current cell only std::uint32_t broadcastRange; float staticPercentage; FormId posReference; // only used if broadcastRange == 0 }; struct Reference { FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence // NOTE: for exterior cells it will be the dummy cell FormId FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; FormId mBaseObj; Placement mPlacement; float mScale = 1.0f; FormId mOwner; FormId mGlobal; std::int32_t mFactionRank = -1; bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) bool mIsMapMarker; std::uint16_t mMapMarker; EnableParent mEsp; std::uint32_t mCount = 1; // only if > 1 FormId mAudioLocation; RadioStationData mRadio; TeleportDest mDoor; bool mIsLocked; std::int8_t mLockLevel; FormId mKey; FormId mTargetRef; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; void blank(); }; } #endif // ESM4_REFR_H openmw-openmw-0.48.0/components/esm4/loadregn.cpp000066400000000000000000000130751445372753700217760ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadregn.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include //#include // FIXME: debug only //#include "formid.hpp" #include "reader.hpp" //#include "writer.hpp" void ESM4::Region::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_RCLR: reader.get(mColour); break; case ESM4::SUB_WNAM: reader.getFormId(mWorldId); break; case ESM4::SUB_ICON: reader.getZString(mShader); break; case ESM4::SUB_RPLI: reader.get(mEdgeFalloff); break; case ESM4::SUB_RPLD: { mRPLD.resize(subHdr.dataSize/sizeof(std::uint32_t)); for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) { reader.get(*it); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "RPLD: 0x" << std::hex << *it << std::endl; #endif } break; } case ESM4::SUB_RDAT: reader.get(mData); break; case ESM4::SUB_RDMP: { assert(mData.type == RDAT_Map && "REGN unexpected data type"); reader.getLocalizedString(mMapName); break; } // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) // FONV none case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon { #if 0 int dummy; reader.get(dummy); std::cout << "REGN " << mEditorId << " " << dummy << std::endl; #else reader.skipSubRecordData(); #endif break; } case ESM4::SUB_RDMO: // not seen in FO3/FONV? { //std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } case ESM4::SUB_RDSD: // Possibly the same as RDSA { //assert(mData.type == RDAT_Map && "REGN unexpected data type"); if (mData.type != RDAT_Sound) throw std::runtime_error("ESM4::REGN::load - unexpected data type " + ESM::printName(subHdr.typeId)); std::size_t numSounds = subHdr.dataSize / sizeof(RegionSound); mSounds.resize(numSounds); for (std::size_t i = 0; i < numSounds; ++i) reader.get(mSounds.at(i)); break; } case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId case ESM4::SUB_RDSA: case ESM4::SUB_RDWT: // formId case ESM4::SUB_RDOT: // formId case ESM4::SUB_RDID: // FONV case ESM4::SUB_RDSB: // FONV case ESM4::SUB_RDSI: // FONV case ESM4::SUB_NVMI: // TES5 { //RDAT skipping... following is a map //RDMP skipping... map name // //RDAT skipping... following is weather //RDWT skipping... weather data // //RDAT skipping... following is sound //RDMD skipping... unknown, maybe music data // //RDSD skipping... unknown, maybe sound data // //RDAT skipping... following is grass //RDGS skipping... unknown, maybe grass //std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip break; } default: throw std::runtime_error("ESM4::REGN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Region::save(ESM4::Writer& writer) const //{ //} void ESM4::Region::blank() { } openmw-openmw-0.48.0/components/esm4/loadregn.hpp000066400000000000000000000052471445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_REGN_H #define ESM4_REGN_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Region { enum RegionDataType { RDAT_None = 0x00, RDAT_Objects = 0x02, RDAT_Weather = 0x03, RDAT_Map = 0x04, RDAT_Landscape = 0x05, RDAT_Grass = 0x06, RDAT_Sound = 0x07, RDAT_Imposter = 0x08 }; #pragma pack(push, 1) struct RegionData { std::uint32_t type; std::uint8_t flag; std::uint8_t priority; std::uint16_t unknown; }; struct RegionSound { FormId sound; std::uint32_t flags; // 0 pleasant, 1 cloudy, 2 rainy, 3 snowy std::uint32_t chance; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::uint32_t mColour; // RGBA FormId mWorldId; // worldspace formid std::string mShader; //?? ICON std::string mMapName; std::uint32_t mEdgeFalloff; std::vector mRPLD; // unknown, point data? RegionData mData; std::vector mSounds; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; void blank(); }; } #endif // ESM4_REGN_H openmw-openmw-0.48.0/components/esm4/loadroad.cpp000066400000000000000000000072551445372753700217730ustar00rootroot00000000000000/* Copyright (C) 2020 - 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadroad.hpp" #include //#include // FIXME: for debugging only #include "formid.hpp" // FIXME: for workaround #include "reader.hpp" //#include "writer.hpp" void ESM4::Road::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; mParent = reader.currWorld(); mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_PGRP: { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); mNodes.resize(numNodes); for (std::size_t i = 0; i < numNodes; ++i) { reader.get(mNodes.at(i)); } break; } case ESM4::SUB_PGRR: { static PGRR link; static RDRP linkPt; for (std::size_t i = 0; i < mNodes.size(); ++i) { for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) { link.startNode = std::int16_t(i); reader.get(linkPt); // FIXME: instead of looping each time, maybe use a map? bool found = false; for (std::size_t k = 0; k < mNodes.size(); ++k) { if (linkPt.x != mNodes[k].x || linkPt.y != mNodes[k].y || linkPt.z != mNodes[k].z) continue; else { link.endNode = std::int16_t(k); mLinks.push_back(link); found = true; break; } } if (!found) throw std::runtime_error("ESM4::ROAD::PGRR - Unknown link point " + std::to_string(j) + "at node " + std::to_string(i) + "."); } } break; } default: throw std::runtime_error("ESM4::ROAD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Road::save(ESM4::Writer& writer) const //{ //} //void ESM4::Road::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadroad.hpp000066400000000000000000000044341445372753700217740ustar00rootroot00000000000000/* Copyright (C) 2020 - 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_ROAD_H #define ESM4_ROAD_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Road { #pragma pack(push, 1) // FIXME: duplicated from PGRD struct PGRP { float x; float y; float z; std::uint8_t numLinks; std::uint8_t unknown1; std::uint16_t unknown2; }; // FIXME: duplicated from PGRD struct PGRR { std::int16_t startNode; std::int16_t endNode; }; struct RDRP { float x; float y; float z; }; #pragma pack(pop) FormId mParent; // world FormId, from the loading sequence FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::vector mNodes; std::vector mLinks; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_ROAD_H openmw-openmw-0.48.0/components/esm4/loadsbsp.cpp000066400000000000000000000040531445372753700220060ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadsbsp.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::SubSpace::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_DNAM: { reader.get(mDimension.x); reader.get(mDimension.y); reader.get(mDimension.z); break; } default: throw std::runtime_error("ESM4::SBSP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::SubSpace::save(ESM4::Writer& writer) const //{ //} //void ESM4::SubSpace::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadsbsp.hpp000066400000000000000000000033371445372753700220170ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SBSP_H #define ESM4_SBSP_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; struct SubSpace { struct Dimension { float x; float y; float z; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; Dimension mDimension; virtual void load(Reader& reader); //virtual void save(Writer& writer) const; //void blank(); }; } #endif // ESM4_SBSP_H openmw-openmw-0.48.0/components/esm4/loadscol.cpp000066400000000000000000000051011445372753700217720ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #include "loadscol.hpp" #include #include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::StaticCollection::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_OBND: case ESM4::SUB_MODL: case ESM4::SUB_MODT: case ESM4::SUB_ONAM: case ESM4::SUB_DATA: { //std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; reader.skipSubRecordData(); //throw std::runtime_error("ESM4::SCOL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::StaticCollection::save(ESM4::Writer& writer) const //{ //} //void ESM4::StaticCollection::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadscol.hpp000066400000000000000000000032531445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_SCOL_H #define ESM4_SCOL_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct StaticCollection { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SCOL_H openmw-openmw-0.48.0/components/esm4/loadscpt.cpp000066400000000000000000000127221445372753700220120ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadscpt.hpp" #include #include // FIXME: debugging only #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Script::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; static ScriptLocalVariableData localVar; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: { reader.getZString(mEditorId); break; } case ESM4::SUB_SCHR: { // For debugging only #if 0 unsigned char mDataBuf[256/*bufSize*/]; reader.get(&mDataBuf[0], subHdr.dataSize); std::ostringstream ss; for (unsigned int i = 0; i < subHdr.dataSize; ++i) { //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) //ss << (char)(mDataBuf[i]) << " "; //else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); if ((i & 0x000f) == 0xf) ss << "\n"; else if (i < 256/*bufSize*/-1) ss << " "; } std::cout << ss.str() << std::endl; #else reader.get(mScript.scriptHeader); #endif break; } case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); //if (mEditorId == "CTrapLogs01SCRIPT") //std::cout << mScript.scriptSource << std::endl; break; case ESM4::SUB_SCDA: // compiled script data { // For debugging only #if 0 if (subHdr.dataSize >= 4096) { std::cout << "Skipping " << mEditorId << std::endl; reader.skipSubRecordData(); break; } std::cout << mEditorId << std::endl; unsigned char mDataBuf[4096/*bufSize*/]; reader.get(&mDataBuf[0], subHdr.dataSize); std::ostringstream ss; for (unsigned int i = 0; i < subHdr.dataSize; ++i) { //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) //ss << (char)(mDataBuf[i]) << " "; //else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); if ((i & 0x000f) == 0xf) ss << "\n"; else if (i < 4096/*bufSize*/-1) ss << " "; } std::cout << ss.str() << std::endl; #else reader.skipSubRecordData(); #endif break; } case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; case ESM4::SUB_SLSD: { localVar.clear(); reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); // WARN: assumes SCVR will follow immediately break; } case ESM4::SUB_SCVR: // assumed always pair with SLSD { reader.getZString(localVar.variableName); mScript.localVarData.push_back(localVar); break; } case ESM4::SUB_SCRV: { std::uint32_t index; reader.get(index); mScript.localRefVarIndex.push_back(index); break; } default: //std::cout << "SCPT " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; //reader.skipSubRecordData(); //break; throw std::runtime_error("ESM4::SCPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Script::save(ESM4::Writer& writer) const //{ //} //void ESM4::Script::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadscpt.hpp000066400000000000000000000032001445372753700220060ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SCPT_H #define ESM4_SCPT_H #include #include "formid.hpp" #include "script.hpp" namespace ESM4 { class Reader; class Writer; struct Script { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; ScriptDefinition mScript; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SCPT_H openmw-openmw-0.48.0/components/esm4/loadscrl.cpp000066400000000000000000000055211445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadscrl.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::Scroll::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_DATA: { reader.get(mData.value); reader.get(mData.weight); break; } case ESM4::SUB_MODL: reader.getZString(mModel); break; //case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_OBND: case ESM4::SUB_CTDA: case ESM4::SUB_EFID: case ESM4::SUB_EFIT: case ESM4::SUB_ETYP: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_MDOB: case ESM4::SUB_MODT: case ESM4::SUB_SPIT: { //std::cout << "SCRL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::SCRL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Scroll::save(ESM4::Writer& writer) const //{ //} //void ESM4::Scroll::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadscrl.hpp000066400000000000000000000034611445372753700220110ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SCRL_H #define ESM4_SCRL_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Scroll { struct Data { std::uint32_t value; float weight; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mText; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SCRL_H openmw-openmw-0.48.0/components/esm4/loadsgst.cpp000066400000000000000000000065271445372753700220270ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadsgst.hpp" #include #include #include "reader.hpp" //#include "writer.hpp" void ESM4::SigilStone::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: { if (mFullName.empty()) { if (!reader.getZString(mFullName)) throw std::runtime_error ("SGST FULL data read error"); } else { // FIXME: should be part of a struct? std::string scriptEffectName; if (!reader.getZString(scriptEffectName)) throw std::runtime_error ("SGST FULL data read error"); mScriptEffect.push_back(scriptEffectName); } break; } case ESM4::SUB_DATA: { reader.get(mData.uses); reader.get(mData.value); reader.get(mData.weight); break; } case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_SCIT: { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } case ESM4::SUB_MODT: case ESM4::SUB_EFID: case ESM4::SUB_EFIT: { reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::SGST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::SigilStone::save(ESM4::Writer& writer) const //{ //} //void ESM4::SigilStone::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadsgst.hpp000066400000000000000000000040341445372753700220230ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SGST_H #define ESM4_SGST_H #include #include #include "effect.hpp" namespace ESM4 { class Reader; class Writer; struct SigilStone { struct Data { std::uint8_t uses; std::uint32_t value; // gold float weight; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory float mBoundRadius; std::vector mScriptEffect; // FIXME: prob. should be in a struct FormId mScriptId; ScriptEffect mEffect; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SGST_H openmw-openmw-0.48.0/components/esm4/loadslgm.cpp000066400000000000000000000053471445372753700220100ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadslgm.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::SoulGem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_DATA: reader.get(mData); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_SOUL: reader.get(mSoul); break; case ESM4::SUB_SLCP: reader.get(mSoulCapacity); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_NAM0: case ESM4::SUB_OBND: { //std::cout << "SLGM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::SLGM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::SoulGem::save(ESM4::Writer& writer) const //{ //} //void ESM4::SoulGem::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadslgm.hpp000066400000000000000000000042031445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SLGM_H #define ESM4_SLGM_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct SoulGem { #pragma pack(push, 1) struct Data { std::uint32_t value; // gold float weight; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mIcon; // inventory float mBoundRadius; FormId mScriptId; std::uint8_t mSoul; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand std::uint8_t mSoulCapacity; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SLGM_H openmw-openmw-0.48.0/components/esm4/loadsndr.cpp000066400000000000000000000060351445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadsndr.hpp" #include //#include // FIXME: for debugging only #include "reader.hpp" //#include "writer.hpp" void ESM4::SoundReference::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_CTDA: { reader.get(&mTargetCondition, 20); reader.get(mTargetCondition.runOn); reader.get(mTargetCondition.reference); if (mTargetCondition.reference) reader.adjustFormId(mTargetCondition.reference); reader.skipSubRecordData(4); // unknown break; } case ESM4::SUB_GNAM: reader.getFormId(mSoundCategory); break; case ESM4::SUB_SNAM: reader.getFormId(mSoundId); break; case ESM4::SUB_ONAM: reader.getFormId(mOutputModel); break; case ESM4::SUB_ANAM: reader.getZString(mSoundFile); break; case ESM4::SUB_LNAM: reader.get(mLoopInfo); break; case ESM4::SUB_BNAM: reader.get(mData); break; case ESM4::SUB_CNAM: // CRC32 hash case ESM4::SUB_FNAM: // unknown { //std::cout << "SNDR " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::SNDR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::SoundReference::save(ESM4::Writer& writer) const //{ //} //void ESM4::SoundReference::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadsndr.hpp000066400000000000000000000045111445372753700220110ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SNDR_H #define ESM4_SNDR_H #include #include #include "formid.hpp" #include "script.hpp" // TargetCondition namespace ESM4 { class Reader; class Writer; #pragma pack(push, 1) struct LoopInfo { std::uint16_t flags; std::uint8_t unknown; std::uint8_t rumble; }; struct SoundInfo { std::int8_t frequencyAdjustment; // %, signed std::uint8_t frequencyVariance; // % std::uint8_t priority; // default 128 std::uint8_t dBVriance; std::uint16_t staticAttenuation; // divide by 100 to get value in dB }; #pragma pack(pop) struct SoundReference { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; FormId mSoundCategory; // SNCT FormId mSoundId; // another SNDR FormId mOutputModel; // SOPM std::string mSoundFile; LoopInfo mLoopInfo; SoundInfo mData; TargetCondition mTargetCondition; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SNDR_H openmw-openmw-0.48.0/components/esm4/loadsoun.cpp000066400000000000000000000054271445372753700220310ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadsoun.hpp" #include //#include // FIXME #include "reader.hpp" //#include "writer.hpp" void ESM4::Sound::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FNAM: reader.getZString(mSoundFile); break; case ESM4::SUB_SNDX: reader.get(mData); break; case ESM4::SUB_SNDD: { if (subHdr.dataSize == 8) reader.get(&mData, 8); else { reader.get(mData); reader.get(mExtra); } break; } case ESM4::SUB_OBND: // TES5 only case ESM4::SUB_SDSC: // TES5 only case ESM4::SUB_ANAM: // FO3 case ESM4::SUB_GNAM: // FO3 case ESM4::SUB_HNAM: // FO3 case ESM4::SUB_RNAM: // FONV { //std::cout << "SOUN " << ESM::printName(subHdr.typeId) << " skipping..." //<< subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::SOUN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Sound::save(ESM4::Writer& writer) const //{ //} //void ESM4::Sound::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadsoun.hpp000066400000000000000000000061101445372753700220240ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_SOUN_H #define ESM4_SOUN_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Sound { enum Flags { Flag_RandomFreqShift = 0x0001, Flag_PlayAtRandom = 0x0002, Flag_EnvIgnored = 0x0004, Flag_RandomLocation = 0x0008, Flag_Loop = 0x0010, Flag_MenuSound = 0x0020, Flag_2D = 0x0040, Flag_360LFE = 0x0080 }; #pragma pack(push, 1) struct SNDX { std::uint8_t minAttenuation; // distance? std::uint8_t maxAttenuation; // distance? std::int8_t freqAdjustment; // %, signed std::uint8_t unknown; // probably padding std::uint16_t flags; std::uint16_t unknown2; // probably padding std::uint16_t staticAttenuation; // divide by 100 to get value in dB std::uint8_t stopTime; // multiply by 1440/256 to get value in minutes std::uint8_t startTime; // multiply by 1440/256 to get value in minutes }; struct SoundData { std::int16_t attenuationPoint1; std::int16_t attenuationPoint2; std::int16_t attenuationPoint3; std::int16_t attenuationPoint4; std::int16_t attenuationPoint5; std::int16_t reverbAttenuationControl; std::int32_t priority; std::int32_t x; std::int32_t y; }; #pragma pack(pop) FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mSoundFile; SNDX mData; SoundData mExtra; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_SOUN_H openmw-openmw-0.48.0/components/esm4/loadstat.cpp000066400000000000000000000064201445372753700220120ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadstat.hpp" #include #include // FIXME: debug only #include "reader.hpp" //#include "writer.hpp" void ESM4::Static::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: { // version is only availabe in TES5 (seems to be 27 or 28?) //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) //std::cout << "STAT MODT ver: " << std::hex << reader.hdr().record.version << std::endl; // for TES4 these are just a sequence of bytes mMODT.resize(subHdr.dataSize/sizeof(std::uint8_t)); for (std::vector::iterator it = mMODT.begin(); it != mMODT.end(); ++it) { reader.get(*it); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "MODT: " << std::hex << *it << std::endl; #endif } break; } case ESM4::SUB_MODS: case ESM4::SUB_OBND: case ESM4::SUB_DNAM: case ESM4::SUB_MNAM: case ESM4::SUB_BRUS: // FONV case ESM4::SUB_RNAM: // FONV { //std::cout << "STAT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::STAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Static::save(ESM4::Writer& writer) const //{ //} //void ESM4::Static::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadstat.hpp000066400000000000000000000033561445372753700220240ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_STAT_H #define ESM4_STAT_H #include #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Static { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; float mBoundRadius; std::vector mMODT; // FIXME texture hash void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_STAT_H openmw-openmw-0.48.0/components/esm4/loadtact.cpp000066400000000000000000000055061445372753700217760ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadtact.hpp" #include #include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::TalkingActivator::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_VNAM: reader.getFormId(mVoiceType); break; case ESM4::SUB_SNAM: reader.getFormId(mLoopSound); break; case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_DEST: // FO3 destruction case ESM4::SUB_DSTD: // FO3 destruction case ESM4::SUB_DSTF: // FO3 destruction case ESM4::SUB_FNAM: case ESM4::SUB_PNAM: case ESM4::SUB_MODT: // texture file hash? case ESM4::SUB_OBND: { //std::cout << "TACT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::TACT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::TalkingActivator::save(ESM4::Writer& writer) const //{ //} //void ESM4::TalkingActivator::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadtact.hpp000066400000000000000000000042771445372753700220070ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_TACT_H #define ESM4_TACT_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; enum TalkingActivatorFlags { TACT_OnLocalMap = 0x00000200, TACT_QuestItem = 0x00000400, TACT_NoVoiceFilter = 0x00002000, TACT_RandomAnimStart = 0x00010000, TACT_RadioStation = 0x00020000, TACT_NonProxy = 0x10000000, // only valid if Radio Station TACT_ContBroadcast = 0x40000000 // only valid if Radio Station }; struct TalkingActivator { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see above for details std::string mEditorId; std::string mFullName; std::string mModel; FormId mScriptId; FormId mVoiceType; // VTYP FormId mLoopSound; // SOUN FormId mRadioTemplate; // SOUN void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_TACT_H openmw-openmw-0.48.0/components/esm4/loadterm.cpp000066400000000000000000000061711445372753700220110ustar00rootroot00000000000000/* Copyright (C) 2019-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadterm.hpp" #include //#include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::Terminal::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_PNAM: reader.getFormId(mPasswordNote); break; case ESM4::SUB_SNAM: reader.getFormId(mSound); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_RNAM: reader.getZString(mResultText); break; case ESM4::SUB_DNAM: // difficulty case ESM4::SUB_ANAM: // flags case ESM4::SUB_CTDA: case ESM4::SUB_INAM: case ESM4::SUB_ITXT: case ESM4::SUB_MODT: // texture hash? case ESM4::SUB_SCDA: case ESM4::SUB_SCHR: case ESM4::SUB_SCRO: case ESM4::SUB_SCRV: case ESM4::SUB_SCTX: case ESM4::SUB_SCVR: case ESM4::SUB_SLSD: case ESM4::SUB_TNAM: case ESM4::SUB_OBND: case ESM4::SUB_MODS: // FONV { //std::cout << "TERM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::TERM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Terminal::save(ESM4::Writer& writer) const //{ //} //void ESM4::Terminal::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadterm.hpp000066400000000000000000000034431445372753700220150ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_TERM_H #define ESM4_TERM_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Terminal { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mText; std::string mModel; std::string mResultText; FormId mScriptId; FormId mPasswordNote; FormId mSound; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_TERM_H openmw-openmw-0.48.0/components/esm4/loadtes4.cpp000066400000000000000000000101451445372753700217150ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadtes4.hpp" #ifdef NDEBUG // FIXME: debuggigng only #undef NDEBUG #endif #include #include #include // FIXME: debugging only #include "common.hpp" #include "formid.hpp" #include "reader.hpp" //#include "writer.hpp" void ESM4::Header::load(ESM4::Reader& reader) { mFlags = reader.hdr().record.flags; // 0x01 = Rec_ESM, 0x80 = Rec_Localized while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_HEDR: { if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) throw std::runtime_error("TES4 HEDR data read error"); if ((size_t)subHdr.dataSize != sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId)) throw std::runtime_error("TES4 HEDR data size mismatch"); break; } case ESM4::SUB_CNAM: reader.getZString(mAuthor); break; case ESM4::SUB_SNAM: reader.getZString(mDesc); break; case ESM4::SUB_MAST: // multiple { ESM::MasterData m; if (!reader.getZString(m.name)) throw std::runtime_error("TES4 MAST data read error"); // NOTE: some mods do not have DATA following MAST so can't read DATA here m.size = 0; mMaster.push_back (m); break; } case ESM4::SUB_DATA: { // WARNING: assumes DATA always follows MAST if (!reader.getExact(mMaster.back().size)) throw std::runtime_error("TES4 DATA data read error"); break; } case ESM4::SUB_ONAM: { mOverrides.resize(subHdr.dataSize/sizeof(FormId)); for (unsigned int & mOverride : mOverrides) { if (!reader.getExact(mOverride)) throw std::runtime_error("TES4 ONAM data read error"); #if 0 std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "ESM4::Header::ONAM overrides: " << formIdToString(mOverride) << std::endl; #endif } break; } case ESM4::SUB_INTV: case ESM4::SUB_INCC: case ESM4::SUB_OFST: // Oblivion only? case ESM4::SUB_DELE: // Oblivion only? { //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::Header::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Header::save(ESM4::Writer& writer) //{ //} openmw-openmw-0.48.0/components/esm4/loadtes4.hpp000066400000000000000000000042511445372753700217230ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_TES4_H #define ESM4_TES4_H #include #include "formid.hpp" #include "../esm/common.hpp" // ESMVersion, MasterData namespace ESM4 { class Reader; class Writer; #pragma pack(push, 1) struct Data { ESM::ESMVersion version; // File format version. std::int32_t records; // Number of records std::uint32_t nextObjectId; }; #pragma pack(pop) struct Header { std::uint32_t mFlags; // 0x01 esm, 0x80 localised strings Data mData; std::string mAuthor; // Author's name std::string mDesc; // File description std::vector mMaster; std::vector mOverrides; // Skyrim only, cell children (ACHR, LAND, NAVM, PGRE, PHZD, REFR) // position in the vector = mod index of master files above // value = adjusted mod index based on all the files loaded so far //std::vector mModIndices; void load (Reader& reader); //void save (Writer& writer); }; } #endif // ESM4_TES4_H openmw-openmw-0.48.0/components/esm4/loadtree.cpp000066400000000000000000000047531445372753700220050ustar00rootroot00000000000000/* Copyright (C) 2016, 2018 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadtree.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Tree::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mLeafTexture); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_MODT: case ESM4::SUB_CNAM: case ESM4::SUB_BNAM: case ESM4::SUB_SNAM: case ESM4::SUB_FULL: case ESM4::SUB_OBND: case ESM4::SUB_PFIG: case ESM4::SUB_PFPC: { //std::cout << "TREE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::TREE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Tree::save(ESM4::Writer& writer) const //{ //} //void ESM4::Tree::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadtree.hpp000066400000000000000000000032711445372753700220040ustar00rootroot00000000000000/* Copyright (C) 2016, 2018, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_TREE_H #define ESM4_TREE_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Tree { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mModel; float mBoundRadius; std::string mLeafTexture; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_TREE_H openmw-openmw-0.48.0/components/esm4/loadtxst.cpp000066400000000000000000000053531445372753700220450ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadtxst.hpp" #include #include // FIXME: testing only #include "reader.hpp" //#include "writer.hpp" void ESM4::TextureSet::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_TX00: reader.getZString(mDiffuse); break; case ESM4::SUB_TX01: reader.getZString(mNormalMap); break; case ESM4::SUB_TX02: reader.getZString(mEnvMask); break; case ESM4::SUB_TX03: reader.getZString(mToneMap); break; case ESM4::SUB_TX04: reader.getZString(mDetailMap); break; case ESM4::SUB_TX05: reader.getZString(mEnvMap); break; case ESM4::SUB_TX06: reader.getZString(mUnknown); break; case ESM4::SUB_TX07: reader.getZString(mSpecular); break; case ESM4::SUB_DNAM: case ESM4::SUB_DODT: case ESM4::SUB_OBND: // object bounds { //std::cout << "TXST " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::TXST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::TextureSet::save(ESM4::Writer& writer) const //{ //} //void ESM4::TextureSet::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadtxst.hpp000066400000000000000000000036231445372753700220500ustar00rootroot00000000000000/* Copyright (C) 2019, 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_TXST_H #define ESM4_TXST_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct TextureSet { FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mDiffuse; // includes alpha info std::string mNormalMap; // includes specular info (alpha channel) std::string mEnvMask; std::string mToneMap; std::string mDetailMap; std::string mEnvMap; std::string mUnknown; std::string mSpecular; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_TXST_H openmw-openmw-0.48.0/components/esm4/loadweap.cpp000066400000000000000000000151311445372753700217720ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadweap.hpp" #include #include "reader.hpp" //#include "writer.hpp" void ESM4::Weapon::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_DATA: { //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 { reader.get(mData.value); reader.get(mData.weight); reader.get(mData.damage); } else if (isFONV || subHdr.dataSize == 15) { reader.get(mData.value); reader.get(mData.health); reader.get(mData.weight); reader.get(mData.damage); reader.get(mData.clipSize); } else { reader.get(mData.type); reader.get(mData.speed); reader.get(mData.reach); reader.get(mData.flags); reader.get(mData.value); reader.get(mData.health); reader.get(mData.weight); reader.get(mData.damage); } break; } case ESM4::SUB_MODL: reader.getZString(mModel); break; case ESM4::SUB_ICON: reader.getZString(mIcon); break; case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; case ESM4::SUB_MODB: reader.get(mBoundRadius); break; case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; case ESM4::SUB_MODT: case ESM4::SUB_BAMT: case ESM4::SUB_BIDS: case ESM4::SUB_INAM: case ESM4::SUB_CNAM: case ESM4::SUB_CRDT: case ESM4::SUB_DNAM: case ESM4::SUB_EAMT: case ESM4::SUB_EITM: case ESM4::SUB_ETYP: case ESM4::SUB_KSIZ: case ESM4::SUB_KWDA: case ESM4::SUB_NAM8: case ESM4::SUB_NAM9: case ESM4::SUB_OBND: case ESM4::SUB_SNAM: case ESM4::SUB_TNAM: case ESM4::SUB_UNAM: case ESM4::SUB_VMAD: case ESM4::SUB_VNAM: case ESM4::SUB_WNAM: case ESM4::SUB_XNAM: // Dawnguard only? case ESM4::SUB_NNAM: case ESM4::SUB_MODS: case ESM4::SUB_NAM0: // FO3 case ESM4::SUB_REPL: // FO3 case ESM4::SUB_MOD2: // FO3 case ESM4::SUB_MO2T: // FO3 case ESM4::SUB_MO2S: // FO3 case ESM4::SUB_NAM6: // FO3 case ESM4::SUB_MOD4: // FO3 case ESM4::SUB_MO4T: // FO3 case ESM4::SUB_MO4S: // FO3 case ESM4::SUB_BIPL: // FO3 case ESM4::SUB_NAM7: // FO3 case ESM4::SUB_MOD3: // FO3 case ESM4::SUB_MO3T: // FO3 case ESM4::SUB_MO3S: // FO3 case ESM4::SUB_MODD: // FO3 //case ESM4::SUB_MOSD: // FO3 case ESM4::SUB_DEST: // FO3 case ESM4::SUB_DSTD: // FO3 case ESM4::SUB_DSTF: // FO3 case ESM4::SUB_DMDL: // FO3 case ESM4::SUB_DMDT: // FO3 case ESM4::SUB_VATS: // FONV case ESM4::SUB_VANM: // FONV case ESM4::SUB_MWD1: // FONV case ESM4::SUB_MWD2: // FONV case ESM4::SUB_MWD3: // FONV case ESM4::SUB_MWD4: // FONV case ESM4::SUB_MWD5: // FONV case ESM4::SUB_MWD6: // FONV case ESM4::SUB_MWD7: // FONV case ESM4::SUB_WMI1: // FONV case ESM4::SUB_WMI2: // FONV case ESM4::SUB_WMI3: // FONV case ESM4::SUB_WMS1: // FONV case ESM4::SUB_WMS2: // FONV case ESM4::SUB_WNM1: // FONV case ESM4::SUB_WNM2: // FONV case ESM4::SUB_WNM3: // FONV case ESM4::SUB_WNM4: // FONV case ESM4::SUB_WNM5: // FONV case ESM4::SUB_WNM6: // FONV case ESM4::SUB_WNM7: // FONV case ESM4::SUB_EFSD: // FONV DeadMoney { //std::cout << "WEAP " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); break; } default: throw std::runtime_error("ESM4::WEAP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } } //void ESM4::Weapon::save(ESM4::Writer& writer) const //{ //} //void ESM4::Weapon::blank() //{ //} openmw-openmw-0.48.0/components/esm4/loadweap.hpp000066400000000000000000000052171445372753700220030ustar00rootroot00000000000000/* Copyright (C) 2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_WEAP_H #define ESM4_WEAP_H #include #include #include "formid.hpp" namespace ESM4 { class Reader; class Writer; struct Weapon { struct Data { // type // 0 = Blade One Hand // 1 = Blade Two Hand // 2 = Blunt One Hand // 3 = Blunt Two Hand // 4 = Staff // 5 = Bow std::uint32_t type; float speed; float reach; std::uint32_t flags; std::uint32_t value; // gold std::uint32_t health; float weight; std::uint16_t damage; std::uint8_t clipSize; // FO3/FONV only Data() : type(0), speed(0.f), reach(0.f), flags(0), value(0), health(0), weight(0.f), damage(0), clipSize(0) {} }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; std::string mModel; std::string mText; std::string mIcon; std::string mMiniIcon; FormId mPickUpSound; FormId mDropSound; float mBoundRadius; FormId mScriptId; std::uint16_t mEnchantmentPoints; FormId mEnchantment; Data mData; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; //void blank(); }; } #endif // ESM4_WEAP_H openmw-openmw-0.48.0/components/esm4/loadwrld.cpp000066400000000000000000000165601445372753700220150ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #include "loadwrld.hpp" #include //#include // FIXME: debug only #include "reader.hpp" //#include "writer.hpp" void ESM4::World::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; // It should be possible to save the current world formId automatically while reading in // the record header rather than doing it manually here but possibly less efficient (may // need to check each record?). // // Alternatively it may be possible to figure it out by examining the group headers, but // apparently the label field is not reliable so the parent world formid may have been // corrupted by the use of ignore flag (TODO: should check to verify). reader.setCurrWorld(mFormId); // save for CELL later std::uint32_t subSize = 0; // for XXXX sub record std::uint32_t esmVer = reader.esmVersion(); //bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100); //bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 bool usingDefaultLevels = true; while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { case ESM4::SUB_EDID: reader.getZString(mEditorId); break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; case ESM4::SUB_WCTR: reader.get(mCenterCell); break; // Center cell, TES5 only case ESM4::SUB_WNAM: reader.getFormId(mParent); break; case ESM4::SUB_SNAM: reader.get(mSound); break; // sound, Oblivion only? case ESM4::SUB_ICON: reader.getZString(mMapFile); break; // map filename, Oblivion only? case ESM4::SUB_CNAM: reader.get(mClimate); break; case ESM4::SUB_NAM2: reader.getFormId(mWater); break; case ESM4::SUB_NAM0: { reader.get(mMinX); reader.get(mMinY); break; } case ESM4::SUB_NAM9: { reader.get(mMaxX); reader.get(mMaxY); break; } case ESM4::SUB_DATA: reader.get(mWorldFlags); break; case ESM4::SUB_MNAM: { reader.get(mMap.width); reader.get(mMap.height); reader.get(mMap.NWcellX); reader.get(mMap.NWcellY); reader.get(mMap.SEcellX); reader.get(mMap.SEcellY); if (subHdr.dataSize == 28) // Skyrim? { reader.get(mMap.minHeight); reader.get(mMap.maxHeight); reader.get(mMap.initialPitch); } break; } case ESM4::SUB_DNAM: // defaults { reader.get(mLandLevel); // -2700.f for TES5 reader.get(mWaterLevel); // -14000.f for TES5 usingDefaultLevels = false; break; } // Only a few worlds in FO3 have music (I'm guessing 00090908 "explore" is the default?) // 00090906 public WRLD: 00000A74 MegatonWorld // 00090CE7 base WRLD: 0001A25D DCWorld18 (Arlington National Cemeteray) // 00090CE7 base WRLD: 0001A266 DCWorld09 (The Mall) // 00090CE7 base WRLD: 0001A267 DCWorld08 (Pennsylvania Avenue) // 000BAD30 tranquilitylane WRLD: 000244A7 TranquilityLane // 00090CE7 base WRLD: 000271C0 MonumentWorld (The Washington Monument) // 00090907 dungeon WRLD: 0004C4D1 MamaDolcesWorld (Mama Dolce's Loading Yard) // // FONV has only 3 (note the different format, also can't find the files?): // 00119D2E freeside\freeside_01.mp3 0010BEEA FreesideWorld (Freeside) // 00119D2E freeside\freeside_01.mp3 0012D94D FreesideNorthWorld (Freeside) // 00119D2E freeside\freeside_01.mp3 0012D94E FreesideFortWorld (Old Mormon Fort) // NOTE: FONV DefaultObjectManager has 00090908 "explore" as the default music case ESM4::SUB_ZNAM: reader.getFormId(mMusic); break; case ESM4::SUB_PNAM: reader.get(mParentUseFlags); break; case ESM4::SUB_RNAM: // multiple case ESM4::SUB_MHDT: case ESM4::SUB_LTMP: case ESM4::SUB_XEZN: case ESM4::SUB_XLCN: case ESM4::SUB_NAM3: case ESM4::SUB_NAM4: case ESM4::SUB_MODL: case ESM4::SUB_NAMA: case ESM4::SUB_ONAM: case ESM4::SUB_TNAM: case ESM4::SUB_UNAM: case ESM4::SUB_XWEM: case ESM4::SUB_MODT: // from Dragonborn onwards? case ESM4::SUB_INAM: // FO3 case ESM4::SUB_NNAM: // FO3 case ESM4::SUB_XNAM: // FO3 case ESM4::SUB_IMPS: // FO3 Anchorage case ESM4::SUB_IMPF: // FO3 Anchorage { //std::cout << "WRLD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip break; } case ESM4::SUB_OFST: { if (subSize) { reader.skipSubRecordData(subSize); // special post XXXX reader.updateRecordRead(subSize); // WARNING: manually update subSize = 0; } else reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip break; } case ESM4::SUB_XXXX: { reader.get(subSize); break; } default: throw std::runtime_error("ESM4::WRLD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } if (isTES5 && usingDefaultLevels) { mLandLevel = -2700.f; mWaterLevel = -14000.f; } } } //void ESM4::World::save(ESM4::Writer& writer) const //{ //} openmw-openmw-0.48.0/components/esm4/loadwrld.hpp000066400000000000000000000105501445372753700220130ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018-2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_WRLD_H #define ESM4_WRLD_H #include #include #include #include "common.hpp" namespace ESM4 { class Reader; class Writer; struct World { enum WorldFlags // TES4 TES5 { // -------------------- ----------------- WLD_Small = 0x01, // Small World Small World WLD_NoFastTravel = 0x02, // Can't Fast Travel Can't Fast Travel WLD_Oblivion = 0x04, // Oblivion worldspace WLD_NoLODWater = 0x08, // No LOD Water WLD_NoLandscpe = 0x10, // No LOD Water No Landscape WLD_NoSky = 0x20, // No Sky wLD_FixedDimension = 0x40, // Fixed Dimensions WLD_NoGrass = 0x80 // No Grass }; struct REFRcoord { FormId formId; std::int16_t unknown1; std::int16_t unknown2; }; struct RNAMstruct { std::int16_t unknown1; std::int16_t unknown2; std::vector refrs; }; //Map size struct 16 or 28 byte structure struct Map { std::uint32_t width; // usable width of the map std::uint32_t height; // usable height of the map std::int16_t NWcellX; std::int16_t NWcellY; std::int16_t SEcellX; std::int16_t SEcellY; float minHeight; // Camera Data (default 50000), new as of Skyrim 1.8, purpose is not yet known. float maxHeight; // Camera Data (default 80000) float initialPitch; }; FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; std::string mFullName; FormId mParent; // parent worldspace formid std::uint8_t mWorldFlags; FormId mClimate; FormId mWater; float mLandLevel; float mWaterLevel; Map mMap; std::int32_t mMinX; std::int32_t mMinY; std::int32_t mMaxX; std::int32_t mMaxY; // ------ TES4 only ----- std::int32_t mSound; // 0 = no record, 1 = Public, 2 = Dungeon std::string mMapFile; // ------ TES5 only ----- Grid mCenterCell; RNAMstruct mData; // ---------------------- FormId mMusic; // 0x01 use Land data // 0x02 use LOD data // 0x04 use Map data // 0x08 use Water data // 0x10 use Climate data // 0x20 use Image Space data (Climate for TES5) // 0x40 use SkyCell (TES5) // 0x80 needs water adjustment (this isn't for parent I think? FONV only set for wastelandnv) std::uint16_t mParentUseFlags; // FO3/FONV // cache formId's of children (e.g. CELL, ROAD) std::vector mCells; std::vector mRoads; void load(ESM4::Reader& reader); //void save(ESM4::Writer& writer) const; }; } #endif // ESM4_WRLD_H openmw-openmw-0.48.0/components/esm4/reader.cpp000066400000000000000000000543271445372753700214520ustar00rootroot00000000000000/* Copyright (C) 2015-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au */ #include "reader.hpp" #ifdef NDEBUG // FIXME: debugging only #undef NDEBUG #endif #undef DEBUG_GROUPSTACK #include #include #include #include // for debugging #include // for debugging #include // for debugging #if defined(_MSC_VER) #pragma warning (push) #pragma warning (disable : 4706) #include #pragma warning (pop) #else #include #endif #include #include #include #include #include #include #include #include "formid.hpp" namespace ESM4 { ReaderContext::ReaderContext() : modIndex(0), recHeaderSize(sizeof(RecordHeader)), filePos(0), fileRead(0), recordRead(0), currWorld(0), currCell(0), cellGridValid(false) { currCellGrid.cellId = 0; currCellGrid.grid.x = 0; currCellGrid.grid.y = 0; subRecordHeader.typeId = 0; subRecordHeader.dataSize = 0; } Reader::Reader(Files::IStreamPtr&& esmStream, const std::string& filename) : mEncoder(nullptr), mFileSize(0), mStream(std::move(esmStream)) { // used by ESMReader only? mCtx.filename = filename; mCtx.fileRead = 0; mStream->seekg(0, mStream->end); mFileSize = mStream->tellg(); mStream->seekg(20); // go to the start but skip the "TES4" record header mSavedStream.reset(); // determine header size std::uint32_t subRecName = 0; mStream->read((char*)&subRecName, sizeof(subRecName)); if (subRecName == 0x52444548) // "HEDR" mCtx.recHeaderSize = sizeof(RecordHeader) - 4; // TES4 header size is 4 bytes smaller than TES5 header else mCtx.recHeaderSize = sizeof(RecordHeader); // restart from the beginning (i.e. "TES4" record header) mStream->seekg(0, mStream->beg); #if 0 unsigned int esmVer = mHeader.mData.version.ui; bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; //bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; //bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; // TES4 header size is 4 bytes smaller than TES5 header mCtx.recHeaderSize = isTes4 ? sizeof(ESM4::RecordHeader) - 4 : sizeof(ESM4::RecordHeader); #endif getRecordHeader(); if (mCtx.recordHeader.record.typeId == REC_TES4) { mHeader.load(*this); mCtx.fileRead += mCtx.recordHeader.record.dataSize; buildLStringIndex(); // for localised strings in Skyrim } else fail("Unknown file format"); } Reader::~Reader() { close(); } // Since the record data may have been compressed, it is not always possible to use seek() to // go to a position of a sub record. // // The record header needs to be saved in the context or the header needs to be re-loaded after // restoring the context. The latter option was chosen. ReaderContext Reader::getContext() { mCtx.filePos = mStream->tellg(); mCtx.filePos -= mCtx.recHeaderSize; // update file position return mCtx; } // NOTE: Assumes that the caller has reopened the file if necessary bool Reader::restoreContext(const ReaderContext& ctx) { if (mSavedStream) // TODO: doesn't seem to ever happen { mStream = std::move(mSavedStream); } mCtx.groupStack.clear(); // probably not necessary since it will be overwritten mCtx = ctx; mStream->seekg(ctx.filePos); // update file position return getRecordHeader(); } void Reader::close() { mStream.reset(); //clearCtx(); //mHeader.blank(); } void Reader::openRaw(Files::IStreamPtr&& stream, const std::string& filename) { close(); mStream = std::move(stream); mCtx.filename = filename; mCtx.fileRead = 0; mStream->seekg(0, mStream->end); mFileSize = mStream->tellg(); mStream->seekg(0, mStream->beg); } void Reader::open(Files::IStreamPtr&& stream, const std::string &filename) { openRaw(std::move(stream), filename); // should at least have the size of ESM3 record header (20 or 24 bytes for ESM4) assert (mFileSize >= 16); std::uint32_t modVer = 0; if (getExact(modVer)) // get the first 4 bytes of the record header only { // FIXME: need to setup header/context if (modVer == REC_TES4) { } else { } } throw std::runtime_error("Unknown file format"); // can't yet use fail() as mCtx is not setup } void Reader::openRaw(const std::string& filename) { openRaw(Files::openConstrainedFileStream(filename), filename); } void Reader::open(const std::string& filename) { open(Files::openConstrainedFileStream(filename), filename); } void Reader::setRecHeaderSize(const std::size_t size) { mCtx.recHeaderSize = size; } // FIXME: only "English" strings supported for now void Reader::buildLStringIndex() { if ((mHeader.mFlags & Rec_ESM) == 0 || (mHeader.mFlags & Rec_Localized) == 0) return; boost::filesystem::path p(mCtx.filename); std::string filename = p.stem().filename().string(); buildLStringIndex("Strings/" + filename + "_English.STRINGS", Type_Strings); buildLStringIndex("Strings/" + filename + "_English.ILSTRINGS", Type_ILStrings); buildLStringIndex("Strings/" + filename + "_English.DLSTRINGS", Type_DLStrings); } void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType) { std::uint32_t numEntries; std::uint32_t dataSize; std::uint32_t stringId; LStringOffset sp; sp.type = stringType; // TODO: possibly check if the resource exists? Files::IStreamPtr filestream = Files::openConstrainedFileStream(stringFile); filestream->seekg(0, std::ios::end); std::size_t fileSize = filestream->tellg(); filestream->seekg(0, std::ios::beg); std::istream* stream = filestream.get(); switch (stringType) { case Type_Strings: mStrings = std::move(filestream); break; case Type_ILStrings: mILStrings = std::move(filestream); break; case Type_DLStrings: mDLStrings = std::move(filestream); break; default: throw std::runtime_error("ESM4::Reader::unknown localised string type"); } stream->read((char*)&numEntries, sizeof(numEntries)); stream->read((char*)&dataSize, sizeof(dataSize)); std::size_t dataStart = fileSize - dataSize; for (unsigned int i = 0; i < numEntries; ++i) { stream->read((char*)&stringId, sizeof(stringId)); stream->read((char*)&sp.offset, sizeof(sp.offset)); sp.offset += (std::uint32_t)dataStart; mLStringIndex[stringId] = sp; } //assert (dataStart - stream->tell() == 0 && "String file start of data section mismatch"); } void Reader::getLocalizedString(std::string& str) { if (!hasLocalizedStrings()) return (void)getZString(str); std::uint32_t stringId; // FormId get(stringId); if (stringId) // TES5 FoxRace, BOOK getLocalizedStringImpl(stringId, str); } // FIXME: very messy and probably slow/inefficient void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) { const std::map::const_iterator it = mLStringIndex.find(stringId); if (it != mLStringIndex.end()) { std::istream* filestream = nullptr; switch (it->second.type) { case Type_Strings: // no string size provided { filestream = mStrings.get(); filestream->seekg(it->second.offset); char ch; std::vector data; do { filestream->read(&ch, sizeof(ch)); data.push_back(ch); } while (ch != 0); str = std::string(data.data()); return; } case Type_ILStrings: filestream = mILStrings.get(); break; case Type_DLStrings: filestream = mDLStrings.get(); break; default: throw std::runtime_error("ESM4::Reader::getLocalizedString unknown string type"); } // get ILStrings or DLStrings (they provide string size) filestream->seekg(it->second.offset); std::uint32_t size = 0; filestream->read((char*)&size, sizeof(size)); getStringImpl(str, size, *filestream, mEncoder, true); // expect null terminated string } else throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found"); } bool Reader::getRecordHeader() { // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block if (/*mStream->eof() && */mSavedStream) { mStream = std::move(mSavedStream); } mStream->read((char*)&mCtx.recordHeader, mCtx.recHeaderSize); std::size_t bytesRead = (std::size_t)mStream->gcount(); // keep track of data left to read from the file mCtx.fileRead += mCtx.recHeaderSize; mCtx.recordRead = 0; // for keeping track of sub records // After reading the record header we can cache a WRLD or CELL formId for convenient access later. // FIXME: currently currWorld and currCell are set manually when loading the WRLD and CELL records // HACK: mCtx.groupStack.back() is updated before the record data are read/skipped // N.B. the data must be fully read/skipped for this to work if (mCtx.recordHeader.record.typeId != REC_GRUP && !mCtx.groupStack.empty()) { mCtx.groupStack.back().second += (std::uint32_t)mCtx.recHeaderSize + mCtx.recordHeader.record.dataSize; // keep track of data left to read from the file mCtx.fileRead += mCtx.recordHeader.record.dataSize; } return bytesRead == mCtx.recHeaderSize; } void Reader::getRecordData(bool dump) { std::uint32_t uncompressedSize = 0; if ((mCtx.recordHeader.record.flags & Rec_Compressed) != 0) { mStream->read(reinterpret_cast(&uncompressedSize), sizeof(std::uint32_t)); std::size_t recordSize = mCtx.recordHeader.record.dataSize - sizeof(std::uint32_t); Bsa::MemoryInputStream compressedRecord(recordSize); mStream->read(compressedRecord.getRawData(), recordSize); std::istream *fileStream = (std::istream*)&compressedRecord; mSavedStream = std::move(mStream); mCtx.recordHeader.record.dataSize = uncompressedSize - sizeof(uncompressedSize); auto memoryStreamPtr = std::make_unique(uncompressedSize); boost::iostreams::filtering_streambuf inputStreamBuf; inputStreamBuf.push(boost::iostreams::zlib_decompressor()); inputStreamBuf.push(*fileStream); boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); boost::iostreams::copy(inputStreamBuf, sr); // For debugging only //#if 0 if (dump) { std::ostringstream ss; char* data = memoryStreamPtr->getRawData(); for (unsigned int i = 0; i < uncompressedSize; ++i) { if (data[i] > 64 && data[i] < 91) ss << (char)(data[i]) << " "; else ss << std::setfill('0') << std::setw(2) << std::hex << (int)(data[i]); if ((i & 0x000f) == 0xf) ss << "\n"; else if (i < uncompressedSize-1) ss << " "; } std::cout << ss.str() << std::endl; } //#endif mStream = std::make_unique>(std::move(memoryStreamPtr)); } } void Reader::skipRecordData() { assert (mCtx.recordRead <= mCtx.recordHeader.record.dataSize && "Skipping after reading more than available"); mStream->ignore(mCtx.recordHeader.record.dataSize - mCtx.recordRead); mCtx.recordRead = mCtx.recordHeader.record.dataSize; // for getSubRecordHeader() } bool Reader::getSubRecordHeader() { bool result = false; // NOTE: some SubRecords have 0 dataSize (e.g. SUB_RDSD in one of REC_REGN records in Oblivion.esm). // Also SUB_XXXX has zero dataSize and the following 4 bytes represent the actual dataSize // - hence it require manual updtes to mCtx.recordRead via updateRecordRead() // See ESM4::NavMesh and ESM4::World. if (mCtx.recordHeader.record.dataSize - mCtx.recordRead >= sizeof(mCtx.subRecordHeader)) { result = getExact(mCtx.subRecordHeader); // HACK: below assumes sub-record data will be read or skipped in full; // this hack aims to avoid updating mCtx.recordRead each time anything is read mCtx.recordRead += (sizeof(mCtx.subRecordHeader) + mCtx.subRecordHeader.dataSize); } else if (mCtx.recordRead > mCtx.recordHeader.record.dataSize) { // try to correct any overshoot, seek to the end of the expected data // this will only work if mCtx.subRecordHeader.dataSize was fully read or skipped // (i.e. it will only correct mCtx.subRecordHeader.dataSize being incorrect) // TODO: not tested std::uint32_t overshoot = (std::uint32_t)mCtx.recordRead - mCtx.recordHeader.record.dataSize; std::size_t pos = mStream->tellg(); mStream->seekg(pos - overshoot); return false; } return result; } void Reader::skipSubRecordData() { mStream->ignore(mCtx.subRecordHeader.dataSize); } void Reader::skipSubRecordData(std::uint32_t size) { mStream->ignore(size); } void Reader::enterGroup() { #ifdef DEBUG_GROUPSTACK std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Starting record group " << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; #endif // empty group if the group size is same as the header size if (mCtx.recordHeader.group.groupSize == (std::uint32_t)mCtx.recHeaderSize) { #ifdef DEBUG_GROUPSTACK std::cout << padding << "Ignoring record group " // FIXME: debugging only << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << " (empty)" << std::endl; #endif if (!mCtx.groupStack.empty()) // top group may be empty (e.g. HAIR in Skyrim) { // don't put on the stack, exitGroupCheck() may not get called before recursing into this method mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; exitGroupCheck(); } return; // don't push an empty group, just return } // push group mCtx.groupStack.push_back(std::make_pair(mCtx.recordHeader.group, (std::uint32_t)mCtx.recHeaderSize)); } void Reader::exitGroupCheck() { if (mCtx.groupStack.empty()) return; // pop finished groups (note reading too much is allowed here) std::uint32_t lastGroupSize = mCtx.groupStack.back().first.groupSize; while (mCtx.groupStack.back().second >= lastGroupSize) { #ifdef DEBUG_GROUPSTACK GroupTypeHeader grp = mCtx.groupStack.back().first; // FIXME: grp is for debugging only #endif // try to correct any overshoot // TODO: not tested std::uint32_t overshoot = mCtx.groupStack.back().second - lastGroupSize; if (overshoot > 0) { std::size_t pos = mStream->tellg(); mStream->seekg(pos - overshoot); } mCtx.groupStack.pop_back(); #ifdef DEBUG_GROUPSTACK std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Finished record group " << printLabel(grp.label, grp.type) << std::endl; #endif // if the previous group was the final one no need to do below if (mCtx.groupStack.empty()) return; mCtx.groupStack.back().second += lastGroupSize; lastGroupSize = mCtx.groupStack.back().first.groupSize; assert (lastGroupSize >= mCtx.groupStack.back().second && "Read more records than available"); //#if 0 if (mCtx.groupStack.back().second > lastGroupSize) // FIXME: debugging only std::cerr << printLabel(mCtx.groupStack.back().first.label, mCtx.groupStack.back().first.type) << " read more records than available" << std::endl; //#endif } } // WARNING: this method should be used after first calling enterGroup() // else the method may try to dereference an element that does not exist const GroupTypeHeader& Reader::grp(std::size_t pos) const { assert (pos <= mCtx.groupStack.size()-1 && "ESM4::Reader::grp - exceeded stack depth"); return (*(mCtx.groupStack.end()-pos-1)).first; } void Reader::skipGroupData() { assert (!mCtx.groupStack.empty() && "Skipping group with an empty stack"); // subtract what was already read/skipped std::uint32_t skipSize = mCtx.groupStack.back().first.groupSize - mCtx.groupStack.back().second; mStream->ignore(skipSize); // keep track of data left to read from the file mCtx.fileRead += skipSize; mCtx.groupStack.back().second = mCtx.groupStack.back().first.groupSize; } void Reader::skipGroup() { #ifdef DEBUG_GROUPSTACK std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Skipping record group " << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; #endif // subtract the size of header already read before skipping std::uint32_t skipSize = mCtx.recordHeader.group.groupSize - (std::uint32_t)mCtx.recHeaderSize; mStream->ignore(skipSize); // keep track of data left to read from the file mCtx.fileRead += skipSize; // NOTE: mCtx.groupStack.back().second already has mCtx.recHeaderSize from enterGroup() if (!mCtx.groupStack.empty()) mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; } const CellGrid& Reader::currCellGrid() const { // Maybe should throw an exception instead? assert (mCtx.cellGridValid && "Attempt to use an invalid cell grid"); return mCtx.currCellGrid; } // NOTE: the parameter 'files' must have the file names in the loaded order void Reader::updateModIndices(const std::vector& files) { if (files.size() >= 0xff) throw std::runtime_error("ESM4::Reader::updateModIndices too many files"); // 0xff is reserved // NOTE: this map is rebuilt each time this method is called (i.e. each time a file is loaded) // Perhaps there is an opportunity to optimize this by saving the result somewhere. // But then, the number of files is at most around 250 so perhaps keeping it simple might be better. // build a lookup map std::unordered_map fileIndex; for (size_t i = 0; i < files.size(); ++i) // ATTENTION: assumes current file is not included fileIndex[Misc::StringUtils::lowerCase(files[i])] = i; mCtx.parentFileIndices.resize(mHeader.mMaster.size()); for (unsigned int i = 0; i < mHeader.mMaster.size(); ++i) { // locate the position of the dependency in already loaded files std::unordered_map::const_iterator it = fileIndex.find(Misc::StringUtils::lowerCase(mHeader.mMaster[i].name)); if (it != fileIndex.end()) mCtx.parentFileIndices[i] = (std::uint32_t)((it->second << 24) & 0xff000000); else throw std::runtime_error("ESM4::Reader::updateModIndices required dependency file not loaded"); #if 0 std::cout << "Master Mod: " << mCtx.header.mMaster[i].name << ", " // FIXME: debugging only << formIdToString(mCtx.parentFileIndices[i]) << std::endl; #endif } if (!mCtx.parentFileIndices.empty() && mCtx.parentFileIndices[0] != 0) throw std::runtime_error("ESM4::Reader::updateModIndices base modIndex is not zero"); } // ModIndex adjusted formId according to master file dependencies // (see http://www.uesp.net/wiki/Tes4Mod:FormID_Fixup) // NOTE: need to update modindex to parentFileIndices.size() before saving // // FIXME: probably should add a parameter to check for mCtx.header::mOverrides // (ACHR, LAND, NAVM, PGRE, PHZD, REFR), but not sure what exactly overrides mean // i.e. use the modindx of its master? // FIXME: Apparently ModIndex '00' in an ESP means the object is defined in one of its masters. // This means we may need to search multiple times to get the correct id. // (see https://www.uesp.net/wiki/Tes4Mod:Formid#ModIndex_Zero) void Reader::adjustFormId(FormId& id) { if (mCtx.parentFileIndices.empty()) return; std::size_t index = (id >> 24) & 0xff; if (index < mCtx.parentFileIndices.size()) id = mCtx.parentFileIndices[index] | (id & 0x00ffffff); else id = mCtx.modIndex | (id & 0x00ffffff); } bool Reader::getFormId(FormId& id) { if (!getExact(id)) return false; adjustFormId(id); return true; } void Reader::adjustGRUPFormId() { adjustFormId(mCtx.recordHeader.group.label.value); } [[noreturn]] void Reader::fail(const std::string& msg) { std::stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << ESM::printName(mCtx.recordHeader.record.typeId); ss << "\n Subrecord: " << ESM::printName(mCtx.subRecordHeader.typeId); if (mStream.get()) ss << "\n Offset: 0x" << std::hex << mStream->tellg(); throw std::runtime_error(ss.str()); } } openmw-openmw-0.48.0/components/esm4/reader.hpp000066400000000000000000000272271445372753700214560ustar00rootroot00000000000000/* Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au */ #ifndef ESM4_READER_H #define ESM4_READER_H #include #include #include #include #include "common.hpp" #include "loadtes4.hpp" #include "../esm/reader.hpp" #include namespace ESM4 { // bytes read from group, updated by // getRecordHeader() in advance // | // v typedef std::vector > GroupStack; struct ReaderContext { std::string filename; // in case we need to reopen to restore the context std::uint32_t modIndex; // the sequential position of this file in the load order: // 0x00 reserved, 0xFF in-game (see notes below) // position in the vector = mod index of master files above // value = adjusted mod index based on all the files loaded so far std::vector parentFileIndices; std::size_t recHeaderSize; // normally should be already set correctly, but just in // case the file was re-opened. default = TES5 size, // can be reduced for TES4 by setRecHeaderSize() std::size_t filePos; // assume that the record header will be re-read once // the context is restored // for keeping track of things std::size_t fileRead; // number of bytes read, incl. the current record GroupStack groupStack; // keep track of bytes left to find when a group is done RecordHeader recordHeader; // header of the current record or group being processed SubRecordHeader subRecordHeader; // header of the current sub record being processed std::uint32_t recordRead; // bytes read from the sub records, incl. the current one FormId currWorld; // formId of current world - for grouping CELL records FormId currCell; // formId of current cell // FIXME: try to get rid of these two members, seem like massive hacks CellGrid currCellGrid; // TODO: should keep a map of cell formids bool cellGridValid; ReaderContext(); }; class Reader : public ESM::Reader { Header mHeader; // ESM4 header ReaderContext mCtx; const ToUTF8::StatelessUtf8Encoder* mEncoder; std::size_t mFileSize; Files::IStreamPtr mStream; Files::IStreamPtr mSavedStream; // mStream is saved here while using deflated memory stream Files::IStreamPtr mStrings; Files::IStreamPtr mILStrings; Files::IStreamPtr mDLStrings; enum LocalizedStringType { Type_Strings = 0, Type_ILStrings = 1, Type_DLStrings = 2 }; struct LStringOffset { LocalizedStringType type; std::uint32_t offset; }; std::map mLStringIndex; void buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType); inline bool hasLocalizedStrings() const { return (mHeader.mFlags & Rec_Localized) != 0; } void getLocalizedStringImpl(const FormId stringId, std::string& str); // Close the file, resets all information. // After calling close() the structure may be reused to load a new file. //void close(); // Raw opening. Opens the file and sets everything up but doesn't parse the header. void openRaw(Files::IStreamPtr&& stream, const std::string& filename); // Load ES file from a new stream, parses the header. // Closes the currently open file first, if any. void open(Files::IStreamPtr&& stream, const std::string& filename); Reader() = default; public: Reader(Files::IStreamPtr&& esmStream, const std::string& filename); ~Reader(); // FIXME: should be private but ESMTool uses it void openRaw(const std::string& filename); void open(const std::string& filename); void close() final; inline bool isEsm4() const final { return true; } inline void setEncoder(const ToUTF8::StatelessUtf8Encoder* encoder) final { mEncoder = encoder; }; const std::vector& getGameFiles() const final { return mHeader.mMaster; } inline int getRecordCount() const final { return mHeader.mData.records; } inline const std::string getAuthor() const final { return mHeader.mAuthor; } inline int getFormat() const final { return 0; }; // prob. not relevant for ESM4 inline const std::string getDesc() const final { return mHeader.mDesc; } inline std::string getFileName() const final { return mCtx.filename; }; // not used inline bool hasMoreRecs() const final { return (mFileSize - mCtx.fileRead) > 0; } // Methods added for updating loading progress bars inline std::size_t getFileSize() const { return mFileSize; } inline std::size_t getFileOffset() const { return mStream->tellg(); } // Methods added for saving/restoring context ReaderContext getContext(); // WARN: must be called immediately after reading the record header bool restoreContext(const ReaderContext& ctx); // returns the result of re-reading the header template inline void get(T& t) { mStream->read((char*)&t, sizeof(T)); } template bool getExact(T& t) { mStream->read((char*)&t, sizeof(T)); return mStream->gcount() == sizeof(T); // FIXME: try/catch block needed? } // for arrays inline bool get(void* p, std::size_t size) { mStream->read((char*)p, size); return mStream->gcount() == (std::streamsize)size; // FIXME: try/catch block needed? } // NOTE: must be called before calling getRecordHeader() void setRecHeaderSize(const std::size_t size); inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } inline unsigned int numRecords() const { return mHeader.mData.records; } void buildLStringIndex(); void getLocalizedString(std::string& str); // Read 24 bytes of header. The caller can then decide whether to process or skip the data. bool getRecordHeader(); inline const RecordHeader& hdr() const { return mCtx.recordHeader; } const GroupTypeHeader& grp(std::size_t pos = 0) const; // The object setting up this reader needs to supply the file's load order index // so that the formId's in this file can be adjusted with the file (i.e. mod) index. void setModIndex(std::uint32_t index) final { mCtx.modIndex = (index << 24) & 0xff000000; } void updateModIndices(const std::vector& files); // Maybe should throw an exception if called when not valid? const CellGrid& currCellGrid() const; inline bool hasCellGrid() const { return mCtx.cellGridValid; } // This is set while loading a CELL record (XCLC sub record) and invalidated // each time loading a CELL (see clearCellGrid()) inline void setCurrCellGrid(const CellGrid& currCell) { mCtx.cellGridValid = true; mCtx.currCellGrid = currCell; } // FIXME: This is called each time a new CELL record is read. Rather than calling this // methos explicitly, mCellGridValid should be set automatically somehow. // // Cell 2c143 is loaded immedicatly after 1bdb1 and can mistakely appear to have grid 0, 1. inline void clearCellGrid() { mCtx.cellGridValid = false; } // Should be set at the beginning of a CELL load inline void setCurrCell(FormId formId) { mCtx.currCell = formId; } inline FormId currCell() const { return mCtx.currCell; } // Should be set at the beginning of a WRLD load inline void setCurrWorld(FormId formId) { mCtx.currWorld = formId; } inline FormId currWorld() const { return mCtx.currWorld; } // Get the data part of a record // Note: assumes the header was read correctly and nothing else was read void getRecordData(bool dump = false); // Skip the data part of a record // Note: assumes the header was read correctly (partial skip is allowed) void skipRecordData(); // Skip the remaining part of the group // Note: assumes the header was read correctly and group was pushed onto the stack void skipGroupData(); // Skip the group without pushing onto the stack // Note: assumes the header was read correctly and group was not pushed onto the stack // (expected to be used during development only while some groups are not yet supported) void skipGroup(); // Read 6 bytes of header. The caller can then decide whether to process or skip the data. bool getSubRecordHeader(); // Manally update (i.e. increase) the bytes read after SUB_XXXX inline void updateRecordRead(std::uint32_t subSize) { mCtx.recordRead += subSize; } inline const SubRecordHeader& subRecordHeader() const { return mCtx.subRecordHeader; } // Skip the data part of a subrecord // Note: assumes the header was read correctly and nothing else was read void skipSubRecordData(); // Special for a subrecord following a XXXX subrecord void skipSubRecordData(std::uint32_t size); // Get a subrecord of a particular type and data type template bool getSubRecord(const ESM4::SubRecordTypes type, T& t) { ESM4::SubRecordHeader hdr; if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) return false; return get(t); } // ModIndex adjusted formId according to master file dependencies void adjustFormId(FormId& id); bool getFormId(FormId& id); void adjustGRUPFormId(); // Note: uses the string size from the subrecord header rather than checking null termination bool getZString(std::string& str) { return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder, true); } bool getString(std::string& str) { return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder); } void enterGroup(); void exitGroupCheck(); // for debugging only size_t stackSize() const { return mCtx.groupStack.size(); } // Used for error handling [[noreturn]] void fail(const std::string& msg); }; } #endif // ESM4_READER_H openmw-openmw-0.48.0/components/esm4/records.hpp000066400000000000000000000062441445372753700216510ustar00rootroot00000000000000#ifndef COMPONENTS_ESM4_RECORDS_H #define COMPONENTS_ESM4_RECORDS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif openmw-openmw-0.48.0/components/esm4/reference.hpp000066400000000000000000000035061445372753700221440ustar00rootroot00000000000000/* Copyright (C) 2020 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. */ #ifndef ESM4_REFERENCE_H #define ESM4_REFERENCE_H #include #include #include "formid.hpp" namespace ESM4 { #pragma pack(push, 1) struct Vector3 { float x; float y; float z; }; // REFR, ACHR, ACRE struct Placement { Vector3 pos; Vector3 rot; // angles are in radian, rz applied first and rx applied last }; // REFR, ACHR, ACRE struct EnableParent { FormId parent; std::uint32_t flags; //0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In }; #pragma pack(pop) struct LODReference { FormId baseObj; Placement placement; float scale; }; } #endif // ESM4_REFERENCE_H openmw-openmw-0.48.0/components/esm4/script.hpp000066400000000000000000000316701445372753700215150ustar00rootroot00000000000000/* Copyright (C) 2020-2021 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. cc9cii cc9c@iinet.net.au Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. */ #ifndef ESM4_SCRIPT_H #define ESM4_SCRIPT_H #include #include #include namespace ESM4 { enum EmotionType { EMO_Neutral = 0, EMO_Anger = 1, EMO_Disgust = 2, EMO_Fear = 3, EMO_Sad = 4, EMO_Happy = 5, EMO_Surprise = 6, EMO_Pained = 7 // FO3/FONV }; enum ConditionTypeAndFlag { // flag CTF_Combine = 0x01, CTF_RunOnTarget = 0x02, CTF_UseGlobal = 0x04, // condition CTF_EqualTo = 0x00, CTF_NotEqualTo = 0x20, CTF_GreaterThan = 0x40, CTF_GrThOrEqTo = 0x60, CTF_LessThan = 0x80, CTF_LeThOrEqTo = 0xA0 }; enum FunctionIndices { FUN_GetDistance = 1, FUN_GetLocked = 5, FUN_GetPos = 6, FUN_GetAngle = 8, FUN_GetStartingPos = 10, FUN_GetStartingAngle = 11, FUN_GetSecondsPassed = 12, FUN_GetActorValue = 14, FUN_GetCurrentTime = 18, FUN_GetScale = 24, FUN_IsMoving = 25, FUN_IsTurning = 26, FUN_GetLineOfSight = 27, FUN_GetIsInSameCell = 32, FUN_GetDisabled = 35, FUN_GetMenuMode = 36, FUN_GetDisease = 39, FUN_GetVampire = 40, FUN_GetClothingValue = 41, FUN_SameFaction = 42, FUN_SameRace = 43, FUN_SameSex = 44, FUN_GetDetected = 45, FUN_GetDead = 46, FUN_GetItemCount = 47, FUN_GetGold = 48, FUN_GetSleeping = 49, FUN_GetTalkedToPC = 50, FUN_GetScriptVariable = 53, FUN_GetQuestRunning = 56, FUN_GetStage = 58, FUN_GetStageDone = 59, FUN_GetFactionRankDifference = 60, FUN_GetAlarmed = 61, FUN_IsRaining = 62, FUN_GetAttacked = 63, FUN_GetIsCreature = 64, FUN_GetLockLevel = 65, FUN_GetShouldAttack = 66, FUN_GetInCell = 67, FUN_GetIsClass = 68, FUN_GetIsRace = 69, FUN_GetIsSex = 70, FUN_GetInFaction = 71, FUN_GetIsID = 72, FUN_GetFactionRank = 73, FUN_GetGlobalValue = 74, FUN_IsSnowing = 75, FUN_GetDisposition = 76, FUN_GetRandomPercent = 77, FUN_GetQuestVariable = 79, FUN_GetLevel = 80, FUN_GetArmorRating = 81, FUN_GetDeadCount = 84, FUN_GetIsAlerted = 91, FUN_GetPlayerControlsDisabled = 98, FUN_GetHeadingAngle = 99, FUN_IsWeaponOut = 101, FUN_IsTorchOut = 102, FUN_IsShieldOut = 103, FUN_IsFacingUp = 106, FUN_GetKnockedState = 107, FUN_GetWeaponAnimType = 108, FUN_IsWeaponSkillType = 109, FUN_GetCurrentAIPackage = 110, FUN_IsWaiting = 111, FUN_IsIdlePlaying = 112, FUN_GetMinorCrimeCount = 116, FUN_GetMajorCrimeCount = 117, FUN_GetActorAggroRadiusViolated = 118, FUN_GetCrime = 122, FUN_IsGreetingPlayer = 123, FUN_IsGuard = 125, FUN_HasBeenEaten = 127, FUN_GetFatiguePercentage = 128, FUN_GetPCIsClass = 129, FUN_GetPCIsRace = 130, FUN_GetPCIsSex = 131, FUN_GetPCInFaction = 132, FUN_SameFactionAsPC = 133, FUN_SameRaceAsPC = 134, FUN_SameSexAsPC = 135, FUN_GetIsReference = 136, FUN_IsTalking = 141, FUN_GetWalkSpeed = 142, FUN_GetCurrentAIProcedure = 143, FUN_GetTrespassWarningLevel = 144, FUN_IsTrespassing = 145, FUN_IsInMyOwnedCell = 146, FUN_GetWindSpeed = 147, FUN_GetCurrentWeatherPercent = 148, FUN_GetIsCurrentWeather = 149, FUN_IsContinuingPackagePCNear = 150, FUN_CanHaveFlames = 153, FUN_HasFlames = 154, FUN_GetOpenState = 157, FUN_GetSitting = 159, FUN_GetFurnitureMarkerID = 160, FUN_GetIsCurrentPackage = 161, FUN_IsCurrentFurnitureRef = 162, FUN_IsCurrentFurnitureObj = 163, FUN_GetDayofWeek = 170, FUN_GetTalkedToPCParam = 172, FUN_IsPCSleeping = 175, FUN_IsPCAMurderer = 176, FUN_GetDetectionLevel = 180, FUN_GetEquipped = 182, FUN_IsSwimming = 185, FUN_GetAmountSoldStolen = 190, FUN_GetIgnoreCrime = 192, FUN_GetPCExpelled = 193, FUN_GetPCFactionMurder = 195, FUN_GetPCEnemyofFaction = 197, FUN_GetPCFactionAttack = 199, FUN_GetDestroyed = 203, FUN_HasMagicEffect = 214, FUN_GetDefaultOpen = 215, FUN_GetAnimAction = 219, FUN_IsSpellTarget = 223, FUN_GetVATSMode = 224, FUN_GetPersuasionNumber = 225, FUN_GetSandman = 226, FUN_GetCannibal = 227, FUN_GetIsClassDefault = 228, FUN_GetClassDefaultMatch = 229, FUN_GetInCellParam = 230, FUN_GetVatsTargetHeight = 235, FUN_GetIsGhost = 237, FUN_GetUnconscious = 242, FUN_GetRestrained = 244, FUN_GetIsUsedItem = 246, FUN_GetIsUsedItemType = 247, FUN_GetIsPlayableRace = 254, FUN_GetOffersServicesNow = 255, FUN_GetUsedItemLevel = 258, FUN_GetUsedItemActivate = 259, FUN_GetBarterGold = 264, FUN_IsTimePassing = 265, FUN_IsPleasant = 266, FUN_IsCloudy = 267, FUN_GetArmorRatingUpperBody = 274, FUN_GetBaseActorValue = 277, FUN_IsOwner = 278, FUN_IsCellOwner = 280, FUN_IsHorseStolen = 282, FUN_IsLeftUp = 285, FUN_IsSneaking = 286, FUN_IsRunning = 287, FUN_GetFriendHit = 288, FUN_IsInCombat = 289, FUN_IsInInterior = 300, FUN_IsWaterObject = 304, FUN_IsActorUsingATorch = 306, FUN_IsXBox = 309, FUN_GetInWorldspace = 310, FUN_GetPCMiscStat = 312, FUN_IsActorEvil = 313, FUN_IsActorAVictim = 314, FUN_GetTotalPersuasionNumber = 315, FUN_GetIdleDoneOnce = 318, FUN_GetNoRumors = 320, FUN_WhichServiceMenu = 323, FUN_IsRidingHorse = 327, FUN_IsInDangerousWater = 332, FUN_GetIgnoreFriendlyHits = 338, FUN_IsPlayersLastRiddenHorse = 339, FUN_IsActor = 353, FUN_IsEssential = 354, FUN_IsPlayerMovingIntoNewSpace = 358, FUN_GetTimeDead = 361, FUN_GetPlayerHasLastRiddenHorse = 362, FUN_IsChild = 365, FUN_GetLastPlayerAction = 367, FUN_IsPlayerActionActive = 368, FUN_IsTalkingActivatorActor = 370, FUN_IsInList = 372, FUN_GetHasNote = 382, FUN_GetHitLocation = 391, FUN_IsPC1stPerson = 392, FUN_GetCauseofDeath = 397, FUN_IsLimbGone = 398, FUN_IsWeaponInList = 399, FUN_HasFriendDisposition = 403, FUN_GetVATSValue = 408, FUN_IsKiller = 409, FUN_IsKillerObject = 410, FUN_GetFactionCombatReaction = 411, FUN_Exists = 415, FUN_GetGroupMemberCount = 416, FUN_GetGroupTargetCount = 417, FUN_GetObjectiveCompleted = 420, FUN_GetObjectiveDisplayed = 421, FUN_GetIsVoiceType = 427, FUN_GetPlantedExplosive = 428, FUN_IsActorTalkingThroughActivator = 430, FUN_GetHealthPercentage = 431, FUN_GetIsObjectType = 433, FUN_GetDialogueEmotion = 435, FUN_GetDialogueEmotionValue = 436, FUN_GetIsCreatureType = 438, FUN_GetInZone = 446, FUN_HasPerk = 449, FUN_GetFactionRelation = 450, FUN_IsLastIdlePlayed = 451, FUN_GetPlayerTeammate = 454, FUN_GetPlayerTeammateCount = 455, FUN_GetActorCrimePlayerEnemy = 459, FUN_GetActorFactionPlayerEnemy = 460, FUN_IsPlayerTagSkill = 462, FUN_IsPlayerGrabbedRef = 464, FUN_GetDestructionStage = 471, FUN_GetIsAlignment = 474, FUN_GetThreatRatio = 478, FUN_GetIsUsedItemEquipType = 480, FUN_GetConcussed = 489, FUN_GetMapMarkerVisible = 492, FUN_GetPermanentActorValue = 495, FUN_GetKillingBlowLimb = 496, FUN_GetWeaponHealthPerc = 500, FUN_GetRadiationLevel = 503, FUN_GetLastHitCritical = 510, FUN_IsCombatTarget = 515, FUN_GetVATSRightAreaFree = 518, FUN_GetVATSLeftAreaFree = 519, FUN_GetVATSBackAreaFree = 520, FUN_GetVATSFrontAreaFree = 521, FUN_GetIsLockBroken = 522, FUN_IsPS3 = 523, FUN_IsWin32 = 524, FUN_GetVATSRightTargetVisible = 525, FUN_GetVATSLeftTargetVisible = 526, FUN_GetVATSBackTargetVisible = 527, FUN_GetVATSFrontTargetVisible = 528, FUN_IsInCriticalStage = 531, FUN_GetXPForNextLevel = 533, FUN_GetQuestCompleted = 546, FUN_IsGoreDisabled = 550, FUN_GetSpellUsageNum = 555, FUN_GetActorsInHigh = 557, FUN_HasLoaded3D = 558, FUN_GetReputation = 573, FUN_GetReputationPct = 574, FUN_GetReputationThreshold = 575, FUN_IsHardcore = 586, FUN_GetForceHitReaction = 601, FUN_ChallengeLocked = 607, FUN_GetCasinoWinningStage = 610, FUN_PlayerInRegion = 612, FUN_GetChallengeCompleted = 614, FUN_IsAlwaysHardcore = 619 }; #pragma pack(push, 1) struct TargetResponseData { std::uint32_t emoType; // EmotionType std::int32_t emoValue; std::uint32_t unknown1; std::uint32_t responseNo; // 1 byte + padding // below FO3/FONV FormId sound; // when 20 bytes usually 0 but there are exceptions (FO3 INFO FormId = 0x0002241f) std::uint32_t flags; // 1 byte + padding (0x01 = use emotion anim) }; struct TargetCondition { std::uint32_t condition; // ConditionTypeAndFlag + padding float comparison; // WARN: can be GLOB FormId if flag set std::uint32_t functionIndex; std::uint32_t param1; // FIXME: if formid needs modindex adjustment or not? std::uint32_t param2; std::uint32_t runOn; // 0 subject, 1 target, 2 reference, 3 combat target, 4 linked reference // below FO3/FONV/TES5 FormId reference; }; struct ScriptHeader { std::uint32_t unused; std::uint32_t refCount; std::uint32_t compiledSize; std::uint32_t variableCount; std::uint16_t type; // 0 object, 1 quest, 0x100 effect std::uint16_t flag; // 0x01 enabled }; #pragma pack(pop) struct ScriptLocalVariableData { // SLSD std::uint32_t index; std::uint32_t unknown1; std::uint32_t unknown2; std::uint32_t unknown3; std::uint32_t type; std::uint32_t unknown4; // SCVR std::string variableName; void clear() { index = 0; type = 0; variableName.clear(); } }; struct ScriptDefinition { ScriptHeader scriptHeader; // SDCA compiled source std::string scriptSource; std::vector localVarData; std::vector localRefVarIndex; FormId globReference; }; } #endif // ESM4_SCRIPT_H openmw-openmw-0.48.0/components/esmloader/000077500000000000000000000000001445372753700205745ustar00rootroot00000000000000openmw-openmw-0.48.0/components/esmloader/esmdata.cpp000066400000000000000000000051151445372753700227200ustar00rootroot00000000000000#include "esmdata.hpp" #include "lessbyid.hpp" #include "record.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EsmLoader { namespace { template auto returnAs(F&& f) { using Result = decltype(std::forward(f)(ESM::Static {})); if constexpr (!std::is_same_v) return Result {}; } template auto withStatic(std::string_view refId, const std::vector& values, F&& f) { const auto it = std::lower_bound(values.begin(), values.end(), refId, LessById {}); if (it == values.end() || it->mId != refId) return returnAs(std::forward(f)); return std::forward(f)(*it); } template auto withStatic(std::string_view refId, ESM::RecNameInts type, const EsmData& content, F&& f) { switch (type) { case ESM::REC_ACTI: return withStatic(refId, content.mActivators, std::forward(f)); case ESM::REC_CONT: return withStatic(refId, content.mContainers, std::forward(f)); case ESM::REC_DOOR: return withStatic(refId, content.mDoors, std::forward(f)); case ESM::REC_STAT: return withStatic(refId, content.mStatics, std::forward(f)); default: break; } return returnAs(std::forward(f)); } } EsmData::~EsmData() {} std::string_view getModel(const EsmData& content, std::string_view refId, ESM::RecNameInts type) { return withStatic(refId, type, content, [] (const auto& v) { return std::string_view(v.mModel); }); } ESM::Variant getGameSetting(const std::vector& records, std::string_view id) { const std::string lower = Misc::StringUtils::lowerCase(id); auto it = std::lower_bound(records.begin(), records.end(), lower, LessById {}); if (it == records.end() || it->mId != lower) throw std::runtime_error("Game settings \"" + std::string(id) + "\" is not found"); return it->mValue; } } openmw-openmw-0.48.0/components/esmloader/esmdata.hpp000066400000000000000000000022661445372753700227310ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESMLOADER_ESMDATA_H #define OPENMW_COMPONENTS_ESMLOADER_ESMDATA_H #include #include #include namespace ESM { struct Activator; struct Cell; struct Container; struct Door; struct GameSetting; struct Land; struct Static; class Variant; } namespace EsmLoader { struct RefIdWithType { std::string_view mId; ESM::RecNameInts mType; }; struct EsmData { std::vector mActivators; std::vector mCells; std::vector mContainers; std::vector mDoors; std::vector mGameSettings; std::vector mLands; std::vector mStatics; std::vector mRefIdTypes; EsmData() = default; EsmData(const EsmData&) = delete; EsmData(EsmData&&) = default; ~EsmData(); }; std::string_view getModel(const EsmData& content, std::string_view refId, ESM::RecNameInts type); ESM::Variant getGameSetting(const std::vector& records, std::string_view id); } #endif openmw-openmw-0.48.0/components/esmloader/lessbyid.hpp000066400000000000000000000007271445372753700231310ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_CONTENT_LESSBYID_H #define OPENMW_COMPONENTS_CONTENT_LESSBYID_H #include namespace EsmLoader { struct LessById { template bool operator()(const T& lhs, const T& rhs) const { return lhs.mId < rhs.mId; } template bool operator()(const T& lhs, std::string_view rhs) const { return lhs.mId < rhs; } }; } #endif openmw-openmw-0.48.0/components/esmloader/load.cpp000066400000000000000000000322671445372753700222310ustar00rootroot00000000000000#include "load.hpp" #include "esmdata.hpp" #include "lessbyid.hpp" #include "record.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EsmLoader { namespace { struct GetKey { template decltype(auto) operator()(const T& v) const { return (v.mId); } const ESM::CellId& operator()(const ESM::Cell& v) const { return v.mCellId; } std::pair operator()(const ESM::Land& v) const { return std::pair(v.mX, v.mY); } template decltype(auto) operator()(const Record& v) const { return (*this)(v.mValue); } }; struct CellRecords { Records mValues; std::map mByName; std::map, std::size_t> mByPosition; }; template > struct HasId : std::false_type {}; template struct HasId> : std::true_type {}; template constexpr bool hasId = HasId::value; template auto loadRecord(ESM::ESMReader& reader, Records& records) -> std::enable_if_t> { T record; bool deleted = false; record.load(reader, deleted); Misc::StringUtils::lowerCaseInPlace(record.mId); if (Misc::ResourceHelpers::isHiddenMarker(record.mId)) return; records.emplace_back(deleted, std::move(record)); } template auto loadRecord(ESM::ESMReader& reader, Records& records) -> std::enable_if_t> { T record; bool deleted = false; record.load(reader, deleted); records.emplace_back(deleted, std::move(record)); } void loadRecord(ESM::ESMReader& reader, CellRecords& records) { ESM::Cell record; bool deleted = false; record.loadNameAndData(reader, deleted); Misc::StringUtils::lowerCaseInPlace(record.mName); if ((record.mData.mFlags & ESM::Cell::Interior) != 0) { const auto it = records.mByName.find(record.mName); if (it == records.mByName.end()) { record.loadCell(reader, true); records.mByName.emplace_hint(it, record.mName, records.mValues.size()); records.mValues.emplace_back(deleted, std::move(record)); } else { Record& old = records.mValues[it->second]; old.mValue.mData = record.mData; old.mValue.loadCell(reader, true); } } else { const std::pair position(record.mData.mX, record.mData.mY); const auto it = records.mByPosition.find(position); if (it == records.mByPosition.end()) { record.loadCell(reader, true); records.mByPosition.emplace_hint(it, position, records.mValues.size()); records.mValues.emplace_back(deleted, std::move(record)); } else { Record& old = records.mValues[it->second]; old.mValue.mData = record.mData; old.mValue.loadCell(reader, true); } } } struct ShallowContent { Records mActivators; CellRecords mCells; Records mContainers; Records mDoors; Records mGameSettings; Records mLands; Records mStatics; }; void loadRecord(const Query& query, const ESM::NAME& name, ESM::ESMReader& reader, ShallowContent& content) { switch (name.toInt()) { case ESM::REC_ACTI: if (query.mLoadActivators) return loadRecord(reader, content.mActivators); break; case ESM::REC_CELL: if (query.mLoadCells) return loadRecord(reader, content.mCells); break; case ESM::REC_CONT: if (query.mLoadContainers) return loadRecord(reader, content.mContainers); break; case ESM::REC_DOOR: if (query.mLoadDoors) return loadRecord(reader, content.mDoors); break; case ESM::REC_GMST: if (query.mLoadGameSettings) return loadRecord(reader, content.mGameSettings); break; case ESM::REC_LAND: if (query.mLoadLands) return loadRecord(reader, content.mLands); break; case ESM::REC_STAT: if (query.mLoadStatics) return loadRecord(reader, content.mStatics); break; } reader.skipRecord(); } void loadEsm(const Query& query, ESM::ESMReader& reader, ShallowContent& content, Loading::Listener* listener) { Log(Debug::Info) << "Loading ESM file " << reader.getName(); while (reader.hasMoreRecs()) { const ESM::NAME recName = reader.getRecName(); reader.getRecHeader(); if (reader.getRecordFlags() & ESM::FLAG_Ignored) { reader.skipRecord(); continue; } loadRecord(query, recName, reader, content); if (listener != nullptr) listener->setProgress(fileProgress * reader.getFileOffset() / reader.getFileSize()); } } ShallowContent shallowLoad(const Query& query, const std::vector& contentFiles, const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { ShallowContent result; const std::set supportedFormats { ".esm", ".esp", ".omwgame", ".omwaddon", ".project", }; for (std::size_t i = 0; i < contentFiles.size(); ++i) { const std::string &file = contentFiles[i]; const std::string extension = Misc::StringUtils::lowerCase(boost::filesystem::path(file).extension().string()); if (supportedFormats.find(extension) == supportedFormats.end()) { Log(Debug::Warning) << "Skipping unsupported content file: " << file; continue; } if (listener != nullptr) { listener->setLabel(file); listener->setProgressRange(fileProgress); } const Files::MultiDirCollection& collection = fileCollections.getCollection(extension); const ESM::ReadersCache::BusyItem reader = readers.get(i); reader->setEncoder(encoder); reader->setIndex(static_cast(i)); reader->open(collection.getPath(file).string()); if (query.mLoadCells) reader->resolveParentFileIndices(readers); loadEsm(query, *reader, result, listener); } return result; } struct WithType { ESM::RecNameInts mType; template RefIdWithType operator()(const T& v) const { return {v.mId, mType}; } }; template void addRefIdsTypes(const std::vector& values, std::vector& refIdsTypes) { std::transform(values.begin(), values.end(), std::back_inserter(refIdsTypes), WithType {static_cast(T::sRecordId)}); } void addRefIdsTypes(EsmData& content) { content.mRefIdTypes.reserve( content.mActivators.size() + content.mContainers.size() + content.mDoors.size() + content.mStatics.size() ); addRefIdsTypes(content.mActivators, content.mRefIdTypes); addRefIdsTypes(content.mContainers, content.mRefIdTypes); addRefIdsTypes(content.mDoors, content.mRefIdTypes); addRefIdsTypes(content.mStatics, content.mRefIdTypes); std::sort(content.mRefIdTypes.begin(), content.mRefIdTypes.end(), LessById {}); } std::vector prepareCellRecords(Records& records) { std::vector result; for (Record& v : records) if (!v.mDeleted) result.emplace_back(std::move(v.mValue)); return result; } } EsmData loadEsmData(const Query& query, const std::vector& contentFiles, const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { Log(Debug::Info) << "Loading ESM data..."; ShallowContent content = shallowLoad(query, contentFiles, fileCollections, readers, encoder, listener); std::ostringstream loaded; if (query.mLoadActivators) loaded << ' ' << content.mActivators.size() << " activators,"; if (query.mLoadCells) loaded << ' ' << content.mCells.mValues.size() << " cells,"; if (query.mLoadContainers) loaded << ' ' << content.mContainers.size() << " containers,"; if (query.mLoadDoors) loaded << ' ' << content.mDoors.size() << " doors,"; if (query.mLoadGameSettings) loaded << ' ' << content.mGameSettings.size() << " game settings,"; if (query.mLoadLands) loaded << ' ' << content.mLands.size() << " lands,"; if (query.mLoadStatics) loaded << ' ' << content.mStatics.size() << " statics,"; Log(Debug::Info) << "Loaded" << loaded.str(); EsmData result; if (query.mLoadActivators) result.mActivators = prepareRecords(content.mActivators, GetKey {}); if (query.mLoadCells) result.mCells = prepareCellRecords(content.mCells.mValues); if (query.mLoadContainers) result.mContainers = prepareRecords(content.mContainers, GetKey {}); if (query.mLoadDoors) result.mDoors = prepareRecords(content.mDoors, GetKey {}); if (query.mLoadGameSettings) result.mGameSettings = prepareRecords(content.mGameSettings, GetKey {}); if (query.mLoadLands) result.mLands = prepareRecords(content.mLands, GetKey {}); if (query.mLoadStatics) result.mStatics = prepareRecords(content.mStatics, GetKey {}); addRefIdsTypes(result); std::ostringstream prepared; if (query.mLoadActivators) prepared << ' ' << result.mActivators.size() << " unique activators,"; if (query.mLoadCells) prepared << ' ' << result.mCells.size() << " unique cells,"; if (query.mLoadContainers) prepared << ' ' << result.mContainers.size() << " unique containers,"; if (query.mLoadDoors) prepared << ' ' << result.mDoors.size() << " unique doors,"; if (query.mLoadGameSettings) prepared << ' ' << result.mGameSettings.size() << " unique game settings,"; if (query.mLoadLands) prepared << ' ' << result.mLands.size() << " unique lands,"; if (query.mLoadStatics) prepared << ' ' << result.mStatics.size() << " unique statics,"; Log(Debug::Info) << "Prepared" << prepared.str(); return result; } } openmw-openmw-0.48.0/components/esmloader/load.hpp000066400000000000000000000016461445372753700222330ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESMLOADER_LOAD_H #define OPENMW_COMPONENTS_ESMLOADER_LOAD_H #include #include #include namespace ToUTF8 { class Utf8Encoder; } namespace Files { class Collections; } namespace Loading { class Listener; } namespace EsmLoader { struct EsmData; inline constexpr std::size_t fileProgress = 1000; struct Query { bool mLoadActivators = false; bool mLoadCells = false; bool mLoadContainers = false; bool mLoadDoors = false; bool mLoadGameSettings = false; bool mLoadLands = false; bool mLoadStatics = false; }; EsmData loadEsmData(const Query& query, const std::vector& contentFiles, const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener = nullptr); } #endif openmw-openmw-0.48.0/components/esmloader/record.hpp000066400000000000000000000024011445372753700225600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_ESMLOADER_RECORD_H #define OPENMW_COMPONENTS_ESMLOADER_RECORD_H #include #include #include #include #include namespace EsmLoader { template struct Record { bool mDeleted; T mValue; template explicit Record(bool deleted, Args&& ... args) : mDeleted(deleted) , mValue(std::forward(args) ...) {} }; template using Records = std::vector>; template inline std::vector prepareRecords(Records& records, const GetKey& getKey) { const auto greaterByKey = [&] (const auto& l, const auto& r) { return getKey(r) < getKey(l); }; const auto equalByKey = [&] (const auto& l, const auto& r) { return getKey(l) == getKey(r); }; std::stable_sort(records.begin(), records.end(), greaterByKey); std::vector result; Misc::forEachUnique(records.rbegin(), records.rend(), equalByKey, [&] (const auto& v) { if (!v.mDeleted) result.emplace_back(std::move(v.mValue)); }); return result; } } #endif openmw-openmw-0.48.0/components/fallback/000077500000000000000000000000001445372753700203605ustar00rootroot00000000000000openmw-openmw-0.48.0/components/fallback/fallback.cpp000066400000000000000000000043011445372753700226210ustar00rootroot00000000000000#include "fallback.hpp" #include #include namespace Fallback { std::map Map::mFallbackMap; void Map::init(const std::map& fallback) { mFallbackMap = fallback; } std::string Map::getString(const std::string& fall) { std::map::const_iterator it; if ((it = mFallbackMap.find(fall)) == mFallbackMap.end()) { return std::string(); } return it->second; } float Map::getFloat(const std::string& fall) { const std::string& fallback = getString(fall); if (!fallback.empty()) { std::stringstream stream(fallback); float number = 0.f; stream >> number; return number; } return 0; } int Map::getInt(const std::string& fall) { const std::string& fallback = getString(fall); if (!fallback.empty()) { std::stringstream stream(fallback); int number = 0; stream >> number; return number; } return 0; } bool Map::getBool(const std::string& fall) { const std::string& fallback = getString(fall); return !fallback.empty() && fallback != "0"; } osg::Vec4f Map::getColour(const std::string& fall) { const std::string& sum = getString(fall); if (!sum.empty()) { try { std::string ret[3]; unsigned int j = 0; for (unsigned int i = 0; i < sum.length(); ++i) { if(sum[i]==',') j++; else if (sum[i] != ' ') ret[j]+=sum[i]; } return osg::Vec4f(std::stoi(ret[0])/255.f,std::stoi(ret[1])/255.f,std::stoi(ret[2])/255.f, 1.f); } catch (const std::invalid_argument&) { Log(Debug::Error) << "Error: '" << fall << "' setting value (" << sum << ") is not a valid color, using middle gray as a fallback"; } } return osg::Vec4f(0.5f,0.5f,0.5f,1.f); } } openmw-openmw-0.48.0/components/fallback/fallback.hpp000066400000000000000000000013331445372753700226300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FALLBACK_H #define OPENMW_COMPONENTS_FALLBACK_H #include #include #include namespace Fallback { /// @brief contains settings imported from the Morrowind INI file. class Map { static std::map mFallbackMap; public: static void init(const std::map& fallback); static std::string getString(const std::string& fall); static float getFloat(const std::string& fall); static int getInt(const std::string& fall); static bool getBool(const std::string& fall); static osg::Vec4f getColour(const std::string& fall); }; } #endif openmw-openmw-0.48.0/components/fallback/validate.cpp000066400000000000000000000013521445372753700226560ustar00rootroot00000000000000#include "validate.hpp" #include #include void Fallback::validate(boost::any& v, std::vector const& tokens, FallbackMap*, int) { if (v.empty()) { v = boost::any(FallbackMap()); } FallbackMap *map = boost::any_cast(&v); for (const auto& token : tokens) { size_t sep = token.find(','); if (sep < 1 || sep == token.length() - 1 || sep == std::string::npos) throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); std::string key(token.substr(0, sep)); std::string value(token.substr(sep + 1)); map->mMap[key] = value; } } openmw-openmw-0.48.0/components/fallback/validate.hpp000066400000000000000000000011041445372753700226560ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FALLBACK_VALIDATE_H #define OPENMW_COMPONENTS_FALLBACK_VALIDATE_H #include #include #include // Parses and validates a fallback map from boost program_options. // Note: for boost to pick up the validate function, you need to pull in the namespace e.g. // by using namespace Fallback; namespace boost { class any; } namespace Fallback { struct FallbackMap { std::map mMap; }; void validate(boost::any &v, std::vector const &tokens, FallbackMap*, int); } #endif openmw-openmw-0.48.0/components/files/000077500000000000000000000000001445372753700177235ustar00rootroot00000000000000openmw-openmw-0.48.0/components/files/androidpath.cpp000066400000000000000000000041741445372753700227320ustar00rootroot00000000000000#include "androidpath.hpp" #if defined(__ANDROID__) #include #include #include #include #include #include static const char *g_path_global; //< Path to global directory root, e.g. /data/data/com.libopenmw.openmw static const char *g_path_user; //< Path to user root, e.g. /sdcard/Android/data/com.libopenmw.openmw /** * \brief Called by java code to set up directory paths */ extern "C" JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring global, jstring user) { g_path_global = env->GetStringUTFChars(global, nullptr); g_path_user = env->GetStringUTFChars(user, nullptr); } namespace Files { AndroidPath::AndroidPath(const std::string& application_name) { } // /sdcard/Android/data/com.libopenmw.openmw/config boost::filesystem::path AndroidPath::getUserConfigPath() const { return boost::filesystem::path(g_path_user) / "config"; } // /sdcard/Android/data/com.libopenmw.openmw/ // (so that saves are placed at /sdcard/Android/data/com.libopenmw.openmw/saves) boost::filesystem::path AndroidPath::getUserDataPath() const { return boost::filesystem::path(g_path_user); } // /data/data/com.libopenmw.openmw/cache // (supposed to be "official" android cache location) boost::filesystem::path AndroidPath::getCachePath() const { return boost::filesystem::path(g_path_global) / "cache"; } // /data/data/com.libopenmw.openmw/files/config // (note the addition of "files") boost::filesystem::path AndroidPath::getGlobalConfigPath() const { return boost::filesystem::path(g_path_global) / "files" / "config"; } boost::filesystem::path AndroidPath::getLocalPath() const { return boost::filesystem::path("./"); } // /sdcard/Android/data/com.libopenmw.openmw // (so that the data is at /sdcard/Android/data/com.libopenmw.openmw/data) boost::filesystem::path AndroidPath::getGlobalDataPath() const { return boost::filesystem::path(g_path_user); } boost::filesystem::path AndroidPath::getInstallPath() const { return boost::filesystem::path(); } } /* namespace Files */ #endif /* defined(__Android__) */ openmw-openmw-0.48.0/components/files/androidpath.hpp000066400000000000000000000022651445372753700227360ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_ANDROIDPATH_H #define COMPONENTS_FILES_ANDROIDPATH_H #if defined(__ANDROID__) #include /** * \namespace Files */ namespace Files { struct AndroidPath { AndroidPath(const std::string& application_name); /** * \brief Return path to the user directory. */ boost::filesystem::path getUserConfigPath() const; boost::filesystem::path getUserDataPath() const; /** * \brief Return path to the global (system) directory where config files can be placed. */ boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime configuration directory which is the * place where an application was started. */ boost::filesystem::path getLocalPath() const; /** * \brief Return path to the global (system) directory where game files can be placed. */ boost::filesystem::path getGlobalDataPath() const; /** * \brief */ boost::filesystem::path getCachePath() const; boost::filesystem::path getInstallPath() const; }; } /* namespace Files */ #endif /* defined(__Android__) */ #endif /* COMPONENTS_FILES_ANDROIDPATH_H */ openmw-openmw-0.48.0/components/files/collections.cpp000066400000000000000000000050461445372753700227520ustar00rootroot00000000000000#include "collections.hpp" #include namespace Files { Collections::Collections() : mDirectories() , mFoldCase(false) , mCollections() { } Collections::Collections(const Files::PathContainer& directories, bool foldCase) : mDirectories(directories) , mFoldCase(foldCase) , mCollections() { } const MultiDirCollection& Collections::getCollection(const std::string& extension) const { std::string ext = Misc::StringUtils::lowerCase(extension); auto iter = mCollections.find(ext); if (iter==mCollections.end()) { std::pair result = mCollections.emplace(ext, MultiDirCollection(mDirectories, ext, mFoldCase)); iter = result.first; } return iter->second; } boost::filesystem::path Collections::getPath(const std::string& file) const { for (Files::PathContainer::const_iterator iter = mDirectories.begin(); iter != mDirectories.end(); ++iter) { for (boost::filesystem::directory_iterator iter2 (*iter); iter2!=boost::filesystem::directory_iterator(); ++iter2) { boost::filesystem::path path = *iter2; if (mFoldCase) { if (Misc::StringUtils::ciEqual(file, path.filename().string())) return path.string(); } else if (path.filename().string() == file) return path.string(); } } throw std::runtime_error ("file " + file + " not found"); } bool Collections::doesExist(const std::string& file) const { for (Files::PathContainer::const_iterator iter = mDirectories.begin(); iter != mDirectories.end(); ++iter) { for (boost::filesystem::directory_iterator iter2 (*iter); iter2!=boost::filesystem::directory_iterator(); ++iter2) { boost::filesystem::path path = *iter2; if (mFoldCase) { if (Misc::StringUtils::ciEqual(file, path.filename().string())) return true; } else if (path.filename().string() == file) return true; } } return false; } const Files::PathContainer& Collections::getPaths() const { return mDirectories; } } openmw-openmw-0.48.0/components/files/collections.hpp000066400000000000000000000026001445372753700227500ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_COLLECTION_HPP #define COMPONENTS_FILES_COLLECTION_HPP #include #include "multidircollection.hpp" namespace Files { class Collections { public: Collections(); ///< Directories are listed with increasing priority. Collections(const Files::PathContainer& directories, bool foldCase); ///< Return a file collection for the given extension. Extension must contain the /// leading dot and must be all lower-case. const MultiDirCollection& getCollection(const std::string& extension) const; boost::filesystem::path getPath(const std::string& file) const; ///< Return full path (including filename) of \a file. /// /// If the file does not exist in any of the collection's /// directories, an exception is thrown. \a file must include the /// extension. bool doesExist(const std::string& file) const; ///< \return Does a file with the given name exist? const Files::PathContainer& getPaths() const; private: typedef std::map MultiDirCollectionContainer; Files::PathContainer mDirectories; bool mFoldCase; mutable MultiDirCollectionContainer mCollections; }; } #endif openmw-openmw-0.48.0/components/files/configfileparser.cpp000066400000000000000000000262711445372753700237610ustar00rootroot00000000000000// This file's contents is largely lifted from boost::program_options with only minor modification. // Its original preamble (without updated dates) from those source files is below: // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #include "configfileparser.hpp" #include #include namespace Files { namespace { /** Standalone parser for config files in ini-line format. The parser is a model of single-pass lvalue iterator, and default constructor creates past-the-end-iterator. The typical usage is: config_file_iterator i(is, ... set of options ...), e; for(; i !=e; ++i) { *i; } Syntax conventions: - config file can not contain positional options - '#' is comment character: it is ignored together with the rest of the line. - variable assignments are in the form name '=' value. spaces around '=' are trimmed. - Section names are given in brackets. The actual option name is constructed by combining current section name and specified option name, with dot between. If section_name already contains dot at the end, new dot is not inserted. For example: @verbatim [gui.accessibility] visual_bell=yes @endverbatim will result in option "gui.accessibility.visual_bell" with value "yes" been returned. TODO: maybe, we should just accept a pointer to options_description class. */ class common_config_file_iterator : public boost::eof_iterator { public: common_config_file_iterator() { found_eof(); } common_config_file_iterator( const std::set& allowed_options, bool allow_unregistered = false); virtual ~common_config_file_iterator() {} public: // Method required by eof_iterator void get(); #if BOOST_WORKAROUND(_MSC_VER, <= 1900) void decrement() {} void advance(difference_type) {} #endif protected: // Stubs for derived classes // Obtains next line from the config file // Note: really, this design is a bit ugly // The most clean thing would be to pass 'line_iterator' to // constructor of this class, but to avoid templating this class // we'd need polymorphic iterator, which does not exist yet. virtual bool getline(std::string&) { return false; } protected: /** Adds another allowed option. If the 'name' ends with '*', then all options with the same prefix are allowed. For example, if 'name' is 'foo*', then 'foo1' and 'foo_bar' are allowed. */ void add_option(const char* name); // Returns true if 's' is a registered option name. bool allowed_option(const std::string& s) const; // That's probably too much data for iterator, since // it will be copied, but let's not bother for now. std::set allowed_options; // Invariant: no element is prefix of other element. std::set allowed_prefixes; std::string m_prefix; bool m_allow_unregistered = false; }; common_config_file_iterator::common_config_file_iterator( const std::set& allowed_options, bool allow_unregistered) : allowed_options(allowed_options), m_allow_unregistered(allow_unregistered) { for (std::set::const_iterator i = allowed_options.begin(); i != allowed_options.end(); ++i) { add_option(i->c_str()); } } void common_config_file_iterator::add_option(const char* name) { std::string s(name); assert(!s.empty()); if (*s.rbegin() == '*') { s.resize(s.size() - 1); bool bad_prefixes(false); // If 's' is a prefix of one of allowed suffix, then // lower_bound will return that element. // If some element is prefix of 's', then lower_bound will // return the next element. std::set::iterator i = allowed_prefixes.lower_bound(s); if (i != allowed_prefixes.end()) { if (i->find(s) == 0) bad_prefixes = true; } if (i != allowed_prefixes.begin()) { --i; if (s.find(*i) == 0) bad_prefixes = true; } if (bad_prefixes) boost::throw_exception(bpo::error("options '" + std::string(name) + "' and '" + *i + "*' will both match the same " "arguments from the configuration file")); allowed_prefixes.insert(s); } } std::string trim_ws(const std::string& s) { std::string::size_type n, n2; n = s.find_first_not_of(" \t\r\n"); if (n == std::string::npos) return std::string(); else { n2 = s.find_last_not_of(" \t\r\n"); return s.substr(n, n2 - n + 1); } } void common_config_file_iterator::get() { std::string s; std::string::size_type n; bool found = false; while (this->getline(s)) { // strip '#' comments and whitespace if (s.find('#') == s.find_first_not_of(" \t\r\n")) continue; s = trim_ws(s); if (!s.empty()) { // Handle section name if (*s.begin() == '[' && *s.rbegin() == ']') { m_prefix = s.substr(1, s.size() - 2); if (*m_prefix.rbegin() != '.') m_prefix += '.'; } else if ((n = s.find('=')) != std::string::npos) { std::string name = m_prefix + trim_ws(s.substr(0, n)); std::string value = trim_ws(s.substr(n + 1)); bool registered = allowed_option(name); if (!registered && !m_allow_unregistered) boost::throw_exception(bpo::unknown_option(name)); found = true; this->value().string_key = name; this->value().value.clear(); this->value().value.push_back(value); this->value().unregistered = !registered; this->value().original_tokens.clear(); this->value().original_tokens.push_back(name); this->value().original_tokens.push_back(value); break; } else { boost::throw_exception(bpo::invalid_config_file_syntax(s, bpo::invalid_syntax::unrecognized_line)); } } } if (!found) found_eof(); } bool common_config_file_iterator::allowed_option(const std::string& s) const { std::set::const_iterator i = allowed_options.find(s); if (i != allowed_options.end()) return true; // If s is "pa" where "p" is allowed prefix then // lower_bound should find the element after "p". // This depends on 'allowed_prefixes' invariant. i = allowed_prefixes.lower_bound(s); if (i != allowed_prefixes.begin() && s.find(*--i) == 0) return true; return false; } template class basic_config_file_iterator : public Files::common_config_file_iterator { public: basic_config_file_iterator() { found_eof(); } /** Creates a config file parser for the specified stream. */ basic_config_file_iterator(std::basic_istream& is, const std::set& allowed_options, bool allow_unregistered = false); private: // base overrides bool getline(std::string&); private: // internal data std::shared_ptr > is; }; template basic_config_file_iterator:: basic_config_file_iterator(std::basic_istream& is, const std::set& allowed_options, bool allow_unregistered) : common_config_file_iterator(allowed_options, allow_unregistered) { this->is.reset(&is, bpo::detail::null_deleter()); get(); } template bool basic_config_file_iterator::getline(std::string& s) { std::basic_string in; if (std::getline(*is, in)) { s = bpo::to_internal(in); return true; } else { return false; } } } template bpo::basic_parsed_options parse_config_file(std::basic_istream& is, const bpo::options_description& desc, bool allow_unregistered) { std::set allowed_options; const std::vector >& options = desc.options(); for (unsigned i = 0; i < options.size(); ++i) { const bpo::option_description& d = *options[i]; if (d.long_name().empty()) boost::throw_exception( bpo::error("abbreviated option names are not permitted in options configuration files")); allowed_options.insert(d.long_name()); } // Parser return char strings bpo::parsed_options result(&desc); copy(basic_config_file_iterator( is, allowed_options, allow_unregistered), basic_config_file_iterator(), back_inserter(result.options)); // Convert char strings into desired type. return bpo::basic_parsed_options(result); } template bpo::basic_parsed_options parse_config_file(std::basic_istream& is, const bpo::options_description& desc, bool allow_unregistered); } openmw-openmw-0.48.0/components/files/configfileparser.hpp000066400000000000000000000007251445372753700237620ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_CONFIGFILEPARSER_HPP #define COMPONENTS_FILES_CONFIGFILEPARSER_HPP #include namespace Files { namespace bpo = boost::program_options; template bpo::basic_parsed_options parse_config_file(std::basic_istream&, const bpo::options_description&, bool allow_unregistered = false); } #endif // COMPONENTS_FILES_CONFIGFILEPARSER_HPPopenmw-openmw-0.48.0/components/files/configurationmanager.cpp000066400000000000000000000400511445372753700246310ustar00rootroot00000000000000#include "configurationmanager.hpp" #include #include #include #include #include #include /** * \namespace Files */ namespace Files { namespace bpo = boost::program_options; static const char* const openmwCfgFile = "openmw.cfg"; #if defined(_WIN32) || defined(__WINDOWS__) static const char* const applicationName = "OpenMW"; #else static const char* const applicationName = "openmw"; #endif const char* const localToken = "?local?"; const char* const userConfigToken = "?userconfig?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager(bool silent) : mFixedPath(applicationName) , mSilent(silent) { setupTokensMapping(); // Initialize with fixed paths, will be overridden in `readConfiguration`. mUserDataPath = mFixedPath.getUserDataPath(); mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; } ConfigurationManager::~ConfigurationManager() { } void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); mTokensMapping.insert(std::make_pair(userConfigToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } static bool hasReplaceConfig(const bpo::variables_map& variables) { if (variables["replace"].empty()) return false; for (const std::string& var : variables["replace"].as>()) { if (var == "config") return true; } return false; } void ConfigurationManager::readConfiguration(bpo::variables_map& variables, const bpo::options_description& description, bool quiet) { bool silent = mSilent; mSilent = quiet; std::optional config = loadConfig(mFixedPath.getLocalPath(), description); if (config) mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); else { mActiveConfigPaths.push_back(mFixedPath.getGlobalConfigPath()); config = loadConfig(mFixedPath.getGlobalConfigPath(), description); } if (!config) { if (!quiet) Log(Debug::Error) << "Neither local config nor global config are available."; mSilent = silent; return; } std::stack extraConfigDirs; addExtraConfigDirs(extraConfigDirs, variables); if (!hasReplaceConfig(variables)) addExtraConfigDirs(extraConfigDirs, *config); std::vector parsedConfigs{*std::move(config)}; std::set alreadyParsedPaths; // needed to prevent infinite loop in case of a circular link alreadyParsedPaths.insert(boost::filesystem::path(mActiveConfigPaths.front())); while (!extraConfigDirs.empty()) { boost::filesystem::path path = extraConfigDirs.top(); extraConfigDirs.pop(); if (alreadyParsedPaths.count(path) > 0) { if (!quiet) Log(Debug::Warning) << "Repeated config dir: " << path; continue; } alreadyParsedPaths.insert(path); config = loadConfig(path, description); if (config && hasReplaceConfig(*config) && parsedConfigs.size() > 1) { mActiveConfigPaths.resize(1); parsedConfigs.resize(1); Log(Debug::Info) << "Skipping previous configs except " << (mActiveConfigPaths.front() / "openmw.cfg") << " due to replace=config in " << (path / "openmw.cfg"); } mActiveConfigPaths.push_back(path); if (config) { addExtraConfigDirs(extraConfigDirs, *config); parsedConfigs.push_back(*std::move(config)); } } for (auto it = parsedConfigs.rbegin(); it != parsedConfigs.rend(); ++it) { auto composingVariables = separateComposingVariables(variables, description); for (auto& [k, v] : *it) { auto variable = variables.find(k); if (variable == variables.end()) variables.insert({k, v}); else if (variable->second.defaulted()) variable->second = v; } mergeComposingVariables(variables, composingVariables, description); } mUserDataPath = variables["user-data"].as(); if (mUserDataPath.empty()) { if (!quiet) Log(Debug::Warning) << "Error: `user-data` is not specified"; mUserDataPath = mFixedPath.getUserDataPath(); } mScreenshotPath = mUserDataPath / "screenshots"; boost::filesystem::create_directories(getUserConfigPath()); boost::filesystem::create_directories(mScreenshotPath); // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails if (!boost::filesystem::is_directory(mScreenshotPath)) mScreenshotPath = mUserDataPath; if (!quiet) { Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string(); Log(Debug::Info) << "User data dir: " << mUserDataPath.string(); Log(Debug::Info) << "Screenshots dir: " << mScreenshotPath.string(); } mSilent = silent; } void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, const bpo::variables_map& variables) const { auto configIt = variables.find("config"); if (configIt == variables.end()) return; Files::PathContainer newDirs = asPathContainer(configIt->second.as()); for (auto it = newDirs.rbegin(); it != newDirs.rend(); ++it) dirs.push(*it); } void ConfigurationManager::addCommonOptions(bpo::options_description& description) { description.add_options() ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "") ->multitoken()->composing(), "additional config directories") ("replace", bpo::value>()->default_value(std::vector(), "")->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") ("user-data", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "set user data directory (used for saves, screenshots, etc)"); } bpo::variables_map separateComposingVariables(bpo::variables_map & variables, const bpo::options_description& description) { bpo::variables_map composingVariables; for (auto itr = variables.begin(); itr != variables.end();) { if (description.find(itr->first, false).semantic()->is_composing()) { composingVariables.emplace(*itr); itr = variables.erase(itr); } else ++itr; } return composingVariables; } void mergeComposingVariables(bpo::variables_map& first, bpo::variables_map& second, const bpo::options_description& description) { // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. std::set replacedVariables; if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; if (!replace.defaulted() && !replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); } } for (const auto& option : description.options()) { if (option->semantic()->is_composing()) { std::string name = option->canonical_display_name(); auto firstPosition = first.find(name); if (firstPosition == first.end()) { first.emplace(name, second[name]); continue; } if (replacedVariables.count(name) || firstPosition->second.defaulted() || firstPosition->second.empty()) { firstPosition->second = second[name]; continue; } if (second[name].defaulted() || second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); const boost::any& secondValue = second[name].value(); if (firstValue.type() == typeid(Files::MaybeQuotedPathContainer)) { auto& firstPathContainer = boost::any_cast(firstValue); const auto& secondPathContainer = boost::any_cast(secondValue); firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } else if (firstValue.type() == typeid(std::vector)) { auto& firstVector = boost::any_cast&>(firstValue); const auto& secondVector = boost::any_cast&>(secondValue); firstVector.insert(firstVector.end(), secondVector.begin(), secondVector.end()); } else if (firstValue.type() == typeid(Fallback::FallbackMap)) { auto& firstMap = boost::any_cast(firstValue); const auto& secondMap = boost::any_cast(secondValue); std::map tempMap(secondMap.mMap); tempMap.merge(firstMap.mMap); firstMap.mMap.swap(tempMap); } else Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; } } } void ConfigurationManager::processPath(boost::filesystem::path& path, const boost::filesystem::path& basePath) const { std::string str = path.string(); if (str.empty() || str[0] != '?') { if (!path.is_absolute()) path = basePath / path; return; } std::string::size_type pos = str.find('?', 1); if (pos != std::string::npos && pos != 0) { auto tokenIt = mTokensMapping.find(str.substr(0, pos + 1)); if (tokenIt != mTokensMapping.end()) { boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); if (pos < str.length() - 1) { // There is something after the token, so we should // append it to the path tempPath /= str.substr(pos + 1, str.length() - pos); } path = tempPath; } else { if (!mSilent) Log(Debug::Warning) << "Path starts with unknown token: " << path; path.clear(); } } } void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, const boost::filesystem::path& basePath) const { for (auto& path : dataDirs) processPath(path, basePath); } void ConfigurationManager::processPaths(boost::program_options::variables_map& variables, const boost::filesystem::path& basePath) const { for (auto& [name, var] : variables) { if (var.defaulted()) continue; if (var.value().type() == typeid(MaybeQuotedPathContainer)) { auto& pathContainer = boost::any_cast(var.value()); for (MaybeQuotedPath& path : pathContainer) processPath(path, basePath); } else if (var.value().type() == typeid(MaybeQuotedPath)) { boost::filesystem::path& path = boost::any_cast(var.value()); processPath(path, basePath); } } } void ConfigurationManager::filterOutNonExistingPaths(Files::PathContainer& dataDirs) const { dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(), [this](const boost::filesystem::path& p) { bool exists = boost::filesystem::is_directory(p); if (!exists && !mSilent) Log(Debug::Warning) << "No such dir: " << p; return !exists; }), dataDirs.end()); } std::optional ConfigurationManager::loadConfig( const boost::filesystem::path& path, const bpo::options_description& description) const { boost::filesystem::path cfgFile(path); cfgFile /= std::string(openmwCfgFile); if (boost::filesystem::is_regular_file(cfgFile)) { if (!mSilent) Log(Debug::Info) << "Loading config file: " << cfgFile.string(); boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) { bpo::variables_map variables; bpo::store(Files::parse_config_file(configFileStream, description, true), variables); processPaths(variables, path); return variables; } else if (!mSilent) Log(Debug::Error) << "Loading failed."; } return std::nullopt; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const { return mFixedPath.getGlobalConfigPath(); } const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { if (mActiveConfigPaths.empty()) return mFixedPath.getUserConfigPath(); else return mActiveConfigPaths.back(); } const boost::filesystem::path& ConfigurationManager::getUserDataPath() const { return mUserDataPath; } const boost::filesystem::path& ConfigurationManager::getLocalPath() const { return mFixedPath.getLocalPath(); } const boost::filesystem::path& ConfigurationManager::getGlobalDataPath() const { return mFixedPath.getGlobalDataPath(); } const boost::filesystem::path& ConfigurationManager::getCachePath() const { return mFixedPath.getCachePath(); } const boost::filesystem::path& ConfigurationManager::getInstallPath() const { return mFixedPath.getInstallPath(); } const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const { return mScreenshotPath; } void parseArgs(int argc, const char* const argv[], bpo::variables_map& variables, const bpo::options_description& description) { bpo::store( bpo::command_line_parser(argc, argv).options(description).allow_unregistered().run(), variables ); } void parseConfig(std::istream& stream, bpo::variables_map& variables, const bpo::options_description& description) { bpo::store(Files::parse_config_file(stream, description, true), variables); } std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath) { // If the stream starts with a double quote, read from stream using boost::filesystem::path rules, then discard anything remaining. // This prevents boost::program_options getting upset that we've not consumed the whole stream. // If it doesn't start with a double quote, read the whole thing verbatim if (istream.peek() == '"') { istream >> static_cast(MaybeQuotedPath); if (istream && !istream.eof() && istream.peek() != EOF) { std::string remainder{std::istreambuf_iterator(istream), {}}; Log(Debug::Warning) << "Trailing data in path setting. Used '" << MaybeQuotedPath.string() << "' but '" << remainder << "' remained"; } } else { std::string intermediate{std::istreambuf_iterator(istream), {}}; static_cast(MaybeQuotedPath) = intermediate; } return istream; } PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer) { return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); } } /* namespace Files */ openmw-openmw-0.48.0/components/files/configurationmanager.hpp000066400000000000000000000077641445372753700246540ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include #include #include #include #include namespace boost::program_options { class options_description; class variables_map; } /** * \namespace Files */ namespace Files { /** * \struct ConfigurationManager */ struct ConfigurationManager { ConfigurationManager(bool silent=false); /// @param silent Emit log messages to cout? virtual ~ConfigurationManager(); void readConfiguration(boost::program_options::variables_map& variables, const boost::program_options::options_description& description, bool quiet=false); void filterOutNonExistingPaths(Files::PathContainer& dataDirs) const; // Replaces tokens (`?local?`, `?global?`, etc.) in paths. Adds `basePath` prefix for relative paths. void processPath(boost::filesystem::path& path, const boost::filesystem::path& basePath) const; void processPaths(Files::PathContainer& dataDirs, const boost::filesystem::path& basePath) const; void processPaths(boost::program_options::variables_map& variables, const boost::filesystem::path& basePath) const; /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; const std::vector& getActiveConfigPaths() const { return mActiveConfigPaths; } const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const { return getUserConfigPath(); } const boost::filesystem::path& getScreenshotPath() const; static void addCommonOptions(boost::program_options::options_description& description); private: typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; std::optional loadConfig( const boost::filesystem::path& path, const boost::program_options::options_description& description) const; void addExtraConfigDirs(std::stack& dirs, const boost::program_options::variables_map& variables) const; void setupTokensMapping(); std::vector mActiveConfigPaths; FixedPathType mFixedPath; boost::filesystem::path mUserDataPath; boost::filesystem::path mScreenshotPath; TokensMappingContainer mTokensMapping; bool mSilent; }; boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, const boost::program_options::options_description& description); void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, const boost::program_options::options_description& description); void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, const boost::program_options::options_description& description); void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, const boost::program_options::options_description& description); class MaybeQuotedPath : public boost::filesystem::path { }; std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath); typedef std::vector MaybeQuotedPathContainer; PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer); } /* namespace Files */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ openmw-openmw-0.48.0/components/files/constrainedfilestream.cpp000066400000000000000000000004601445372753700250140ustar00rootroot00000000000000#include "constrainedfilestream.hpp" namespace Files { IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length) { return std::make_unique(std::make_unique(filename, start, length)); } } openmw-openmw-0.48.0/components/files/constrainedfilestream.hpp000066400000000000000000000011161445372753700250200ustar00rootroot00000000000000#ifndef OPENMW_CONSTRAINEDFILESTREAM_H #define OPENMW_CONSTRAINEDFILESTREAM_H #include "constrainedfilestreambuf.hpp" #include "streamwithbuffer.hpp" #include "istreamptr.hpp" #include #include namespace Files { /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. using ConstrainedFileStream = StreamWithBuffer; IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start = 0, std::size_t length = std::numeric_limits::max()); } #endif openmw-openmw-0.48.0/components/files/constrainedfilestreambuf.cpp000066400000000000000000000052621445372753700255160ustar00rootroot00000000000000#include "constrainedfilestreambuf.hpp" #include #include namespace Files { namespace File = Platform::File; ConstrainedFileStreamBuf::ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length) : mOrigin(start) { mFile = File::open(fname.c_str()); mSize = length != std::numeric_limits::max() ? length : File::size(mFile) - start; if (start != 0) File::seek(mFile, start); setg(nullptr, nullptr, nullptr); } std::streambuf::int_type ConstrainedFileStreamBuf::underflow() { if (gptr() == egptr()) { const std::size_t toRead = std::min((mOrigin + mSize) - (File::tell(mFile)), sizeof(mBuffer)); // Read in the next chunk of data, and set the read pointers on success // Failure will throw exception. const std::size_t got = File::read(mFile, mBuffer, toRead); setg(&mBuffer[0], &mBuffer[0], &mBuffer[0] + got); } if (gptr() == egptr()) return traits_type::eof(); return traits_type::to_int_type(*gptr()); } std::streambuf::pos_type ConstrainedFileStreamBuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) { if ((mode & std::ios_base::out) || !(mode & std::ios_base::in)) return traits_type::eof(); // new file position, relative to mOrigin size_t newPos; switch (whence) { case std::ios_base::beg: newPos = offset; break; case std::ios_base::cur: newPos = (File::tell(mFile) - mOrigin - (egptr() - gptr())) + offset; break; case std::ios_base::end: newPos = mSize + offset; break; default: return traits_type::eof(); } if (newPos > mSize) return traits_type::eof(); File::seek(mFile, mOrigin + newPos); // Clear read pointers so underflow() gets called on the next read attempt. setg(nullptr, nullptr, nullptr); return newPos; } std::streambuf::pos_type ConstrainedFileStreamBuf::seekpos(pos_type pos, std::ios_base::openmode mode) { if ((mode & std::ios_base::out) || !(mode & std::ios_base::in)) return traits_type::eof(); if (static_cast(pos) > mSize) return traits_type::eof(); File::seek(mFile, mOrigin + pos); // Clear read pointers so underflow() gets called on the next read attempt. setg(nullptr, nullptr, nullptr); return pos; } } openmw-openmw-0.48.0/components/files/constrainedfilestreambuf.hpp000066400000000000000000000015321445372753700255170ustar00rootroot00000000000000#ifndef OPENMW_CONSTRAINEDFILESTREAMBUF_H #define OPENMW_CONSTRAINEDFILESTREAMBUF_H #include #include namespace Files { /// A file streambuf constrained to a specific region in the file, specified by the 'start' and 'length' parameters. class ConstrainedFileStreamBuf final : public std::streambuf { public: ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length); int_type underflow() final; pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) final; pos_type seekpos(pos_type pos, std::ios_base::openmode mode) final; private: std::size_t mOrigin; std::size_t mSize; Platform::File::ScopedHandle mFile; char mBuffer[8192]{ 0 }; }; } #endif openmw-openmw-0.48.0/components/files/fixedpath.hpp000066400000000000000000000063411445372753700224140ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_FIXEDPATH_HPP #define COMPONENTS_FILES_FIXEDPATH_HPP #include #include #if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #ifndef ANDROID #include namespace Files { typedef LinuxPath TargetPathType; } #else #include namespace Files { typedef AndroidPath TargetPathType; } #endif #elif defined(__WIN32) || defined(__WINDOWS__) || defined(_WIN32) #include namespace Files { typedef WindowsPath TargetPathType; } #elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) #include namespace Files { typedef MacOsPath TargetPathType; } #else #error "Unknown platform!" #endif /** * \namespace Files */ namespace Files { /** * \struct Path * * \tparam P - Path strategy class type (depends on target system) * */ template < class P = TargetPathType > struct FixedPath { typedef P PathType; /** * \brief Path constructor. * * \param [in] application_name - Name of the application */ FixedPath(const std::string& application_name) : mPath(application_name + "/") , mUserConfigPath(mPath.getUserConfigPath()) , mUserDataPath(mPath.getUserDataPath()) , mGlobalConfigPath(mPath.getGlobalConfigPath()) , mLocalPath(mPath.getLocalPath()) , mGlobalDataPath(mPath.getGlobalDataPath()) , mCachePath(mPath.getCachePath()) , mInstallPath(mPath.getInstallPath()) { } /** * \brief Return path pointing to the user local configuration directory. */ const boost::filesystem::path& getUserConfigPath() const { return mUserConfigPath; } const boost::filesystem::path& getUserDataPath() const { return mUserDataPath; } /** * \brief Return path pointing to the global (system) configuration directory. */ const boost::filesystem::path& getGlobalConfigPath() const { return mGlobalConfigPath; } /** * \brief Return path pointing to the directory where application was started. */ const boost::filesystem::path& getLocalPath() const { return mLocalPath; } const boost::filesystem::path& getInstallPath() const { return mInstallPath; } const boost::filesystem::path& getGlobalDataPath() const { return mGlobalDataPath; } const boost::filesystem::path& getCachePath() const { return mCachePath; } private: PathType mPath; boost::filesystem::path mUserConfigPath; /**< User path */ boost::filesystem::path mUserDataPath; boost::filesystem::path mGlobalConfigPath; /**< Global path */ boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */ boost::filesystem::path mGlobalDataPath; /**< Global application data path */ boost::filesystem::path mCachePath; boost::filesystem::path mInstallPath; }; } /* namespace Files */ #endif /* COMPONENTS_FILES_FIXEDPATH_HPP */ openmw-openmw-0.48.0/components/files/hash.cpp000066400000000000000000000024071445372753700213550ustar00rootroot00000000000000#include "hash.hpp" #include #include #include #include #include namespace Files { std::array getHash(const std::string& fileName, std::istream& stream) { std::array hash {0, 0}; try { const auto start = stream.tellg(); const auto exceptions = stream.exceptions(); stream.exceptions(std::ios_base::badbit); while (stream) { std::array value; stream.read(value.data(), value.size()); const std::streamsize read = stream.gcount(); if (read == 0) break; std::array blockHash {0, 0}; MurmurHash3_x64_128(value.data(), static_cast(read), hash.data(), blockHash.data()); hash = blockHash; } stream.clear(); stream.exceptions(exceptions); stream.seekg(start); } catch (const std::exception& e) { throw std::runtime_error("Error while reading \"" + fileName + "\" to get hash: " + std::string(e.what())); } return hash; } } openmw-openmw-0.48.0/components/files/hash.hpp000066400000000000000000000004031445372753700213540ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_HASH_H #define COMPONENTS_FILES_HASH_H #include #include #include #include namespace Files { std::array getHash(const std::string& fileName, std::istream& stream); } #endif openmw-openmw-0.48.0/components/files/istreamptr.hpp000066400000000000000000000003221445372753700226230ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FILES_ISTREAMPTR_H #define OPENMW_COMPONENTS_FILES_ISTREAMPTR_H #include #include namespace Files { using IStreamPtr = std::unique_ptr; } #endif openmw-openmw-0.48.0/components/files/linuxpath.cpp000066400000000000000000000123401445372753700224430ustar00rootroot00000000000000#include "linuxpath.hpp" #if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #include #include #include #include #include namespace { boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); if (dir == nullptr) { struct passwd* pwd = getpwuid(getuid()); if (pwd != nullptr) { dir = pwd->pw_dir; } } if (dir == nullptr) return boost::filesystem::path(); else return boost::filesystem::path(dir); } boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) { const char* result = getenv(envVariable.c_str()); if (!result) return fallback; boost::filesystem::path dir(result); if (dir.empty()) return fallback; else return dir; } } /** * \namespace Files */ namespace Files { LinuxPath::LinuxPath(const std::string& application_name) : mName(application_name) { boost::filesystem::path localPath = getLocalPath(); if (chdir(localPath.string().c_str()) != 0) Log(Debug::Warning) << "Error " << errno << " when changing current directory"; } boost::filesystem::path LinuxPath::getUserConfigPath() const { return getEnv("XDG_CONFIG_HOME", getUserHome() / ".config") / mName; } boost::filesystem::path LinuxPath::getUserDataPath() const { return getEnv("XDG_DATA_HOME", getUserHome() / ".local/share") / mName; } boost::filesystem::path LinuxPath::getCachePath() const { return getEnv("XDG_CACHE_HOME", getUserHome() / ".cache") / mName; } boost::filesystem::path LinuxPath::getGlobalConfigPath() const { boost::filesystem::path globalPath(GLOBAL_CONFIG_PATH); return globalPath / mName; } boost::filesystem::path LinuxPath::getLocalPath() const { boost::filesystem::path localPath("./"); const char *statusPaths[] = {"/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file"}; for(const char *path : statusPaths) { boost::filesystem::path statusPath(path); if (!boost::filesystem::exists(statusPath)) continue; statusPath = boost::filesystem::read_symlink(statusPath); if (!boost::filesystem::is_empty(statusPath)) { localPath = statusPath.parent_path() / "/"; break; } } return localPath; } boost::filesystem::path LinuxPath::getGlobalDataPath() const { boost::filesystem::path globalDataPath(GLOBAL_DATA_PATH); return globalDataPath / mName; } boost::filesystem::path LinuxPath::getInstallPath() const { boost::filesystem::path installPath; boost::filesystem::path homePath = getUserHome(); if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; if (boost::filesystem::is_regular_file(wineDefaultRegistry)) { boost::filesystem::ifstream file(wineDefaultRegistry); bool isRegEntry = false; std::string line; std::string mwpath; while (std::getline(file, line)) { if (line[0] == '[') // we found an entry { if (isRegEntry) { break; } isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos); } else if (isRegEntry) { if (line[0] == '"') // empty line means new registry key { std::string key = line.substr(1, line.find('"', 1) - 1); if (strcasecmp(key.c_str(), "Installed Path") == 0) { std::string::size_type valuePos = line.find('=') + 2; mwpath = line.substr(valuePos, line.rfind('"') - valuePos); std::string::size_type pos = mwpath.find("\\"); while (pos != std::string::npos) { mwpath.replace(pos, 2, "/"); pos = mwpath.find("\\", pos + 1); } break; } } } } if (!mwpath.empty()) { // Change drive letter to lowercase, so we could use // ~/.wine/dosdevices symlinks mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; if (!boost::filesystem::is_directory(installPath)) { installPath.clear(); } } } } return installPath; } } /* namespace Files */ #endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) */ openmw-openmw-0.48.0/components/files/linuxpath.hpp000066400000000000000000000027241445372753700224550ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_LINUXPATH_H #define COMPONENTS_FILES_LINUXPATH_H #if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #include /** * \namespace Files */ namespace Files { /** * \struct LinuxPath */ struct LinuxPath { LinuxPath(const std::string& application_name); /** * \brief Return path to the user directory. */ boost::filesystem::path getUserConfigPath() const; boost::filesystem::path getUserDataPath() const; /** * \brief Return path to the global (system) directory where config files can be placed. */ boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime configuration directory which is the * place where an application was started. */ boost::filesystem::path getLocalPath() const; /** * \brief Return path to the global (system) directory where game files can be placed. */ boost::filesystem::path getGlobalDataPath() const; /** * \brief */ boost::filesystem::path getCachePath() const; /** * \brief Gets the path of the installed Morrowind version if there is one. */ boost::filesystem::path getInstallPath() const; std::string mName; }; } /* namespace Files */ #endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) */ #endif /* COMPONENTS_FILES_LINUXPATH_H */ openmw-openmw-0.48.0/components/files/macospath.cpp000066400000000000000000000104001445372753700224010ustar00rootroot00000000000000#include "macospath.hpp" #if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) #include #include #include #include #include namespace { boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); if (dir == nullptr) { struct passwd* pwd = getpwuid(getuid()); if (pwd != nullptr) { dir = pwd->pw_dir; } } if (dir == nullptr) return boost::filesystem::path(); else return boost::filesystem::path(dir); } } namespace Files { MacOsPath::MacOsPath(const std::string& application_name) : mName(application_name) { } boost::filesystem::path MacOsPath::getUserConfigPath() const { boost::filesystem::path userPath (getUserHome()); userPath /= "Library/Preferences/"; return userPath / mName; } boost::filesystem::path MacOsPath::getUserDataPath() const { boost::filesystem::path userPath (getUserHome()); userPath /= "Library/Application Support/"; return userPath / mName; } boost::filesystem::path MacOsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("/Library/Preferences/"); return globalPath / mName; } boost::filesystem::path MacOsPath::getCachePath() const { boost::filesystem::path userPath (getUserHome()); userPath /= "Library/Caches"; return userPath / mName; } boost::filesystem::path MacOsPath::getLocalPath() const { return boost::filesystem::path("../Resources/"); } boost::filesystem::path MacOsPath::getGlobalDataPath() const { boost::filesystem::path globalDataPath("/Library/Application Support/"); return globalDataPath / mName; } boost::filesystem::path MacOsPath::getInstallPath() const { boost::filesystem::path installPath; boost::filesystem::path homePath = getUserHome(); if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; if (boost::filesystem::is_regular_file(wineDefaultRegistry)) { boost::filesystem::ifstream file(wineDefaultRegistry); bool isRegEntry = false; std::string line; std::string mwpath; while (std::getline(file, line)) { if (line[0] == '[') // we found an entry { if (isRegEntry) { break; } isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos); } else if (isRegEntry) { if (line[0] == '"') // empty line means new registry key { std::string key = line.substr(1, line.find('"', 1) - 1); if (strcasecmp(key.c_str(), "Installed Path") == 0) { std::string::size_type valuePos = line.find('=') + 2; mwpath = line.substr(valuePos, line.rfind('"') - valuePos); std::string::size_type pos = mwpath.find("\\"); while (pos != std::string::npos) { mwpath.replace(pos, 2, "/"); pos = mwpath.find("\\", pos + 1); } break; } } } } if (!mwpath.empty()) { // Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; if (!boost::filesystem::is_directory(installPath)) { installPath.clear(); } } } } return installPath; } } /* namespace Files */ #endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */ openmw-openmw-0.48.0/components/files/macospath.hpp000066400000000000000000000027031445372753700224150ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_MACOSPATH_H #define COMPONENTS_FILES_MACOSPATH_H #if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) #include /** * \namespace Files */ namespace Files { /** * \struct MacOsPath */ struct MacOsPath { MacOsPath(const std::string& application_name); /** * \brief Return path to the local directory. * * \return boost::filesystem::path */ boost::filesystem::path getUserConfigPath() const; boost::filesystem::path getUserDataPath() const; /** * \brief Return path to the global (system) directory. * * \return boost::filesystem::path */ boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime directory which is the * place where an application was started. * * \return boost::filesystem::path */ boost::filesystem::path getLocalPath() const; /** * \brief * * \return boost::filesystem::path */ boost::filesystem::path getCachePath() const; /** * \brief * * \return boost::filesystem::path */ boost::filesystem::path getGlobalDataPath() const; boost::filesystem::path getInstallPath() const; std::string mName; }; } /* namespace Files */ #endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */ #endif /* COMPONENTS_FILES_MACOSPATH_H */ openmw-openmw-0.48.0/components/files/memorystream.hpp000066400000000000000000000026171445372753700231660ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FILES_MEMORYSTREAM_H #define OPENMW_COMPONENTS_FILES_MEMORYSTREAM_H #include namespace Files { struct MemBuf : std::streambuf { MemBuf(char const* buffer, size_t size) // a streambuf isn't specific to istreams, so we need a non-const pointer :/ : bufferStart(const_cast(buffer)) , bufferEnd(bufferStart + size) { this->setg(bufferStart, bufferStart, bufferEnd); } pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override { if (dir == std::ios_base::cur) gbump(off); else setg(bufferStart, (dir == std::ios_base::beg ? bufferStart : bufferEnd) + off, bufferEnd); return gptr() - bufferStart; } pos_type seekpos(pos_type pos, std::ios_base::openmode which) override { return seekoff(pos, std::ios_base::beg, which); } protected: char* bufferStart; char* bufferEnd; }; /// @brief A variant of std::istream that reads from a constant in-memory buffer. struct IMemStream: virtual MemBuf, std::istream { IMemStream(char const* buffer, size_t size) : MemBuf(buffer, size) , std::istream(static_cast(this)) { } }; } #endif openmw-openmw-0.48.0/components/files/multidircollection.cpp000066400000000000000000000050521445372753700243360ustar00rootroot00000000000000#include "multidircollection.hpp" #include #include namespace Files { struct NameEqual { bool mStrict; NameEqual (bool strict) : mStrict (strict) {} bool operator() (const std::string& left, const std::string& right) const { if (mStrict) return left==right; return Misc::StringUtils::ciEqual(left, right); } }; MultiDirCollection::MultiDirCollection(const Files::PathContainer& directories, const std::string& extension, bool foldCase) : mFiles (NameLess (!foldCase)) { NameEqual equal (!foldCase); for (PathContainer::const_iterator iter = directories.begin(); iter!=directories.end(); ++iter) { if (!boost::filesystem::is_directory(*iter)) { Log(Debug::Info) << "Skipping invalid directory: " << (*iter).string(); continue; } for (boost::filesystem::directory_iterator dirIter(*iter); dirIter != boost::filesystem::directory_iterator(); ++dirIter) { boost::filesystem::path path = *dirIter; if (!equal (extension, path.extension().string())) continue; std::string filename = path.filename().string(); TIter result = mFiles.find (filename); if (result==mFiles.end()) { mFiles.insert (std::make_pair (filename, path)); } else if (result->first==filename) { mFiles[filename] = path; } else { // handle case folding mFiles.erase (result->first); mFiles.insert (std::make_pair (filename, path)); } } } } boost::filesystem::path MultiDirCollection::getPath (const std::string& file) const { TIter iter = mFiles.find (file); if (iter==mFiles.end()) throw std::runtime_error ("file " + file + " not found"); return iter->second; } bool MultiDirCollection::doesExist (const std::string& file) const { return mFiles.find (file)!=mFiles.end(); } MultiDirCollection::TIter MultiDirCollection::begin() const { return mFiles.begin(); } MultiDirCollection::TIter MultiDirCollection::end() const { return mFiles.end(); } } openmw-openmw-0.48.0/components/files/multidircollection.hpp000066400000000000000000000042361445372753700243460ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_MULTIDIRSOLLECTION_HPP #define COMPONENTS_FILES_MULTIDIRSOLLECTION_HPP #include #include #include #include #include #include namespace Files { typedef std::vector PathContainer; struct NameLess { bool mStrict; NameLess (bool strict) : mStrict (strict) {} bool operator() (const std::string& left, const std::string& right) const { if (mStrict) return left TContainer; typedef TContainer::const_iterator TIter; private: TContainer mFiles; public: MultiDirCollection (const Files::PathContainer& directories, const std::string& extension, bool foldCase); ///< Directories are listed with increasing priority. /// \param extension The extension that should be listed in this collection. Must /// contain the leading dot. /// \param foldCase Ignore filename case boost::filesystem::path getPath (const std::string& file) const; ///< Return full path (including filename) of \a file. /// /// If the file does not exist, an exception is thrown. \a file must include /// the extension. bool doesExist (const std::string& file) const; ///< \return Does a file with the given name exist? TIter begin() const; ///< Return iterator pointing to the first file. TIter end() const; ///< Return iterator pointing past the last file. }; } #endif openmw-openmw-0.48.0/components/files/openfile.cpp000066400000000000000000000010041445372753700222230ustar00rootroot00000000000000#include "openfile.hpp" #include namespace Files { std::unique_ptr openBinaryInputFileStream(const std::string& path) { auto stream = std::make_unique(path, boost::filesystem::fstream::binary); if (!stream->is_open()) throw std::runtime_error("Failed to open '" + path + "' for reading: " + std::strerror(errno)); stream->exceptions(boost::filesystem::fstream::badbit); return stream; } } openmw-openmw-0.48.0/components/files/openfile.hpp000066400000000000000000000004701445372753700222360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FILES_OPENFILE_H #define OPENMW_COMPONENTS_FILES_OPENFILE_H #include #include #include #include namespace Files { std::unique_ptr openBinaryInputFileStream(const std::string& path); } #endif openmw-openmw-0.48.0/components/files/streamwithbuffer.hpp000066400000000000000000000010111445372753700240060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H #define OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H #include #include namespace Files { template class StreamWithBuffer final : public std::istream { public: explicit StreamWithBuffer(std::unique_ptr&& buffer) : std::istream(buffer.get()) , mBuffer(std::move(buffer)) {} private: std::unique_ptr mBuffer; }; } #endif openmw-openmw-0.48.0/components/files/windowspath.cpp000066400000000000000000000067561445372753700230140ustar00rootroot00000000000000#include "windowspath.hpp" #if defined(_WIN32) || defined(__WINDOWS__) #include #define FAR #define NEAR #include #include #include #undef NEAR #undef FAR #include namespace bconv = boost::locale::conv; #include /** * FIXME: Someone with Windows system should check this and correct if necessary * FIXME: MAX_PATH is irrelevant for extended-length paths, i.e. \\?\... */ /** * \namespace Files */ namespace Files { WindowsPath::WindowsPath(const std::string& application_name) : mName(application_name) { /* Since on Windows boost::path.string() returns string of narrow characters in local encoding, it is required to path::imbue() with UTF-8 encoding (generated for empty name from boost::locale) to handle Unicode in platform-agnostic way using std::string. See boost::filesystem and boost::locale reference for details. */ boost::filesystem::path::imbue(boost::locale::generator().generate("")); boost::filesystem::path localPath = getLocalPath(); if (!SetCurrentDirectoryA(localPath.string().c_str())) Log(Debug::Warning) << "Error " << GetLastError() << " when changing current directory"; } boost::filesystem::path WindowsPath::getUserConfigPath() const { boost::filesystem::path userPath("."); WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); if(SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr, 0, path))) { userPath = boost::filesystem::path(bconv::utf_to_utf(path)); } return userPath / "My Games" / mName; } boost::filesystem::path WindowsPath::getUserDataPath() const { // Have some chaos, windows people! return getUserConfigPath(); } boost::filesystem::path WindowsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("."); WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); if(SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, nullptr, 0, path))) { globalPath = boost::filesystem::path(bconv::utf_to_utf(path)); } return globalPath / mName; } boost::filesystem::path WindowsPath::getLocalPath() const { boost::filesystem::path localPath("./"); WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); if (GetModuleFileNameW(nullptr, path, MAX_PATH + 1) > 0) { localPath = boost::filesystem::path(bconv::utf_to_utf(path)).parent_path() / "/"; } // lookup exe path return localPath; } boost::filesystem::path WindowsPath::getGlobalDataPath() const { return getGlobalConfigPath(); } boost::filesystem::path WindowsPath::getCachePath() const { return getUserConfigPath() / "cache"; } boost::filesystem::path WindowsPath::getInstallPath() const { boost::filesystem::path installPath(""); HKEY hKey; LPCTSTR regkey = TEXT("SOFTWARE\\Bethesda Softworks\\Morrowind"); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) { //Key existed, let's try to read the install dir std::vector buf(512); int len = 512; if (RegQueryValueEx(hKey, TEXT("Installed Path"), nullptr, nullptr, (LPBYTE)&buf[0], (LPDWORD)&len) == ERROR_SUCCESS) { installPath = &buf[0]; } RegCloseKey(hKey); } return installPath; } } /* namespace Files */ #endif /* defined(_WIN32) || defined(__WINDOWS__) */ openmw-openmw-0.48.0/components/files/windowspath.hpp000066400000000000000000000033051445372753700230040ustar00rootroot00000000000000#ifndef COMPONENTS_FILES_WINDOWSPATH_HPP #define COMPONENTS_FILES_WINDOWSPATH_HPP #if defined(_WIN32) || defined(__WINDOWS__) #include /** * \namespace Files */ namespace Files { /** * \struct WindowsPath */ struct WindowsPath { /** * \brief WindowsPath constructor. * * \param [in] application_name - The name of the application. */ WindowsPath(const std::string& application_name); /** * \brief Returns user path i.e.: * "X:\Documents And Settings\\My Documents\My Games\" * * \return boost::filesystem::path */ boost::filesystem::path getUserConfigPath() const; boost::filesystem::path getUserDataPath() const; /** * \brief Returns "X:\Program Files\" * * \return boost::filesystem::path */ boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return local path which is a location where * an application was started * * \return boost::filesystem::path */ boost::filesystem::path getLocalPath() const; /** * \brief * * \return boost::filesystem::path */ boost::filesystem::path getCachePath() const; /** * \brief Return same path like getGlobalPath * * \return boost::filesystem::path */ boost::filesystem::path getGlobalDataPath() const; /** * \brief Gets the path of the installed Morrowind version if there is one. * * \return boost::filesystem::path */ boost::filesystem::path getInstallPath() const; std::string mName; }; } /* namespace Files */ #endif /* defined(_WIN32) || defined(__WINDOWS__) */ #endif /* COMPONENTS_FILES_WINDOWSPATH_HPP */ openmw-openmw-0.48.0/components/fontloader/000077500000000000000000000000001445372753700207565ustar00rootroot00000000000000openmw-openmw-0.48.0/components/fontloader/fontloader.cpp000066400000000000000000000647111445372753700236300ustar00rootroot00000000000000#include "fontloader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { MyGUI::xml::ElementPtr getProperty(MyGUI::xml::ElementPtr resourceNode, const std::string propertyName) { MyGUI::xml::ElementPtr propertyNode = nullptr; MyGUI::xml::ElementEnumerator propertyIterator = resourceNode->getElementEnumerator(); while (propertyIterator.next("Property")) { std::string key = propertyIterator->findAttribute("key"); if (key == propertyName) { propertyNode = propertyIterator.current(); break; } } return propertyNode; } MyGUI::IntSize getBookSize(MyGUI::IDataStream* layersStream) { MyGUI::xml::Document xmlDocument; xmlDocument.open(layersStream); MyGUI::xml::ElementPtr root = xmlDocument.getRoot(); MyGUI::xml::ElementEnumerator layersIterator = root->getElementEnumerator(); while (layersIterator.next("Layer")) { std::string name = layersIterator->findAttribute("name"); if (name == "JournalBooks") { MyGUI::xml::ElementPtr sizeProperty = getProperty(layersIterator.current(), "Size"); const std::string& sizeValue = sizeProperty != nullptr ? sizeProperty->findAttribute("value") : std::string(); if (!sizeValue.empty()) return MyGUI::IntSize::parse(sizeValue); } } return MyGUI::RenderManager::getInstance().getViewSize(); } unsigned long utf8ToUnicode(std::string_view utf8) { if (utf8.empty()) return 0; size_t i = 0; unsigned long unicode; size_t numbytes; unsigned char ch = utf8[i++]; if (ch <= 0x7F) { unicode = ch; numbytes = 0; } else if (ch <= 0xBF) { throw std::logic_error("not a UTF-8 string"); } else if (ch <= 0xDF) { unicode = ch&0x1F; numbytes = 1; } else if (ch <= 0xEF) { unicode = ch&0x0F; numbytes = 2; } else if (ch <= 0xF7) { unicode = ch&0x07; numbytes = 3; } else { throw std::logic_error("not a UTF-8 string"); } for (size_t j = 0; j < numbytes; ++j) { ch = utf8[i++]; if (ch < 0x80 || ch > 0xBF) throw std::logic_error("not a UTF-8 string"); unicode <<= 6; unicode += ch & 0x3F; } if (unicode >= 0xD800 && unicode <= 0xDFFF) throw std::logic_error("not a UTF-8 string"); if (unicode > 0x10FFFF) throw std::logic_error("not a UTF-8 string"); return unicode; } /// This is a hack for Polish font unsigned char mapUtf8Char(unsigned char c) { switch(c){ case 0x80: return 0xc6; case 0x81: return 0x9c; case 0x82: return 0xe6; case 0x83: return 0xb3; case 0x84: return 0xf1; case 0x85: return 0xb9; case 0x86: return 0xbf; case 0x87: return 0x9f; case 0x88: return 0xea; case 0x89: return 0xea; case 0x8a: return 0x00; // not contained in win1250 case 0x8b: return 0x00; // not contained in win1250 case 0x8c: return 0x8f; case 0x8d: return 0xaf; case 0x8e: return 0xa5; case 0x8f: return 0x8c; case 0x90: return 0xca; case 0x93: return 0xa3; case 0x94: return 0xf6; case 0x95: return 0xf3; case 0x96: return 0xaf; case 0x97: return 0x8f; case 0x99: return 0xd3; case 0x9a: return 0xd1; case 0x9c: return 0x00; // not contained in win1250 case 0xa0: return 0xb9; case 0xa1: return 0xaf; case 0xa2: return 0xf3; case 0xa3: return 0xbf; case 0xa4: return 0x00; // not contained in win1250 case 0xe1: return 0x8c; case 0xe3: return 0x00; // not contained in win1250 case 0xf5: return 0x00; // not contained in win1250 default: return c; } } // getUnicode includes various hacks for dealing with Morrowind's .fnt files that are *mostly* // in the expected win12XX encoding, but also have randomly swapped characters sometimes. // Looks like the Morrowind developers found standard encodings too boring and threw in some twists for fun. unsigned long getUnicode(unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) // Hack for polish font { const std::array str {static_cast(mapUtf8Char(c)), '\0'}; return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); } else { const std::array str {static_cast(c), '\0'}; return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); } } [[noreturn]] void fail(std::istream& stream, const std::string& fileName, const std::string& message) { std::stringstream error; error << "Font loading error: " << message; error << "\n File: " << fileName; error << "\n Offset: 0x" << std::hex << stream.tellg(); throw std::runtime_error(error.str()); } } namespace Gui { FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor) : mVFS(vfs) , mFontHeight(std::clamp(Settings::Manager::getInt("font size", "GUI"), 12, 18)) , mScalingFactor(scalingFactor) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; else mEncoding = encoding; MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = MyGUI::newDelegate(this, &FontLoader::overrideLineHeight); loadFonts(); } void FontLoader::loadFonts() { std::string defaultFont = Fallback::Map::getString("Fonts_Font_0"); std::string scrollFont = Fallback::Map::getString("Fonts_Font_2"); loadFont(defaultFont, "DefaultFont"); loadFont(scrollFont, "ScrollFont"); loadFont("DejaVuLGCSansMono", "MonoFont"); // We need to use a TrueType monospace font to display debug texts properly. // Use our TrueType fonts as a fallback. if (!MyGUI::ResourceManager::getInstance().isExist("DefaultFont") && !Misc::StringUtils::ciEqual(defaultFont, "MysticCards")) loadFont("MysticCards", "DefaultFont"); if (!MyGUI::ResourceManager::getInstance().isExist("ScrollFont") && !Misc::StringUtils::ciEqual(scrollFont, "DemonicLetters")) loadFont("DemonicLetters", "ScrollFont"); } void FontLoader::loadFont(const std::string& fileName, const std::string& fontId) { if (mVFS->exists("fonts/" + fileName + ".fnt")) loadBitmapFont(fileName + ".fnt", fontId); else if (mVFS->exists("fonts/" + fileName + ".omwfont")) loadTrueTypeFont(fileName + ".omwfont", fontId); else Log(Debug::Error) << "Font '" << fileName << "' is not found."; } void FontLoader::loadTrueTypeFont(const std::string& fileName, const std::string& fontId) { Log(Debug::Info) << "Loading font file " << fileName; osgMyGUI::DataManager* dataManager = dynamic_cast(&osgMyGUI::DataManager::getInstance()); if (!dataManager) { Log(Debug::Error) << "Can not load TrueType font " << fontId << ": osgMyGUI::DataManager is not available."; return; } // TODO: it may be worth to take in account resolution change, but it is not safe to replace used assets std::unique_ptr layersStream(dataManager->getData("openmw_layers.xml")); MyGUI::IntSize bookSize = getBookSize(layersStream.get()); float bookScale = osgMyGUI::ScalingLayer::getScaleFactor(bookSize); std::string oldDataPath = dataManager->getDataPath(""); dataManager->setResourcePath("fonts"); std::unique_ptr dataStream(dataManager->getData(fileName)); MyGUI::xml::Document xmlDocument; xmlDocument.open(dataStream.get()); MyGUI::xml::ElementPtr root = xmlDocument.getRoot(); MyGUI::xml::ElementEnumerator resourceNode = root->getElementEnumerator(); bool valid = false; if (resourceNode.next("Resource")) { std::string type = resourceNode->findAttribute("type"); valid = (type == "ResourceTrueTypeFont"); } if (valid == false) { dataManager->setResourcePath(oldDataPath); Log(Debug::Error) << "Can not load TrueType font " << fontId << ": " << fileName << " is invalid."; return; } int resolution = 70; MyGUI::xml::ElementPtr resolutionNode = getProperty(resourceNode.current(), "Resolution"); if (resolutionNode == nullptr) { resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); } else resolution = MyGUI::utility::parseInt(resolutionNode->findAttribute("value")); resolutionNode->setAttribute("value", MyGUI::utility::toString(resolution * std::ceil(mScalingFactor))); MyGUI::xml::ElementPtr sizeNode = resourceNode->createChild("Property"); sizeNode->addAttribute("key", "Size"); sizeNode->addAttribute("value", std::to_string(mFontHeight)); MyGUI::ResourceTrueTypeFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceTrueTypeFont")); font->deserialization(resourceNode.current(), MyGUI::Version(3,2,0)); font->setResourceName(fontId); MyGUI::ResourceManager::getInstance().addResource(font); resolutionNode->setAttribute("value", MyGUI::utility::toString(static_cast(resolution * bookScale * mScalingFactor))); MyGUI::ResourceTrueTypeFont* bookFont = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceTrueTypeFont")); bookFont->deserialization(resourceNode.current(), MyGUI::Version(3,2,0)); bookFont->setResourceName("Journalbook " + fontId); MyGUI::ResourceManager::getInstance().addResource(bookFont); dataManager->setResourcePath(oldDataPath); if (resourceNode.next("Resource")) Log(Debug::Warning) << "Font file " << fileName << " contains multiple Resource entries, only first one will be used."; } typedef struct { float x; float y; } Point; typedef struct { float u1; // appears unused, always 0 Point top_left; Point top_right; Point bottom_left; Point bottom_right; float width; float height; float u2; // appears unused, always 0 float kerning; float ascent; } GlyphInfo; void FontLoader::loadBitmapFont(const std::string &fileName, const std::string& fontId) { Log(Debug::Info) << "Loading font file " << fileName; Files::IStreamPtr file = mVFS->get("fonts/" + fileName); float fontSize; file->read((char*)&fontSize, sizeof(fontSize)); if (!file->good()) fail(*file, fileName, "File too small to be a valid font"); int one; file->read((char*)&one, sizeof(one)); if (!file->good()) fail(*file, fileName, "File too small to be a valid font"); if (one != 1) fail(*file, fileName, "Unexpected value"); file->read((char*)&one, sizeof(one)); if (!file->good()) fail(*file, fileName, "File too small to be a valid font"); if (one != 1) fail(*file, fileName, "Unexpected value"); char name_[284]; file->read(name_, sizeof(name_)); if (!file->good()) fail(*file, fileName, "File too small to be a valid font"); std::string name(name_); GlyphInfo data[256]; file->read((char*)data, sizeof(data)); if (!file->good()) fail(*file, fileName, "File too small to be a valid font"); file.reset(); // Create the font texture std::string bitmapFilename = "fonts/" + std::string(name) + ".tex"; Files::IStreamPtr bitmapFile = mVFS->get(bitmapFilename); int width, height; bitmapFile->read((char*)&width, sizeof(int)); bitmapFile->read((char*)&height, sizeof(int)); if (!bitmapFile->good()) fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); if (width <= 0 || height <= 0) fail(*bitmapFile, bitmapFilename, "Width and height must be positive"); std::vector textureData; textureData.resize(width*height*4); bitmapFile->read(&textureData[0], width*height*4); if (!bitmapFile->good()) fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); bitmapFile.reset(); MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture(bitmapFilename); tex->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); unsigned char* texData = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); memcpy(texData, &textureData[0], textureData.size()); tex->unlock(); // We need to emulate loading from XML because the data members are private as of mygui 3.2.0 MyGUI::xml::Document xmlDocument; MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont"); root->addAttribute("name", fontId); MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property"); defaultHeight->addAttribute("key", "DefaultHeight"); defaultHeight->addAttribute("value", fontSize); MyGUI::xml::ElementPtr source = root->createChild("Property"); source->addAttribute("key", "Source"); source->addAttribute("value", std::string(bitmapFilename)); MyGUI::xml::ElementPtr codes = root->createChild("Codes"); for(int i = 0; i < 256; i++) { float x1 = data[i].top_left.x*width; float y1 = data[i].top_left.y*height; float w = data[i].top_right.x*width - x1; float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = getUnicode(i, encoder, mEncoding); MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); code->addAttribute("size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game fonts std::multimap additional; // fallback glyph index, unicode additional.insert(std::make_pair(156, 0x00A2)); // cent sign additional.insert(std::make_pair(89, 0x00A5)); // yen sign additional.insert(std::make_pair(221, 0x00A6)); // broken bar additional.insert(std::make_pair(99, 0x00A9)); // copyright sign additional.insert(std::make_pair(97, 0x00AA)); // prima ordinal indicator additional.insert(std::make_pair(60, 0x00AB)); // double left-pointing angle quotation mark additional.insert(std::make_pair(45, 0x00AD)); // soft hyphen additional.insert(std::make_pair(114, 0x00AE)); // registered trademark symbol additional.insert(std::make_pair(45, 0x00AF)); // macron additional.insert(std::make_pair(241, 0x00B1)); // plus-minus sign additional.insert(std::make_pair(50, 0x00B2)); // superscript two additional.insert(std::make_pair(51, 0x00B3)); // superscript three additional.insert(std::make_pair(44, 0x00B8)); // cedilla additional.insert(std::make_pair(49, 0x00B9)); // superscript one additional.insert(std::make_pair(111, 0x00BA)); // primo ordinal indicator additional.insert(std::make_pair(62, 0x00BB)); // double right-pointing angle quotation mark additional.insert(std::make_pair(63, 0x00BF)); // inverted question mark additional.insert(std::make_pair(65, 0x00C6)); // latin capital ae ligature additional.insert(std::make_pair(79, 0x00D8)); // latin capital o with stroke additional.insert(std::make_pair(97, 0x00E6)); // latin small ae ligature additional.insert(std::make_pair(111, 0x00F8)); // latin small o with stroke additional.insert(std::make_pair(79, 0x0152)); // latin capital oe ligature additional.insert(std::make_pair(111, 0x0153)); // latin small oe ligature additional.insert(std::make_pair(83, 0x015A)); // latin capital s with caron additional.insert(std::make_pair(115, 0x015B)); // latin small s with caron additional.insert(std::make_pair(89, 0x0178)); // latin capital y with diaresis additional.insert(std::make_pair(90, 0x017D)); // latin capital z with caron additional.insert(std::make_pair(122, 0x017E)); // latin small z with caron additional.insert(std::make_pair(102, 0x0192)); // latin small f with hook additional.insert(std::make_pair(94, 0x02C6)); // circumflex modifier additional.insert(std::make_pair(126, 0x02DC)); // small tilde additional.insert(std::make_pair(69, 0x0401)); // cyrillic capital io (no diaeresis latin e is available) additional.insert(std::make_pair(137, 0x0451)); // cyrillic small io additional.insert(std::make_pair(45, 0x2012)); // figure dash additional.insert(std::make_pair(45, 0x2013)); // en dash additional.insert(std::make_pair(45, 0x2014)); // em dash additional.insert(std::make_pair(39, 0x2018)); // left single quotation mark additional.insert(std::make_pair(39, 0x2019)); // right single quotation mark additional.insert(std::make_pair(44, 0x201A)); // single low quotation mark additional.insert(std::make_pair(39, 0x201B)); // single high quotation mark (reversed) additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark additional.insert(std::make_pair(44, 0x201E)); // double low quotation mark additional.insert(std::make_pair(34, 0x201F)); // double high quotation mark (reversed) additional.insert(std::make_pair(43, 0x2020)); // dagger additional.insert(std::make_pair(216, 0x2021)); // double dagger (note: this glyph is not available) additional.insert(std::make_pair(46, 0x2026)); // ellipsis additional.insert(std::make_pair(37, 0x2030)); // per mille sign additional.insert(std::make_pair(60, 0x2039)); // single left-pointing angle quotation mark additional.insert(std::make_pair(62, 0x203A)); // single right-pointing angle quotation mark additional.insert(std::make_pair(101, 0x20AC)); // euro sign additional.insert(std::make_pair(84, 0x2122)); // trademark sign additional.insert(std::make_pair(45, 0x2212)); // minus sign for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) { if (it->first != i) continue; code = codes->createChild("Code"); code->addAttribute("index", it->second); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); code->addAttribute("size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); } // ASCII vertical bar, use this as text input cursor if (i == 124) { MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); cursorCode->addAttribute("index", MyGUI::FontCodeType::Cursor); cursorCode->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); cursorCode->addAttribute("size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); } // Question mark, use for NotDefined marker (used for glyphs not existing in the font) if (i == 63) { MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); cursorCode->addAttribute("index", MyGUI::FontCodeType::NotDefined); cursorCode->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); cursorCode->addAttribute("size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); } } // These are required as well, but the fonts don't provide them for (int i=0; i<2; ++i) { MyGUI::FontCodeType::Enum type; if(i == 0) type = MyGUI::FontCodeType::Selected; else // if (i == 1) type = MyGUI::FontCodeType::SelectedBack; MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); cursorCode->addAttribute("index", type); cursorCode->addAttribute("coord", "0 0 0 0"); cursorCode->addAttribute("advance", "0"); cursorCode->addAttribute("bearing", "0 0"); cursorCode->addAttribute("size", "0 0"); } // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); font->deserialization(root, MyGUI::Version(3,2,0)); MyGUI::ResourceManualFont* bookFont = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); bookFont->deserialization(root, MyGUI::Version(3,2,0)); bookFont->setResourceName("Journalbook " + fontId); MyGUI::ResourceManager::getInstance().addResource(font); MyGUI::ResourceManager::getInstance().addResource(bookFont); } void FontLoader::overrideLineHeight(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) { MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); while (resourceNode.next("Resource")) { std::string type = resourceNode->findAttribute("type"); if (Misc::StringUtils::ciEqual(type, "ResourceSkin") || Misc::StringUtils::ciEqual(type, "AutoSizedResourceSkin")) { // We should adjust line height for MyGUI widgets depending on font size MyGUI::xml::ElementPtr heightNode = resourceNode->createChild("Property"); heightNode->addAttribute("key", "HeightLine"); heightNode->addAttribute("value", std::to_string(mFontHeight+2)); } } MyGUI::ResourceManager::getInstance().loadFromXmlNode(_node, _file, _version); } int FontLoader::getFontHeight() { return mFontHeight; } std::string FontLoader::getFontForFace(const std::string& face) { const std::string lowerFace = Misc::StringUtils::lowerCase(face); if (lowerFace == "daedric") return "ScrollFont"; return "DefaultFont"; } } openmw-openmw-0.48.0/components/fontloader/fontloader.hpp000066400000000000000000000025751445372753700236350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FONTLOADER_H #define OPENMW_COMPONENTS_FONTLOADER_H #include #include #include #include namespace VFS { class Manager; } namespace MyGUI { class ITexture; class ResourceManualFont; } namespace Gui { /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and OSG /// @note The FontLoader needs to remain in scope as long as you want to use the loaded fonts. class FontLoader { public: FontLoader (ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor); void overrideLineHeight(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); int getFontHeight(); static std::string getFontForFace(const std::string& face); private: ToUTF8::FromType mEncoding; const VFS::Manager* mVFS; int mFontHeight; float mScalingFactor; void loadFonts(); void loadFont(const std::string& fontName, const std::string& fontId); void loadBitmapFont (const std::string& fileName, const std::string& fontId); void loadTrueTypeFont(const std::string& fileName, const std::string& fontId); FontLoader(const FontLoader&); void operator=(const FontLoader&); }; } #endif openmw-openmw-0.48.0/components/fx/000077500000000000000000000000001445372753700172365ustar00rootroot00000000000000openmw-openmw-0.48.0/components/fx/lexer.cpp000066400000000000000000000177761445372753700211030ustar00rootroot00000000000000#include "lexer.hpp" #include #include #include #include #include #include #include #include #include #include #include "types.hpp" namespace fx { namespace Lexer { Lexer::Lexer(std::string_view buffer) : mHead(buffer.data()) , mTail(mHead + buffer.length()) , mAbsolutePos(0) , mColumn(0) , mLine(0) , mBuffer(buffer) , mLastToken(Eof{}) { } Token Lexer::next() { if (mLookahead) { auto token = *mLookahead; drop(); return token; } mLastToken = scanToken(); return mLastToken; } Token Lexer::peek() { if (!mLookahead) mLookahead = scanToken(); return *mLookahead; } void Lexer::drop() { mLookahead = std::nullopt; } std::optional Lexer::jump() { bool multi = false; bool single = false; auto start = mHead; std::size_t level = 1; mLastJumpBlock.line = mLine; if (head() == '}') { mLastJumpBlock.content = {}; return mLastJumpBlock.content; } for (; mHead != mTail; advance()) { if (head() == '\n') { mLine++; mColumn = 0; if (single) { single = false; continue; } } else if (multi && head() == '*' && peekChar('/')) { multi = false; advance(); continue; } else if (multi || single) { continue; } else if (head() == '/' && peekChar('/')) { single = true; advance(); continue; } else if (head() == '/' && peekChar('*')) { multi = true; advance(); continue; } if (head() == '{') level++; else if (head() == '}') level--; if (level == 0) { mHead--; auto sv = std::string_view{start, static_cast(mHead + 1 - start)}; mLastJumpBlock.content = sv; return sv; } } mLastJumpBlock = {}; return std::nullopt; } Lexer::Block Lexer::getLastJumpBlock() const { return mLastJumpBlock; } [[noreturn]] void Lexer::error(const std::string& msg) { throw LexerException(Misc::StringUtils::format("Line %zu Col %zu. %s", mLine + 1, mColumn, msg)); } void Lexer::advance() { mAbsolutePos++; mHead++; mColumn++; } char Lexer::head() { return *mHead; } bool Lexer::peekChar(char c) { if (mHead == mTail) return false; return *(mHead + 1) == c; } Token Lexer::scanToken() { while (true) { if (mHead == mTail) return {Eof{}}; if (head() == '\n') { mLine++; mColumn = 0; } if (!std::isspace(head())) break; advance(); } if (head() == '\"') return scanStringLiteral(); if (std::isalpha(head())) return scanLiteral(); if (std::isdigit(head()) || head() == '.' || head() == '-') return scanNumber(); switch(head()) { case '=': advance(); return {Equal{}}; case '{': advance(); return {Open_bracket{}}; case '}': advance(); return {Close_bracket{}}; case '(': advance(); return {Open_Parenthesis{}}; case ')': advance(); return {Close_Parenthesis{}}; case '\"': advance(); return {Quote{}}; case ':': advance(); return {Colon{}}; case ';': advance(); return {SemiColon{}}; case '|': advance(); return {VBar{}}; case ',': advance(); return {Comma{}}; default: error(Misc::StringUtils::format("unexpected token <%c>", head())); } } Token Lexer::scanLiteral() { auto start = mHead; advance(); while (mHead != mTail && (std::isalnum(head()) || head() == '_')) advance(); std::string_view value{start, static_cast(mHead - start)}; if (value == "shared") return Shared{}; if (value == "technique") return Technique{}; if (value == "render_target") return Render_Target{}; if (value == "vertex") return Vertex{}; if (value == "fragment") return Fragment{}; if (value == "compute") return Compute{}; if (value == "sampler_1d") return Sampler_1D{}; if (value == "sampler_2d") return Sampler_2D{}; if (value == "sampler_3d") return Sampler_3D{}; if (value == "uniform_bool") return Uniform_Bool{}; if (value == "uniform_float") return Uniform_Float{}; if (value == "uniform_int") return Uniform_Int{}; if (value == "uniform_vec2") return Uniform_Vec2{}; if (value == "uniform_vec3") return Uniform_Vec3{}; if (value == "uniform_vec4") return Uniform_Vec4{}; if (value == "true") return True{}; if (value == "false") return False{}; if (value == "vec2") return Vec2{}; if (value == "vec3") return Vec3{}; if (value == "vec4") return Vec4{}; return Literal{value}; } Token Lexer::scanStringLiteral() { advance(); // consume quote auto start = mHead; bool terminated = false; for (; mHead != mTail; advance()) { if (head() == '\"') { terminated = true; advance(); break; } } if (!terminated) error("unterminated string"); return String{{start, static_cast(mHead - start - 1)}}; } Token Lexer::scanNumber() { double buffer; char* endPtr; buffer = std::strtod(mHead, &endPtr); if (endPtr == nullptr) error("critical error while parsing number"); const char* tmp = mHead; mHead = endPtr; for (; tmp != endPtr; ++tmp) { if ((*tmp == '.')) return Float{static_cast(buffer)}; } return Integer{static_cast(buffer)}; } } } openmw-openmw-0.48.0/components/fx/lexer.hpp000066400000000000000000000035101445372753700210650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_LEXER_H #define OPENMW_COMPONENTS_FX_LEXER_H #include #include #include #include #include #include #include #include #include #include "lexer_types.hpp" namespace fx { namespace Lexer { struct LexerException : std::runtime_error { LexerException(const std::string& message) : std::runtime_error(message) {} LexerException(const char* message) : std::runtime_error(message) {} }; class Lexer { public: struct Block { int line; std::string_view content; }; Lexer(std::string_view buffer); Lexer() = delete; Token next(); Token peek(); // Jump ahead to next uncommented closing bracket at level zero. Assumes the head is at an opening bracket. // Returns the contents of the block excluding the brackets and places cursor at closing bracket. std::optional jump(); Block getLastJumpBlock() const; [[noreturn]] void error(const std::string& msg); private: void drop(); void advance(); char head(); bool peekChar(char c); Token scanToken(); Token scanLiteral(); Token scanStringLiteral(); Token scanNumber(); const char* mHead; const char* mTail; std::size_t mAbsolutePos; std::size_t mColumn; std::size_t mLine; std::string_view mBuffer; Token mLastToken; std::optional mLookahead; Block mLastJumpBlock; }; } } #endif openmw-openmw-0.48.0/components/fx/lexer_types.hpp000066400000000000000000000102041445372753700223070ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_LEXER_TYPES_H #define OPENMW_COMPONENTS_FX_LEXER_TYPES_H #include #include namespace fx { namespace Lexer { struct Float { inline static constexpr std::string_view repr = "float"; float value = 0.0;}; struct Integer { inline static constexpr std::string_view repr = "integer"; int value = 0;}; struct Boolean { inline static constexpr std::string_view repr = "boolean"; bool value = false;}; struct Literal { inline static constexpr std::string_view repr = "literal"; std::string_view value;}; struct String { inline static constexpr std::string_view repr = "string"; std::string_view value;}; struct Shared { inline static constexpr std::string_view repr = "shared"; }; struct Vertex { inline static constexpr std::string_view repr = "vertex"; }; struct Fragment { inline static constexpr std::string_view repr = "fragment"; }; struct Compute { inline static constexpr std::string_view repr = "compute"; }; struct Technique { inline static constexpr std::string_view repr = "technique"; }; struct Render_Target { inline static constexpr std::string_view repr = "render_target"; }; struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; }; struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; }; struct Sampler_3D { inline static constexpr std::string_view repr = "sampler_3d"; }; struct Uniform_Bool { inline static constexpr std::string_view repr = "uniform_bool"; }; struct Uniform_Float { inline static constexpr std::string_view repr = "uniform_float"; }; struct Uniform_Int { inline static constexpr std::string_view repr = "uniform_int"; }; struct Uniform_Vec2 { inline static constexpr std::string_view repr = "uniform_vec2"; }; struct Uniform_Vec3 { inline static constexpr std::string_view repr = "uniform_vec3"; }; struct Uniform_Vec4 { inline static constexpr std::string_view repr = "uniform_vec4"; }; struct Eof { inline static constexpr std::string_view repr = "eof"; }; struct Equal { inline static constexpr std::string_view repr = "equal"; }; struct Open_bracket { inline static constexpr std::string_view repr = "open_bracket"; }; struct Close_bracket { inline static constexpr std::string_view repr = "close_bracket"; }; struct Open_Parenthesis { inline static constexpr std::string_view repr = "open_parenthesis"; }; struct Close_Parenthesis{ inline static constexpr std::string_view repr = "close_parenthesis"; }; struct Quote { inline static constexpr std::string_view repr = "quote"; }; struct SemiColon { inline static constexpr std::string_view repr = "semicolon"; }; struct Comma { inline static constexpr std::string_view repr = "comma"; }; struct VBar { inline static constexpr std::string_view repr = "vbar"; }; struct Colon { inline static constexpr std::string_view repr = "colon"; }; struct True { inline static constexpr std::string_view repr = "true"; }; struct False { inline static constexpr std::string_view repr = "false"; }; struct Vec2 { inline static constexpr std::string_view repr = "vec2"; }; struct Vec3 { inline static constexpr std::string_view repr = "vec3"; }; struct Vec4 { inline static constexpr std::string_view repr = "vec4"; }; using Token = std::variant; } } #endifopenmw-openmw-0.48.0/components/fx/parse_constants.hpp000066400000000000000000000145231445372753700231620ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H #define OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H #include #include #include #include #include #include #include #include "technique.hpp" namespace fx { namespace constants { constexpr std::array, 6> TechniqueFlag = {{ {"disable_interiors" , Technique::Flag_Disable_Interiors}, {"disable_exteriors" , Technique::Flag_Disable_Exteriors}, {"disable_underwater" , Technique::Flag_Disable_Underwater}, {"disable_abovewater" , Technique::Flag_Disable_Abovewater}, {"disable_sunglare" , Technique::Flag_Disable_SunGlare}, {"hidden" , Technique::Flag_Hidden} }}; constexpr std::array, 6> SourceFormat = {{ {"red" , GL_RED}, {"rg" , GL_RG}, {"rgb" , GL_RGB}, {"bgr" , GL_BGR}, {"rgba", GL_RGBA}, {"bgra", GL_BGRA}, }}; constexpr std::array, 9> SourceType = {{ {"byte" , GL_BYTE}, {"unsigned_byte" , GL_UNSIGNED_BYTE}, {"short" , GL_SHORT}, {"unsigned_short" , GL_UNSIGNED_SHORT}, {"int" , GL_INT}, {"unsigned_int" , GL_UNSIGNED_INT}, {"unsigned_int_24_8", GL_UNSIGNED_INT_24_8}, {"float" , GL_FLOAT}, {"double" , GL_DOUBLE}, }}; constexpr std::array, 16> InternalFormat = {{ {"red" , GL_RED}, {"r16f" , GL_R16F}, {"r32f" , GL_R32F}, {"rg" , GL_RG}, {"rg16f" , GL_RG16F}, {"rg32f" , GL_RG32F}, {"rgb" , GL_RGB}, {"rgb16f" , GL_RGB16F}, {"rgb32f" , GL_RGB32F}, {"rgba" , GL_RGBA}, {"rgba16f" , GL_RGBA16F}, {"rgba32f" , GL_RGBA32F}, {"depth_component16" , GL_DEPTH_COMPONENT16}, {"depth_component24" , GL_DEPTH_COMPONENT24}, {"depth_component32" , GL_DEPTH_COMPONENT32}, {"depth_component32f", GL_DEPTH_COMPONENT32F} }}; constexpr std::array, 13> Compression = {{ {"auto" , osg::Texture::USE_USER_DEFINED_FORMAT}, {"arb" , osg::Texture::USE_ARB_COMPRESSION}, {"s3tc_dxt1" , osg::Texture::USE_S3TC_DXT1_COMPRESSION}, {"s3tc_dxt3" , osg::Texture::USE_S3TC_DXT3_COMPRESSION}, {"s3tc_dxt5" , osg::Texture::USE_S3TC_DXT5_COMPRESSION}, {"pvrtc_2bpp" , osg::Texture::USE_PVRTC_2BPP_COMPRESSION}, {"pvrtc_4bpp" , osg::Texture::USE_PVRTC_4BPP_COMPRESSION}, {"etc" , osg::Texture::USE_ETC_COMPRESSION}, {"etc2" , osg::Texture::USE_ETC2_COMPRESSION}, {"rgtc1" , osg::Texture::USE_RGTC1_COMPRESSION}, {"rgtc2" , osg::Texture::USE_RGTC2_COMPRESSION}, {"s3tc_dxt1c" , osg::Texture::USE_S3TC_DXT1c_COMPRESSION}, {"s3tc_dxt1a" , osg::Texture::USE_S3TC_DXT1a_COMPRESSION} }}; constexpr std::array, 4> WrapMode = {{ {"clamp_to_edge" , osg::Texture::CLAMP_TO_EDGE}, {"clamp_to_border", osg::Texture::CLAMP_TO_BORDER}, {"repeat" , osg::Texture::REPEAT}, {"mirror" , osg::Texture::MIRROR} }}; constexpr std::array, 6> FilterMode = {{ {"linear" , osg::Texture::LINEAR}, {"linear_mipmap_linear" , osg::Texture::LINEAR_MIPMAP_LINEAR}, {"linear_mipmap_nearest" , osg::Texture::LINEAR_MIPMAP_NEAREST}, {"nearest" , osg::Texture::NEAREST}, {"nearest_mipmap_linear" , osg::Texture::NEAREST_MIPMAP_LINEAR}, {"nearest_mipmap_nearest", osg::Texture::NEAREST_MIPMAP_NEAREST} }}; constexpr std::array, 15> BlendFunc = {{ {"dst_alpha" , osg::BlendFunc::DST_ALPHA}, {"dst_color" , osg::BlendFunc::DST_COLOR}, {"one" , osg::BlendFunc::ONE}, {"one_minus_dst_alpha" , osg::BlendFunc::ONE_MINUS_DST_ALPHA}, {"one_minus_dst_color" , osg::BlendFunc::ONE_MINUS_DST_COLOR}, {"one_minus_src_alpha" , osg::BlendFunc::ONE_MINUS_SRC_ALPHA}, {"one_minus_src_color" , osg::BlendFunc::ONE_MINUS_SRC_COLOR}, {"src_alpha" , osg::BlendFunc::SRC_ALPHA}, {"src_alpha_saturate" , osg::BlendFunc::SRC_ALPHA_SATURATE}, {"src_color" , osg::BlendFunc::SRC_COLOR}, {"constant_color" , osg::BlendFunc::CONSTANT_COLOR}, {"one_minus_constant_color" , osg::BlendFunc::ONE_MINUS_CONSTANT_COLOR}, {"constant_alpha" , osg::BlendFunc::CONSTANT_ALPHA}, {"one_minus_constant_alpha" , osg::BlendFunc::ONE_MINUS_CONSTANT_ALPHA}, {"zero" , osg::BlendFunc::ZERO} }}; constexpr std::array, 8> BlendEquation = {{ {"rgba_min" , osg::BlendEquation::RGBA_MIN}, {"rgba_max" , osg::BlendEquation::RGBA_MAX}, {"alpha_min" , osg::BlendEquation::ALPHA_MIN}, {"alpha_max" , osg::BlendEquation::ALPHA_MAX}, {"logic_op" , osg::BlendEquation::LOGIC_OP}, {"add" , osg::BlendEquation::FUNC_ADD}, {"subtract" , osg::BlendEquation::FUNC_SUBTRACT}, {"reverse_subtract" , osg::BlendEquation::FUNC_REVERSE_SUBTRACT} }}; } } #endifopenmw-openmw-0.48.0/components/fx/pass.cpp000066400000000000000000000243631445372753700207200ustar00rootroot00000000000000#include "pass.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "technique.hpp" #include "stateupdater.hpp" namespace { constexpr char s_DefaultVertex[] = R"GLSL( #if OMW_USE_BINDINGS omw_In vec2 omw_Vertex; #endif omw_Out vec2 omw_TexCoord; void main() { omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0); omw_TexCoord = omw_Position.xy * 0.5 + 0.5; })GLSL"; constexpr char s_DefaultVertexMultiview[] = R"GLSL( layout(num_views = 2) in; #if OMW_USE_BINDINGS omw_In vec2 omw_Vertex; #endif omw_Out vec2 omw_TexCoord; void main() { omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0); omw_TexCoord = omw_Position.xy * 0.5 + 0.5; })GLSL"; } namespace fx { Pass::Pass(Pass::Type type, Pass::Order order, bool ubo) : mCompiled(false) , mType(type) , mOrder(order) , mLegacyGLSL(true) , mUBO(ubo) { } std::string Pass::getPassHeader(Technique& technique, std::string_view preamble, bool fragOut) { std::string header = R"GLSL( #version @version @profile @extensions @uboStruct #define OMW_REVERSE_Z @reverseZ #define OMW_RADIAL_FOG @radialFog #define OMW_EXPONENTIAL_FOG @exponentialFog #define OMW_HDR @hdr #define OMW_NORMALS @normals #define OMW_USE_BINDINGS @useBindings #define OMW_MULTIVIEW @multiview #define omw_In @in #define omw_Out @out #define omw_Position @position #define omw_Texture1D @texture1D #define omw_Texture2D @texture2D #define omw_Texture3D @texture3D #define omw_Vertex @vertex #define omw_FragColor @fragColor @fragBinding uniform @builtinSampler omw_SamplerLastShader; uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerNormals; uniform vec4 omw_PointLights[@pointLightCount]; uniform int omw_PointLightsCount; #if OMW_MULTIVIEW uniform mat4 projectionMatrixMultiView[2]; uniform mat4 invProjectionMatrixMultiView[2]; #endif int omw_GetPointLightCount() { return omw_PointLightsCount; } vec3 omw_GetPointLightWorldPos(int index) { return omw_PointLights[(index * 3)].xyz; } vec3 omw_GetPointLightDiffuse(int index) { return omw_PointLights[(index * 3) + 1].xyz; } vec3 omw_GetPointLightAttenuation(int index) { return omw_PointLights[(index * 3) + 2].xyz; } float omw_GetPointLightRadius(int index) { return omw_PointLights[(index * 3) + 2].w; } #if @ubo layout(std140) uniform _data { _omw_data omw; }; #else uniform _omw_data omw; #endif mat4 omw_ProjectionMatrix() { #if OMW_MULTIVIEW return projectionMatrixMultiView[gl_ViewID_OVR]; #else return omw.projectionMatrix; #endif } mat4 omw_InvProjectionMatrix() { #if OMW_MULTIVIEW return invProjectionMatrixMultiView[gl_ViewID_OVR]; #else return omw.invProjectionMatrix; #endif } float omw_GetDepth(vec2 uv) { #if OMW_MULTIVIEW float depth = omw_Texture2D(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r; #else float depth = omw_Texture2D(omw_SamplerDepth, uv).r; #endif #if OMW_REVERSE_Z return 1.0 - depth; #else return depth; #endif } vec4 omw_GetLastShader(vec2 uv) { #if OMW_MULTIVIEW return omw_Texture2D(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR)); #else return omw_Texture2D(omw_SamplerLastShader, uv); #endif } vec4 omw_GetLastPass(vec2 uv) { #if OMW_MULTIVIEW return omw_Texture2D(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR)); #else return omw_Texture2D(omw_SamplerLastPass, uv); #endif } vec3 omw_GetNormals(vec2 uv) { #if OMW_MULTIVIEW return omw_Texture2D(omw_SamplerNormals, vec3(uv, gl_ViewID_OVR)).rgb * 2.0 - 1.0; #else return omw_Texture2D(omw_SamplerNormals, uv).rgb * 2.0 - 1.0; #endif } vec3 omw_GetWorldPosFromUV(vec2 uv) { float depth = omw_GetDepth(uv); #if (OMW_REVERSE_Z == 1) float flippedDepth = 1.0 - depth; #else float flippedDepth = depth * 2.0 - 1.0; #endif vec4 clip_space = vec4(uv * 2.0 - 1.0, flippedDepth, 1.0); vec4 world_space = omw.invViewMatrix * (omw.invProjectionMatrix * clip_space); return world_space.xyz / world_space.w; } float omw_GetLinearDepth(vec2 uv) { #if (OMW_REVERSE_Z == 1) float depth = omw_GetDepth(uv); float dist = omw.near * omw.far / (omw.far + depth * (omw.near - omw.far)); #else float depth = omw_GetDepth(uv) * 2.0 - 1.0; float dist = 2.0 * omw.near * omw.far / (omw.far + omw.near - depth * (omw.far - omw.near)); #endif return dist; } float omw_EstimateFogCoverageFromUV(vec2 uv) { #if OMW_RADIAL_FOG vec3 uvPos = omw_GetWorldPosFromUV(uv); float dist = length(uvPos - omw.eyePos.xyz); #else float dist = omw_GetLinearDepth(uv); #endif #if OMW_EXPONENTIAL_FOG float fogValue = 1.0 - exp(-2.0 * max(0.0, dist - omw.fogNear/2.0) / (omw.fogFar - omw.fogNear/2.0)); #else float fogValue = clamp((dist - omw.fogNear) / (omw.fogFar - omw.fogNear), 0.0, 1.0); #endif return fogValue; } #if OMW_HDR uniform sampler2D omw_EyeAdaptation; #endif float omw_GetEyeAdaptation() { #if OMW_HDR return omw_Texture2D(omw_EyeAdaptation, vec2(0.5, 0.5)).r; #else return 1.0; #endif } )GLSL"; std::stringstream extBlock; for (const auto& extension : technique.getGLSLExtensions()) extBlock << "#ifdef " << extension << '\n' << "\t#extension " << extension << ": enable" << '\n' << "#endif" << '\n'; const std::vector> defines = { {"@pointLightCount", std::to_string(SceneUtil::PPLightBuffer::sMaxPPLightsArraySize)}, {"@version", std::to_string(technique.getGLSLVersion())}, {"@multiview", Stereo::getMultiview() ? "1" : "0"}, {"@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D"}, {"@profile", technique.getGLSLProfile()}, {"@extensions", extBlock.str()}, {"@uboStruct", StateUpdater::getStructDefinition()}, {"@ubo", mUBO ? "1" : "0"}, {"@normals", technique.getNormals() ? "1" : "0"}, {"@reverseZ", SceneUtil::AutoDepth::isReversed() ? "1" : "0"}, {"@radialFog", Settings::Manager::getBool("radial fog", "Fog") ? "1" : "0"}, {"@exponentialFog", Settings::Manager::getBool("exponential fog", "Fog") ? "1" : "0"}, {"@hdr", technique.getHDR() ? "1" : "0"}, {"@in", mLegacyGLSL ? "varying" : "in"}, {"@out", mLegacyGLSL ? "varying" : "out"}, {"@position", "gl_Position"}, {"@texture1D", mLegacyGLSL ? "texture1D" : "texture"}, {"@texture2D", mLegacyGLSL ? "texture2D" : "texture"}, {"@texture3D", mLegacyGLSL ? "texture3D" : "texture"}, {"@vertex", mLegacyGLSL ? "gl_Vertex" : "_omw_Vertex"}, {"@fragColor", mLegacyGLSL ? "gl_FragColor" : "_omw_FragColor"}, {"@useBindings", mLegacyGLSL ? "0" : "1"}, {"@fragBinding", mLegacyGLSL ? "" : "out vec4 omw_FragColor;"} }; for (const auto& [define, value]: defines) for (size_t pos = header.find(define); pos != std::string::npos; pos = header.find(define)) header.replace(pos, define.size(), value); for (const auto& target : mRenderTargets) header.append("uniform sampler2D " + std::string(target) + ";"); for (auto& uniform : technique.getUniformMap()) if (auto glsl = uniform->getGLSL()) header.append(glsl.value()); header.append(preamble); return header; } void Pass::prepareStateSet(osg::StateSet* stateSet, const std::string& name) const { osg::ref_ptr program = new osg::Program; if (mType == Type::Pixel) { program->addShader(new osg::Shader(*mVertex)); program->addShader(new osg::Shader(*mFragment)); } else if (mType == Type::Compute) { program->addShader(new osg::Shader(*mCompute)); } if (mUBO) program->addBindUniformBlock("_data", static_cast(Resource::SceneManager::UBOBinding::PostProcessor)); program->setName(name); if (!mLegacyGLSL) { program->addBindFragDataLocation("_omw_FragColor", 0); program->addBindAttribLocation("_omw_Vertex", 0); } stateSet->setAttribute(program); if (mBlendSource && mBlendDest) stateSet->setAttributeAndModes(new osg::BlendFunc(mBlendSource.value(), mBlendDest.value())); if (mBlendEq) stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value())); if (mClearColor) stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); } void Pass::dirty() { mVertex = nullptr; mFragment = nullptr; mCompute = nullptr; mCompiled = false; } void Pass::compile(Technique& technique, std::string_view preamble) { if (mCompiled) return; mLegacyGLSL = technique.getGLSLVersion() != 330; if (mType == Type::Pixel) { if (!mVertex) mVertex = new osg::Shader(osg::Shader::VERTEX, Stereo::getMultiview() ? s_DefaultVertexMultiview : s_DefaultVertex); mVertex->setShaderSource(getPassHeader(technique, preamble).append(mVertex->getShaderSource())); mFragment->setShaderSource(getPassHeader(technique, preamble, true).append(mFragment->getShaderSource())); mVertex->setName(mName); mFragment->setName(mName); } else if (mType == Type::Compute) { mCompute->setShaderSource(getPassHeader(technique, preamble).append(mCompute->getShaderSource())); mCompute->setName(mName); } mCompiled = true; } } openmw-openmw-0.48.0/components/fx/pass.hpp000066400000000000000000000034551445372753700207240ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_PASS_H #define OPENMW_COMPONENTS_FX_PASS_H #include #include #include #include #include #include #include #include #include #include #include #include namespace fx { class Technique; class Pass { public: enum class Order { Forward, Post }; enum class Type { None, Pixel, Compute }; friend class Technique; Pass(Type type=Type::Pixel, Order order=Order::Post, bool ubo = false); void compile(Technique& technique, std::string_view preamble); std::string getTarget() const { return mTarget; } const std::array& getRenderTargets() const { return mRenderTargets; } void prepareStateSet(osg::StateSet* stateSet, const std::string& name) const; std::string getName() const { return mName; } void dirty(); private: std::string getPassHeader(Technique& technique, std::string_view preamble, bool fragOut = false); bool mCompiled; osg::ref_ptr mVertex; osg::ref_ptr mFragment; osg::ref_ptr mCompute; Type mType; Order mOrder; std::string mName; bool mLegacyGLSL; bool mUBO; std::array mRenderTargets; std::string mTarget; std::optional mClearColor; std::optional mBlendSource; std::optional mBlendDest; std::optional mBlendEq; }; } #endif openmw-openmw-0.48.0/components/fx/stateupdater.cpp000066400000000000000000000046651445372753700224620ustar00rootroot00000000000000#include "stateupdater.hpp" #include #include #include #include namespace fx { StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) {} void StateUpdater::setDefaults(osg::StateSet* stateset) { if (mUseUBO) { osg::ref_ptr ubo = new osg::UniformBufferObject; osg::ref_ptr> data = new osg::BufferTemplate(); data->setBufferObject(ubo); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::PostProcessor), data, 0, mData.getGPUSize()); stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } else { const auto createUniform = [&] (const auto& v) { using T = std::decay_t; std::string name = "omw." + std::string(T::sName); stateset->addUniform(new osg::Uniform(name.c_str(), mData.get())); }; std::apply([&] (const auto& ... v) { (createUniform(v) , ...); }, mData.getData()); } } void StateUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) { if (mUseUBO) { osg::UniformBufferBinding* ubb = dynamic_cast(stateset->getAttribute(osg::StateAttribute::UNIFORMBUFFERBINDING, static_cast(Resource::SceneManager::UBOBinding::PostProcessor))); if (!ubb) throw std::runtime_error("StateUpdater::apply: failed to get an UniformBufferBinding!"); auto& dest = static_cast*>(ubb->getBufferData())->getData(); mData.copyTo(dest); ubb->getBufferData()->dirty(); } else { const auto setUniform = [&] (const auto& v) { using T = std::decay_t; std::string name = "omw." + std::string(T::sName); stateset->getUniform(name)->set(mData.get()); }; std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData()); } if (mPointLightBuffer) mPointLightBuffer->applyUniforms(nv->getTraversalNumber(), stateset); } } openmw-openmw-0.48.0/components/fx/stateupdater.hpp000066400000000000000000000163451445372753700224650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H #define OPENMW_COMPONENTS_FX_STATEUPDATER_H #include #include #include #include namespace fx { class StateUpdater : public SceneUtil::StateSetUpdater { public: StateUpdater(bool useUBO); void setProjectionMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; mData.get() = osg::Matrixf::inverse(matrix); } void setViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; mData.get() = osg::Matrixf::inverse(matrix); } void setPrevViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix;} void setEyePos(const osg::Vec3f& pos) { mData.get() = osg::Vec4f(pos, 0.f); } void setEyeVec(const osg::Vec3f& vec) { mData.get() = osg::Vec4f(vec, 0.f); } void setFogColor(const osg::Vec4f& color) { mData.get() = color; } void setSunColor(const osg::Vec4f& color) { mData.get() = color; } void setSunPos(const osg::Vec4f& pos, bool night) { mData.get() = pos; if (night) mData.get().z() *= -1.f; } void setResolution(const osg::Vec2f& size) { mData.get() = size; mData.get() = {1.f / size.x(), 1.f / size.y()}; } void setSunVis(float vis) { mData.get() = vis; } void setFogRange(float near, float far) { mData.get() = near; mData.get() = far; } void setNearFar(float near, float far) { mData.get() = near; mData.get() = far; } void setIsUnderwater(bool underwater) { mData.get() = underwater; } void setIsInterior(bool interior) { mData.get() = interior; } void setFov(float fov) { mData.get() = fov; } void setGameHour(float hour) { mData.get() = hour; } void setWeatherId(int id) { mData.get() = id; } void setNextWeatherId(int id) { mData.get() = id; } void setWaterHeight(float height) { mData.get() = height; } void setIsWaterEnabled(bool enabled) { mData.get() = enabled; } void setSimulationTime(float time) { mData.get() = time; } void setDeltaSimulationTime(float time) { mData.get() = time; } void setWindSpeed(float speed) { mData.get() = speed; } void setWeatherTransition(float transition) { mData.get() = transition > 0 ? 1 - transition : 0; } void bindPointLights(std::shared_ptr buffer) { mPointLightBuffer = buffer; } static std::string getStructDefinition() { static std::string definition = UniformData::getDefinition("_omw_data"); return definition; } void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; private: struct ProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "projectionMatrix"; }; struct InvProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "invProjectionMatrix"; }; struct ViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "viewMatrix"; }; struct PrevViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "prevViewMatrix"; }; struct InvViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "invViewMatrix"; }; struct EyePos : std140::Vec4 { static constexpr std::string_view sName = "eyePos"; }; struct EyeVec : std140::Vec4 { static constexpr std::string_view sName = "eyeVec"; }; struct FogColor : std140::Vec4 { static constexpr std::string_view sName = "fogColor"; }; struct SunColor : std140::Vec4 { static constexpr std::string_view sName = "sunColor"; }; struct SunPos : std140::Vec4 { static constexpr std::string_view sName = "sunPos"; }; struct Resolution : std140::Vec2 { static constexpr std::string_view sName = "resolution"; }; struct RcpResolution : std140::Vec2 { static constexpr std::string_view sName = "rcpResolution"; }; struct FogNear : std140::Float { static constexpr std::string_view sName = "fogNear"; }; struct FogFar : std140::Float { static constexpr std::string_view sName = "fogFar"; }; struct Near : std140::Float { static constexpr std::string_view sName = "near"; }; struct Far : std140::Float { static constexpr std::string_view sName = "far"; }; struct Fov : std140::Float { static constexpr std::string_view sName = "fov"; }; struct GameHour : std140::Float { static constexpr std::string_view sName = "gameHour"; }; struct SunVis : std140::Float { static constexpr std::string_view sName = "sunVis"; }; struct WaterHeight : std140::Float { static constexpr std::string_view sName = "waterHeight"; }; struct IsWaterEnabled : std140::Bool { static constexpr std::string_view sName = "isWaterEnabled"; }; struct SimulationTime : std140::Float { static constexpr std::string_view sName = "simulationTime"; }; struct DeltaSimulationTime : std140::Float { static constexpr std::string_view sName = "deltaSimulationTime"; }; struct WindSpeed : std140::Float { static constexpr std::string_view sName = "windSpeed"; }; struct WeatherTransition : std140::Float { static constexpr std::string_view sName = "weatherTransition"; }; struct WeatherID : std140::Int { static constexpr std::string_view sName = "weatherID"; }; struct NextWeatherID : std140::Int { static constexpr std::string_view sName = "nextWeatherID"; }; struct IsUnderwater : std140::Bool { static constexpr std::string_view sName = "isUnderwater"; }; struct IsInterior : std140::Bool { static constexpr std::string_view sName = "isInterior"; }; using UniformData = std140::UBO< ProjectionMatrix, InvProjectionMatrix, ViewMatrix, PrevViewMatrix, InvViewMatrix, EyePos, EyeVec, FogColor, SunColor, SunPos, Resolution, RcpResolution, FogNear, FogFar, Near, Far, Fov, GameHour, SunVis, WaterHeight, IsWaterEnabled, SimulationTime, DeltaSimulationTime, WindSpeed, WeatherTransition, WeatherID, NextWeatherID, IsUnderwater, IsInterior >; UniformData mData; bool mUseUBO; std::shared_ptr mPointLightBuffer; }; } #endif openmw-openmw-0.48.0/components/fx/technique.cpp000066400000000000000000001043061445372753700217330ustar00rootroot00000000000000#include "technique.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parse_constants.hpp" namespace { struct ProxyTextureData { osg::Texture::WrapMode wrap_s = osg::Texture::CLAMP_TO_EDGE; osg::Texture::WrapMode wrap_t = osg::Texture::CLAMP_TO_EDGE; osg::Texture::WrapMode wrap_r = osg::Texture::CLAMP_TO_EDGE; osg::Texture::FilterMode min_filter = osg::Texture::LINEAR_MIPMAP_LINEAR; osg::Texture::FilterMode mag_filter =osg::Texture::LINEAR; osg::Texture::InternalFormatMode compression = osg::Texture::USE_IMAGE_DATA_FORMAT; std::optional source_format; std::optional source_type; std::optional internal_format; }; } namespace fx { Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals) : mName(name) , mFileName((boost::filesystem::path(Technique::sSubdir) / (mName + Technique::sExt)).string()) , mLastModificationTime(std::chrono::time_point()) , mWidth(width) , mHeight(height) , mVFS(vfs) , mImageManager(imageManager) , mUBO(ubo) , mSupportsNormals(supportsNormals) { clear(); } void Technique::clear() { mTextures.clear(); mStatus = Status::Uncompiled; mValid = false; mHDR = false; mNormals = false; mLights = false; mEnabled = true; mPassMap.clear(); mPasses.clear(); mPassKeys.clear(); mDefinedUniforms.clear(); mRenderTargets.clear(); mLastAppliedType = Pass::Type::None; mFlags = 0; mShared.clear(); mAuthor = {}; mDescription = {}; mVersion = {}; mGLSLExtensions.clear(); mGLSLVersion = mUBO ? 330 : 120; mGLSLProfile.clear(); mDynamic = false; } std::string Technique::getBlockWithLineDirective() { auto block = mLexer->getLastJumpBlock(); std::string content = std::string(block.content); content = "\n#line " + std::to_string(block.line + 1) + "\n" + std::string(block.content) + "\n"; return content; } Technique::UniformMap::iterator Technique::findUniform(const std::string& name) { return std::find_if(mDefinedUniforms.begin(), mDefinedUniforms.end(), [&name](const auto& uniform) { return uniform->mName == name; }); } bool Technique::compile() { clear(); if (!mVFS.exists(mFileName)) { Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; mStatus = Status::File_Not_exists; return false; } try { std::string source(std::istreambuf_iterator(*mVFS.get(getFileName())), {}); parse(std::move(source)); if (mPassKeys.empty()) error("no pass list found, ensure you define one in a 'technique' block"); int swaps = 0; for (auto& name : mPassKeys) { auto it = mPassMap.find(name); if (it == mPassMap.end()) error(Misc::StringUtils::format("pass '%s' was found in the pass list, but there was no matching 'fragment', 'vertex' or 'compute' block", std::string(name))); if (mLastAppliedType != Pass::Type::None && mLastAppliedType != it->second->mType) { swaps++; if (swaps == 2) Log(Debug::Warning) << "compute and pixel shaders are being swapped multiple times in shader chain, this can lead to serious performance drain."; } else mLastAppliedType = it->second->mType; if (Stereo::getMultiview()) { mGLSLExtensions.insert("GL_OVR_multiview"); mGLSLExtensions.insert("GL_OVR_multiview2"); mGLSLExtensions.insert("GL_EXT_texture_array"); } it->second->compile(*this, mShared); if (!it->second->mTarget.empty()) { auto rtIt = mRenderTargets.find(it->second->mTarget); if (rtIt == mRenderTargets.end()) error(Misc::StringUtils::format("target '%s' not defined", std::string(it->second->mTarget))); } mPasses.emplace_back(it->second); } if (mPasses.empty()) error("invalid pass list, no passes defined for technique"); mValid = true; } catch(const std::runtime_error& e) { clear(); mStatus = Status::Parse_Error; mLastError = "Failed parsing technique '" + getName() + "' " + e.what();; Log(Debug::Error) << mLastError; } return mValid; } std::string Technique::getName() const { return mName; } std::string Technique::getFileName() const { return mFileName; } bool Technique::setLastModificationTime(std::time_t timeStamp) { auto convertedTime = std::chrono::system_clock::from_time_t(timeStamp); const bool isDirty = convertedTime != mLastModificationTime; mLastModificationTime = convertedTime; return isDirty; } [[noreturn]] void Technique::error(const std::string& msg) { mLexer->error(msg); } template<> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'shared' block, expected closing brackets")); if (!mShared.empty()) error("repeated 'shared' block, only one allowed per technique file"); mShared = getBlockWithLineDirective(); } template<> void Technique::parseBlockImp() { if (!mPassKeys.empty()) error("exactly one 'technique' block can appear per file"); while (!isNext() && !isNext()) { expect(); auto key = std::get(mToken).value; expect(); if (key == "passes") mPassKeys = parseLiteralList(); else if (key == "version") mVersion = parseString(); else if (key == "description") mDescription = parseString(); else if (key == "author") mAuthor = parseString(); else if (key == "glsl_version") { int version = parseInteger(); if (mUBO && version > 330) mGLSLVersion = version; } else if (key == "flags") mFlags = parseFlags(); else if (key == "hdr") mHDR = parseBool(); else if (key == "pass_normals") mNormals = parseBool() && mSupportsNormals; else if (key == "pass_lights") mLights = parseBool(); else if (key == "glsl_profile") { expect(); mGLSLProfile = std::string(std::get(mToken).value); } else if (key == "glsl_extensions") { for (const auto& ext : parseLiteralList()) mGLSLExtensions.emplace(ext); } else if (key == "dynamic") mDynamic = parseBool(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); expect(); } if (mPassKeys.empty()) error("pass list in 'technique' block cannot be empty."); } template<> void Technique::parseBlockImp() { if (mRenderTargets.count(mBlockName)) error(Misc::StringUtils::format("redeclaration of render target '%s'", std::string(mBlockName))); fx::Types::RenderTarget rt; rt.mTarget->setTextureSize(mWidth, mHeight); rt.mTarget->setSourceFormat(GL_RGB); rt.mTarget->setInternalFormat(GL_RGB); rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); while (!isNext() && !isNext()) { expect(); auto key = std::get(mToken).value; expect(); if (key == "min_filter") rt.mTarget->setFilter(osg::Texture2D::MIN_FILTER, parseFilterMode()); else if (key == "mag_filter") rt.mTarget->setFilter(osg::Texture2D::MAG_FILTER, parseFilterMode()); else if (key == "wrap_s") rt.mTarget->setWrap(osg::Texture2D::WRAP_S, parseWrapMode()); else if (key == "wrap_t") rt.mTarget->setWrap(osg::Texture2D::WRAP_T, parseWrapMode()); else if (key == "width_ratio") rt.mSize.mWidthRatio = parseFloat(); else if (key == "height_ratio") rt.mSize.mHeightRatio = parseFloat(); else if (key == "width") rt.mSize.mWidth = parseInteger(); else if (key == "height") rt.mSize.mHeight = parseInteger(); else if (key == "internal_format") rt.mTarget->setInternalFormat(parseInternalFormat()); else if (key == "source_type") rt.mTarget->setSourceType(parseSourceType()); else if (key == "source_format") rt.mTarget->setSourceFormat(parseSourceFormat()); else if (key == "mipmaps") rt.mMipMap = parseBool(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); expect(); } mRenderTargets.emplace(mBlockName, std::move(rt)); } template<> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'vertex' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mName = mBlockName; if (pass->mCompute) error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); else if (!pass->mVertex) pass->mVertex = new osg::Shader(osg::Shader::VERTEX, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Pixel; } template<> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'fragment' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mUBO = mUBO; pass->mName = mBlockName; if (pass->mCompute) error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); else if (!pass->mFragment) pass->mFragment = new osg::Shader(osg::Shader::FRAGMENT, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Pixel; } template<> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'compute' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mName = mBlockName; if (pass->mFragment) error(Misc::StringUtils::format("'fragment' block already defined. Usage is ambiguous.")); else if (pass->mVertex) error(Misc::StringUtils::format("'vertex' block already defined. Usage is ambiguous.")); else if (!pass->mFragment) pass->mCompute = new osg::Shader(osg::Shader::COMPUTE, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Compute; } template void Technique::parseSampler() { if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); ProxyTextureData proxy; osg::ref_ptr sampler; constexpr bool is1D = std::is_same_v; constexpr bool is3D = std::is_same_v; Types::SamplerType type; while (!isNext() && !isNext()) { expect(); auto key = asLiteral(); expect(); if (!is1D && key == "min_filter") proxy.min_filter = parseFilterMode(); else if (!is1D && key == "mag_filter") proxy.mag_filter = parseFilterMode(); else if (key == "wrap_s") proxy.wrap_s = parseWrapMode(); else if (key == "wrap_t") proxy.wrap_t = parseWrapMode(); else if (is3D && key == "wrap_r") proxy.wrap_r = parseWrapMode(); else if (key == "compression") proxy.compression = parseCompression(); else if (key == "source_type") proxy.source_type = parseSourceType(); else if (key == "source_format") proxy.source_format = parseSourceFormat(); else if (key == "internal_format") proxy.internal_format = parseInternalFormat(); else if (key == "source") { expect(); auto image = mImageManager.getImage(std::string{std::get(mToken).value}, is3D); if constexpr (is1D) { type = Types::SamplerType::Texture_1D; sampler = new osg::Texture1D(image); } else if constexpr (is3D) { type = Types::SamplerType::Texture_3D; sampler = new osg::Texture3D(image); } else { type = Types::SamplerType::Texture_2D; sampler = new osg::Texture2D(image); } } else error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); expect(); } if (!sampler) error(Misc::StringUtils::format("%s '%s' requires a filename", std::string(T::repr), std::string{mBlockName})); if (!is1D) { sampler->setFilter(osg::Texture::MIN_FILTER, proxy.min_filter); sampler->setFilter(osg::Texture::MAG_FILTER, proxy.mag_filter); } if (is3D) sampler->setWrap(osg::Texture::WRAP_R, proxy.wrap_r); sampler->setWrap(osg::Texture::WRAP_S, proxy.wrap_s); sampler->setWrap(osg::Texture::WRAP_T, proxy.wrap_t); sampler->setInternalFormatMode(proxy.compression); if (proxy.internal_format.has_value()) sampler->setInternalFormat(proxy.internal_format.value()); if (proxy.source_type.has_value()) sampler->setSourceType(proxy.source_type.value()); if (proxy.internal_format.has_value()) sampler->setSourceFormat(proxy.internal_format.value()); sampler->setName(std::string{mBlockName}); sampler->setResizeNonPowerOfTwoHint(false); mTextures.emplace_back(sampler); std::shared_ptr uniform = std::make_shared(); uniform->mSamplerType = type; uniform->mName = std::string(mBlockName); mDefinedUniforms.emplace_back(std::move(uniform)); } template void Technique::parseUniform() { if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); std::shared_ptr uniform = std::make_shared(); Types::Uniform data = Types::Uniform(); while (!isNext() && !isNext()) { expect(); auto key = asLiteral(); expect("error parsing config for uniform block"); constexpr bool isVec = std::is_same_v || std::is_same_v || std::is_same_v; constexpr bool isFloat = std::is_same_v; constexpr bool isInt = std::is_same_v; constexpr bool isBool = std::is_same_v; static_assert(isVec || isFloat || isInt || isBool, "Unsupported type"); if (key == "default") { if constexpr (isVec) data.mDefault = parseVec(); else if constexpr (isFloat) data.mDefault = parseFloat(); else if constexpr (isInt) data.mDefault = parseInteger(); else if constexpr (isBool) data.mDefault = parseBool(); } else if (key == "size") { if constexpr (isBool) error("bool arrays currently unsupported"); int size = parseInteger(); if (size > 1) data.mArray = std::vector(size); } else if (key == "min") { if constexpr (isVec) data.mMin = parseVec(); else if constexpr (isFloat) data.mMin = parseFloat(); else if constexpr (isInt) data.mMin = parseInteger(); else if constexpr (isBool) data.mMin = parseBool(); } else if (key == "max") { if constexpr (isVec) data.mMax = parseVec(); else if constexpr (isFloat) data.mMax = parseFloat(); else if constexpr (isInt) data.mMax = parseInteger(); else if constexpr (isBool) data.mMax = parseBool(); } else if (key == "step") uniform->mStep = parseFloat(); else if (key == "static") uniform->mStatic = parseBool(); else if (key == "description") { expect(); uniform->mDescription = std::get(mToken).value; } else if (key == "header") { expect(); uniform->mHeader = std::get(mToken).value; } else if (key == "display_name") { expect(); uniform->mDisplayName = std::get(mToken).value; } else error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); expect(); } if (data.isArray()) uniform->mStatic = false; uniform->mName = std::string(mBlockName); uniform->mData = data; uniform->mTechniqueName = mName; if (data.mArray) { if constexpr (!std::is_same_v) { if (auto cached = Settings::ShaderManager::get().getValue>(mName, uniform->mName)) uniform->setValue(cached.value()); } } else if (auto cached = Settings::ShaderManager::get().getValue(mName, uniform->mName)) { uniform->setValue(cached.value()); } mDefinedUniforms.emplace_back(std::move(uniform)); } template<> void Technique::parseBlockImp() { parseSampler(); } template<> void Technique::parseBlockImp() { parseSampler(); } template<> void Technique::parseBlockImp() { parseSampler(); } template<> void Technique::parseBlockImp() { parseUniform(); } template<> void Technique::parseBlockImp() { parseUniform(); } template<> void Technique::parseBlockImp() { parseUniform(); } template<> void Technique::parseBlockImp() { parseUniform(); } template<> void Technique::parseBlockImp() { parseUniform(); } template<> void Technique::parseBlockImp() { parseUniform(); } template void Technique::expect(const std::string& err) { mToken = mLexer->next(); if (!std::holds_alternative(mToken)) { if (err.empty()) error(Misc::StringUtils::format("Expected %s", std::string(T::repr))); else error(Misc::StringUtils::format("%s. Expected %s", err, std::string(T::repr))); } } template void Technique::expect(const std::string& err) { mToken = mLexer->next(); if (!std::holds_alternative(mToken) && !std::holds_alternative(mToken)) { if (err.empty()) error(Misc::StringUtils::format("%s. Expected %s or %s", err, std::string(T::repr), std::string(T2::repr))); else error(Misc::StringUtils::format("Expected %s or %s", std::string(T::repr), std::string(T2::repr))); } } template bool Technique::isNext() { return std::holds_alternative(mLexer->peek()); } void Technique::parse(std::string&& buffer) { mBuffer = std::move(buffer); Misc::StringUtils::replaceAll(mBuffer, "\r\n", "\n"); mLexer = std::make_unique(mBuffer); for (auto t = mLexer->next(); !std::holds_alternative(t); t = mLexer->next()) { std::visit([this](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) parseBlock(false); else if constexpr (std::is_same_v) parseBlock(false); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else error("invalid top level block"); } , t); } } template void Technique::parseBlock(bool named) { mBlockName = T::repr; if (named) { expect("name is required for preceeding block decleration"); mBlockName = std::get(mToken).value; if (isNext()) parseBlockHeader(); } expect(); parseBlockImp(); expect(); } template std::vector Technique::parseLiteralList() { std::vector data; while (!isNext()) { expect(); data.emplace_back(std::get(mToken).value); if (!isNext()) break; mLexer->next(); } return data; } void Technique::parseBlockHeader() { expect(); if (isNext()) { mLexer->next(); return; } auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); bool clear = true; osg::Vec4f clearColor = {1,1,1,1}; while (!isNext()) { expect("invalid key in block header"); std::string_view key = std::get(mToken).value; expect(); if (key == "target") { expect(); pass->mTarget = std::get(mToken).value; } else if (key == "rt1") { expect(); pass->mRenderTargets[0] = std::get(mToken).value; } else if (key == "rt2") { expect(); pass->mRenderTargets[1] = std::get(mToken).value; } else if (key == "rt3") { expect(); pass->mRenderTargets[2] =std::get(mToken).value; } else if (key == "blend") { expect(); osg::BlendEquation::Equation blendEq = parseBlendEquation(); expect(); osg::BlendFunc::BlendFuncMode blendSrc = parseBlendFuncMode(); expect(); osg::BlendFunc::BlendFuncMode blendDest = parseBlendFuncMode(); expect(); pass->mBlendSource = blendSrc; pass->mBlendDest = blendDest; if (blendEq != osg::BlendEquation::FUNC_ADD) pass->mBlendEq = blendEq; } else if (key == "clear") clear = parseBool(); else if (key == "clear_color") clearColor = parseVec(); else error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); mToken = mLexer->next(); if (std::holds_alternative(mToken)) { if (std::holds_alternative(mLexer->peek())) error(Misc::StringUtils::format("leading comma in '%s' is not allowed", std::string(mBlockName))); else continue; } if (std::holds_alternative(mToken)) return; } if (clear) pass->mClearColor = clearColor; error("malformed block header"); } std::string_view Technique::asLiteral() const { return std::get(mToken).value; } FlagsType Technique::parseFlags() { auto parseBit = [this] (std::string_view term) { for (const auto& [identifer, bit]: constants::TechniqueFlag) { if (Misc::StringUtils::ciEqual(term, identifer)) return bit; } error(Misc::StringUtils::format("unrecognized flag '%s'", std::string(term))); }; FlagsType flag = 0; for (const auto& bit : parseLiteralList()) flag |= parseBit(bit); return flag; } osg::Texture::FilterMode Technique::parseFilterMode() { expect(); for (const auto& [identifer, mode]: constants::FilterMode) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized filter mode '%s'", std::string{asLiteral()})); } osg::Texture::WrapMode Technique::parseWrapMode() { expect(); for (const auto& [identifer, mode]: constants::WrapMode) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized wrap mode '%s'", std::string{asLiteral()})); } osg::Texture::InternalFormatMode Technique::parseCompression() { expect(); for (const auto& [identifer, mode]: constants::Compression) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized compression '%s'", std::string{asLiteral()})); } int Technique::parseInternalFormat() { expect(); for (const auto& [identifer, mode]: constants::InternalFormat) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized internal format '%s'", std::string{asLiteral()})); } int Technique::parseSourceType() { expect(); for (const auto& [identifer, mode]: constants::SourceType) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized source type '%s'", std::string{asLiteral()})); } int Technique::parseSourceFormat() { expect(); for (const auto& [identifer, mode]: constants::SourceFormat) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized source format '%s'", std::string{asLiteral()})); } osg::BlendEquation::Equation Technique::parseBlendEquation() { expect(); for (const auto& [identifer, mode]: constants::BlendEquation) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized blend equation '%s'", std::string{asLiteral()})); } osg::BlendFunc::BlendFuncMode Technique::parseBlendFuncMode() { expect(); for (const auto& [identifer, mode]: constants::BlendFunc) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{asLiteral()})); } bool Technique::parseBool() { mToken = mLexer->next(); if (std::holds_alternative(mToken)) return true; if (std::holds_alternative(mToken)) return false; error("expected 'true' or 'false' as boolean value"); } std::string_view Technique::parseString() { expect(); return std::get(mToken).value; } float Technique::parseFloat() { mToken = mLexer->next(); if (std::holds_alternative(mToken)) return std::get(mToken).value; if (std::holds_alternative(mToken)) return static_cast(std::get(mToken).value); error("expected float value"); } int Technique::parseInteger() { expect(); return std::get(mToken).value; } template OSGVec Technique::parseVec() { expect(); expect(); OSGVec value; for (int i = 0; i < OSGVec::num_components; ++i) { value[i] = parseFloat(); if (i < OSGVec::num_components - 1) expect(); } expect("check definition of the vector"); return value; } } openmw-openmw-0.48.0/components/fx/technique.hpp000066400000000000000000000215111445372753700217340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_TECHNIQUE_H #define OPENMW_COMPONENTS_FX_TECHNIQUE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pass.hpp" #include "lexer.hpp" #include "types.hpp" namespace Resource { class ImageManager; } namespace VFS { class Manager; } namespace fx { using FlagsType = size_t; struct DispatchNode { DispatchNode() = default; DispatchNode(const DispatchNode& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mHandle(other.mHandle) , mFlags(other.mFlags) , mRootStateSet(other.mRootStateSet) { mPasses.reserve(other.mPasses.size()); for (const auto& subpass : other.mPasses) mPasses.emplace_back(subpass, copyOp); } struct SubPass { SubPass() = default; osg::ref_ptr mStateSet = new osg::StateSet; osg::ref_ptr mRenderTarget; osg::ref_ptr mRenderTexture; bool mResolve = false; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) , mResolve(other.mResolve) { if (other.mRenderTarget) mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); if (other.mRenderTexture) mRenderTexture = new osg::Texture2D(*other.mRenderTexture, copyOp); } }; void compile() { for (auto rit = mPasses.rbegin(); rit != mPasses.rend(); ++rit) { if (!rit->mRenderTarget) { rit->mResolve = true; break; } } } // not safe to read/write in draw thread std::shared_ptr mHandle = nullptr; FlagsType mFlags = 0; std::vector mPasses; osg::ref_ptr mRootStateSet = new osg::StateSet; }; using DispatchArray = std::vector; class Technique { public: using PassList = std::vector>; using TexList = std::vector>; using UniformMap = std::vector>; using RenderTargetMap = std::unordered_map; inline static std::string sExt = ".omwfx"; inline static std::string sSubdir = "shaders"; enum class Status { Success, Uncompiled, File_Not_exists, Parse_Error }; static constexpr FlagsType Flag_Disable_Interiors = (1 << 0); static constexpr FlagsType Flag_Disable_Exteriors = (1 << 1); static constexpr FlagsType Flag_Disable_Underwater = (1 << 2); static constexpr FlagsType Flag_Disable_Abovewater = (1 << 3); static constexpr FlagsType Flag_Disable_SunGlare = (1 << 4); static constexpr FlagsType Flag_Hidden = (1 << 5); Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals); bool compile(); std::string getName() const; std::string getFileName() const; bool setLastModificationTime(std::time_t timeStamp); bool isValid() const { return mValid; } bool getHDR() const { return mHDR; } bool getNormals() const { return mNormals && mSupportsNormals; } bool getLights() const { return mLights; } const PassList& getPasses() { return mPasses; } const TexList& getTextures() const { return mTextures; } Status getStatus() const { return mStatus; } std::string_view getAuthor() const { return mAuthor; } std::string_view getDescription() const { return mDescription; } std::string_view getVersion() const { return mVersion; } int getGLSLVersion() const { return mGLSLVersion; } std::string getGLSLProfile() const { return mGLSLProfile; } const std::unordered_set& getGLSLExtensions() const { return mGLSLExtensions; } FlagsType getFlags() const { return mFlags; } bool getHidden() const { return mFlags & Flag_Hidden; } UniformMap& getUniformMap() { return mDefinedUniforms; } RenderTargetMap& getRenderTargetsMap() { return mRenderTargets; } std::string getLastError() const { return mLastError; } UniformMap::iterator findUniform(const std::string& name); bool getDynamic() const { return mDynamic; } void setLocked(bool locked) { mLocked = locked; } bool getLocked() const { return mLocked; } private: [[noreturn]] void error(const std::string& msg); void clear(); std::string_view asLiteral() const; template void expect(const std::string& err=""); template void expect(const std::string& err=""); template bool isNext(); void parse(std::string&& buffer); template void parseUniform(); template void parseSampler(); template void parseBlock(bool named=true); template void parseBlockImp() {} void parseBlockHeader(); bool parseBool(); std::string_view parseString(); float parseFloat(); int parseInteger(); int parseInternalFormat(); int parseSourceType(); int parseSourceFormat(); osg::BlendEquation::Equation parseBlendEquation(); osg::BlendFunc::BlendFuncMode parseBlendFuncMode(); osg::Texture::WrapMode parseWrapMode(); osg::Texture::InternalFormatMode parseCompression(); FlagsType parseFlags(); osg::Texture::FilterMode parseFilterMode(); template std::vector parseLiteralList(); template OSGVec parseVec(); std::string getBlockWithLineDirective(); std::unique_ptr mLexer; Lexer::Token mToken; std::string mShared; std::string mName; std::string mFileName; std::string_view mBlockName; std::string_view mAuthor; std::string_view mDescription; std::string_view mVersion; std::unordered_set mGLSLExtensions; int mGLSLVersion; std::string mGLSLProfile; FlagsType mFlags; Status mStatus; bool mEnabled; std::chrono::time_point mLastModificationTime; bool mValid; bool mHDR; bool mNormals; bool mLights; int mWidth; int mHeight; RenderTargetMap mRenderTargets; TexList mTextures; PassList mPasses; std::unordered_map> mPassMap; std::vector mPassKeys; Pass::Type mLastAppliedType; UniformMap mDefinedUniforms; const VFS::Manager& mVFS; Resource::ImageManager& mImageManager; bool mUBO; bool mSupportsNormals; std::string mBuffer; std::string mLastError; bool mDynamic = false; bool mLocked = false; }; template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); } #endif openmw-openmw-0.48.0/components/fx/types.hpp000066400000000000000000000244601445372753700211210ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_TYPES_H #define OPENMW_COMPONENTS_FX_TYPES_H #include #include #include #include #include #include #include #include #include #include #include #include #include "pass.hpp" namespace fx { namespace Types { struct SizeProxy { std::optional mWidthRatio; std::optional mHeightRatio; std::optional mWidth; std::optional mHeight; std::tuple get(int width, int height) const { int scaledWidth = width; int scaledHeight = height; if (mWidthRatio) scaledWidth = width * mWidthRatio.value(); else if (mWidth) scaledWidth = mWidth.value(); if (mHeightRatio > 0.f) scaledHeight = height * mHeightRatio.value(); else if (mHeight) scaledHeight = mHeight.value(); return std::make_tuple(scaledWidth, scaledHeight); } }; struct RenderTarget { osg::ref_ptr mTarget = new osg::Texture2D; SizeProxy mSize; bool mMipMap = false; }; template struct Uniform { std::optional mValue; std::optional> mArray; T mDefault = {}; T mMin = std::numeric_limits::lowest(); T mMax = std::numeric_limits::max(); using value_type = T; bool isArray() const { return mArray.has_value(); } const std::vector& getArray() const { return *mArray; } T getValue() const { return mValue.value_or(mDefault); } }; using Uniform_t = std::variant< Uniform, Uniform, Uniform, Uniform, Uniform, Uniform >; enum SamplerType { Texture_1D, Texture_2D, Texture_3D }; struct UniformBase { std::string mName; std::string mDisplayName; std::string mHeader; std::string mTechniqueName; std::string mDescription; bool mStatic = true; std::optional mSamplerType = std::nullopt; double mStep = 1.0; Uniform_t mData; template T getValue() const { auto value = Settings::ShaderManager::get().getValue(mTechniqueName, mName); return value.value_or(std::get>(mData).getValue()); } size_t getNumElements() const { return std::visit([&](auto&& arg) { ;return arg.isArray() ? arg.getArray().size() : 1; }, mData); } template T getMin() const { return std::get>(mData).mMin; } template T getMax() const { return std::get>(mData).mMax; } template T getDefault() const { return std::get>(mData).mDefault; } template void setValue(const T& value) { std::visit([&, value](auto&& arg){ using U = typename std::decay_t::value_type; if constexpr (std::is_same_v) { arg.mValue = value; Settings::ShaderManager::get().setValue(mTechniqueName, mName, value); } else { Log(Debug::Warning) << "Attempting to set uniform '" << mName << "' with wrong type"; } }, mData); } template void setValue(const std::vector& value) { std::visit([&, value](auto&& arg) { using U = typename std::decay_t::value_type; if (!arg.isArray() || arg.getArray().size() != value.size()) { Log(Debug::Error) << "Attempting to set uniform array '" << mName << "' with mismatching array sizes"; return; } if constexpr (std::is_same_v) { arg.mArray = value; Settings::ShaderManager::get().setValue(mTechniqueName, mName, value); } else Log(Debug::Warning) << "Attempting to set uniform array '" << mName << "' with wrong type"; }, mData); } void setUniform(osg::Uniform* uniform) { auto type = getType(); if (!type || type.value() != uniform->getType()) return; std::visit([&](auto&& arg) { if (arg.isArray()) { for (size_t i = 0; i < arg.getArray().size(); ++i) uniform->setElement(i, arg.getArray()[i]); uniform->dirty(); } else uniform->set(arg.getValue()); }, mData); } std::optional getType() const { return std::visit([](auto&& arg) -> std::optional { using T = typename std::decay_t::value_type; if constexpr (std::is_same_v) return osg::Uniform::FLOAT_VEC2; else if constexpr (std::is_same_v) return osg::Uniform::FLOAT_VEC3; else if constexpr (std::is_same_v) return osg::Uniform::FLOAT_VEC4; else if constexpr (std::is_same_v) return osg::Uniform::FLOAT; else if constexpr (std::is_same_v) return osg::Uniform::INT; else if constexpr (std::is_same_v) return osg::Uniform::BOOL; return std::nullopt; }, mData); } std::optional getGLSL() { if (mSamplerType) { switch (mSamplerType.value()) { case Texture_1D: return Misc::StringUtils::format("uniform sampler1D %s;", mName); case Texture_2D: return Misc::StringUtils::format("uniform sampler2D %s;", mName); case Texture_3D: return Misc::StringUtils::format("uniform sampler3D %s;", mName); } } return std::visit([&](auto&& arg) -> std::optional { using T = typename std::decay_t::value_type; auto value = arg.getValue(); const bool useUniform = arg.isArray() || (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug || mStatic == false); const std::string uname = arg.isArray() ? Misc::StringUtils::format("%s[%zu]", mName, arg.getArray().size()) : mName; if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform vec2 %s;", uname); return Misc::StringUtils::format("const vec2 %s=vec2(%f,%f);", mName, value[0], value[1]); } else if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform vec3 %s;", uname); return Misc::StringUtils::format("const vec3 %s=vec3(%f,%f,%f);", mName, value[0], value[1], value[2]); } else if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform vec4 %s;", uname); return Misc::StringUtils::format("const vec4 %s=vec4(%f,%f,%f,%f);", mName, value[0], value[1], value[2], value[3]); } else if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform float %s;", uname); return Misc::StringUtils::format("const float %s=%f;", mName, value); } else if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform int %s;", uname); return Misc::StringUtils::format("const int %s=%i;", mName, value); } else if constexpr (std::is_same_v) { if (useUniform) return Misc::StringUtils::format("uniform bool %s;", uname); return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false"); } return std::nullopt; }, mData); } }; } } #endif openmw-openmw-0.48.0/components/fx/widgets.cpp000066400000000000000000000126271445372753700214200ustar00rootroot00000000000000#include "widgets.hpp" #include namespace { template void createVectorWidget(const std::shared_ptr& uniform, MyGUI::Widget* client, fx::Widgets::UniformBase* base) { int height = client->getHeight(); base->setSize(base->getSize().width, (base->getSize().height - height) + (height * T::num_components)); client->setSize(client->getSize().width, height * T::num_components); for (int i = 0; i < T::num_components; ++i) { auto* widget = client->createWidget("MW_ValueEditNumber", {0, height * i, client->getWidth(), height}, MyGUI::Align::Default); widget->setData(uniform, static_cast(i)); base->addItem(widget); } } } namespace fx { namespace Widgets { void EditBool::setValue(bool value) { auto uniform = mUniform.lock(); if (!uniform) return; mCheckbutton->setCaptionWithReplacing(value ? "#{sOn}" : "#{sOff}"); mFill->setVisible(value); uniform->setValue(value); } void EditBool::setValueFromUniform() { auto uniform = mUniform.lock(); if (!uniform) return; setValue(uniform->template getValue()); } void EditBool::toDefault() { auto uniform = mUniform.lock(); if (!uniform) return; setValue(uniform->getDefault()); } void EditBool::initialiseOverride() { Base::initialiseOverride(); assignWidget(mCheckbutton, "Checkbutton"); assignWidget(mFill, "Fill"); mCheckbutton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditBool::notifyMouseButtonClick); } void EditBool::notifyMouseButtonClick(MyGUI::Widget* sender) { auto uniform = mUniform.lock(); if (!uniform) return; setValue(!uniform->getValue()); } void UniformBase::init(const std::shared_ptr& uniform) { if (uniform->mDisplayName.empty()) mLabel->setCaption(uniform->mName); else mLabel->setCaptionWithReplacing(uniform->mDisplayName); if (uniform->mDescription.empty()) { mLabel->setUserString("ToolTipType", ""); } else { mLabel->setUserString("ToolTipType", "Layout"); mLabel->setUserString("ToolTipLayout", "TextToolTip"); mLabel->setUserString("Caption_Text", uniform->mDescription); } std::visit([this, &uniform](auto&& arg) { using T = typename std::decay_t::value_type; if constexpr (std::is_same_v) { createVectorWidget(uniform, mClient, this); } else if constexpr (std::is_same_v) { createVectorWidget(uniform, mClient, this); } else if constexpr (std::is_same_v) { createVectorWidget(uniform, mClient, this); } else if constexpr (std::is_same_v) { auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); widget->setData(uniform); mBases.emplace_back(widget); } else if constexpr (std::is_same_v) { auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); widget->setData(uniform); mBases.emplace_back(widget); } else if constexpr (std::is_same_v) { auto* widget = mClient->createWidget("MW_ValueEditBool", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); widget->setData(uniform); mBases.emplace_back(widget); } mReset->eventMouseButtonClick += MyGUI::newDelegate(this, &UniformBase::notifyResetClicked); for (EditBase* base : mBases) base->setValueFromUniform(); }, uniform->mData); } void UniformBase::addItem(EditBase* item) { mBases.emplace_back(item); } void UniformBase::toDefault() { for (EditBase* base : mBases) { if (base) base->toDefault(); } } void UniformBase::notifyResetClicked(MyGUI::Widget* sender) { toDefault(); } void UniformBase::initialiseOverride() { Base::initialiseOverride(); assignWidget(mReset, "Reset"); assignWidget(mLabel, "Label"); assignWidget(mClient, "Client"); } } } openmw-openmw-0.48.0/components/fx/widgets.hpp000066400000000000000000000217171445372753700214250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_FX_WIDGETS_H #define OPENMW_COMPONENTS_FX_WIDGETS_H #include #include #include #include #include #include #include #include "technique.hpp" #include "types.hpp" namespace Gui { class AutoSizedTextBox; class AutoSizedButton; } namespace fx { namespace Widgets { enum Index { None = -1, Zero = 0, One = 1, Two = 2, Three = 3 }; class EditBase { public: virtual ~EditBase() = default; void setData(const std::shared_ptr& uniform, Index index = None) { mUniform = uniform; mIndex = index; } virtual void setValueFromUniform() = 0; virtual void toDefault() = 0; protected: std::weak_ptr mUniform; Index mIndex; }; class EditBool : public EditBase, public MyGUI::Widget { MYGUI_RTTI_DERIVED(EditBool) public: void setValue(bool value); void setValueFromUniform() override; void toDefault() override; private: void initialiseOverride() override; void notifyMouseButtonClick(MyGUI::Widget* sender); MyGUI::Button* mCheckbutton{nullptr}; MyGUI::Widget* mFill{nullptr}; }; template class EditNumber : public EditBase, public MyGUI::Widget { MYGUI_RTTI_DERIVED(EditNumber) public: void setValue(T value) { mValue = value; if constexpr (std::is_floating_point_v) mValueLabel->setCaption(Misc::StringUtils::format("%.3f", mValue)); else mValueLabel->setCaption(std::to_string(mValue)); float range = 0.f; float min = 0.f; if (auto uniform = mUniform.lock()) { if constexpr (std::is_fundamental_v) { uniform->template setValue(mValue); range = uniform->template getMax() - uniform->template getMin(); min = uniform->template getMin(); } else { UType uvalue = uniform->template getValue(); uvalue[mIndex] = mValue; uniform->template setValue(uvalue); range = uniform->template getMax()[mIndex] - uniform->template getMin()[mIndex]; min = uniform->template getMin()[mIndex]; } } float fill = (range == 0.f) ? 1.f : (mValue - min) / range; mFill->setRealSize(fill, 1.0); } void setValueFromUniform() override { if (auto uniform = mUniform.lock()) { T value; if constexpr (std::is_fundamental_v) value = uniform->template getValue(); else value = uniform->template getValue()[mIndex]; setValue(value); } } void toDefault() override { if (auto uniform = mUniform.lock()) { if constexpr (std::is_fundamental_v) setValue(uniform->template getDefault()); else setValue(uniform->template getDefault()[mIndex]); } } private: void initialiseOverride() override { Base::initialiseOverride(); assignWidget(mDragger, "Dragger"); assignWidget(mValueLabel, "Value"); assignWidget(mButtonIncrease, "ButtonIncrease"); assignWidget(mButtonDecrease, "ButtonDecrease"); assignWidget(mFill, "Fill"); mButtonIncrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); mButtonDecrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); mDragger->eventMouseButtonPressed += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonPressed); mDragger->eventMouseDrag += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonDragged); mDragger->eventMouseWheel += MyGUI::newDelegate(this, &EditNumber::notifyMouseWheel); } void notifyMouseWheel(MyGUI::Widget* sender, int rel) { auto uniform = mUniform.lock(); if (!uniform) return; if (rel > 0) increment(uniform->mStep); else increment(-uniform->mStep); } void notifyMouseButtonDragged(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) { if (id != MyGUI::MouseButton::Left) return; auto uniform = mUniform.lock(); if (!uniform) return; int delta = left - mLastPointerX; // allow finer tuning when shift is pressed constexpr double scaling = 20.0; T step = MyGUI::InputManager::getInstance().isShiftPressed() ? uniform->mStep / scaling : uniform->mStep; if (step == 0) { if constexpr (std::is_integral_v) step = 1; else step = uniform->mStep; } if (delta > 0) increment(step); else if (delta < 0) increment(-step); mLastPointerX = left; } void notifyMouseButtonPressed(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) { if (id != MyGUI::MouseButton::Left) return; mLastPointerX = left; } void increment(T step) { auto uniform = mUniform.lock(); if (!uniform) return; if constexpr (std::is_fundamental_v) setValue(std::clamp(uniform->template getValue() + step, uniform->template getMin(), uniform->template getMax())); else setValue(std::clamp(uniform->template getValue()[mIndex] + step, uniform->template getMin()[mIndex], uniform->template getMax()[mIndex])); } void notifyButtonClicked(MyGUI::Widget* sender) { auto uniform = mUniform.lock(); if (!uniform) return; if (sender == mButtonDecrease) increment(-uniform->mStep); else if (sender == mButtonIncrease) increment(uniform->mStep); } MyGUI::Button* mButtonDecrease{nullptr}; MyGUI::Button* mButtonIncrease{nullptr}; MyGUI::Widget* mDragger{nullptr}; MyGUI::Widget* mFill{nullptr}; MyGUI::TextBox* mValueLabel{nullptr}; T mValue{}; int mLastPointerX{0}; }; class EditNumberFloat4 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat4) }; class EditNumberFloat3 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat3) }; class EditNumberFloat2 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat2) }; class EditNumberFloat : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat) }; class EditNumberInt : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberInt) }; class UniformBase final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(UniformBase) public: void init(const std::shared_ptr& uniform); void toDefault(); void addItem(EditBase* item); Gui::AutoSizedTextBox* getLabel() { return mLabel; } private: void notifyResetClicked(MyGUI::Widget* sender); void initialiseOverride() override; Gui::AutoSizedButton* mReset{nullptr}; Gui::AutoSizedTextBox* mLabel{nullptr}; MyGUI::Widget* mClient{nullptr}; std::vector mBases; }; } } #endif openmw-openmw-0.48.0/components/interpreter/000077500000000000000000000000001445372753700211645ustar00rootroot00000000000000openmw-openmw-0.48.0/components/interpreter/context.hpp000066400000000000000000000061131445372753700233620ustar00rootroot00000000000000#ifndef INTERPRETER_CONTEXT_H_INCLUDED #define INTERPRETER_CONTEXT_H_INCLUDED #include #include #include namespace Interpreter { class Context { public: virtual ~Context() {} virtual std::string getTarget() const = 0; virtual int getLocalShort (int index) const = 0; virtual int getLocalLong (int index) const = 0; virtual float getLocalFloat (int index) const = 0; virtual void setLocalShort (int index, int value) = 0; virtual void setLocalLong (int index, int value) = 0; virtual void setLocalFloat (int index, float value) = 0; virtual void messageBox (const std::string& message, const std::vector& buttons) = 0; void messageBox (const std::string& message) { std::vector empty; messageBox (message, empty); } virtual void report (const std::string& message) = 0; virtual int getGlobalShort(std::string_view name) const = 0; virtual int getGlobalLong(std::string_view name) const = 0; virtual float getGlobalFloat(std::string_view name) const = 0; virtual void setGlobalShort(std::string_view name, int value) = 0; virtual void setGlobalLong(std::string_view name, int value) = 0; virtual void setGlobalFloat(std::string_view name, float value) = 0; virtual std::vector getGlobals () const = 0; virtual char getGlobalType(std::string_view name) const = 0; virtual std::string getActionBinding(std::string_view action) const = 0; virtual std::string getActorName() const = 0; virtual std::string getNPCRace() const = 0; virtual std::string getNPCClass() const = 0; virtual std::string getNPCFaction() const = 0; virtual std::string getNPCRank() const = 0; virtual std::string getPCName() const = 0; virtual std::string getPCRace() const = 0; virtual std::string getPCClass() const = 0; virtual std::string getPCRank() const = 0; virtual std::string getPCNextRank() const = 0; virtual int getPCBounty() const = 0; virtual std::string getCurrentCellName() const = 0; virtual int getMemberShort(std::string_view id, std::string_view name, bool global) const = 0; virtual int getMemberLong(std::string_view id, std::string_view name, bool global) const = 0; virtual float getMemberFloat(std::string_view id, std::string_view name, bool global) const = 0; virtual void setMemberShort(std::string_view id, std::string_view name, int value, bool global) = 0; virtual void setMemberLong(std::string_view id, std::string_view name, int value, bool global) = 0; virtual void setMemberFloat(std::string_view id, std::string_view name, float value, bool global) = 0; }; } #endif openmw-openmw-0.48.0/components/interpreter/controlopcodes.hpp000066400000000000000000000032051445372753700247320ustar00rootroot00000000000000#ifndef INTERPRETER_CONTROLOPCODES_H_INCLUDED #define INTERPRETER_CONTROLOPCODES_H_INCLUDED #include #include "opcodes.hpp" #include "runtime.hpp" namespace Interpreter { class OpReturn : public Opcode0 { public: void execute (Runtime& runtime) override { runtime.setPC (-1); } }; class OpSkipZero : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; runtime.pop(); if (data==0) runtime.setPC (runtime.getPC()+1); } }; class OpSkipNonZero : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; runtime.pop(); if (data!=0) runtime.setPC (runtime.getPC()+1); } }; class OpJumpForward : public Opcode1 { public: void execute (Runtime& runtime, unsigned int arg0) override { if (arg0==0) throw std::logic_error ("infinite loop"); runtime.setPC (runtime.getPC()+arg0-1); } }; class OpJumpBackward : public Opcode1 { public: void execute (Runtime& runtime, unsigned int arg0) override { if (arg0==0) throw std::logic_error ("infinite loop"); runtime.setPC (runtime.getPC()-arg0-1); } }; } #endif openmw-openmw-0.48.0/components/interpreter/defines.cpp000066400000000000000000000241561445372753700233150ustar00rootroot00000000000000#include "defines.hpp" #include #include #include #include #include namespace Interpreter{ bool check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start) { bool retval = str.find(escword) == 0; if(retval){ (*i) += escword.length(); (*start) = (*i) + 1; } return retval; } std::vector globals; bool longerStr(const std::string& a, const std::string& b) { return a.length() > b.length(); } static std::string fixDefinesReal(const std::string& text, bool dialogue, Context& context) { unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++) { char eschar = text[i]; if(eschar == '%' || eschar == '^') { retval << text.substr(start, i - start); std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); bool found = false; try { if( (found = check(temp, "actionslideright", &i, &start))){ retval << context.getActionBinding("#{sRight}"); } else if((found = check(temp, "actionreadymagic", &i, &start))){ retval << context.getActionBinding("#{sReady_Magic}"); } else if((found = check(temp, "actionprevweapon", &i, &start))){ retval << context.getActionBinding("#{sPrevWeapon}"); } else if((found = check(temp, "actionnextweapon", &i, &start))){ retval << context.getActionBinding("#{sNextWeapon}"); } else if((found = check(temp, "actiontogglerun", &i, &start))){ retval << context.getActionBinding("#{sAuto_Run}"); } else if((found = check(temp, "actionslideleft", &i, &start))){ retval << context.getActionBinding("#{sLeft}"); } else if((found = check(temp, "actionreadyitem", &i, &start))){ retval << context.getActionBinding("#{sReady_Weapon}"); } else if((found = check(temp, "actionprevspell", &i, &start))){ retval << context.getActionBinding("#{sPrevSpell}"); } else if((found = check(temp, "actionnextspell", &i, &start))){ retval << context.getActionBinding("#{sNextSpell}"); } else if((found = check(temp, "actionrestmenu", &i, &start))){ retval << context.getActionBinding("#{sRestKey}"); } else if((found = check(temp, "actionmenumode", &i, &start))){ retval << context.getActionBinding("#{sInventory}"); } else if((found = check(temp, "actionactivate", &i, &start))){ retval << context.getActionBinding("#{sActivate}"); } else if((found = check(temp, "actionjournal", &i, &start))){ retval << context.getActionBinding("#{sJournal}"); } else if((found = check(temp, "actionforward", &i, &start))){ retval << context.getActionBinding("#{sForward}"); } else if((found = check(temp, "pccrimelevel", &i, &start))){ retval << context.getPCBounty(); } else if((found = check(temp, "actioncrouch", &i, &start))){ retval << context.getActionBinding("#{sCrouch_Sneak}"); } else if((found = check(temp, "actionjump", &i, &start))){ retval << context.getActionBinding("#{sJump}"); } else if((found = check(temp, "actionback", &i, &start))){ retval << context.getActionBinding("#{sBack}"); } else if((found = check(temp, "actionuse", &i, &start))){ retval << context.getActionBinding("#{sUse}"); } else if((found = check(temp, "actionrun", &i, &start))){ retval << context.getActionBinding("#{sRun}"); } else if((found = check(temp, "pcclass", &i, &start))){ retval << context.getPCClass(); } else if((found = check(temp, "pcrace", &i, &start))){ retval << context.getPCRace(); } else if((found = check(temp, "pcname", &i, &start))){ retval << context.getPCName(); } else if((found = check(temp, "cell", &i, &start))){ retval << context.getCurrentCellName(); } else if(dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } else if((found = check(temp, "nextpcrank", &i, &start))){ retval << context.getPCNextRank(); } else if((found = check(temp, "pcnextrank", &i, &start))){ retval << context.getPCNextRank(); } else if((found = check(temp, "pcrank", &i, &start))){ retval << context.getPCRank(); } else if((found = check(temp, "rank", &i, &start))){ retval << context.getNPCRank(); } else if((found = check(temp, "class", &i, &start))){ retval << context.getNPCClass(); } else if((found = check(temp, "race", &i, &start))){ retval << context.getNPCRace(); } else if((found = check(temp, "name", &i, &start))){ retval << context.getActorName(); } } else { // In messagebox or book, not dialogue /* empty outside dialogue */ if( (found = check(temp, "faction", &i, &start))); else if((found = check(temp, "nextpcrank", &i, &start))); else if((found = check(temp, "pcnextrank", &i, &start))); else if((found = check(temp, "pcrank", &i, &start))); else if((found = check(temp, "rank", &i, &start))); /* uses pc in messageboxes */ else if((found = check(temp, "class", &i, &start))){ retval << context.getPCClass(); } else if((found = check(temp, "race", &i, &start))){ retval << context.getPCRace(); } else if((found = check(temp, "name", &i, &start))){ retval << context.getPCName(); } } /* Not a builtin, try global variables */ if(!found){ /* if list of globals is empty, grab it and sort it by descending string length */ if(globals.empty()){ globals = context.getGlobals(); sort(globals.begin(), globals.end(), longerStr); } for(unsigned int j = 0; j < globals.size(); j++){ if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name temp = Misc::StringUtils::lowerCase(text.substr(i+1, globals[j].length())); } found = check(temp, globals[j], &i, &start); if(found){ char type = context.getGlobalType(globals[j]); switch(type){ case 's': retval << context.getGlobalShort(globals[j]); break; case 'l': retval << context.getGlobalLong(globals[j]); break; case 'f': retval << context.getGlobalFloat(globals[j]); break; } break; } } } } catch (std::exception& e) { Log(Debug::Error) << "Error: Failed to replace escape character, with the following error: " << e.what(); Log(Debug::Error) << "Full text below:\n" << text; } // Not found, or error if(!found){ /* leave unmodified */ i += 1; start = i; retval << eschar; } } } retval << text.substr(start, text.length() - start); return retval.str (); } std::string fixDefinesDialog(const std::string& text, Context& context){ return fixDefinesReal(text, true, context); } std::string fixDefinesMsgBox(const std::string& text, Context& context){ return fixDefinesReal(text, false, context); } std::string fixDefinesBook(const std::string& text, Context& context){ return fixDefinesReal(text, false, context); } } openmw-openmw-0.48.0/components/interpreter/defines.hpp000066400000000000000000000005771445372753700233230ustar00rootroot00000000000000#ifndef INTERPRETER_DEFINES_H_INCLUDED #define INTERPRETER_DEFINES_H_INCLUDED #include #include "context.hpp" namespace Interpreter{ std::string fixDefinesDialog(const std::string& text, Context& context); std::string fixDefinesMsgBox(const std::string& text, Context& context); std::string fixDefinesBook(const std::string& text, Context& context); } #endif openmw-openmw-0.48.0/components/interpreter/docs/000077500000000000000000000000001445372753700221145ustar00rootroot00000000000000openmw-openmw-0.48.0/components/interpreter/docs/vmformat.txt000066400000000000000000000135171445372753700245170ustar00rootroot00000000000000Note: a word is considered to be 32 bit long. Header (4 words): word: number of words in code block word: number of words in integer literal block word: number of words in float literal block word: number of words in string literal block Body (variable length): code block integer literal block (contains a collection of 1 word long integers) float literal block (contains a collection of 1 word long floating point numbers) string literal block (contains a collection of strings of variable length, word-padded) Code bit-patterns: 3322222222221111111111 10987654321098765432109876543210 00ccccccAAAAAAAAAAAAAAAAAAAAAAAA segment 0: 64 opcodes, 1 24-bit argument 01ccccccAAAAAAAAAAAABBBBBBBBBBBB segment 1: 64 opcodes, 2 12-bit arguments 10ccccccccccAAAAAAAAAAAAAAAAAAAA segment 2: 1024 opcodes, 1 20-bit argument 110000ccccccccccccccccccAAAAAAAA segment 3: 262144 opcodes, 1 8-bit argument 110001ccccccccccAAAAAAAABBBBBBBB segment 4: 1024 opcodes, 2 8-bit arguments 110010cccccccccccccccccccccccccc segment 5: 67108864 opcodes, no arguments other bit-patterns reserved legent: c: code A: argument 0 B: argument 1 Segment 0: op 0: push arg0 op 1: move pc ahead by arg0 op 2: move pc back by arg0 opcodes 3-31 unused opcodes 32-63 reserved for extensions Segment 1: opcodes 0-31 unused opcodes 32-63 reserved for extensions Segment 2: opcodes 0-511 unused opcodes 512-1023 reserved for extensions Segment 3: op 0: show message box with message string literal index in stack[0]; buttons (if any) in stack[arg0]..stack[1]; additional arguments (if any) in stack[arg0+n]..stack[arg0+1]; n is determined according to the message string all arguments are removed from stack opcodes 1-131071 unused opcodes 131072-262143 reserved for extensions Segment 4: opcodes 0-511 unused opcodes 512-1023 reserved for extensions Segment 5: op 0: store stack[0] in local short stack[1] and pop twice op 1: store stack[0] in local long stack[1] and pop twice op 2: store stack[0] in local float stack[1] and pop twice op 3: convert stack[0] from integer to float op 4: replace stack[0] with integer literal index stack[0] op 5: replace stack[0] with float literal index stack[0] op 6: convert stack[0] from float to integer op 7: invert sign of int value stack[0] op 8: invert sign of float value stack[0] op 9: add (integer) stack[0] to stack[1], pop twice, push result op 10: add (float) stack[0] to stack[1], pop twice, push result op 11: sub (integer) stack[1] from stack[0], pop twice, push result op 12: sub (float) stack[1] from stack[0], pop twice, push result op 13: mul (integer) stack[0] with stack[1], pop twice, push result op 14: mul (float) stack[0] with stack[1], pop twice, push result op 15: div (integer) stack[1] by stack[0], pop twice, push result op 16: div (float) stack[1] by stack[0], pop twice, push result op 17: convert stack[1] from integer to float op 18: convert stack[1] from float to integer opcode 19 unused op 20: return op 21: replace stack[0] with local short stack[0] op 22: replace stack[0] with local long stack[0] op 23: replace stack[0] with local float stack[0] op 24: skip next instruction if stack[0]==0; pop op 25: skip next instruction if stack[0]!=0; pop op 26: compare (intger) stack[1] with stack[0]; pop twice; push 1 if equal, 0 else op 27: compare (intger) stack[1] with stack[0]; pop twice; push 1 if no equal, 0 else op 28: compare (intger) stack[1] with stack[0]; pop twice; push 1 if lesser than, 0 else op 29: compare (intger) stack[1] with stack[0]; pop twice; push 1 if lesser or equal, 0 else op 30: compare (intger) stack[1] with stack[0]; pop twice; push 1 if greater than, 0 else op 31: compare (intger) stack[1] with stack[0]; pop twice; push 1 if greater or equal, 0 else op 32: compare (float) stack[1] with stack[0]; pop twice; push 1 if equal, 0 else op 33: compare (float) stack[1] with stack[0]; pop twice; push 1 if no equal, 0 else op 34: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser than, 0 else op 35: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser or equal, 0 else op 36: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater than, 0 else op 37: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater or equal, 0 else opcode 38 unused op 39: store stack[0] in global short stack[1] and pop twice op 40: store stack[0] in global long stack[1] and pop twice op 41: store stack[0] in global float stack[1] and pop twice op 42: replace stack[0] with global short stack[0] op 43: replace stack[0] with global long stack[0] op 44: replace stack[0] with global float stack[0] opcodes 45-57 unused op 58: report string literal index in stack[0]; additional arguments (if any) in stack[n]..stack[1]; n is determined according to the message string all arguments are removed from stack op 59: store stack[0] in member short stack[2] of object with ID stack[1] op 60: store stack[0] in member long stack[2] of object with ID stack[1] op 61: store stack[0] in member float stack[2] of object with ID stack[1] op 62: replace stack[0] with member short stack[1] of object with ID stack[0] op 63: replace stack[0] with member short stack[1] of object with ID stack[0] op 64: replace stack[0] with member short stack[1] of object with ID stack[0] op 65: store stack[0] in member short stack[2] of global script with ID stack[1] op 66: store stack[0] in member long stack[2] of global script with ID stack[1] op 67: store stack[0] in member float stack[2] of global script with ID stack[1] op 68: replace stack[0] with member short stack[1] of global script with ID stack[0] op 69: replace stack[0] with member short stack[1] of global script with ID stack[0] op 70: replace stack[0] with member short stack[1] of global script with ID stack[0] opcodes 71-33554431 unused opcodes 33554432-67108863 reserved for extensions openmw-openmw-0.48.0/components/interpreter/genericopcodes.hpp000066400000000000000000000046471445372753700247010ustar00rootroot00000000000000#ifndef INTERPRETER_GENERICOPCODES_H_INCLUDED #define INTERPRETER_GENERICOPCODES_H_INCLUDED #include "opcodes.hpp" #include "runtime.hpp" namespace Interpreter { class OpPushInt : public Opcode1 { public: void execute (Runtime& runtime, unsigned int arg0) override { runtime.push (static_cast (arg0)); } }; class OpIntToFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; Type_Float floatValue = static_cast (data); runtime[0].mFloat = floatValue; } }; class OpFloatToInt : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; Type_Integer integerValue = static_cast (data); runtime[0].mInteger = integerValue; } }; class OpNegateInt : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; data = -data; runtime[0].mInteger = data; } }; class OpNegateFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; data = -data; runtime[0].mFloat = data; } }; class OpIntToFloat1 : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[1].mInteger; Type_Float floatValue = static_cast (data); runtime[1].mFloat = floatValue; } }; class OpFloatToInt1 : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[1].mFloat; Type_Integer integerValue = static_cast (data); runtime[1].mInteger = integerValue; } }; } #endif openmw-openmw-0.48.0/components/interpreter/installopcodes.cpp000066400000000000000000000106311445372753700247140ustar00rootroot00000000000000#include "installopcodes.hpp" #include #include "interpreter.hpp" #include "genericopcodes.hpp" #include "localopcodes.hpp" #include "mathopcodes.hpp" #include "controlopcodes.hpp" #include "miscopcodes.hpp" namespace Interpreter { void installOpcodes(Interpreter& interpreter) { // generic interpreter.installSegment0(0); interpreter.installSegment5(3); interpreter.installSegment5(6); interpreter.installSegment5(7); interpreter.installSegment5(8); interpreter.installSegment5(17); interpreter.installSegment5(18); // local variables, global variables & literals interpreter.installSegment5(0); interpreter.installSegment5(1); interpreter.installSegment5(2); interpreter.installSegment5(4); interpreter.installSegment5(5); interpreter.installSegment5(21); interpreter.installSegment5(22); interpreter.installSegment5(23); interpreter.installSegment5(39); interpreter.installSegment5(40); interpreter.installSegment5(41); interpreter.installSegment5(42); interpreter.installSegment5(43); interpreter.installSegment5(44); interpreter.installSegment5>(59); interpreter.installSegment5>(60); interpreter.installSegment5>(61); interpreter.installSegment5>(62); interpreter.installSegment5>(63); interpreter.installSegment5>(64); interpreter.installSegment5>(65); interpreter.installSegment5>(66); interpreter.installSegment5>(67); interpreter.installSegment5>(68); interpreter.installSegment5>(69); interpreter.installSegment5>(70); // math interpreter.installSegment5>(9); interpreter.installSegment5>(10); interpreter.installSegment5>(11); interpreter.installSegment5>(12); interpreter.installSegment5>(13); interpreter.installSegment5>(14); interpreter.installSegment5>(15); interpreter.installSegment5>(16); interpreter.installSegment5 >>(26); interpreter.installSegment5 >>(27); interpreter.installSegment5 >>(28); interpreter.installSegment5 >>(29); interpreter.installSegment5 >>(30); interpreter.installSegment5 >>(31); interpreter.installSegment5 >>(32); interpreter.installSegment5 >>(33); interpreter.installSegment5 >>(34); interpreter.installSegment5 >>(35); interpreter.installSegment5 >>(36); interpreter.installSegment5 >>(37); // control structures interpreter.installSegment5(20); interpreter.installSegment5(24); interpreter.installSegment5(25); interpreter.installSegment0(1); interpreter.installSegment0(2); // misc interpreter.installSegment3(0); interpreter.installSegment5(58); } } openmw-openmw-0.48.0/components/interpreter/installopcodes.hpp000066400000000000000000000003171445372753700247210ustar00rootroot00000000000000#ifndef INTERPRETER_INSTALLOPCODES_H_INCLUDED #define INTERPRETER_INSTALLOPCODES_H_INCLUDED namespace Interpreter { class Interpreter; void installOpcodes (Interpreter& interpreter); } #endif openmw-openmw-0.48.0/components/interpreter/interpreter.cpp000066400000000000000000000063271445372753700242430ustar00rootroot00000000000000#include "interpreter.hpp" #include #include #include #include "opcodes.hpp" namespace Interpreter { [[noreturn]] static void abortUnknownCode(int segment, int opcode) { const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment); throw std::runtime_error(error); } [[noreturn]] static void abortUnknownSegment(Type_Code code) { const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code); throw std::runtime_error(error); } template auto& getDispatcher(const T& segment, unsigned int seg, int opcode) { auto it = segment.find(opcode); if (it == segment.end()) { abortUnknownCode(seg, opcode); } return it->second; } void Interpreter::execute (Type_Code code) { unsigned int segSpec = code >> 30; switch (segSpec) { case 0: { const int opcode = code >> 24; const unsigned int arg0 = code & 0xffffff; return getDispatcher(mSegment0, 0, opcode)->execute(mRuntime, arg0); } case 2: { const int opcode = (code >> 20) & 0x3ff; const unsigned int arg0 = code & 0xfffff; return getDispatcher(mSegment2, 2, opcode)->execute(mRuntime, arg0); } } segSpec = code >> 26; switch (segSpec) { case 0x30: { const int opcode = (code >> 8) & 0x3ffff; const unsigned int arg0 = code & 0xff; return getDispatcher(mSegment3, 3, opcode)->execute(mRuntime, arg0); } case 0x32: { const int opcode = code & 0x3ffffff; return getDispatcher(mSegment5, 5, opcode)->execute(mRuntime); } } abortUnknownSegment (code); } void Interpreter::begin() { if (mRunning) { mCallstack.push (mRuntime); mRuntime.clear(); } else { mRunning = true; } } void Interpreter::end() { if (mCallstack.empty()) { mRuntime.clear(); mRunning = false; } else { mRuntime = mCallstack.top(); mCallstack.pop(); } } Interpreter::Interpreter() : mRunning (false) {} void Interpreter::run (const Type_Code *code, int codeSize, Context& context) { assert (codeSize>=4); begin(); try { mRuntime.configure (code, codeSize, context); int opcodes = static_cast (code[0]); const Type_Code *codeBlock = code + 4; while (mRuntime.getPC()>=0 && mRuntime.getPC() #include #include #include #include #include "runtime.hpp" #include "types.hpp" #include "opcodes.hpp" namespace Interpreter { class Interpreter { std::stack mCallstack; bool mRunning; Runtime mRuntime; std::map> mSegment0; std::map> mSegment2; std::map> mSegment3; std::map> mSegment5; // not implemented Interpreter (const Interpreter&); Interpreter& operator= (const Interpreter&); void execute (Type_Code code); void begin(); void end(); template void installSegment(TSeg& seg, int code, TOp&& op) { assert(seg.find(code) == seg.end()); seg.emplace(code, std::move(op)); } public: Interpreter(); template void installSegment0(int code, TArgs&& ...args) { installSegment(mSegment0, code, std::make_unique(std::forward(args)...)); } template void installSegment2(int code, TArgs&& ...args) { installSegment(mSegment2, code, std::make_unique(std::forward(args)...)); } template void installSegment3(int code, TArgs&& ...args) { installSegment(mSegment3, code, std::make_unique(std::forward(args)...)); } template void installSegment5(int code, TArgs&& ...args) { installSegment(mSegment5, code, std::make_unique(std::forward(args)...)); } void run (const Type_Code *code, int codeSize, Context& context); }; } #endif openmw-openmw-0.48.0/components/interpreter/localopcodes.hpp000066400000000000000000000222171445372753700243500ustar00rootroot00000000000000#ifndef INTERPRETER_LOCALOPCODES_H_INCLUDED #define INTERPRETER_LOCALOPCODES_H_INCLUDED #include "opcodes.hpp" #include "runtime.hpp" #include "context.hpp" namespace Interpreter { class OpStoreLocalShort : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; int index = runtime[1].mInteger; runtime.getContext().setLocalShort (index, data); runtime.pop(); runtime.pop(); } }; class OpStoreLocalLong : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; int index = runtime[1].mInteger; runtime.getContext().setLocalLong (index, data); runtime.pop(); runtime.pop(); } }; class OpStoreLocalFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; int index = runtime[1].mInteger; runtime.getContext().setLocalFloat (index, data); runtime.pop(); runtime.pop(); } }; class OpFetchIntLiteral : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer intValue = runtime.getIntegerLiteral (runtime[0].mInteger); runtime[0].mInteger = intValue; } }; class OpFetchFloatLiteral : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float floatValue = runtime.getFloatLiteral (runtime[0].mInteger); runtime[0].mFloat = floatValue; } }; class OpFetchLocalShort : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; int value = runtime.getContext().getLocalShort (index); runtime[0].mInteger = value; } }; class OpFetchLocalLong : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; int value = runtime.getContext().getLocalLong (index); runtime[0].mInteger = value; } }; class OpFetchLocalFloat : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; float value = runtime.getContext().getLocalFloat (index); runtime[0].mFloat = value; } }; class OpStoreGlobalShort : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; int index = runtime[1].mInteger; std::string_view name = runtime.getStringLiteral (index); runtime.getContext().setGlobalShort (name, data); runtime.pop(); runtime.pop(); } }; class OpStoreGlobalLong : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; int index = runtime[1].mInteger; std::string_view name = runtime.getStringLiteral (index); runtime.getContext().setGlobalLong (name, data); runtime.pop(); runtime.pop(); } }; class OpStoreGlobalFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; int index = runtime[1].mInteger; std::string_view name = runtime.getStringLiteral (index); runtime.getContext().setGlobalFloat (name, data); runtime.pop(); runtime.pop(); } }; class OpFetchGlobalShort : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; std::string_view name = runtime.getStringLiteral (index); Type_Integer value = runtime.getContext().getGlobalShort (name); runtime[0].mInteger = value; } }; class OpFetchGlobalLong : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; std::string_view name = runtime.getStringLiteral (index); Type_Integer value = runtime.getContext().getGlobalLong (name); runtime[0].mInteger = value; } }; class OpFetchGlobalFloat : public Opcode0 { public: void execute (Runtime& runtime) override { int index = runtime[0].mInteger; std::string_view name = runtime.getStringLiteral (index); Type_Float value = runtime.getContext().getGlobalFloat (name); runtime[0].mFloat = value; } }; template class OpStoreMemberShort : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; Type_Integer index = runtime[1].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.getContext().setMemberShort (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; template class OpStoreMemberLong : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; Type_Integer index = runtime[1].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.getContext().setMemberLong (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; template class OpStoreMemberFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; Type_Integer index = runtime[1].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[2].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.getContext().setMemberFloat (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); runtime.pop(); } }; template class OpFetchMemberShort : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.pop(); int value = runtime.getContext().getMemberShort (id, variable, TGlobal); runtime[0].mInteger = value; } }; template class OpFetchMemberLong : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.pop(); int value = runtime.getContext().getMemberLong (id, variable, TGlobal); runtime[0].mInteger = value; } }; template class OpFetchMemberFloat : public Opcode0 { public: void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; std::string_view id = runtime.getStringLiteral (index); index = runtime[1].mInteger; std::string_view variable = runtime.getStringLiteral (index); runtime.pop(); float value = runtime.getContext().getMemberFloat (id, variable, TGlobal); runtime[0].mFloat = value; } }; } #endif openmw-openmw-0.48.0/components/interpreter/mathopcodes.hpp000066400000000000000000000043211445372753700242030ustar00rootroot00000000000000#ifndef INTERPRETER_MATHOPCODES_H_INCLUDED #define INTERPRETER_MATHOPCODES_H_INCLUDED #include #include #include "opcodes.hpp" #include "runtime.hpp" namespace Interpreter { template class OpAddInt : public Opcode0 { public: void execute (Runtime& runtime) override { T result = getData (runtime[1]) + getData (runtime[0]); runtime.pop(); getData (runtime[0]) = result; } }; template class OpSubInt : public Opcode0 { public: void execute (Runtime& runtime) override { T result = getData (runtime[1]) - getData (runtime[0]); runtime.pop(); getData (runtime[0]) = result; } }; template class OpMulInt : public Opcode0 { public: void execute (Runtime& runtime) override { T result = getData (runtime[1]) * getData (runtime[0]); runtime.pop(); getData (runtime[0]) = result; } }; template class OpDivInt : public Opcode0 { public: void execute (Runtime& runtime) override { T left = getData (runtime[0]); if (left==0) throw std::runtime_error ("division by zero"); T result = getData (runtime[1]) / left; runtime.pop(); getData (runtime[0]) = result; } }; template class OpCompare : public Opcode0 { public: void execute (Runtime& runtime) override { int result = C() (getData (runtime[1]), getData (runtime[0])); runtime.pop(); runtime[0].mInteger = result; } }; } #endif openmw-openmw-0.48.0/components/interpreter/miscopcodes.hpp000066400000000000000000000125421445372753700242110ustar00rootroot00000000000000#ifndef INTERPRETER_MISCOPCODES_H_INCLUDED #define INTERPRETER_MISCOPCODES_H_INCLUDED #include #include #include #include #include #include "opcodes.hpp" #include "runtime.hpp" #include "defines.hpp" #include namespace Interpreter { class RuntimeMessageFormatter : public Misc::MessageFormatParser { private: std::string mFormattedMessage; Runtime& mRuntime; protected: void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) override { std::ostringstream out; out.fill(padding); if (width != -1) out.width(width); if (precision != -1) out.precision(precision); switch (placeholder) { case StringPlaceholder: { int index = mRuntime[0].mInteger; mRuntime.pop(); out << mRuntime.getStringLiteral(index); mFormattedMessage += out.str(); } break; case IntegerPlaceholder: { Type_Integer value = mRuntime[0].mInteger; mRuntime.pop(); out << value; mFormattedMessage += out.str(); } break; case FloatPlaceholder: { float value = mRuntime[0].mFloat; mRuntime.pop(); if (notation == FixedNotation) { out << std::fixed << value; mFormattedMessage += out.str(); } else if (notation == ShortestNotation) { out << value; std::string standard = out.str(); out.str(std::string()); out.clear(); out << std::scientific << value; std::string scientific = out.str(); mFormattedMessage += standard.length() < scientific.length() ? standard : scientific; } else { out << std::scientific << value; mFormattedMessage += out.str(); } } break; default: break; } } void visitedCharacter(char c) override { mFormattedMessage += c; } public: RuntimeMessageFormatter(Runtime& runtime) : mRuntime(runtime) { } void process(std::string_view message) override { mFormattedMessage.clear(); MessageFormatParser::process(message); } std::string getFormattedMessage() const { return mFormattedMessage; } }; inline std::string formatMessage (std::string_view message, Runtime& runtime) { RuntimeMessageFormatter formatter(runtime); formatter.process(message); std::string formattedMessage = formatter.getFormattedMessage(); formattedMessage = fixDefinesMsgBox(formattedMessage, runtime.getContext()); return formattedMessage; } class OpMessageBox : public Opcode1 { public: void execute (Runtime& runtime, unsigned int arg0) override { // message int index = runtime[0].mInteger; runtime.pop(); std::string_view message = runtime.getStringLiteral (index); // buttons std::vector buttons; for (std::size_t i=0; i #include #include namespace Interpreter { Runtime::Runtime() : mContext (nullptr), mCode (nullptr), mCodeSize(0), mPC (0) {} int Runtime::getPC() const { return mPC; } int Runtime::getIntegerLiteral (int index) const { if (index < 0 || index >= static_cast (mCode[1])) throw std::out_of_range("out of range"); const Type_Code *literalBlock = mCode + 4 + mCode[0]; return *reinterpret_cast (&literalBlock[index]); } float Runtime::getFloatLiteral (int index) const { if (index < 0 || index >= static_cast (mCode[2])) throw std::out_of_range("out of range"); const Type_Code *literalBlock = mCode + 4 + mCode[0] + mCode[1]; return *reinterpret_cast (&literalBlock[index]); } std::string_view Runtime::getStringLiteral(int index) const { if (index < 0 || static_cast (mCode[3]) <= 0) throw std::out_of_range("out of range"); const char *literalBlock = reinterpret_cast (mCode + 4 + mCode[0] + mCode[1] + mCode[2]); size_t offset = 0; for (; index; --index) { offset += std::strlen(literalBlock + offset) + 1; if (offset / 4 >= mCode[3]) throw std::out_of_range("out of range"); } return literalBlock+offset; } void Runtime::configure (const Type_Code *code, int codeSize, Context& context) { clear(); mContext = &context; mCode = code; mCodeSize = codeSize; mPC = 0; } void Runtime::clear() { mContext = nullptr; mCode = nullptr; mCodeSize = 0; mStack.clear(); } void Runtime::setPC (int PC) { mPC = PC; } void Runtime::push (const Data& data) { mStack.push_back (data); } void Runtime::push (Type_Integer value) { Data data; data.mInteger = value; push (data); } void Runtime::push (Type_Float value) { Data data; data.mFloat = value; push (data); } void Runtime::pop() { if (mStack.empty()) throw std::runtime_error ("stack underflow"); mStack.pop_back(); } Data& Runtime::operator[] (int Index) { if (Index<0 || Index>=static_cast (mStack.size())) throw std::runtime_error ("stack index out of range"); return mStack[mStack.size()-Index-1]; } Context& Runtime::getContext() { assert (mContext); return *mContext; } } openmw-openmw-0.48.0/components/interpreter/runtime.hpp000066400000000000000000000027531445372753700233670ustar00rootroot00000000000000#ifndef INTERPRETER_RUNTIME_H_INCLUDED #define INTERPRETER_RUNTIME_H_INCLUDED #include #include #include "types.hpp" namespace Interpreter { class Context; /// Runtime data and engine interface class Runtime { Context *mContext; const Type_Code *mCode; int mCodeSize; int mPC; std::vector mStack; public: Runtime (); int getPC() const; ///< return program counter. int getIntegerLiteral (int index) const; float getFloatLiteral (int index) const; std::string_view getStringLiteral(int index) const; void configure (const Type_Code *code, int codeSize, Context& context); ///< \a context and \a code must exist as least until either configure, clear or /// the destructor is called. \a codeSize is given in 32-bit words. void clear(); void setPC (int PC); ///< set program counter. void push (const Data& data); ///< push data on stack void push (Type_Integer value); ///< push integer data on stack. void push (Type_Float value); ///< push float data on stack. void pop(); ///< pop stack Data& operator[] (int Index); ///< Access stack member, counted from the top. Context& getContext(); }; } #endif openmw-openmw-0.48.0/components/interpreter/types.hpp000066400000000000000000000014351445372753700230440ustar00rootroot00000000000000#ifndef INTERPRETER_TYPES_H_INCLUDED #define INTERPRETER_TYPES_H_INCLUDED #include namespace Interpreter { typedef unsigned int Type_Code; // 32 bit typedef unsigned int Type_Data; // 32 bit typedef short Type_Short; // 16 bit typedef int Type_Integer; // 32 bit typedef float Type_Float; // 32 bit union Data { Type_Integer mInteger; Type_Float mFloat; }; template T& getData (Data& data) { throw std::runtime_error ("unsupported data type"); } template<> inline Type_Integer& getData (Data& data) { return data.mInteger; } template<> inline Type_Float& getData (Data& data) { return data.mFloat; } } #endif openmw-openmw-0.48.0/components/l10n/000077500000000000000000000000001445372753700173735ustar00rootroot00000000000000openmw-openmw-0.48.0/components/l10n/messagebundles.cpp000066400000000000000000000146401445372753700231050ustar00rootroot00000000000000#include "messagebundles.hpp" #include #include #include #include #include namespace l10n { MessageBundles::MessageBundles(const std::vector &preferredLocales, icu::Locale &fallbackLocale) : mFallbackLocale(fallbackLocale) { setPreferredLocales(preferredLocales); } void MessageBundles::setPreferredLocales(const std::vector &preferredLocales) { mPreferredLocales.clear(); mPreferredLocaleStrings.clear(); for (const icu::Locale &loc: preferredLocales) { mPreferredLocales.push_back(loc); mPreferredLocaleStrings.emplace_back(loc.getName()); // Try without variant or country if they are specified, starting with the most specific if (strcmp(loc.getVariant(), "") != 0) { icu::Locale withoutVariant(loc.getLanguage(), loc.getCountry()); mPreferredLocales.push_back(withoutVariant); mPreferredLocaleStrings.emplace_back(withoutVariant.getName()); } if (strcmp(loc.getCountry(), "") != 0) { icu::Locale withoutCountry(loc.getLanguage()); mPreferredLocales.push_back(withoutCountry); mPreferredLocaleStrings.emplace_back(withoutCountry.getName()); } } } std::string getErrorText(const UParseError &parseError) { icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); std::string parseErrorString; preContext.toUTF8String(parseErrorString); postContext.toUTF8String(parseErrorString); return parseErrorString; } static bool checkSuccess(const icu::ErrorCode &status, const std::string &message, const UParseError parseError = UParseError()) { if (status.isFailure()) { std::string errorText = getErrorText(parseError); if (!errorText.empty()) { Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; } else { Log(Debug::Error) << message << ": " << status.errorName(); } } return status.isSuccess(); } void MessageBundles::load(std::istream &input, const icu::Locale& lang, const std::string &path) { try { YAML::Node data = YAML::Load(input); std::string localeName = lang.getName(); for (const auto& it: data) { const auto key = it.first.as(); const auto value = it.second.as(); icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), value.size())); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, lang, parseError, status); if (checkSuccess(status, std::string("Failed to create message ") + key + " for locale " + lang.getName(), parseError)) { mBundles[localeName].insert(std::make_pair(key, message)); } } } catch (std::exception& e) { Log(Debug::Error) << "Can not load " << path << ": " << e.what(); } } const icu::MessageFormat * MessageBundles::findMessage(std::string_view key, const std::string &localeName) const { auto iter = mBundles.find(localeName); if (iter != mBundles.end()) { auto message = iter->second.find(key.data()); if (message != iter->second.end()) { return &(message->second); } } return nullptr; } std::string MessageBundles::formatMessage(std::string_view key, const std::map &args) const { std::vector argNames; std::vector argValues; for (auto& [k, v] : args) { argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), k.size()))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); } std::string MessageBundles::formatMessage(std::string_view key, const std::vector &argNames, const std::vector &args) const { icu::UnicodeString result; std::string resultString; icu::ErrorCode success; const icu::MessageFormat *message = nullptr; for (auto &loc: mPreferredLocaleStrings) { message = findMessage(key, loc); if (message) break; } // If no requested locales included the message, try the fallback locale if (!message) message = findMessage(key, mFallbackLocale.getName()); if (message) { if (!args.empty() && !argNames.empty()) message->format(&argNames[0], &args[0], args.size(), result, success); else message->format(nullptr, nullptr, args.size(), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; } icu::Locale defaultLocale(nullptr); if (!mPreferredLocales.empty()) { defaultLocale = mPreferredLocales[0]; } UParseError parseError; icu::MessageFormat defaultMessage(icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), key.size())), defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) defaultMessage.format(&argNames[0], &args[0], args.size(), result, success); else defaultMessage.format(nullptr, nullptr, args.size(), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; } } openmw-openmw-0.48.0/components/l10n/messagebundles.hpp000066400000000000000000000051751445372753700231150ustar00rootroot00000000000000#ifndef COMPONENTS_L10N_MESSAGEBUNDLES_H #define COMPONENTS_L10N_MESSAGEBUNDLES_H #include #include #include #include #include #include namespace l10n { /** * @brief A collection of Message Bundles * * Class handling localised message storage and lookup, including fallback locales when messages are missing. * * If no fallback locale is provided (or a message fails to be found), the key will be formatted instead, * or returned verbatim if formatting fails. * */ class MessageBundles { public: /* @brief Constructs an empty MessageBundles * * @param preferredLocales user-requested locales, in order of priority * Each locale will be checked when looking up messages, in case some resource files are incomplete. * For each locale which contains a country code or a variant, the locales obtained by removing first * the variant, then the country code, will also be checked before moving on to the next locale in the list. * @param fallbackLocale the fallback locale which should be used if messages cannot be found for the user * preferred locales */ MessageBundles(const std::vector &preferredLocales, icu::Locale &fallbackLocale); std::string formatMessage(std::string_view key, const std::map &args) const; std::string formatMessage(std::string_view key, const std::vector &argNames, const std::vector &args) const; void setPreferredLocales(const std::vector &preferredLocales); const std::vector & getPreferredLocales() const { return mPreferredLocales; } void load(std::istream &input, const icu::Locale &lang, const std::string &path); bool isLoaded(const icu::Locale& loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } const icu::Locale & getFallbackLocale() const { return mFallbackLocale; } private: // icu::Locale isn't hashable (or comparable), so we use the string form instead, which is canonicalized std::unordered_map> mBundles; const icu::Locale mFallbackLocale; std::vector mPreferredLocaleStrings; std::vector mPreferredLocales; const icu::MessageFormat * findMessage(std::string_view key, const std::string &localeName) const; }; } #endif // COMPONENTS_L10N_MESSAGEBUNDLES_H openmw-openmw-0.48.0/components/loadinglistener/000077500000000000000000000000001445372753700220045ustar00rootroot00000000000000openmw-openmw-0.48.0/components/loadinglistener/loadinglistener.hpp000066400000000000000000000034611445372753700257040ustar00rootroot00000000000000#ifndef COMPONENTS_LOADINGLISTENER_H #define COMPONENTS_LOADINGLISTENER_H #include namespace Loading { class Listener { public: /// Set a text label to show on the loading screen. /// @param label The label /// @param important Is the label considered important to show? /// @note "non-important" labels may not show on screen if the loading process went so fast /// that the implementation decided not to show a loading screen at all. "important" labels /// will show in a separate message-box if the loading screen was not shown. virtual void setLabel (const std::string& label, bool important=false) {} /// Start a loading sequence. Must call loadingOff() when done. /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, /// so that the loading is exception safe. virtual void loadingOn(bool visible=true) {} virtual void loadingOff() {} /// Set the total range of progress (e.g. the number of objects to load). virtual void setProgressRange (size_t range) {} /// Set current progress. Valid range is [0, progressRange) virtual void setProgress (size_t value) {} /// Increase current progress, default by 1. virtual void increaseProgress (size_t increase = 1) {} virtual ~Listener() = default; }; /// @brief Used for stopping a loading sequence when the object goes out of scope struct ScopedLoad { ScopedLoad(Listener* l) : mListener(l) { mListener->loadingOn(); } ~ScopedLoad() { mListener->loadingOff(); } Listener* mListener; }; } #endif openmw-openmw-0.48.0/components/loadinglistener/reporter.cpp000066400000000000000000000015601445372753700243540ustar00rootroot00000000000000#include "reporter.hpp" #include "loadinglistener.hpp" #include #include #include namespace Loading { void Reporter::addTotal(std::size_t value) { const std::lock_guard lock(mMutex); mTotal += value; mUpdated.notify_all(); } void Reporter::addProgress(std::size_t value) { const std::lock_guard lock(mMutex); mProgress += value; mUpdated.notify_all(); } void Reporter::complete() { const std::lock_guard lock(mMutex); mDone = true; mUpdated.notify_all(); } void Reporter::wait(Listener& listener) const { std::unique_lock lock(mMutex); while (!mDone) { listener.setProgressRange(mTotal); listener.setProgress(mProgress); mUpdated.wait(lock); } } } openmw-openmw-0.48.0/components/loadinglistener/reporter.hpp000066400000000000000000000011411445372753700243540ustar00rootroot00000000000000#ifndef COMPONENTS_LOADINGLISTENER_REPORTER_H #define COMPONENTS_LOADINGLISTENER_REPORTER_H #include #include #include namespace Loading { class Listener; class Reporter { public: void addTotal(std::size_t value); void addProgress(std::size_t value); void complete(); void wait(Listener& listener) const; private: std::size_t mProgress = 0; std::size_t mTotal = 0; bool mDone = false; mutable std::mutex mMutex; mutable std::condition_variable mUpdated; }; } #endif openmw-openmw-0.48.0/components/lua/000077500000000000000000000000001445372753700174025ustar00rootroot00000000000000openmw-openmw-0.48.0/components/lua/asyncpackage.cpp000066400000000000000000000077701445372753700225520ustar00rootroot00000000000000#include "asyncpackage.hpp" namespace sol { template <> struct is_automagical : std::false_type { }; template <> struct is_automagical : std::false_type { }; } namespace LuaUtil { struct TimerCallback { AsyncPackageId mAsyncId; std::string mName; }; Callback Callback::fromLua(const sol::table& t) { return Callback{ t.raw_get(1), t.raw_get(2).mHiddenData }; } bool Callback::isLuaCallback(const sol::object& t) { if (!t.is()) return false; sol::object meta = sol::table(t)[sol::metatable_key]; if (!meta.is()) return false; return sol::table(meta).raw_get_or("isCallback", false); } sol::function getAsyncPackageInitializer( lua_State* L, std::function simulationTimeFn, std::function gameTimeFn) { sol::state_view lua(L); using TimerType = ScriptsContainer::TimerType; sol::usertype api = lua.new_usertype("AsyncPackage"); api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::main_protected_function callback) { asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback)); return TimerCallback{ asyncId, std::string(name) }; }; api["newSimulationTimer"] = [simulationTimeFn](const AsyncPackageId&, double delay, const TimerCallback& callback, sol::main_object callbackArg) { callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::SIMULATION_TIME, simulationTimeFn() + delay, callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; api["newGameTimer"] = [gameTimeFn](const AsyncPackageId&, double delay, const TimerCallback& callback, sol::main_object callbackArg) { callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::GAME_TIME, gameTimeFn() + delay, callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; api["newUnsavableSimulationTimer"] = [simulationTimeFn](const AsyncPackageId& asyncId, double delay, sol::main_protected_function callback) { asyncId.mContainer->setupUnsavableTimer( TimerType::SIMULATION_TIME, simulationTimeFn() + delay, asyncId.mScriptId, std::move(callback)); }; api["newUnsavableGameTimer"] = [gameTimeFn](const AsyncPackageId& asyncId, double delay, sol::main_protected_function callback) { asyncId.mContainer->setupUnsavableTimer( TimerType::GAME_TIME, gameTimeFn() + delay, asyncId.mScriptId, std::move(callback)); }; sol::table callbackMeta = sol::table::create(L); callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { return Callback::fromLua(callback).call(sol::as_args(va)); }; callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; callbackMeta[sol::meta_function::metatable] = false; callbackMeta["isCallback"] = true; api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { sol::table c = sol::table::create(fn.lua_state(), 2); c.raw_set(1, std::move(fn), 2, asyncId); c[sol::metatable_key] = callbackMeta; return c; }; auto initializer = [](sol::table hiddenData) { ScriptsContainer::ScriptId id = hiddenData[ScriptsContainer::sScriptIdKey]; return AsyncPackageId{ id.mContainer, id.mIndex, hiddenData }; }; return sol::make_object(lua, initializer); } } openmw-openmw-0.48.0/components/lua/asyncpackage.hpp000066400000000000000000000033521445372753700225470ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_ASYNCPACKAGE_H #define COMPONENTS_LUA_ASYNCPACKAGE_H #include "scriptscontainer.hpp" namespace LuaUtil { struct AsyncPackageId { ScriptsContainer* mContainer; int mScriptId; sol::table mHiddenData; }; sol::function getAsyncPackageInitializer( lua_State* L, std::function simulationTimeFn, std::function gameTimeFn); // Wrapper for a Lua function. // Holds information about the script the function belongs to. // Needed to prevent callback calls if the script was removed. struct Callback { sol::main_protected_function mFunc; sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer static bool isLuaCallback(const sol::object&); static Callback fromLua(const sol::table&); bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; } template sol::object call(Args&&... args) const { if (isValid()) return LuaUtil::call(mFunc, std::forward(args)...); else Log(Debug::Debug) << "Ignored callback to the removed script " << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); return sol::nil; } template void tryCall(Args&&... args) const { try { this->call(std::forward(args)...); } catch (std::exception& e) { Log(Debug::Error) << "Error in callback: " << e.what(); } } }; } #endif // COMPONENTS_LUA_ASYNCPACKAGE_H openmw-openmw-0.48.0/components/lua/configuration.cpp000066400000000000000000000242111445372753700227550ustar00rootroot00000000000000#include "configuration.hpp" #include #include #include #include #include namespace LuaUtil { namespace { const std::map> flagsByName{ {"GLOBAL", ESM::LuaScriptCfg::sGlobal}, {"CUSTOM", ESM::LuaScriptCfg::sCustom}, {"PLAYER", ESM::LuaScriptCfg::sPlayer}, }; const std::map> typeTagsByName{ {"ACTIVATOR", ESM::REC_ACTI}, {"ARMOR", ESM::REC_ARMO}, {"BOOK", ESM::REC_BOOK}, {"CLOTHING", ESM::REC_CLOT}, {"CONTAINER", ESM::REC_CONT}, {"CREATURE", ESM::REC_CREA}, {"DOOR", ESM::REC_DOOR}, {"INGREDIENT", ESM::REC_INGR}, {"LIGHT", ESM::REC_LIGH}, {"MISC_ITEM", ESM::REC_MISC}, {"NPC", ESM::REC_NPC_}, {"POTION", ESM::REC_ALCH}, {"WEAPON", ESM::REC_WEAP}, {"APPARATUS", ESM::REC_APPA}, {"LOCKPICK", ESM::REC_LOCK}, {"PROBE", ESM::REC_PROB}, {"REPAIR", ESM::REC_REPA}, }; bool isSpace(char c) { return std::isspace(static_cast(c)); } } void ScriptsConfiguration::init(ESM::LuaScriptsCfg cfg) { mScripts.clear(); mPathToIndex.clear(); // Find duplicates; only the last occurrence will be used (unless `sMerge` flag is used). // Search for duplicates is case insensitive. std::vector skip(cfg.mScripts.size(), false); for (size_t i = 0; i < cfg.mScripts.size(); ++i) { const ESM::LuaScriptCfg& script = cfg.mScripts[i]; bool global = script.mFlags & ESM::LuaScriptCfg::sGlobal; if (global && (script.mFlags & ~ESM::LuaScriptCfg::sMerge) != ESM::LuaScriptCfg::sGlobal) throw std::runtime_error(std::string("Global script can not have local flags: ") + script.mScriptPath); if (global && (!script.mTypes.empty() || !script.mRecords.empty() || !script.mRefs.empty())) throw std::runtime_error(std::string( "Global script can not have per-type and per-object configuration") + script.mScriptPath); auto [it, inserted] = mPathToIndex.emplace( Misc::StringUtils::lowerCase(script.mScriptPath), i); if (inserted) continue; ESM::LuaScriptCfg& oldScript = cfg.mScripts[it->second]; if (global != bool(oldScript.mFlags & ESM::LuaScriptCfg::sGlobal)) throw std::runtime_error(std::string("Flags mismatch for ") + script.mScriptPath); if (script.mFlags & ESM::LuaScriptCfg::sMerge) { oldScript.mFlags |= (script.mFlags & ~ESM::LuaScriptCfg::sMerge); if (!script.mInitializationData.empty()) oldScript.mInitializationData = script.mInitializationData; oldScript.mTypes.insert(oldScript.mTypes.end(), script.mTypes.begin(), script.mTypes.end()); oldScript.mRecords.insert(oldScript.mRecords.end(), script.mRecords.begin(), script.mRecords.end()); oldScript.mRefs.insert(oldScript.mRefs.end(), script.mRefs.begin(), script.mRefs.end()); skip[i] = true; } else skip[it->second] = true; } // Filter duplicates for (size_t i = 0; i < cfg.mScripts.size(); ++i) { if (!skip[i]) mScripts.push_back(std::move(cfg.mScripts[i])); } // Initialize mappings mPathToIndex.clear(); for (int i = 0; i < static_cast(mScripts.size()); ++i) { const ESM::LuaScriptCfg& s = mScripts[i]; mPathToIndex[s.mScriptPath] = i; // Stored paths are case sensitive. for (uint32_t t : s.mTypes) mScriptsPerType[t].push_back(i); for (const ESM::LuaScriptCfg::PerRecordCfg& r : s.mRecords) { std::string_view data = r.mInitializationData.empty() ? s.mInitializationData : r.mInitializationData; mScriptsPerRecordId[r.mRecordId].push_back(DetailedConf{i, r.mAttach, data}); } for (const ESM::LuaScriptCfg::PerRefCfg& r : s.mRefs) { std::string_view data = r.mInitializationData.empty() ? s.mInitializationData : r.mInitializationData; mScriptsPerRefNum[ESM::RefNum{r.mRefnumIndex, r.mRefnumContentFile}].push_back(DetailedConf{i, r.mAttach, data}); } } } std::optional ScriptsConfiguration::findId(std::string_view path) const { auto it = mPathToIndex.find(path); if (it != mPathToIndex.end()) return it->second; else return std::nullopt; } ScriptIdsWithInitializationData ScriptsConfiguration::getConfByFlag(ESM::LuaScriptCfg::Flags flag) const { ScriptIdsWithInitializationData res; for (size_t id = 0; id < mScripts.size(); ++id) { const ESM::LuaScriptCfg& script = mScripts[id]; if (script.mFlags & flag) res[id] = script.mInitializationData; } return res; } ScriptIdsWithInitializationData ScriptsConfiguration::getLocalConf( uint32_t type, std::string_view recordId, ESM::RefNum refnum) const { ScriptIdsWithInitializationData res; auto typeIt = mScriptsPerType.find(type); if (typeIt != mScriptsPerType.end()) for (int scriptId : typeIt->second) res[scriptId] = mScripts[scriptId].mInitializationData; auto recordIt = mScriptsPerRecordId.find(recordId); if (recordIt != mScriptsPerRecordId.end()) { for (const DetailedConf& d : recordIt->second) { if (d.mAttach) res[d.mScriptId] = d.mInitializationData; else res.erase(d.mScriptId); } } if (!refnum.hasContentFile()) return res; auto refIt = mScriptsPerRefNum.find(refnum); if (refIt == mScriptsPerRefNum.end()) return res; for (const DetailedConf& d : refIt->second) { if (d.mAttach) res[d.mScriptId] = d.mInitializationData; else res.erase(d.mScriptId); } return res; } void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data) { while (!data.empty()) { // Get next line std::string_view line = data.substr(0, data.find('\n')); data = data.substr(std::min(line.size() + 1, data.size())); if (!line.empty() && line.back() == '\r') line = line.substr(0, line.size() - 1); while (!line.empty() && isSpace(line[0])) line = line.substr(1); if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; while (!line.empty() && isSpace(line.back())) line = line.substr(0, line.size() - 1); if (!Misc::StringUtils::ciEndsWith(line, ".lua")) throw std::runtime_error(Misc::StringUtils::format( "Lua script should have suffix '.lua', got: %s", std::string(line.substr(0, 300)))); // Split tags and script path size_t semicolonPos = line.find(':'); if (semicolonPos == std::string::npos) throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line))); std::string_view tagsStr = line.substr(0, semicolonPos); std::string_view scriptPath = line.substr(semicolonPos + 1); while (isSpace(scriptPath[0])) scriptPath = scriptPath.substr(1); ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back(); script.mScriptPath = std::string(scriptPath); script.mFlags = 0; // Parse tags size_t tagsPos = 0; while (true) { while (tagsPos < tagsStr.size() && (isSpace(tagsStr[tagsPos]) || tagsStr[tagsPos] == ',')) tagsPos++; size_t startPos = tagsPos; while (tagsPos < tagsStr.size() && !isSpace(tagsStr[tagsPos]) && tagsStr[tagsPos] != ',') tagsPos++; if (startPos == tagsPos) break; std::string_view tagName = tagsStr.substr(startPos, tagsPos - startPos); auto it = flagsByName.find(tagName); auto typesIt = typeTagsByName.find(tagName); if (it != flagsByName.end()) script.mFlags |= it->second; else if (typesIt != typeTagsByName.end()) script.mTypes.push_back(typesIt->second); else throw std::runtime_error(Misc::StringUtils::format("Unknown tag '%s' in: %s", std::string(tagName), std::string(line))); } } } std::string scriptCfgToString(const ESM::LuaScriptCfg& script) { std::stringstream ss; if (script.mFlags & ESM::LuaScriptCfg::sMerge) ss << "+ "; for (const auto& [flagName, flag] : flagsByName) { if (script.mFlags & flag) ss << flagName << " "; } for (uint32_t type : script.mTypes) { for (const auto& [tagName, t] : typeTagsByName) { if (type == t) ss << tagName << " "; } } ss << ": " << script.mScriptPath; if (!script.mInitializationData.empty()) ss << " ; data " << script.mInitializationData.size() << " bytes"; if (!script.mRecords.empty()) ss << " ; " << script.mRecords.size() << " records"; if (!script.mRefs.empty()) ss << " ; " << script.mRefs.size() << " objects"; return ss.str(); } } openmw-openmw-0.48.0/components/lua/configuration.hpp000066400000000000000000000035551445372753700227720ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_CONFIGURATION_H #define COMPONENTS_LUA_CONFIGURATION_H #include #include #include "components/esm/luascripts.hpp" #include "components/esm3/cellref.hpp" namespace LuaUtil { using ScriptIdsWithInitializationData = std::map; class ScriptsConfiguration { public: void init(ESM::LuaScriptsCfg); size_t size() const { return mScripts.size(); } const ESM::LuaScriptCfg& operator[](int id) const { return mScripts[id]; } std::optional findId(std::string_view path) const; bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; } ScriptIdsWithInitializationData getGlobalConf() const { return getConfByFlag(ESM::LuaScriptCfg::sGlobal); } ScriptIdsWithInitializationData getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); } ScriptIdsWithInitializationData getLocalConf(uint32_t type, std::string_view recordId, ESM::RefNum refnum) const; private: ScriptIdsWithInitializationData getConfByFlag(ESM::LuaScriptCfg::Flags flag) const; std::vector mScripts; std::map> mPathToIndex; struct DetailedConf { int mScriptId; bool mAttach; std::string_view mInitializationData; }; std::map> mScriptsPerType; std::map, std::less<>> mScriptsPerRecordId; std::map> mScriptsPerRefNum; }; // Parse ESM::LuaScriptsCfg from text and add to `cfg`. void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data); std::string scriptCfgToString(const ESM::LuaScriptCfg& script); } #endif // COMPONENTS_LUA_CONFIGURATION_H openmw-openmw-0.48.0/components/lua/l10n.cpp000066400000000000000000000133421445372753700206630ustar00rootroot00000000000000#include "l10n.hpp" #include #include #include namespace sol { template <> struct is_automagical : std::false_type {}; } namespace LuaUtil { void L10nManager::init() { sol::usertype ctx = mLua->sol().new_usertype("L10nContext"); ctx[sol::meta_function::call] = &Context::translate; } std::string L10nManager::translate(const std::string& contextName, const std::string& key) { Context& ctx = getContext(contextName).as(); return ctx.translate(key, sol::nil); } void L10nManager::setPreferredLocales(const std::vector& langs) { mPreferredLocales.clear(); for (const auto &lang : langs) mPreferredLocales.push_back(icu::Locale(lang.c_str())); { Log msg(Debug::Info); msg << "Preferred locales:"; for (const icu::Locale& l : mPreferredLocales) msg << " " << l.getName(); } for (auto& [_, context] : mContexts) context.updateLang(this); } void L10nManager::Context::readLangData(L10nManager* manager, const icu::Locale& lang) { std::string path = "l10n/"; path.append(mName); path.append("/"); path.append(lang.getName()); path.append(".yaml"); if (!manager->mVFS->exists(path)) return; mMessageBundles->load(*manager->mVFS->get(path), lang, path); } std::pair, std::vector> getICUArgs(std::string_view messageId, const sol::table &table) { std::vector args; std::vector argNames; for (auto& [key, value] : table) { // Argument values if (value.is()) args.push_back(icu::Formattable(LuaUtil::cast(value).c_str())); // Note: While we pass all numbers as doubles, they still seem to be handled appropriately. // Numbers can be forced to be integers using the argType number and argStyle integer // E.g. {var, number, integer} else if (value.is()) args.push_back(icu::Formattable(LuaUtil::cast(value))); else { Log(Debug::Error) << "Unrecognized argument type for key \"" << LuaUtil::cast(key) << "\" when formatting message \"" << messageId << "\""; } // Argument names const auto str = LuaUtil::cast(key); argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(str.data(), str.size()))); } return std::make_pair(args, argNames); } std::string L10nManager::Context::translate(std::string_view key, const sol::object& data) { std::vector args; std::vector argNames; if (data.is()) { sol::table dataTable = data.as(); auto argData = getICUArgs(key, dataTable); args = argData.first; argNames = argData.second; } return mMessageBundles->formatMessage(key, argNames, args); } void L10nManager::Context::updateLang(L10nManager* manager) { icu::Locale fallbackLocale = mMessageBundles->getFallbackLocale(); mMessageBundles->setPreferredLocales(manager->mPreferredLocales); int localeCount = 0; bool fallbackLocaleInPreferred = false; for (const icu::Locale& loc: mMessageBundles->getPreferredLocales()) { if (!mMessageBundles->isLoaded(loc)) readLangData(manager, loc); if (mMessageBundles->isLoaded(loc)) { localeCount++; Log(Debug::Verbose) << "Language file \"l10n/" << mName << "/" << loc.getName() << ".yaml\" is enabled"; if (loc == fallbackLocale) fallbackLocaleInPreferred = true; } } if (!mMessageBundles->isLoaded(fallbackLocale)) readLangData(manager, fallbackLocale); if (mMessageBundles->isLoaded(fallbackLocale) && !fallbackLocaleInPreferred) Log(Debug::Verbose) << "Fallback language file \"l10n/" << mName << "/" << fallbackLocale.getName() << ".yaml\" is enabled"; if (localeCount == 0) { Log(Debug::Warning) << "No language files for the preferred languages found in \"l10n/" << mName << "\""; } } sol::object L10nManager::getContext(const std::string& contextName, const std::string& fallbackLocaleName) { auto it = mContexts.find(contextName); if (it != mContexts.end()) return sol::make_object(mLua->sol(), it->second); auto allowedChar = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'; }; bool valid = !contextName.empty(); for (char c : contextName) valid = valid && allowedChar(c); if (!valid) throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName); icu::Locale fallbackLocale(fallbackLocaleName.c_str()); Context ctx{contextName, std::make_shared(mPreferredLocales, fallbackLocale)}; { Log msg(Debug::Verbose); msg << "Fallback locale: " << fallbackLocale.getName(); } ctx.updateLang(this); mContexts.emplace(contextName, ctx); return sol::make_object(mLua->sol(), ctx); } } openmw-openmw-0.48.0/components/lua/l10n.hpp000066400000000000000000000026361445372753700206740ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_I18N_H #define COMPONENTS_LUA_I18N_H #include "luastate.hpp" #include namespace VFS { class Manager; } namespace LuaUtil { class L10nManager { public: L10nManager(const VFS::Manager* vfs, LuaState* lua) : mVFS(vfs), mLua(lua) {} void init(); void clear() { mContexts.clear(); } void setPreferredLocales(const std::vector& locales); const std::vector& getPreferredLocales() const { return mPreferredLocales; } sol::object getContext(const std::string& contextName, const std::string& fallbackLocale = "en"); std::string translate(const std::string& contextName, const std::string& key); private: struct Context { const std::string mName; // Must be a shared pointer so that sol::make_object copies the pointer, not the data structure. std::shared_ptr mMessageBundles; void updateLang(L10nManager* manager); void readLangData(L10nManager* manager, const icu::Locale& lang); std::string translate(std::string_view key, const sol::object& data); }; const VFS::Manager* mVFS; LuaState* mLua; std::vector mPreferredLocales; std::map mContexts; }; } #endif // COMPONENTS_LUA_I18N_H openmw-openmw-0.48.0/components/lua/luastate.cpp000066400000000000000000000303731445372753700217360ustar00rootroot00000000000000#include "luastate.hpp" #ifndef NO_LUAJIT #include #endif // NO_LUAJIT #include #include #include #include namespace LuaUtil { static std::string packageNameToVfsPath(std::string_view packageName, const VFS::Manager* vfs) { std::string path(packageName); std::replace(path.begin(), path.end(), '.', '/'); std::string pathWithInit = path + "/init.lua"; path.append(".lua"); if (vfs->exists(path)) return path; else if (vfs->exists(pathWithInit)) return pathWithInit; else throw std::runtime_error("module not found: " + std::string(packageName)); } static std::string packageNameToPath(std::string_view packageName, const std::vector& searchDirs) { std::string path(packageName); std::replace(path.begin(), path.end(), '.', '/'); std::string pathWithInit = path + "/init.lua"; path.append(".lua"); for (const std::string& dir : searchDirs) { boost::filesystem::path base(dir); boost::filesystem::path p1 = base / path; if (boost::filesystem::exists(p1)) return p1.string(); boost::filesystem::path p2 = base / pathWithInit; if (boost::filesystem::exists(p2)) return p2.string(); } throw std::runtime_error("module not found: " + std::string(packageName)); } static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable"}; static const std::string safePackages[] = {"coroutine", "math", "string", "table"}; LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs) { mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug); mLua["math"]["randomseed"](static_cast(std::time(nullptr))); mLua["math"]["randomseed"] = []{}; mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mLua["setEnvironment"] = [](const sol::environment& env, const sol::function& fn) { sol::set_environment(env, fn); }; mLua["loadFromVFS"] = [this](std::string_view packageName) { return loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); }; mLua["loadInternalLib"] = [this](std::string_view packageName) { return loadInternalLib(packageName); }; // Some fixes for compatibility between different Lua versions if (mLua["unpack"] == sol::nil) mLua["unpack"] = mLua["table"]["unpack"]; else if (mLua["table"]["unpack"] == sol::nil) mLua["table"]["unpack"] = mLua["unpack"]; if (LUA_VERSION_NUM <= 501) { mLua.script(R"( local _pairs = pairs local _ipairs = ipairs pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end ipairs = function(v) return (rawget(getmetatable(v) or {}, '__ipairs') or _ipairs)(v) end )"); } mLua.script(R"( local printToLog = function(...) local strs = {} for i = 1, select('#', ...) do strs[i] = tostring(select(i, ...)) end return writeToLog(table.concat(strs, '\t')) end printGen = function(name) return function(...) return printToLog(name, ...) end end function requireGen(env, loaded, loadFn) return function(packageName) local p = loaded[packageName] if p == nil then local loader = loadFn(packageName) setEnvironment(env, loader) p = loader(packageName) loaded[packageName] = p end return p end end function createStrictIndexFn(tbl) return function(_, key) local res = tbl[key] if res ~= nil then return res else error('Key not found: '..tostring(key), 2) end end end function pairsForReadOnly(v) local nextFn, t, firstKey = pairs(getmetatable(v).t) return function(_, k) return nextFn(t, k) end, v, firstKey end function ipairsForReadOnly(v) local nextFn, t, firstKey = ipairs(getmetatable(v).t) return function(_, k) return nextFn(t, k) end, v, firstKey end local function nextForArray(array, index) index = (index or 0) + 1 if index <= #array then return index, array[index] end end function ipairsForArray(array) return nextForArray, array, 0 end getmetatable('').__metatable = false getSafeMetatable = function(v) if type(v) ~= 'table' then error('getmetatable is allowed only for tables', 2) end return getmetatable(v) end )"); mSandboxEnv = sol::table(mLua, sol::create); mSandboxEnv["_VERSION"] = mLua["_VERSION"]; for (const std::string& s : safeFunctions) { if (mLua[s] == sol::nil) throw std::logic_error("Lua function not found: " + s); mSandboxEnv[s] = mLua[s]; } for (const std::string& s : safePackages) { if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s); mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]); } mSandboxEnv["getmetatable"] = mLua["getSafeMetatable"]; mCommonPackages["os"] = mSandboxEnv["os"] = makeReadOnly(tableFromPairs({ {"date", mLua["os"]["date"]}, {"difftime", mLua["os"]["difftime"]}, {"time", mLua["os"]["time"]} })); } LuaState::~LuaState() { // Should be cleaned before destructing mLua. mCommonPackages.clear(); mSandboxEnv = sol::nil; } sol::table makeReadOnly(const sol::table& table, bool strictIndex) { if (table == sol::nil) return table; if (table.is()) return table; // it is already userdata, no sense to wrap it again lua_State* luaState = table.lua_state(); sol::state_view lua(luaState); sol::table meta(lua, sol::create); meta["t"] = table; if (strictIndex) meta["__index"] = lua["createStrictIndexFn"](table); else meta["__index"] = table; meta["__pairs"] = lua["pairsForReadOnly"]; meta["__ipairs"] = lua["ipairsForReadOnly"]; lua_newuserdata(luaState, 0); sol::stack::push(luaState, meta); lua_setmetatable(luaState, -2); return sol::stack::pop(luaState); } sol::table getMutableFromReadOnly(const sol::userdata& ro) { return ro[sol::metatable_key].get()["t"]; } void LuaState::addCommonPackage(std::string packageName, sol::object package) { if (!package.is()) package = makeReadOnly(std::move(package)); mCommonPackages.emplace(std::move(packageName), std::move(package)); } sol::protected_function_result LuaState::runInNewSandbox( const std::string& path, const std::string& namePrefix, const std::map& packages, const sol::object& hiddenData) { sol::protected_function script = loadScriptAndCache(path); sol::environment env(mLua, sol::create, mSandboxEnv); std::string envName = namePrefix + "[" + path + "]:"; env["print"] = mLua["printGen"](envName); env["_G"] = env; env[sol::metatable_key]["__metatable"] = false; auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object { if (package.is()) return call(package.as(), hiddenData); else return package; }; sol::table loaded(mLua, sol::create); for (const auto& [key, value] : mCommonPackages) loaded[key] = maybeRunLoader(value); for (const auto& [key, value] : packages) loaded[key] = maybeRunLoader(value); env["require"] = mLua["requireGen"](env, loaded, mLua["loadFromVFS"]); sol::set_environment(env, script); return call(script); } sol::environment LuaState::newInternalLibEnvironment() { sol::environment env(mLua, sol::create, mSandboxEnv); sol::table loaded(mLua, sol::create); for (const std::string& s : safePackages) loaded[s] = static_cast(mSandboxEnv[s]); env["require"] = mLua["requireGen"](env, loaded, mLua["loadInternalLib"]); return env; } sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) throw std::runtime_error("Lua error: " + res.get()); else return std::move(res); } sol::function LuaState::loadScriptAndCache(const std::string& path) { auto iter = mCompiledScripts.find(path); if (iter != mCompiledScripts.end()) return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary); sol::function res = loadFromVFS(path); mCompiledScripts[path] = res.dump(); return res; } sol::function LuaState::loadFromVFS(const std::string& path) { std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text); if (!res.valid()) throw std::runtime_error("Lua error: " + res.get()); return res; } sol::function LuaState::loadInternalLib(std::string_view libName) { std::string path = packageNameToPath(libName, mLibSearchPaths); std::ifstream stream(path); std::string fileContent(std::istreambuf_iterator(stream), {}); sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text); if (!res.valid()) throw std::runtime_error("Lua error: " + res.get()); return res; } std::string getLuaVersion() { #ifdef NO_LUAJIT return LUA_RELEASE; #else return LUA_RELEASE " (" LUAJIT_VERSION ")"; #endif } std::string toString(const sol::object& obj) { if (obj == sol::nil) return "nil"; else if (obj.get_type() == sol::type::string) return "\"" + obj.as() + "\""; else return call(sol::state_view(obj.lua_state())["tostring"], obj); } std::string internal::formatCastingError(const sol::object& obj, const std::type_info& t) { const char* typeName = t.name(); if (t == typeid(int)) typeName = "int"; else if (t == typeid(unsigned)) typeName = "uint32"; else if (t == typeid(size_t)) typeName = "size_t"; else if (t == typeid(float)) typeName = "float"; else if (t == typeid(double)) typeName = "double"; else if (t == typeid(sol::table)) typeName = "sol::table"; else if (t == typeid(sol::function) || t == typeid(sol::protected_function)) typeName = "sol::function"; else if (t == typeid(std::string) || t == typeid(std::string_view)) typeName = "string"; return std::string("Value \"") + toString(obj) + std::string("\" can not be casted to ") + typeName; } } openmw-openmw-0.48.0/components/lua/luastate.hpp000066400000000000000000000154701445372753700217440ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_LUASTATE_H #define COMPONENTS_LUA_LUASTATE_H #include #include #include #include "configuration.hpp" namespace VFS { class Manager; } namespace LuaUtil { std::string getLuaVersion(); // Holds Lua state. // Provides additional features: // - Load scripts from the virtual filesystem; // - Caching of loaded scripts; // - Disable unsafe Lua functions; // - Run every instance of every script in a separate sandbox; // - Forbid any interactions between sandboxes except than via provided API; // - Access to common read-only resources from different sandboxes; // - Replace standard `require` with a safe version that allows to search // Lua libraries (only source, no dll's) in the virtual filesystem; // - Make `print` to add the script name to every message and // write to the Log rather than directly to stdout; class LuaState { public: explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf); ~LuaState(); // Returns underlying sol::state. sol::state& sol() { return mLua; } // Can be used by a C++ function that is called from Lua to get the Lua traceback. // Makes no sense if called not from Lua code. // Note: It is a slow function, should be used for debug purposes only. std::string debugTraceback() { return mLua["debug"]["traceback"]().get(); } // A shortcut to create a new Lua table. sol::table newTable() { return sol::table(mLua, sol::create); } template sol::table tableFromPairs(std::initializer_list> list) { sol::table res(mLua, sol::create); for (const auto& [k, v] : list) res[k] = v; return res; } // Registers a package that will be available from every sandbox via `require(name)`. // The package can be either a sol::table with an API or a sol::function. If it is a function, // it will be evaluated (once per sandbox) the first time when requested. If the package // is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains). void addCommonPackage(std::string packageName, sol::object package); // Creates a new sandbox, runs a script, and returns the result // (the result is expected to be an interface of the script). // Args: // path: path to the script in the virtual filesystem; // namePrefix: sandbox name will be "[]". Sandbox name // will be added to every `print` output. // packages: additional packages that should be available from the sandbox via `require`. Each package // should be either a sol::table or a sol::function. If it is a function, it will be evaluated // (once per sandbox) with the argument 'hiddenData' the first time when requested. sol::protected_function_result runInNewSandbox(const std::string& path, const std::string& namePrefix = "", const std::map& packages = {}, const sol::object& hiddenData = sol::nil); void dropScriptCache() { mCompiledScripts.clear(); } const ScriptsConfiguration& getConfiguration() const { return *mConf; } // Load internal Lua library. All libraries are loaded in one sandbox and shouldn't be exposed to scripts directly. void addInternalLibSearchPath(const std::string& path) { mLibSearchPaths.push_back(path); } sol::function loadInternalLib(std::string_view libName); sol::function loadFromVFS(const std::string& path); sol::environment newInternalLibEnvironment(); private: static sol::protected_function_result throwIfError(sol::protected_function_result&&); template friend sol::protected_function_result call(const sol::protected_function& fn, Args&&... args); sol::function loadScriptAndCache(const std::string& path); sol::state mLua; const ScriptsConfiguration* mConf; sol::table mSandboxEnv; std::map mCompiledScripts; std::map mCommonPackages; const VFS::Manager* mVFS; std::vector mLibSearchPaths; }; // Should be used for every call of every Lua function. // It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078 template sol::protected_function_result call(const sol::protected_function& fn, Args&&... args) { try { return LuaState::throwIfError(fn(std::forward(args)...)); } catch (std::exception&) { throw; } catch (...) { throw std::runtime_error("Unknown error"); } } // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. template sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) { if (!table.is()) return sol::nil; if constexpr (sizeof...(str) == 0) return table.as()[first]; else return getFieldOrNil(table.as()[first], str...); } // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); namespace internal { std::string formatCastingError(const sol::object& obj, const std::type_info&); } template decltype(auto) cast(const sol::object& obj) { if (!obj.is()) throw std::runtime_error(internal::formatCastingError(obj, typeid(T))); return obj.as(); } template T getValueOrDefault(const sol::object& obj, const T& defaultValue) { if (obj == sol::nil) return defaultValue; return cast(obj); } // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. // Needed to forbid any changes in common resources that can be accessed from different sandboxes. // `strictIndex = true` replaces default `__index` with a strict version that throws an error if key is not found. sol::table makeReadOnly(const sol::table&, bool strictIndex = false); inline sol::table makeStrictReadOnly(const sol::table& tbl) { return makeReadOnly(tbl, true); } sol::table getMutableFromReadOnly(const sol::userdata&); } #endif // COMPONENTS_LUA_LUASTATE_H openmw-openmw-0.48.0/components/lua/scriptscontainer.cpp000066400000000000000000000515311445372753700235050ustar00rootroot00000000000000#include "scriptscontainer.hpp" #include namespace LuaUtil { static constexpr std::string_view ENGINE_HANDLERS = "engineHandlers"; static constexpr std::string_view EVENT_HANDLERS = "eventHandlers"; static constexpr std::string_view INTERFACE_NAME = "interfaceName"; static constexpr std::string_view INTERFACE = "interface"; static constexpr std::string_view HANDLER_INIT = "onInit"; static constexpr std::string_view HANDLER_SAVE = "onSave"; static constexpr std::string_view HANDLER_LOAD = "onLoad"; static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride"; ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) { registerEngineHandlers({&mUpdateHandlers}); mPublicInterfaces = sol::table(lua->sol(), sol::create); addPackage("openmw.interfaces", mPublicInterfaces); } void ScriptsContainer::printError(int scriptId, std::string_view msg, const std::exception& e) { Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what(); } void ScriptsContainer::addPackage(std::string packageName, sol::object package) { mAPI.emplace(std::move(packageName), makeReadOnly(std::move(package))); } bool ScriptsContainer::addCustomScript(int scriptId) { const ScriptsConfiguration& conf = mLua.getConfiguration(); assert(conf.isCustomScript(scriptId)); std::optional onInit, onLoad; bool ok = addScript(scriptId, onInit, onLoad); if (ok && onInit) callOnInit(scriptId, *onInit, conf[scriptId].mInitializationData); return ok; } void ScriptsContainer::addAutoStartedScripts() { for (const auto& [scriptId, data] : mAutoStartScripts) { std::optional onInit, onLoad; bool ok = addScript(scriptId, onInit, onLoad); if (ok && onInit) callOnInit(scriptId, *onInit, data); } } bool ScriptsContainer::addScript(int scriptId, std::optional& onInit, std::optional& onLoad) { assert(scriptId >= 0 && scriptId < static_cast(mLua.getConfiguration().size())); if (mScripts.count(scriptId) != 0) return false; // already present const std::string& path = scriptPath(scriptId); std::string debugName = mNamePrefix; debugName.push_back('['); debugName.append(path); debugName.push_back(']'); Script& script = mScripts[scriptId]; script.mHiddenData = mLua.newTable(); script.mHiddenData[sScriptIdKey] = ScriptId{this, scriptId}; script.mHiddenData[sScriptDebugNameKey] = debugName; script.mPath = path; try { sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData); if (scriptOutput == sol::nil) return true; sol::object engineHandlers = sol::nil, eventHandlers = sol::nil; for (const auto& [key, value] : cast(scriptOutput)) { std::string_view sectionName = cast(key); if (sectionName == ENGINE_HANDLERS) engineHandlers = value; else if (sectionName == EVENT_HANDLERS) eventHandlers = value; else if (sectionName == INTERFACE_NAME) script.mInterfaceName = cast(value); else if (sectionName == INTERFACE) script.mInterface = cast(value); else Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << debugName; } if (engineHandlers != sol::nil) { for (const auto& [key, handler] : cast(engineHandlers)) { std::string_view handlerName = cast(key); sol::function fn = cast(handler); if (handlerName == HANDLER_INIT) onInit = fn; else if (handlerName == HANDLER_LOAD) onLoad = fn; else if (handlerName == HANDLER_SAVE) script.mOnSave = fn; else if (handlerName == HANDLER_INTERFACE_OVERRIDE) script.mOnOverride = fn; else { auto it = mEngineHandlers.find(handlerName); if (it == mEngineHandlers.end()) Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << debugName; else insertHandler(it->second->mList, scriptId, fn); } } } if (eventHandlers != sol::nil) { for (const auto& [key, fn] : cast(eventHandlers)) { std::string_view eventName = cast(key); auto it = mEventHandlers.find(eventName); if (it == mEventHandlers.end()) it = mEventHandlers.emplace(std::string(eventName), EventHandlerList()).first; insertHandler(it->second, scriptId, cast(fn)); } } if (script.mInterfaceName.empty() == script.mInterface.has_value()) { Log(Debug::Error) << debugName << ": 'interfaceName' should always be used together with 'interface'"; script.mInterfaceName.clear(); script.mInterface = sol::nil; } else if (script.mInterface) { script.mInterface = makeReadOnly(*script.mInterface); insertInterface(scriptId, script); } return true; } catch (std::exception& e) { mScripts[scriptId].mHiddenData[sScriptIdKey] = sol::nil; mScripts.erase(scriptId); Log(Debug::Error) << "Can't start " << debugName << "; " << e.what(); return false; } } void ScriptsContainer::removeScript(int scriptId) { auto scriptIter = mScripts.find(scriptId); if (scriptIter == mScripts.end()) return; // no such script Script& script = scriptIter->second; if (script.mInterface) removeInterface(scriptId, script); script.mHiddenData[sScriptIdKey] = sol::nil; mScripts.erase(scriptIter); for (auto& [_, handlers] : mEngineHandlers) removeHandler(handlers->mList, scriptId); for (auto& [_, handlers] : mEventHandlers) removeHandler(handlers, scriptId); } void ScriptsContainer::insertInterface(int scriptId, const Script& script) { assert(script.mInterface); const Script* prev = nullptr; const Script* next = nullptr; int nextId = 0; for (const auto& [otherId, otherScript] : mScripts) { if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName) continue; if (otherId < scriptId) prev = &otherScript; else { next = &otherScript; nextId = otherId; break; } } if (prev && script.mOnOverride) { try { LuaUtil::call(*script.mOnOverride, *prev->mInterface); } catch (std::exception& e) { printError(scriptId, "onInterfaceOverride failed", e); } } if (next && next->mOnOverride) { try { LuaUtil::call(*next->mOnOverride, *script.mInterface); } catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); } } if (next == nullptr) mPublicInterfaces[script.mInterfaceName] = *script.mInterface; } void ScriptsContainer::removeInterface(int scriptId, const Script& script) { assert(script.mInterface); const Script* prev = nullptr; const Script* next = nullptr; int nextId = 0; for (const auto& [otherId, otherScript] : mScripts) { if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName) continue; if (otherId < scriptId) prev = &otherScript; else { next = &otherScript; nextId = otherId; break; } } if (next) { if (next->mOnOverride) { sol::object prevInterface = sol::nil; if (prev) prevInterface = *prev->mInterface; try { LuaUtil::call(*next->mOnOverride, prevInterface); } catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); } } } else if (prev) mPublicInterfaces[script.mInterfaceName] = *prev->mInterface; else mPublicInterfaces[script.mInterfaceName] = sol::nil; } void ScriptsContainer::insertHandler(std::vector& list, int scriptId, sol::function fn) { list.emplace_back(); int pos = list.size() - 1; while (pos > 0 && list[pos - 1].mScriptId > scriptId) { list[pos] = std::move(list[pos - 1]); pos--; } list[pos].mScriptId = scriptId; list[pos].mFn = std::move(fn); } void ScriptsContainer::removeHandler(std::vector& list, int scriptId) { list.erase(std::remove_if(list.begin(), list.end(), [scriptId](const Handler& h){ return h.mScriptId == scriptId; }), list.end()); } void ScriptsContainer::receiveEvent(std::string_view eventName, std::string_view eventData) { auto it = mEventHandlers.find(eventName); if (it == mEventHandlers.end()) { Log(Debug::Warning) << mNamePrefix << " has received event '" << eventName << "', but there are no handlers for this event"; return; } sol::object data; try { data = LuaUtil::deserialize(mLua.sol(), eventData, mSerializer); } catch (std::exception& e) { Log(Debug::Error) << mNamePrefix << " can not parse eventData for '" << eventName << "': " << e.what(); return; } EventHandlerList& list = it->second; for (int i = list.size() - 1; i >= 0; --i) { try { sol::object res = LuaUtil::call(list[i].mFn, data); if (res.is() && !res.as()) break; // Skip other handlers if 'false' was returned. } catch (std::exception& e) { Log(Debug::Error) << mNamePrefix << "[" << scriptPath(list[i].mScriptId) << "] eventHandler[" << eventName << "] failed. " << e.what(); } } } void ScriptsContainer::registerEngineHandlers(std::initializer_list handlers) { for (EngineHandlerList* h : handlers) mEngineHandlers[h->mName] = h; } void ScriptsContainer::callOnInit(int scriptId, const sol::function& onInit, std::string_view data) { try { LuaUtil::call(onInit, deserialize(mLua.sol(), data, mSerializer)); } catch (std::exception& e) { printError(scriptId, "onInit failed", e); } } void ScriptsContainer::save(ESM::LuaScripts& data) { std::map> timers; auto saveTimerFn = [&](const Timer& timer, TimerType timerType) { if (!timer.mSerializable) return; ESM::LuaTimer savedTimer; savedTimer.mTime = timer.mTime; savedTimer.mType = timerType; savedTimer.mCallbackName = std::get(timer.mCallback); savedTimer.mCallbackArgument = timer.mSerializedArg; timers[timer.mScriptId].push_back(std::move(savedTimer)); }; for (const Timer& timer : mSimulationTimersQueue) saveTimerFn(timer, TimerType::SIMULATION_TIME); for (const Timer& timer : mGameTimersQueue) saveTimerFn(timer, TimerType::GAME_TIME); data.mScripts.clear(); for (auto& [scriptId, script] : mScripts) { ESM::LuaScript savedScript; // Note: We can not use `scriptPath(scriptId)` here because `save` can be called during // evaluating "reloadlua" command when ScriptsConfiguration is already changed. savedScript.mScriptPath = script.mPath; if (script.mOnSave) { try { sol::object state = LuaUtil::call(*script.mOnSave); savedScript.mData = serialize(state, mSerializer); } catch (std::exception& e) { printError(scriptId, "onSave failed", e); } } auto timersIt = timers.find(scriptId); if (timersIt != timers.end()) savedScript.mTimers = std::move(timersIt->second); data.mScripts.push_back(std::move(savedScript)); } } void ScriptsContainer::load(const ESM::LuaScripts& data) { removeAllScripts(); const ScriptsConfiguration& cfg = mLua.getConfiguration(); struct ScriptInfo { std::string_view mInitData; const ESM::LuaScript* mSavedData; }; std::map scripts; for (const auto& [scriptId, initData] : mAutoStartScripts) scripts[scriptId] = {initData, nullptr}; for (const ESM::LuaScript& s : data.mScripts) { std::optional scriptId = cfg.findId(s.mScriptPath); if (!scriptId) { Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered"; continue; } auto it = scripts.find(*scriptId); if (it != scripts.end()) it->second.mSavedData = &s; else if (cfg.isCustomScript(*scriptId)) scripts[*scriptId] = {cfg[*scriptId].mInitializationData, &s}; else Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; this script is not allowed here"; } for (const auto& [scriptId, scriptInfo] : scripts) { std::optional onInit, onLoad; if (!addScript(scriptId, onInit, onLoad)) continue; if (scriptInfo.mSavedData == nullptr) { if (onInit) callOnInit(scriptId, *onInit, scriptInfo.mInitData); continue; } if (onLoad) { try { sol::object state = deserialize(mLua.sol(), scriptInfo.mSavedData->mData, mSavedDataDeserializer); sol::object initializationData = deserialize(mLua.sol(), scriptInfo.mInitData, mSerializer); LuaUtil::call(*onLoad, state, initializationData); } catch (std::exception& e) { printError(scriptId, "onLoad failed", e); } } for (const ESM::LuaTimer& savedTimer : scriptInfo.mSavedData->mTimers) { Timer timer; timer.mCallback = savedTimer.mCallbackName; timer.mSerializable = true; timer.mScriptId = scriptId; timer.mTime = savedTimer.mTime; try { timer.mArg = sol::main_object( deserialize(mLua.sol(), savedTimer.mCallbackArgument, mSavedDataDeserializer)); // It is important if the order of content files was changed. The deserialize-serialize procedure // updates refnums, so timer.mSerializedArg may be not equal to savedTimer.mCallbackArgument. timer.mSerializedArg = serialize(timer.mArg, mSerializer); if (savedTimer.mType == TimerType::GAME_TIME) mGameTimersQueue.push_back(std::move(timer)); else mSimulationTimersQueue.push_back(std::move(timer)); } catch (std::exception& e) { printError(scriptId, "can not load timer", e); } } } std::make_heap(mSimulationTimersQueue.begin(), mSimulationTimersQueue.end()); std::make_heap(mGameTimersQueue.begin(), mGameTimersQueue.end()); } ScriptsContainer::~ScriptsContainer() { for (auto& [_, script] : mScripts) script.mHiddenData[sScriptIdKey] = sol::nil; } // Note: shouldn't be called from destructor because mEngineHandlers has pointers on // external objects that are already removed during child class destruction. void ScriptsContainer::removeAllScripts() { for (auto& [_, script] : mScripts) script.mHiddenData[sScriptIdKey] = sol::nil; mScripts.clear(); for (auto& [_, handlers] : mEngineHandlers) handlers->mList.clear(); mEventHandlers.clear(); mSimulationTimersQueue.clear(); mGameTimersQueue.clear(); mPublicInterfaces.clear(); } ScriptsContainer::Script& ScriptsContainer::getScript(int scriptId) { auto it = mScripts.find(scriptId); if (it == mScripts.end()) throw std::logic_error("Script doesn't exist"); return it->second; } void ScriptsContainer::registerTimerCallback( int scriptId, std::string_view callbackName, sol::main_protected_function callback) { getScript(scriptId).mRegisteredCallbacks.emplace(std::string(callbackName), std::move(callback)); } void ScriptsContainer::insertTimer(std::vector& timerQueue, Timer&& t) { timerQueue.push_back(std::move(t)); std::push_heap(timerQueue.begin(), timerQueue.end()); } void ScriptsContainer::setupSerializableTimer( TimerType type, double time, int scriptId, std::string_view callbackName, sol::main_object callbackArg) { Timer t; t.mCallback = std::string(callbackName); t.mScriptId = scriptId; t.mSerializable = true; t.mTime = time; t.mArg = callbackArg; t.mSerializedArg = serialize(t.mArg, mSerializer); insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t)); } void ScriptsContainer::setupUnsavableTimer( TimerType type, double time, int scriptId, sol::main_protected_function callback) { Timer t; t.mScriptId = scriptId; t.mSerializable = false; t.mTime = time; t.mCallback = mTemporaryCallbackCounter; getScript(t.mScriptId).mTemporaryCallbacks.emplace(mTemporaryCallbackCounter, std::move(callback)); mTemporaryCallbackCounter++; insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t)); } void ScriptsContainer::callTimer(const Timer& t) { try { Script& script = getScript(t.mScriptId); if (t.mSerializable) { const std::string& callbackName = std::get(t.mCallback); auto it = script.mRegisteredCallbacks.find(callbackName); if (it == script.mRegisteredCallbacks.end()) throw std::logic_error("Callback '" + callbackName + "' doesn't exist"); LuaUtil::call(it->second, t.mArg); } else { int64_t id = std::get(t.mCallback); LuaUtil::call(script.mTemporaryCallbacks.at(id)); script.mTemporaryCallbacks.erase(id); } } catch (std::exception& e) { printError(t.mScriptId, "callTimer failed", e); } } void ScriptsContainer::updateTimerQueue(std::vector& timerQueue, double time) { while (!timerQueue.empty() && timerQueue.front().mTime <= time) { callTimer(timerQueue.front()); std::pop_heap(timerQueue.begin(), timerQueue.end()); timerQueue.pop_back(); } } void ScriptsContainer::processTimers(double simulationTime, double gameTime) { updateTimerQueue(mSimulationTimersQueue, simulationTime); updateTimerQueue(mGameTimersQueue, gameTime); } } openmw-openmw-0.48.0/components/lua/scriptscontainer.hpp000066400000000000000000000270601445372753700235120ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_SCRIPTSCONTAINER_H #define COMPONENTS_LUA_SCRIPTSCONTAINER_H #include #include #include #include #include #include "luastate.hpp" #include "serialization.hpp" namespace LuaUtil { // ScriptsContainer is a base class for all scripts containers (LocalScripts, // GlobalScripts, PlayerScripts, etc). Each script runs in a separate sandbox. // Scripts from different containers can interact to each other only via events. // Scripts within one container can interact via interfaces. // All scripts from one container have the same set of API packages available. // // Each script should return a table in a specific format that describes its // handlers and interfaces. Every section of the table is optional. Basic structure: // // local function update(dt) // print("Update") // end // // local function someEventHandler(eventData) // print("'SomeEvent' received") // end // // return { // -- Provides interface for other scripts in the same container // interfaceName = "InterfaceName", // interface = { // someFunction = function() print("someFunction was called from another script") end, // }, // // -- Script interface for the engine. Not available for other script. // -- An error is printed if unknown handler is specified. // engineHandlers = { // onUpdate = update, // onInit = function(initData) ... end, -- used when the script is just created (not loaded) // onSave = function() return ... end, // onLoad = function(state, initData) ... end, -- "state" is the data that was earlier returned by onSave // // -- Works only if a child class has passed a EngineHandlerList // -- for 'onSomethingElse' to ScriptsContainer::registerEngineHandlers. // onSomethingElse = function() print("something else") end // }, // // -- Handlers for events, sent from other scripts. Engine itself never sent events. Any name can be used for an event. // eventHandlers = { // SomeEvent = someEventHandler // } // } class ScriptsContainer { public: // ScriptId of each script is stored with this key in Script::mHiddenData. // Removed from mHiddenData when the script if removed. constexpr static std::string_view sScriptIdKey = "_id"; // Debug identifier of each script is stored with this key in Script::mHiddenData. // Present in mHiddenData even after removal of the script from ScriptsContainer. constexpr static std::string_view sScriptDebugNameKey = "_name"; struct ScriptId { ScriptsContainer* mContainer; int mIndex; // index in LuaUtil::ScriptsConfiguration }; using TimerType = ESM::LuaTimer::Type; // `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output. // `autoStartScripts` specifies the list of scripts that should be autostarted in this container; // the script names themselves are stored in ScriptsConfiguration. ScriptsContainer(LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; virtual ~ScriptsContainer(); void setAutoStartConf(ScriptIdsWithInitializationData conf) { mAutoStartScripts = std::move(conf); } const ScriptIdsWithInitializationData& getAutoStartConf() const { return mAutoStartScripts; } // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. void addPackage(std::string packageName, sol::object package); // Gets script with given id from ScriptsConfiguration, finds the source in the virtual file system, starts as a new script, // adds it to the container, and calls onInit for this script. Returns `true` if the script was successfully added. // The script should have CUSTOM flag. If the flag is not set, or file not found, or has syntax errors, returns false. // If such script already exists in the container, then also returns false. bool addCustomScript(int scriptId); bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; } void removeScript(int scriptId); void processTimers(double simulationTime, double gameTime); // Calls `onUpdate` (if present) for every script in the container. // Handlers are called in the same order as scripts were added. void update(float dt) { callEngineHandlers(mUpdateHandlers, dt); } // Calls event handlers `eventName` (if present) for every script. // If several scripts register handlers for `eventName`, they are called in reverse order. // If some handler returns `false`, all remaining handlers are ignored. Any other return value // (including `nil`) has no effect. void receiveEvent(std::string_view eventName, std::string_view eventData); // Serializer defines how to serialize/deserialize userdata. If serializer is not provided, // only built-in types and types from util package can be serialized. void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; } // Special deserializer to use when load data from saves. Can be used to remap content files in Refnums. void setSavedDataDeserializer(const UserdataSerializer* serializer) { mSavedDataDeserializer = serializer; } // Starts scripts according to `autoStartMode` and calls `onInit` for them. Not needed if `load` is used. void addAutoStartedScripts(); // Removes all scripts including the auto started. void removeAllScripts(); // Calls engineHandler "onSave" for every script and saves the list of the scripts with serialized data to ESM::LuaScripts. void save(ESM::LuaScripts&); // Removes all scripts; starts scripts according to `autoStartMode` and // loads the savedScripts. Runs "onLoad" for each script. void load(const ESM::LuaScripts& savedScripts); // Callbacks for serializable timers should be registered in advance. // The script with the given path should already present in the container. void registerTimerCallback(int scriptId, std::string_view callbackName, sol::main_protected_function callback); // Sets up a timer, that can be automatically saved and loaded. // type - the type of timer, either SIMULATION_TIME or GAME_TIME. // time - the absolute game time (in seconds or in hours) when the timer should be executed. // scriptPath - script path in VFS is used as script id. The script with the given path should already present // in the container. callbackName - callback (should be registered in advance) for this timer. callbackArg - // parameter for the callback (should be serializable). void setupSerializableTimer( TimerType type, double time, int scriptId, std::string_view callbackName, sol::main_object callbackArg); // Creates a timer. `callback` is an arbitrary Lua function. These timers are called "unsavable" // because they can not be stored in saves. I.e. loading a saved game will not fully restore the state. void setupUnsavableTimer(TimerType type, double time, int scriptId, sol::main_protected_function callback); protected: struct Handler { int mScriptId; sol::function mFn; }; struct EngineHandlerList { std::string_view mName; std::vector mList; // "name" must be string literal explicit EngineHandlerList(std::string_view name) : mName(name) {} }; // Calls given handlers in direct order. template void callEngineHandlers(EngineHandlerList& handlers, const Args&... args) { for (Handler& handler : handlers.mList) { try { LuaUtil::call(handler.mFn, args...); } catch (std::exception& e) { Log(Debug::Error) << mNamePrefix << "[" << scriptPath(handler.mScriptId) << "] " << handlers.mName << " failed. " << e.what(); } } } // To add a new engine handler a derived class should register the corresponding EngineHandlerList and define // a public function (see how ScriptsContainer::update is implemented) that calls `callEngineHandlers`. void registerEngineHandlers(std::initializer_list handlers); const std::string mNamePrefix; LuaUtil::LuaState& mLua; private: struct Script { std::optional mOnSave; std::optional mOnOverride; std::optional mInterface; std::string mInterfaceName; sol::table mHiddenData; std::map mRegisteredCallbacks; std::map mTemporaryCallbacks; std::string mPath; }; struct Timer { double mTime; bool mSerializable; int mScriptId; std::variant mCallback; // string if serializable, integer otherwise sol::main_object mArg; std::string mSerializedArg; bool operator<(const Timer& t) const { return mTime > t.mTime; } }; using EventHandlerList = std::vector; // Add to container without calling onInit/onLoad. bool addScript(int scriptId, std::optional& onInit, std::optional& onLoad); // Returns script by id (throws an exception if doesn't exist) Script& getScript(int scriptId); void printError(int scriptId, std::string_view msg, const std::exception& e); const std::string& scriptPath(int scriptId) const { return mLua.getConfiguration()[scriptId].mScriptPath; } void callOnInit(int scriptId, const sol::function& onInit, std::string_view data); void callTimer(const Timer& t); void updateTimerQueue(std::vector& timerQueue, double time); static void insertTimer(std::vector& timerQueue, Timer&& t); static void insertHandler(std::vector& list, int scriptId, sol::function fn); static void removeHandler(std::vector& list, int scriptId); void insertInterface(int scriptId, const Script& script); void removeInterface(int scriptId, const Script& script); ScriptIdsWithInitializationData mAutoStartScripts; const UserdataSerializer* mSerializer = nullptr; const UserdataSerializer* mSavedDataDeserializer = nullptr; std::map mAPI; std::map mScripts; sol::table mPublicInterfaces; EngineHandlerList mUpdateHandlers{"onUpdate"}; std::map mEngineHandlers; std::map> mEventHandlers; std::vector mSimulationTimersQueue; std::vector mGameTimersQueue; int64_t mTemporaryCallbackCounter = 0; }; } #endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H openmw-openmw-0.48.0/components/lua/serialization.cpp000066400000000000000000000345761445372753700230020ustar00rootroot00000000000000#include "serialization.hpp" #include #include #include #include #include #include #include #include "luastate.hpp" #include "utilpackage.hpp" namespace LuaUtil { constexpr unsigned char FORMAT_VERSION = 0; enum class SerializedType : char { NUMBER = 0x0, LONG_STRING = 0x1, BOOLEAN = 0x2, TABLE_START = 0x3, TABLE_END = 0x4, VEC2 = 0x10, VEC3 = 0x11, TRANSFORM_M = 0x12, TRANSFORM_Q = 0x13, VEC4 = 0x14, COLOR = 0x15, // All values should be lesser than 0x20 (SHORT_STRING_FLAG). }; constexpr unsigned char SHORT_STRING_FLAG = 0x20; // 0b001SSSSS. SSSSS = string length constexpr unsigned char CUSTOM_FULL_FLAG = 0x40; // 0b01TTTTTT + 32bit dataSize constexpr unsigned char CUSTOM_COMPACT_FLAG = 0x80; // 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1) static void appendType(BinaryData& out, SerializedType type) { out.push_back(static_cast(type)); } template static void appendValue(BinaryData& out, T v) { v = Misc::toLittleEndian(v); out.append(reinterpret_cast(&v), sizeof(v)); } template static T getValue(std::string_view& binaryData) { if (binaryData.size() < sizeof(T)) throw std::runtime_error("Unexpected end of serialized data."); T v; std::memcpy(&v, binaryData.data(), sizeof(T)); binaryData = binaryData.substr(sizeof(T)); return Misc::fromLittleEndian(v); } static void appendString(BinaryData& out, std::string_view str) { if (str.size() < 32) out.push_back(SHORT_STRING_FLAG | char(str.size())); else { appendType(out, SerializedType::LONG_STRING); appendValue(out, str.size()); } out.append(str.data(), str.size()); } static void appendData(BinaryData& out, const void* data, size_t dataSize) { out.append(reinterpret_cast(data), dataSize); } void UserdataSerializer::append(BinaryData& out, std::string_view typeName, const void* data, size_t dataSize) { assert(!typeName.empty() && typeName.size() <= 64); if (typeName.size() <= 8 && dataSize < 16) { // Compact form: 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1). unsigned char t = CUSTOM_COMPACT_FLAG | (dataSize << 3) | (typeName.size() - 1); out.push_back(t); } else { // Full form: 0b01TTTTTT + 32bit dataSize. unsigned char t = CUSTOM_FULL_FLAG | (typeName.size() - 1); out.push_back(t); appendValue(out, dataSize); } out.append(typeName.data(), typeName.size()); appendData(out, data, dataSize); } void UserdataSerializer::appendRefNum(BinaryData& out, ESM::RefNum refnum) { static_assert(sizeof(ESM::RefNum) == 8); refnum.mIndex = Misc::toLittleEndian(refnum.mIndex); refnum.mContentFile = Misc::toLittleEndian(refnum.mContentFile); append(out, sRefNumTypeName, &refnum, sizeof(ESM::RefNum)); } bool BasicSerializer::serialize(BinaryData& out, const sol::userdata& data) const { appendRefNum(out, cast(data)); return true; } bool BasicSerializer::deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const { if (typeName != sRefNumTypeName) return false; ESM::RefNum refnum = loadRefNum(binaryData); if (mAdjustContentFilesIndexFn) refnum.mContentFile = mAdjustContentFilesIndexFn(refnum.mContentFile); sol::stack::push(lua, refnum); return true; } ESM::RefNum UserdataSerializer::loadRefNum(std::string_view data) { if (data.size() != sizeof(ESM::RefNum)) throw std::runtime_error("Incorrect serialization format. Size of RefNum doesn't match."); ESM::RefNum refnum; std::memcpy(&refnum, data.data(), sizeof(ESM::RefNum)); refnum.mIndex = Misc::fromLittleEndian(refnum.mIndex); refnum.mContentFile = Misc::fromLittleEndian(refnum.mContentFile); return refnum; } static void serializeUserdata(BinaryData& out, const sol::userdata& data, const UserdataSerializer* customSerializer) { if (data.is()) { appendType(out, SerializedType::VEC2); osg::Vec2f v = data.as(); appendValue(out, v.x()); appendValue(out, v.y()); return; } if (data.is()) { appendType(out, SerializedType::VEC3); osg::Vec3f v = data.as(); appendValue(out, v.x()); appendValue(out, v.y()); appendValue(out, v.z()); return; } if (data.is()) { appendType(out, SerializedType::TRANSFORM_M); osg::Matrixf matrix = data.as().mM; for (size_t i = 0; i < 4; i++) for (size_t j = 0; j < 4; j++) appendValue(out, matrix(i,j)); return; } if (data.is()) { appendType(out, SerializedType::TRANSFORM_Q); osg::Quat quat = data.as().mQ; for(size_t i = 0; i < 4; i++) appendValue(out, quat[i]); return; } if (data.is()) { appendType(out, SerializedType::VEC4); osg::Vec4f v = data.as(); appendValue(out, v.x()); appendValue(out, v.y()); appendValue(out, v.z()); appendValue(out, v.w()); return; } if (data.is()) { appendType(out, SerializedType::COLOR); Misc::Color v = data.as (); appendValue(out, v.r()); appendValue(out, v.g()); appendValue(out, v.b()); appendValue(out, v.a()); return; } if (customSerializer && customSerializer->serialize(out, data)) return; else throw std::runtime_error("Value is not serializable."); } static void serialize(BinaryData& out, const sol::object& obj, const UserdataSerializer* customSerializer, int recursionCounter) { if (obj.get_type() == sol::type::lightuserdata) throw std::runtime_error("Light userdata is not allowed to be serialized."); if (obj.is()) throw std::runtime_error("Functions are not allowed to be serialized."); else if (obj.is()) serializeUserdata(out, obj, customSerializer); else if (obj.is()) { if (recursionCounter >= 32) throw std::runtime_error("Can not serialize more than 32 nested tables. Likely the table contains itself."); sol::table table = obj; appendType(out, SerializedType::TABLE_START); for (auto& [key, value] : table) { serialize(out, key, customSerializer, recursionCounter + 1); serialize(out, value, customSerializer, recursionCounter + 1); } appendType(out, SerializedType::TABLE_END); } else if (obj.is()) { appendType(out, SerializedType::NUMBER); appendValue(out, obj.as()); } else if (obj.is()) appendString(out, obj.as()); else if (obj.is()) { char v = obj.as() ? 1 : 0; appendType(out, SerializedType::BOOLEAN); out.push_back(v); } else throw std::runtime_error("Unknown Lua type."); } static void deserializeImpl(lua_State* lua, std::string_view& binaryData, const UserdataSerializer* customSerializer, bool readOnly) { if (binaryData.empty()) throw std::runtime_error("Unexpected end of serialized data."); unsigned char type = binaryData[0]; binaryData = binaryData.substr(1); if (type & (CUSTOM_COMPACT_FLAG | CUSTOM_FULL_FLAG)) { size_t typeNameSize, dataSize; if (type & CUSTOM_COMPACT_FLAG) { // Compact form: 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1). typeNameSize = (type & 7) + 1; dataSize = (type >> 3) & 15; } else { // Full form: 0b01TTTTTT + 32bit dataSize. typeNameSize = (type & 63) + 1; dataSize = getValue(binaryData); } std::string_view typeName = binaryData.substr(0, typeNameSize); std::string_view data = binaryData.substr(typeNameSize, dataSize); binaryData = binaryData.substr(typeNameSize + dataSize); if (!customSerializer || !customSerializer->deserialize(typeName, data, lua)) throw std::runtime_error("Unknown type in serialized data: " + std::string(typeName)); return; } if (type & SHORT_STRING_FLAG) { size_t size = type & 0x1f; sol::stack::push(lua, binaryData.substr(0, size)); binaryData = binaryData.substr(size); return; } switch (static_cast(type)) { case SerializedType::NUMBER: sol::stack::push(lua, getValue(binaryData)); return; case SerializedType::BOOLEAN: sol::stack::push(lua, getValue(binaryData) != 0); return; case SerializedType::LONG_STRING: { uint32_t size = getValue(binaryData); sol::stack::push(lua, binaryData.substr(0, size)); binaryData = binaryData.substr(size); return; } case SerializedType::TABLE_START: { lua_createtable(lua, 0, 0); while (!binaryData.empty() && binaryData[0] != char(SerializedType::TABLE_END)) { deserializeImpl(lua, binaryData, customSerializer, readOnly); deserializeImpl(lua, binaryData, customSerializer, readOnly); lua_settable(lua, -3); } if (binaryData.empty()) throw std::runtime_error("Unexpected end of serialized data."); binaryData = binaryData.substr(1); if (readOnly) sol::stack::push(lua, makeReadOnly(sol::stack::pop(lua))); return; } case SerializedType::TABLE_END: throw std::runtime_error("Unexpected end of table during deserialization."); case SerializedType::VEC2: { float x = getValue(binaryData); float y = getValue(binaryData); sol::stack::push(lua, osg::Vec2f(x, y)); return; } case SerializedType::VEC3: { float x = getValue(binaryData); float y = getValue(binaryData); float z = getValue(binaryData); sol::stack::push(lua, osg::Vec3f(x, y, z)); return; } case SerializedType::TRANSFORM_M: { osg::Matrixf mat; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) mat(i, j) = getValue(binaryData); sol::stack::push(lua, asTransform(mat)); return; } case SerializedType::TRANSFORM_Q: { osg::Quat q; for (int i = 0; i < 4; i++) q[i] = getValue(binaryData); sol::stack::push(lua, asTransform(q)); return; } case SerializedType::VEC4: { float x = getValue(binaryData); float y = getValue(binaryData); float z = getValue(binaryData); float w = getValue(binaryData); sol::stack::push(lua, osg::Vec4f(x, y, z, w)); return; } case SerializedType::COLOR: { float r = getValue(binaryData); float g = getValue(binaryData); float b = getValue(binaryData); float a = getValue(binaryData); sol::stack::push(lua, Misc::Color(r, g, b, a)); return; } } throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type)); } BinaryData serialize(const sol::object& obj, const UserdataSerializer* customSerializer) { if (obj == sol::nil) return ""; BinaryData res; res.push_back(FORMAT_VERSION); serialize(res, obj, customSerializer, 0); return res; } sol::object deserialize(lua_State* lua, std::string_view binaryData, const UserdataSerializer* customSerializer, bool readOnly) { if (binaryData.empty()) return sol::nil; if (binaryData[0] != FORMAT_VERSION) throw std::runtime_error("Incorrect version of Lua serialization format: " + std::to_string(static_cast(binaryData[0]))); binaryData = binaryData.substr(1); deserializeImpl(lua, binaryData, customSerializer, readOnly); if (!binaryData.empty()) throw std::runtime_error("Unexpected data after serialized object"); return sol::stack::pop(lua); } } openmw-openmw-0.48.0/components/lua/serialization.hpp000066400000000000000000000043701445372753700227740ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_SERIALIZATION_H #define COMPONENTS_LUA_SERIALIZATION_H #include #include namespace LuaUtil { // Note: it can contain \0 using BinaryData = std::string; class UserdataSerializer { public: virtual ~UserdataSerializer() {} // Appends serialized sol::userdata to the end of BinaryData. // Returns false if this type of userdata is not supported by this serializer. virtual bool serialize(BinaryData&, const sol::userdata&) const = 0; // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. // Returns false if this type is not supported by this serializer. virtual bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State*) const = 0; protected: static void append(BinaryData&, std::string_view typeName, const void* data, size_t dataSize); static constexpr std::string_view sRefNumTypeName = "o"; static void appendRefNum(BinaryData&, ESM::RefNum); static ESM::RefNum loadRefNum(std::string_view data); }; // Serializer that can load Lua data from content files and saved games, but doesn't depend on apps/openmw. // Instead of LObject/GObject (that are defined in apps/openmw) it loads refnums directly as ESM::RefNum. class BasicSerializer final : public UserdataSerializer { public: BasicSerializer() = default; explicit BasicSerializer(std::function adjustContentFileIndexFn) : mAdjustContentFilesIndexFn(std::move(adjustContentFileIndexFn)) {} private: bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override; bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override; std::function mAdjustContentFilesIndexFn; }; BinaryData serialize(const sol::object&, const UserdataSerializer* customSerializer = nullptr); sol::object deserialize(lua_State* lua, std::string_view binaryData, const UserdataSerializer* customSerializer = nullptr, bool readOnly = false); } #endif // COMPONENTS_LUA_SERIALIZATION_H openmw-openmw-0.48.0/components/lua/storage.cpp000066400000000000000000000177511445372753700215650ustar00rootroot00000000000000#include "storage.hpp" #include #include #include namespace sol { template <> struct is_automagical : std::false_type {}; } namespace LuaUtil { LuaStorage::Value LuaStorage::Section::sEmpty; sol::object LuaStorage::Value::getCopy(lua_State* L) const { return deserialize(L, mSerializedValue); } sol::object LuaStorage::Value::getReadOnly(lua_State* L) const { if (mReadOnlyValue == sol::nil && !mSerializedValue.empty()) mReadOnlyValue = deserialize(L, mSerializedValue, nullptr, true); return mReadOnlyValue; } const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const { auto it = mValues.find(key); if (it != mValues.end()) return it->second; else return sEmpty; } void LuaStorage::Section::runCallbacks(sol::optional changedKey) { mStorage->mRunningCallbacks.insert(this); mCallbacks.erase(std::remove_if(mCallbacks.begin(), mCallbacks.end(), [&](const Callback& callback) { bool valid = callback.isValid(); if (valid) callback.tryCall(mSectionName, changedKey); return !valid; }), mCallbacks.end()); mStorage->mRunningCallbacks.erase(this); } void LuaStorage::Section::throwIfCallbackRecursionIsTooDeep() { if (mStorage->mRunningCallbacks.count(this) > 0) throw std::runtime_error("Storage handler shouldn't change the storage section it handles (leads to an infinite recursion)"); if (mStorage->mRunningCallbacks.size() > 10) throw std::runtime_error("Too many subscribe callbacks triggering in a chain, likely an infinite recursion"); } void LuaStorage::Section::set(std::string_view key, const sol::object& value) { throwIfCallbackRecursionIsTooDeep(); if (value != sol::nil) mValues[std::string(key)] = Value(value); else { auto it = mValues.find(key); if (it != mValues.end()) mValues.erase(it); } if (mStorage->mListener) mStorage->mListener->valueChanged(mSectionName, key, value); runCallbacks(key); } void LuaStorage::Section::setAll(const sol::optional& values) { throwIfCallbackRecursionIsTooDeep(); mValues.clear(); if (values) { for (const auto& [k, v] : *values) mValues[cast(k)] = Value(v); } if (mStorage->mListener) mStorage->mListener->sectionReplaced(mSectionName, values); runCallbacks(sol::nullopt); } sol::table LuaStorage::Section::asTable() { sol::table res(mStorage->mLua, sol::create); for (const auto& [k, v] : mValues) res[k] = v.getCopy(mStorage->mLua); return res; } void LuaStorage::initLuaBindings(lua_State* L) { sol::state_view lua(L); sol::usertype sview = lua.new_usertype("Section"); sview["get"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getReadOnly(s); }; sview["getCopy"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getCopy(s); }; sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); }; sview["subscribe"] = [](const SectionView& section, const sol::table& callback) { std::vector& callbacks = section.mSection->mCallbacks; if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) { callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [&](const Callback& c) { return !c.isValid(); }), callbacks.end()); } callbacks.push_back(Callback::fromLua(callback)); }; sview["reset"] = [](const SectionView& section, const sol::optional& newValues) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->setAll(newValues); }; sview["removeOnExit"] = [](const SectionView& section) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->mPermanent = false; }; sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->set(key, value); }; } void LuaStorage::clearTemporaryAndRemoveCallbacks() { auto it = mData.begin(); while (it != mData.end()) { it->second->mCallbacks.clear(); if (!it->second->mPermanent) { it->second->mValues.clear(); it = mData.erase(it); } else ++it; } } void LuaStorage::load(const boost::filesystem::path& path) { assert(mData.empty()); // Shouldn't be used before loading try { Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << boost::filesystem::file_size(path) << " bytes)"; boost::filesystem::ifstream fin(path, boost::filesystem::fstream::binary); std::string serializedData((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); sol::table data = deserialize(mLua, serializedData); for (const auto& [sectionName, sectionTable] : data) { const std::shared_ptr
& section = getSection(cast(sectionName)); for (const auto& [key, value] : cast(sectionTable)) section->set(cast(key), value); } } catch (std::exception& e) { Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what(); } } void LuaStorage::save(const boost::filesystem::path& path) const { sol::table data(mLua, sol::create); for (const auto& [sectionName, section] : mData) { if (section->mPermanent && !section->mValues.empty()) data[sectionName] = section->asTable(); } std::string serializedData = serialize(data); Log(Debug::Info) << "Saving Lua storage \"" << path << "\" (" << serializedData.size() << " bytes)"; boost::filesystem::ofstream fout(path, boost::filesystem::ofstream::binary); fout.write(serializedData.data(), serializedData.size()); fout.close(); } const std::shared_ptr& LuaStorage::getSection(std::string_view sectionName) { auto it = mData.find(sectionName); if (it != mData.end()) return it->second; auto section = std::make_shared
(this, std::string(sectionName)); sectionName = section->mSectionName; auto [newIt, _] = mData.emplace(sectionName, std::move(section)); return newIt->second; } sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly) { const std::shared_ptr
& section = getSection(sectionName); return sol::make_object(mLua, SectionView{section, readOnly}); } sol::table LuaStorage::getAllSections(bool readOnly) { sol::table res(mLua, sol::create); for (const auto& [sectionName, _] : mData) res[sectionName] = getSection(sectionName, readOnly); return res; } } openmw-openmw-0.48.0/components/lua/storage.hpp000066400000000000000000000062121445372753700215600ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_STORAGE_H #define COMPONENTS_LUA_STORAGE_H #include #include #include #include "asyncpackage.hpp" #include "serialization.hpp" namespace LuaUtil { class LuaStorage { public: static void initLuaBindings(lua_State*); explicit LuaStorage(lua_State* lua) : mLua(lua) {} void clearTemporaryAndRemoveCallbacks(); void load(const boost::filesystem::path& path); void save(const boost::filesystem::path& path) const; sol::object getSection(std::string_view sectionName, bool readOnly); sol::object getMutableSection(std::string_view sectionName) { return getSection(sectionName, false); } sol::object getReadOnlySection(std::string_view sectionName) { return getSection(sectionName, true); } sol::table getAllSections(bool readOnly = false); void setSingleValue(std::string_view section, std::string_view key, const sol::object& value) { getSection(section)->set(key, value); } void setSectionValues(std::string_view section, const sol::optional& values) { getSection(section)->setAll(values); } class Listener { public: virtual void valueChanged(std::string_view section, std::string_view key, const sol::object& value) const = 0; virtual void sectionReplaced(std::string_view section, const sol::optional& values) const = 0; }; void setListener(const Listener* listener) { mListener = listener; } private: class Value { public: Value() {} Value(const sol::object& value) : mSerializedValue(serialize(value)) {} sol::object getCopy(lua_State* L) const; sol::object getReadOnly(lua_State* L) const; private: std::string mSerializedValue; mutable sol::object mReadOnlyValue = sol::nil; }; struct Section { explicit Section(LuaStorage* storage, std::string name) : mStorage(storage), mSectionName(std::move(name)) {} const Value& get(std::string_view key) const; void set(std::string_view key, const sol::object& value); void setAll(const sol::optional& values); sol::table asTable(); void runCallbacks(sol::optional changedKey); void throwIfCallbackRecursionIsTooDeep(); LuaStorage* mStorage; std::string mSectionName; std::map> mValues; std::vector mCallbacks; bool mPermanent = true; static Value sEmpty; }; struct SectionView { std::shared_ptr
mSection; bool mReadOnly; }; const std::shared_ptr
& getSection(std::string_view sectionName); lua_State* mLua; std::map> mData; const Listener* mListener = nullptr; std::set mRunningCallbacks; }; } #endif // COMPONENTS_LUA_STORAGE_H openmw-openmw-0.48.0/components/lua/utilpackage.cpp000066400000000000000000000277401445372753700224110ustar00rootroot00000000000000#include "utilpackage.hpp" #include #include #include #include #include #include "luastate.hpp" namespace sol { template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; } namespace LuaUtil { namespace { template void addVectorMethods(sol::usertype& vectorType) { vectorType[sol::meta_function::unary_minus] = [](const T& a) { return -a; }; vectorType[sol::meta_function::addition] = [](const T& a, const T& b) { return a + b; }; vectorType[sol::meta_function::subtraction] = [](const T& a, const T& b) { return a - b; }; vectorType[sol::meta_function::equal_to] = [](const T& a, const T& b) { return a == b; }; vectorType[sol::meta_function::multiplication] = sol::overload( [](const T& a, float c) { return a * c; }, [](const T& a, const T& b) { return a * b; }); vectorType[sol::meta_function::division] = [](const T& a, float c) { return a / c; }; vectorType["dot"] = [](const T& a, const T b) { return a * b; }; vectorType["length"] = &T::length; vectorType["length2"] = &T::length2; vectorType["normalize"] = [](const T& v) { float len = v.length(); if (len == 0) return std::make_tuple(T(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; vectorType["emul"] = [](const T& a, const T& b) { T result; for (int i = 0; i < T::num_components; ++i) result[i] = a[i] * b[i]; return result; }; vectorType["ediv"] = [](const T& a, const T& b) { T result; for (int i = 0; i < T::num_components; ++i) result[i] = a[i] / b[i]; return result; }; vectorType[sol::meta_function::to_string] = [](const T& v) { std::stringstream ss; ss << "(" << v[0]; for (int i = 1; i < T::num_components; ++i) ss << ", " << v[i]; ss << ")"; return ss.str(); }; } } sol::table initUtilPackage(sol::state& lua) { sol::table util(lua, sol::create); // Lua bindings for Vec2 util["vector2"] = [](float x, float y) { return Vec2(x, y); }; sol::usertype vec2Type = lua.new_usertype("Vec2"); vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); addVectorMethods(vec2Type); vec2Type["rotate"] = &Misc::rotateVec2f; // Lua bindings for Vec3 util["vector3"] = [](float x, float y, float z) { return Vec3(x, y, z); }; sol::usertype vec3Type = lua.new_usertype("Vec3"); vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); addVectorMethods(vec3Type); vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; // Lua bindings for Vec4 util["vector4"] = [](float x, float y, float z, float w) { return Vec4(x, y, z, w); }; sol::usertype vec4Type = lua.new_usertype("Vec4"); vec4Type["x"] = sol::readonly_property([](const Vec4& v) -> float { return v.x(); }); vec4Type["y"] = sol::readonly_property([](const Vec4& v) -> float { return v.y(); }); vec4Type["z"] = sol::readonly_property([](const Vec4& v) -> float { return v.z(); }); vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); addVectorMethods(vec4Type); // Lua bindings for Color sol::usertype colorType = lua.new_usertype("Color"); colorType["r"] = sol::readonly_property([](const Misc::Color& c) { return c.r(); }); colorType["g"] = sol::readonly_property([](const Misc::Color& c) { return c.g(); }); colorType["b"] = sol::readonly_property([](const Misc::Color& c) { return c.b(); }); colorType["a"] = sol::readonly_property([](const Misc::Color& c) { return c.a(); }); colorType[sol::meta_function::to_string] = [](const Misc::Color& c) { return c.toString(); }; colorType["asRgba"] = [](const Misc::Color& c) { return Vec4(c.r(), c.g(), c.b(), c.a()); }; colorType["asRgb"] = [](const Misc::Color& c) { return Vec3(c.r(), c.g(), c.b()); }; colorType["asHex"] = [](const Misc::Color& c) { return c.toHex(); }; sol::table color(lua, sol::create); color["rgba"] = [](float r, float g, float b, float a) { return Misc::Color(r, g, b, a); }; color["rgb"] = [](float r, float g, float b) { return Misc::Color(r, g, b, 1); }; color["hex"] = [](std::string_view hex) { return Misc::Color::fromHex(hex); }; util["color"] = LuaUtil::makeReadOnly(color); // Lua bindings for Transform sol::usertype transMType = lua.new_usertype("TransformM"); sol::usertype transQType = lua.new_usertype("TransformQ"); sol::table transforms(lua, sol::create); util["transform"] = LuaUtil::makeReadOnly(transforms); transforms["identity"] = sol::make_object(lua, TransformM{osg::Matrixf::identity()}); transforms["move"] = sol::overload( [](const Vec3& v) { return TransformM{osg::Matrixf::translate(v)}; }, [](float x, float y, float z) { return TransformM{osg::Matrixf::translate(x, y, z)}; }); transforms["scale"] = sol::overload( [](const Vec3& v) { return TransformM{osg::Matrixf::scale(v)}; }, [](float x, float y, float z) { return TransformM{osg::Matrixf::scale(x, y, z)}; }); transforms["rotate"] = [](float angle, const Vec3& axis) { return TransformQ{osg::Quat(angle, axis)}; }; transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(-1, 0, 0))}; }; transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, -1, 0))}; }; transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, -1))}; }; transMType[sol::meta_function::multiplication] = sol::overload( [](const TransformM& a, const Vec3& b) { return a.mM.preMult(b); }, [](const TransformM& a, const TransformM& b) { return TransformM{b.mM * a.mM}; }, [](const TransformM& a, const TransformQ& b) { TransformM res{a.mM}; res.mM.preMultRotate(b.mQ); return res; }); transMType[sol::meta_function::to_string] = [](const TransformM& m) { osg::Vec3f trans, scale; osg::Quat rotation, so; m.mM.decompose(trans, rotation, scale, so); osg::Quat::value_type rot_angle, so_angle; osg::Vec3f rot_axis, so_axis; rotation.getRotate(rot_angle, rot_axis); so.getRotate(so_angle, so_axis); std::stringstream ss; ss << "TransformM{ "; if (trans.length2() > 0) ss << "move(" << trans.x() << ", " << trans.y() << ", " << trans.z() << ") "; if (rot_angle != 0) ss << "rotation(angle=" << rot_angle << ", axis=(" << rot_axis.x() << ", " << rot_axis.y() << ", " << rot_axis.z() << ")) "; if (scale.x() != 1 || scale.y() != 1 || scale.z() != 1) ss << "scale(" << scale.x() << ", " << scale.y() << ", " << scale.z() << ") "; if (so_angle != 0) ss << "rotation(angle=" << so_angle << ", axis=(" << so_axis.x() << ", " << so_axis.y() << ", " << so_axis.z() << ")) "; ss << "}"; return ss.str(); }; transMType["inverse"] = [](const TransformM& m) { TransformM res; if (!res.mM.invert_4x3(m.mM)) throw std::runtime_error("This Transform is not invertible"); return res; }; transQType[sol::meta_function::multiplication] = sol::overload( [](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, [](const TransformQ& a, const TransformQ& b) { return TransformQ{b.mQ * a.mQ}; }, [](const TransformQ& a, const TransformM& b) { TransformM res{b}; res.mM.postMultRotate(a.mQ); return res; }); transQType[sol::meta_function::to_string] = [](const TransformQ& q) { osg::Quat::value_type angle; osg::Vec3f axis; q.mQ.getRotate(angle, axis); std::stringstream ss; ss << "TransformQ{ rotation(angle=" << angle << ", axis=(" << axis.x() << ", " << axis.y() << ", " << axis.z() << ")) }"; return ss.str(); }; transQType["inverse"] = [](const TransformQ& q) { return TransformQ{q.mQ.inverse()}; }; // Utility functions util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' util["normalizeAngle"] = &Misc::normalizeAngle; util["makeReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/false); }; util["makeStrictReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/true); }; if (lua["bit32"] != sol::nil) { sol::table bit = lua["bit32"]; util["bitOr"] = bit["bor"]; util["bitAnd"] = bit["band"]; util["bitXor"] = bit["bxor"]; util["bitNot"] = bit["bnot"]; } else { util["bitOr"] = [](unsigned a, sol::variadic_args va) { for (const auto& v : va) a |= cast(v); return a; }; util["bitAnd"] = [](unsigned a, sol::variadic_args va) { for (const auto& v : va) a &= cast(v); return a; }; util["bitXor"] = [](unsigned a, sol::variadic_args va) { for (const auto& v : va) a ^= cast(v); return a; }; util["bitNot"] = [](unsigned a) { return ~a; }; } util["loadCode"] = [](const std::string& code, const sol::table& env, sol::this_state s) { sol::state_view lua(s); sol::load_result res = lua.load(code, "", sol::load_mode::text); if (!res.valid()) throw std::runtime_error("Lua error: " + res.get()); sol::function fn = res; sol::environment newEnv(lua, sol::create, env); newEnv[sol::metatable_key][sol::meta_function::new_index] = env; sol::set_environment(newEnv, fn); return fn; }; return util; } } openmw-openmw-0.48.0/components/lua/utilpackage.hpp000066400000000000000000000014221445372753700224030ustar00rootroot00000000000000#ifndef COMPONENTS_LUA_UTILPACKAGE_H #define COMPONENTS_LUA_UTILPACKAGE_H #include #include #include #include #include namespace LuaUtil { using Vec2 = osg::Vec2f; using Vec3 = osg::Vec3f; using Vec4 = osg::Vec4f; // For performance reasons "Transform" is implemented as 2 types with the same interface. // Transform supports only composition, inversion, and applying to a 3d vector. struct TransformM { osg::Matrixf mM; }; struct TransformQ { osg::Quat mQ; }; inline TransformM asTransform(const osg::Matrixf& m) { return {m}; } inline TransformQ asTransform(const osg::Quat& q) { return {q}; } sol::table initUtilPackage(sol::state&); } #endif // COMPONENTS_LUA_UTILPACKAGE_H openmw-openmw-0.48.0/components/lua_ui/000077500000000000000000000000001445372753700200775ustar00rootroot00000000000000openmw-openmw-0.48.0/components/lua_ui/adapter.cpp000066400000000000000000000026071445372753700222300ustar00rootroot00000000000000#include "adapter.hpp" #include #include "element.hpp" #include "container.hpp" namespace LuaUi { namespace { sol::state luaState; } LuaAdapter::LuaAdapter() : mElement(nullptr) , mContainer(nullptr) { mContainer = MyGUI::Gui::getInstancePtr()->createWidget( "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); mContainer->initialize(luaState, mContainer); mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { setSize(coord.size()); }); mContainer->widget()->attachToWidget(this); } void LuaAdapter::attach(const std::shared_ptr& element) { detachElement(); mElement = element; attachElement(); setSize(mContainer->widget()->getSize()); // workaround for MyGUI bug // parent visibility doesn't affect added children setVisible(!getVisible()); setVisible(!getVisible()); } void LuaAdapter::detach() { detachElement(); setSize({ 0, 0 }); } void LuaAdapter::attachElement() { if (mElement.get()) mElement->attachToWidget(mContainer); } void LuaAdapter::detachElement() { if (mElement.get()) mElement->detachFromWidget(); mElement = nullptr; } } openmw-openmw-0.48.0/components/lua_ui/adapter.hpp000066400000000000000000000011371445372753700222320ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_ADAPTER #define OPENMW_LUAUI_ADAPTER #include #include namespace LuaUi { class LuaContainer; struct Element; class LuaAdapter : public MyGUI::Widget { MYGUI_RTTI_DERIVED(LuaAdapter) public: LuaAdapter(); void attach(const std::shared_ptr& element); void detach(); private: std::shared_ptr mElement; LuaContainer* mContainer; void attachElement(); void detachElement(); }; } #endif // !OPENMW_LUAUI_ADAPTER openmw-openmw-0.48.0/components/lua_ui/alignment.cpp000066400000000000000000000010341445372753700225570ustar00rootroot00000000000000#include "alignment.hpp" namespace LuaUi { MyGUI::Align alignmentToMyGui(Alignment horizontal, Alignment vertical) { MyGUI::Align align(MyGUI::Align::Center); if (horizontal == Alignment::Start) align |= MyGUI::Align::Left; if (horizontal == Alignment::End) align |= MyGUI::Align::Right; if (vertical == Alignment::Start) align |= MyGUI::Align::Top; if (vertical == Alignment::End) align |= MyGUI::Align::Bottom; return align; } } openmw-openmw-0.48.0/components/lua_ui/alignment.hpp000066400000000000000000000004741445372753700225730ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_ALIGNMENT #define OPENMW_LUAUI_ALIGNMENT #include namespace LuaUi { enum class Alignment { Start = 0, Center = 1, End = 2 }; MyGUI::Align alignmentToMyGui(Alignment horizontal, Alignment vertical); } #endif // !OPENMW_LUAUI_PROPERTIES openmw-openmw-0.48.0/components/lua_ui/container.cpp000066400000000000000000000025341445372753700225710ustar00rootroot00000000000000#include "container.hpp" #include namespace LuaUi { void LuaContainer::updateChildren() { WidgetExtension::updateChildren(); updateSizeToFit(); } MyGUI::IntSize LuaContainer::childScalingSize() { return MyGUI::IntSize(); } MyGUI::IntSize LuaContainer::templateScalingSize() { return mInnerSize; } void LuaContainer::updateSizeToFit() { MyGUI::IntSize innerSize = MyGUI::IntSize(); for (auto w : children()) { MyGUI::IntCoord coord = w->calculateCoord(); innerSize.width = std::max(innerSize.width, coord.left + coord.width); innerSize.height = std::max(innerSize.height, coord.top + coord.height); } MyGUI::IntSize outerSize = innerSize; for (auto w : templateChildren()) { MyGUI::IntCoord coord = w->calculateCoord(); outerSize.width = std::max(outerSize.width, coord.left + coord.width); outerSize.height = std::max(outerSize.height, coord.top + coord.height); } mInnerSize = innerSize; mOuterSize = outerSize; } MyGUI::IntSize LuaContainer::calculateSize() { return mOuterSize; } void LuaContainer::updateCoord() { updateSizeToFit(); WidgetExtension::updateCoord(); } } openmw-openmw-0.48.0/components/lua_ui/container.hpp000066400000000000000000000012271445372753700225740ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_CONTAINER #define OPENMW_LUAUI_CONTAINER #include "widget.hpp" namespace LuaUi { class LuaContainer : public WidgetExtension, public MyGUI::Widget { MYGUI_RTTI_DERIVED(LuaContainer) MyGUI::IntSize calculateSize() override; void updateCoord() override; protected: void updateChildren() override; MyGUI::IntSize childScalingSize() override; MyGUI::IntSize templateScalingSize() override; private: void updateSizeToFit(); MyGUI::IntSize mInnerSize; MyGUI::IntSize mOuterSize; }; } #endif // !OPENMW_LUAUI_CONTAINER openmw-openmw-0.48.0/components/lua_ui/content.cpp000066400000000000000000000014361445372753700222610ustar00rootroot00000000000000#include "content.hpp" namespace LuaUi { sol::protected_function loadContentConstructor(LuaUtil::LuaState* state) { sol::function loader = state->loadInternalLib("content"); sol::set_environment(state->newInternalLibEnvironment(), loader); sol::table metatable = loader().get(); if (metatable["new"].get_type() != sol::type::function) throw std::logic_error("Expected function"); return metatable["new"].get(); } bool isValidContent(const sol::object& object) { if (object.get_type() != sol::type::table) return false; sol::table table = object; return table.traverse_get>(sol::metatable_key, "__Content").value_or(false); } } openmw-openmw-0.48.0/components/lua_ui/content.hpp000066400000000000000000000067541445372753700222760ustar00rootroot00000000000000#ifndef COMPONENTS_LUAUI_CONTENT #define COMPONENTS_LUAUI_CONTENT #include #include #include #include namespace LuaUi { sol::protected_function loadContentConstructor(LuaUtil::LuaState* state); bool isValidContent(const sol::object& object); class ContentView { public: // accepts only Lua tables returned by ui.content explicit ContentView(sol::table table) : mTable(std::move(table)) { if (!isValidContent(mTable)) throw std::domain_error("Expected a Content table"); } size_t size() const { return mTable.size(); } void assign(std::string_view name, const sol::table& table) { if (indexOf(name).has_value()) mTable[name] = table; else throw std::domain_error("Invalid Content key"); } void assign(size_t index, const sol::table& table) { if (index <= size()) mTable[toLua(index)] = table; else throw std::range_error("Invalid Content index"); } void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); } sol::table at(size_t index) const { if (index < size()) return mTable.get(toLua(index)); else throw std::range_error("Invalid Content index"); } sol::table at(std::string_view name) const { if (indexOf(name).has_value()) return mTable.get(name); else throw std::range_error("Invalid Content key"); } void remove(size_t index) { if (index < size()) // for some reason mTable[key] = value doesn't call __newindex getMetatable()[sol::meta_function::new_index].get()( mTable, toLua(index), sol::nil); else throw std::range_error("Invalid Content index"); } void remove(std::string_view name) { auto index = indexOf(name); if (index.has_value()) remove(index.value()); else throw std::domain_error("Invalid Content key"); } std::optional indexOf(std::string_view name) const { sol::object result = callMethod("indexOf", name); if (result.is()) return fromLua(LuaUtil::cast(result)); else return std::nullopt; } std::optional indexOf(const sol::table& table) const { sol::object result = callMethod("indexOf", table); if (result.is()) return fromLua(LuaUtil::cast(result)); else return std::nullopt; } sol::table getMetatable() const { return mTable[sol::metatable_key].get(); } private: sol::table mTable; template sol::object callMethod(std::string_view name, Arg&&... arg) const { return mTable.get(name)(mTable, arg...); } static inline size_t toLua(size_t index) { return index + 1; } static inline size_t fromLua(size_t index) { return index - 1; } }; } #endif // COMPONENTS_LUAUI_CONTENT openmw-openmw-0.48.0/components/lua_ui/content.lua000066400000000000000000000070471445372753700222640ustar00rootroot00000000000000local M = {} M.__Content = true M.new = function(source) local result = {} result.__nameIndex = {} for i, v in ipairs(source) do if type(v) ~= 'table' then error('Content can only contain tables') end result[i] = v if type(v.name) == 'string' then result.__nameIndex[v.name] = i end end return setmetatable(result, M) end local function validateIndex(self, index) if type(index) ~= 'number' then error('Unexpected Content key: ' .. tostring(index)) end if index < 1 or (#self + 1) < index then error('Invalid Content index: ' .. tostring(index)) end end local function getIndexFromKey(self, key) local index = key if type(key) == 'string' then index = self.__nameIndex[key] if not index then error('Unexpected content key:' .. key) end end validateIndex(self, index) return index end local methods = { insert = function(self, index, value) validateIndex(self, index) if type(value) ~= 'table' then error('Content can only contain tables') end for i = #self, index, -1 do rawset(self, i + 1, rawget(self, i)) local name = rawget(self, i + 1) if name then self.__nameIndex[name] = i + 1 end end rawset(self, index, value) if value.name then self.__nameIndex[value.name] = index end end, indexOf = function(self, value) if type(value) == 'string' then return self.__nameIndex[value] elseif type(value) == 'table' then for i = 1, #self do if rawget(self, i) == value then return i end end end return nil end, add = function(self, value) self:insert(#self + 1, value) return #self end, } M.__index = function(self, key) if methods[key] then return methods[key] end local index = getIndexFromKey(self, key) return rawget(self, index) end local function nameAt(self, index) local v = rawget(self, index) return v and type(v.name) == 'string' and v.name end local function remove(self, index) local oldName = nameAt(self, index) if oldName then self.__nameIndex[oldName] = nil end if index > #self then error('Invalid Content index:' .. tostring(index)) end for i = index, #self - 1 do local v = rawget(self, i + 1) rawset(self, i, v) if type(v.name) == 'string' then self.__nameIndex[v.name] = i end end rawset(self, #self, nil) end local function assign(self, index, value) local oldName = nameAt(self, index) if oldName then self.__nameIndex[oldName] = nil end rawset(self, index, value) if value.name then self.__nameIndex[value.name] = index end end M.__newindex = function(self, key, value) local index = getIndexFromKey(self, key) if value == nil then remove(self, index) elseif type(value) == 'table' then assign(self, index, value) else error('Content can only contain tables') end end M.__tostring = function(self) return ('UiContent{%d layouts}'):format(#self) end local function next(self, index) local v = rawget(self, index) if v then return index + 1, v else return nil, nil end end M.__pairs = function(self) return next, self, 1 end M.__ipairs = M.__pairs M.__metatable = false return M openmw-openmw-0.48.0/components/lua_ui/element.cpp000066400000000000000000000226441445372753700222440ustar00rootroot00000000000000#include "element.hpp" #include #include "content.hpp" #include "util.hpp" #include "widget.hpp" namespace LuaUi { namespace { namespace LayoutKeys { constexpr std::string_view type = "type"; constexpr std::string_view name = "name"; constexpr std::string_view layer = "layer"; constexpr std::string_view templateLayout = "template"; constexpr std::string_view props = "props"; constexpr std::string_view events = "events"; constexpr std::string_view content = "content"; constexpr std::string_view external = "external"; } const std::string defaultWidgetType = "LuaWidget"; constexpr uint64_t maxDepth = 250; std::string widgetType(const sol::table& layout) { sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); if (templateTypeField != sol::nil) { std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); if (typeField != sol::nil && templateType != type) throw std::logic_error(std::string("Template layout type ") + type + std::string(" doesn't match template type ") + templateType); type = templateType; } return type; } void destroyWidget(LuaUi::WidgetExtension* ext) { ext->deinitialize(); MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); std::vector updateContent( const std::vector& children, const sol::object& contentObj, uint64_t depth) { ++depth; std::vector result; if (contentObj == sol::nil) { for (WidgetExtension* w : children) destroyWidget(w); return result; } ContentView content(LuaUtil::cast(contentObj)); result.resize(content.size()); size_t minSize = std::min(children.size(), content.size()); for (size_t i = 0; i < minSize; i++) { WidgetExtension* ext = children[i]; sol::table newLayout = content.at(i); if (ext->widget()->getTypeName() == widgetType(newLayout)) { updateWidget(ext, newLayout, depth); } else { destroyWidget(ext); ext = createWidget(newLayout, depth); } result[i] = ext; } for (size_t i = minSize; i < children.size(); i++) destroyWidget(children[i]); for (size_t i = minSize; i < content.size(); i++) result[i] = createWidget(content.at(i), depth); return result; } void setTemplate(WidgetExtension* ext, const sol::object& templateLayout, uint64_t depth) { ++depth; sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props); ext->setTemplateProperties(props); sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content); ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth)); } void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj) { ext->clearCallbacks(); if (eventsObj == sol::nil) return; if (!eventsObj.is()) throw std::logic_error("The \"events\" layout field must be a table of callbacks"); auto events = eventsObj.as(); events.for_each([ext](const sol::object& name, const sol::object& callback) { if (name.is() && LuaUtil::Callback::isLuaCallback(callback)) ext->setCallback(name.as(), LuaUtil::Callback::fromLua(callback)); else if (!name.is()) Log(Debug::Warning) << "UI event key must be a string"; else Log(Debug::Warning) << "UI event handler for key \"" << name.as() << "\" must be an openmw.async.callback"; }); } WidgetExtension* createWidget(const sol::table& layout, uint64_t depth) { static auto widgetTypeMap = widgetTypeToName(); std::string type = widgetType(layout); if (widgetTypeMap.find(type) == widgetTypeMap.end()) throw std::logic_error(std::string("Invalid widget type ") += type); std::string name = layout.get_or(LayoutKeys::name, std::string()); MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); ext->initialize(layout.lua_state(), widget); updateWidget(ext, layout, depth); return ext; } void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth) { if (depth >= maxDepth) throw std::runtime_error("Maximum layout depth exceeded, probably caused by a circular reference"); ext->reset(); ext->setLayout(layout); ext->setExternal(layout.get(LayoutKeys::external)); setTemplate(ext, layout.get(LayoutKeys::templateLayout), depth); ext->setProperties(layout.get(LayoutKeys::props)); setEventCallbacks(ext, layout.get(LayoutKeys::events)); ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content), depth)); ext->updateCoord(); } std::string setLayer(WidgetExtension* ext, const sol::table& layout) { MyGUI::ILayer* layerNode = ext->widget()->getLayer(); std::string currentLayer = layerNode ? layerNode->getName() : std::string(); std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); else if (newLayer != currentLayer) { MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); } return newLayer; } } std::map> Element::sAllElements; Element::Element(sol::table layout) : mRoot(nullptr) , mAttachedTo(nullptr) , mLayout(std::move(layout)) , mLayer() , mUpdate(false) , mDestroy(false) {} std::shared_ptr Element::make(sol::table layout) { std::shared_ptr ptr(new Element(std::move(layout))); sAllElements[ptr.get()] = ptr; return ptr; } void Element::create() { assert(!mRoot); if (!mRoot) { mRoot = createWidget(layout(), 0); mLayer = setLayer(mRoot, layout()); updateAttachment(); } } void Element::update() { if (mRoot && mUpdate) { if (mRoot->widget()->getTypeName() != widgetType(layout())) { destroyWidget(mRoot); mRoot = createWidget(layout(), 0); } else { updateWidget(mRoot, layout(), 0); } mLayer = setLayer(mRoot, layout()); updateAttachment(); } mUpdate = false; } void Element::destroy() { sAllElements.erase(this); if (!mRoot) return; destroyWidget(mRoot); mRoot = nullptr; mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } void Element::attachToWidget(WidgetExtension* w) { if (mAttachedTo) throw std::logic_error("A UI element can't be attached to two widgets at once"); mAttachedTo = w; updateAttachment(); } void Element::detachFromWidget() { if (mRoot) mRoot->widget()->detachFromWidget(); if (mAttachedTo) mAttachedTo->setChildren({}); mAttachedTo = nullptr; } void Element::updateAttachment() { if (!mRoot) return; if (mAttachedTo) { if (!mLayer.empty()) Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; mAttachedTo->setChildren({ mRoot }); mAttachedTo->updateCoord(); } } } openmw-openmw-0.48.0/components/lua_ui/element.hpp000066400000000000000000000017631445372753700222500ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_ELEMENT #define OPENMW_LUAUI_ELEMENT #include "widget.hpp" namespace LuaUi { struct Element { static std::shared_ptr make(sol::table layout); template static void forEach(Callback callback) { for(auto& [e, _] : sAllElements) callback(e); } WidgetExtension* mRoot; WidgetExtension* mAttachedTo; sol::object mLayout; std::string mLayer; bool mUpdate; bool mDestroy; void create(); void update(); void destroy(); friend void clearUserInterface(); void attachToWidget(WidgetExtension* w); void detachFromWidget(); private: Element(sol::table layout); sol::table layout() { return LuaUtil::cast(mLayout); } static std::map> sAllElements; void updateAttachment(); }; } #endif // !OPENMW_LUAUI_ELEMENT openmw-openmw-0.48.0/components/lua_ui/flex.cpp000066400000000000000000000065431445372753700215510ustar00rootroot00000000000000#include "flex.hpp" namespace LuaUi { void LuaFlex::updateProperties() { mHorizontal = propertyValue("horizontal", false); mAutoSized = propertyValue("autoSize", true); mAlign = propertyValue("align", Alignment::Start); mArrange = propertyValue("arrange", Alignment::Start); WidgetExtension::updateProperties(); } namespace { int alignSize(int container, int content, Alignment alignment) { int alignedPosition = 0; { switch (alignment) { case Alignment::Start: alignedPosition = 0; break; case Alignment::Center: alignedPosition = (container - content) / 2; break; case Alignment::End: alignedPosition = container - content; break; } } return alignedPosition; } float getGrow(WidgetExtension* w) { return std::max(0.0f, w->externalValue("grow", 0.0f)); } } void LuaFlex::updateChildren() { float totalGrow = 0; MyGUI::IntSize childrenSize; for (auto* w: children()) { w->clearForced(); MyGUI::IntSize size = w->calculateSize(); primary(childrenSize) += primary(size); secondary(childrenSize) = std::max(secondary(childrenSize), secondary(size)); totalGrow += getGrow(w); } mChildrenSize = childrenSize; MyGUI::IntSize flexSize = calculateSize(); int growSize = 0; float growFactor = 0; if (totalGrow > 0) { growSize = primary(flexSize) - primary(childrenSize); growFactor = growSize / totalGrow; } MyGUI::IntPoint childPosition; primary(childPosition) = alignSize(primary(flexSize) - growSize, primary(childrenSize), mAlign); for (auto* w : children()) { MyGUI::IntSize size = w->calculateSize(); primary(size) += static_cast(growFactor * getGrow(w)); float stretch = std::clamp(w->externalValue("stretch", 0.0f), 0.0f, 1.0f); secondary(size) = std::max(secondary(size), static_cast(stretch * secondary(flexSize))); secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange); w->forcePosition(childPosition); w->forceSize(size); w->updateCoord(); primary(childPosition) += primary(size); } WidgetExtension::updateChildren(); } MyGUI::IntSize LuaFlex::childScalingSize() { // Call the base method to prevent relativeSize feedback loop MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) primary(size) = 0; return size; } MyGUI::IntSize LuaFlex::calculateSize() { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) { primary(size) = std::max(primary(size), primary(mChildrenSize)); secondary(size) = std::max(secondary(size), secondary(mChildrenSize)); } return size; } void LuaFlex::updateCoord() { updateChildren(); WidgetExtension::updateCoord(); } } openmw-openmw-0.48.0/components/lua_ui/flex.hpp000066400000000000000000000026161445372753700215530ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_FLEX #define OPENMW_LUAUI_FLEX #include "widget.hpp" #include "alignment.hpp" namespace LuaUi { class LuaFlex : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaFlex) protected: MyGUI::IntSize calculateSize() override; void updateProperties() override; void updateChildren() override; MyGUI::IntSize childScalingSize() override; void updateCoord() override; private: bool mHorizontal; bool mAutoSized; MyGUI::IntSize mChildrenSize; Alignment mAlign; Alignment mArrange; template T& primary(MyGUI::types::TPoint& point) { return mHorizontal ? point.left : point.top; } template T& secondary(MyGUI::types::TPoint& point) { return mHorizontal ? point.top : point.left; } template T& primary(MyGUI::types::TSize& size) { return mHorizontal ? size.width : size.height; } template T& secondary(MyGUI::types::TSize& size) { return mHorizontal ? size.height : size.width; } }; } #endif // OPENMW_LUAUI_FLEX openmw-openmw-0.48.0/components/lua_ui/image.cpp000066400000000000000000000046501445372753700216720ustar00rootroot00000000000000#include "image.hpp" #include #include "resources.hpp" namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) { mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); mAlign = MyGUI::Align::Stretch; MyGUI::TileRect::_setAlign(_oldsize); mTileSize = mSetTileSize; // zero tilesize stands for not tiling if (mTileSize.width == 0) mTileSize.width = mCoord.width; if (mTileSize.height == 0) mTileSize.height = mCoord.height; // mCoord could be zero, prevent division by 0 // use arbitrary large numbers to prevent performance issues if (mTileSize.width <= 0) mTileSize.width = 1e7; if (mTileSize.height <= 0) mTileSize.height = 1e7; } void LuaImage::initialize() { changeWidgetSkin("LuaImage"); mTileRect = dynamic_cast(getSubWidgetMain()); WidgetExtension::initialize(); } void LuaImage::updateProperties() { deleteAllItems(); TextureResource* resource = propertyValue("resource", nullptr); MyGUI::IntCoord atlasCoord; if (resource) { atlasCoord = MyGUI::IntCoord( static_cast(resource->mOffset.x()), static_cast(resource->mOffset.y()), static_cast(resource->mSize.x()), static_cast(resource->mSize.y())); setImageTexture(resource->mPath); } bool tileH = propertyValue("tileH", false); bool tileV = propertyValue("tileV", false); MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(_getTextureName()); MyGUI::IntSize textureSize; if (texture != nullptr) textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); mTileRect->updateSize(MyGUI::IntSize( tileH ? textureSize.width : 0, tileV ? textureSize.height : 0 )); setImageTile(textureSize); if (atlasCoord.width == 0) atlasCoord.width = textureSize.width; if (atlasCoord.height == 0) atlasCoord.height = textureSize.height; setImageCoord(atlasCoord); setColour(propertyValue("color", MyGUI::Colour(1,1,1,1))); WidgetExtension::updateProperties(); } } openmw-openmw-0.48.0/components/lua_ui/image.hpp000066400000000000000000000014101445372753700216660ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_IMAGE #define OPENMW_LUAUI_IMAGE #include #include #include "widget.hpp" namespace LuaUi { class LuaTileRect : public MyGUI::TileRect { MYGUI_RTTI_DERIVED(LuaTileRect) public: void _setAlign(const MyGUI::IntSize& _oldsize) override; void updateSize(MyGUI::IntSize tileSize) { mSetTileSize = tileSize; } protected: MyGUI::IntSize mSetTileSize; }; class LuaImage : public MyGUI::ImageBox, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaImage) protected: void initialize() override; void updateProperties() override; LuaTileRect* mTileRect; }; } #endif // OPENMW_LUAUI_IMAGE openmw-openmw-0.48.0/components/lua_ui/layers.cpp000066400000000000000000000015651445372753700221110ustar00rootroot00000000000000#include "layers.hpp" #include namespace LuaUi { size_t Layer::indexOf(std::string_view name) { for (size_t i = 0; i < count(); i++) if (at(i)->getName() == name) return i; return count(); } void Layer::insert(size_t index, std::string_view name, Options options) { if (index > count()) throw std::logic_error("Invalid layer index"); if (indexOf(name) < count()) Log(Debug::Error) << "Layer \"" << name << "\" already exists"; else { auto layer = MyGUI::LayerManager::getInstance() .createLayerAt(std::string(name), "OverlappedLayer", index); auto overlappedLayer = dynamic_cast(layer); overlappedLayer->setPick(options.mInteractive); } } } openmw-openmw-0.48.0/components/lua_ui/layers.hpp000066400000000000000000000035211445372753700221100ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_LAYERS #define OPENMW_LUAUI_LAYERS #include #include #include #include #include namespace LuaUi { // this wrapper is necessary, because the MyGUI LayerManager // stores layers in a vector and their indices could change class Layer { public: Layer(size_t index) : mName(at(index)->getName()) , mCachedIndex(index) {} const std::string& name() const noexcept { return mName; }; const osg::Vec2f size() { MyGUI::ILayer* p = refresh(); MyGUI::IntSize size = p->getSize(); return osg::Vec2f(size.width, size.height); } struct Options { bool mInteractive; }; static size_t count() { return MyGUI::LayerManager::getInstance().getLayerCount(); } static size_t indexOf(std::string_view name); static void insert(size_t index, std::string_view name, Options options); private: static MyGUI::ILayer* at(size_t index) { if (index >= count()) throw std::logic_error("Invalid layer index"); return MyGUI::LayerManager::getInstance().getLayer(index); } MyGUI::ILayer* refresh() { MyGUI::ILayer* p = at(mCachedIndex); if (p->getName() != mName) { mCachedIndex = indexOf(mName); p = at(mCachedIndex); } return p; } std::string mName; size_t mCachedIndex; }; } #endif // OPENMW_LUAUI_LAYERS openmw-openmw-0.48.0/components/lua_ui/properties.hpp000066400000000000000000000055261445372753700230140ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_PROPERTIES #define OPENMW_LUAUI_PROPERTIES #include #include #include #include #include namespace LuaUi { template constexpr bool isMyGuiVector() { return std::is_same() || std::is_same() || std::is_same() || std::is_same(); } template constexpr bool isMyGuiColor() { return std::is_same(); } template sol::optional parseValue( sol::object table, std::string_view field, std::string_view errorPrefix) { sol::object opt = LuaUtil::getFieldOrNil(table, field); if (opt != sol::nil && !opt.is()) { std::string error(errorPrefix); error += " \""; error += field; error += "\" has an invalid value \""; error += LuaUtil::toString(opt); error += "\""; throw std::logic_error(error); } if (!opt.is()) return sol::nullopt; LuaT luaT = opt.as(); if constexpr (isMyGuiVector()) return T(luaT.x(), luaT.y()); else if constexpr (isMyGuiColor()) return T(luaT.r(), luaT.g(), luaT.b(), luaT.a()); else return luaT; } template sol::optional parseValue( sol::object table, std::string_view field, std::string_view errorPrefix) { if constexpr (isMyGuiVector()) return parseValue(table, field, errorPrefix); else if constexpr (isMyGuiColor()) return parseValue(table, field, errorPrefix); else return parseValue(table, field, errorPrefix); } template T parseProperty( sol::object props, sol::object templateProps, std::string_view field, const T& defaultValue) { auto propOptional = parseValue(props, field, "Property"); auto templateOptional = parseValue(templateProps, field, "Template property"); if (propOptional.has_value()) return propOptional.value(); else if (templateOptional.has_value()) return templateOptional.value(); else return defaultValue; } template T parseExternal( sol::object external, std::string_view field, const T& defaultValue) { auto optional = parseValue(external, field, "External value"); return optional.value_or(defaultValue); } } #endif // !OPENMW_LUAUI_PROPERTIES openmw-openmw-0.48.0/components/lua_ui/registerscriptsettings.hpp000066400000000000000000000004621445372753700254440ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_REGISTERSCRIPTSETTINGS #define OPENMW_LUAUI_REGISTERSCRIPTSETTINGS #include namespace LuaUi { // implemented in scriptsettings.cpp void registerSettingsPage(const sol::table& options); void clearSettings(); } #endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS openmw-openmw-0.48.0/components/lua_ui/resources.cpp000066400000000000000000000007741445372753700226250ustar00rootroot00000000000000#include "resources.hpp" #include #include namespace LuaUi { std::shared_ptr ResourceManager::registerTexture(TextureData data) { data.mPath = mVfs->normalizeFilename(data.mPath); TextureResources& list = mTextures[data.mPath]; list.push_back(std::make_shared(data)); return list.back(); } void ResourceManager::clear() { mTextures.clear(); } } openmw-openmw-0.48.0/components/lua_ui/resources.hpp000066400000000000000000000016771445372753700226350ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_RESOURCES #define OPENMW_LUAUI_RESOURCES #include #include #include #include #include namespace VFS { class Manager; } namespace LuaUi { struct TextureData { std::string mPath; osg::Vec2f mOffset; osg::Vec2f mSize; }; // will have more/different data when automated atlasing is supported using TextureResource = TextureData; class ResourceManager { public: ResourceManager(const VFS::Manager* vfs) : mVfs(vfs) {} std::shared_ptr registerTexture(TextureData data); void clear(); private: const VFS::Manager* mVfs; using TextureResources = std::vector>; std::unordered_map mTextures; }; } #endif // OPENMW_LUAUI_LAYERS openmw-openmw-0.48.0/components/lua_ui/scriptsettings.cpp000066400000000000000000000030071445372753700236700ustar00rootroot00000000000000#include "scriptsettings.hpp" #include #include #include "registerscriptsettings.hpp" #include "element.hpp" #include "adapter.hpp" namespace LuaUi { namespace { std::vector allPages; ScriptSettingsPage parse(const sol::table& options) { auto name = options.get_or("name", std::string()); auto searchHints = options.get_or("searchHints", std::string()); auto element = options.get_or>("element", nullptr); if (name.empty()) Log(Debug::Warning) << "A script settings page has an empty name"; if (!element.get()) Log(Debug::Warning) << "A script settings page has no UI element assigned"; return { name, searchHints, element }; } } size_t scriptSettingsPageCount() { return allPages.size(); } ScriptSettingsPage scriptSettingsPageAt(size_t index) { return parse(allPages[index]); } void registerSettingsPage(const sol::table& options) { allPages.push_back(options); } void clearSettings() { allPages.clear(); } void attachPageAt(size_t index, LuaAdapter* adapter) { if (index < allPages.size()) { ScriptSettingsPage page = parse(allPages[index]); adapter->detach(); if (page.mElement.get()) adapter->attach(page.mElement); } } } openmw-openmw-0.48.0/components/lua_ui/scriptsettings.hpp000066400000000000000000000010311445372753700236700ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_SCRIPTSETTINGS #define OPENMW_LUAUI_SCRIPTSETTINGS #include #include #include namespace LuaUi { class LuaAdapter; struct Element; struct ScriptSettingsPage { std::string mName; std::string mSearchHints; std::shared_ptr mElement; }; size_t scriptSettingsPageCount(); ScriptSettingsPage scriptSettingsPageAt(size_t index); void attachPageAt(size_t index, LuaAdapter* adapter); } #endif // !OPENMW_LUAUI_SCRIPTSETTINGS openmw-openmw-0.48.0/components/lua_ui/text.cpp000066400000000000000000000027561445372753700216010ustar00rootroot00000000000000#include "text.hpp" #include "alignment.hpp" namespace LuaUi { LuaText::LuaText() : mAutoSized(true) {} void LuaText::initialize() { changeWidgetSkin("LuaText"); setEditStatic(true); setVisibleHScroll(false); setVisibleVScroll(false); WidgetExtension::initialize(); } void LuaText::updateProperties() { mAutoSized = propertyValue("autoSize", true); setCaption(propertyValue("text", std::string())); setFontHeight(propertyValue("textSize", 10)); setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); setEditMultiLine(propertyValue("multiline", false)); setEditWordWrap(propertyValue("wordWrap", false)); Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); Alignment vertical(propertyValue("textAlignV", Alignment::Start)); setTextAlign(alignmentToMyGui(horizontal, vertical)); setTextShadow(propertyValue("textShadow", false)); setTextShadowColour(propertyValue("textShadowColor", MyGUI::Colour(0, 0, 0, 1))); WidgetExtension::updateProperties(); } void LuaText::setCaption(const MyGUI::UString& caption) { MyGUI::TextBox::setCaption(caption); if (mAutoSized) updateCoord(); } MyGUI::IntSize LuaText::calculateSize() { if (mAutoSized) return getTextSize(); else return WidgetExtension::calculateSize(); } } openmw-openmw-0.48.0/components/lua_ui/text.hpp000066400000000000000000000011071445372753700215730ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_TEXT #define OPENMW_LUAUI_TEXT #include #include "widget.hpp" namespace LuaUi { class LuaText : public MyGUI::EditBox, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaText) public: LuaText(); void initialize() override; void updateProperties() override; void setCaption(const MyGUI::UString& caption) override; private: bool mAutoSized; protected: MyGUI::IntSize calculateSize() override; }; } #endif // OPENMW_LUAUI_TEXT openmw-openmw-0.48.0/components/lua_ui/textedit.cpp000066400000000000000000000047371445372753700224500ustar00rootroot00000000000000#include "textedit.hpp" #include "alignment.hpp" namespace LuaUi { void LuaTextEdit::initialize() { mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); registerEvents(mEditBox); WidgetExtension::initialize(); } void LuaTextEdit::deinitialize() { mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); clearEvents(mEditBox); WidgetExtension::deinitialize(); } void LuaTextEdit::updateProperties() { mEditBox->setFontHeight(propertyValue("textSize", 10)); mEditBox->setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); mEditBox->setEditWordWrap(propertyValue("wordWrap", false)); Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); Alignment vertical(propertyValue("textAlignV", Alignment::Start)); mEditBox->setTextAlign(alignmentToMyGui(horizontal, vertical)); mMultiline = propertyValue("multiline", false); mEditBox->setEditMultiLine(mMultiline); bool readOnly = propertyValue("readOnly", false); mEditBox->setEditStatic(readOnly); mAutoSize = (readOnly || !mMultiline) && propertyValue("autoSize", false); // change caption last, for multiline and wordwrap to apply mEditBox->setCaption(propertyValue("text", std::string())); WidgetExtension::updateProperties(); } void LuaTextEdit::textChange(MyGUI::EditBox*) { triggerEvent("textChanged", sol::make_object(lua(), mEditBox->getCaption().asUTF8())); } void LuaTextEdit::updateCoord() { WidgetExtension::updateCoord(); mEditBox->setSize(widget()->getSize()); } void LuaTextEdit::updateChildren() { WidgetExtension::updateChildren(); // otherwise it won't be focusable mEditBox->detachFromWidget(); mEditBox->attachToWidget(this); } MyGUI::IntSize LuaTextEdit::calculateSize() { MyGUI::IntSize normalSize = WidgetExtension::calculateSize(); if (mAutoSize) { mEditBox->setSize(normalSize); int targetHeight = mMultiline ? mEditBox->getTextSize().height : mEditBox->getFontHeight(); normalSize.height = std::max(normalSize.height, targetHeight); } return normalSize; } } openmw-openmw-0.48.0/components/lua_ui/textedit.hpp000066400000000000000000000013651445372753700224470ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_TEXTEDIT #define OPENMW_LUAUI_TEXTEDIT #include #include "widget.hpp" namespace LuaUi { class LuaTextEdit : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaTextEdit) protected: void initialize() override; void deinitialize() override; void updateProperties() override; void updateCoord() override; void updateChildren() override; MyGUI::IntSize calculateSize() override; private: void textChange(MyGUI::EditBox*); MyGUI::EditBox* mEditBox = nullptr; bool mMultiline{false}; bool mAutoSize{false}; }; } #endif // OPENMW_LUAUI_TEXTEDIT openmw-openmw-0.48.0/components/lua_ui/util.cpp000066400000000000000000000033261445372753700215640ustar00rootroot00000000000000#include "util.hpp" #include #include "adapter.hpp" #include "widget.hpp" #include "text.hpp" #include "textedit.hpp" #include "window.hpp" #include "image.hpp" #include "container.hpp" #include "flex.hpp" #include "element.hpp" #include "registerscriptsettings.hpp" namespace LuaUi { void registerAllWidgets() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } const std::unordered_map& widgetTypeToName() { static std::unordered_map types{ { "LuaWidget", "Widget" }, { "LuaText", "Text" }, { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, { "LuaImage", "Image" }, { "LuaFlex", "Flex" }, { "LuaContainer", "Container" }, }; return types; } void clearUserInterface() { clearSettings(); while (!Element::sAllElements.empty()) Element::sAllElements.begin()->second->destroy(); } } openmw-openmw-0.48.0/components/lua_ui/util.hpp000066400000000000000000000004601445372753700215650ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_WIDGETLIST #define OPENMW_LUAUI_WIDGETLIST #include #include namespace LuaUi { void registerAllWidgets(); const std::unordered_map& widgetTypeToName(); void clearUserInterface(); } #endif // OPENMW_LUAUI_WIDGETLIST openmw-openmw-0.48.0/components/lua_ui/widget.cpp000066400000000000000000000323741445372753700220770ustar00rootroot00000000000000#include "widget.hpp" #include #include #include "text.hpp" #include "textedit.hpp" #include "window.hpp" namespace LuaUi { WidgetExtension::WidgetExtension() : mForcePosition(false) , mForceSize(false) , mPropagateEvents(true) , mLua(nullptr) , mWidget(nullptr) , mSlot(this) , mLayout(sol::nil) , mProperties(sol::nil) , mTemplateProperties(sol::nil) , mExternal(sol::nil) , mParent(nullptr) , mTemplateChild(false) {} void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) { mLua = lua; mWidget = self; initialize(); updateTemplate(); } void WidgetExtension::initialize() { // \todo might be more efficient to only register these if there are Lua callbacks registerEvents(mWidget); } void WidgetExtension::deinitialize() { clearCallbacks(); clearEvents(mWidget); mOnCoordChange.reset(); for (WidgetExtension* w : mChildren) w->deinitialize(); for (WidgetExtension* w : mTemplateChildren) w->deinitialize(); } void WidgetExtension::registerEvents(MyGUI::Widget* w) { w->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); w->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); w->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick); w->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick); w->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress); w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); w->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); w->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); } void WidgetExtension::clearEvents(MyGUI::Widget* w) { w->eventKeyButtonPressed.clear(); w->eventKeyButtonReleased.clear(); w->eventMouseButtonClick.clear(); w->eventMouseButtonDoubleClick.clear(); w->eventMouseButtonPressed.clear(); w->eventMouseButtonReleased.clear(); w->eventMouseMove.clear(); w->eventMouseDrag.m_event.clear(); w->eventMouseSetFocus.clear(); w->eventMouseLostFocus.clear(); w->eventKeySetFocus.clear(); w->eventKeyLostFocus.clear(); } void WidgetExtension::reset() { // detach all children from the slot widget, in case it gets destroyed for (auto& w: mChildren) w->widget()->detachFromWidget(); } void WidgetExtension::attach(WidgetExtension* ext) { ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); // workaround for MyGUI bug // parent visibility doesn't affect added children ext->widget()->setVisible(!ext->widget()->getVisible()); ext->widget()->setVisible(!ext->widget()->getVisible()); } void WidgetExtension::attachTemplate(WidgetExtension* ext) { ext->mParent = this; ext->mTemplateChild = true; ext->widget()->attachToWidget(widget()); // workaround for MyGUI bug // parent visibility doesn't affect added children ext->widget()->setVisible(!ext->widget()->getVisible()); ext->widget()->setVisible(!ext->widget()->getVisible()); } WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { for (WidgetExtension* w : mChildren) { WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } if (externalValue(flagName, false)) return this; return nullptr; } void WidgetExtension::findAll(std::string_view flagName, std::vector& result) { if (externalValue(flagName, false)) result.push_back(this); for (WidgetExtension* w : mChildren) w->findAll(flagName, result); } WidgetExtension* WidgetExtension::findDeepInTemplates(std::string_view flagName) { for (WidgetExtension* w : mTemplateChildren) { WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } return nullptr; } std::vector WidgetExtension::findAllInTemplates(std::string_view flagName) { std::vector result; for (WidgetExtension* w : mTemplateChildren) w->findAll(flagName, result); return result; } sol::table WidgetExtension::makeTable() const { return sol::table(lua(), sol::create); } sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const { auto keySym = SDL_Keysym(); keySym.sym = SDLUtil::myGuiKeyToSdl(code); keySym.scancode = SDL_GetScancodeFromKey(keySym.sym); keySym.mod = SDL_GetModState(); return sol::make_object(lua(), keySym); } sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const { osg::Vec2f position(left, top); MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition(); osg::Vec2f offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); sol::table table = makeTable(); int sdlButton = SDLUtil::myGuiMouseButtonToSdl(button); table["position"] = position; table["offset"] = offset; if (sdlButton != 0) // nil if no button was pressed table["button"] = sdlButton; return table; } void WidgetExtension::setChildren(const std::vector& children) { mChildren.resize(children.size()); for (size_t i = 0; i < children.size(); ++i) { mChildren[i] = children[i]; attach(mChildren[i]); } updateChildren(); } void WidgetExtension::setTemplateChildren(const std::vector& children) { mTemplateChildren.resize(children.size()); for (size_t i = 0; i < children.size(); ++i) { mTemplateChildren[i] = children[i]; attachTemplate(mTemplateChildren[i]); } updateTemplate(); } void WidgetExtension::updateTemplate() { WidgetExtension* slot = findDeepInTemplates("slot"); if (slot == nullptr) mSlot = this; else mSlot = slot->mSlot; } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) { mCallbacks[name] = callback; } void WidgetExtension::clearCallbacks() { mCallbacks.clear(); } MyGUI::IntCoord WidgetExtension::forcedCoord() { return mForcedCoord; } void WidgetExtension::forceCoord(const MyGUI::IntCoord& offset) { mForcePosition = true; mForceSize = true; mForcedCoord = offset; } void WidgetExtension::forcePosition(const MyGUI::IntPoint& pos) { mForcePosition = true; mForcedCoord = pos; } void WidgetExtension::forceSize(const MyGUI::IntSize& size) { mForceSize = true; mForcedCoord = size; } void WidgetExtension::clearForced() { mForcePosition = false; mForceSize = false; } void WidgetExtension::updateCoord() { MyGUI::IntCoord oldCoord = mWidget->getCoord(); MyGUI::IntCoord newCoord = calculateCoord(); if (oldCoord != newCoord) mWidget->setCoord(newCoord); updateChildrenCoord(); if (oldCoord != newCoord && mOnCoordChange.has_value()) mOnCoordChange.value()(this, newCoord); } void WidgetExtension::setProperties(sol::object props) { mProperties = props; updateProperties(); } void WidgetExtension::updateProperties() { mPropagateEvents = propertyValue("propagateEvents", true); mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint()); mAbsoluteCoord = propertyValue("size", MyGUI::IntSize()); mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize()); mAnchor = propertyValue("anchor", MyGUI::FloatSize()); mWidget->setVisible(propertyValue("visible", true)); mWidget->setPointer(propertyValue("pointer", std::string("arrow"))); mWidget->setAlpha(propertyValue("alpha", 1.f)); mWidget->setInheritsAlpha(propertyValue("inheritAlpha", true)); } void WidgetExtension::updateChildrenCoord() { for (WidgetExtension* w : mTemplateChildren) w->updateCoord(); for (WidgetExtension* w : mChildren) w->updateCoord(); } MyGUI::IntSize WidgetExtension::parentSize() { if (!mParent) return widget()->getParentSize(); // size of the layer if (mTemplateChild) return mParent->templateScalingSize(); else return mParent->childScalingSize(); } MyGUI::IntSize WidgetExtension::calculateSize() { if (mForceSize) return mForcedCoord.size(); MyGUI::IntSize pSize = parentSize(); MyGUI::IntSize newSize; newSize = mAbsoluteCoord.size(); newSize.width += mRelativeCoord.width * pSize.width; newSize.height += mRelativeCoord.height * pSize.height; return newSize; } MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) { if (mForcePosition) return mForcedCoord.point(); MyGUI::IntSize pSize = parentSize(); MyGUI::IntPoint newPosition; newPosition = mAbsoluteCoord.point(); newPosition.left += mRelativeCoord.left * pSize.width - mAnchor.width * size.width; newPosition.top += mRelativeCoord.top * pSize.height - mAnchor.height * size.height; return newPosition; } MyGUI::IntCoord WidgetExtension::calculateCoord() { MyGUI::IntCoord newCoord; newCoord = calculateSize(); newCoord = calculatePosition(newCoord.size()); return newCoord; } MyGUI::IntSize WidgetExtension::childScalingSize() { return mSlot->widget()->getSize(); } MyGUI::IntSize WidgetExtension::templateScalingSize() { return widget()->getSize(); } void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const { auto it = mCallbacks.find(name); if (it != mCallbacks.end()) it->second.call(argument, mLayout); } void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch) { if (code == MyGUI::KeyCode::None) { propagateEvent("textInput", [ch](auto w) { MyGUI::UString uString; uString.push_back(static_cast(ch)); return sol::make_object(w->lua(), uString.asUTF8()); }); } else propagateEvent("keyPress", [code](auto w){ return w->keyEvent(code); }); } void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code) { propagateEvent("keyRelease", [code](auto w) { return w->keyEvent(code); }); } void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top) { propagateEvent("mouseMove", [left, top](auto w) { return w->mouseEvent(left, top); }); } void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { propagateEvent("mouseMove", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::mouseClick(MyGUI::Widget* _widget) { propagateEvent("mouseClick", [](auto){ return sol::nil; }); } void WidgetExtension::mouseDoubleClick(MyGUI::Widget* _widget) { propagateEvent("mouseDoubleClick", [](auto){ return sol::nil; }); } void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { propagateEvent("mousePress", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { propagateEvent("mouseRelease", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*) { propagateEvent("focusGain", [](auto){ return sol::nil; }); } void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*) { propagateEvent("focusLoss", [](auto){ return sol::nil; }); } } openmw-openmw-0.48.0/components/lua_ui/widget.hpp000066400000000000000000000146031445372753700220770ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_WIDGET #define OPENMW_LUAUI_WIDGET #include #include #include #include #include #include "properties.hpp" namespace LuaUi { /* * extends MyGUI::Widget and its child classes * memory ownership is controlled by MyGUI * it is important not to call any WidgetExtension methods after destroying the MyGUI::Widget */ class WidgetExtension { public: WidgetExtension(); // must be called after creating the underlying MyGUI::Widget void initialize(lua_State* lua, MyGUI::Widget* self); // must be called after before destroying the underlying MyGUI::Widget virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } WidgetExtension* slot() const { return mSlot; } void reset(); const std::vector& children() { return mChildren; } void setChildren(const std::vector&); const std::vector& templateChildren() { return mTemplateChildren; } void setTemplateChildren(const std::vector&); void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); void setProperties(sol::object); void setTemplateProperties(sol::object props) { mTemplateProperties = props; } void setExternal(sol::object external) { mExternal = external; } MyGUI::IntCoord forcedCoord(); void forceCoord(const MyGUI::IntCoord& offset); void forceSize(const MyGUI::IntSize& size); void forcePosition(const MyGUI::IntPoint& pos); void clearForced(); virtual void updateCoord(); const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } template T externalValue(std::string_view name, const T& defaultValue) { return parseExternal(mExternal, name, defaultValue); } void onCoordChange(const std::optional>& callback) { mOnCoordChange = callback; } virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); protected: virtual void initialize(); void registerEvents(MyGUI::Widget* w); void clearEvents(MyGUI::Widget* w); sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; MyGUI::IntSize parentSize(); virtual MyGUI::IntSize childScalingSize(); virtual MyGUI::IntSize templateScalingSize(); template T propertyValue(std::string_view name, const T& defaultValue) { return parseProperty(mProperties, mTemplateProperties, name, defaultValue); } WidgetExtension* findDeepInTemplates(std::string_view flagName); std::vector findAllInTemplates(std::string_view flagName); virtual void updateTemplate(); virtual void updateProperties(); virtual void updateChildren() {}; lua_State* lua() const { return mLua; } void triggerEvent(std::string_view name, sol::object argument) const; template void propagateEvent(std::string_view name, const ArgFactory& argumentFactory) const { const WidgetExtension* w = this; while (w) { bool shouldPropagate = true; auto it = w->mCallbacks.find(name); if (it != w->mCallbacks.end()) { sol::object res = it->second.call(argumentFactory(w), w->mLayout); shouldPropagate = res.is() && res.as(); } if (w->mParent && w->mPropagateEvents && shouldPropagate) w = w->mParent; else w = nullptr; } } bool mForcePosition; bool mForceSize; // offsets the position and size, used only in C++ widget code MyGUI::IntCoord mForcedCoord; // position and size in pixels MyGUI::IntCoord mAbsoluteCoord; // position and size as a ratio of parent size MyGUI::FloatCoord mRelativeCoord; // negative position offset as a ratio of this widget's size // used in combination with relative coord to align the widget, e. g. center it MyGUI::FloatSize mAnchor; bool mPropagateEvents; private: // use lua_State* instead of sol::state_view because MyGUI requires a default constructor lua_State* mLua; MyGUI::Widget* mWidget; std::vector mChildren; std::vector mTemplateChildren; WidgetExtension* mSlot; std::map> mCallbacks; sol::table mLayout; sol::object mProperties; sol::object mTemplateProperties; sol::object mExternal; WidgetExtension* mParent; bool mTemplateChild; void attach(WidgetExtension* ext); void attachTemplate(WidgetExtension* ext); WidgetExtension* findDeep(std::string_view name); void findAll(std::string_view flagName, std::vector& result); void updateChildrenCoord(); void keyPress(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char); void keyRelease(MyGUI::Widget*, MyGUI::KeyCode); void mouseMove(MyGUI::Widget*, int, int); void mouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); void mouseClick(MyGUI::Widget*); void mouseDoubleClick(MyGUI::Widget*); void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton); void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); std::optional> mOnCoordChange; }; class LuaWidget : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaWidget) }; } #endif // !OPENMW_LUAUI_WIDGET openmw-openmw-0.48.0/components/lua_ui/window.cpp000066400000000000000000000051001445372753700221060ustar00rootroot00000000000000#include "window.hpp" #include #include #include namespace LuaUi { LuaWindow::LuaWindow() : mCaption(nullptr) {} void LuaWindow::updateTemplate() { for (auto& [w, _] : mActionWidgets) { w->eventMouseButtonPressed.clear(); w->eventMouseDrag.m_event.clear(); } mActionWidgets.clear(); WidgetExtension* captionWidget = findDeepInTemplates("caption"); mCaption = dynamic_cast(captionWidget); if (mCaption) mActionWidgets.emplace(mCaption->widget(), mCaption); for (WidgetExtension* ext : findAllInTemplates("action")) mActionWidgets.emplace(ext->widget(), ext); for (auto& [w, _] : mActionWidgets) { w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); } WidgetExtension::updateTemplate(); } void LuaWindow::updateProperties() { if (mCaption) mCaption->setCaption(propertyValue("caption", std::string())); mMoveResize = MyGUI::IntCoord(); clearForced(); WidgetExtension::updateProperties(); } void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) { if (id != MyGUI::MouseButton::Left) return; mPreviousMouse.left = left; mPreviousMouse.top = top; WidgetExtension* ext = mActionWidgets[sender]; mChangeScale = MyGUI::IntCoord( ext->externalValue("move", MyGUI::IntPoint(1, 1)), ext->externalValue("resize", MyGUI::IntSize(0, 0))); } void LuaWindow::notifyMouseDrag(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) { if (id != MyGUI::MouseButton::Left) return; MyGUI::IntCoord change = mChangeScale; change.left *= (left - mPreviousMouse.left); change.top *= (top - mPreviousMouse.top); change.width *= (left - mPreviousMouse.left); change.height *= (top - mPreviousMouse.top); mMoveResize = mMoveResize + change; forceCoord(mMoveResize); updateCoord(); mPreviousMouse.left = left; mPreviousMouse.top = top; sol::table table = makeTable(); table["position"] = osg::Vec2f(mCoord.left, mCoord.top); table["size"] = osg::Vec2f(mCoord.width, mCoord.height); triggerEvent("windowDrag", table); } } openmw-openmw-0.48.0/components/lua_ui/window.hpp000066400000000000000000000015231445372753700221200ustar00rootroot00000000000000#ifndef OPENMW_LUAUI_WINDOW #define OPENMW_LUAUI_WINDOW #include #include "widget.hpp" #include "text.hpp" namespace LuaUi { class LuaWindow : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaWindow) public: LuaWindow(); void updateTemplate() override; void updateProperties() override; private: LuaText* mCaption; std::map mActionWidgets; MyGUI::IntPoint mPreviousMouse; MyGUI::IntCoord mChangeScale; MyGUI::IntCoord mMoveResize; protected: void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); }; } #endif // OPENMW_LUAUI_WINDOW openmw-openmw-0.48.0/components/misc/000077500000000000000000000000001445372753700175545ustar00rootroot00000000000000openmw-openmw-0.48.0/components/misc/algorithm.hpp000066400000000000000000000031171445372753700222550ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_ALGORITHM_H #define OPENMW_COMPONENTS_MISC_ALGORITHM_H #include #include #include "stringops.hpp" namespace Misc { template inline Iterator forEachUnique(Iterator begin, Iterator end, BinaryPredicate predicate, Function function) { static_assert( std::is_base_of_v< std::forward_iterator_tag, typename std::iterator_traits::iterator_category > ); if (begin == end) return begin; function(*begin); auto last = begin; ++begin; while (begin != end) { if (!predicate(*begin, *last)) { function(*begin); last = begin; } ++begin; } return begin; } /// Performs a binary search on a sorted container for a string that 'key' starts with template static Iterator partialBinarySearch(Iterator begin, Iterator end, const T& key) { const Iterator notFound = end; while(begin < end) { const Iterator middle = begin + (std::distance(begin, end) / 2); int comp = Misc::StringUtils::ciCompareLen((*middle), key, (*middle).size()); if(comp == 0) return middle; else if(comp > 0) end = middle; else begin = middle + 1; } return notFound; } } #endif openmw-openmw-0.48.0/components/misc/barrier.hpp000066400000000000000000000027141445372753700217170ustar00rootroot00000000000000#ifndef OPENMW_BARRIER_H #define OPENMW_BARRIER_H #include #include namespace Misc { /// @brief Synchronize several threads class Barrier { public: /// @param count number of threads to wait on explicit Barrier(unsigned count) : mThreadCount(count), mRendezvousCount(0), mGeneration(0) { } /// @brief stop execution of threads until count distinct threads reach this point /// @param func callable to be executed once after all threads have met template void wait(Callback&& func) { std::unique_lock lock(mMutex); ++mRendezvousCount; const unsigned int currentGeneration = mGeneration; if (mRendezvousCount == mThreadCount || mThreadCount == 0) { ++mGeneration; mRendezvousCount = 0; func(); mRendezvous.notify_all(); } else { mRendezvous.wait(lock, [&]() { return mGeneration != currentGeneration; }); } } private: unsigned int mThreadCount; unsigned int mRendezvousCount; unsigned int mGeneration; mutable std::mutex mMutex; std::condition_variable mRendezvous; }; } #endif openmw-openmw-0.48.0/components/misc/budgetmeasurement.hpp000066400000000000000000000021501445372753700240030ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H #define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H namespace Misc { class BudgetMeasurement { std::array mBudgetHistory; std::array mBudgetStepCount; public: BudgetMeasurement(const float default_expense) { mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; mBudgetStepCount = {1, 1, 1, 1}; } void reset(const float default_expense) { mBudgetHistory = {default_expense, default_expense, default_expense, default_expense}; mBudgetStepCount = {1, 1, 1, 1}; } void update(double delta, unsigned int stepCount, size_t cursor) { mBudgetHistory[cursor%4] = delta; mBudgetStepCount[cursor%4] = stepCount; } double get() const { float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]); unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]); return sum/float(stepCountSum); } }; } #endif openmw-openmw-0.48.0/components/misc/color.cpp000066400000000000000000000033741445372753700214050ustar00rootroot00000000000000#include "color.hpp" #include #include #include #include namespace Misc { Color::Color(float r, float g, float b, float a) : mR(std::clamp(r, 0.f, 1.f)) , mG(std::clamp(g, 0.f, 1.f)) , mB(std::clamp(b, 0.f, 1.f)) , mA(std::clamp(a, 0.f, 1.f)) {} std::string Color::toString() const { std::ostringstream ss; ss << "(" << r() << ", " << g() << ", " << b() << ", " << a() << ')'; return ss.str(); } Color Color::fromHex(std::string_view hex) { if (hex.size() != 6) throw std::logic_error(std::string("Invalid hex color: ") += hex); std::array rgb; for (size_t i = 0; i < rgb.size(); i++) { auto sub = hex.substr(i * 2, 2); int v = 0; auto [_, ec] = std::from_chars(sub.data(), sub.data() + sub.size(), v, 16); if (ec != std::errc()) throw std::logic_error(std::string("Invalid hex color: ") += hex); rgb[i] = v / 255.0f; } return Color(rgb[0], rgb[1], rgb[2], 1); } std::string Color::toHex() const { std::string result(6, '0'); std::array rgb = { mR, mG, mB }; for (size_t i = 0; i < rgb.size(); i++) { int b = static_cast(rgb[i] * 255.0f); auto [_, ec] = std::to_chars(result.data() + i * 2, result.data() + (i + 1) * 2, b, 16); if (ec != std::errc()) throw std::logic_error("Error when converting number to base 16"); } return result; } bool operator==(const Color& l, const Color& r) { return l.mR == r.mR && l.mG == r.mG && l.mB == r.mB && l.mA == r.mA; } } openmw-openmw-0.48.0/components/misc/color.hpp000066400000000000000000000013211445372753700214000ustar00rootroot00000000000000#ifndef COMPONENTS_MISC_COLOR #define COMPONENTS_MISC_COLOR #include namespace Misc { class Color { public: Color(float r, float g, float b, float a); float r() const { return mR; } float g() const { return mG; } float b() const { return mB; } float a() const { return mA; } std::string toString() const; static Color fromHex(std::string_view hex); std::string toHex() const; friend bool operator==(const Color& l, const Color& r); private: float mR; float mG; float mB; float mA; }; } #endif // !COMPONENTS_MISC_COLOR openmw-openmw-0.48.0/components/misc/compression.cpp000066400000000000000000000035531445372753700226270ustar00rootroot00000000000000#include "compression.hpp" #include #include #include #include #include #include namespace Misc { std::vector compress(const std::vector& data) { const std::size_t originalSize = data.size(); std::vector result(static_cast(LZ4_compressBound(static_cast(originalSize)) + sizeof(originalSize))); const int size = LZ4_compress_default( reinterpret_cast(data.data()), reinterpret_cast(result.data()) + sizeof(originalSize), static_cast(data.size()), static_cast(result.size() - sizeof(originalSize)) ); if (size == 0) throw std::runtime_error("Failed to compress"); std::memcpy(result.data(), &originalSize, sizeof(originalSize)); result.resize(static_cast(size) + sizeof(originalSize)); return result; } std::vector decompress(const std::vector& data) { std::size_t originalSize; std::memcpy(&originalSize, data.data(), sizeof(originalSize)); std::vector result(originalSize); const int size = LZ4_decompress_safe( reinterpret_cast(data.data()) + sizeof(originalSize), reinterpret_cast(result.data()), static_cast(data.size() - sizeof(originalSize)), static_cast(result.size()) ); if (size < 0) throw std::runtime_error("Failed to decompress"); if (originalSize != static_cast(size)) throw std::runtime_error("Size of decompressed data (" + std::to_string(size) + ") doesn't match stored (" + std::to_string(originalSize) + ")"); return result; } } openmw-openmw-0.48.0/components/misc/compression.hpp000066400000000000000000000004611445372753700226270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_COMPRESSION_H #define OPENMW_COMPONENTS_MISC_COMPRESSION_H #include #include namespace Misc { std::vector compress(const std::vector& data); std::vector decompress(const std::vector& data); } #endif openmw-openmw-0.48.0/components/misc/constants.hpp000066400000000000000000000024051445372753700223020ustar00rootroot00000000000000#ifndef OPENMW_CONSTANTS_H #define OPENMW_CONSTANTS_H #include namespace Constants { // The game uses 64 units per yard const float UnitsPerMeter = 69.99125109f; const float UnitsPerFoot = 21.33333333f; // Sound speed in meters per second const float SoundSpeedInAir = 343.3f; const float SoundSpeedUnderwater = 1484.0f; // Gravity constant in m/sec^2 // Note: 8.96 m/sec^2 = 9.8 yards/sec^2 // Probaly original engine's developers just forgot // that their engine uses yards instead of meters // and used standart gravity value as it is const float GravityConst = 8.96f; // Size of one exterior cell in game units const int CellSizeInUnits = 8192; // Size of active cell grid in cells (it is a square with the (2 * CellGridRadius + 1) cells side) const int CellGridRadius = 1; // A label to mark night/day visual switches const std::string NightDayLabel = "NightDaySwitch"; // A label to mark visual switches for herbalism feature const std::string HerbalismLabel = "HerbalismSwitch"; // Percentage height at which projectiles are spawned from an actor const float TorsoHeight = 0.75f; static constexpr float sStepSizeUp = 34.0f; static constexpr float sMaxSlope = 46.0f; // Identifier for main scene camera const std::string SceneCamera = "SceneCam"; } #endif openmw-openmw-0.48.0/components/misc/convert.hpp000066400000000000000000000042711445372753700217510ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H #include #include #include #include #include #include #include namespace Misc::Convert { inline osg::Vec3f makeOsgVec3f(const float* values) { return osg::Vec3f(values[0], values[1], values[2]); } inline osg::Vec3f makeOsgVec3f(const ESM::Pathgrid::Point& value) { return osg::Vec3f(value.mX, value.mY, value.mZ); } inline btVector3 toBullet(const osg::Vec3f& vec) { return btVector3(vec.x(), vec.y(), vec.z()); } inline btQuaternion toBullet(const osg::Quat& quat) { return btQuaternion(quat.x(), quat.y(), quat.z(), quat.w()); } inline osg::Vec3f toOsg(const btVector3& vec) { return osg::Vec3f(vec.x(), vec.y(), vec.z()); } inline osg::Quat toOsg(const btQuaternion& quat) { return osg::Quat(quat.x(), quat.y(), quat.z(), quat.w()); } inline osg::Quat makeOsgQuat(const float (&rotation)[3]) { return osg::Quat(rotation[2], osg::Vec3f(0, 0, -1)) * osg::Quat(rotation[1], osg::Vec3f(0, -1, 0)) * osg::Quat(rotation[0], osg::Vec3f(-1, 0, 0)); } inline osg::Quat makeOsgQuat(const ESM::Position& position) { return makeOsgQuat(position.rot); } inline btQuaternion makeBulletQuaternion(const float (&rotation)[3]) { return btQuaternion(btVector3(0, 0, -1), rotation[2]) * btQuaternion(btVector3(0, -1, 0), rotation[1]) * btQuaternion(btVector3(-1, 0, 0), rotation[0]); } inline btQuaternion makeBulletQuaternion(const ESM::Position& position) { return makeBulletQuaternion(position.rot); } inline btTransform makeBulletTransform(const ESM::Position& position) { return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3())); } inline osg::Vec2f toOsgXY(const btVector3& value) { return osg::Vec2f(static_cast(value.x()), static_cast(value.y())); } } #endif openmw-openmw-0.48.0/components/misc/coordinateconverter.hpp000066400000000000000000000040451445372753700243470ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H #define OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H #include #include #include #include namespace Misc { /// \brief convert coordinates between world and local cell class CoordinateConverter { public: CoordinateConverter(bool exterior, int cellX, int cellY) : mCellX(exterior ? cellX * ESM::Land::REAL_SIZE : 0), mCellY(exterior ? cellY * ESM::Land::REAL_SIZE : 0) { } explicit CoordinateConverter(const ESM::Cell* cell) : CoordinateConverter(cell->isExterior(), cell->mData.mX, cell->mData.mY) { } /// in-place conversion from local to world void toWorld(ESM::Pathgrid::Point& point) const { point.mX += mCellX; point.mY += mCellY; } ESM::Pathgrid::Point toWorldPoint(ESM::Pathgrid::Point point) const { toWorld(point); return point; } /// in-place conversion from local to world void toWorld(osg::Vec3f& point) const { point.x() += static_cast(mCellX); point.y() += static_cast(mCellY); } /// in-place conversion from world to local void toLocal(osg::Vec3f& point) const { point.x() -= static_cast(mCellX); point.y() -= static_cast(mCellY); } osg::Vec3f toLocalVec3(const osg::Vec3f& point) const { return osg::Vec3f( point.x() - static_cast(mCellX), point.y() - static_cast(mCellY), point.z() ); } private: int mCellX; int mCellY; }; } #endif openmw-openmw-0.48.0/components/misc/endianness.hpp000066400000000000000000000052621445372753700224210ustar00rootroot00000000000000#ifndef COMPONENTS_MISC_ENDIANNESS_H #define COMPONENTS_MISC_ENDIANNESS_H #include #include #include namespace Misc { // Two-way conversion little-endian <-> big-endian template void swapEndiannessInplace(T& v) { static_assert(std::is_arithmetic_v); static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); if constexpr (sizeof(T) == 2) { uint16_t v16; std::memcpy(&v16, &v, sizeof(T)); v16 = (v16 >> 8) | (v16 << 8); std::memcpy(&v, &v16, sizeof(T)); } if constexpr (sizeof(T) == 4) { uint32_t v32; std::memcpy(&v32, &v, sizeof(T)); v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | (v32 << 24); std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) { uint64_t v64; std::memcpy(&v64, &v, sizeof(T)); v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); std::memcpy(&v, &v64, sizeof(T)); } } #ifdef _WIN32 constexpr bool IS_LITTLE_ENDIAN = true; constexpr bool IS_BIG_ENDIAN = false; #else constexpr bool IS_LITTLE_ENDIAN = __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__; constexpr bool IS_BIG_ENDIAN = __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; #endif // Usage: swapEndiannessInplaceIf(v) - native to little-endian or back // swapEndiannessInplaceIf(v) - native to big-endian or back template void swapEndiannessInplaceIf(T& v) { static_assert(std::is_arithmetic_v); static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); if constexpr (C) swapEndiannessInplace(v); } template T toLittleEndian(T v) { swapEndiannessInplaceIf(v); return v; } template T fromLittleEndian(T v) { swapEndiannessInplaceIf(v); return v; } template T toBigEndian(T v) { swapEndiannessInplaceIf(v); return v; } template T fromBigEndian(T v) { swapEndiannessInplaceIf(v); return v; } } #endif // COMPONENTS_MISC_ENDIANNESS_H openmw-openmw-0.48.0/components/misc/errorMarker.cpp000066400000000000000000001311631445372753700225600ustar00rootroot00000000000000#include "errorMarker.hpp" namespace Misc { const std::string errorMarker = "#Ascii Scene " "#Version 162 " "#Generator OpenSceneGraph 3.6.5 " "" "osg::Group {" " UniqueID 1 " " Children 5 {" " osg::Group {" " UniqueID 2 " " Name \"Error\" " " Children 1 {" " osg::Geometry {" " UniqueID 3 " " DataVariance STATIC " " StateSet TRUE {" " osg::StateSet {" " UniqueID 4 " " DataVariance STATIC " " ModeList 1 {" " GL_BLEND ON " " }" " AttributeList 1 {" " osg::Material {" " UniqueID 5 " " Name \"Error\" " " Ambient TRUE Front 1 1 1 0.5 Back 1 1 1 0.5 " " Diffuse TRUE Front 0.8 0.704 0.32 0.5 Back 0.8 0.704 0.32 0.5 " " Specular TRUE Front 0.5 0.5 0.5 0.5 Back 0.5 0.5 0.5 0.5 " " Emission TRUE Front 1 0.88 0.4 0.5 Back 1 0.88 0.4 0.5 " " Shininess TRUE Front 28.8 Back 28.8 " " }" " Value OFF " " }" " RenderingHint 2 " " RenderBinMode USE_RENDERBIN_DETAILS " " BinNumber 10 " " BinName \"DepthSortedBin\" " " }" " }" " PrimitiveSetList 1 {" " osg::DrawElementsUShort {" " UniqueID 6 " " BufferObject TRUE {" " osg::ElementBufferObject {" " UniqueID 7 " " Target 34963 " " }" " }" " Mode TRIANGLES " " vector 108 {" " 0 1 2 3 " " 0 2 2 4 " " 3 5 3 4 " " 4 6 5 6 " " 7 8 8 9 " " 10 6 8 10 " " 5 6 11 11 " " 6 10 12 5 " " 11 13 12 11 " " 11 14 13 10 " " 15 11 11 15 " " 16 15 17 16 " " 18 16 17 17 " " 19 18 20 21 " " 22 23 20 22 " " 22 24 23 25 " " 23 24 24 26 " " 25 26 27 28 " " 28 29 30 26 " " 28 30 25 26 " " 31 31 26 30 " " 32 25 31 33 " " 32 31 31 34 " " 33 30 35 31 " " 31 35 36 35 " " 37 36 38 36 " " 37 37 39 38 " " " " }" " }" " }" " VertexArray TRUE {" " osg::Vec3Array {" " UniqueID 8 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 9 " " }" " }" " Binding BIND_PER_VERTEX " " vector 40 {" " -4.51996 -5.9634 -60.7026 " " 2e-06 -5.96339 -61.6017 " " 4.51996 -5.96339 -60.7026 " " -8.3518 -5.9634 -58.1422 " " 8.3518 -5.96339 -58.1422 " " -58.1422 -5.96341 -8.3518 " " 58.1422 -5.96339 -8.3518 " " 60.7026 -5.96339 -4.51996 " " 61.6017 -5.96339 0 " " 60.7026 -5.96339 4.51996 " " 58.1423 -5.96339 8.3518 " " -58.1422 -5.96341 8.3518 " " -60.7026 -5.96341 -4.51996 " " -61.6017 -5.96341 0 " " -60.7026 -5.96341 4.51996 " " 8.3518 -5.9634 58.1422 " " -8.3518 -5.96341 58.1422 " " 4.51997 -5.96341 60.7026 " " -4.51996 -5.96341 60.7026 " " 2e-06 -5.96341 61.6017 " " -60.7026 5.96339 -4.51996 " " -61.6017 5.96339 0 " " -60.7026 5.96339 4.51996 " " -58.1423 5.96339 -8.3518 " " -58.1422 5.96339 8.3518 " " -8.3518 5.9634 -58.1422 " " -8.3518 5.96339 58.1422 " " -4.51996 5.96339 60.7026 " " -2e-06 5.96339 61.6017 " " 4.51996 5.9634 60.7026 " " 8.3518 5.9634 58.1422 " " 8.3518 5.96341 -58.1422 " " -4.51997 5.96341 -60.7026 " " -2e-06 5.96341 -61.6017 " " 4.51996 5.96341 -60.7026 " " 58.1422 5.96341 8.3518 " " 58.1422 5.96341 -8.3518 " " 60.7026 5.96341 4.51996 " " 60.7026 5.96341 -4.51996 " " 61.6017 5.96341 0 " " }" " }" " }" " NormalArray TRUE {" " osg::Vec3Array {" " UniqueID 10 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 9 " " }" " }" " Binding BIND_PER_VERTEX " " vector 40 {" " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " }" " }" " }" " TexCoordArrayList 1 {" " osg::Vec2Array {" " UniqueID 11 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 9 " " }" " }" " Binding BIND_PER_VERTEX " " vector 40 {" " 0.37739 0.519384 " " 0.384197 0.509197 " " 0.394384 0.50239 " " 0.375 0.531401 " " 0.406401 0.5 " " 0.375 0.718599 " " 0.593599 0.5 " " 0.605616 0.50239 " " 0.615803 0.509197 " " 0.62261 0.519384 " " 0.625 0.531401 " " 0.406401 0.75 " " 0.37739 0.730616 " " 0.384197 0.740803 " " 0.394384 0.74761 " " 0.625 0.718599 " " 0.593599 0.75 " " 0.62261 0.730616 " " 0.605616 0.74761 " " 0.615803 0.740803 " " 0.37739 0.019384 " " 0.384197 0.009197 " " 0.394384 0.00239 " " 0.375 0.031401 " " 0.406401 0 " " 0.375 0.218599 " " 0.593599 0 " " 0.605616 0.00239 " " 0.615803 0.009197 " " 0.62261 0.019384 " " 0.625 0.031401 " " 0.406401 0.25 " " 0.37739 0.230616 " " 0.384197 0.240803 " " 0.394384 0.24761 " " 0.625 0.218599 " " 0.593599 0.25 " " 0.62261 0.230616 " " 0.605616 0.24761 " " 0.615803 0.240803 " " }" " }" " }" " }" " }" " }" " osg::Group {" " UniqueID 12 " " Name \"Error\" " " Children 1 {" " osg::Geometry {" " UniqueID 13 " " DataVariance STATIC " " StateSet TRUE {" " osg::StateSet {" " UniqueID 4 " " }" " }" " PrimitiveSetList 1 {" " osg::DrawElementsUShort {" " UniqueID 14 " " BufferObject TRUE {" " osg::ElementBufferObject {" " UniqueID 15 " " Target 34963 " " }" " }" " Mode TRIANGLES " " vector 120 {" " 0 1 2 0 " " 3 1 3 4 " " 1 3 5 4 " " 4 5 6 4 " " 6 7 8 7 " " 6 8 6 9 " " 10 8 9 10 " " 9 11 12 13 " " 11 12 11 14 " " 15 12 14 15 " " 14 16 16 17 " " 15 16 18 17 " " 18 19 17 18 " " 20 19 20 21 " " 19 20 22 21 " " 22 23 24 22 " " 25 23 25 26 " " 23 25 27 26 " " 28 26 27 28 " " 29 26 30 29 " " 28 30 28 31 " " 32 30 31 32 " " 31 33 34 35 " " 33 34 33 36 " " 37 34 36 37 " " 36 38 39 37 " " 38 39 38 40 " " 40 41 39 40 " " 42 41 42 43 " " 41 42 0 43 " " " " }" " }" " }" " VertexArray TRUE {" " osg::Vec3Array {" " UniqueID 16 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 17 " " }" " }" " Binding BIND_PER_VERTEX " " vector 44 {" " 61.6017 -5.96339 0 " " 60.7026 5.96341 -4.51996 " " 61.6017 5.96341 0 " " 60.7026 -5.96339 -4.51996 " " 58.1422 5.96341 -8.3518 " " 58.1422 -5.96339 -8.3518 " " 8.3518 -5.96339 -58.1422 " " 8.3518 5.96341 -58.1422 " " 4.51996 5.96341 -60.7026 " " 4.51996 -5.96339 -60.7026 " " -2e-06 5.96341 -61.6017 " " 2e-06 -5.96339 -61.6017 " " -4.51997 5.96341 -60.7026 " " -2e-06 5.96341 -61.6017 " " -4.51996 -5.9634 -60.7026 " " -8.3518 5.9634 -58.1422 " " -8.3518 -5.9634 -58.1422 " " -58.1423 5.96339 -8.3518 " " -58.1422 -5.96341 -8.3518 " " -60.7026 5.96339 -4.51996 " " -60.7026 -5.96341 -4.51996 " " -61.6017 5.96339 0 " " -61.6017 -5.96341 0 " " -60.7026 5.96339 4.51996 " " -61.6017 5.96339 0 " " -60.7026 -5.96341 4.51996 " " -58.1422 5.96339 8.3518 " " -58.1422 -5.96341 8.3518 " " -8.3518 -5.96341 58.1422 " " -8.3518 5.96339 58.1422 " " -4.51996 5.96339 60.7026 " " -4.51996 -5.96341 60.7026 " " -2e-06 5.96339 61.6017 " " 2e-06 -5.96341 61.6017 " " 4.51996 5.9634 60.7026 " " -2e-06 5.96339 61.6017 " " 4.51997 -5.96341 60.7026 " " 8.3518 5.9634 58.1422 " " 8.3518 -5.9634 58.1422 " " 58.1422 5.96341 8.3518 " " 58.1423 -5.96339 8.3518 " " 60.7026 5.96341 4.51996 " " 60.7026 -5.96339 4.51996 " " 61.6017 5.96341 0 " " }" " }" " }" " NormalArray TRUE {" " osg::Vec3Array {" " UniqueID 18 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 17 " " }" " }" " Binding BIND_PER_VERTEX " " vector 44 {" " 1 0 0 " " 0.923877 0 -0.38269 " " 0.980784 0 -0.195097 " " 0.923877 0 -0.38269 " " 0.773003 0 -0.634402 " " 0.773003 0 -0.634402 " " 0.634402 0 -0.773003 " " 0.634402 0 -0.773003 " " 0.38269 0 -0.923877 " " 0.38269 0 -0.923877 " " 0.195097 0 -0.980784 " " 0 0 -1 " " -0.38269 -0 -0.923877 " " -0.195097 -0 -0.980784 " " -0.38269 -0 -0.923877 " " -0.634402 -0 -0.773003 " " -0.634402 -0 -0.773003 " " -0.773003 -0 -0.634402 " " -0.773003 -0 -0.634402 " " -0.923877 -0 -0.38269 " " -0.923877 -0 -0.38269 " " -0.980784 -0 -0.195097 " " -1 0 0 " " -0.923877 0 0.38269 " " -0.980784 0 0.195097 " " -0.923877 0 0.38269 " " -0.773003 0 0.634402 " " -0.773003 0 0.634402 " " -0.634402 0 0.773003 " " -0.634402 0 0.773003 " " -0.38269 0 0.923877 " " -0.38269 0 0.923877 " " -0.195097 0 0.980784 " " 0 0 1 " " 0.38269 0 0.923877 " " 0.195097 0 0.980784 " " 0.38269 0 0.923877 " " 0.634402 0 0.773003 " " 0.634402 0 0.773003 " " 0.773003 0 0.634402 " " 0.773003 0 0.634402 " " 0.923877 0 0.38269 " " 0.923877 0 0.38269 " " 0.980784 0 0.195097 " " }" " }" " }" " TexCoordArrayList 1 {" " osg::Vec2Array {" " UniqueID 19 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 17 " " }" " }" " Binding BIND_PER_VERTEX " " vector 44 {" " 0.625 0.5 " " 0.605616 0.25 " " 0.625 0.25 " " 0.605616 0.5 " " 0.593599 0.25 " " 0.593599 0.5 " " 0.406401 0.5 " " 0.406401 0.25 " " 0.394384 0.25 " " 0.394384 0.5 " " 0.375 0.25 " " 0.375 0.5 " " 0.125 0.519384 " " 0.125 0.5 " " 0.375 0.519384 " " 0.125 0.531401 " " 0.375 0.531401 " " 0.125 0.718599 " " 0.375 0.718599 " " 0.125 0.730616 " " 0.375 0.730616 " " 0.125 0.75 " " 0.375 0.75 " " 0.394384 1 " " 0.375 1 " " 0.394384 0.75 " " 0.406401 1 " " 0.406401 0.75 " " 0.593599 0.75 " " 0.593599 1 " " 0.605616 1 " " 0.605616 0.75 " " 0.625 1 " " 0.625 0.75 " " 0.875 0.730616 " " 0.875 0.75 " " 0.625 0.730616 " " 0.875 0.718599 " " 0.625 0.718599 " " 0.875 0.531401 " " 0.625 0.531401 " " 0.875 0.519384 " " 0.625 0.519384 " " 0.875 0.5 " " }" " }" " }" " }" " }" " }" " osg::Group {" " UniqueID 20 " " Name \"Error\" " " Children 1 {" " osg::Geometry {" " UniqueID 21 " " DataVariance STATIC " " StateSet TRUE {" " osg::StateSet {" " UniqueID 22 " " DataVariance STATIC " " AttributeList 1 {" " osg::Material {" " UniqueID 23 " " Name \"ErrorLabel\" " " Ambient TRUE Front 1 1 1 1 Back 1 1 1 1 " " Diffuse TRUE Front 0.176208 0.176208 0.176208 1 Back 0.176208 0.176208 0.176208 1 " " Specular TRUE Front 0.5 0.5 0.5 1 Back 0.5 0.5 0.5 1 " " Emission TRUE Front 0.22026 0.22026 0.22026 1 Back 0.22026 0.22026 0.22026 1 " " Shininess TRUE Front 28.8 Back 28.8 " " }" " Value OFF " " }" " }" " }" " PrimitiveSetList 1 {" " osg::DrawElementsUShort {" " UniqueID 24 " " BufferObject TRUE {" " osg::ElementBufferObject {" " UniqueID 25 " " Target 34963 " " }" " }" " Mode TRIANGLES " " vector 216 {" " 0 1 2 3 " " 0 2 2 4 " " 3 4 5 3 " " 5 6 7 5 " " 8 3 8 5 " " 7 7 9 8 " " 10 3 8 8 " " 11 10 12 13 " " 10 14 12 10 " " 15 14 10 10 " " 11 15 16 15 " " 11 11 17 16 " " 18 16 17 17 " " 19 18 20 21 " " 22 23 20 22 " " 22 24 23 25 " " 23 24 24 26 " " 25 26 27 28 " " 28 29 30 26 " " 28 30 25 26 " " 31 31 26 30 " " 32 25 31 33 " " 32 31 31 34 " " 33 30 35 31 " " 31 35 36 35 " " 37 36 38 36 " " 37 37 39 38 " " 40 41 42 43 " " 40 42 42 44 " " 43 45 43 44 " " 44 46 45 47 " " 45 46 48 47 " " 46 46 49 48 " " 44 50 46 51 " " 46 50 50 52 " " 53 54 50 53 " " 53 55 54 50 " " 54 51 54 56 " " 51 56 57 51 " " 58 51 57 57 " " 59 58 60 61 " " 62 63 60 62 " " 62 64 63 65 " " 63 64 64 66 " " 65 66 67 68 " " 69 70 65 71 " " 69 65 72 71 " " 65 66 68 73 " " 65 66 73 68 " " 74 73 73 72 " " 65 73 75 72 " " 72 75 76 75 " " 77 76 78 76 " " 77 77 79 78 " " " " }" " }" " }" " VertexArray TRUE {" " osg::Vec3Array {" " UniqueID 26 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 27 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " -7.12646 -9.95049 -13.7349 " " -6.35115 -9.95049 -14.8952 " " -5.1908 -9.95049 -15.6705 " " -7.39872 -9.95049 -12.3661 " " -3.82208 -9.95049 -15.9428 " " 3.82208 -9.95049 -15.9428 " " 5.1908 -9.95049 -15.6705 " " 6.35115 -9.95049 -14.8952 " " 7.39872 -9.95049 -12.3661 " " 7.12646 -9.95049 -13.7349 " " -7.39872 -9.95049 33.4208 " " 7.39872 -9.95049 33.4208 " " -6.35115 -9.95049 35.9499 " " -7.12647 -9.95049 34.7895 " " -5.1908 -9.95049 36.7252 " " -3.82208 -9.95049 36.9975 " " 3.82208 -9.95049 36.9975 " " 7.12646 -9.95049 34.7895 " " 5.1908 -9.95049 36.7252 " " 6.35115 -9.95049 35.9499 " " -7.12646 -9.95042 -36.7346 " " -6.35115 -9.95042 -37.8949 " " -5.1908 -9.95042 -38.6702 " " -7.39872 -9.95042 -35.3659 " " -3.82208 -9.95042 -38.9425 " " -7.39872 -9.95042 -27.7217 " " 3.82208 -9.95042 -38.9425 " " 5.1908 -9.95042 -38.6702 " " 6.35115 -9.95042 -37.8949 " " 7.12646 -9.95042 -36.7346 " " 7.39872 -9.95042 -35.3659 " " -3.82208 -9.95042 -24.1451 " " -7.12647 -9.95042 -26.353 " " -6.35115 -9.95042 -25.1926 " " -5.1908 -9.95042 -24.4173 " " 7.39872 -9.95042 -27.7217 " " 3.82208 -9.95042 -24.1451 " " 7.12646 -9.95042 -26.353 " " 5.1908 -9.95042 -24.4173 " " 6.35115 -9.95042 -25.1926 " " -5.1908 9.95055 -15.6705 " " -6.35115 9.95055 -14.8952 " " -7.12646 9.95055 -13.7349 " " -3.82208 9.95055 -15.9428 " " -7.39872 9.95055 -12.3661 " " 3.82208 9.95055 -15.9428 " " 7.39872 9.95055 -12.3661 " " 5.1908 9.95055 -15.6705 " " 6.35115 9.95055 -14.8952 " " 7.12646 9.95055 -13.7349 " " -7.39872 9.95055 33.4208 " " 7.39872 9.95055 33.4208 " " -7.12646 9.95055 34.7895 " " -6.35115 9.95056 35.9499 " " -3.82208 9.95056 36.9975 " " -5.1908 9.95056 36.7252 " " 3.82208 9.95055 36.9975 " " 5.19081 9.95055 36.7252 " " 7.12646 9.95056 34.7895 " " 6.35115 9.95055 35.9499 " " -5.1908 9.95062 -38.6702 " " -6.35115 9.95062 -37.8949 " " -7.12646 9.95062 -36.7346 " " -3.82208 9.95062 -38.9425 " " -7.39872 9.95062 -35.3659 " " 3.82208 9.95062 -38.9425 " " -7.39872 9.95063 -27.7217 " " -7.12646 9.95063 -26.353 " " -6.35115 9.95063 -25.1926 " " 6.35115 9.95062 -37.8949 " " 5.1908 9.95062 -38.6702 " " 7.12647 9.95062 -36.7346 " " 7.39872 9.95062 -35.3659 " " -3.82208 9.95063 -24.1451 " " -5.1908 9.95063 -24.4173 " " 3.82208 9.95063 -24.1451 " " 7.39872 9.95062 -27.7217 " " 5.1908 9.95063 -24.4173 " " 7.12646 9.95063 -26.353 " " 6.35115 9.95063 -25.1926 " " }" " }" " }" " NormalArray TRUE {" " osg::Vec3Array {" " UniqueID 28 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 27 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " }" " }" " }" " TexCoordArrayList 1 {" " osg::Vec2Array {" " UniqueID 29 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 27 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " 0.006133 0.041706 " " 0.023598 0.006596 " " 0.149209 0.001714 " " 0 0.06756 " " 0.241707 0 " " 0.758294 0 " " 0.850791 0.001714 " " 0.976402 0.006596 " " 1 0.06756 " " 0.993867 0.041706 " " 0 0.93244 " " 1 0.93244 " " 0.023598 0.993404 " " 0.006133 0.958294 " " 0.149209 0.998286 " " 0.241706 1 " " 0.758294 1 " " 0.993867 0.958294 " " 0.850791 0.998286 " " 0.976402 0.993404 " " 0.006133 0.149209 " " 0.023598 0.023598 " " 0.149209 0.006133 " " 0 0.241707 " " 0.241707 0 " " 0 0.758294 " " 0.758294 0 " " 0.850791 0.006133 " " 0.976402 0.023598 " " 0.993867 0.149209 " " 1 0.241707 " " 0.241706 1 " " 0.006133 0.850791 " " 0.023598 0.976402 " " 0.149209 0.993867 " " 1 0.758293 " " 0.758294 1 " " 0.993867 0.850791 " " 0.850791 0.993867 " " 0.976402 0.976402 " " 0.149209 0.001714 " " 0.023598 0.006596 " " 0.006133 0.041706 " " 0.241706 0 " " 0 0.06756 " " 0.758294 0 " " 1 0.06756 " " 0.850791 0.001714 " " 0.976402 0.006596 " " 0.993867 0.041706 " " 0 0.93244 " " 1 0.93244 " " 0.006133 0.958294 " " 0.023598 0.993404 " " 0.241707 1 " " 0.149209 0.998286 " " 0.758294 1 " " 0.850791 0.998286 " " 0.993867 0.958294 " " 0.976402 0.993404 " " 0.149209 0.006133 " " 0.023598 0.023598 " " 0.006133 0.149209 " " 0.241706 0 " " 0 0.241707 " " 0.758294 0 " " 0 0.758294 " " 0.006133 0.850791 " " 0.023598 0.976402 " " 0.976402 0.023598 " " 0.850791 0.006133 " " 0.993867 0.149209 " " 1 0.241706 " " 0.241707 1 " " 0.149209 0.993867 " " 0.758294 1 " " 1 0.758294 " " 0.850791 0.993867 " " 0.993867 0.850791 " " 0.976402 0.976402 " " }" " }" " }" " }" " }" " }" " osg::Group {" " UniqueID 30 " " Name \"Error\" " " Children 1 {" " osg::Geometry {" " UniqueID 31 " " DataVariance STATIC " " StateSet TRUE {" " osg::StateSet {" " UniqueID 22 " " }" " }" " PrimitiveSetList 1 {" " osg::DrawElementsUShort {" " UniqueID 32 " " BufferObject TRUE {" " osg::ElementBufferObject {" " UniqueID 33 " " Target 34963 " " }" " }" " Mode TRIANGLES " " vector 240 {" " 0 1 2 0 " " 3 1 0 2 " " 4 0 5 3 " " 4 2 6 5 " " 7 3 4 6 " " 8 5 9 7 " " 6 10 8 9 " " 11 7 6 12 " " 10 9 13 11 " " 12 14 10 13 " " 15 11 12 16 " " 14 13 17 15 " " 16 18 14 19 " " 15 17 16 20 " " 18 19 21 15 " " 18 20 22 23 " " 21 19 18 22 " " 24 23 19 25 " " 26 24 22 27 " " 23 25 26 22 " " 28 27 25 29 " " 30 26 28 31 " " 27 29 30 32 " " 26 31 29 33 " " 34 32 30 35 " " 31 33 34 30 " " 36 35 33 37 " " 38 34 36 39 " " 35 37 38 39 " " 34 39 38 35 " " 40 41 42 43 " " 41 40 40 42 " " 44 43 40 45 " " 46 44 42 47 " " 43 45 46 48 " " 44 47 45 49 " " 50 48 46 51 " " 47 49 50 46 " " 52 51 49 53 " " 54 50 52 55 " " 51 53 54 52 " " 56 55 53 57 " " 58 54 56 57 " " 59 55 58 56 " " 60 57 61 59 " " 58 60 62 61 " " 63 59 58 62 " " 64 61 65 63 " " 62 66 64 65 " " 67 63 62 68 " " 66 65 69 67 " " 66 68 70 69 " " 71 67 66 70 " " 72 69 73 71 " " 70 74 72 71 " " 73 75 70 76 " " 74 71 75 77 " " 76 78 74 75 " " 79 77 76 79 " " 78 75 78 79 " " " " }" " }" " }" " VertexArray TRUE {" " osg::Vec3Array {" " UniqueID 34 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 35 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " -7.39872 9.95055 -12.3661 " " -7.39872 -9.95049 -12.3661 " " -7.39872 -9.95049 33.4208 " " -7.12646 -9.95049 -13.7349 " " -7.39872 9.95055 33.4208 " " -7.12646 9.95055 -13.7349 " " -7.12647 -9.95049 34.7895 " " -6.35115 -9.95049 -14.8952 " " -7.12646 9.95055 34.7895 " " -6.35115 9.95055 -14.8952 " " -6.35115 9.95056 35.9499 " " -5.1908 -9.95049 -15.6705 " " -6.35115 -9.95049 35.9499 " " -5.1908 9.95055 -15.6705 " " -5.1908 9.95056 36.7252 " " -3.82208 -9.95049 -15.9428 " " -5.1908 -9.95049 36.7252 " " -3.82208 9.95055 -15.9428 " " -3.82208 9.95056 36.9975 " " 3.82208 9.95055 -15.9428 " " -3.82208 -9.95049 36.9975 " " 3.82208 -9.95049 -15.9428 " " 3.82208 -9.95049 36.9975 " " 5.1908 -9.95049 -15.6705 " " 3.82208 9.95055 36.9975 " " 5.1908 9.95055 -15.6705 " " 5.19081 9.95055 36.7252 " " 6.35115 -9.95049 -14.8952 " " 5.1908 -9.95049 36.7252 " " 6.35115 9.95055 -14.8952 " " 6.35115 -9.95049 35.9499 " " 7.12646 -9.95049 -13.7349 " " 6.35115 9.95055 35.9499 " " 7.12646 9.95055 -13.7349 " " 7.12646 9.95056 34.7895 " " 7.39872 -9.95049 -12.3661 " " 7.12646 -9.95049 34.7895 " " 7.39872 9.95055 -12.3661 " " 7.39872 -9.95049 33.4208 " " 7.39872 9.95055 33.4208 " " -3.82208 9.95063 -24.1451 " " -3.82208 -9.95042 -24.1451 " " 3.82208 -9.95042 -24.1451 " " -5.1908 -9.95042 -24.4173 " " 3.82208 9.95063 -24.1451 " " -5.1908 9.95063 -24.4173 " " 5.1908 -9.95042 -24.4173 " " -6.35115 -9.95042 -25.1926 " " 5.1908 9.95063 -24.4173 " " -6.35115 9.95063 -25.1926 " " 6.35115 9.95063 -25.1926 " " -7.12647 -9.95042 -26.353 " " 6.35115 -9.95042 -25.1926 " " -7.12646 9.95063 -26.353 " " 7.12646 9.95063 -26.353 " " -7.39872 -9.95042 -27.7217 " " 7.12646 -9.95042 -26.353 " " -7.39872 9.95063 -27.7217 " " 7.39872 9.95062 -27.7217 " " -7.39872 -9.95042 -35.3659 " " 7.39872 -9.95042 -27.7217 " " -7.39872 9.95062 -35.3659 " " 7.39872 -9.95042 -35.3659 " " -7.12646 -9.95042 -36.7346 " " 7.39872 9.95062 -35.3659 " " -7.12646 9.95062 -36.7346 " " 7.12647 9.95062 -36.7346 " " -6.35115 -9.95042 -37.8949 " " 7.12646 -9.95042 -36.7346 " " -6.35115 9.95062 -37.8949 " " 6.35115 -9.95042 -37.8949 " " -5.1908 -9.95042 -38.6702 " " 6.35115 9.95062 -37.8949 " " -5.1908 9.95062 -38.6702 " " 5.1908 9.95062 -38.6702 " " -3.82208 9.95062 -38.9425 " " 5.1908 -9.95042 -38.6702 " " -3.82208 -9.95042 -38.9425 " " 3.82208 9.95062 -38.9425 " " 3.82208 -9.95042 -38.9425 " " }" " }" " }" " NormalArray TRUE {" " osg::Vec3Array {" " UniqueID 36 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 35 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " -0.995187 -0 -0.0979987 " " -0.995187 -0 -0.0979987 " " -0.995187 0 0.0979987 " " -0.923877 -0 -0.38269 " " -0.995187 0 0.0979987 " " -0.923877 -0 -0.38269 " " -0.923877 0 0.38269 " " -0.707107 -0 -0.707107 " " -0.923877 0 0.38269 " " -0.707107 -0 -0.707107 " " -0.707107 0 0.707107 " " -0.38269 -0 -0.923877 " " -0.707107 0 0.707107 " " -0.38269 -0 -0.923877 " " -0.38269 0 0.923877 " " -0.0979987 -0 -0.995187 " " -0.38269 0 0.923877 " " -0.0979987 -0 -0.995187 " " -0.0979987 0 0.995187 " " 0.0979987 0 -0.995187 " " -0.0979987 0 0.995187 " " 0.0979987 0 -0.995187 " " 0.0979987 0 0.995187 " " 0.38269 0 -0.923877 " " 0.0979987 0 0.995187 " " 0.38269 0 -0.923877 " " 0.38269 0 0.923877 " " 0.707107 0 -0.707107 " " 0.38269 0 0.923877 " " 0.707107 0 -0.707107 " " 0.707107 0 0.707107 " " 0.923877 0 -0.38269 " " 0.707107 0 0.707107 " " 0.923877 0 -0.38269 " " 0.923877 0 0.38269 " " 0.995187 0 -0.0979987 " " 0.923877 0 0.38269 " " 0.995187 0 -0.0979987 " " 0.995187 0 0.0979987 " " 0.995187 0 0.0979987 " " -0.0979987 0 0.995187 " " -0.0979987 0 0.995187 " " 0.0979987 0 0.995187 " " -0.38269 0 0.923877 " " 0.0979987 0 0.995187 " " -0.38269 0 0.923877 " " 0.38269 0 0.923877 " " -0.707107 0 0.707107 " " 0.38269 0 0.923877 " " -0.707107 0 0.707107 " " 0.707107 0 0.707107 " " -0.923877 0 0.38269 " " 0.707107 0 0.707107 " " -0.923877 0 0.38269 " " 0.923877 0 0.38269 " " -0.995187 0 0.0979987 " " 0.923877 0 0.38269 " " -0.995187 0 0.0979987 " " 0.995187 0 0.0979987 " " -0.995187 -0 -0.0979987 " " 0.995187 0 0.0979987 " " -0.995187 -0 -0.0979987 " " 0.995187 0 -0.0979987 " " -0.923877 -0 -0.38269 " " 0.995187 0 -0.0979987 " " -0.923877 -0 -0.38269 " " 0.923877 0 -0.38269 " " -0.707107 -0 -0.707107 " " 0.923877 0 -0.38269 " " -0.707107 -0 -0.707107 " " 0.707107 0 -0.707107 " " -0.38269 -0 -0.923877 " " 0.707107 0 -0.707107 " " -0.38269 -0 -0.923877 " " 0.38269 0 -0.923877 " " -0.0979987 -0 -0.995187 " " 0.38269 0 -0.923877 " " -0.0979987 -0 -0.995187 " " 0.0979987 0 -0.995187 " " 0.0979987 0 -0.995187 " " }" " }" " }" " TexCoordArrayList 1 {" " osg::Vec2Array {" " UniqueID 37 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 35 " " }" " }" " Binding BIND_PER_VERTEX " " vector 80 {" " 0 0.06756 " " 0 0.06756 " " 0 0.93244 " " 0.006133 0.041706 " " 0 0.93244 " " 0.006133 0.041706 " " 0.006133 0.958294 " " 0.023598 0.006596 " " 0.006133 0.958294 " " 0.023598 0.006596 " " 0.023598 0.993404 " " 0.149209 0.001714 " " 0.023598 0.993404 " " 0.149209 0.001714 " " 0.149209 0.998286 " " 0.241706 0 " " 0.149209 0.998286 " " 0.241707 0 " " 0.241706 1 " " 0.758294 0 " " 0.241707 1 " " 0.758294 0 " " 0.758294 1 " " 0.850791 0.001714 " " 0.758294 1 " " 0.850791 0.001714 " " 0.850791 0.998286 " " 0.976402 0.006596 " " 0.850791 0.998286 " " 0.976402 0.006596 " " 0.976402 0.993404 " " 0.993867 0.041706 " " 0.976402 0.993404 " " 0.993867 0.041706 " " 0.993867 0.958294 " " 1 0.06756 " " 0.993867 0.958294 " " 1 0.06756 " " 1 0.93244 " " 1 0.93244 " " 0.241706 1 " " 0.241707 1 " " 0.758294 1 " " 0.149209 0.993867 " " 0.758294 1 " " 0.149209 0.993867 " " 0.850791 0.993867 " " 0.023598 0.976402 " " 0.850791 0.993867 " " 0.023598 0.976402 " " 0.976402 0.976402 " " 0.006133 0.850791 " " 0.976402 0.976402 " " 0.006133 0.850791 " " 0.993867 0.850791 " " 0 0.758293 " " 0.993867 0.850791 " " 0 0.758294 " " 1 0.758294 " " 0 0.241707 " " 1 0.758294 " " 0 0.241706 " " 1 0.241707 " " 0.006133 0.149209 " " 1 0.241707 " " 0.006133 0.149209 " " 0.993867 0.149209 " " 0.023598 0.023598 " " 0.993867 0.149209 " " 0.023598 0.023598 " " 0.976402 0.023598 " " 0.149209 0.006133 " " 0.976402 0.023598 " " 0.149209 0.006133 " " 0.850791 0.006133 " " 0.241707 0 " " 0.850791 0.006133 " " 0.241706 0 " " 0.758294 0 " " 0.758294 0 " " }" " }" " }" " }" " }" " }" " osg::Group {" " UniqueID 38 " " Name \"Cube\" " " Children 1 {" " osg::Geometry {" " UniqueID 39 " " DataVariance STATIC " " StateSet TRUE {" " osg::StateSet {" " UniqueID 40 " " DataVariance STATIC " " AttributeList 1 {" " osg::Material {" " UniqueID 41 " " Name \"Material\" " " Ambient TRUE Front 1 1 1 1 Back 1 1 1 1 " " Diffuse TRUE Front 0.8 0.8 0.8 1 Back 0.8 0.8 0.8 1 " " Specular TRUE Front 0.5 0.5 0.5 1 Back 0.5 0.5 0.5 1 " " Emission TRUE Front 0 0 0 1 Back 0 0 0 1 " " Shininess TRUE Front 41.344 Back 41.344 " " }" " Value OFF " " }" " }" " }" " PrimitiveSetList 1 {" " osg::DrawElementsUShort {" " UniqueID 42 " " BufferObject TRUE {" " osg::ElementBufferObject {" " UniqueID 43 " " Target 34963 " " }" " }" " Mode TRIANGLES " " vector 36 {" " 0 1 2 0 " " 2 3 4 5 " " 6 4 6 7 " " 8 9 10 8 " " 10 11 12 13 " " 14 12 14 15 " " 16 17 18 16 " " 18 19 20 21 " " 22 20 22 23 " " " " }" " }" " }" " VertexArray TRUE {" " osg::Vec3Array {" " UniqueID 44 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 45 " " }" " }" " Binding BIND_PER_VERTEX " " vector 24 {" " 1 1 1 " " -1 1 1 " " -1 -1 1 " " 1 -1 1 " " 1 -1 -1 " " 1 -1 1 " " -1 -1 1 " " -1 -1 -1 " " -1 -1 -1 " " -1 -1 1 " " -1 1 1 " " -1 1 -1 " " -1 1 -1 " " 1 1 -1 " " 1 -1 -1 " " -1 -1 -1 " " 1 1 -1 " " 1 1 1 " " 1 -1 1 " " 1 -1 -1 " " -1 1 -1 " " -1 1 1 " " 1 1 1 " " 1 1 -1 " " }" " }" " }" " NormalArray TRUE {" " osg::Vec3Array {" " UniqueID 46 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 45 " " }" " }" " Binding BIND_PER_VERTEX " " vector 24 {" " 0 0 1 " " 0 0 1 " " 0 0 1 " " 0 0 1 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " 0 -1 0 " " -1 0 0 " " -1 0 0 " " -1 0 0 " " -1 0 0 " " 0 0 -1 " " 0 0 -1 " " 0 0 -1 " " 0 0 -1 " " 1 0 0 " " 1 0 0 " " 1 0 0 " " 1 0 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " 0 1 0 " " }" " }" " }" " TexCoordArrayList 1 {" " osg::Vec2Array {" " UniqueID 47 " " BufferObject TRUE {" " osg::VertexBufferObject {" " UniqueID 45 " " }" " }" " Binding BIND_PER_VERTEX " " vector 24 {" " 0.625 0.5 " " 0.875 0.5 " " 0.875 0.75 " " 0.625 0.75 " " 0.375 0.75 " " 0.625 0.75 " " 0.625 1 " " 0.375 1 " " 0.375 0 " " 0.625 0 " " 0.625 0.25 " " 0.375 0.25 " " 0.125 0.5 " " 0.375 0.5 " " 0.375 0.75 " " 0.125 0.75 " " 0.375 0.5 " " 0.625 0.5 " " 0.625 0.75 " " 0.375 0.75 " " 0.375 0.25 " " 0.625 0.25 " " 0.625 0.5 " " 0.375 0.5 " " }" " }" " }" " }" " }" " }" " }" "}"; } openmw-openmw-0.48.0/components/misc/errorMarker.hpp000066400000000000000000000002631445372753700225610ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_ERRORMARKER_H #define OPENMW_COMPONENTS_MISC_ERRORMARKER_H #include namespace Misc { extern const std::string errorMarker; } #endif openmw-openmw-0.48.0/components/misc/frameratelimiter.hpp000066400000000000000000000037471445372753700236340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H #define OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H #include #include namespace Misc { class FrameRateLimiter { public: template explicit FrameRateLimiter(std::chrono::duration maxFrameDuration, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) : mMaxFrameDuration(std::chrono::duration_cast(maxFrameDuration)) , mLastMeasurement(now) , mLastFrameDuration(0) {} std::chrono::steady_clock::duration getLastFrameDuration() const { return mLastFrameDuration; } void limit(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) { const auto passed = now - mLastMeasurement; const auto left = mMaxFrameDuration - passed; if (left > left.zero()) { std::this_thread::sleep_for(left); mLastMeasurement = now + left; mLastFrameDuration = mMaxFrameDuration; } else { mLastMeasurement = now; mLastFrameDuration = passed; } } private: std::chrono::steady_clock::duration mMaxFrameDuration; std::chrono::steady_clock::time_point mLastMeasurement; std::chrono::steady_clock::duration mLastFrameDuration; }; inline Misc::FrameRateLimiter makeFrameRateLimiter(float frameRateLimit) { if (frameRateLimit > 0.0f) return Misc::FrameRateLimiter(std::chrono::duration(1.0f / frameRateLimit)); else return Misc::FrameRateLimiter(std::chrono::steady_clock::duration::zero()); } } #endif openmw-openmw-0.48.0/components/misc/guarded.hpp000066400000000000000000000044621445372753700217060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_GUARDED_H #define OPENMW_COMPONENTS_MISC_GUARDED_H #include #include #include #include namespace Misc { template class Locked { public: Locked(std::mutex& mutex, std::remove_reference_t& value) : mLock(mutex), mValue(value) {} std::remove_reference_t& get() const { return mValue.get(); } std::remove_reference_t* operator ->() const { return &get(); } std::remove_reference_t& operator *() const { return get(); } private: std::unique_lock mLock; std::reference_wrapper> mValue; }; template class ScopeGuarded { public: ScopeGuarded() : mMutex() , mValue() {} ScopeGuarded(const T& value) : mMutex() , mValue(value) {} ScopeGuarded(T&& value) : mMutex() , mValue(std::move(value)) {} template ScopeGuarded(Args&& ... args) : mMutex() , mValue(std::forward(args) ...) {} ScopeGuarded(const ScopeGuarded& other) : mMutex() , mValue(other.lock().get()) {} ScopeGuarded(ScopeGuarded&& other) : mMutex() , mValue(std::move(other.lock().get())) {} Locked lock() { return Locked(mMutex, mValue); } Locked lockConst() const { return Locked(mMutex, mValue); } template void wait(std::condition_variable& cv, Predicate&& predicate) { std::unique_lock lock(mMutex); cv.wait(lock, [&] { return predicate(mValue); }); } private: mutable std::mutex mMutex; T mValue; }; } #endif openmw-openmw-0.48.0/components/misc/hash.hpp000066400000000000000000000007531445372753700212150ustar00rootroot00000000000000#ifndef MISC_HASH_H #define MISC_HASH_H #include #include #include namespace Misc { /// Implemented similar to the boost::hash_combine template inline void hashCombine(Seed& seed, const T& v) { static_assert(sizeof(Seed) >= sizeof(std::size_t), "Resulting hash will be truncated"); std::hash hasher; seed ^= static_cast(hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2)); } } #endif openmw-openmw-0.48.0/components/misc/helpviewer.cpp000066400000000000000000000003701445372753700224320ustar00rootroot00000000000000#include "helpviewer.hpp" #include #include #include void Misc::HelpViewer::openHelp(const char* url) { QString link {OPENMW_DOC_BASEURL}; link.append(url); QDesktopServices::openUrl(QUrl(link)); } openmw-openmw-0.48.0/components/misc/helpviewer.hpp000066400000000000000000000001521445372753700224350ustar00rootroot00000000000000#pragma once namespace Misc { namespace HelpViewer { void openHelp(const char* url); } } openmw-openmw-0.48.0/components/misc/math.hpp000066400000000000000000000005461445372753700212230ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_MATH_H #define OPENMW_COMPONENTS_MISC_MATH_H #include namespace Misc { inline osg::Vec3f getVectorToLine(const osg::Vec3f& position, const osg::Vec3f& a, const osg::Vec3f& b) { osg::Vec3f direction = b - a; direction.normalize(); return (position - a) ^ direction; } } #endif openmw-openmw-0.48.0/components/misc/mathutil.hpp000066400000000000000000000012541445372753700221160ustar00rootroot00000000000000#ifndef MISC_MATHUTIL_H #define MISC_MATHUTIL_H #include #include namespace Misc { /// Normalizes given angle to the range [-PI, PI]. E.g. PI*3/2 -> -PI/2. inline double normalizeAngle(double angle) { double fullTurns = angle / (2 * osg::PI) + 0.5; return (fullTurns - floor(fullTurns) - 0.5) * (2 * osg::PI); } /// Rotates given 2d vector counterclockwise. Angle is in radians. inline osg::Vec2f rotateVec2f(osg::Vec2f vec, float angle) { float s = std::sin(angle); float c = std::cos(angle); return osg::Vec2f(vec.x() * c + vec.y() * -s, vec.x() * s + vec.y() * c); } } #endif openmw-openmw-0.48.0/components/misc/messageformatparser.cpp000066400000000000000000000053631445372753700243410ustar00rootroot00000000000000#include "messageformatparser.hpp" namespace Misc { MessageFormatParser::~MessageFormatParser() {} void MessageFormatParser::process(std::string_view m) { for (unsigned int i = 0; i < m.size(); ++i) { if (m[i] == '%') { if (++i < m.size()) { if (m[i] == '%') visitedCharacter('%'); else { char pad = ' '; if (m[i] == '0' || m[i] == ' ') { pad = m[i]; ++i; } int width = 0; bool widthSet = false; while (i < m.size() && m[i] >= '0' && m[i] <= '9') { width = width * 10 + (m[i] - '0'); widthSet = true; ++i; } if (i < m.size()) { int precision = -1; if (m[i] == '.') { precision = 0; while (++i < m.size() && m[i] >= '0' && m[i] <= '9') { precision = precision * 10 + (m[i] - '0'); } } if (i < m.size()) { width = (widthSet) ? width : -1; if (m[i] == 'S' || m[i] == 's') visitedPlaceholder(StringPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'd' || m[i] == 'i') visitedPlaceholder(IntegerPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'f' || m[i] == 'F') visitedPlaceholder(FloatPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'e' || m[i] == 'E') visitedPlaceholder(FloatPlaceholder, pad, width, precision, ScientificNotation); else if (m[i] == 'g' || m[i] == 'G') visitedPlaceholder(FloatPlaceholder, pad, width, precision, ShortestNotation); } } } } } else { visitedCharacter(m[i]); } } } } openmw-openmw-0.48.0/components/misc/messageformatparser.hpp000066400000000000000000000015211445372753700243360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_MESSAGEFORMATPARSER_H #define OPENMW_COMPONENTS_MISC_MESSAGEFORMATPARSER_H #include namespace Misc { class MessageFormatParser { protected: enum Placeholder { StringPlaceholder, IntegerPlaceholder, FloatPlaceholder }; enum Notation { FixedNotation, ScientificNotation, ShortestNotation }; virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) = 0; virtual void visitedCharacter(char c) = 0; public: virtual ~MessageFormatParser(); virtual void process(std::string_view message); }; } #endif openmw-openmw-0.48.0/components/misc/notnullptr.hpp000066400000000000000000000011161445372753700225050ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_NOTNULLPTR_H #define OPENMW_COMPONENTS_MISC_NOTNULLPTR_H #include #include #include namespace Misc { template class NotNullPtr { public: NotNullPtr(T* value) : mValue(value) { assert(mValue != nullptr); } NotNullPtr(std::nullptr_t) = delete; operator T*() const { return mValue; } T* operator->() const { return mValue; } T& operator*() const { return *mValue; } private: T* mValue; }; } #endif openmw-openmw-0.48.0/components/misc/objectpool.hpp000066400000000000000000000036101445372753700224250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_OBJECTPOOL_H #define OPENMW_COMPONENTS_MISC_OBJECTPOOL_H #include #include #include namespace Misc { template class ObjectPool; template class ObjectPtrDeleter { public: ObjectPtrDeleter(std::nullptr_t) : mPool(nullptr) {} ObjectPtrDeleter(ObjectPool& pool) : mPool(&pool) {} void operator()(T* object) const { mPool->recycle(object); } private: ObjectPool* mPool; }; template struct ObjectPtr final : std::unique_ptr> { using std::unique_ptr>::unique_ptr; using std::unique_ptr>::operator=; ObjectPtr() : ObjectPtr(nullptr) {} ObjectPtr(std::nullptr_t) : std::unique_ptr>(nullptr, nullptr) {} }; template class ObjectPool { friend class ObjectPtrDeleter; public: ObjectPool() : mObjects(std::make_unique>()) {} ObjectPtr get() { T* object; if (!mUnused.empty()) { object = mUnused.back(); mUnused.pop_back(); } else { mObjects->emplace_back(); object = &mObjects->back(); } return ObjectPtr(object, ObjectPtrDeleter(*this)); } private: std::unique_ptr> mObjects; std::vector mUnused; void recycle(T* object) { mUnused.push_back(object); } }; } #endif openmw-openmw-0.48.0/components/misc/osguservalues.cpp000066400000000000000000000003541445372753700231710ustar00rootroot00000000000000#include "osguservalues.hpp" namespace Misc { const std::string OsgUserValues::sFileHash = "fileHash"; const std::string OsgUserValues::sExtraData = "xData"; const std::string OsgUserValues::sXSoftEffect = "xSoftEffect"; } openmw-openmw-0.48.0/components/misc/osguservalues.hpp000066400000000000000000000004731445372753700232000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H #define OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H #include namespace Misc { struct OsgUserValues { static const std::string sFileHash; static const std::string sExtraData; static const std::string sXSoftEffect; }; } #endif openmw-openmw-0.48.0/components/misc/pathhelpers.hpp000066400000000000000000000016451445372753700226120ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_PATHHELPERS_H #define OPENMW_COMPONENTS_MISC_PATHHELPERS_H #include namespace Misc { inline std::string_view getFileExtension(std::string_view file) { if (auto extPos = file.find_last_of('.'); extPos != std::string::npos) { file.remove_prefix(extPos + 1); return file; } return {}; } inline std::string_view getFileName(std::string_view path) { if (auto namePos = path.find_last_of("/\\"); namePos != std::string::npos) { path.remove_prefix(namePos + 1); } return path; } inline std::string_view stemFile(std::string_view path) { path = getFileName(path); if (auto extPos = path.find_last_of("."); extPos != std::string::npos) { path.remove_suffix(path.size() - extPos); } return path; } } #endif openmw-openmw-0.48.0/components/misc/progressreporter.hpp000066400000000000000000000031001445372753700237060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H #define OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H #include #include #include #include #include namespace Misc { template class ProgressReporter { public: explicit ProgressReporter(Report&& report = Report {}) : mReport(std::forward(report)) {} explicit ProgressReporter(std::chrono::steady_clock::duration interval, Report&& report = Report {}) : mInterval(interval) , mReport(std::forward(report)) {} void operator()(std::size_t provided, std::size_t expected) { expected = std::max(expected, provided); const bool shouldReport = [&] { const std::lock_guard lock(mMutex); const auto now = std::chrono::steady_clock::now(); if (mNextReport > now || provided == expected) return false; if (mInterval.count() > 0) mNextReport = mNextReport + mInterval * ((now - mNextReport + mInterval).count() / mInterval.count()); return true; } (); if (shouldReport) mReport(provided, expected); } private: const std::chrono::steady_clock::duration mInterval = std::chrono::seconds(1); Report mReport; std::mutex mMutex; std::chrono::steady_clock::time_point mNextReport {std::chrono::steady_clock::now() + mInterval}; }; } #endif openmw-openmw-0.48.0/components/misc/resourcehelpers.cpp000066400000000000000000000123301445372753700234710ustar00rootroot00000000000000#include "resourcehelpers.hpp" #include #include #include #include namespace { struct MatchPathSeparator { bool operator()( char ch ) const { return ch == '\\' || ch == '/'; } }; std::string getBasename( std::string const& pathname ) { return std::string( std::find_if( pathname.rbegin(), pathname.rend(), MatchPathSeparator() ).base(), pathname.end() ); } } bool changeExtension(std::string &path, std::string_view ext) { std::string::size_type pos = path.rfind('.'); if(pos != std::string::npos && path.compare(pos, path.length() - pos, ext) != 0) { path.replace(pos, path.length(), ext); return true; } return false; } bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) { return changeExtension(path, ".dds"); } std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all * texture file name references were kept as .tga. */ std::string prefix1 = topLevelDirectory + '\\'; std::string prefix2 = topLevelDirectory + '/'; std::string correctedPath = resPath; Misc::StringUtils::lowerCaseInPlace(correctedPath); // Apparently, leading separators are allowed while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) correctedPath.erase(0, 1); if(correctedPath.compare(0, prefix1.size(), prefix1.data()) != 0 && correctedPath.compare(0, prefix2.size(), prefix2.data()) != 0) correctedPath = prefix1 + correctedPath; std::string origExt = correctedPath; // since we know all (GOTY edition or less) textures end // in .dds, we change the extension bool changedToDds = changeExtensionToDds(correctedPath); if (vfs->exists(correctedPath)) return correctedPath; // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) // verify, and revert if false (this call succeeds quickly, but fails slowly) if (changedToDds && vfs->exists(origExt)) return origExt; // fall back to a resource in the top level directory if it exists std::string fallback = topLevelDirectory + "\\" + getBasename(correctedPath); if (vfs->exists(fallback)) return fallback; if (changedToDds) { fallback = topLevelDirectory + "\\" + getBasename(origExt); if (vfs->exists(fallback)) return fallback; } return correctedPath; } std::string Misc::ResourceHelpers::correctTexturePath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "textures"; return correctResourcePath(dir, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "icons"; return correctResourcePath(dir, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "bookart"; std::string image = correctResourcePath(dir, resPath, vfs); return image; } std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, int width, int height, const VFS::Manager* vfs) { std::string image = correctBookartPath(resPath, vfs); // Apparently a bug with some morrowind versions, they reference the image without the size suffix. // So if the image isn't found, try appending the size. if (!vfs->exists(image)) { std::stringstream str; str << image.substr(0, image.rfind('.')) << "_" << width << "_" << height << image.substr(image.rfind('.')); image = Misc::ResourceHelpers::correctBookartPath(str.str(), vfs); } return image; } std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs) { std::string mdlname = resPath; std::string::size_type p = mdlname.find_last_of("/\\"); if(p != std::string::npos) mdlname.insert(mdlname.begin()+p+1, 'x'); else mdlname.insert(mdlname.begin(), 'x'); if(!vfs->exists(mdlname)) { return resPath; } return mdlname; } std::string Misc::ResourceHelpers::correctMeshPath(const std::string &resPath, const VFS::Manager* vfs) { return "meshes\\" + resPath; } std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath, const VFS::Manager* vfs) { std::string sound = resPath; // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. if (!vfs->exists(sound)) changeExtension(sound, ".mp3"); return vfs->normalizeFilename(sound); } bool Misc::ResourceHelpers::isHiddenMarker(std::string_view id) { return Misc::StringUtils::ciEqual(id, "prisonmarker") || Misc::StringUtils::ciEqual(id, "divinemarker") || Misc::StringUtils::ciEqual(id, "templemarker") || Misc::StringUtils::ciEqual(id, "northmarker"); } openmw-openmw-0.48.0/components/misc/resourcehelpers.hpp000066400000000000000000000030141445372753700234750ustar00rootroot00000000000000#ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H #include #include namespace VFS { class Manager; } namespace Misc { // Workarounds for messy resource handling in vanilla morrowind // The below functions are provided on a opt-in basis, instead of built into the VFS, // so we have the opportunity to use proper resource handling for content created in OpenMW-CS. namespace ResourceHelpers { bool changeExtensionToDds(std::string &path); std::string correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath, const VFS::Manager* vfs); std::string correctTexturePath(const std::string &resPath, const VFS::Manager* vfs); std::string correctIconPath(const std::string &resPath, const VFS::Manager* vfs); std::string correctBookartPath(const std::string &resPath, const VFS::Manager* vfs); std::string correctBookartPath(const std::string &resPath, int width, int height, const VFS::Manager* vfs); /// Use "xfoo.nif" instead of "foo.nif" if available std::string correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs); std::string correctMeshPath(const std::string &resPath, const VFS::Manager* vfs); std::string correctSoundPath(const std::string& resPath, const VFS::Manager* vfs); /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(std::string_view id); } } #endif openmw-openmw-0.48.0/components/misc/rng.cpp000066400000000000000000000037131445372753700210520ustar00rootroot00000000000000#include "rng.hpp" #include #include #include #include namespace Misc::Rng { static Generator sGenerator; Generator& getGenerator() { return sGenerator; } std::string serialize(const Generator& prng) { std::stringstream ss; ss << prng; return ss.str(); } void deserialize(std::string_view data, Generator& prng) { std::stringstream ss; ss << data; ss.seekg(0); ss >> prng; } unsigned int generateDefaultSeed() { auto res = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); return res; } void init(unsigned int seed) { sGenerator.seed(seed); } float rollProbability() { return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(getGenerator()); } float rollProbability(Generator& prng) { return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(prng); } float rollClosedProbability() { return std::uniform_real_distribution(0, 1)(getGenerator()); } float rollClosedProbability(Generator& prng) { return std::uniform_real_distribution(0, 1)(prng); } int rollDice(int max) { return max > 0 ? std::uniform_int_distribution(0, max - 1)(getGenerator()) : 0; } int rollDice(int max, Generator& prng) { return max > 0 ? std::uniform_int_distribution(0, max - 1)(prng) : 0; } float deviate(float mean, float deviation) { return std::uniform_real_distribution(mean - deviation, mean + deviation)(getGenerator()); } float deviate(float mean, float deviation, Generator& prng) { return std::uniform_real_distribution(mean - deviation, mean + deviation)(prng); } } openmw-openmw-0.48.0/components/misc/rng.hpp000066400000000000000000000025661445372753700210640ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_RNG_H #define OPENMW_COMPONENTS_MISC_RNG_H #include #include #include /* Provides central implementation of the RNG logic */ namespace Misc::Rng { /// The use of a rather minimalistic prng is preferred to avoid saving a lot of state in the save game. using Generator = std::minstd_rand; Generator& getGenerator(); std::string serialize(const Generator& prng); void deserialize(std::string_view data, Generator& prng); /// returns default seed for RNG unsigned int generateDefaultSeed(); /// seed the RNG void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. float rollProbability(); float rollProbability(Generator& prng); /// return value in range [0.0f, 1.0f] <- note closed upper range. float rollClosedProbability(); float rollClosedProbability(Generator& prng); /// return value in range [0, max) <- note open upper range. int rollDice(int max); int rollDice(int max, Generator& prng); /// return value in range [0, 99] inline int roll0to99(Generator& prng) { return rollDice(100, prng); } inline int roll0to99() { return rollDice(100); } float deviate(float mean, float deviation); float deviate(float mean, float deviation, Generator& prng); } #endif openmw-openmw-0.48.0/components/misc/span.hpp000066400000000000000000000014331445372753700212270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_SPAN_H #define OPENMW_COMPONENTS_MISC_SPAN_H #include namespace Misc { template class Span { public: constexpr Span() = default; constexpr Span(T* pointer, std::size_t size) : mPointer(pointer) , mSize(size) {} template constexpr Span(Range& range) : Span(range.data(), range.size()) {} constexpr T* begin() const { return mPointer; } constexpr T* end() const { return mPointer + mSize; } constexpr std::size_t size() const { return mSize; } private: T* mPointer = nullptr; std::size_t mSize = 0; }; } #endif openmw-openmw-0.48.0/components/misc/stringops.hpp000066400000000000000000000202041445372753700223130ustar00rootroot00000000000000#ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H #include #include #include #include #include #include /* Mapping table to go from uppercase to lowercase for plain ASCII.*/ static constexpr unsigned char tolowermap[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; namespace Misc { class StringUtils { struct ci { bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; // Allow to convert complex arguments to C-style strings for format() function template static T argument(T value) noexcept { static_assert(!std::is_same_v, "std::string_view is not supported"); return value; } template static T const * argument(std::basic_string const & value) noexcept { return value.c_str(); } public: /// Plain and simple locale-unaware toLower. Anything from A to Z is lower-cased, multibyte characters are unchanged. /// Don't use std::tolower(char, locale&) because that is abysmally slow. /// Don't use tolower(int) because that depends on global locale. static constexpr char toLower(char c) { return tolowermap[static_cast(c)]; } static bool ciLess(std::string_view x, std::string_view y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } template static bool ciEqual(const X& x, const Y& y) { if (std::size(x) != std::size(y)) return false; return std::equal(std::begin(x), std::end(x), std::begin(y), [] (char l, char r) { return toLower(l) == toLower(r); }); } template static auto ciEqual(const char(& x)[n], const char(& y)[n]) { static_assert(n > 0); return ciEqual(std::string_view(x, n - 1), std::string_view(y, n - 1)); } template static auto ciEqual(const char(& x)[n], const T& y) { static_assert(n > 0); return ciEqual(std::string_view(x, n - 1), y); } template static auto ciEqual(const T& x, const char(& y)[n]) { static_assert(n > 0); return ciEqual(x, std::string_view(y, n - 1)); } static int ciCompareLen(std::string_view x, std::string_view y, std::size_t len) { std::string_view::const_iterator xit = x.begin(); std::string_view::const_iterator yit = y.begin(); for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { char left = *xit; char right = *yit; if (left == right) continue; left = toLower(left); right = toLower(right); int res = left - right; if(res != 0) return (res > 0) ? 1 : -1; } if(len > 0) { if(xit != x.end()) return 1; if(yit != y.end()) return -1; } return 0; } /// Transforms input string to lower case w/o copy static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i{}(str); } }; struct CiComp { bool operator()(std::string_view left, std::string_view right) const { return ciLess(left, right); } }; /** @brief Replaces all occurrences of a string in another string. * * @param str The string to operate on. * @param what The string to replace. * @param with The replacement string. * @return A reference to the string passed in @p str. */ static std::string &replaceAll(std::string &str, std::string_view what, std::string_view with) { std::size_t found; std::size_t offset = 0; while((found = str.find(what, offset)) != std::string::npos) { str.replace(found, what.size(), with); offset = found + with.size(); } return str; } // Requires some C++11 features: // 1. std::string needs to be contiguous // 2. std::snprintf with zero size (second argument) returns an output string size // 3. variadic templates support template static std::string format(const char* fmt, Args const & ... args) { auto size = std::snprintf(nullptr, 0, fmt, argument(args) ...); // Note: sprintf also writes a trailing null character. We should remove it. std::string ret(size+1, '\0'); std::sprintf(&ret[0], fmt, argument(args) ...); ret.erase(size); return ret; } template static std::string format(const std::string& fmt, Args const & ... args) { return format(fmt.c_str(), args ...); } static inline void trim(std::string &s) { const auto notSpace = [](char ch) { // TODO Do we care about multibyte whitespace? return !std::isspace(static_cast(ch)); }; // left trim s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); // right trim s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end()); } template static inline void split(std::string_view str, Container& cont, std::string_view delims = " ") { std::size_t current = str.find_first_of(delims); std::size_t previous = 0; while (current != std::string::npos) { cont.emplace_back(str.substr(previous, current - previous)); previous = current + 1; current = str.find_first_of(delims, previous); } cont.emplace_back(str.substr(previous, current - previous)); } static inline void replaceLast(std::string& str, std::string_view substr, std::string_view with) { size_t pos = str.rfind(substr); if (pos == std::string::npos) return; str.replace(pos, substr.size(), with); } static inline bool ciEndsWith(std::string_view s, std::string_view suffix) { return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin(), [](char l, char r) { return toLower(l) == toLower(r); }); }; }; } #endif openmw-openmw-0.48.0/components/misc/strongtypedef.hpp000066400000000000000000000013621445372753700231640ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_STRONGTYPEDEF_H #define OPENMW_COMPONENTS_MISC_STRONGTYPEDEF_H #include namespace Misc { template struct StrongTypedef { T mValue; StrongTypedef() = default; explicit StrongTypedef(const T& value) : mValue(value) {} explicit StrongTypedef(T&& value) : mValue(std::move(value)) {} operator const T&() const { return mValue; } operator T&() { return mValue; } StrongTypedef& operator++() { ++mValue; return *this; } StrongTypedef operator++(int) { StrongTypedef copy(*this); operator++(); return copy; } }; } #endif openmw-openmw-0.48.0/components/misc/thread.cpp000066400000000000000000000034051445372753700215310ustar00rootroot00000000000000#include "thread.hpp" #include #include #include #ifdef __linux__ #include #include namespace Misc { void setCurrentThreadIdlePriority() { sched_param param; param.sched_priority = 0; if (pthread_setschedparam(pthread_self(), SCHED_IDLE, ¶m) == 0) Log(Debug::Verbose) << "Using idle priority for thread=" << std::this_thread::get_id(); else Log(Debug::Warning) << "Failed to set idle priority for thread=" << std::this_thread::get_id() << ": " << std::strerror(errno); } } #elif defined(WIN32) #include namespace Misc { void setCurrentThreadIdlePriority() { if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST)) Log(Debug::Verbose) << "Using idle priority for thread=" << std::this_thread::get_id(); else Log(Debug::Warning) << "Failed to set idle priority for thread=" << std::this_thread::get_id() << ": " << GetLastError(); } } #elif defined(__FreeBSD__) #include #include namespace Misc { void setCurrentThreadIdlePriority() { struct rtprio prio; prio.type = RTP_PRIO_IDLE; prio.prio = RTP_PRIO_MAX; if (rtprio_thread(RTP_SET, 0, &prio) == 0) Log(Debug::Verbose) << "Using idle priority for thread=" << std::this_thread::get_id(); else Log(Debug::Warning) << "Failed to set idle priority for thread=" << std::this_thread::get_id() << ": " << std::strerror(errno); } } #else namespace Misc { void setCurrentThreadIdlePriority() { Log(Debug::Warning) << "Idle thread priority is not supported on this system"; } } #endif openmw-openmw-0.48.0/components/misc/thread.hpp000066400000000000000000000002501445372753700215310ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_THREAD_H #define OPENMW_COMPONENTS_MISC_THREAD_H #include namespace Misc { void setCurrentThreadIdlePriority(); } #endif openmw-openmw-0.48.0/components/misc/timer.hpp000066400000000000000000000017551445372753700214150ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_TIMER_H #define OPENMW_COMPONENTS_MISC_TIMER_H #include "rng.hpp" namespace Misc { enum class TimerStatus { Waiting, Elapsed, }; class DeviatingPeriodicTimer { public: explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft) : mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft) {} TimerStatus update(float duration, Rng::Generator& prng) { if (mTimeLeft > 0) { mTimeLeft -= duration; return TimerStatus::Waiting; } mTimeLeft = Rng::deviate(mPeriod, mDeviation, prng); return TimerStatus::Elapsed; } void reset(float timeLeft) { mTimeLeft = timeLeft; } private: const float mPeriod; const float mDeviation; float mTimeLeft; }; } #endif openmw-openmw-0.48.0/components/misc/typetraits.hpp000066400000000000000000000006151445372753700224770ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MISC_TYPETRAITS_H #define OPENMW_COMPONENTS_MISC_TYPETRAITS_H #include #include namespace Misc { template struct IsOptional : std::false_type {}; template struct IsOptional> : std::true_type {}; template inline constexpr bool isOptional = IsOptional::value; } #endif openmw-openmw-0.48.0/components/misc/utf8stream.hpp000066400000000000000000000115101445372753700223650ustar00rootroot00000000000000#ifndef MISC_UTF8ITER_HPP #define MISC_UTF8ITER_HPP #include #include #include #include class Utf8Stream { public: typedef uint32_t UnicodeChar; typedef unsigned char const * Point; //static const unicode_char sBadChar = 0xFFFFFFFF; gcc can't handle this static UnicodeChar sBadChar () { return UnicodeChar (0xFFFFFFFF); } Utf8Stream (Point begin, Point end) : cur (begin), nxt (begin), end (end), val(Utf8Stream::sBadChar()) { } Utf8Stream (const char * str) : cur (reinterpret_cast(str)), nxt (reinterpret_cast(str)), end (reinterpret_cast(str) + strlen(str)), val(Utf8Stream::sBadChar()) { } Utf8Stream (std::pair range) : cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { } Utf8Stream (std::string_view str) : Utf8Stream (reinterpret_cast(str.data()), reinterpret_cast(str.data() + str.size())) { } bool eof () const { return cur == end; } Point current () const { return cur; } UnicodeChar peek () { if (cur == nxt) next (); return val; } UnicodeChar consume () { if (cur == nxt) next (); cur = nxt; return val; } static std::pair decode (Point cur, Point end) { if ((*cur & 0x80) == 0) { UnicodeChar chr = *cur++; return std::make_pair (chr, cur); } int octets; UnicodeChar chr; std::tie (octets, chr) = octet_count (*cur++); if (octets > 5) return std::make_pair (sBadChar(), cur); Point eoc = cur + octets; if (eoc > end) return std::make_pair (sBadChar(), cur); while (cur != eoc) { if ((*cur & 0xC0) != 0x80) // check continuation mark return std::make_pair (sBadChar(), cur); chr = (chr << 6) | UnicodeChar ((*cur++) & 0x3F); } return std::make_pair (chr, cur); } static UnicodeChar toLowerUtf8(UnicodeChar ch) { // Russian alphabet if (ch >= 0x0410 && ch < 0x0430) return ch + 0x20; // Cyrillic IO character if (ch == 0x0401) return ch + 0x50; // Latin alphabet if (ch >= 0x41 && ch < 0x60) return ch + 0x20; // German characters if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc) return ch + 0x20; if (ch == 0x1e9e) return 0xdf; // TODO: probably we will need to support characters from other languages return ch; } static std::string lowerCaseUtf8(const std::string& str) { if (str.empty()) return str; // Decode string as utf8 characters, convert to lower case and pack them to string std::string out; Utf8Stream stream (str.c_str()); while (!stream.eof ()) { UnicodeChar character = toLowerUtf8(stream.peek()); if (character <= 0x7f) out.append(1, static_cast(character)); else if (character <= 0x7ff) { out.append(1, static_cast(0xc0 | ((character >> 6) & 0x1f))); out.append(1, static_cast(0x80 | (character & 0x3f))); } else if (character <= 0xffff) { out.append(1, static_cast(0xe0 | ((character >> 12) & 0x0f))); out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); out.append(1, static_cast(0x80 | (character & 0x3f))); } else { out.append(1, static_cast(0xf0 | ((character >> 18) & 0x07))); out.append(1, static_cast(0x80 | ((character >> 12) & 0x3f))); out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); out.append(1, static_cast(0x80 | (character & 0x3f))); } stream.consume(); } return out; } private: static std::pair octet_count (unsigned char octet) { int octets; unsigned char mark = 0xC0; unsigned char mask = 0xE0; for (octets = 1; octets <= 5; ++octets) { if ((octet & mask) == mark) break; mark = (mark >> 1) | 0x80; mask = (mask >> 1) | 0x80; } return std::make_pair (octets, octet & ~mask); } void next () { std::tie (val, nxt) = decode (nxt, end); } Point cur; Point nxt; Point end; UnicodeChar val; }; #endif openmw-openmw-0.48.0/components/misc/weakcache.hpp000066400000000000000000000073261445372753700222100ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP #define OPENMW_COMPONENTS_WEAKCACHE_HPP #include #include #include namespace Misc { /// \class WeakCache /// Provides a container to weakly store pointers to shared data. template class WeakCache { public: using WeakPtr = std::weak_ptr; using StrongPtr = std::shared_ptr; using Map = std::unordered_map; class iterator { public: iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end); iterator& operator++(); bool operator==(const iterator& other) const; bool operator!=(const iterator& other) const; StrongPtr operator*(); private: WeakCache* mCache; typename Map::iterator mCurrent, mEnd; StrongPtr mPtr; }; /// Stores a weak pointer to the item. void insert(Key key, StrongPtr value, bool prune=true); /// Retrieves the item associated with the key. /// \return An item or null. StrongPtr get(Key key); iterator begin(); iterator end(); /// Removes known invalid entries void prune(); private: Map mData; std::vector mDirty; }; template WeakCache::iterator::iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end) : mCache(cache) , mCurrent(current) , mEnd(end) { // Move to 1st available valid item for ( ; mCurrent != mEnd; ++mCurrent) { mPtr = mCurrent->second.lock(); if (mPtr) break; else mCache->mDirty.push_back(mCurrent->first); } } template typename WeakCache::iterator& WeakCache::iterator::operator++() { auto next = mCurrent; ++next; return *this = iterator(mCache, next, mEnd); } template bool WeakCache::iterator::operator==(const iterator& other) const { return mCurrent == other.mCurrent; } template bool WeakCache::iterator::operator!=(const iterator& other) const { return !(*this == other); } template typename WeakCache::StrongPtr WeakCache::iterator::operator*() { return mPtr; } template void WeakCache::insert(Key key, StrongPtr value, bool shouldPrune) { mData[key] = WeakPtr(value); if (shouldPrune) prune(); } template typename WeakCache::StrongPtr WeakCache::get(Key key) { auto searchIt = mData.find(key); if (searchIt != mData.end()) return searchIt->second.lock(); else return StrongPtr(); } template typename WeakCache::iterator WeakCache::begin() { return iterator(this, mData.begin(), mData.end()); } template typename WeakCache::iterator WeakCache::end() { return iterator(this, mData.end(), mData.end()); } template void WeakCache::prune() { // Remove empty entries for (auto& key : mDirty) { auto it = mData.find(key); if (it != mData.end() && it->second.use_count() == 0) mData.erase(it); } mDirty.clear(); } } #endif openmw-openmw-0.48.0/components/myguiplatform/000077500000000000000000000000001445372753700215205ustar00rootroot00000000000000openmw-openmw-0.48.0/components/myguiplatform/additivelayer.cpp000066400000000000000000000014741445372753700250600ustar00rootroot00000000000000#include "additivelayer.hpp" #include #include #include "myguirendermanager.hpp" namespace osgMyGUI { AdditiveLayer::AdditiveLayer() { mStateSet = new osg::StateSet; mStateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE)); } AdditiveLayer::~AdditiveLayer() { // defined in .cpp file since we can't delete incomplete types } void AdditiveLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) { RenderManager& renderManager = static_cast(MyGUI::RenderManager::getInstance()); renderManager.setInjectState(mStateSet.get()); MyGUI::OverlappedLayer::renderToTarget(_target, _update); renderManager.setInjectState(nullptr); } } openmw-openmw-0.48.0/components/myguiplatform/additivelayer.hpp000066400000000000000000000011751445372753700250630ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER #define OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER #include #include namespace osg { class StateSet; } namespace osgMyGUI { /// @brief A Layer rendering with additive blend mode. class AdditiveLayer final : public MyGUI::OverlappedLayer { public: MYGUI_RTTI_DERIVED( AdditiveLayer ) AdditiveLayer(); ~AdditiveLayer() override; void renderToTarget(MyGUI::IRenderTarget* _target, bool _update) override; private: osg::ref_ptr mStateSet; }; } #endif openmw-openmw-0.48.0/components/myguiplatform/myguidatamanager.cpp000066400000000000000000000030461445372753700255460ustar00rootroot00000000000000#include "myguidatamanager.hpp" #include #include #include #include #include namespace { class DataStream final : public MyGUI::DataStream { public: explicit DataStream(std::unique_ptr&& stream) : MyGUI::DataStream(stream.get()) , mOwnedStream(std::move(stream)) {} private: std::unique_ptr mOwnedStream; }; } namespace osgMyGUI { void DataManager::setResourcePath(const std::string &path) { mResourcePath = path; } DataManager::DataManager(const std::string& resourcePath, const VFS::Manager* vfs) : mResourcePath(resourcePath) , mVfs(vfs) { } MyGUI::IDataStream *DataManager::getData(const std::string &name) const { return new DataStream(mVfs->get(mResourcePath + "/" + name)); } void DataManager::freeData(MyGUI::IDataStream *data) { delete data; } bool DataManager::isDataExist(const std::string &name) const { return mVfs->exists(mResourcePath + "/" + name); } const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) const { throw std::runtime_error("DataManager::getDataListNames is not implemented - VFS is used"); } const std::string &DataManager::getDataPath(const std::string &name) const { static std::string result; result.clear(); if (name.empty()) return mResourcePath; if (!isDataExist(name)) return result; result = mResourcePath + "/" + name; return result; } } openmw-openmw-0.48.0/components/myguiplatform/myguidatamanager.hpp000066400000000000000000000025651445372753700255600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H #include #include namespace VFS { class Manager; } namespace osgMyGUI { class DataManager : public MyGUI::DataManager { public: explicit DataManager(const std::string& path, const VFS::Manager* vfs); void setResourcePath(const std::string& path); /** Get data stream from specified resource name. @param _name Resource name (usually file name). */ MyGUI::IDataStream* getData(const std::string& _name) const override; /** Free data stream. @param _data Data stream. */ void freeData(MyGUI::IDataStream* _data) override; /** Is data with specified name exist. @param _name Resource name. */ bool isDataExist(const std::string& _name) const override; /** Get all data names with names that matches pattern. @param _pattern Pattern to match (for example "*.layout"). */ const MyGUI::VectorString& getDataListNames(const std::string& _pattern) const override; /** Get full path to data. @param _name Resource name. @return Return full path to specified data. */ const std::string& getDataPath(const std::string& _name) const override; private: std::string mResourcePath; const VFS::Manager* mVfs; }; } #endif openmw-openmw-0.48.0/components/myguiplatform/myguiloglistener.cpp000066400000000000000000000024211445372753700256250ustar00rootroot00000000000000#include "myguiloglistener.hpp" #include #include namespace osgMyGUI { void CustomLogListener::open() { mStream.open(boost::filesystem::path(mFileName), std::ios_base::out); if (!mStream.is_open()) Log(Debug::Error) << "Unable to create MyGUI log with path " << mFileName; } void CustomLogListener::close() { if (mStream.is_open()) mStream.close(); } void CustomLogListener::flush() { if (mStream.is_open()) mStream.flush(); } void CustomLogListener::log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line) { if (mStream.is_open()) { const char* separator = " | "; mStream << std::setw(2) << std::setfill('0') << _time->tm_hour << ":" << std::setw(2) << std::setfill('0') << _time->tm_min << ":" << std::setw(2) << std::setfill('0') << _time->tm_sec << separator << _section << separator << _level.print() << separator << _message << separator << _file << separator << _line << std::endl; } } } openmw-openmw-0.48.0/components/myguiplatform/myguiloglistener.hpp000066400000000000000000000036341445372753700256410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_LOGLISTENER_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_LOGLISTENER_H #include #include #include #include #include #include namespace osgMyGUI { /// \brief Custom MyGUI::ILogListener interface implementation /// being able to portably handle UTF-8 encoded path. /// \todo try patching MyGUI to make this easier class CustomLogListener : public MyGUI::ILogListener { public: CustomLogListener(const std::string &name) : mFileName(name) {} ~CustomLogListener() {} void open() override; void close() override; void flush() override; void log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line) override; const std::string& getFileName() const { return mFileName; } private: boost::filesystem::ofstream mStream; std::string mFileName; }; /// \brief Helper class holding data that required during /// MyGUI log creation class LogFacility { MyGUI::ConsoleLogListener mConsole; CustomLogListener mFile; MyGUI::LevelLogFilter mFilter; MyGUI::LogSource mSource; public: LogFacility(const std::string &output, bool console) : mFile(output) { mConsole.setEnabled(console); mFilter.setLoggingLevel(MyGUI::LogLevel::Info); mSource.addLogListener(&mFile); mSource.addLogListener(&mConsole); mSource.setLogFilter(&mFilter); mSource.open(); } MyGUI::LogSource *getSource() { return &mSource; } }; } #endif openmw-openmw-0.48.0/components/myguiplatform/myguiplatform.cpp000066400000000000000000000021011445372753700251150ustar00rootroot00000000000000#include "myguiplatform.hpp" #include "myguirendermanager.hpp" #include "myguidatamanager.hpp" #include "myguiloglistener.hpp" namespace osgMyGUI { Platform::Platform(osgViewer::Viewer *viewer, osg::Group* guiRoot, Resource::ImageManager* imageManager, const VFS::Manager* vfs, float uiScalingFactor, const std::string& resourcePath, const std::string& logName) : mLogFacility(logName.empty() ? nullptr : std::make_unique(logName, false)) , mLogManager(std::make_unique()) , mDataManager(std::make_unique(resourcePath, vfs)) , mRenderManager(std::make_unique(viewer, guiRoot, imageManager, uiScalingFactor)) { if (mLogFacility != nullptr) mLogManager->addLogSource(mLogFacility->getSource()); mRenderManager->initialise(); } Platform::~Platform() = default; void Platform::shutdown() { mRenderManager->shutdown(); } RenderManager *Platform::getRenderManagerPtr() { return mRenderManager.get(); } DataManager *Platform::getDataManagerPtr() { return mDataManager.get(); } } openmw-openmw-0.48.0/components/myguiplatform/myguiplatform.hpp000066400000000000000000000021371445372753700251330ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H #include #include namespace osgViewer { class Viewer; } namespace osg { class Group; } namespace Resource { class ImageManager; } namespace MyGUI { class LogManager; } namespace VFS { class Manager; } namespace osgMyGUI { class RenderManager; class DataManager; class LogFacility; class Platform { public: Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ImageManager* imageManager, const VFS::Manager* vfs, float uiScalingFactor, const std::string& resourcePath, const std::string& logName = "MyGUI.log"); ~Platform(); void shutdown(); RenderManager* getRenderManagerPtr(); DataManager* getDataManagerPtr(); private: std::unique_ptr mLogFacility; std::unique_ptr mLogManager; std::unique_ptr mDataManager; std::unique_ptr mRenderManager; }; } #endif openmw-openmw-0.48.0/components/myguiplatform/myguirendermanager.cpp000066400000000000000000000410131445372753700261100ustar00rootroot00000000000000#include "myguirendermanager.hpp" #include #include #include #include #include #include #include #include #include #include "myguitexture.hpp" #define MYGUI_PLATFORM_LOG_SECTION "Platform" #define MYGUI_PLATFORM_LOG(level, text) MYGUI_LOGGING(MYGUI_PLATFORM_LOG_SECTION, level, text) #define MYGUI_PLATFORM_EXCEPT(dest) do { \ MYGUI_PLATFORM_LOG(Critical, dest); \ std::ostringstream stream; \ stream << dest << "\n"; \ MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ } while(0) #define MYGUI_PLATFORM_ASSERT(exp, dest) do { \ if ( ! (exp) ) \ { \ MYGUI_PLATFORM_LOG(Critical, dest); \ std::ostringstream stream; \ stream << dest << "\n"; \ MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ } \ } while(0) namespace osgMyGUI { class Drawable : public osg::Drawable { osgMyGUI::RenderManager *mParent; osg::ref_ptr mStateSet; public: // Stage 0: update widget animations and controllers. Run during the Update traversal. class FrameUpdate : public SceneUtil::NodeCallback { public: FrameUpdate() : mRenderManager(nullptr) { } void setRenderManager(osgMyGUI::RenderManager* renderManager) { mRenderManager = renderManager; } void operator()(osg::Node*, osg::NodeVisitor*) { mRenderManager->update(); } private: osgMyGUI::RenderManager* mRenderManager; }; // Stage 1: collect draw calls. Run during the Cull traversal. class CollectDrawCalls : public SceneUtil::NodeCallback { public: CollectDrawCalls() : mRenderManager(nullptr) { } void setRenderManager(osgMyGUI::RenderManager* renderManager) { mRenderManager = renderManager; } void operator()(osg::Node*, osg::NodeVisitor*) { mRenderManager->collectDrawCalls(); } private: osgMyGUI::RenderManager* mRenderManager; }; // Stage 2: execute the draw calls. Run during the Draw traversal. May run in parallel with the update traversal of the next frame. void drawImplementation(osg::RenderInfo &renderInfo) const override { osg::State *state = renderInfo.getState(); state->pushStateSet(mStateSet); state->apply(); state->disableAllVertexArrays(); state->setClientActiveTextureUnit(0); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); mReadFrom = (mReadFrom+1)%sNumBuffers; const std::vector& vec = mBatchVector[mReadFrom]; for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) { const Batch& batch = *it; osg::VertexBufferObject *vbo = batch.mVertexBuffer; if (batch.mStateSet) { state->pushStateSet(batch.mStateSet); state->apply(); } // A GUI element without an associated texture would be extremely rare. // It is worth it to use a dummy 1x1 black texture sampler instead of either adding a conditional or relinking shaders. osg::Texture2D* texture = batch.mTexture; if(texture) state->applyTextureAttribute(0, texture); else state->applyTextureAttribute(0, mDummyTexture); osg::GLBufferObject* bufferobject = state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : nullptr; if (bufferobject) { state->bindVertexBufferObject(bufferobject); glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), reinterpret_cast(0)); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), reinterpret_cast(12)); glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), reinterpret_cast(16)); } else { glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), reinterpret_cast(vbo->getArray(0)->getDataPointer())); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), reinterpret_cast(vbo->getArray(0)->getDataPointer()) + 12); glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), reinterpret_cast(vbo->getArray(0)->getDataPointer()) + 16); } glDrawArrays(GL_TRIANGLES, 0, batch.mVertexCount); if (batch.mStateSet) { state->popStateSet(); state->apply(); } } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); state->popStateSet(); state->unbindVertexBufferObject(); state->dirtyAllVertexArrays(); state->disableAllVertexArrays(); } public: Drawable(osgMyGUI::RenderManager *parent = nullptr) : mParent(parent) , mWriteTo(0) , mReadFrom(0) { setSupportsDisplayList(false); osg::ref_ptr collectDrawCalls = new CollectDrawCalls; collectDrawCalls->setRenderManager(mParent); setCullCallback(collectDrawCalls); osg::ref_ptr frameUpdate = new FrameUpdate; frameUpdate->setRenderManager(mParent); setUpdateCallback(frameUpdate); mStateSet = new osg::StateSet; mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); mStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); mStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mStateSet->setMode(GL_BLEND, osg::StateAttribute::ON); mDummyTexture = new osg::Texture2D; mDummyTexture->setInternalFormat(GL_RGB); mDummyTexture->setTextureSize(1,1); mDummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mDummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); // need to flip tex coords since MyGUI uses DirectX convention of top left image origin osg::Matrix flipMat; flipMat.preMultTranslate(osg::Vec3f(0,1,0)); flipMat.preMultScale(osg::Vec3f(1,-1,1)); mStateSet->setTextureAttribute(0, new osg::TexMat(flipMat), osg::StateAttribute::ON); } Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) : osg::Drawable(copy, copyop) , mParent(copy.mParent) , mStateSet(copy.mStateSet) , mWriteTo(0) , mReadFrom(0) , mDummyTexture(copy.mDummyTexture) { } // Defines the necessary information for a draw call struct Batch { // May be empty osg::ref_ptr mTexture; osg::ref_ptr mVertexBuffer; // need to hold on to this too as the mVertexBuffer does not hold a ref to its own array osg::ref_ptr mArray; // optional osg::ref_ptr mStateSet; size_t mVertexCount; }; void addBatch(const Batch& batch) { mBatchVector[mWriteTo].push_back(batch); } void clear() { mWriteTo = (mWriteTo+1)%sNumBuffers; mBatchVector[mWriteTo].clear(); } osg::StateSet* getDrawableStateSet() { return mStateSet; } META_Object(osgMyGUI, Drawable) private: // 2 would be enough in most cases, use 4 to get stereo working static const int sNumBuffers = 4; // double buffering approach, to avoid the need for synchronization with the draw thread std::vector mBatchVector[sNumBuffers]; int mWriteTo; mutable int mReadFrom; osg::ref_ptr mDummyTexture; }; class OSGVertexBuffer : public MyGUI::IVertexBuffer { osg::ref_ptr mBuffer[2]; osg::ref_ptr mVertexArray[2]; size_t mNeedVertexCount; unsigned int mCurrentBuffer; bool mUsed; // has the mCurrentBuffer been submitted to the rendering thread void destroy(); osg::UByteArray* create(); public: OSGVertexBuffer(); virtual ~OSGVertexBuffer() {} void markUsed(); osg::Array* getVertexArray(); osg::VertexBufferObject* getVertexBuffer(); void setVertexCount(size_t count) override; size_t getVertexCount() const override; MyGUI::Vertex *lock() override; void unlock() override; }; OSGVertexBuffer::OSGVertexBuffer() : mNeedVertexCount(0) , mCurrentBuffer(0) , mUsed(false) { } void OSGVertexBuffer::markUsed() { mUsed = true; } void OSGVertexBuffer::setVertexCount(size_t count) { if(count == mNeedVertexCount) return; mNeedVertexCount = count; } size_t OSGVertexBuffer::getVertexCount() const { return mNeedVertexCount; } MyGUI::Vertex *OSGVertexBuffer::lock() { if (mUsed) { mCurrentBuffer = (mCurrentBuffer+1)%2; mUsed = false; } osg::UByteArray* array = mVertexArray[mCurrentBuffer]; if (!array) { array = create(); } else if (array->size() != mNeedVertexCount * sizeof(MyGUI::Vertex)) { array->resize(mNeedVertexCount * sizeof(MyGUI::Vertex)); } return (MyGUI::Vertex*)&(*array)[0]; } void OSGVertexBuffer::unlock() { mVertexArray[mCurrentBuffer]->dirty(); mBuffer[mCurrentBuffer]->dirty(); } osg::UByteArray* OSGVertexBuffer::create() { mVertexArray[mCurrentBuffer] = new osg::UByteArray(mNeedVertexCount*sizeof(MyGUI::Vertex)); mBuffer[mCurrentBuffer] = new osg::VertexBufferObject; mBuffer[mCurrentBuffer]->setDataVariance(osg::Object::DYNAMIC); mBuffer[mCurrentBuffer]->setUsage(GL_DYNAMIC_DRAW); // NB mBuffer does not own the array mBuffer[mCurrentBuffer]->setArray(0, mVertexArray[mCurrentBuffer].get()); return mVertexArray[mCurrentBuffer]; } osg::Array* OSGVertexBuffer::getVertexArray() { return mVertexArray[mCurrentBuffer]; } osg::VertexBufferObject* OSGVertexBuffer::getVertexBuffer() { return mBuffer[mCurrentBuffer]; } // --------------------------------------------------------------------------- RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor) : mViewer(viewer) , mSceneRoot(sceneroot) , mImageManager(imageManager) , mUpdate(false) , mIsInitialise(false) , mInvScalingFactor(1.f) , mInjectState(nullptr) { if (scalingFactor != 0.f) mInvScalingFactor = 1.f / scalingFactor; } RenderManager::~RenderManager() { MYGUI_PLATFORM_LOG(Info, "* Shutdown: "<removeChild(mGuiRoot.get()); mGuiRoot = nullptr; mSceneRoot = nullptr; mViewer = nullptr; MYGUI_PLATFORM_LOG(Info, getClassTypeName()<<" successfully shutdown"); mIsInitialise = false; } void RenderManager::initialise() { MYGUI_PLATFORM_ASSERT(!mIsInitialise, getClassTypeName()<<" initialised twice"); MYGUI_PLATFORM_LOG(Info, "* Initialise: "< camera = new osg::Camera(); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); camera->setProjectionResizePolicy(osg::Camera::FIXED); camera->setProjectionMatrix(osg::Matrix::identity()); camera->setViewMatrix(osg::Matrix::identity()); camera->setRenderOrder(osg::Camera::POST_RENDER); camera->setClearMask(GL_NONE); mDrawable->setCullingActive(false); camera->addChild(mDrawable.get()); mGuiRoot = camera; mSceneRoot->addChild(mGuiRoot.get()); osg::ref_ptr vp = mViewer->getCamera()->getViewport(); setViewSize(vp->width(), vp->height()); MYGUI_PLATFORM_LOG(Info, getClassTypeName()<<" successfully initialized"); mIsInitialise = true; } void RenderManager::shutdown() { mGuiRoot->removeChildren(0, mGuiRoot->getNumChildren()); mSceneRoot->removeChild(mGuiRoot); } void RenderManager::enableShaders(Shader::ShaderManager& shaderManager) { auto vertexShader = shaderManager.getShader("gui_vertex.glsl", {}, osg::Shader::VERTEX); auto fragmentShader = shaderManager.getShader("gui_fragment.glsl", {}, osg::Shader::FRAGMENT); auto program = shaderManager.getProgram(vertexShader, fragmentShader); mDrawable->getDrawableStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON); mDrawable->getDrawableStateSet()->addUniform(new osg::Uniform("diffuseMap", 0)); } MyGUI::IVertexBuffer* RenderManager::createVertexBuffer() { return new OSGVertexBuffer(); } void RenderManager::destroyVertexBuffer(MyGUI::IVertexBuffer *buffer) { delete buffer; } void RenderManager::begin() { mDrawable->clear(); // variance will be recomputed based on textures being rendered in this frame mDrawable->setDataVariance(osg::Object::STATIC); } void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count) { Drawable::Batch batch; batch.mVertexCount = count; batch.mVertexBuffer = static_cast(buffer)->getVertexBuffer(); batch.mArray = static_cast(buffer)->getVertexArray(); static_cast(buffer)->markUsed(); if (OSGTexture* osgtexture = static_cast(texture)) { batch.mTexture = osgtexture->getTexture(); if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() if (!mInjectState && osgtexture->getInjectState()) batch.mStateSet = osgtexture->getInjectState(); } if (mInjectState) batch.mStateSet = mInjectState; mDrawable->addBatch(batch); } void RenderManager::setInjectState(osg::StateSet* stateSet) { mInjectState = stateSet; } void RenderManager::end() { } void RenderManager::update() { static MyGUI::Timer timer; static unsigned long last_time = timer.getMilliseconds(); unsigned long now_time = timer.getMilliseconds(); unsigned long time = now_time - last_time; onFrameEvent((float)((double)(time) / (double)1000)); last_time = now_time; } void RenderManager::collectDrawCalls() { begin(); onRenderToTarget(this, mUpdate); end(); mUpdate = false; } void RenderManager::setViewSize(int width, int height) { if(width < 1) width = 1; if(height < 1) height = 1; mGuiRoot->setViewport(0, 0, width, height); mViewSize.set(width * mInvScalingFactor, height * mInvScalingFactor); mInfo.maximumDepth = 1; mInfo.hOffset = 0; mInfo.vOffset = 0; mInfo.aspectCoef = float(mViewSize.height) / float(mViewSize.width); mInfo.pixScaleX = 1.0f / float(mViewSize.width); mInfo.pixScaleY = 1.0f / float(mViewSize.height); onResizeView(mViewSize); mUpdate = true; } bool RenderManager::isFormatSupported(MyGUI::PixelFormat /*format*/, MyGUI::TextureUsage /*usage*/) { return true; } MyGUI::ITexture* RenderManager::createTexture(const std::string &name) { const auto it = mTextures.insert_or_assign(name, OSGTexture(name, mImageManager)).first; return &it->second; } void RenderManager::destroyTexture(MyGUI::ITexture *texture) { if(texture == nullptr) return; const auto item = mTextures.find(texture->getName()); MYGUI_PLATFORM_ASSERT(item != mTextures.end(), "Texture '"<getName()<<"' not found"); mTextures.erase(item); } MyGUI::ITexture* RenderManager::getTexture(const std::string &name) { if (name.empty()) return nullptr; const auto item = mTextures.find(name); if(item == mTextures.end()) { MyGUI::ITexture* tex = createTexture(name); tex->loadFromFile(name); return tex; } return &item->second; } bool RenderManager::checkTexture(MyGUI::ITexture* _texture) { // We support external textures that aren't registered via this manager, so can't implement this method sensibly. return true; } void RenderManager::registerShader( const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) { MYGUI_PLATFORM_LOG(Warning, "osgMyGUI::RenderManager::registerShader is not implemented"); } } openmw-openmw-0.48.0/components/myguiplatform/myguirendermanager.hpp000066400000000000000000000066341445372753700261270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H #include #include namespace Resource { class ImageManager; } namespace Shader { class ShaderManager; } namespace osgViewer { class Viewer; } namespace osg { class Group; class Camera; class RenderInfo; class StateSet; } namespace osgMyGUI { class Drawable; class OSGTexture; class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget { osg::ref_ptr mViewer; osg::ref_ptr mSceneRoot; osg::ref_ptr mDrawable; Resource::ImageManager* mImageManager; MyGUI::IntSize mViewSize; bool mUpdate; MyGUI::VertexColourType mVertexFormat; MyGUI::RenderTargetInfo mInfo; std::map mTextures; bool mIsInitialise; osg::ref_ptr mGuiRoot; float mInvScalingFactor; osg::StateSet* mInjectState; public: RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::ImageManager* imageManager, float scalingFactor); virtual ~RenderManager(); void initialise(); void shutdown(); void enableShaders(Shader::ShaderManager& shaderManager); void setScalingFactor(float factor); static RenderManager& getInstance() { return *getInstancePtr(); } static RenderManager* getInstancePtr() { return static_cast(MyGUI::RenderManager::getInstancePtr()); } /** @see RenderManager::getViewSize */ const MyGUI::IntSize& getViewSize() const override { return mViewSize; } /** @see RenderManager::getVertexFormat */ MyGUI::VertexColourType getVertexFormat() const override { return mVertexFormat; } /** @see RenderManager::isFormatSupported */ bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage) override; /** @see RenderManager::createVertexBuffer */ MyGUI::IVertexBuffer* createVertexBuffer() override; /** @see RenderManager::destroyVertexBuffer */ void destroyVertexBuffer(MyGUI::IVertexBuffer *buffer) override; /** @see RenderManager::createTexture */ MyGUI::ITexture* createTexture(const std::string &name) override; /** @see RenderManager::destroyTexture */ void destroyTexture(MyGUI::ITexture* _texture) override; /** @see RenderManager::getTexture */ MyGUI::ITexture* getTexture(const std::string &name) override; // Called by the update traversal void update(); // Called by the cull traversal /** @see IRenderTarget::begin */ void begin() override; /** @see IRenderTarget::end */ void end() override; /** @see IRenderTarget::doRender */ void doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count) override; /** specify a StateSet to inject for rendering. The StateSet will be used by future doRender calls until you reset it to nullptr again. */ void setInjectState(osg::StateSet* stateSet); /** @see IRenderTarget::getInfo */ const MyGUI::RenderTargetInfo& getInfo() const override { return mInfo; } bool checkTexture(MyGUI::ITexture* _texture); void setViewSize(int width, int height) override; void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) override; /*internal:*/ void collectDrawCalls(); }; } #endif openmw-openmw-0.48.0/components/myguiplatform/myguitexture.cpp000066400000000000000000000131451445372753700250030ustar00rootroot00000000000000#include "myguitexture.hpp" #include #include #include #include #include namespace osgMyGUI { OSGTexture::OSGTexture(const std::string &name, Resource::ImageManager* imageManager) : mName(name) , mImageManager(imageManager) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) , mNumElemBytes(0) , mWidth(0) , mHeight(0) { } OSGTexture::OSGTexture(osg::Texture2D *texture, osg::StateSet *injectState) : mImageManager(nullptr) , mTexture(texture) , mInjectState(injectState) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) , mNumElemBytes(0) , mWidth(texture->getTextureWidth()) , mHeight(texture->getTextureHeight()) { } OSGTexture::~OSGTexture() { } void OSGTexture::createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) { GLenum glfmt = GL_NONE; size_t numelems = 0; switch(format.getValue()) { case MyGUI::PixelFormat::L8: glfmt = GL_LUMINANCE; numelems = 1; break; case MyGUI::PixelFormat::L8A8: glfmt = GL_LUMINANCE_ALPHA; numelems = 2; break; case MyGUI::PixelFormat::R8G8B8: glfmt = GL_RGB; numelems = 3; break; case MyGUI::PixelFormat::R8G8B8A8: glfmt = GL_RGBA; numelems = 4; break; } if(glfmt == GL_NONE) throw std::runtime_error("Texture format not supported"); mTexture = new osg::Texture2D(); mTexture->setTextureSize(width, height); mTexture->setSourceFormat(glfmt); mTexture->setSourceType(GL_UNSIGNED_BYTE); mWidth = width; mHeight = height; mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mFormat = format; mUsage = usage; mNumElemBytes = numelems; } void OSGTexture::destroy() { mTexture = nullptr; mFormat = MyGUI::PixelFormat::Unknow; mUsage = MyGUI::TextureUsage::Default; mNumElemBytes = 0; mWidth = 0; mHeight = 0; } void OSGTexture::loadFromFile(const std::string &fname) { if (!mImageManager) throw std::runtime_error("No imagemanager set"); osg::ref_ptr image (mImageManager->getImage(fname)); mTexture = new osg::Texture2D(image); mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mTexture->setTextureWidth(image->s()); mTexture->setTextureHeight(image->t()); // disable mip-maps mTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); mWidth = image->s(); mHeight = image->t(); mUsage = MyGUI::TextureUsage::Static; } void OSGTexture::saveToFile(const std::string &fname) { Log(Debug::Warning) << "Would save image to file " << fname; } void *OSGTexture::lock(MyGUI::TextureUsage /*access*/) { if (!mTexture.valid()) throw std::runtime_error("Texture is not created"); if (mLockedImage.valid()) throw std::runtime_error("Texture already locked"); mLockedImage = new osg::Image(); mLockedImage->allocateImage( mTexture->getTextureWidth(), mTexture->getTextureHeight(), mTexture->getTextureDepth(), mTexture->getSourceFormat(), mTexture->getSourceType() ); return mLockedImage->data(); } void OSGTexture::unlock() { if (!mLockedImage.valid()) throw std::runtime_error("Texture not locked"); mLockedImage->flipVertical(); // mTexture might be in use by the draw thread, so create a new texture instead and use that. osg::ref_ptr newTexture = new osg::Texture2D; newTexture->setTextureSize(getWidth(), getHeight()); newTexture->setSourceFormat(mTexture->getSourceFormat()); newTexture->setSourceType(mTexture->getSourceType()); newTexture->setFilter(osg::Texture::MIN_FILTER, mTexture->getFilter(osg::Texture::MIN_FILTER)); newTexture->setFilter(osg::Texture::MAG_FILTER, mTexture->getFilter(osg::Texture::MAG_FILTER)); newTexture->setWrap(osg::Texture::WRAP_S, mTexture->getWrap(osg::Texture::WRAP_S)); newTexture->setWrap(osg::Texture::WRAP_T, mTexture->getWrap(osg::Texture::WRAP_T)); newTexture->setImage(mLockedImage.get()); // Tell the texture it can get rid of the image for static textures (since // they aren't expected to update much at all). newTexture->setUnRefImageDataAfterApply(mUsage.isValue(MyGUI::TextureUsage::Static) ? true : false); mTexture = newTexture; mLockedImage = nullptr; } // Render-to-texture not currently implemented. MyGUI::IRenderTarget* OSGTexture::getRenderTarget() { return nullptr; } void OSGTexture::setShader(const std::string& _shaderName) { Log(Debug::Warning) << "OSGTexture::setShader is not implemented"; } } openmw-openmw-0.48.0/components/myguiplatform/myguitexture.hpp000066400000000000000000000040431445372753700250050ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H #define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H #include #include namespace osg { class Image; class Texture2D; class StateSet; } namespace Resource { class ImageManager; } namespace osgMyGUI { class OSGTexture final : public MyGUI::ITexture { std::string mName; Resource::ImageManager* mImageManager; osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; osg::ref_ptr mInjectState; MyGUI::PixelFormat mFormat; MyGUI::TextureUsage mUsage; size_t mNumElemBytes; int mWidth; int mHeight; public: OSGTexture(const std::string &name, Resource::ImageManager* imageManager); OSGTexture(osg::Texture2D* texture, osg::StateSet* injectState = nullptr); ~OSGTexture() override; osg::StateSet* getInjectState() { return mInjectState; } const std::string& getName() const override { return mName; } void createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) override; void loadFromFile(const std::string &fname) override; void saveToFile(const std::string &fname) override; void destroy() override; void* lock(MyGUI::TextureUsage access) override; void unlock() override; bool isLocked() const override { return mLockedImage.valid(); } int getWidth() const override { return mWidth; } int getHeight() const override { return mHeight; } MyGUI::PixelFormat getFormat() const override { return mFormat; } MyGUI::TextureUsage getUsage() const override { return mUsage; } size_t getNumElemBytes() const override { return mNumElemBytes; } MyGUI::IRenderTarget *getRenderTarget() override; void setShader(const std::string& _shaderName) override; /*internal:*/ osg::Texture2D *getTexture() const { return mTexture.get(); } }; } #endif openmw-openmw-0.48.0/components/myguiplatform/scalinglayer.cpp000066400000000000000000000111531445372753700247020ustar00rootroot00000000000000#include "scalinglayer.hpp" #include #include namespace osgMyGUI { /// @brief the ProxyRenderTarget allows to adjust the pixel scale and offset for a "source" render target. class ProxyRenderTarget : public MyGUI::IRenderTarget { public: /// @param target The target to render to. /// @param viewSize The size of the underlying layer node to render. /// @param hoffset The horizontal rendering offset, specified as an offset from the left screen edge in range 0-1. /// @param voffset The vertical rendering offset, specified as an offset from the top screen edge in range 0-1. ProxyRenderTarget(MyGUI::IRenderTarget* target, MyGUI::IntSize viewSize, float hoffset, float voffset) : mTarget(target) , mViewSize(viewSize) , mHOffset(hoffset) , mVOffset(voffset) { } void begin() override { mTarget->begin(); } void end() override { mTarget->end(); } void doRender(MyGUI::IVertexBuffer* _buffer, MyGUI::ITexture* _texture, size_t _count) override { mTarget->doRender(_buffer, _texture, _count); } const MyGUI::RenderTargetInfo& getInfo() const override { mInfo = mTarget->getInfo(); mInfo.hOffset = mHOffset; mInfo.vOffset = mVOffset; mInfo.pixScaleX = 1.f / mViewSize.width; mInfo.pixScaleY = 1.f / mViewSize.height; return mInfo; } private: MyGUI::IRenderTarget* mTarget; MyGUI::IntSize mViewSize; float mHOffset, mVOffset; mutable MyGUI::RenderTargetInfo mInfo; }; MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const { screenToLayerCoords(_left, _top); return OverlappedLayer::getLayerItemByPoint(_left, _top); } void ScalingLayer::screenToLayerCoords(int& _left, int& _top) const { float scale = getScaleFactor(mViewSize); if (scale <= 0.f) return; MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); _left -= globalViewSize.width/2; _top -= globalViewSize.height/2; _left = static_cast(_left/scale); _top = static_cast(_top/scale); _left += mViewSize.width/2; _top += mViewSize.height/2; } float ScalingLayer::getScaleFactor(const MyGUI::IntSize& _layerViewSize) { MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float w = static_cast(viewSize.width); float h = static_cast(viewSize.height); float heightScale = (h / _layerViewSize.height); float widthScale = (w / _layerViewSize.width); return std::min(widthScale, heightScale); } MyGUI::IntPoint ScalingLayer::getPosition(int _left, int _top) const { screenToLayerCoords(_left, _top); return MyGUI::IntPoint(_left, _top); } void ScalingLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) { MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntSize viewSize = globalViewSize; float scale = getScaleFactor(mViewSize); viewSize.width = static_cast(viewSize.width / scale); viewSize.height = static_cast(viewSize.height / scale); float hoffset = (globalViewSize.width - mViewSize.width*getScaleFactor(mViewSize))/2.f / static_cast(globalViewSize.width); float voffset = (globalViewSize.height - mViewSize.height*getScaleFactor(mViewSize))/2.f / static_cast(globalViewSize.height); ProxyRenderTarget proxy(_target, viewSize, hoffset, voffset); MyGUI::OverlappedLayer::renderToTarget(&proxy, _update); } void ScalingLayer::resizeView(const MyGUI::IntSize &_viewSize) { // do nothing } void ScalingLayer::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) { MyGUI::OverlappedLayer::deserialization(_node, _version); MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); while (info.next()) { if (info->getName() == "Property") { const std::string& key = info->findAttribute("key"); const std::string& value = info->findAttribute("value"); if (key == "Size") { mViewSize = MyGUI::IntSize::parse(value); } } } } } openmw-openmw-0.48.0/components/myguiplatform/scalinglayer.hpp000066400000000000000000000022101445372753700247010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER #define OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER #include namespace osgMyGUI { ///@brief A Layer that lays out and renders widgets in screen-relative coordinates. The "Size" property determines the size of the virtual screen, /// which is then upscaled to the real screen size during rendering. The aspect ratio is kept intact, adding blanks to the sides when necessary. class ScalingLayer final : public MyGUI::OverlappedLayer { public: MYGUI_RTTI_DERIVED(ScalingLayer) void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) override; MyGUI::ILayerItem* getLayerItemByPoint(int _left, int _top) const override; MyGUI::IntPoint getPosition(int _left, int _top) const override; void renderToTarget(MyGUI::IRenderTarget* _target, bool _update) override; void resizeView(const MyGUI::IntSize& _viewSize) override; static float getScaleFactor(const MyGUI::IntSize& _layerViewSize); private: void screenToLayerCoords(int& _left, int& _top) const; }; } #endif openmw-openmw-0.48.0/components/navmeshtool/000077500000000000000000000000001445372753700211605ustar00rootroot00000000000000openmw-openmw-0.48.0/components/navmeshtool/protocol.cpp000066400000000000000000000136601445372753700235330ustar00rootroot00000000000000#include "protocol.hpp" #include #include #include #include #include #include #include #include namespace NavMeshTool { namespace { std::string formatMagic(const char (&value)[std::size(messageMagic)]) { std::ostringstream stream; for (const char v : value) { if (std::isprint(v) && !std::isspace(v)) stream << '\'' << v << '\''; else stream << "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << static_cast(v); stream << ' '; } return stream.str(); } template struct Format : Serialization::Format> { using Serialization::Format>::operator(); template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, Message>> { if constexpr (mode == Serialization::Mode::Write) visitor(*this, messageMagic); else { static_assert(mode == Serialization::Mode::Read); char magic[std::size(messageMagic)]; visitor(*this, magic); if (std::memcmp(magic, messageMagic, sizeof(magic)) != 0) throw std::runtime_error("Bad navmeshtool message magic: " + formatMagic(magic)); } visitor(*this, value.mType); visitor(*this, value.mSize); if constexpr (mode == Serialization::Mode::Write) visitor(*this, value.mData, value.mSize); else visitor(*this, value.mData); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, ExpectedCells>> { visitor(*this, value.mCount); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, ProcessedCells>> { visitor(*this, value.mCount); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, ExpectedTiles>> { visitor(*this, value.mCount); } template auto operator()(Visitor&& visitor, T& value) const -> std::enable_if_t, GeneratedTiles>> { visitor(*this, value.mCount); } }; template std::vector serializeToVector(const T& value) { constexpr Format format; Serialization::SizeAccumulator sizeAccumulator; format(sizeAccumulator, value); std::vector buffer(sizeAccumulator.value()); format(Serialization::BinaryWriter(buffer.data(), buffer.data() + buffer.size()), value); return buffer; } template std::vector serializeImpl(const T& value) { const auto data = serializeToVector(value); const Message message {static_cast(T::sMessageType), static_cast(data.size()), data.data()}; return serializeToVector(message); } } std::vector serialize(const ExpectedCells& value) { return serializeImpl(value); } std::vector serialize(const ProcessedCells& value) { return serializeImpl(value); } std::vector serialize(const ExpectedTiles& value) { return serializeImpl(value); } std::vector serialize(const GeneratedTiles& value) { return serializeImpl(value); } const std::byte* deserialize(const std::byte* begin, const std::byte* end, Message& message) { try { constexpr Format format; Serialization::BinaryReader reader(begin, end); format(reader, message); return message.mData + message.mSize; } catch (const Serialization::NotEnoughData&) { return begin; } } TypedMessage decode(const Message& message) { constexpr Format format; Serialization::BinaryReader reader(message.mData, message.mData + message.mSize); switch (static_cast(message.mType)) { case MessageType::ExpectedCells: { ExpectedCells value; format(reader, value); return value; } case MessageType::ProcessedCells: { ProcessedCells value; format(reader, value); return value; } case MessageType::ExpectedTiles: { ExpectedTiles value; format(reader, value); return value; } case MessageType::GeneratedTiles: { GeneratedTiles value; format(reader, value); return value; } } throw std::logic_error("Unsupported message type: " + std::to_string(message.mType)); } } openmw-openmw-0.48.0/components/navmeshtool/protocol.hpp000066400000000000000000000033621445372753700235360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NAVMESHTOOL_PROTOCOL_H #define OPENMW_COMPONENTS_NAVMESHTOOL_PROTOCOL_H #include #include #include #include #include namespace NavMeshTool { inline constexpr char messageMagic[] = {'n', 'v', 't', 'm'}; enum class MessageType : std::uint64_t { ExpectedCells = 1, ProcessedCells = 2, ExpectedTiles = 3, GeneratedTiles = 4, }; struct Message { std::uint64_t mType = 0; std::uint64_t mSize = 0; const std::byte* mData = nullptr; }; struct ExpectedCells { static constexpr MessageType sMessageType = MessageType::ExpectedCells; std::uint64_t mCount = 0; }; struct ProcessedCells { static constexpr MessageType sMessageType = MessageType::ProcessedCells; std::uint64_t mCount = 0; }; struct ExpectedTiles { static constexpr MessageType sMessageType = MessageType::ExpectedTiles; std::uint64_t mCount = 0; }; struct GeneratedTiles { static constexpr MessageType sMessageType = MessageType::GeneratedTiles; std::uint64_t mCount = 0; }; using TypedMessage = std::variant< ExpectedCells, ProcessedCells, ExpectedTiles, GeneratedTiles >; std::vector serialize(const ExpectedCells& value); std::vector serialize(const ProcessedCells& value); std::vector serialize(const ExpectedTiles& value); std::vector serialize(const GeneratedTiles& value); const std::byte* deserialize(const std::byte* begin, const std::byte* end, Message& message); TypedMessage decode(const Message& message); } #endif openmw-openmw-0.48.0/components/nif/000077500000000000000000000000001445372753700173755ustar00rootroot00000000000000openmw-openmw-0.48.0/components/nif/base.cpp000066400000000000000000000013561445372753700210200ustar00rootroot00000000000000#include "base.hpp" namespace Nif { void Extra::read(NIFStream *nif) { if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) name = nif->getString(); else if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) { next.read(nif); recordSize = nif->getUInt(); } } void Named::read(NIFStream *nif) { name = nif->getString(); if (nif->getVersion() < NIFStream::generateVersion(10,0,1,0)) extra.read(nif); else extralist.read(nif); controller.read(nif); } void Named::post(NIFFile *nif) { extra.post(nif); extralist.post(nif); controller.post(nif); } } openmw-openmw-0.48.0/components/nif/base.hpp000066400000000000000000000026611445372753700210250ustar00rootroot00000000000000///This file holds the main classes of NIF Records used by everything else. #ifndef OPENMW_COMPONENTS_NIF_BASE_HPP #define OPENMW_COMPONENTS_NIF_BASE_HPP #include "recordptr.hpp" namespace Nif { struct File; struct Record; struct Stream; // An extra data record. All the extra data connected to an object form a linked list. struct Extra : public Record { std::string name; ExtraPtr next; // Next extra data record in the list unsigned int recordSize{0u}; void read(NIFStream *nif) override; void post(NIFFile *nif) override { next.post(nif); } }; struct Controller : public Record { enum Flags { Flag_Active = 0x8 }; enum ExtrapolationMode { Cycle = 0, Reverse = 2, Constant = 4, Mask = 6 }; ControllerPtr next; int flags; float frequency, phase; float timeStart, timeStop; NamedPtr target; void read(NIFStream *nif) override; void post(NIFFile *nif) override; bool isActive() const { return flags & Flag_Active; } ExtrapolationMode extrapolationMode() const { return static_cast(flags & Mask); } }; /// Has name, extra-data and controller struct Named : public Record { std::string name; ExtraPtr extra; ExtraList extralist; ControllerPtr controller; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; using NiSequenceStreamHelper = Named; } // Namespace #endif openmw-openmw-0.48.0/components/nif/controlled.cpp000066400000000000000000000071171445372753700222540ustar00rootroot00000000000000#include "controlled.hpp" #include "data.hpp" namespace Nif { void NiSourceTexture::read(NIFStream *nif) { Named::read(nif); external = nif->getChar() != 0; bool internal = false; if (external) filename = nif->getString(); else { if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3)) internal = nif->getChar(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) filename = nif->getString(); // Original file path of the internal texture } if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3)) { if (!external && internal) data.read(nif); } else { data.read(nif); } pixel = nif->getUInt(); mipmap = nif->getUInt(); alpha = nif->getUInt(); // Renderer hints, typically of no use for us /* bool mIsStatic = */nif->getChar(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103)) /* bool mDirectRendering = */nif->getBoolean(); if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4)) /* bool mPersistRenderData = */nif->getBoolean(); } void NiSourceTexture::post(NIFFile *nif) { Named::post(nif); data.post(nif); } void BSShaderTextureSet::read(NIFStream *nif) { nif->getSizedStrings(textures, nif->getUInt()); } void NiParticleModifier::read(NIFStream *nif) { next.read(nif); controller.read(nif); } void NiParticleModifier::post(NIFFile *nif) { next.post(nif); controller.post(nif); } void NiParticleGrowFade::read(NIFStream *nif) { NiParticleModifier::read(nif); growTime = nif->getFloat(); fadeTime = nif->getFloat(); } void NiParticleColorModifier::read(NIFStream *nif) { NiParticleModifier::read(nif); data.read(nif); } void NiParticleColorModifier::post(NIFFile *nif) { NiParticleModifier::post(nif); data.post(nif); } void NiGravity::read(NIFStream *nif) { NiParticleModifier::read(nif); mDecay = nif->getFloat(); mForce = nif->getFloat(); mType = nif->getUInt(); mPosition = nif->getVector3(); mDirection = nif->getVector3(); } void NiParticleCollider::read(NIFStream *nif) { NiParticleModifier::read(nif); mBounceFactor = nif->getFloat(); if (nif->getVersion() >= NIFStream::generateVersion(4,2,0,2)) { // Unused in NifSkope. Need to figure out what these do. /*bool mSpawnOnCollision = */nif->getBoolean(); /*bool mDieOnCollision = */nif->getBoolean(); } } void NiPlanarCollider::read(NIFStream *nif) { NiParticleCollider::read(nif); mExtents = nif->getVector2(); mPosition = nif->getVector3(); mXVector = nif->getVector3(); mYVector = nif->getVector3(); mPlaneNormal = nif->getVector3(); mPlaneDistance = nif->getFloat(); } void NiParticleRotation::read(NIFStream *nif) { NiParticleModifier::read(nif); /* bool mRandomInitialAxis = */nif->getChar(); /* osg::Vec3f mInitialAxis = */nif->getVector3(); /* float mRotationSpeed = */nif->getFloat(); } void NiSphericalCollider::read(NIFStream* nif) { NiParticleCollider::read(nif); mRadius = nif->getFloat(); mCenter = nif->getVector3(); } } openmw-openmw-0.48.0/components/nif/controlled.hpp000066400000000000000000000071101445372753700222520ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (controlled.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP #define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP #include "base.hpp" namespace Nif { struct NiSourceTexture : public Named { // Is this an external (references a separate texture file) or // internal (data is inside the nif itself) texture? bool external; std::string filename; // In case of external textures NiPixelDataPtr data; // In case of internal textures /* Pixel layout 0 - Palettised 1 - High color 16 2 - True color 32 3 - Compressed 4 - Bumpmap 5 - Default */ unsigned int pixel; /* Mipmap format 0 - no 1 - yes 2 - default */ unsigned int mipmap; /* Alpha 0 - none 1 - binary 2 - smooth 3 - default (use material alpha, or multiply material with texture if present) */ unsigned int alpha; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct BSShaderTextureSet : public Record { enum TextureType { TextureType_Base = 0, TextureType_Normal = 1, TextureType_Glow = 2, TextureType_Parallax = 3, TextureType_Env = 4, TextureType_EnvMask = 5, TextureType_Subsurface = 6, TextureType_BackLighting = 7 }; std::vector textures; void read(NIFStream *nif) override; }; struct NiParticleModifier : public Record { NiParticleModifierPtr next; ControllerPtr controller; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiParticleGrowFade : public NiParticleModifier { float growTime; float fadeTime; void read(NIFStream *nif) override; }; struct NiParticleColorModifier : public NiParticleModifier { NiColorDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiGravity : public NiParticleModifier { float mForce; /* 0 - Wind (fixed direction) * 1 - Point (fixed origin) */ int mType; float mDecay; osg::Vec3f mPosition; osg::Vec3f mDirection; void read(NIFStream *nif) override; }; struct NiParticleCollider : public NiParticleModifier { float mBounceFactor; void read(NIFStream *nif) override; }; // NiPinaColada struct NiPlanarCollider : public NiParticleCollider { osg::Vec2f mExtents; osg::Vec3f mPosition; osg::Vec3f mXVector, mYVector; osg::Vec3f mPlaneNormal; float mPlaneDistance; void read(NIFStream *nif) override; }; struct NiSphericalCollider : public NiParticleCollider { float mRadius; osg::Vec3f mCenter; void read(NIFStream *nif) override; }; struct NiParticleRotation : public NiParticleModifier { void read(NIFStream *nif) override; }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/controller.cpp000066400000000000000000000215021445372753700222640ustar00rootroot00000000000000#include "controller.hpp" #include "controlled.hpp" #include "data.hpp" #include "node.hpp" #include "recordptr.hpp" namespace Nif { void Controller::read(NIFStream *nif) { next.read(nif); flags = nif->getUShort(); frequency = nif->getFloat(); phase = nif->getFloat(); timeStart = nif->getFloat(); timeStop = nif->getFloat(); target.read(nif); } void Controller::post(NIFFile *nif) { Record::post(nif); next.post(nif); target.post(nif); } void NiParticleSystemController::read(NIFStream *nif) { Controller::read(nif); velocity = nif->getFloat(); velocityRandom = nif->getFloat(); verticalDir = nif->getFloat(); verticalAngle = nif->getFloat(); horizontalDir = nif->getFloat(); horizontalAngle = nif->getFloat(); /*normal?*/ nif->getVector3(); color = nif->getVector4(); size = nif->getFloat(); startTime = nif->getFloat(); stopTime = nif->getFloat(); nif->getChar(); emitRate = nif->getFloat(); lifetime = nif->getFloat(); lifetimeRandom = nif->getFloat(); emitFlags = nif->getUShort(); offsetRandom = nif->getVector3(); emitter.read(nif); /* Unknown Short, 0? * Unknown Float, 1.0? * Unknown Int, 1? * Unknown Int, 0? * Unknown Short, 0? */ nif->skip(16); numParticles = nif->getUShort(); activeCount = nif->getUShort(); particles.resize(numParticles); for(size_t i = 0;i < particles.size();i++) { particles[i].velocity = nif->getVector3(); nif->getVector3(); /* unknown */ particles[i].lifetime = nif->getFloat(); particles[i].lifespan = nif->getFloat(); particles[i].timestamp = nif->getFloat(); nif->getUShort(); /* unknown */ particles[i].vertex = nif->getUShort(); } nif->getUInt(); /* -1? */ affectors.read(nif); colliders.read(nif); nif->getChar(); } void NiParticleSystemController::post(NIFFile *nif) { Controller::post(nif); emitter.post(nif); affectors.post(nif); colliders.post(nif); } void NiMaterialColorController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() > NIFStream::generateVersion(10,1,0,103)) interpolator.read(nif); // Two bits that correspond to the controlled material color. // 00: Ambient // 01: Diffuse // 10: Specular // 11: Emissive if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) targetColor = nif->getUShort() & 3; else targetColor = (flags >> 4) & 3; if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) data.read(nif); } void NiMaterialColorController::post(NIFFile *nif) { Controller::post(nif); interpolator.post(nif); data.post(nif); } void NiLookAtController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) lookAtFlags = nif->getUShort(); target.read(nif); } void NiLookAtController::post(NIFFile *nif) { Controller::post(nif); target.post(nif); } void NiPathController::read(NIFStream *nif) { Controller::read(nif); bankDir = nif->getInt(); maxBankAngle = nif->getFloat(); smoothing = nif->getFloat(); followAxis = nif->getShort(); posData.read(nif); floatData.read(nif); } void NiPathController::post(NIFFile *nif) { Controller::post(nif); posData.post(nif); floatData.post(nif); } void NiUVController::read(NIFStream *nif) { Controller::read(nif); uvSet = nif->getUShort(); data.read(nif); } void NiUVController::post(NIFFile *nif) { Controller::post(nif); data.post(nif); } void NiKeyframeController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) data.read(nif); else interpolator.read(nif); } void NiKeyframeController::post(NIFFile *nif) { Controller::post(nif); data.post(nif); interpolator.post(nif); } void NiFloatInterpController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) data.read(nif); else interpolator.read(nif); } void NiFloatInterpController::post(NIFFile *nif) { Controller::post(nif); data.post(nif); interpolator.post(nif); } void NiGeomMorpherController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() >= NIFFile::NIFVersion::VER_OB_OLD) /*bool updateNormals = !!*/nif->getUShort(); data.read(nif); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) { /*bool alwaysActive = */nif->getChar(); // Always 0 if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,106)) { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { interpolators.read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,2,0,0) && nif->getBethVersion() > 9) { unsigned int numUnknown = nif->getUInt(); nif->skip(4 * numUnknown); } } else { // TODO: handle weighted interpolators unsigned int numInterps = nif->getUInt(); nif->skip(8 * numInterps); } } } } void NiGeomMorpherController::post(NIFFile *nif) { Controller::post(nif); data.post(nif); interpolators.post(nif); } void NiVisController::read(NIFStream *nif) { Controller::read(nif); data.read(nif); } void NiVisController::post(NIFFile *nif) { Controller::post(nif); data.post(nif); } void NiFlipController::read(NIFStream *nif) { Controller::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,2,0,0)) mInterpolator.read(nif); mTexSlot = nif->getUInt(); if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) { timeStart = nif->getFloat(); mDelta = nif->getFloat(); } mSources.read(nif); } void NiFlipController::post(NIFFile *nif) { Controller::post(nif); mInterpolator.post(nif); mSources.post(nif); } void bhkBlendController::read(NIFStream *nif) { Controller::read(nif); nif->getUInt(); // Zero } void NiControllerManager::read(NIFStream *nif) { Controller::read(nif); mCumulative = nif->getBoolean(); unsigned int numSequences = nif->getUInt(); nif->skip(4 * numSequences); // Controller sequences nif->skip(4); // Object palette } void NiPoint3Interpolator::read(NIFStream *nif) { defaultVal = nif->getVector3(); data.read(nif); } void NiPoint3Interpolator::post(NIFFile *nif) { data.post(nif); } void NiBoolInterpolator::read(NIFStream *nif) { defaultVal = nif->getBoolean(); data.read(nif); } void NiBoolInterpolator::post(NIFFile *nif) { data.post(nif); } void NiFloatInterpolator::read(NIFStream *nif) { defaultVal = nif->getFloat(); data.read(nif); } void NiFloatInterpolator::post(NIFFile *nif) { data.post(nif); } void NiTransformInterpolator::read(NIFStream *nif) { defaultPos = nif->getVector3(); defaultRot = nif->getQuaternion(); defaultScale = nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,109)) { if (!nif->getBoolean()) defaultPos = osg::Vec3f(); if (!nif->getBoolean()) defaultRot = osg::Quat(); if (!nif->getBoolean()) defaultScale = 1.f; } data.read(nif); } void NiTransformInterpolator::post(NIFFile *nif) { data.post(nif); } void NiColorInterpolator::read(NIFStream *nif) { defaultVal = nif->getVector4(); data.read(nif); } void NiColorInterpolator::post(NIFFile *nif) { data.post(nif); } } openmw-openmw-0.48.0/components/nif/controller.hpp000066400000000000000000000136451445372753700223020ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (controller.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP #define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP #include "base.hpp" namespace Nif { struct NiParticleSystemController : public Controller { enum BSPArrayController { BSPArrayController_AtNode = 0x8, BSPArrayController_AtVertex = 0x10 }; struct Particle { osg::Vec3f velocity; float lifetime; float lifespan; float timestamp; unsigned short vertex; }; float velocity; float velocityRandom; float verticalDir; // 0=up, pi/2=horizontal, pi=down float verticalAngle; float horizontalDir; float horizontalAngle; osg::Vec4f color; float size; float startTime; float stopTime; float emitRate; float lifetime; float lifetimeRandom; enum EmitFlags { EmitFlag_NoAutoAdjust = 0x1 // If this flag is set, we use the emitRate value. Otherwise, // we calculate an emit rate so that the maximum number of particles // in the system (numParticles) is never exceeded. }; int emitFlags; osg::Vec3f offsetRandom; NodePtr emitter; int numParticles; int activeCount; std::vector particles; NiParticleModifierPtr affectors; NiParticleModifierPtr colliders; void read(NIFStream *nif) override; void post(NIFFile *nif) override; bool noAutoAdjust() const { return emitFlags & EmitFlag_NoAutoAdjust; } bool emitAtVertex() const { return flags & BSPArrayController_AtVertex; } }; using NiBSPArrayController = NiParticleSystemController; struct NiMaterialColorController : public Controller { NiPoint3InterpolatorPtr interpolator; NiPosDataPtr data; unsigned int targetColor; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiPathController : public Controller { NiPosDataPtr posData; NiFloatDataPtr floatData; enum Flags { Flag_OpenCurve = 0x020, Flag_AllowFlip = 0x040, Flag_Bank = 0x080, Flag_ConstVelocity = 0x100, Flag_Follow = 0x200, Flag_FlipFollowAxis = 0x400 }; int bankDir; float maxBankAngle, smoothing; short followAxis; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiLookAtController : public Controller { NodePtr target; unsigned short lookAtFlags{0}; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiUVController : public Controller { NiUVDataPtr data; unsigned int uvSet; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiKeyframeController : public Controller { NiKeyframeDataPtr data; NiTransformInterpolatorPtr interpolator; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiFloatInterpController : public Controller { NiFloatDataPtr data; NiFloatInterpolatorPtr interpolator; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiAlphaController : public NiFloatInterpController { }; struct NiRollController : public NiFloatInterpController { }; struct NiGeomMorpherController : public Controller { NiMorphDataPtr data; NiFloatInterpolatorList interpolators; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiVisController : public Controller { NiVisDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiFlipController : public Controller { NiFloatInterpolatorPtr mInterpolator; int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct bhkBlendController : public Controller { void read(NIFStream *nif) override; }; struct NiControllerManager : public Controller { bool mCumulative; void read(NIFStream *nif) override; }; struct Interpolator : public Record { }; struct NiPoint3Interpolator : public Interpolator { osg::Vec3f defaultVal; NiPosDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiBoolInterpolator : public Interpolator { bool defaultVal; NiBoolDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiFloatInterpolator : public Interpolator { float defaultVal; NiFloatDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiTransformInterpolator : public Interpolator { osg::Vec3f defaultPos; osg::Quat defaultRot; float defaultScale; NiKeyframeDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiColorInterpolator : public Interpolator { osg::Vec4f defaultVal; NiColorDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/data.cpp000066400000000000000000000334571445372753700210260ustar00rootroot00000000000000#include "data.hpp" #include "nifkey.hpp" #include "node.hpp" namespace Nif { void NiSkinInstance::read(NIFStream *nif) { data.read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,101)) partitions.read(nif); root.read(nif); bones.read(nif); } void NiSkinInstance::post(NIFFile *nif) { data.post(nif); partitions.post(nif); root.post(nif); bones.post(nif); if(data.empty() || root.empty()) nif->fail("NiSkinInstance missing root or data"); size_t bnum = bones.length(); if(bnum != data->bones.size()) nif->fail("Mismatch in NiSkinData bone count"); for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); bones[i]->setBone(); } } void BSDismemberSkinInstance::read(NIFStream *nif) { NiSkinInstance::read(nif); unsigned int numPartitions = nif->getUInt(); nif->skip(4 * numPartitions); // Body part information } void NiGeometryData::read(NIFStream *nif) { if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,114)) nif->getInt(); // Group ID. (Almost?) always 0. int verts = nif->getUShort(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) nif->skip(2); // Keep flags and compress flags if (nif->getBoolean()) nif->getVector3s(vertices, verts); unsigned int dataFlags = 0; if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) dataFlags = nif->getUShort(); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) nif->getUInt(); // Material CRC if (nif->getBoolean()) { nif->getVector3s(normals, verts); if (dataFlags & 0x1000) { nif->getVector3s(tangents, verts); nif->getVector3s(bitangents, verts); } } center = nif->getVector3(); radius = nif->getFloat(); if (nif->getBoolean()) nif->getVector4s(colors, verts); unsigned int numUVs = dataFlags; if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) numUVs = nif->getUShort(); // In Morrowind this field only corresponds to the number of UV sets. // In later games only the first 6 bits are used as a count and the rest are flags. if (nif->getVersion() > NIFFile::NIFVersion::VER_MW) { numUVs &= 0x3f; if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) numUVs &= 0x1; } bool hasUVs = true; if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) hasUVs = nif->getBoolean(); if (hasUVs) { uvlist.resize(numUVs); for (unsigned int i = 0; i < numUVs; i++) { nif->getVector2s(uvlist[i], verts); // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin for (unsigned int uv=0; uvgetVersion() >= NIFStream::generateVersion(10,0,1,0)) nif->getUShort(); // Consistency flags if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4)) nif->skip(4); // Additional data } void NiTriShapeData::read(NIFStream *nif) { NiGeometryData::read(nif); /*int tris =*/ nif->getUShort(); // We have three times as many vertices as triangles, so this // is always equal to tris*3. int cnt = nif->getInt(); bool hasTriangles = true; if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) hasTriangles = nif->getBoolean(); if (hasTriangles) nif->getUShorts(triangles, cnt); // Read the match list, which lists the vertices that are equal to // vertices. We don't actually need need this for anything, so // just skip it. unsigned short verts = nif->getUShort(); for (unsigned short i=0; i < verts; i++) { // Number of vertices matching vertex 'i' int num = nif->getUShort(); nif->skip(num * sizeof(short)); } } void NiTriStripsData::read(NIFStream *nif) { NiGeometryData::read(nif); // Every strip with n points defines n-2 triangles, so this should be unnecessary. /*int tris =*/ nif->getUShort(); // Number of triangle strips int numStrips = nif->getUShort(); std::vector lengths; nif->getUShorts(lengths, numStrips); // "Has Strips" flag. Exceptionally useful. bool hasStrips = true; if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) hasStrips = nif->getBoolean(); if (!hasStrips || !numStrips) return; strips.resize(numStrips); for (int i = 0; i < numStrips; i++) nif->getUShorts(strips[i], lengths[i]); } void NiLinesData::read(NIFStream *nif) { NiGeometryData::read(nif); size_t num = vertices.size(); std::vector flags; nif->getChars(flags, num); // Can't construct a line from a single vertex. if (num < 2) return; // Convert connectivity flags into usable geometry. The last element needs special handling. for (size_t i = 0; i < num-1; ++i) { if (flags[i] & 1) { lines.emplace_back(i); lines.emplace_back(i+1); } } // If there are just two vertices, they can be connected twice. Probably isn't critical. if (flags[num-1] & 1) { lines.emplace_back(num-1); lines.emplace_back(0); } } void NiParticlesData::read(NIFStream *nif) { NiGeometryData::read(nif); // Should always match the number of vertices if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) numParticles = nif->getUShort(); if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat()); else if (nif->getBoolean()) nif->getFloats(particleRadii, vertices.size()); activeCount = nif->getUShort(); // Particle sizes if (nif->getBoolean()) nif->getFloats(sizes, vertices.size()); if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0) && nif->getBoolean()) nif->getQuaternions(rotations, vertices.size()); if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4)) { if (nif->getBoolean()) nif->getFloats(rotationAngles, vertices.size()); if (nif->getBoolean()) nif->getVector3s(rotationAxes, vertices.size()); } } void NiRotatingParticlesData::read(NIFStream *nif) { NiParticlesData::read(nif); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean()) nif->getQuaternions(rotations, vertices.size()); } void NiPosData::read(NIFStream *nif) { mKeyList = std::make_shared(); mKeyList->read(nif); } void NiUVData::read(NIFStream *nif) { for(int i = 0;i < 4;i++) { mKeyList[i] = std::make_shared(); mKeyList[i]->read(nif); } } void NiFloatData::read(NIFStream *nif) { mKeyList = std::make_shared(); mKeyList->read(nif); } void NiPixelData::read(NIFStream *nif) { fmt = (Format)nif->getUInt(); if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2)) { for (unsigned int i = 0; i < 4; ++i) colorMask[i] = nif->getUInt(); bpp = nif->getUInt(); nif->skip(8); // "Old Fast Compare". Whatever that means. if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) pixelTiling = nif->getUInt(); } else // TODO: see if anything from here needs to be implemented { bpp = nif->getChar(); nif->skip(4); // Renderer hint nif->skip(4); // Extra data nif->skip(4); // Flags pixelTiling = nif->getUInt(); if (nif->getVersion() >= NIFStream::generateVersion(20,3,0,4)) sRGB = nif->getBoolean(); nif->skip(4*10); // Channel data } palette.read(nif); numberOfMipmaps = nif->getUInt(); // Bytes per pixel, should be bpp / 8 /* int bytes = */ nif->getUInt(); for(unsigned int i=0; igetUInt(); m.height = nif->getUInt(); m.dataOffset = nif->getUInt(); mipmaps.push_back(m); } // Read the data unsigned int numPixels = nif->getUInt(); bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10,4,0,2); unsigned int numFaces = hasFaces ? nif->getUInt() : 1; if (numPixels && numFaces) nif->getUChars(data, numPixels * numFaces); } void NiPixelData::post(NIFFile *nif) { palette.post(nif); } void NiColorData::read(NIFStream *nif) { mKeyMap = std::make_shared(); mKeyMap->read(nif); } void NiVisData::read(NIFStream *nif) { int count = nif->getInt(); mVis.resize(count); for(size_t i = 0;i < mVis.size();i++) { mVis[i].time = nif->getFloat(); mVis[i].isSet = (nif->getChar() != 0); } } void NiSkinData::read(NIFStream *nif) { trafo.rotation = nif->getMatrix3(); trafo.pos = nif->getVector3(); trafo.scale = nif->getFloat(); int boneNum = nif->getInt(); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) partitions.read(nif); // Has vertex weights flag if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean()) return; bones.resize(boneNum); for (BoneInfo &bi : bones) { bi.trafo.rotation = nif->getMatrix3(); bi.trafo.pos = nif->getVector3(); bi.trafo.scale = nif->getFloat(); bi.boundSphereCenter = nif->getVector3(); bi.boundSphereRadius = nif->getFloat(); // Number of vertex weights bi.weights.resize(nif->getUShort()); for(size_t j = 0;j < bi.weights.size();j++) { bi.weights[j].vertex = nif->getUShort(); bi.weights[j].weight = nif->getFloat(); } } } void NiSkinData::post(NIFFile *nif) { partitions.post(nif); } void NiSkinPartition::read(NIFStream *nif) { unsigned int num = nif->getUInt(); data.resize(num); for (auto& partition : data) partition.read(nif); } void NiSkinPartition::Partition::read(NIFStream *nif) { size_t numVertices = nif->getUShort(); size_t numTriangles = nif->getUShort(); size_t numBones = nif->getUShort(); size_t numStrips = nif->getUShort(); size_t bonesPerVertex = nif->getUShort(); if (numBones) nif->getUShorts(bones, numBones); bool hasVertexMap = true; if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) hasVertexMap = nif->getBoolean(); if (hasVertexMap && numVertices) nif->getUShorts(vertexMap, numVertices); bool hasVertexWeights = true; if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) hasVertexWeights = nif->getBoolean(); if (hasVertexWeights && numVertices && bonesPerVertex) nif->getFloats(weights, numVertices * bonesPerVertex); std::vector stripLengths; if (numStrips) nif->getUShorts(stripLengths, numStrips); bool hasFaces = true; if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) hasFaces = nif->getBoolean(); if (hasFaces) { if (numStrips) { strips.resize(numStrips); for (size_t i = 0; i < numStrips; i++) nif->getUShorts(strips[i], stripLengths[i]); } else if (numTriangles) nif->getUShorts(triangles, numTriangles * 3); } bool hasBoneIndices = nif->getChar() != 0; if (hasBoneIndices && numVertices && bonesPerVertex) nif->getChars(boneIndices, numVertices * bonesPerVertex); if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { nif->getChar(); // LOD level nif->getBoolean(); // Global VB } } void NiMorphData::read(NIFStream *nif) { int morphCount = nif->getInt(); int vertCount = nif->getInt(); nif->getChar(); // Relative targets, always 1 mMorphs.resize(morphCount); for(int i = 0;i < morphCount;i++) { mMorphs[i].mKeyFrames = std::make_shared(); mMorphs[i].mKeyFrames->read(nif, /*morph*/true); nif->getVector3s(mMorphs[i].mVertices, vertCount); } } void NiKeyframeData::read(NIFStream *nif) { mRotations = std::make_shared(); mRotations->read(nif); if(mRotations->mInterpolationType == InterpolationType_XYZ) { if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) mAxisOrder = static_cast(nif->getInt()); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); mXRotations->read(nif); mYRotations->read(nif); mZRotations->read(nif); } mTranslations = std::make_shared(); mTranslations->read(nif); mScales = std::make_shared(); mScales->read(nif); } void NiPalette::read(NIFStream *nif) { unsigned int alphaMask = !nif->getChar() ? 0xFF000000 : 0; // Fill the entire palette with black even if there isn't enough entries. colors.resize(256); unsigned int numEntries = nif->getUInt(); for (unsigned int i = 0; i < numEntries; i++) colors[i] = nif->getUInt() | alphaMask; } void NiStringPalette::read(NIFStream *nif) { palette = nif->getString(); if (nif->getUInt() != palette.size()) nif->file->warn("Failed size check in NiStringPalette"); } void NiBoolData::read(NIFStream *nif) { mKeyList = std::make_shared(); mKeyList->read(nif); } } // Namespace openmw-openmw-0.48.0/components/nif/data.hpp000066400000000000000000000137231445372753700210250ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (data.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_DATA_HPP #define OPENMW_COMPONENTS_NIF_DATA_HPP #include "base.hpp" #include "nifkey.hpp" #include "niftypes.hpp" // Transformation namespace Nif { // Common ancestor for several data classes struct NiGeometryData : public Record { std::vector vertices, normals, tangents, bitangents; std::vector colors; std::vector< std::vector > uvlist; osg::Vec3f center; float radius; void read(NIFStream *nif) override; }; struct NiTriShapeData : public NiGeometryData { // Triangles, three vertex indices per triangle std::vector triangles; void read(NIFStream *nif) override; }; struct NiTriStripsData : public NiGeometryData { // Triangle strips, series of vertex indices. std::vector> strips; void read(NIFStream *nif) override; }; struct NiLinesData : public NiGeometryData { // Lines, series of indices that correspond to connected vertices. std::vector lines; void read(NIFStream *nif) override; }; struct NiParticlesData : public NiGeometryData { int numParticles{0}; int activeCount{0}; std::vector particleRadii, sizes, rotationAngles; std::vector rotations; std::vector rotationAxes; void read(NIFStream *nif) override; }; struct NiRotatingParticlesData : public NiParticlesData { void read(NIFStream *nif) override; }; struct NiPosData : public Record { Vector3KeyMapPtr mKeyList; void read(NIFStream *nif) override; }; struct NiUVData : public Record { FloatKeyMapPtr mKeyList[4]; void read(NIFStream *nif) override; }; struct NiFloatData : public Record { FloatKeyMapPtr mKeyList; void read(NIFStream *nif) override; }; struct NiPixelData : public Record { enum Format { NIPXFMT_RGB8, NIPXFMT_RGBA8, NIPXFMT_PAL8, NIPXFMT_PALA8, NIPXFMT_DXT1, NIPXFMT_DXT3, NIPXFMT_DXT5, NIPXFMT_DXT5_ALT }; Format fmt{NIPXFMT_RGB8}; unsigned int colorMask[4]{0}; unsigned int bpp{0}, pixelTiling{0}; bool sRGB{false}; NiPalettePtr palette; unsigned int numberOfMipmaps{0}; struct Mipmap { int width, height; int dataOffset; }; std::vector mipmaps; std::vector data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiColorData : public Record { Vector4KeyMapPtr mKeyMap; void read(NIFStream *nif) override; }; struct NiVisData : public Record { struct VisData { float time; bool isSet; }; std::vector mVis; void read(NIFStream *nif) override; }; struct NiSkinInstance : public Record { NiSkinDataPtr data; NiSkinPartitionPtr partitions; NodePtr root; NodeList bones; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct BSDismemberSkinInstance : public NiSkinInstance { void read(NIFStream *nif) override; }; struct NiSkinData : public Record { struct VertWeight { unsigned short vertex; float weight; }; struct BoneInfo { Transformation trafo; osg::Vec3f boundSphereCenter; float boundSphereRadius; std::vector weights; }; Transformation trafo; std::vector bones; NiSkinPartitionPtr partitions; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiSkinPartition : public Record { struct Partition { std::vector bones; std::vector vertexMap; std::vector weights; std::vector> strips; std::vector triangles; std::vector boneIndices; void read(NIFStream *nif); }; std::vector data; void read(NIFStream *nif) override; }; struct NiMorphData : public Record { struct MorphData { FloatKeyMapPtr mKeyFrames; std::vector mVertices; }; std::vector mMorphs; void read(NIFStream *nif) override; }; struct NiKeyframeData : public Record { QuaternionKeyMapPtr mRotations; // may be NULL FloatKeyMapPtr mXRotations; FloatKeyMapPtr mYRotations; FloatKeyMapPtr mZRotations; Vector3KeyMapPtr mTranslations; FloatKeyMapPtr mScales; enum class AxisOrder { Order_XYZ = 0, Order_XZY = 1, Order_YZX = 2, Order_YXZ = 3, Order_ZXY = 4, Order_ZYX = 5, Order_XYX = 6, Order_YZY = 7, Order_ZXZ = 8 }; AxisOrder mAxisOrder{AxisOrder::Order_XYZ}; void read(NIFStream *nif) override; }; struct NiPalette : public Record { // 32-bit RGBA colors that correspond to 8-bit indices std::vector colors; void read(NIFStream *nif) override; }; struct NiStringPalette : public Record { std::string palette; void read(NIFStream *nif) override; }; struct NiBoolData : public Record { ByteKeyMapPtr mKeyList; void read(NIFStream *nif) override; }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/effect.cpp000066400000000000000000000036571445372753700213500ustar00rootroot00000000000000#include "effect.hpp" #include "controlled.hpp" #include "node.hpp" namespace Nif { void NiDynamicEffect::read(NIFStream *nif) { Node::read(nif); if (nif->getVersion() >= nif->generateVersion(10,1,0,106) && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) nif->getBoolean(); // Switch state unsigned int numAffectedNodes = nif->getUInt(); for (unsigned int i=0; igetUInt(); // ref to another Node } void NiLight::read(NIFStream *nif) { NiDynamicEffect::read(nif); dimmer = nif->getFloat(); ambient = nif->getVector3(); diffuse = nif->getVector3(); specular = nif->getVector3(); } void NiTextureEffect::read(NIFStream *nif) { NiDynamicEffect::read(nif); // Model Projection Matrix nif->skip(3 * 3 * sizeof(float)); // Model Projection Transform nif->skip(3 * sizeof(float)); // Texture Filtering nif->skip(4); // Max anisotropy samples if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4)) nif->skip(2); clamp = nif->getUInt(); textureType = (TextureType)nif->getUInt(); coordGenType = (CoordGenType)nif->getUInt(); texture.read(nif); nif->skip(1); // Use clipping plane nif->skip(16); // Clipping plane dimensions vector if (nif->getVersion() <= NIFStream::generateVersion(10,2,0,0)) nif->skip(4); // PS2-specific shorts if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,12)) nif->skip(2); // Unknown short } void NiTextureEffect::post(NIFFile *nif) { NiDynamicEffect::post(nif); texture.post(nif); } void NiPointLight::read(NIFStream *nif) { NiLight::read(nif); constantAttenuation = nif->getFloat(); linearAttenuation = nif->getFloat(); quadraticAttenuation = nif->getFloat(); } void NiSpotLight::read(NIFStream *nif) { NiPointLight::read(nif); cutoff = nif->getFloat(); exponent = nif->getFloat(); } } openmw-openmw-0.48.0/components/nif/effect.hpp000066400000000000000000000043161445372753700213460ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (effect.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_EFFECT_HPP #define OPENMW_COMPONENTS_NIF_EFFECT_HPP #include "node.hpp" namespace Nif { struct NiDynamicEffect : public Node { void read(NIFStream *nif) override; }; // Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight. struct NiLight : NiDynamicEffect { float dimmer; osg::Vec3f ambient; osg::Vec3f diffuse; osg::Vec3f specular; void read(NIFStream *nif) override; }; struct NiPointLight : public NiLight { float constantAttenuation; float linearAttenuation; float quadraticAttenuation; void read(NIFStream *nif) override; }; struct NiSpotLight : public NiPointLight { float cutoff; float exponent; void read(NIFStream *nif) override; }; struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; unsigned int clamp; enum TextureType { Projected_Light = 0, Projected_Shadow = 1, Environment_Map = 2, Fog_Map = 3 }; TextureType textureType; enum CoordGenType { World_Parallel = 0, World_Perspective, Sphere_Map, Specular_Cube_Map, Diffuse_Cube_Map }; CoordGenType coordGenType; void read(NIFStream *nif) override; void post(NIFFile *nif) override; bool wrapT() const { return clamp & 1; } bool wrapS() const { return (clamp >> 1) & 1; } }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/extra.cpp000066400000000000000000000047461445372753700212370ustar00rootroot00000000000000#include "extra.hpp" namespace Nif { void NiExtraData::read(NIFStream *nif) { Extra::read(nif); if (recordSize) nif->getChars(data, recordSize); } void NiStringExtraData::read(NIFStream *nif) { Extra::read(nif); string = nif->getString(); } void NiTextKeyExtraData::read(NIFStream *nif) { Extra::read(nif); int keynum = nif->getInt(); list.resize(keynum); for(int i=0; igetFloat(); list[i].text = nif->getString(); } } void NiVertWeightsExtraData::read(NIFStream *nif) { Extra::read(nif); nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess } void NiIntegerExtraData::read(NIFStream *nif) { Extra::read(nif); data = nif->getUInt(); } void NiIntegersExtraData::read(NIFStream *nif) { Extra::read(nif); unsigned int num = nif->getUInt(); if (num) nif->getUInts(data, num); } void NiBinaryExtraData::read(NIFStream *nif) { Extra::read(nif); unsigned int size = nif->getUInt(); if (size) nif->getChars(data, size); } void NiBooleanExtraData::read(NIFStream *nif) { Extra::read(nif); data = nif->getBoolean(); } void NiVectorExtraData::read(NIFStream *nif) { Extra::read(nif); data = nif->getVector4(); } void NiFloatExtraData::read(NIFStream *nif) { Extra::read(nif); data = nif->getFloat(); } void NiFloatsExtraData::read(NIFStream *nif) { Extra::read(nif); unsigned int num = nif->getUInt(); if (num) nif->getFloats(data, num); } void BSBound::read(NIFStream *nif) { Extra::read(nif); center = nif->getVector3(); halfExtents = nif->getVector3(); } void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream *nif) { mOffset = nif->getVector3(); mOrientation = nif->getUShort(); mPositionRef = nif->getChar(); nif->skip(1); // Position ref 2 } void BSFurnitureMarker::FurniturePosition::read(NIFStream *nif) { mOffset = nif->getVector3(); mHeading = nif->getFloat(); mType = nif->getUShort(); mEntryPoint = nif->getUShort(); } void BSFurnitureMarker::read(NIFStream *nif) { Extra::read(nif); unsigned int num = nif->getUInt(); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { mLegacyMarkers.resize(num); for (auto& marker : mLegacyMarkers) marker.read(nif); } else { mMarkers.resize(num); for (auto& marker : mMarkers) marker.read(nif); } } } openmw-openmw-0.48.0/components/nif/extra.hpp000066400000000000000000000060571445372753700212410ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (extra.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP #define OPENMW_COMPONENTS_NIF_EXTRA_HPP #include "base.hpp" namespace Nif { struct NiExtraData : public Extra { std::vector data; void read(NIFStream *nif) override; }; struct NiVertWeightsExtraData : public Extra { void read(NIFStream *nif) override; }; struct NiTextKeyExtraData : public Extra { struct TextKey { float time; std::string text; }; std::vector list; void read(NIFStream *nif) override; }; struct NiStringExtraData : public Extra { /* Known meanings: "MRK" - marker, only visible in the editor, not rendered in-game "NCC" - no collision except with the camera Anything else starting with "NC" - no collision */ std::string string; void read(NIFStream *nif) override; }; struct NiIntegerExtraData : public Extra { unsigned int data; void read(NIFStream *nif) override; }; struct NiIntegersExtraData : public Extra { std::vector data; void read(NIFStream *nif) override; }; struct NiBinaryExtraData : public Extra { std::vector data; void read(NIFStream *nif) override; }; struct NiBooleanExtraData : public Extra { bool data; void read(NIFStream *nif) override; }; struct NiVectorExtraData : public Extra { osg::Vec4f data; void read(NIFStream *nif) override; }; struct NiFloatExtraData : public Extra { float data; void read(NIFStream *nif) override; }; struct NiFloatsExtraData : public Extra { std::vector data; void read(NIFStream *nif) override; }; struct BSBound : public Extra { osg::Vec3f center, halfExtents; void read(NIFStream *nif) override; }; struct BSFurnitureMarker : public Extra { struct LegacyFurniturePosition { osg::Vec3f mOffset; uint16_t mOrientation; uint8_t mPositionRef; void read(NIFStream *nif); }; struct FurniturePosition { osg::Vec3f mOffset; float mHeading; uint16_t mType; uint16_t mEntryPoint; void read(NIFStream *nif); }; std::vector mLegacyMarkers; std::vector mMarkers; void read(NIFStream *nif) override; }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/niffile.cpp000066400000000000000000000532131445372753700215210ustar00rootroot00000000000000#include "niffile.hpp" #include #include #include #include #include #include #include #include #include "controlled.hpp" #include "controller.hpp" #include "data.hpp" #include "effect.hpp" #include "extra.hpp" #include "physics.hpp" #include "property.hpp" namespace Nif { /// Open a NIF stream. The name is used for error messages. NIFFile::NIFFile(Files::IStreamPtr&& stream, const std::string &name) : filename(name) { parse(std::move(stream)); } template static std::unique_ptr construct() { auto result = std::make_unique(); result->recType = recordType; return result; } using CreateRecord = std::unique_ptr (*)(); ///These are all the record types we know how to read. static std::map makeFactory() { return { {"NiNode" , &construct }, {"NiSwitchNode" , &construct }, {"NiLODNode" , &construct }, {"NiFltAnimationNode" , &construct }, {"AvoidNode" , &construct }, {"NiCollisionSwitch" , &construct }, {"NiBSParticleNode" , &construct }, {"NiBSAnimationNode" , &construct }, {"NiBillboardNode" , &construct }, {"NiTriShape" , &construct }, {"NiTriStrips" , &construct }, {"NiLines" , &construct }, {"NiParticles" , &construct }, {"NiRotatingParticles" , &construct }, {"NiAutoNormalParticles" , &construct }, {"NiCamera" , &construct }, {"RootCollisionNode" , &construct }, {"NiTexturingProperty" , &construct }, {"NiFogProperty" , &construct }, {"NiMaterialProperty" , &construct }, {"NiZBufferProperty" , &construct }, {"NiAlphaProperty" , &construct }, {"NiVertexColorProperty" , &construct }, {"NiShadeProperty" , &construct }, {"NiDitherProperty" , &construct }, {"NiWireframeProperty" , &construct }, {"NiSpecularProperty" , &construct }, {"NiStencilProperty" , &construct }, {"NiVisController" , &construct }, {"NiGeomMorpherController" , &construct }, {"NiKeyframeController" , &construct }, {"NiAlphaController" , &construct }, {"NiRollController" , &construct }, {"NiUVController" , &construct }, {"NiPathController" , &construct }, {"NiMaterialColorController" , &construct }, {"NiBSPArrayController" , &construct }, {"NiParticleSystemController" , &construct }, {"NiFlipController" , &construct }, {"NiAmbientLight" , &construct }, {"NiDirectionalLight" , &construct }, {"NiPointLight" , &construct }, {"NiSpotLight" , &construct }, {"NiTextureEffect" , &construct }, {"NiExtraData" , &construct }, {"NiVertWeightsExtraData" , &construct }, {"NiTextKeyExtraData" , &construct }, {"NiStringExtraData" , &construct }, {"NiGravity" , &construct }, {"NiPlanarCollider" , &construct }, {"NiSphericalCollider" , &construct }, {"NiParticleGrowFade" , &construct }, {"NiParticleColorModifier" , &construct }, {"NiParticleRotation" , &construct }, {"NiFloatData" , &construct }, {"NiTriShapeData" , &construct }, {"NiTriStripsData" , &construct }, {"NiLinesData" , &construct }, {"NiVisData" , &construct }, {"NiColorData" , &construct }, {"NiPixelData" , &construct }, {"NiMorphData" , &construct }, {"NiKeyframeData" , &construct }, {"NiSkinData" , &construct }, {"NiUVData" , &construct }, {"NiPosData" , &construct }, {"NiParticlesData" , &construct }, {"NiRotatingParticlesData" , &construct }, {"NiAutoNormalParticlesData" , &construct }, {"NiSequenceStreamHelper" , &construct }, {"NiSourceTexture" , &construct }, {"NiSkinInstance" , &construct }, {"NiLookAtController" , &construct }, {"NiPalette" , &construct }, {"NiIntegerExtraData" , &construct }, {"NiIntegersExtraData" , &construct }, {"NiBinaryExtraData" , &construct }, {"NiBooleanExtraData" , &construct }, {"NiVectorExtraData" , &construct }, {"NiColorExtraData" , &construct }, {"NiFloatExtraData" , &construct }, {"NiFloatsExtraData" , &construct }, {"NiStringPalette" , &construct }, {"NiBoolData" , &construct }, {"NiSkinPartition" , &construct }, {"BSXFlags" , &construct }, {"BSBound" , &construct }, {"NiTransformData" , &construct }, {"BSFadeNode" , &construct }, {"bhkBlendController" , &construct }, {"NiFloatInterpolator" , &construct }, {"NiBoolInterpolator" , &construct }, {"NiPoint3Interpolator" , &construct }, {"NiTransformController" , &construct }, {"NiTransformInterpolator" , &construct }, {"NiColorInterpolator" , &construct }, {"BSShaderTextureSet" , &construct }, {"BSLODTriShape" , &construct }, {"BSShaderProperty" , &construct }, {"BSShaderPPLightingProperty" , &construct }, {"BSShaderNoLightingProperty" , &construct }, {"BSFurnitureMarker" , &construct }, {"NiCollisionObject" , &construct }, {"bhkCollisionObject" , &construct }, {"BSDismemberSkinInstance" , &construct }, {"NiControllerManager" , &construct }, {"bhkMoppBvTreeShape" , &construct }, {"bhkNiTriStripsShape" , &construct }, {"bhkPackedNiTriStripsShape" , &construct }, {"hkPackedNiTriStripsData" , &construct }, {"bhkConvexVerticesShape" , &construct }, {"bhkBoxShape" , &construct }, {"bhkListShape" , &construct }, {"bhkRigidBody" , &construct }, {"bhkRigidBodyT" , &construct }, {"BSLightingShaderProperty" , &construct }, {"NiSortAdjustNode" , &construct }, {"NiClusterAccumulator" , &construct }, {"NiAlphaAccumulator" , &construct }, }; } ///Make the factory map used for parsing the file static const std::map factories = makeFactory(); std::string NIFFile::printVersion(unsigned int version) { int major = (version >> 24) & 0xFF; int minor = (version >> 16) & 0xFF; int patch = (version >> 8) & 0xFF; int rev = version & 0xFF; std::stringstream stream; stream << major << "." << minor << "." << patch << "." << rev; return stream.str(); } void NIFFile::parse(Files::IStreamPtr&& stream) { const std::array fileHash = Files::getHash(filename, *stream); hash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); NIFStream nif (this, std::move(stream)); // Check the header string std::string head = nif.getVersionString(); static const std::array verStrings = { "NetImmerse File Format", "Gamebryo File Format" }; const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), [&] (const std::string& verString) { return head.compare(0, verString.size(), verString) == 0; }); if (!supportedHeader) fail("Invalid NIF header: " + head); // Get BCD version ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. // It's not used by Morrowind assets but Morrowind supports it. static const std::array supportedVers = { NIFStream::generateVersion(4,0,0,0), VER_MW }; const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); if (!supportedVersion) { if (sLoadUnsupportedFiles) warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!"); else fail("Unsupported NIF version: " + printVersion(ver)); } // NIF data endianness if (ver >= NIFStream::generateVersion(20,0,0,4)) { unsigned char endianness = nif.getChar(); if (endianness == 0) fail("Big endian NIF files are unsupported"); } // User version if (ver > NIFStream::generateVersion(10,0,1,8)) userVer = nif.getUInt(); // Number of records const std::size_t recNum = nif.getUInt(); records.resize(recNum); // Bethesda stream header // It contains Bethesda format version and (useless) export information if (ver == VER_OB_OLD || (userVer >= 3 && ((ver == VER_OB || ver == VER_BGS) || (ver >= NIFStream::generateVersion(10,1,0,0) && ver <= NIFStream::generateVersion(20,0,0,4) && userVer <= 11)))) { bethVer = nif.getUInt(); nif.getExportString(); // Author if (bethVer > BETHVER_FO4) nif.getUInt(); // Unknown nif.getExportString(); // Process script nif.getExportString(); // Export script if (bethVer == BETHVER_FO4) nif.getExportString(); // Max file path } std::vector recTypes; std::vector recTypeIndices; const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5,0,0,1); if (hasRecTypeListings) { unsigned short recTypeNum = nif.getUShort(); if (recTypeNum) // Record type list nif.getSizedStrings(recTypes, recTypeNum); if (recNum) // Record type mapping for each record nif.getUShorts(recTypeIndices, recNum); if (ver >= NIFStream::generateVersion(5,0,0,6)) // Groups { if (ver >= NIFStream::generateVersion(20,1,0,1)) // String table { if (ver >= NIFStream::generateVersion(20,2,0,5) && recNum) // Record sizes { std::vector recSizes; // Currently unused nif.getUInts(recSizes, recNum); } const std::size_t stringNum = nif.getUInt(); nif.getUInt(); // Max string length if (stringNum) nif.getSizedStrings(strings, stringNum); } std::vector groups; // Currently unused unsigned int groupNum = nif.getUInt(); if (groupNum) nif.getUInts(groups, groupNum); } } const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0); for (std::size_t i = 0; i < recNum; i++) { std::unique_ptr r; std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); if(rec.empty()) { std::stringstream error; error << "Record number " << i << " out of " << recNum << " is blank."; fail(error.str()); } // Record separator. Some Havok records in Oblivion do not have it. if (hasRecordSeparators && rec.compare(0, 3, "bhk")) { if (nif.getInt()) { std::stringstream warning; warning << "Record number " << i << " out of " << recNum << " is preceded by a non-zero separator."; warn(warning.str()); } } const auto entry = factories.find(rec); if (entry == factories.end()) fail("Unknown record type " + rec); r = entry->second(); if (!supportedVersion) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; assert(r != nullptr); assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; r->read(&nif); records[i] = std::move(r); } const std::size_t rootNum = nif.getUInt(); roots.resize(rootNum); //Determine which records are roots for (std::size_t i = 0; i < rootNum; i++) { int idx = nif.getInt(); if (idx >= 0 && static_cast(idx) < records.size()) { roots[i] = records[idx].get(); } else { roots[i] = nullptr; warn("Root " + std::to_string(i + 1) + " does not point to a record: index " + std::to_string(idx)); } } // Once parsing is done, do post-processing. for (const auto& record : records) record->post(this); } void NIFFile::setUseSkinning(bool skinning) { mUseSkinning = skinning; } bool NIFFile::getUseSkinning() const { return mUseSkinning; } std::atomic_bool NIFFile::sLoadUnsupportedFiles = false; void NIFFile::setLoadUnsupportedFiles(bool load) { sLoadUnsupportedFiles = load; } void NIFFile::warn(const std::string &msg) const { Log(Debug::Warning) << " NIFFile Warning: " << msg << "\nFile: " << filename; } [[noreturn]] void NIFFile::fail(const std::string &msg) const { throw std::runtime_error(" NIFFile Error: " + msg + "\nFile: " + filename); } std::string NIFFile::getString(uint32_t index) const { if (index == std::numeric_limits::max()) return std::string(); return strings.at(index); } } openmw-openmw-0.48.0/components/nif/niffile.hpp000066400000000000000000000103541445372753700215250ustar00rootroot00000000000000///Main header for reading .nif files #ifndef OPENMW_COMPONENTS_NIF_NIFFILE_HPP #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP #include #include #include #include "record.hpp" namespace Nif { struct File { virtual ~File() = default; virtual Record *getRecord(size_t index) const = 0; virtual size_t numRecords() const = 0; virtual Record *getRoot(size_t index = 0) const = 0; virtual size_t numRoots() const = 0; virtual std::string getString(uint32_t index) const = 0; virtual void setUseSkinning(bool skinning) = 0; virtual bool getUseSkinning() const = 0; virtual std::string getFilename() const = 0; virtual std::string getHash() const = 0; virtual unsigned int getVersion() const = 0; virtual unsigned int getUserVersion() const = 0; virtual unsigned int getBethVersion() const = 0; }; class NIFFile final : public File { /// File version, user version, Bethesda version unsigned int ver = 0; unsigned int userVer = 0; unsigned int bethVer = 0; /// File name, used for error messages and opening the file std::string filename; std::string hash; /// Record list std::vector> records; /// Root list. This is a select portion of the pointers from records std::vector roots; /// String table std::vector strings; bool mUseSkinning = false; static std::atomic_bool sLoadUnsupportedFiles; /// Parse the file void parse(Files::IStreamPtr&& stream); /// Get the file's version in a human readable form ///\returns A string containing a human readable NIF version number std::string printVersion(unsigned int version); ///Private Copy Constructor NIFFile (NIFFile const &); ///\overload void operator = (NIFFile const &); public: // For generic versions NIFStream::generateVersion() is used instead enum NIFVersion { VER_MW = 0x04000002, // 4.0.0.2. Main Morrowind NIF version. VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version. VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version. VER_BGS = 0x14020007 // 20.2.0.7. Main Fallout 3/4/76/New Vegas and Skyrim/SkyrimSE NIF version. }; enum BethVersion { BETHVER_FO3 = 34, // Fallout 3 BETHVER_FO4 = 130 // Fallout 4 }; /// Used if file parsing fails [[noreturn]] void fail(const std::string &msg) const; /// Used when something goes wrong, but not catastrophically so void warn(const std::string &msg) const; /// Open a NIF stream. The name is used for error messages. NIFFile(Files::IStreamPtr&& stream, const std::string &name); /// Get a given record Record *getRecord(size_t index) const override { return records.at(index).get(); } /// Number of records size_t numRecords() const override { return records.size(); } /// Get a given root Record *getRoot(size_t index=0) const override { Record *res = roots.at(index); return res; } /// Number of roots size_t numRoots() const override { return roots.size(); } /// Get a given string from the file's string table std::string getString(uint32_t index) const override; /// Set whether there is skinning contained in this NIF file. /// @note This is just a hint for users of the NIF file and has no effect on the loading procedure. void setUseSkinning(bool skinning) override; bool getUseSkinning() const override; /// Get the name of the file std::string getFilename() const override { return filename; } std::string getHash() const override { return hash; } /// Get the version of the NIF format used unsigned int getVersion() const override { return ver; } /// Get the user version of the NIF format used unsigned int getUserVersion() const override { return userVer; } /// Get the Bethesda version of the NIF format used unsigned int getBethVersion() const override { return bethVer; } static void setLoadUnsupportedFiles(bool load); }; using NIFFilePtr = std::shared_ptr; } // Namespace #endif openmw-openmw-0.48.0/components/nif/nifkey.hpp000066400000000000000000000114071445372753700213760ustar00rootroot00000000000000///File to handle keys used by nif file records #ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP #define OPENMW_COMPONENTS_NIF_NIFKEY_HPP #include #include "nifstream.hpp" #include "niffile.hpp" namespace Nif { enum InterpolationType { InterpolationType_Unknown = 0, InterpolationType_Linear = 1, InterpolationType_Quadratic = 2, InterpolationType_TBC = 3, InterpolationType_XYZ = 4, InterpolationType_Constant = 5 }; template struct KeyT { T mValue; T mInTan; // Only for Quadratic interpolation, and never for QuaternionKeyList T mOutTan; // Only for Quadratic interpolation, and never for QuaternionKeyList // FIXME: Implement TBC interpolation /* float mTension; // Only for TBC interpolation float mBias; // Only for TBC interpolation float mContinuity; // Only for TBC interpolation */ }; using FloatKey = KeyT; using Vector3Key = KeyT; using Vector4Key = KeyT; using QuaternionKey = KeyT; template struct KeyMapT { using MapType = std::map>; using ValueType = T; using KeyType = KeyT; unsigned int mInterpolationType = InterpolationType_Unknown; MapType mKeys; //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) void read(NIFStream *nif, bool morph = false) { assert(nif); if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106)) nif->getString(); // Frame name size_t count = nif->getUInt(); if (count != 0 || morph) mInterpolationType = nif->getUInt(); KeyType key = {}; if (mInterpolationType == InterpolationType_Linear || mInterpolationType == InterpolationType_Constant) { for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); readValue(*nif, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_Quadratic) { for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); readQuadratic(*nif, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_TBC) { for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); readTBC(*nif, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_XYZ) { //XYZ keys aren't actually read here. //data.cpp sees that the last type read was InterpolationType_XYZ and: // Eats a floating point number, then // Re-runs the read function 3 more times. // When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ. } else if (count != 0) { nif->file->fail("Unhandled interpolation type: " + std::to_string(mInterpolationType)); } if (morph && nif->getVersion() > NIFStream::generateVersion(10,1,0,0)) { if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,104) && nif->getVersion() <= NIFStream::generateVersion(20,1,0,2) && nif->getBethVersion() < 10) nif->getFloat(); // Legacy weight } } private: static void readValue(NIFStream &nif, KeyT &key) { key.mValue = (nif.*getValue)(); } template static void readQuadratic(NIFStream &nif, KeyT &key) { readValue(nif, key); key.mInTan = (nif.*getValue)(); key.mOutTan = (nif.*getValue)(); } static void readQuadratic(NIFStream &nif, KeyT &key) { readValue(nif, key); } static void readTBC(NIFStream &nif, KeyT &key) { readValue(nif, key); /*key.mTension = */nif.getFloat(); /*key.mBias = */nif.getFloat(); /*key.mContinuity = */nif.getFloat(); } }; using FloatKeyMap = KeyMapT; using Vector3KeyMap = KeyMapT; using Vector4KeyMap = KeyMapT; using QuaternionKeyMap = KeyMapT; using ByteKeyMap = KeyMapT; using FloatKeyMapPtr = std::shared_ptr; using Vector3KeyMapPtr = std::shared_ptr; using Vector4KeyMapPtr = std::shared_ptr; using QuaternionKeyMapPtr = std::shared_ptr; using ByteKeyMapPtr = std::shared_ptr; } // Namespace #endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP openmw-openmw-0.48.0/components/nif/nifstream.cpp000066400000000000000000000025721445372753700220770ustar00rootroot00000000000000#include "nifstream.hpp" //For error reporting #include "niffile.hpp" namespace Nif { osg::Quat NIFStream::getQuaternion() { float f[4]; readLittleEndianBufferOfType<4, float>(inp, f); osg::Quat quat; quat.w() = f[0]; quat.x() = f[1]; quat.y() = f[2]; quat.z() = f[3]; return quat; } Transformation NIFStream::getTrafo() { Transformation t; t.pos = getVector3(); t.rotation = getMatrix3(); t.scale = getFloat(); return t; } ///Booleans in 4.0.0.2 (Morrowind format) and earlier are 4 byte, while in 4.1.0.0+ they're 1 byte. bool NIFStream::getBoolean() { return getVersion() < generateVersion(4,1,0,0) ? getInt() != 0 : getChar() != 0; } ///Read in a string, either from the string table using the index or from the stream using the specified length std::string NIFStream::getString() { return getVersion() < generateVersion(20,1,0,1) ? getSizedString() : file->getString(getUInt()); } // Convenience utility functions: get the versions of the currently read file unsigned int NIFStream::getVersion() const { return file->getVersion(); } unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); } unsigned int NIFStream::getBethVersion() const { return file->getBethVersion(); } } openmw-openmw-0.48.0/components/nif/nifstream.hpp000066400000000000000000000164051445372753700221040ustar00rootroot00000000000000///Functions used to read raw binary data from .nif files #ifndef OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP #define OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP #include #include #include #include #include #include #include #include #include #include #include #include "niftypes.hpp" namespace Nif { class NIFFile; template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) { static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); pIStream->read((char*)dest, numInstances * sizeof(T)); if (pIStream->bad()) throw std::runtime_error("Failed to read little endian typed (" + std::string(typeid(T).name()) + ") buffer of " + std::to_string(numInstances) + " instances"); if constexpr (Misc::IS_BIG_ENDIAN) for (std::size_t i = 0; i < numInstances; i++) Misc::swapEndiannessInplace(dest[i]); } template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, std::size_t numInstances) { static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); pIStream->read((char*)dest, numInstances * sizeof(T)); if (pIStream->bad()) throw std::runtime_error("Failed to read little endian dynamic buffer of " + std::to_string(numInstances) + " instances"); if constexpr (Misc::IS_BIG_ENDIAN) for (std::size_t i = 0; i < numInstances; i++) Misc::swapEndiannessInplace(dest[i]); } template type inline readLittleEndianType(Files::IStreamPtr &pIStream) { type val; readLittleEndianBufferOfType<1, type>(pIStream, &val); return val; } class NIFStream { /// Input stream Files::IStreamPtr inp; public: NIFFile * const file; NIFStream (NIFFile * file, Files::IStreamPtr&& inp): inp (std::move(inp)), file (file) {} void skip(size_t size) { inp->ignore(size); } char getChar() { return readLittleEndianType(inp); } short getShort() { return readLittleEndianType(inp); } unsigned short getUShort() { return readLittleEndianType(inp); } int getInt() { return readLittleEndianType(inp); } unsigned int getUInt() { return readLittleEndianType(inp); } float getFloat() { return readLittleEndianType(inp); } osg::Vec2f getVector2() { osg::Vec2f vec; readLittleEndianBufferOfType<2,float>(inp, vec._v); return vec; } osg::Vec3f getVector3() { osg::Vec3f vec; readLittleEndianBufferOfType<3, float>(inp, vec._v); return vec; } osg::Vec4f getVector4() { osg::Vec4f vec; readLittleEndianBufferOfType<4, float>(inp, vec._v); return vec; } Matrix3 getMatrix3() { Matrix3 mat; readLittleEndianBufferOfType<9, float>(inp, (float*)&mat.mValues); return mat; } osg::Quat getQuaternion(); Transformation getTrafo(); bool getBoolean(); std::string getString(); unsigned int getVersion() const; unsigned int getUserVersion() const; unsigned int getBethVersion() const; // Convert human-readable version numbers into a number that can be compared. static constexpr uint32_t generateVersion(uint8_t major, uint8_t minor, uint8_t patch, uint8_t rev) { return (major << 24) + (minor << 16) + (patch << 8) + rev; } ///Read in a string of the given length std::string getSizedString(size_t length) { std::string str(length, '\0'); inp->read(str.data(), length); if (inp->bad()) throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); size_t end = str.find('\0'); if (end != std::string::npos) str.erase(end); return str; } ///Read in a string of the length specified in the file std::string getSizedString() { size_t size = readLittleEndianType(inp); return getSizedString(size); } ///Specific to Bethesda headers, uses a byte for length std::string getExportString() { size_t size = static_cast(readLittleEndianType(inp)); return getSizedString(size); } ///This is special since the version string doesn't start with a number, and ends with "\n" std::string getVersionString() { std::string result; std::getline(*inp, result); if (inp->bad()) throw std::runtime_error("Failed to read version string"); return result; } void getChars(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUChars(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUShorts(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getInts(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUInts(std::vector &vec, size_t size) { vec.resize(size); readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) { quat.resize(size); for (size_t i = 0;i < quat.size();i++) quat[i] = getQuaternion(); } void getStrings(std::vector &vec, size_t size) { vec.resize(size); for (size_t i = 0; i < vec.size(); i++) vec[i] = getString(); } /// We need to use this when the string table isn't actually initialized. void getSizedStrings(std::vector &vec, size_t size) { vec.resize(size); for (size_t i = 0; i < vec.size(); i++) vec[i] = getSizedString(); } }; } #endif openmw-openmw-0.48.0/components/nif/niftypes.hpp000066400000000000000000000042711445372753700217530ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (nif_types.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_NIFTYPES_HPP #define OPENMW_COMPONENTS_NIF_NIFTYPES_HPP #include #include // Common types used in NIF files namespace Nif { struct Matrix3 { float mValues[3][3]; Matrix3() { for (int i=0;i<3;++i) for (int j=0;j<3;++j) mValues[i][j] = (i==j) ? 1.f : 0.f; } bool isIdentity() const { for (int i=0;i<3;++i) for (int j=0;j<3;++j) if ((i==j) != (mValues[i][j] == 1)) return false; return true; } }; struct Transformation { osg::Vec3f pos; Matrix3 rotation; // this can contain scale components too, including negative and nonuniform scales float scale; osg::Matrixf toMatrix() const { osg::Matrixf transform; transform.setTrans(pos); for (int i=0;i<3;++i) for (int j=0;j<3;++j) transform(j,i) = rotation.mValues[i][j] * scale; // NB column/row major difference return transform; } bool isIdentity() const { return pos == osg::Vec3f(0,0,0) && rotation.isIdentity() && scale == 1.f; } static const Transformation& getIdentity() { static const Transformation identity = { osg::Vec3f(), Matrix3(), 1.0f }; return identity; } }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/node.cpp000066400000000000000000000177611445372753700210420ustar00rootroot00000000000000#include "node.hpp" #include #include "data.hpp" #include "physics.hpp" #include "property.hpp" namespace Nif { void NiBoundingVolume::read(NIFStream* nif) { type = nif->getUInt(); switch (type) { case BASE_BV: break; case SPHERE_BV: { sphere.center = nif->getVector3(); sphere.radius = nif->getFloat(); break; } case BOX_BV: { box.center = nif->getVector3(); box.axes = nif->getMatrix3(); box.extents = nif->getVector3(); break; } case CAPSULE_BV: { capsule.center = nif->getVector3(); capsule.axis = nif->getVector3(); capsule.extent = nif->getFloat(); capsule.radius = nif->getFloat(); break; } case LOZENGE_BV: { lozenge.radius = nif->getFloat(); if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0)) { lozenge.extent0 = nif->getFloat(); lozenge.extent1 = nif->getFloat(); } lozenge.center = nif->getVector3(); lozenge.axis0 = nif->getVector3(); lozenge.axis1 = nif->getVector3(); break; } case UNION_BV: { unsigned int numChildren = nif->getUInt(); if (numChildren == 0) break; children.resize(numChildren); for (NiBoundingVolume& child : children) child.read(nif); break; } case HALFSPACE_BV: { halfSpace.plane = osg::Plane(nif->getVector4()); if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0)) halfSpace.origin = nif->getVector3(); break; } default: { nif->file->fail("Unhandled NiBoundingVolume type: " + std::to_string(type)); } } } void Node::read(NIFStream *nif) { Named::read(nif); flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); trafo = nif->getTrafo(); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) velocity = nif->getVector3(); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) props.read(nif); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) hasBounds = nif->getBoolean(); if (hasBounds) bounds.read(nif); // Reference to the collision object in Gamebryo files. if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) collision.read(nif); parents.clear(); isBone = false; } void Node::post(NIFFile *nif) { Named::post(nif); props.post(nif); collision.post(nif); } void Node::setBone() { isBone = true; } void NiNode::read(NIFStream *nif) { Node::read(nif); children.read(nif); if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) effects.read(nif); // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but // can be expanded if needed. // FIXME: if node 0 is *not* the only root node, this must not happen. if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) { trafo = Nif::Transformation::getIdentity(); } } void NiNode::post(NIFFile *nif) { Node::post(nif); children.post(nif); effects.post(nif); for (size_t i = 0; i < children.length(); i++) { // Why would a unique list of children contain empty refs? if (!children[i].empty()) children[i]->parents.push_back(this); } } void NiGeometry::MaterialData::read(NIFStream *nif) { if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) return; unsigned int num = 0; if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3)) num = nif->getBoolean(); // Has Shader else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) num = nif->getUInt(); if (num) { nif->getStrings(names, num); nif->getInts(extra, num); } if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) active = nif->getUInt(); if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) needsUpdate = nif->getBoolean(); } void NiGeometry::read(NIFStream *nif) { Node::read(nif); data.read(nif); skin.read(nif); material.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { shaderprop.read(nif); alphaprop.read(nif); } } void NiGeometry::post(NIFFile *nif) { Node::post(nif); data.post(nif); skin.post(nif); shaderprop.post(nif); alphaprop.post(nif); if (recType != RC_NiParticles && !skin.empty()) nif->setUseSkinning(true); } void BSLODTriShape::read(NIFStream *nif) { NiTriShape::read(nif); lod0 = nif->getUInt(); lod1 = nif->getUInt(); lod2 = nif->getUInt(); } void NiCamera::Camera::read(NIFStream *nif) { if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) cameraFlags = nif->getUShort(); left = nif->getFloat(); right = nif->getFloat(); top = nif->getFloat(); bottom = nif->getFloat(); nearDist = nif->getFloat(); farDist = nif->getFloat(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) orthographic = nif->getBoolean(); vleft = nif->getFloat(); vright = nif->getFloat(); vtop = nif->getFloat(); vbottom = nif->getFloat(); LOD = nif->getFloat(); } void NiCamera::read(NIFStream *nif) { Node::read(nif); cam.read(nif); nif->getInt(); // -1 nif->getInt(); // 0 if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0)) nif->getInt(); // 0 } void NiSwitchNode::read(NIFStream *nif) { NiNode::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) switchFlags = nif->getUShort(); initialIndex = nif->getUInt(); } void NiLODNode::read(NIFStream *nif) { NiSwitchNode::read(nif); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) lodCenter = nif->getVector3(); else if (nif->getVersion() > NIFStream::generateVersion(10,0,1,0)) { nif->skip(4); // NiLODData, unsupported at the moment return; } unsigned int numLodLevels = nif->getUInt(); for (unsigned int i=0; igetFloat(); r.maxRange = nif->getFloat(); lodLevels.push_back(r); } } void NiFltAnimationNode::read(NIFStream *nif) { NiSwitchNode::read(nif); mDuration = nif->getFloat(); } void NiSortAdjustNode::read(NIFStream *nif) { NiNode::read(nif); mMode = nif->getInt(); if (nif->getVersion() <= NIFStream::generateVersion(20,0,0,3)) mSubSorter.read(nif); } void NiSortAdjustNode::post(NIFFile *nif) { NiNode::post(nif); mSubSorter.post(nif); } } openmw-openmw-0.48.0/components/nif/node.hpp000066400000000000000000000124111445372753700210320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIF_NODE_HPP #define OPENMW_COMPONENTS_NIF_NODE_HPP #include #include "base.hpp" namespace Nif { struct NiNode; struct NiBoundingVolume { enum Type { BASE_BV = 0xFFFFFFFF, SPHERE_BV = 0, BOX_BV = 1, CAPSULE_BV = 2, LOZENGE_BV = 3, UNION_BV = 4, HALFSPACE_BV = 5 }; struct NiSphereBV { osg::Vec3f center; float radius{0.f}; }; struct NiBoxBV { osg::Vec3f center; Matrix3 axes; osg::Vec3f extents; }; struct NiCapsuleBV { osg::Vec3f center, axis; float extent{0.f}, radius{0.f}; }; struct NiLozengeBV { float radius{0.f}, extent0{0.f}, extent1{0.f}; osg::Vec3f center, axis0, axis1; }; struct NiHalfSpaceBV { osg::Plane plane; osg::Vec3f origin; }; unsigned int type; NiSphereBV sphere; NiBoxBV box; NiCapsuleBV capsule; NiLozengeBV lozenge; std::vector children; NiHalfSpaceBV halfSpace; void read(NIFStream* nif); }; /** A Node is an object that's part of the main NIF tree. It has parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. */ struct Node : public Named { enum Flags { Flag_Hidden = 0x0001, Flag_MeshCollision = 0x0002, Flag_BBoxCollision = 0x0004, Flag_ActiveCollision = 0x0020 }; // Node flags. Interpretation depends somewhat on the type of node. unsigned int flags; Transformation trafo; osg::Vec3f velocity; // Unused? Might be a run-time game state PropertyList props; // Bounding box info bool hasBounds{false}; NiBoundingVolume bounds; // Collision object info NiCollisionObjectPtr collision; void read(NIFStream *nif) override; void post(NIFFile *nif) override; // Parent node, or nullptr for the root node. As far as I'm aware, only // NiNodes (or types derived from NiNodes) can be parents. std::vector parents; bool isBone{false}; void setBone(); bool isHidden() const { return flags & Flag_Hidden; } bool hasMeshCollision() const { return flags & Flag_MeshCollision; } bool hasBBoxCollision() const { return flags & Flag_BBoxCollision; } bool collisionActive() const { return flags & Flag_ActiveCollision; } }; struct NiNode : Node { NodeList children; NodeList effects; enum BSAnimFlags { AnimFlag_AutoPlay = 0x0020 }; enum BSParticleFlags { ParticleFlag_AutoPlay = 0x0020, ParticleFlag_LocalSpace = 0x0080 }; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiGeometry : Node { /* Possible flags: 0x40 - mesh has no vertex normals ? Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have been observed so far. */ struct MaterialData { std::vector names; std::vector extra; unsigned int active{0}; bool needsUpdate{false}; void read(NIFStream *nif); }; NiGeometryDataPtr data; NiSkinInstancePtr skin; MaterialData material; BSShaderPropertyPtr shaderprop; NiAlphaPropertyPtr alphaprop; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiTriShape : NiGeometry {}; struct BSLODTriShape : NiTriShape { unsigned int lod0, lod1, lod2; void read(NIFStream *nif) override; }; struct NiTriStrips : NiGeometry {}; struct NiLines : NiGeometry {}; struct NiParticles : NiGeometry { }; struct NiCamera : Node { struct Camera { unsigned short cameraFlags{0}; // Camera frustrum float left, right, top, bottom, nearDist, farDist; // Viewport float vleft, vright, vtop, vbottom; // Level of detail modifier float LOD; // Orthographic projection usage flag bool orthographic{false}; void read(NIFStream *nif); }; Camera cam; void read(NIFStream *nif) override; }; // A node used as the base to switch between child nodes, such as for LOD levels. struct NiSwitchNode : public NiNode { unsigned int switchFlags{0}; unsigned int initialIndex{0}; void read(NIFStream *nif) override; }; struct NiLODNode : public NiSwitchNode { osg::Vec3f lodCenter; struct LODRange { float minRange; float maxRange; }; std::vector lodLevels; void read(NIFStream *nif) override; }; struct NiFltAnimationNode : public NiSwitchNode { float mDuration; enum Flags { Flag_Swing = 0x40 }; void read(NIFStream *nif) override; bool swing() const { return flags & Flag_Swing; } }; // Abstract struct NiAccumulator : Record { void read(NIFStream *nif) override {} }; // Node children sorters struct NiClusterAccumulator : NiAccumulator {}; struct NiAlphaAccumulator : NiClusterAccumulator {}; struct NiSortAdjustNode : NiNode { enum SortingMode { SortingMode_Inherit, SortingMode_Off, SortingMode_Subsort }; int mMode; NiAccumulatorPtr mSubSorter; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/parent.hpp000066400000000000000000000003361445372753700214010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIF_PARENT_HPP #define OPENMW_COMPONENTS_NIF_PARENT_HPP namespace Nif { struct NiNode; struct Parent { const NiNode& mNiNode; const Parent* mParent; }; } #endif openmw-openmw-0.48.0/components/nif/physics.cpp000066400000000000000000000225331445372753700215700ustar00rootroot00000000000000#include "physics.hpp" #include "data.hpp" #include "node.hpp" namespace Nif { /// Non-record data types void bhkWorldObjCInfoProperty::read(NIFStream *nif) { mData = nif->getUInt(); mSize = nif->getUInt(); mCapacityAndFlags = nif->getUInt(); } void bhkWorldObjectCInfo::read(NIFStream *nif) { nif->skip(4); // Unused mPhaseType = static_cast(nif->getChar()); nif->skip(3); // Unused mProperty.read(nif); } void HavokMaterial::read(NIFStream *nif) { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown mMaterial = nif->getUInt(); } void HavokFilter::read(NIFStream *nif) { mLayer = nif->getChar(); mFlags = nif->getChar(); mGroup = nif->getUShort(); } void hkSubPartData::read(NIFStream *nif) { mHavokFilter.read(nif); mNumVertices = nif->getUInt(); mHavokMaterial.read(nif); } void hkpMoppCode::read(NIFStream *nif) { unsigned int size = nif->getUInt(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) mOffset = nif->getVector4(); if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) nif->getChar(); // MOPP data build type if (size) nif->getChars(mData, size); } void bhkEntityCInfo::read(NIFStream *nif) { mResponseType = static_cast(nif->getChar()); nif->skip(1); // Unused mProcessContactDelay = nif->getUShort(); } void TriangleData::read(NIFStream *nif) { for (int i = 0; i < 3; i++) mTriangle[i] = nif->getUShort(); mWeldingInfo = nif->getUShort(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) mNormal = nif->getVector3(); } void bhkRigidBodyCInfo::read(NIFStream *nif) { if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) { nif->skip(4); // Unused mHavokFilter.read(nif); nif->skip(4); // Unused if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) { if (nif->getBethVersion() >= 83) nif->skip(4); // Unused mResponseType = static_cast(nif->getChar()); nif->skip(1); // Unused mProcessContactDelay = nif->getUShort(); } } if (nif->getBethVersion() < 83) nif->skip(4); // Unused mTranslation = nif->getVector4(); mRotation = nif->getQuaternion(); mLinearVelocity = nif->getVector4(); mAngularVelocity = nif->getVector4(); for (int i = 0; i < 3; i++) for (int j = 0; j < 4; j++) mInertiaTensor[i][j] = nif->getFloat(); mCenter = nif->getVector4(); mMass = nif->getFloat(); mLinearDamping = nif->getFloat(); mAngularDamping = nif->getFloat(); if (nif->getBethVersion() >= 83) { if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) mTimeFactor = nif->getFloat(); mGravityFactor = nif->getFloat(); } mFriction = nif->getFloat(); if (nif->getBethVersion() >= 83) mRollingFrictionMult = nif->getFloat(); mRestitution = nif->getFloat(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) { mMaxLinearVelocity = nif->getFloat(); mMaxAngularVelocity = nif->getFloat(); if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) mPenetrationDepth = nif->getFloat(); } mMotionType = static_cast(nif->getChar()); if (nif->getBethVersion() < 83) mDeactivatorType = static_cast(nif->getChar()); else mEnableDeactivation = nif->getBoolean(); mSolverDeactivation = static_cast(nif->getChar()); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) { nif->skip(1); mPenetrationDepth = nif->getFloat(); mTimeFactor = nif->getFloat(); nif->skip(4); mResponseType = static_cast(nif->getChar()); nif->skip(1); // Unused mProcessContactDelay = nif->getUShort(); } mQualityType = static_cast(nif->getChar()); if (nif->getBethVersion() >= 83) { mAutoRemoveLevel = nif->getChar(); mResponseModifierFlags = nif->getChar(); mNumContactPointShapeKeys = nif->getChar(); mForceCollidedOntoPPU = nif->getBoolean(); } if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) nif->skip(3); // Unused else nif->skip(12); // Unused } /// Record types void bhkCollisionObject::read(NIFStream *nif) { NiCollisionObject::read(nif); mFlags = nif->getUShort(); mBody.read(nif); } void bhkWorldObject::read(NIFStream *nif) { mShape.read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown mHavokFilter.read(nif); mWorldObjectInfo.read(nif); } void bhkWorldObject::post(NIFFile *nif) { mShape.post(nif); } void bhkEntity::read(NIFStream *nif) { bhkWorldObject::read(nif); mInfo.read(nif); } void bhkBvTreeShape::read(NIFStream *nif) { mShape.read(nif); } void bhkBvTreeShape::post(NIFFile *nif) { mShape.post(nif); } void bhkMoppBvTreeShape::read(NIFStream *nif) { bhkBvTreeShape::read(nif); nif->skip(12); // Unused mScale = nif->getFloat(); mMopp.read(nif); } void bhkNiTriStripsShape::read(NIFStream *nif) { mHavokMaterial.read(nif); mRadius = nif->getFloat(); nif->skip(20); // Unused mGrowBy = nif->getUInt(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) mScale = nif->getVector4(); mData.read(nif); unsigned int numFilters = nif->getUInt(); nif->getUInts(mFilters, numFilters); } void bhkNiTriStripsShape::post(NIFFile *nif) { mData.post(nif); } void bhkPackedNiTriStripsShape::read(NIFStream *nif) { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { mSubshapes.resize(nif->getUShort()); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } mUserData = nif->getUInt(); nif->skip(4); // Unused mRadius = nif->getFloat(); nif->skip(4); // Unused mScale = nif->getVector4(); nif->skip(20); // Duplicates of the two previous fields mData.read(nif); } void bhkPackedNiTriStripsShape::post(NIFFile *nif) { mData.post(nif); } void hkPackedNiTriStripsData::read(NIFStream *nif) { unsigned int numTriangles = nif->getUInt(); mTriangles.resize(numTriangles); for (unsigned int i = 0; i < numTriangles; i++) mTriangles[i].read(nif); unsigned int numVertices = nif->getUInt(); bool compressed = false; if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) compressed = nif->getBoolean(); if (!compressed) nif->getVector3s(mVertices, numVertices); else nif->skip(6 * numVertices); // Half-precision vectors are not currently supported if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) { mSubshapes.resize(nif->getUShort()); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } } void bhkSphereRepShape::read(NIFStream *nif) { mHavokMaterial.read(nif); } void bhkConvexShape::read(NIFStream *nif) { bhkSphereRepShape::read(nif); mRadius = nif->getFloat(); } void bhkConvexVerticesShape::read(NIFStream *nif) { bhkConvexShape::read(nif); mVerticesProperty.read(nif); mNormalsProperty.read(nif); unsigned int numVertices = nif->getUInt(); if (numVertices) nif->getVector4s(mVertices, numVertices); unsigned int numNormals = nif->getUInt(); if (numNormals) nif->getVector4s(mNormals, numNormals); } void bhkBoxShape::read(NIFStream *nif) { bhkConvexShape::read(nif); nif->skip(8); // Unused mExtents = nif->getVector3(); nif->skip(4); // Unused } void bhkListShape::read(NIFStream *nif) { mSubshapes.read(nif); mHavokMaterial.read(nif); mChildShapeProperty.read(nif); mChildFilterProperty.read(nif); unsigned int numFilters = nif->getUInt(); mHavokFilters.resize(numFilters); for (HavokFilter& filter : mHavokFilters) filter.read(nif); } void bhkRigidBody::read(NIFStream *nif) { bhkEntity::read(nif); mInfo.read(nif); mConstraints.read(nif); if (nif->getBethVersion() < 76) mBodyFlags = nif->getUInt(); else mBodyFlags = nif->getUShort(); } } // Namespace openmw-openmw-0.48.0/components/nif/physics.hpp000066400000000000000000000166431445372753700216020ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP #define OPENMW_COMPONENTS_NIF_PHYSICS_HPP #include "base.hpp" // This header contains certain record definitions // specific to Bethesda implementation of Havok physics namespace Nif { /// Non-record data types struct bhkWorldObjCInfoProperty { unsigned int mData; unsigned int mSize; unsigned int mCapacityAndFlags; void read(NIFStream *nif); }; enum class BroadPhaseType : uint8_t { BroadPhase_Invalid = 0, BroadPhase_Entity = 1, BroadPhase_Phantom = 2, BroadPhase_Border = 3 }; struct bhkWorldObjectCInfo { BroadPhaseType mPhaseType; bhkWorldObjCInfoProperty mProperty; void read(NIFStream *nif); }; struct HavokMaterial { unsigned int mMaterial; void read(NIFStream *nif); }; struct HavokFilter { unsigned char mLayer; unsigned char mFlags; unsigned short mGroup; void read(NIFStream *nif); }; struct hkSubPartData { HavokMaterial mHavokMaterial; unsigned int mNumVertices; HavokFilter mHavokFilter; void read(NIFStream *nif); }; enum class hkResponseType : uint8_t { Response_Invalid = 0, Response_SimpleContact = 1, Response_Reporting = 2, Response_None = 3 }; struct bhkEntityCInfo { hkResponseType mResponseType; unsigned short mProcessContactDelay; void read(NIFStream *nif); }; struct hkpMoppCode { osg::Vec4f mOffset; std::vector mData; void read(NIFStream *nif); }; struct TriangleData { unsigned short mTriangle[3]; unsigned short mWeldingInfo; osg::Vec3f mNormal; void read(NIFStream *nif); }; enum class hkMotionType : uint8_t { Motion_Invalid = 0, Motion_Dynamic = 1, Motion_SphereInertia = 2, Motion_SphereStabilized = 3, Motion_BoxInertia = 4, Motion_BoxStabilized = 5, Motion_Keyframed = 6, Motion_Fixed = 7, Motion_ThinBox = 8, Motion_Character = 9 }; enum class hkDeactivatorType : uint8_t { Deactivator_Invalid = 0, Deactivator_Never = 1, Deactivator_Spatial = 2 }; enum class hkSolverDeactivation : uint8_t { SolverDeactivation_Invalid = 0, SolverDeactivation_Off = 1, SolverDeactivation_Low = 2, SolverDeactivation_Medium = 3, SolverDeactivation_High = 4, SolverDeactivation_Max = 5 }; enum class hkQualityType : uint8_t { Quality_Invalid = 0, Quality_Fixed = 1, Quality_Keyframed = 2, Quality_Debris = 3, Quality_Moving = 4, Quality_Critical = 5, Quality_Bullet = 6, Quality_User = 7, Quality_Character = 8, Quality_KeyframedReport = 9 }; struct bhkRigidBodyCInfo { HavokFilter mHavokFilter; hkResponseType mResponseType; unsigned short mProcessContactDelay; osg::Vec4f mTranslation; osg::Quat mRotation; osg::Vec4f mLinearVelocity; osg::Vec4f mAngularVelocity; float mInertiaTensor[3][4]; osg::Vec4f mCenter; float mMass; float mLinearDamping; float mAngularDamping; float mTimeFactor{1.f}; float mGravityFactor{1.f}; float mFriction; float mRollingFrictionMult; float mRestitution; float mMaxLinearVelocity; float mMaxAngularVelocity; float mPenetrationDepth; hkMotionType mMotionType; hkDeactivatorType mDeactivatorType; bool mEnableDeactivation{true}; hkSolverDeactivation mSolverDeactivation; hkQualityType mQualityType; unsigned char mAutoRemoveLevel; unsigned char mResponseModifierFlags; unsigned char mNumContactPointShapeKeys; bool mForceCollidedOntoPPU; void read(NIFStream *nif); }; /// Record types // Abstract Bethesda Havok object struct bhkRefObject : public Record {}; // Abstract serializable Bethesda Havok object struct bhkSerializable : public bhkRefObject {}; // Abstract narrowphase collision detection object struct bhkShape : public bhkSerializable {}; // Abstract bhkShape collection struct bhkShapeCollection : public bhkShape {}; // Generic collision object struct NiCollisionObject : public Record { // The node that references this object NodePtr mTarget; void read(NIFStream *nif) override { mTarget.read(nif); } void post(NIFFile *nif) override { mTarget.post(nif); } }; // Bethesda Havok-specific collision object struct bhkCollisionObject : public NiCollisionObject { unsigned short mFlags; bhkWorldObjectPtr mBody; void read(NIFStream *nif) override; void post(NIFFile *nif) override { NiCollisionObject::post(nif); mBody.post(nif); } }; // Abstract Havok shape info record struct bhkWorldObject : public bhkSerializable { bhkShapePtr mShape; HavokFilter mHavokFilter; bhkWorldObjectCInfo mWorldObjectInfo; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; // Abstract struct bhkEntity : public bhkWorldObject { bhkEntityCInfo mInfo; void read(NIFStream *nif) override; }; // Bethesda extension of hkpBvTreeShape // hkpBvTreeShape adds a bounding volume tree to an hkpShapeCollection struct bhkBvTreeShape : public bhkShape { bhkShapePtr mShape; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; // bhkBvTreeShape with Havok MOPP code struct bhkMoppBvTreeShape : public bhkBvTreeShape { float mScale; hkpMoppCode mMopp; void read(NIFStream *nif) override; }; // Bethesda triangle strip-based Havok shape collection struct bhkNiTriStripsShape : public bhkShape { HavokMaterial mHavokMaterial; float mRadius; unsigned int mGrowBy; osg::Vec4f mScale{1.f, 1.f, 1.f, 0.f}; NiTriStripsDataList mData; std::vector mFilters; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; // Bethesda packed triangle strip-based Havok shape collection struct bhkPackedNiTriStripsShape : public bhkShapeCollection { std::vector mSubshapes; unsigned int mUserData; float mRadius; osg::Vec4f mScale; hkPackedNiTriStripsDataPtr mData; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; // bhkPackedNiTriStripsShape data block struct hkPackedNiTriStripsData : public bhkShapeCollection { std::vector mTriangles; std::vector mVertices; std::vector mSubshapes; void read(NIFStream *nif) override; }; // Abstract struct bhkSphereRepShape : public bhkShape { HavokMaterial mHavokMaterial; void read(NIFStream *nif) override; }; // Abstract struct bhkConvexShape : public bhkSphereRepShape { float mRadius; void read(NIFStream *nif) override; }; // A convex shape built from vertices struct bhkConvexVerticesShape : public bhkConvexShape { bhkWorldObjCInfoProperty mVerticesProperty; bhkWorldObjCInfoProperty mNormalsProperty; std::vector mVertices; std::vector mNormals; void read(NIFStream *nif) override; }; // A box struct bhkBoxShape : public bhkConvexShape { osg::Vec3f mExtents; void read(NIFStream *nif) override; }; // A list of shapes struct bhkListShape : public bhkShapeCollection { bhkShapeList mSubshapes; HavokMaterial mHavokMaterial; bhkWorldObjCInfoProperty mChildShapeProperty; bhkWorldObjCInfoProperty mChildFilterProperty; std::vector mHavokFilters; void read(NIFStream *nif) override; }; struct bhkRigidBody : public bhkEntity { bhkRigidBodyCInfo mInfo; bhkSerializableList mConstraints; unsigned int mBodyFlags; void read(NIFStream *nif) override; }; } // Namespace #endifopenmw-openmw-0.48.0/components/nif/property.cpp000066400000000000000000000167731445372753700220030ustar00rootroot00000000000000#include "property.hpp" #include "data.hpp" #include "controlled.hpp" namespace Nif { void NiTexturingProperty::Texture::read(NIFStream *nif) { inUse = nif->getBoolean(); if(!inUse) return; texture.read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { clamp = nif->getInt(); nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible } else { clamp = nif->getUShort() & 0xF; } // Max anisotropy. I assume we'll always only use the global anisotropy setting. if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4)) nif->getUShort(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) uvSet = nif->getUInt(); // Two PS2-specific shorts. if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2)) nif->skip(4); if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,18)) nif->skip(2); // Unknown short else if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) { if (nif->getBoolean()) // Has texture transform { nif->getVector2(); // UV translation nif->getVector2(); // UV scale nif->getFloat(); // W axis rotation nif->getUInt(); // Transform method nif->getVector2(); // Texture rotation origin } } } void NiTexturingProperty::Texture::post(NIFFile *nif) { texture.post(nif); } void NiTexturingProperty::read(NIFStream *nif) { Property::read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20,1,0,2)) flags = nif->getUShort(); if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,1)) apply = nif->getUInt(); unsigned int numTextures = nif->getUInt(); if (!numTextures) return; textures.resize(numTextures); for (unsigned int i = 0; i < numTextures; i++) { textures[i].read(nif); if (i == 5 && textures[5].inUse) // Bump map settings { envMapLumaBias = nif->getVector2(); bumpMapMatrix = nif->getVector4(); } else if (i == 7 && textures[7].inUse && nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) /*float parallaxOffset = */nif->getFloat(); } if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) { unsigned int numShaderTextures = nif->getUInt(); shaderTextures.resize(numShaderTextures); for (unsigned int i = 0; i < numShaderTextures; i++) { shaderTextures[i].read(nif); if (shaderTextures[i].inUse) nif->getUInt(); // Unique identifier } } } void NiTexturingProperty::post(NIFFile *nif) { Property::post(nif); for (size_t i = 0; i < textures.size(); i++) textures[i].post(nif); for (size_t i = 0; i < shaderTextures.size(); i++) shaderTextures[i].post(nif); } void BSShaderProperty::read(NIFStream *nif) { NiShadeProperty::read(nif); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { type = nif->getUInt(); flags1 = nif->getUInt(); flags2 = nif->getUInt(); envMapIntensity = nif->getFloat(); } } void BSShaderLightingProperty::read(NIFStream *nif) { BSShaderProperty::read(nif); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) clamp = nif->getUInt(); } void BSShaderPPLightingProperty::read(NIFStream *nif) { BSShaderLightingProperty::read(nif); textureSet.read(nif); if (nif->getBethVersion() <= 14) return; refraction.strength = nif->getFloat(); refraction.period = nif->getInt(); if (nif->getBethVersion() <= 24) return; parallax.passes = nif->getFloat(); parallax.scale = nif->getFloat(); } void BSShaderPPLightingProperty::post(NIFFile *nif) { BSShaderLightingProperty::post(nif); textureSet.post(nif); } void BSShaderNoLightingProperty::read(NIFStream *nif) { BSShaderLightingProperty::read(nif); filename = nif->getSizedString(); if (nif->getBethVersion() >= 27) falloffParams = nif->getVector4(); } void BSLightingShaderProperty::read(NIFStream *nif) { type = nif->getUInt(); BSShaderProperty::read(nif); flags1 = nif->getUInt(); flags2 = nif->getUInt(); nif->skip(8); // UV offset nif->skip(8); // UV scale mTextureSet.read(nif); mEmissive = nif->getVector3(); mEmissiveMult = nif->getFloat(); mClamp = nif->getUInt(); mAlpha = nif->getFloat(); nif->getFloat(); // Refraction strength mGlossiness = nif->getFloat(); mSpecular = nif->getVector3(); mSpecStrength = nif->getFloat(); nif->skip(8); // Lighting effects switch (static_cast(type)) { case BSLightingShaderType::ShaderType_EnvMap: nif->skip(4); // Environment map scale break; case BSLightingShaderType::ShaderType_SkinTint: case BSLightingShaderType::ShaderType_HairTint: nif->skip(12); // Tint color break; case BSLightingShaderType::ShaderType_ParallaxOcc: nif->skip(4); // Max passes nif->skip(4); // Scale break; case BSLightingShaderType::ShaderType_MultiLayerParallax: nif->skip(4); // Inner layer thickness nif->skip(4); // Refraction scale nif->skip(8); // Inner layer texture scale nif->skip(4); // Environment map strength break; case BSLightingShaderType::ShaderType_SparkleSnow: nif->skip(16); // Sparkle parameters break; case BSLightingShaderType::ShaderType_EyeEnvmap: nif->skip(4); // Cube map scale nif->skip(12); // Left eye cube map offset nif->skip(12); // Right eye cube map offset break; default: break; } } void BSLightingShaderProperty::post(NIFFile *nif) { BSShaderProperty::post(nif); mTextureSet.post(nif); } void NiFogProperty::read(NIFStream *nif) { Property::read(nif); mFlags = nif->getUShort(); mFogDepth = nif->getFloat(); mColour = nif->getVector3(); } void S_MaterialProperty::read(NIFStream *nif) { if (nif->getBethVersion() < 26) { ambient = nif->getVector3(); diffuse = nif->getVector3(); } specular = nif->getVector3(); emissive = nif->getVector3(); glossiness = nif->getFloat(); alpha = nif->getFloat(); if (nif->getBethVersion() >= 22) emissiveMult = nif->getFloat(); } void S_VertexColorProperty::read(NIFStream *nif) { vertmode = nif->getInt(); lightmode = nif->getInt(); } void S_AlphaProperty::read(NIFStream *nif) { threshold = nif->getChar(); } void S_StencilProperty::read(NIFStream *nif) { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { enabled = nif->getChar(); compareFunc = nif->getInt(); stencilRef = nif->getUInt(); stencilMask = nif->getUInt(); failAction = nif->getInt(); zFailAction = nif->getInt(); zPassAction = nif->getInt(); drawMode = nif->getInt(); } else { unsigned short flags = nif->getUShort(); enabled = flags & 0x1; failAction = (flags >> 1) & 0x7; zFailAction = (flags >> 4) & 0x7; zPassAction = (flags >> 7) & 0x7; drawMode = (flags >> 10) & 0x3; compareFunc = (flags >> 12) & 0x7; stencilRef = nif->getUInt(); stencilMask = nif->getUInt(); } } } openmw-openmw-0.48.0/components/nif/property.hpp000066400000000000000000000242051445372753700217750ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (property.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP #define OPENMW_COMPONENTS_NIF_PROPERTY_HPP #include "base.hpp" namespace Nif { struct Property : public Named { }; struct NiTexturingProperty : public Property { unsigned short flags{0u}; // A sub-texture struct Texture { /* Clamp mode 0 - clampS clampT 1 - clampS wrapT 2 - wrapS clampT 3 - wrapS wrapT */ bool inUse; NiSourceTexturePtr texture; unsigned int clamp, uvSet; void read(NIFStream *nif); void post(NIFFile *nif); bool wrapT() const { return clamp & 1; } bool wrapS() const { return (clamp >> 1) & 1; } }; /* Apply mode: 0 - replace 1 - decal 2 - modulate 3 - hilight // These two are for PS2 only? 4 - hilight2 */ unsigned int apply{0}; /* * The textures in this list are as follows: * * 0 - Base texture * 1 - Dark texture * 2 - Detail texture * 3 - Gloss texture * 4 - Glow texture * 5 - Bump map texture * 6 - Decal texture */ enum TextureType { BaseTexture = 0, DarkTexture = 1, DetailTexture = 2, GlossTexture = 3, GlowTexture = 4, BumpTexture = 5, DecalTexture = 6, }; std::vector textures; std::vector shaderTextures; osg::Vec2f envMapLumaBias; osg::Vec4f bumpMapMatrix; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiFogProperty : public Property { unsigned short mFlags; float mFogDepth; osg::Vec3f mColour; void read(NIFStream *nif) override; }; // These contain no other data than the 'flags' field struct NiShadeProperty : public Property { unsigned short flags{0u}; void read(NIFStream *nif) override { Property::read(nif); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) flags = nif->getUShort(); } }; enum class BSShaderType : unsigned int { ShaderType_TallGrass = 0, ShaderType_Default = 1, ShaderType_Sky = 10, ShaderType_Skin = 14, ShaderType_Water = 17, ShaderType_Lighting30 = 29, ShaderType_Tile = 32, ShaderType_NoLighting = 33 }; struct BSShaderProperty : public NiShadeProperty { unsigned int type{0u}, flags1{0u}, flags2{0u}; float envMapIntensity{0.f}; void read(NIFStream *nif) override; }; struct BSShaderLightingProperty : public BSShaderProperty { unsigned int clamp{0u}; void read(NIFStream *nif) override; bool wrapT() const { return clamp & 1; } bool wrapS() const { return (clamp >> 1) & 1; } }; struct BSShaderPPLightingProperty : public BSShaderLightingProperty { BSShaderTextureSetPtr textureSet; struct RefractionSettings { float strength{0.f}; int period{0}; }; struct ParallaxSettings { float passes{0.f}; float scale{0.f}; }; RefractionSettings refraction; ParallaxSettings parallax; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct BSShaderNoLightingProperty : public BSShaderLightingProperty { std::string filename; osg::Vec4f falloffParams; void read(NIFStream *nif) override; }; enum class BSLightingShaderType : unsigned int { ShaderType_Default = 0, ShaderType_EnvMap = 1, ShaderType_Glow = 2, ShaderType_Parallax = 3, ShaderType_FaceTint = 4, ShaderType_SkinTint = 5, ShaderType_HairTint = 6, ShaderType_ParallaxOcc = 7, ShaderType_MultitexLand = 8, ShaderType_LODLand = 9, ShaderType_Snow = 10, ShaderType_MultiLayerParallax = 11, ShaderType_TreeAnim = 12, ShaderType_LODObjects = 13, ShaderType_SparkleSnow = 14, ShaderType_LODObjectsHD = 15, ShaderType_EyeEnvmap = 16, ShaderType_Cloud = 17, ShaderType_LODNoise = 18, ShaderType_MultitexLandLODBlend = 19, ShaderType_Dismemberment = 20 }; struct BSLightingShaderProperty : public BSShaderProperty { BSShaderTextureSetPtr mTextureSet; unsigned int mClamp{0u}; float mAlpha; float mGlossiness; osg::Vec3f mEmissive, mSpecular; float mEmissiveMult, mSpecStrength; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; struct NiDitherProperty : public Property { unsigned short flags; void read(NIFStream* nif) override { Property::read(nif); flags = nif->getUShort(); } }; struct NiZBufferProperty : public Property { unsigned short flags; unsigned int testFunction; void read(NIFStream *nif) override { Property::read(nif); flags = nif->getUShort(); testFunction = (flags >> 2) & 0x7; if (nif->getVersion() >= NIFStream::generateVersion(4,1,0,12) && nif->getVersion() <= NIFFile::NIFVersion::VER_OB) testFunction = nif->getUInt(); } bool depthTest() const { return flags & 1; } bool depthWrite() const { return (flags >> 1) & 1; } }; struct NiSpecularProperty : public Property { unsigned short flags; void read(NIFStream* nif) override { Property::read(nif); flags = nif->getUShort(); } bool isEnabled() const { return flags & 1; } }; struct NiWireframeProperty : public Property { unsigned short flags; void read(NIFStream* nif) override { Property::read(nif); flags = nif->getUShort(); } bool isEnabled() const { return flags & 1; } }; // The rest are all struct-based template struct StructPropT : Property { T data; unsigned short flags; void read(NIFStream *nif) override { Property::read(nif); flags = nif->getUShort(); data.read(nif); } }; struct S_MaterialProperty { // The vector components are R,G,B osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; osg::Vec3f specular, emissive; float glossiness{0.f}, alpha{0.f}, emissiveMult{1.f}; void read(NIFStream *nif); }; struct S_VertexColorProperty { /* Vertex mode: 0 - source ignore 1 - source emmisive 2 - source amb diff Lighting mode 0 - lighting emmisive 1 - lighting emmisive ambient/diffuse */ int vertmode, lightmode; void read(NIFStream *nif); }; struct S_AlphaProperty { /* NiAlphaProperty blend modes (glBlendFunc): 0000 GL_ONE 0001 GL_ZERO 0010 GL_SRC_COLOR 0011 GL_ONE_MINUS_SRC_COLOR 0100 GL_DST_COLOR 0101 GL_ONE_MINUS_DST_COLOR 0110 GL_SRC_ALPHA 0111 GL_ONE_MINUS_SRC_ALPHA 1000 GL_DST_ALPHA 1001 GL_ONE_MINUS_DST_ALPHA 1010 GL_SRC_ALPHA_SATURATE test modes (glAlphaFunc): 000 GL_ALWAYS 001 GL_LESS 010 GL_EQUAL 011 GL_LEQUAL 100 GL_GREATER 101 GL_NOTEQUAL 110 GL_GEQUAL 111 GL_NEVER Taken from: http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html */ // Tested against when certain flags are set (see above.) unsigned char threshold; void read(NIFStream *nif); }; /* Docs taken from: http://niftools.sourceforge.net/doc/nif/NiStencilProperty.html */ struct S_StencilProperty { // Is stencil test enabled? unsigned char enabled; /* 0 TEST_NEVER 1 TEST_LESS 2 TEST_EQUAL 3 TEST_LESS_EQUAL 4 TEST_GREATER 5 TEST_NOT_EQUAL 6 TEST_GREATER_EQUAL 7 TEST_NEVER (though nifskope comment says TEST_ALWAYS, but ingame it is TEST_NEVER) */ int compareFunc; unsigned stencilRef; unsigned stencilMask; /* Stencil test fail action, depth test fail action and depth test pass action: 0 ACTION_KEEP 1 ACTION_ZERO 2 ACTION_REPLACE 3 ACTION_INCREMENT 4 ACTION_DECREMENT 5 ACTION_INVERT */ int failAction; int zFailAction; int zPassAction; /* Face draw mode: 0 DRAW_CCW_OR_BOTH 1 DRAW_CCW [default] 2 DRAW_CW 3 DRAW_BOTH */ int drawMode; void read(NIFStream *nif); }; struct NiAlphaProperty : public StructPropT { bool useAlphaBlending() const { return flags & 1; } int sourceBlendMode() const { return (flags >> 1) & 0xF; } int destinationBlendMode() const { return (flags >> 5) & 0xF; } bool noSorter() const { return (flags >> 13) & 1; } bool useAlphaTesting() const { return (flags >> 9) & 1; } int alphaTestMode() const { return (flags >> 10) & 0x7; } }; struct NiVertexColorProperty : public StructPropT { }; struct NiStencilProperty : public Property { S_StencilProperty data; unsigned short flags{0u}; void read(NIFStream *nif) override { Property::read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) flags = nif->getUShort(); data.read(nif); } }; struct NiMaterialProperty : public Property { S_MaterialProperty data; unsigned short flags{0u}; void read(NIFStream *nif) override { Property::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(3,0,0,0) && nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) flags = nif->getUShort(); data.read(nif); } }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/record.hpp000066400000000000000000000076071445372753700213760ustar00rootroot00000000000000/* OpenMW - The completely unofficial reimplementation of Morrowind Copyright (C) 2008-2010 Nicolay Korslund Email: < korslund@gmail.com > WWW: https://openmw.org/ This file (record.h) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License version 3 along with this program. If not, see https://www.gnu.org/licenses/ . */ #ifndef OPENMW_COMPONENTS_NIF_RECORD_HPP #define OPENMW_COMPONENTS_NIF_RECORD_HPP #include namespace Nif { class NIFFile; class NIFStream; enum RecordType { RC_MISSING = 0, RC_NiNode, RC_NiSwitchNode, RC_NiLODNode, RC_NiFltAnimationNode, RC_NiBillboardNode, RC_AvoidNode, RC_NiCollisionSwitch, RC_NiTriShape, RC_NiTriStrips, RC_NiLines, RC_NiParticles, RC_NiBSParticleNode, RC_NiCamera, RC_NiTexturingProperty, RC_NiFogProperty, RC_NiMaterialProperty, RC_NiZBufferProperty, RC_NiAlphaProperty, RC_NiVertexColorProperty, RC_NiShadeProperty, RC_NiDitherProperty, RC_NiWireframeProperty, RC_NiSpecularProperty, RC_NiStencilProperty, RC_NiVisController, RC_NiGeomMorpherController, RC_NiKeyframeController, RC_NiAlphaController, RC_NiRollController, RC_NiUVController, RC_NiPathController, RC_NiMaterialColorController, RC_NiBSPArrayController, RC_NiParticleSystemController, RC_NiFlipController, RC_NiBSAnimationNode, RC_NiLight, RC_NiTextureEffect, RC_NiExtraData, RC_NiVertWeightsExtraData, RC_NiTextKeyExtraData, RC_NiStringExtraData, RC_NiGravity, RC_NiPlanarCollider, RC_NiParticleGrowFade, RC_NiParticleColorModifier, RC_NiParticleRotation, RC_NiFloatData, RC_NiTriShapeData, RC_NiTriStripsData, RC_NiLinesData, RC_NiVisData, RC_NiColorData, RC_NiPixelData, RC_NiMorphData, RC_NiKeyframeData, RC_NiSkinData, RC_NiUVData, RC_NiPosData, RC_NiRotatingParticlesData, RC_NiParticlesData, RC_NiSequenceStreamHelper, RC_NiSourceTexture, RC_NiSkinInstance, RC_RootCollisionNode, RC_NiSphericalCollider, RC_NiLookAtController, RC_NiPalette, RC_NiIntegerExtraData, RC_NiIntegersExtraData, RC_NiBinaryExtraData, RC_NiBooleanExtraData, RC_NiVectorExtraData, RC_NiColorExtraData, RC_NiFloatExtraData, RC_NiFloatsExtraData, RC_NiStringPalette, RC_NiBoolData, RC_NiSkinPartition, RC_BSXFlags, RC_BSBound, RC_bhkBlendController, RC_NiFloatInterpolator, RC_NiPoint3Interpolator, RC_NiBoolInterpolator, RC_NiTransformInterpolator, RC_NiColorInterpolator, RC_BSShaderTextureSet, RC_BSLODTriShape, RC_BSShaderProperty, RC_BSShaderPPLightingProperty, RC_BSShaderNoLightingProperty, RC_BSFurnitureMarker, RC_NiCollisionObject, RC_bhkCollisionObject, RC_BSDismemberSkinInstance, RC_NiControllerManager, RC_bhkMoppBvTreeShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, RC_hkPackedNiTriStripsData, RC_bhkConvexVerticesShape, RC_bhkBoxShape, RC_bhkListShape, RC_bhkRigidBody, RC_bhkRigidBodyT, RC_BSLightingShaderProperty, RC_NiClusterAccumulator, RC_NiAlphaAccumulator, RC_NiSortAdjustNode }; /// Base class for all records struct Record { // Record type and type name RecordType recType{RC_MISSING}; std::string recName; unsigned int recIndex{~0u}; Record() = default; /// Parses the record from file virtual void read(NIFStream *nif) = 0; /// Does post-processing, after the entire tree is loaded virtual void post(NIFFile *nif) {} virtual ~Record() {} }; } // Namespace #endif openmw-openmw-0.48.0/components/nif/recordptr.hpp000066400000000000000000000121561445372753700221170ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP #define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP #include "niffile.hpp" #include "nifstream.hpp" #include namespace Nif { /** A reference to another record. It is read as an index from the NIF, and later looked up in the index table to get an actual pointer. */ template class RecordPtrT { union { intptr_t index; X* ptr; }; public: RecordPtrT() : index(-2) {} RecordPtrT(X* ptr) : ptr(ptr) {} /// Read the index from the nif void read(NIFStream *nif) { // Can only read the index once assert(index == -2); // Store the index for later index = nif->getInt(); assert(index >= -1); } /// Resolve index to pointer void post(NIFFile *nif) { if(index < 0) ptr = nullptr; else { Record *r = nif->getRecord(index); // And cast it ptr = dynamic_cast(r); assert(ptr != nullptr); } } /// Look up the actual object from the index const X* getPtr() const { assert(ptr != nullptr); return ptr; } X* getPtr() { assert(ptr != nullptr); return ptr; } const X& get() const { return *getPtr(); } X& get() { return *getPtr(); } /// Syntactic sugar const X* operator->() const { return getPtr(); } X* operator->() { return getPtr(); } /// Pointers are allowed to be empty bool empty() const { return ptr == nullptr; } }; /** A list of references to other records. These are read as a list, and later converted to pointers as needed. Not an optimized implementation. */ template class RecordListT { typedef RecordPtrT Ptr; std::vector list; public: RecordListT() = default; RecordListT(std::vector list) : list(std::move(list)) {} void read(NIFStream *nif) { int len = nif->getInt(); list.resize(len); for(size_t i=0;i < list.size();i++) list[i].read(nif); } void post(NIFFile *nif) { for(size_t i=0;i < list.size();i++) list[i].post(nif); } const Ptr& operator[](size_t index) const { return list.at(index); } Ptr& operator[](size_t index) { return list.at(index); } size_t length() const { return list.size(); } }; struct Node; struct Extra; struct Property; struct NiUVData; struct NiPosData; struct NiVisData; struct Controller; struct Named; struct NiSkinData; struct NiFloatData; struct NiMorphData; struct NiPixelData; struct NiColorData; struct NiKeyframeData; struct NiTriStripsData; struct NiSkinInstance; struct NiSourceTexture; struct NiPalette; struct NiParticleModifier; struct NiBoolData; struct NiSkinPartition; struct NiFloatInterpolator; struct NiPoint3Interpolator; struct NiTransformInterpolator; struct BSShaderTextureSet; struct NiGeometryData; struct BSShaderProperty; struct NiAlphaProperty; struct NiCollisionObject; struct bhkWorldObject; struct bhkShape; struct bhkSerializable; struct hkPackedNiTriStripsData; struct NiAccumulator; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; using NiUVDataPtr = RecordPtrT; using NiPosDataPtr = RecordPtrT; using NiVisDataPtr = RecordPtrT; using ControllerPtr = RecordPtrT; using NamedPtr = RecordPtrT; using NiSkinDataPtr = RecordPtrT; using NiMorphDataPtr = RecordPtrT; using NiPixelDataPtr = RecordPtrT; using NiFloatDataPtr = RecordPtrT; using NiColorDataPtr = RecordPtrT; using NiKeyframeDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; using NiFloatInterpolatorPtr = RecordPtrT; using NiPoint3InterpolatorPtr = RecordPtrT; using NiTransformInterpolatorPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; using NiGeometryDataPtr = RecordPtrT; using BSShaderPropertyPtr = RecordPtrT; using NiAlphaPropertyPtr = RecordPtrT; using NiCollisionObjectPtr = RecordPtrT; using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; using hkPackedNiTriStripsDataPtr = RecordPtrT; using NiAccumulatorPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiFloatInterpolatorList = RecordListT; using NiTriStripsDataList = RecordListT; using bhkShapeList = RecordListT; using bhkSerializableList = RecordListT; } // Namespace #endif openmw-openmw-0.48.0/components/nifbullet/000077500000000000000000000000001445372753700206055ustar00rootroot00000000000000openmw-openmw-0.48.0/components/nifbullet/bulletnifloader.cpp000066400000000000000000000414621445372753700244730ustar00rootroot00000000000000#include "bulletnifloader.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { osg::Matrixf getWorldTransform(const Nif::Node& node, const Nif::Parent* nodeParent) { osg::Matrixf result = node.trafo.toMatrix(); for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) result *= parent->mNiNode.trafo.toMatrix(); return result; } bool pathFileNameStartsWithX(const std::string& path) { const std::size_t slashpos = path.find_last_of("/\\"); const std::size_t letterPos = slashpos == std::string::npos ? 0 : slashpos + 1; return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) { const std::vector &vertices = data.vertices; const std::vector &triangles = data.triangles; mesh.preallocateVertices(static_cast(vertices.size())); mesh.preallocateIndices(static_cast(triangles.size())); for (std::size_t i = 0; i < triangles.size(); i += 3) { mesh.addTriangle( Misc::Convert::toBullet(vertices[triangles[i + 0]] * transform), Misc::Convert::toBullet(vertices[triangles[i + 1]] * transform), Misc::Convert::toBullet(vertices[triangles[i + 2]] * transform) ); } } void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform) { const std::vector &vertices = data.vertices; const std::vector> &strips = data.strips; mesh.preallocateVertices(static_cast(vertices.size())); int numTriangles = 0; for (const std::vector& strip : strips) { // Each strip with N points contains information about N-2 triangles. if (strip.size() >= 3) numTriangles += static_cast(strip.size()-2); } mesh.preallocateIndices(static_cast(numTriangles)); // It's triangulation time. Totally not a NifSkope spell ripoff. for (const std::vector& strip : strips) { // Can't make a triangle from less than 3 points. if (strip.size() < 3) continue; unsigned short a; unsigned short b = strip[0]; unsigned short c = strip[1]; for (size_t i = 2; i < strip.size(); i++) { a = b; b = c; c = strip[i]; if (a != b && b != c && a != c) { if (i%2==0) { mesh.addTriangle( Misc::Convert::toBullet(vertices[a] * transform), Misc::Convert::toBullet(vertices[b] * transform), Misc::Convert::toBullet(vertices[c] * transform) ); } else { mesh.addTriangle( Misc::Convert::toBullet(vertices[a] * transform), Misc::Convert::toBullet(vertices[c] * transform), Misc::Convert::toBullet(vertices[b] * transform) ); } } } } } template auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) -> decltype(function(static_cast(geometry.data.get()))) { if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) { if (geometry.data->recType != Nif::RC_NiTriShapeData) return {}; auto data = static_cast(geometry.data.getPtr()); if (data->triangles.empty()) return {}; return function(static_cast(*data)); } if (geometry.recType == Nif::RC_NiTriStrips) { if (geometry.data->recType != Nif::RC_NiTriStripsData) return {}; auto data = static_cast(geometry.data.getPtr()); if (data->strips.empty()) return {}; return function(static_cast(*data)); } return {}; } std::monostate fillTriangleMesh(std::unique_ptr& mesh, const Nif::NiGeometry& geometry, const osg::Matrixf &transform) { return handleNiGeometry(geometry, [&] (const auto& data) { if (mesh == nullptr) mesh = std::make_unique(false); fillTriangleMesh(*mesh, data, transform); return std::monostate {}; }); } std::unique_ptr makeChildMesh(const Nif::NiGeometry& geometry) { return handleNiGeometry(geometry, [&] (const auto& data) { auto mesh = std::make_unique(); fillTriangleMesh(*mesh, data, osg::Matrixf()); return mesh; }); } } namespace NifBullet { osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; mCompoundShape.reset(); mStaticMesh.reset(); mAvoidStaticMesh.reset(); mShape->mFileHash = nif.getHash(); const size_t numRoots = nif.numRoots(); std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif.getRoot(i); if (!r) continue; const Nif::Node* node = dynamic_cast(r); if (node) roots.emplace_back(node); } const std::string filename = nif.getFilename(); mShape->mFileName = filename; if (roots.empty()) { warn("Found no root nodes in NIF file " + filename); return mShape; } // Try to find a valid bounding box first. If one's found for any root node, use that. for (const Nif::Node* node : roots) { if (findBoundingBox(*node, filename)) { const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); auto compound = std::make_unique(); auto boxShape = std::make_unique(extents); btTransform transform = btTransform::getIdentity(); transform.setOrigin(center); compound->addChildShape(transform, boxShape.get()); std::ignore = boxShape.release(); mShape->mCollisionShape.reset(compound.release()); return mShape; } } // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // assume all nodes in the file will be animated const bool isAnimated = pathFileNameStartsWithX(filename); // If there's no bounding box, we'll have to generate a Bullet collision shape // from the collision data present in every root node. for (const Nif::Node* node : roots) { bool hasCollisionNode = hasRootCollisionNode(*node); bool hasCollisionShape = hasCollisionNode && !collisionShapeIsEmpty(*node); if (hasCollisionNode && !hasCollisionShape) mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; bool generateCollisionShape = !hasCollisionShape; handleNode(filename, *node, nullptr, 0, generateCollisionShape, isAnimated, generateCollisionShape, false, mShape->mVisualCollisionType); } if (mCompoundShape) { if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { btTransform trans; trans.setIdentity(); std::unique_ptr child = std::make_unique(mStaticMesh.get(), true); mCompoundShape->addChildShape(trans, child.get()); std::ignore = child.release(); std::ignore = mStaticMesh.release(); } mShape->mCollisionShape = std::move(mCompoundShape); } else if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { mShape->mCollisionShape.reset(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); std::ignore = mStaticMesh.release(); } if (mAvoidStaticMesh != nullptr && mAvoidStaticMesh->getNumTriangles() > 0) { mShape->mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false)); std::ignore = mAvoidStaticMesh.release(); } return mShape; } // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? bool BulletNifLoader::findBoundingBox(const Nif::Node& node, const std::string& filename) { if (node.hasBounds) { unsigned int type = node.bounds.type; switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: mShape->mCollisionBox.mExtents = node.bounds.box.extents; mShape->mCollisionBox.mCenter = node.bounds.box.center; break; default: { std::stringstream warning; warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; warning << " in file " << filename; warn(warning.str()); } } if (node.hasBBoxCollision()) { return true; } } if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) { if (findBoundingBox(list[i].get(), filename)) return true; } } } return false; } bool BulletNifLoader::hasRootCollisionNode(const Nif::Node& rootNode) const { if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(list[i].empty()) continue; if (list[i].getPtr()->recType == Nif::RC_RootCollisionNode) return true; } } return false; } bool BulletNifLoader::collisionShapeIsEmpty(const Nif::Node& rootNode) const { if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(list[i].empty()) continue; const Nif::Node* childNode = list[i].getPtr(); if (childNode->recType != Nif::RC_RootCollisionNode) continue; const Nif::NiNode* niChildnode = static_cast(childNode); // RootCollisionNode is always a NiNode if (childNode->hasBounds || niChildnode->children.length() > 0) return false; } } return true; } void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) return; // If RootCollisionNode is empty we treat it as NCC flag and autogenerate collision shape as there was no RootCollisionNode. // So ignoring it here if `autogenerated` is true and collisionType was set to `Camera`. if (node.recType == Nif::RC_RootCollisionNode && autogenerated && visualCollisionType == Resource::VisualCollisionType::Camera) return; // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node.flags; if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController && node.controller->isActive()) isAnimated = true; isCollisionNode = isCollisionNode || (node.recType == Nif::RC_RootCollisionNode); // Don't collide with AvoidNode shapes avoid = avoid || (node.recType == Nif::RC_AvoidNode); // We encountered a RootCollisionNode inside autogenerated mesh. It is not right. if (node.recType == Nif::RC_RootCollisionNode && autogenerated) Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName << ". Treating it as a common NiTriShape."; // Check for extra data for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) { if (e->recType == Nif::RC_NiStringExtraData) { // String markers may contain important information // affecting the entire subtree of this node Nif::NiStringExtraData *sd = (Nif::NiStringExtraData*)e.getPtr(); if (Misc::StringUtils::ciCompareLen(sd->string, "NC", 2) == 0) { // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be uppercase if (sd->string.length() > 2 && sd->string[2] == 'C') // Collide only with camera. visualCollisionType = Resource::VisualCollisionType::Camera; else // No collision. visualCollisionType = Resource::VisualCollisionType::Default; } else if (sd->string == "MRK" && autogenerated) { // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } } } if (isCollisionNode) { // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) if(!node.hasBounds && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(static_cast(node), parent, getWorldTransform(node, parent), isAnimated, avoid); } } // For NiNodes, loop through children if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < list.length();i++) { if (list[i].empty()) continue; assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end()); handleNode(fileName, list[i].get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid, visualCollisionType); } } } void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, const osg::Matrixf &transform, bool isAnimated, bool avoid) { if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; if (!niGeometry.skin.empty()) isAnimated = false; if (isAnimated) { std::unique_ptr childMesh = makeChildMesh(niGeometry); if (childMesh == nullptr || childMesh->getNumTriangles() == 0) return; if (!mCompoundShape) mCompoundShape.reset(new btCompoundShape); auto childShape = std::make_unique(childMesh.get(), true); std::ignore = childMesh.release(); float scale = niGeometry.trafo.scale; for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) scale *= parent->mNiNode.trafo.scale; osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); mShape->mAnimatedShapes.emplace(niGeometry.recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); std::ignore = childShape.release(); } else if (avoid) fillTriangleMesh(mAvoidStaticMesh, niGeometry, transform); else fillTriangleMesh(mStaticMesh, niGeometry, transform); } } // namespace NifBullet openmw-openmw-0.48.0/components/nifbullet/bulletnifloader.hpp000066400000000000000000000036761445372753700245050ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #include #include #include #include #include #include #include #include #include #include #include #include class btTriangleMesh; class btCompoundShape; class btCollisionShape; namespace Nif { struct Node; struct Transformation; struct NiTriShape; struct NiTriStrips; struct NiGeometry; struct Parent; } namespace NifBullet { /** *Load bulletShape from NIF files. */ class BulletNifLoader { public: void warn(const std::string &msg) { Log(Debug::Warning) << "NIFLoader: Warn: " << msg; } [[noreturn]] void fail(const std::string &msg) { Log(Debug::Error) << "NIFLoader: Fail: "<< msg; abort(); } osg::ref_ptr load(const Nif::File& file); private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType); bool hasRootCollisionNode(const Nif::Node& rootNode) const; bool collisionShapeIsEmpty(const Nif::Node& rootNode) const; void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform, bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; std::unique_ptr mStaticMesh; std::unique_ptr mAvoidStaticMesh; osg::ref_ptr mShape; }; } #endif openmw-openmw-0.48.0/components/nifosg/000077500000000000000000000000001445372753700201065ustar00rootroot00000000000000openmw-openmw-0.48.0/components/nifosg/controller.cpp000066400000000000000000000455541445372753700230120ustar00rootroot00000000000000#include "controller.hpp" #include #include #include #include #include #include #include #include "matrixtransform.hpp" namespace NifOsg { ControllerFunction::ControllerFunction(const Nif::Controller *ctrl) : mFrequency(ctrl->frequency) , mPhase(ctrl->phase) , mStartTime(ctrl->timeStart) , mStopTime(ctrl->timeStop) , mExtrapolationMode(ctrl->extrapolationMode()) { } float ControllerFunction::calculate(float value) const { float time = mFrequency * value + mPhase; if (time >= mStartTime && time <= mStopTime) return time; switch (mExtrapolationMode) { case Nif::Controller::ExtrapolationMode::Cycle: { float delta = mStopTime - mStartTime; if ( delta <= 0 ) return mStartTime; float cycles = ( time - mStartTime ) / delta; float remainder = ( cycles - std::floor( cycles ) ) * delta; return mStartTime + remainder; } case Nif::Controller::ExtrapolationMode::Reverse: { float delta = mStopTime - mStartTime; if ( delta <= 0 ) return mStartTime; float cycles = ( time - mStartTime ) / delta; float remainder = ( cycles - std::floor( cycles ) ) * delta; // Even number of cycles? if ( ( static_cast(std::fabs( std::floor( cycles ) )) % 2 ) == 0 ) return mStartTime + remainder; return mStopTime - remainder; } case Nif::Controller::ExtrapolationMode::Constant: default: return std::clamp(time, mStartTime, mStopTime); } } float ControllerFunction::getMaximum() const { return mStopTime; } KeyframeController::KeyframeController() { } KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : osg::Object(copy, copyop) , SceneUtil::KeyframeController(copy) , SceneUtil::NodeCallback(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) , mZRotations(copy.mZRotations) , mTranslations(copy.mTranslations) , mScales(copy.mScales) , mAxisOrder(copy.mAxisOrder) { } KeyframeController::KeyframeController(const Nif::NiKeyframeController *keyctrl) { if (!keyctrl->interpolator.empty()) { const Nif::NiTransformInterpolator* interp = keyctrl->interpolator.getPtr(); if (!interp->data.empty()) { mRotations = QuaternionInterpolator(interp->data->mRotations, interp->defaultRot); mXRotations = FloatInterpolator(interp->data->mXRotations); mYRotations = FloatInterpolator(interp->data->mYRotations); mZRotations = FloatInterpolator(interp->data->mZRotations); mTranslations = Vec3Interpolator(interp->data->mTranslations, interp->defaultPos); mScales = FloatInterpolator(interp->data->mScales, interp->defaultScale); mAxisOrder = interp->data->mAxisOrder; } else { mRotations = QuaternionInterpolator(Nif::QuaternionKeyMapPtr(), interp->defaultRot); mTranslations = Vec3Interpolator(Nif::Vector3KeyMapPtr(), interp->defaultPos); mScales = FloatInterpolator(Nif::FloatKeyMapPtr(), interp->defaultScale); } } else if (!keyctrl->data.empty()) { const Nif::NiKeyframeData* keydata = keyctrl->data.getPtr(); mRotations = QuaternionInterpolator(keydata->mRotations); mXRotations = FloatInterpolator(keydata->mXRotations); mYRotations = FloatInterpolator(keydata->mYRotations); mZRotations = FloatInterpolator(keydata->mZRotations); mTranslations = Vec3Interpolator(keydata->mTranslations); mScales = FloatInterpolator(keydata->mScales, 1.f); mAxisOrder = keydata->mAxisOrder; } } osg::Quat KeyframeController::getXYZRotation(float time) const { float xrot = 0, yrot = 0, zrot = 0; if (!mXRotations.empty()) xrot = mXRotations.interpKey(time); if (!mYRotations.empty()) yrot = mYRotations.interpKey(time); if (!mZRotations.empty()) zrot = mZRotations.interpKey(time); osg::Quat xr(xrot, osg::X_AXIS); osg::Quat yr(yrot, osg::Y_AXIS); osg::Quat zr(zrot, osg::Z_AXIS); switch (mAxisOrder) { case Nif::NiKeyframeData::AxisOrder::Order_XYZ: return xr * yr * zr; case Nif::NiKeyframeData::AxisOrder::Order_XZY: return xr * zr * yr; case Nif::NiKeyframeData::AxisOrder::Order_YZX: return yr * zr * xr; case Nif::NiKeyframeData::AxisOrder::Order_YXZ: return yr * xr * zr; case Nif::NiKeyframeData::AxisOrder::Order_ZXY: return zr * xr * yr; case Nif::NiKeyframeData::AxisOrder::Order_ZYX: return zr * yr * xr; case Nif::NiKeyframeData::AxisOrder::Order_XYX: return xr * yr * xr; case Nif::NiKeyframeData::AxisOrder::Order_YZY: return yr * zr * yr; case Nif::NiKeyframeData::AxisOrder::Order_ZXZ: return zr * xr * zr; } return xr * yr * zr; } osg::Vec3f KeyframeController::getTranslation(float time) const { if(!mTranslations.empty()) return mTranslations.interpKey(time); return osg::Vec3f(); } void KeyframeController::operator() (NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { if (hasInput()) { float time = getInputValue(nv); if (!mRotations.empty()) node->setRotation(mRotations.interpKey(time)); else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) node->setRotation(getXYZRotation(time)); else node->setRotation(node->mRotationScale); if (!mScales.empty()) node->setScale(mScales.interpKey(time)); if (!mTranslations.empty()) node->setTranslation(mTranslations.interpKey(time)); } traverse(node, nv); } GeomMorpherController::GeomMorpherController() { } GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, const osg::CopyOp ©op) : Controller(copy) , SceneUtil::NodeCallback(copy, copyop) , mKeyFrames(copy.mKeyFrames) { } GeomMorpherController::GeomMorpherController(const Nif::NiGeomMorpherController* ctrl) { if (ctrl->interpolators.length() == 0) { if (ctrl->data.empty()) return; for (const auto& morph : ctrl->data->mMorphs) mKeyFrames.emplace_back(morph.mKeyFrames); } else { for (size_t i = 0; i < ctrl->interpolators.length(); ++i) { if (!ctrl->interpolators[i].empty()) mKeyFrames.emplace_back(ctrl->interpolators[i].getPtr()); else mKeyFrames.emplace_back(); } } } void GeomMorpherController::operator()(SceneUtil::MorphGeometry* node, osg::NodeVisitor *nv) { if (hasInput()) { if (mKeyFrames.size() <= 1) return; float input = getInputValue(nv); int i = 1; for (std::vector::iterator it = mKeyFrames.begin()+1; it != mKeyFrames.end(); ++it,++i) { float val = 0; if (!(*it).empty()) val = it->interpKey(input); SceneUtil::MorphGeometry::MorphTarget& target = node->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); node->dirty(); } } } } UVController::UVController() { } UVController::UVController(const Nif::NiUVData *data, const std::set& textureUnits) : mUTrans(data->mKeyList[0], 0.f) , mVTrans(data->mKeyList[1], 0.f) , mUScale(data->mKeyList[2], 1.f) , mVScale(data->mKeyList[3], 1.f) , mTextureUnits(textureUnits) { } UVController::UVController(const UVController& copy, const osg::CopyOp& copyop) : osg::Object(copy, copyop), StateSetUpdater(copy, copyop), Controller(copy) , mUTrans(copy.mUTrans) , mVTrans(copy.mVTrans) , mUScale(copy.mUScale) , mVScale(copy.mVScale) , mTextureUnits(copy.mTextureUnits) { } void UVController::setDefaults(osg::StateSet *stateset) { osg::ref_ptr texMat (new osg::TexMat); for (std::set::const_iterator it = mTextureUnits.begin(); it != mTextureUnits.end(); ++it) stateset->setTextureAttributeAndModes(*it, texMat, osg::StateAttribute::ON); } void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) { if (hasInput()) { float value = getInputValue(nv); // First scale the UV relative to its center, then apply the offset. // U offset is flipped regardless of the graphics library, // while V offset is flipped to account for OpenGL Y axis convention. osg::Vec3f uvOrigin(0.5f, 0.5f, 0.f); osg::Vec3f uvScale(mUScale.interpKey(value), mVScale.interpKey(value), 1.f); osg::Vec3f uvTrans(-mUTrans.interpKey(value), -mVTrans.interpKey(value), 0.f); osg::Matrixf mat = osg::Matrixf::translate(uvOrigin); mat.preMultScale(uvScale); mat.preMultTranslate(-uvOrigin); mat.setTrans(mat.getTrans() + uvTrans); // setting once is enough because all other texture units share the same TexMat (see setDefaults). if (!mTextureUnits.empty()) { osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(*mTextureUnits.begin(), osg::StateAttribute::TEXMAT)); texMat->setMatrix(mat); } } } VisController::VisController(const Nif::NiVisData *data, unsigned int mask) : mData(data->mVis) , mMask(mask) { } VisController::VisController() : mMask(0) { } VisController::VisController(const VisController ©, const osg::CopyOp ©op) : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mMask(copy.mMask) { } bool VisController::calculate(float time) const { if(mData.size() == 0) return true; for(size_t i = 1;i < mData.size();i++) { if(mData[i].time > time) return mData[i-1].isSet; } return mData.back().isSet; } void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) { if (hasInput()) { bool vis = calculate(getInputValue(nv)); node->setNodeMask(vis ? ~0 : mMask); } traverse(node, nv); } RollController::RollController(const Nif::NiFloatData *data) : mData(data->mKeyList, 1.f) { } RollController::RollController(const Nif::NiFloatInterpolator* interpolator) : mData(interpolator) { } RollController::RollController(const RollController ©, const osg::CopyOp ©op) : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mStartingTime(copy.mStartingTime) { } void RollController::operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv) { traverse(node, nv); if (hasInput()) { double newTime = nv->getFrameStamp()->getSimulationTime(); double duration = newTime - mStartingTime; mStartingTime = newTime; float value = mData.interpKey(getInputValue(nv)); // Rotate around "roll" axis. // Note: in original game rotation speed is the framerate-dependent in a very tricky way. // Do not replicate this behaviour until we will really need it. // For now consider controller's current value as an angular speed in radians per 1/60 seconds. node->preMult(osg::Matrix::rotate(value * duration * 60.f, 0, 0, 1)); // Note: doing it like this means RollControllers are not compatible with KeyframeControllers. // KeyframeController currently wins the conflict. // However unlikely that is, NetImmerse might combine the transformations somehow. } } AlphaController::AlphaController() { } AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial) : mData(data->mKeyList, 1.f) , mBaseMaterial(baseMaterial) { } AlphaController::AlphaController(const Nif::NiFloatInterpolator* interpolator, const osg::Material* baseMaterial) : mData(interpolator) , mBaseMaterial(baseMaterial) { } AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) , mBaseMaterial(copy.mBaseMaterial) { } void AlphaController::setDefaults(osg::StateSet *stateset) { stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (hasInput()) { float value = mData.interpKey(getInputValue(nv)); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse.a() = value; mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); } } MaterialColorController::MaterialColorController() { } MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial) : mData(data->mKeyList, osg::Vec3f(1,1,1)) , mTargetColor(color) , mBaseMaterial(baseMaterial) { } MaterialColorController::MaterialColorController(const Nif::NiPoint3Interpolator* interpolator, TargetColor color, const osg::Material* baseMaterial) : mData(interpolator) , mTargetColor(color) , mBaseMaterial(baseMaterial) { } MaterialColorController::MaterialColorController(const MaterialColorController ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop), Controller(copy) , mData(copy.mData) , mTargetColor(copy.mTargetColor) , mBaseMaterial(copy.mBaseMaterial) { } void MaterialColorController::setDefaults(osg::StateSet *stateset) { stateset->setAttribute(static_cast(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON); } void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (hasInput()) { osg::Vec3f value = mData.interpKey(getInputValue(nv)); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); switch (mTargetColor) { case Diffuse: { osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse.set(value.x(), value.y(), value.z(), diffuse.a()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); break; } case Specular: { osg::Vec4f specular = mat->getSpecular(osg::Material::FRONT_AND_BACK); specular.set(value.x(), value.y(), value.z(), specular.a()); mat->setSpecular(osg::Material::FRONT_AND_BACK, specular); break; } case Emissive: { osg::Vec4f emissive = mat->getEmission(osg::Material::FRONT_AND_BACK); emissive.set(value.x(), value.y(), value.z(), emissive.a()); mat->setEmission(osg::Material::FRONT_AND_BACK, emissive); break; } case Ambient: default: { osg::Vec4f ambient = mat->getAmbient(osg::Material::FRONT_AND_BACK); ambient.set(value.x(), value.y(), value.z(), ambient.a()); mat->setAmbient(osg::Material::FRONT_AND_BACK, ambient); } } } } FlipController::FlipController(const Nif::NiFlipController *ctrl, const std::vector >& textures) : mTexSlot(0) // always affects diffuse , mDelta(ctrl->mDelta) , mTextures(textures) { if (!ctrl->mInterpolator.empty()) mData = ctrl->mInterpolator.getPtr(); } FlipController::FlipController(int texSlot, float delta, const std::vector >& textures) : mTexSlot(texSlot) , mDelta(delta) , mTextures(textures) { } FlipController::FlipController(const FlipController ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop) , Controller(copy) , mTexSlot(copy.mTexSlot) , mDelta(copy.mDelta) , mTextures(copy.mTextures) , mData(copy.mData) { } void FlipController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) { if (hasInput() && !mTextures.empty()) { int curTexture = 0; if (mDelta != 0) curTexture = int(getInputValue(nv) / mDelta) % mTextures.size(); else curTexture = int(mData.interpKey(getInputValue(nv))) % mTextures.size(); stateset->setTextureAttribute(mTexSlot, mTextures[curTexture]); } } ParticleSystemController::ParticleSystemController(const Nif::NiParticleSystemController *ctrl) : mEmitStart(ctrl->startTime), mEmitStop(ctrl->stopTime) { } ParticleSystemController::ParticleSystemController() : mEmitStart(0.f), mEmitStop(0.f) { } ParticleSystemController::ParticleSystemController(const ParticleSystemController ©, const osg::CopyOp ©op) : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mEmitStart(copy.mEmitStart) , mEmitStop(copy.mEmitStop) { } void ParticleSystemController::operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv) { if (hasInput()) { float time = getInputValue(nv); node->getParticleSystem()->setFrozen(false); node->setEnabled(time >= mEmitStart && time < mEmitStop); } else node->getParticleSystem()->setFrozen(true); traverse(node, nv); } PathController::PathController(const PathController ©, const osg::CopyOp ©op) : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mPath(copy.mPath) , mPercent(copy.mPercent) , mFlags(copy.mFlags) { } PathController::PathController(const Nif::NiPathController* ctrl) : mPath(ctrl->posData->mKeyList, osg::Vec3f()) , mPercent(ctrl->floatData->mKeyList, 1.f) , mFlags(ctrl->flags) { } float PathController::getPercent(float time) const { float percent = mPercent.interpKey(time); if (percent < 0.f) percent = std::fmod(percent, 1.f) + 1.f; else if (percent > 1.f) percent = std::fmod(percent, 1.f); return percent; } void PathController::operator() (NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { if (mPath.empty() || mPercent.empty() || !hasInput()) { traverse(node, nv); return; } float time = getInputValue(nv); float percent = getPercent(time); node->setTranslation(mPath.interpKey(percent)); traverse(node, nv); } } openmw-openmw-0.48.0/components/nifosg/controller.hpp000066400000000000000000000335641445372753700230150ustar00rootroot00000000000000#ifndef COMPONENTS_NIFOSG_CONTROLLER_H #define COMPONENTS_NIFOSG_CONTROLLER_H #include #include #include #include #include #include #include #include #include #include namespace osg { class Material; class MatrixTransform; } namespace osgParticle { class ParticleProcessor; } namespace SceneUtil { class MorphGeometry; } namespace NifOsg { class MatrixTransform; // interpolation of keyframes template class ValueInterpolator { typename MapT::MapType::const_iterator retrieveKey(float time) const { // retrieve the current position in the map, optimized for the most common case // where time moves linearly along the keyframe track if (mLastHighKey != mKeys->mKeys.end()) { if (time > mLastHighKey->first) { // try if we're there by incrementing one ++mLastLowKey; ++mLastHighKey; } if (mLastHighKey != mKeys->mKeys.end() && time >= mLastLowKey->first && time <= mLastHighKey->first) return mLastHighKey; } return mKeys->mKeys.lower_bound(time); } public: using ValueT = typename MapT::ValueType; ValueInterpolator() = default; template< class T, typename = std::enable_if_t< std::conjunction_v< std::disjunction< std::is_same, std::is_same, std::is_same, std::is_same >, std::is_same >, T > > ValueInterpolator(const T* interpolator) : mDefaultVal(interpolator->defaultVal) { if (interpolator->data.empty()) return; mKeys = interpolator->data->mKeyList; if (mKeys) { mLastLowKey = mKeys->mKeys.end(); mLastHighKey = mKeys->mKeys.end(); } } ValueInterpolator(std::shared_ptr keys, ValueT defaultVal = ValueT()) : mKeys(keys) , mDefaultVal(defaultVal) { if (keys) { mLastLowKey = mKeys->mKeys.end(); mLastHighKey = mKeys->mKeys.end(); } } ValueT interpKey(float time) const { if (empty()) return mDefaultVal; const typename MapT::MapType & keys = mKeys->mKeys; if(time <= keys.begin()->first) return keys.begin()->second.mValue; typename MapT::MapType::const_iterator it = retrieveKey(time); // now do the actual interpolation if (it != keys.end()) { // cache for next time mLastHighKey = it; mLastLowKey = --it; float a = (time - mLastLowKey->first) / (mLastHighKey->first - mLastLowKey->first); return interpolate(mLastLowKey->second, mLastHighKey->second, a, mKeys->mInterpolationType); } return keys.rbegin()->second.mValue; } bool empty() const { return !mKeys || mKeys->mKeys.empty(); } private: template ValueType interpolate(const Nif::KeyT& a, const Nif::KeyT& b, float fraction, unsigned int type) const { switch (type) { case Nif::InterpolationType_Constant: return fraction > 0.5f ? b.mValue : a.mValue; case Nif::InterpolationType_Quadratic: { // Using a cubic Hermite spline. // b1(t) = 2t^3 - 3t^2 + 1 // b2(t) = -2t^3 + 3t^2 // b3(t) = t^3 - 2t^2 + t // b4(t) = t^3 - t^2 // f(t) = a.mValue * b1(t) + b.mValue * b2(t) + a.mOutTan * b3(t) + b.mInTan * b4(t) const float t = fraction; const float t2 = t * t; const float t3 = t2 * t; const float b1 = 2.f * t3 - 3.f * t2 + 1; const float b2 = -2.f * t3 + 3.f * t2; const float b3 = t3 - 2.f * t2 + t; const float b4 = t3 - t2; return a.mValue * b1 + b.mValue * b2 + a.mOutTan * b3 + b.mInTan * b4; } // TODO: Implement TBC interpolation default: return a.mValue + ((b.mValue - a.mValue) * fraction); } } osg::Quat interpolate(const Nif::KeyT& a, const Nif::KeyT& b, float fraction, unsigned int type) const { switch (type) { case Nif::InterpolationType_Constant: return fraction > 0.5f ? b.mValue : a.mValue; // TODO: Implement Quadratic and TBC interpolation default: { osg::Quat result; result.slerp(fraction, a.mValue, b.mValue); return result; } } } mutable typename MapT::MapType::const_iterator mLastLowKey; mutable typename MapT::MapType::const_iterator mLastHighKey; std::shared_ptr mKeys; ValueT mDefaultVal = ValueT(); }; using QuaternionInterpolator = ValueInterpolator; using FloatInterpolator = ValueInterpolator; using Vec3Interpolator = ValueInterpolator; using Vec4Interpolator = ValueInterpolator; class ControllerFunction : public SceneUtil::ControllerFunction { private: float mFrequency; float mPhase; float mStartTime; float mStopTime; Nif::Controller::ExtrapolationMode mExtrapolationMode; public: ControllerFunction(const Nif::Controller *ctrl); float calculate(float value) const override; float getMaximum() const override; }; class GeomMorpherController : public SceneUtil::Controller, public SceneUtil::NodeCallback { public: GeomMorpherController(const Nif::NiGeomMorpherController* ctrl); GeomMorpherController(); GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, GeomMorpherController) void operator()(SceneUtil::MorphGeometry*, osg::NodeVisitor*); private: std::vector mKeyFrames; }; #ifdef _MSC_VER #pragma warning( push ) /* * Warning C4250: 'NifOsg::KeyframeController': inherits 'osg::Callback::osg::Callback::asCallback' via dominance, * there is no way to solved this if an object must inherit from both osg::Object and osg::Callback */ #pragma warning( disable : 4250 ) #endif class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: KeyframeController(); KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); KeyframeController(const Nif::NiKeyframeController *keyctrl); META_Object(NifOsg, KeyframeController) osg::Vec3f getTranslation(float time) const override; osg::Callback* getAsCallback() override { return this; } void operator() (NifOsg::MatrixTransform*, osg::NodeVisitor*); private: QuaternionInterpolator mRotations; FloatInterpolator mXRotations; FloatInterpolator mYRotations; FloatInterpolator mZRotations; Vec3Interpolator mTranslations; FloatInterpolator mScales; Nif::NiKeyframeData::AxisOrder mAxisOrder{Nif::NiKeyframeData::AxisOrder::Order_XYZ}; osg::Quat getXYZRotation(float time) const; }; #ifdef _MSC_VER #pragma warning( pop ) #endif class UVController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { public: UVController(); UVController(const UVController&,const osg::CopyOp&); UVController(const Nif::NiUVData *data, const std::set& textureUnits); META_Object(NifOsg,UVController) void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; private: FloatInterpolator mUTrans; FloatInterpolator mVTrans; FloatInterpolator mUScale; FloatInterpolator mVScale; std::set mTextureUnits; }; class VisController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: std::vector mData; unsigned int mMask; bool calculate(float time) const; public: VisController(const Nif::NiVisData *data, unsigned int mask); VisController(); VisController(const VisController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, VisController) void operator() (osg::Node* node, osg::NodeVisitor* nv); }; class RollController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: FloatInterpolator mData; double mStartingTime{0}; public: RollController(const Nif::NiFloatData *data); RollController(const Nif::NiFloatInterpolator* interpolator); RollController() = default; RollController(const RollController& copy, const osg::CopyOp& copyop); void operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv); META_Object(NifOsg, RollController) }; class AlphaController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { private: FloatInterpolator mData; osg::ref_ptr mBaseMaterial; public: AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial); AlphaController(const Nif::NiFloatInterpolator* interpolator, const osg::Material* baseMaterial); AlphaController(); AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; META_Object(NifOsg, AlphaController) }; class MaterialColorController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { public: enum TargetColor { Ambient = 0, Diffuse = 1, Specular = 2, Emissive = 3 }; MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial); MaterialColorController(const Nif::NiPoint3Interpolator* interpolator, TargetColor color, const osg::Material* baseMaterial); MaterialColorController(); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, MaterialColorController) void setDefaults(osg::StateSet* stateset) override; void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; private: Vec3Interpolator mData; TargetColor mTargetColor = Ambient; osg::ref_ptr mBaseMaterial; }; class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { private: int mTexSlot{0}; float mDelta{0.f}; std::vector > mTextures; FloatInterpolator mData; public: FlipController(const Nif::NiFlipController* ctrl, const std::vector >& textures); FlipController(int texSlot, float delta, const std::vector >& textures); FlipController() = default; FlipController(const FlipController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, FlipController) std::vector >& getTextures() { return mTextures; } void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; }; class ParticleSystemController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: ParticleSystemController(const Nif::NiParticleSystemController* ctrl); ParticleSystemController(); ParticleSystemController(const ParticleSystemController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, ParticleSystemController) void operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv); private: float mEmitStart; float mEmitStop; }; class PathController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: PathController(const Nif::NiPathController* ctrl); PathController() = default; PathController(const PathController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, PathController) void operator() (NifOsg::MatrixTransform*, osg::NodeVisitor*); private: Vec3Interpolator mPath; FloatInterpolator mPercent; int mFlags{0}; float getPercent(float time) const; }; } #endif openmw-openmw-0.48.0/components/nifosg/matrixtransform.cpp000066400000000000000000000042311445372753700240520ustar00rootroot00000000000000#include "matrixtransform.hpp" namespace NifOsg { MatrixTransform::MatrixTransform(const Nif::Transformation &trafo) : osg::MatrixTransform(trafo.toMatrix()) , mScale(trafo.scale) , mRotationScale(trafo.rotation) { } MatrixTransform::MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op) : osg::MatrixTransform(copy, copyop) , mScale(copy.mScale) , mRotationScale(copy.mRotationScale) { } void MatrixTransform::setScale(float scale) { // Update the decomposed scale. mScale = scale; // Rescale the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) _matrix(i,j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); } void MatrixTransform::setRotation(const osg::Quat &rotation) { // First override the rotation ignoring the scale. _matrix.setRotate(rotation); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { // Update the current decomposed rotation and restore the known scale. mRotationScale.mValues[j][i] = _matrix(i,j); // NB: column/row major difference _matrix(i,j) *= mScale; } } _inverseDirty = true; dirtyBound(); } void MatrixTransform::setRotation(const Nif::Matrix3 &rotation) { // Update the decomposed rotation. mRotationScale = rotation; // Reorient the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) _matrix(i,j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); } void MatrixTransform::setTranslation(const osg::Vec3f &translation) { // The translation is independent from the rotation and scale so we can apply it directly. _matrix.setTrans(translation); _inverseDirty = true; dirtyBound(); } } openmw-openmw-0.48.0/components/nifosg/matrixtransform.hpp000066400000000000000000000026541445372753700240660ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H #define OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H #include #include namespace NifOsg { class MatrixTransform : public osg::MatrixTransform { public: MatrixTransform() = default; MatrixTransform(const Nif::Transformation &trafo); MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op); META_Node(NifOsg, MatrixTransform) // Hack: account for Transform differences between OSG and NIFs. // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. // Decomposing the original components from the 4x4 matrix isn't possible, which causes // problems when a KeyframeController wants to change only one of these components. So // we store the scale and rotation components separately here. float mScale{0.f}; Nif::Matrix3 mRotationScale; // Utility methods to transform the node and keep these components up-to-date. // The matrix's components should not be overridden manually or using preMult/postMult // unless you're sure you know what you are doing. void setScale(float scale); void setRotation(const osg::Quat &rotation); void setRotation(const Nif::Matrix3 &rotation); void setTranslation(const osg::Vec3f &translation); }; } #endif openmw-openmw-0.48.0/components/nifosg/nifloader.cpp000066400000000000000000003321541445372753700225650ustar00rootroot00000000000000#include "nifloader.hpp" #include #include #include #include #include #include #include #include #include #include // resource #include #include #include #include #include #include #include // particle #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "matrixtransform.hpp" #include "particle.hpp" namespace { struct DisableOptimizer : osg::NodeVisitor { DisableOptimizer(osg::NodeVisitor::TraversalMode mode = TRAVERSE_ALL_CHILDREN) : osg::NodeVisitor(mode) {} void apply(osg::Node &node) override { node.setDataVariance(osg::Object::DYNAMIC); traverse(node); } void apply(osg::Drawable &node) override { traverse(node); } }; void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) { if (const Nif::NiNode* ninode = dynamic_cast(node)) { outIndices.push_back(ninode->recIndex); for (unsigned int i=0; ichildren.length(); ++i) if (!ninode->children[i].empty()) getAllNiNodes(ninode->children[i].getPtr(), outIndices); } } bool isTypeGeometry(int type) { switch (type) { case Nif::RC_NiTriShape: case Nif::RC_NiTriStrips: case Nif::RC_NiLines: case Nif::RC_BSLODTriShape: return true; } return false; } // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. void collectDrawableProperties(const Nif::Node* nifNode, const Nif::Parent* parent, std::vector& out) { if (parent != nullptr) collectDrawableProperties(&parent->mNiNode, parent->mParent, out); const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i recType) { case Nif::RC_NiMaterialProperty: case Nif::RC_NiVertexColorProperty: case Nif::RC_NiSpecularProperty: case Nif::RC_NiAlphaProperty: out.push_back(props[i].getPtr()); break; default: break; } } } auto geometry = dynamic_cast(nifNode); if (geometry) { if (!geometry->shaderprop.empty()) out.emplace_back(geometry->shaderprop.getPtr()); if (!geometry->alphaprop.empty()) out.emplace_back(geometry->alphaprop.getPtr()); } } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. class BillboardCallback : public SceneUtil::NodeCallback { public: BillboardCallback() { } BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop) : SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, BillboardCallback) void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::Matrix modelView = *cv->getModelViewMatrix(); // attempt to preserve scale float mag[3]; for (int i=0;i<3;++i) { mag[i] = std::sqrt(modelView(0,i) * modelView(0,i) + modelView(1,i) * modelView(1,i) + modelView(2,i) * modelView(2,i)); } modelView.setRotate(osg::Quat()); modelView(0,0) = mag[0]; modelView(1,1) = mag[1]; modelView(2,2) = mag[2]; cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); } }; void extractTextKeys(const Nif::NiTextKeyExtraData *tk, SceneUtil::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) { std::vector results; Misc::StringUtils::split(tk->list[i].text, results, "\r\n"); for (std::string &result : results) { Misc::StringUtils::trim(result); Misc::StringUtils::lowerCaseInPlace(result); if (!result.empty()) textkeys.emplace(tk->list[i].time, std::move(result)); } } } } namespace NifOsg { bool Loader::sShowMarkers = false; void Loader::setShowMarkers(bool show) { sShowMarkers = show; } bool Loader::getShowMarkers() { return sShowMarkers; } unsigned int Loader::sHiddenNodeMask = 0; void Loader::setHiddenNodeMask(unsigned int mask) { sHiddenNodeMask = mask; } unsigned int Loader::getHiddenNodeMask() { return sHiddenNodeMask; } unsigned int Loader::sIntersectionDisabledNodeMask = ~0u; void Loader::setIntersectionDisabledNodeMask(unsigned int mask) { sIntersectionDisabledNodeMask = mask; } unsigned int Loader::getIntersectionDisabledNodeMask() { return sIntersectionDisabledNodeMask; } class LoaderImpl { public: /// @param filename used for warning messages. LoaderImpl(const std::string& filename, unsigned int ver, unsigned int userver, unsigned int bethver) : mFilename(filename), mVersion(ver), mUserVersion(userver), mBethVersion(bethver) { } std::string mFilename; unsigned int mVersion, mUserVersion, mBethVersion; size_t mFirstRootTextureIndex{~0u}; bool mFoundFirstRootTexturingProperty = false; bool mHasNightDayLabel = false; bool mHasHerbalismLabel = false; bool mHasStencilProperty = false; const Nif::NiSortAdjustNode* mPushedSorter = nullptr; const Nif::NiSortAdjustNode* mLastAppliedNoInheritSorter = nullptr; // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; static void loadKf(Nif::NIFFilePtr nif, SceneUtil::KeyframeHolder& target) { const Nif::NiSequenceStreamHelper *seq = nullptr; const size_t numRoots = nif->numRoots(); for (size_t i = 0; i < numRoots; ++i) { const Nif::Record *r = nif->getRoot(i); if (r && r->recType == Nif::RC_NiSequenceStreamHelper) { seq = static_cast(r); break; } } if (!seq) { nif->warn("Found no NiSequenceStreamHelper root record"); return; } Nif::ExtraPtr extra = seq->extra; if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) { nif->warn("First extra data was not a NiTextKeyExtraData, but a "+ (extra.empty() ? std::string("nil") : extra->recName)+"."); return; } extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); extra = extra->next; Nif::ControllerPtr ctrl = seq->controller; for(;!extra.empty() && !ctrl.empty();(extra=extra->next),(ctrl=ctrl->next)) { if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) { nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName); continue; } // Vanilla seems to ignore the "active" flag for NiKeyframeController, // so we don't want to skip inactive controllers here. const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); if (key->data.empty() && key->interpolator.empty()) continue; osg::ref_ptr callback = new NifOsg::KeyframeController(key); setupController(key, callback, /*animflags*/0); if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version"; } } osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* imageManager) { const size_t numRoots = nif->numRoots(); std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif->getRoot(i); if (!r) continue; const Nif::Node* nifNode = dynamic_cast(r); if (nifNode) roots.emplace_back(nifNode); } if (roots.empty()) nif->fail("Found no root nodes"); osg::ref_ptr textkeys (new SceneUtil::TextKeyMapHolder); osg::ref_ptr created(new osg::Group); created->setDataVariance(osg::Object::STATIC); for (const Nif::Node* root : roots) { auto node = handleNode(root, nullptr, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); created->addChild(node); } if (mHasNightDayLabel) created->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); if (mHasHerbalismLabel) created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); if (nif->getUseSkinning()) { osg::ref_ptr skel = new SceneUtil::Skeleton; skel->setStateSet(created->getStateSet()); skel->setName(created->getName()); for (unsigned int i=0; i < created->getNumChildren(); ++i) skel->addChild(created->getChild(i)); created->removeChildren(0, created->getNumChildren()); created = skel; } if (!textkeys->mTextKeys.empty()) created->getOrCreateUserDataContainer()->addUserObject(textkeys); created->setUserValue(Misc::OsgUserValues::sFileHash, nif->getHash()); return created; } void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; bool hasStencilProperty = false; for (size_t i = 0; i recType == Nif::RC_NiStencilProperty) { const Nif::NiStencilProperty* stencilprop = static_cast(props[i].getPtr()); if (stencilprop->data.enabled != 0) { hasStencilProperty = true; break; } } } for (size_t i = 0; i parents.empty() && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { mFirstRootTextureIndex = props[i].getPtr()->recIndex; mFoundFirstRootTexturingProperty = true; } else if (props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { if (props[i].getPtr()->recIndex == mFirstRootTextureIndex) applyTo->setUserValue("overrideFx", 1); } handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } } auto geometry = dynamic_cast(nifNode); // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property if (geometry && !geometry->shaderprop.empty()) handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } static void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) { bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; if (autoPlay) toSetup->setSource(std::make_shared()); toSetup->setFunction(std::make_shared(ctrl)); } static osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) { osg::ref_ptr lod (new osg::LOD); lod->setName(niLodNode->name); lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER); lod->setCenter(niLodNode->lodCenter); for (unsigned int i=0; ilodLevels.size(); ++i) { const Nif::NiLODNode::LODRange& range = niLodNode->lodLevels[i]; lod->setRange(i, range.minRange, range.maxRange); } lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT); return lod; } static osg::ref_ptr handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode) { osg::ref_ptr switchNode (new osg::Switch); switchNode->setName(niSwitchNode->name); switchNode->setNewChildDefaultValue(false); switchNode->setSingleChildOn(niSwitchNode->initialIndex); return switchNode; } static osg::ref_ptr prepareSequenceNode(const Nif::Node* nifNode) { const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::ref_ptr sequenceNode (new osg::Sequence); sequenceNode->setName(niFltAnimationNode->name); if (niFltAnimationNode->children.length()!=0) { if (niFltAnimationNode->swing()) sequenceNode->setDefaultTime(niFltAnimationNode->mDuration/(niFltAnimationNode->children.length()*2)); else sequenceNode->setDefaultTime(niFltAnimationNode->mDuration/niFltAnimationNode->children.length()); } return sequenceNode; } static void activateSequenceNode(osg::Group* osgNode, const Nif::Node* nifNode) { const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::Sequence* sequenceNode = static_cast(osgNode); if (niFltAnimationNode->swing()) sequenceNode->setInterval(osg::Sequence::SWING, 0,-1); else sequenceNode->setInterval(osg::Sequence::LOOP, 0,-1); sequenceNode->setDuration(1.0f, -1); sequenceNode->setMode(osg::Sequence::START); } osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) { if (!st) return nullptr; osg::ref_ptr image; if (!st->external && !st->data.empty()) { image = handleInternalTexture(st->data.getPtr()); } else { std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); image = imageManager->getImage(filename); } return image; } void handleEffect(const Nif::Node* nifNode, osg::Node* node, Resource::ImageManager* imageManager) { if (nifNode->recType != Nif::RC_NiTextureEffect) { Log(Debug::Info) << "Unhandled effect " << nifNode->recName << " in " << mFilename; return; } const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) { Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " << mFilename; return; } if (textureEffect->texture.empty()) { Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename; return; } osg::ref_ptr texGen (new osg::TexGen); switch (textureEffect->coordGenType) { case Nif::NiTextureEffect::World_Parallel: texGen->setMode(osg::TexGen::OBJECT_LINEAR); break; case Nif::NiTextureEffect::World_Perspective: texGen->setMode(osg::TexGen::EYE_LINEAR); break; case Nif::NiTextureEffect::Sphere_Map: texGen->setMode(osg::TexGen::SPHERE_MAP); break; default: Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << " in " << mFilename; return; } osg::ref_ptr image (handleSourceTexture(textureEffect->texture.getPtr(), imageManager)); osg::ref_ptr texture2d (new osg::Texture2D(image)); if (image) texture2d->setTextureSize(image->s(), image->t()); texture2d->setName("envMap"); texture2d->setWrap(osg::Texture::WRAP_S, textureEffect->wrapS() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, textureEffect->wrapT() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); int texUnit = 3; // FIXME osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); } // Get a default dataVariance for this node to be used as a hint by optimization (post)routines osg::ref_ptr createNode(const Nif::Node* nifNode) { osg::ref_ptr node; osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED; switch (nifNode->recType) { case Nif::RC_NiBillboardNode: dataVariance = osg::Object::DYNAMIC; break; default: // The Root node can be created as a Group if no transformation is required. // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you try). if (nifNode->parents.empty() && nifNode->controller.empty() && nifNode->trafo.isIdentity()) node = new osg::Group; dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; break; } if (!node) node = new NifOsg::MatrixTransform(nifNode->trafo); node->setDataVariance(dataVariance); return node; } osg::ref_ptr handleNode(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; osg::ref_ptr node = createNode(nifNode); if (nifNode->recType == Nif::RC_NiBillboardNode) { node->addCullCallback(new BillboardCallback); } node->setName(nifNode->name); if (parentNode) parentNode->addChild(node); if (!rootNode) rootNode = node; // The original NIF record index is used for a variety of features: // - finding the correct emitter node for a particle system // - establishing connections to the animated collision shapes, which are handled in a separate loader // - finding a random child NiNode in NiBspArrayController node->setUserValue("recIndex", nifNode->recIndex); std::vector extraCollection; for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) extraCollection.emplace_back(e); for (size_t i = 0; i < nifNode->extralist.length(); ++i) { Nif::ExtraPtr e = nifNode->extralist[i]; if (!e.empty()) extraCollection.emplace_back(e); } for (const auto& e : extraCollection) { if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) { const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); extractTextKeys(tk, *textKeys); } else if(e->recType == Nif::RC_NiStringExtraData) { const Nif::NiStringExtraData *sd = static_cast(e.getPtr()); constexpr std::string_view extraDataIdentifer = "omw:data"; // String markers may contain important information // affecting the entire subtree of this obj if (sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. hasMarkers = true; } else if (sd->string == "BONE") { node->getOrCreateUserDataContainer()->addDescription("CustomBone"); } else if (sd->string.rfind(extraDataIdentifer, 0) == 0) { node->setUserValue(Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length())); } } } if (nifNode->recType == Nif::RC_NiBSAnimationNode || nifNode->recType == Nif::RC_NiBSParticleNode) animflags = nifNode->flags; if (nifNode->recType == Nif::RC_NiSortAdjustNode) { auto sortNode = static_cast(nifNode); if (sortNode->mSubSorter.empty()) { Log(Debug::Warning) << "Empty accumulator found in '" << nifNode->recName << "' node " << nifNode->recIndex; } else { if (mPushedSorter && !mPushedSorter->mSubSorter.empty() && mPushedSorter->mMode != Nif::NiSortAdjustNode::SortingMode_Inherit) mLastAppliedNoInheritSorter = mPushedSorter; mPushedSorter = sortNode; } } // Hide collision shapes, but don't skip the subgraph // We still need to animate the hidden bones so the physics system can access them if (nifNode->recType == Nif::RC_RootCollisionNode) { skipMeshes = true; node->setNodeMask(Loader::getHiddenNodeMask()); } // We can skip creating meshes for hidden nodes if they don't have a VisController that // might make them visible later if (nifNode->isHidden()) { bool hasVisController = false; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { hasVisController |= (ctrl->recType == Nif::RC_NiVisController); if (hasVisController) break; } if (!hasVisController) skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes node->setNodeMask(Loader::getHiddenNodeMask()); } if (nifNode->recType == Nif::RC_NiCollisionSwitch && !nifNode->collisionActive()) node->setNodeMask(Loader::getIntersectionDisabledNodeMask()); osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); const bool isGeometry = isTypeGeometry(nifNode->recType); if (isGeometry && !skipMeshes) { const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name); static const std::string markerName = "tri editormarker"; static const std::string shadowName = "shadow"; static const std::string shadowName2 = "tri shadow"; const bool isMarker = hasMarkers && !nodeName.compare(0, markerName.size(), markerName); if (!isMarker && nodeName.compare(0, shadowName.size(), shadowName) && nodeName.compare(0, shadowName2.size(), shadowName2)) { Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) handleGeometry(nifNode, parent, node, composite, boundTextures, animflags); else handleSkinnedGeometry(nifNode, parent, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); } } if (nifNode->recType == Nif::RC_NiParticles) handleParticleSystem(nifNode, parent, node, composite, animflags); if (composite->getNumControllers() > 0) { osg::Callback *cb = composite; if (composite->getNumControllers() == 1) cb = composite->getController(0); if (animflags & Nif::NiNode::AnimFlag_AutoPlay) node->addCullCallback(cb); else node->addUpdateCallback(cb); // have to remain as UpdateCallback so AssignControllerSourcesVisitor can find it. } bool isAnimated = false; handleNodeControllers(nifNode, node, animflags, isAnimated); hasAnimatedParents |= isAnimated; // Make sure empty nodes and animated shapes are not optimized away so the physics system can find them. if (isAnimated || (hasAnimatedParents && ((skipMeshes || hasMarkers) || isGeometry))) node->setDataVariance(osg::Object::DYNAMIC); // LOD and Switch nodes must be wrapped by a transform (the current node) to support transformations properly // and we need to attach their children to the osg::LOD/osg::Switch nodes // but we must return that transform to the caller of handleNode instead of the actual LOD/Switch nodes. osg::ref_ptr currentNode = node; if (nifNode->recType == Nif::RC_NiSwitchNode) { const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); node->addChild(switchNode); if (niSwitchNode->name == Constants::NightDayLabel) mHasNightDayLabel = true; else if (niSwitchNode->name == Constants::HerbalismLabel) mHasHerbalismLabel = true; currentNode = switchNode; } else if (nifNode->recType == Nif::RC_NiLODNode) { const Nif::NiLODNode* niLodNode = static_cast(nifNode); osg::ref_ptr lodNode = handleLodNode(niLodNode); node->addChild(lodNode); currentNode = lodNode; } else if (nifNode->recType == Nif::RC_NiFltAnimationNode) { osg::ref_ptr sequenceNode = prepareSequenceNode(nifNode); node->addChild(sequenceNode); currentNode = sequenceNode; } const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { const Nif::NodeList &effects = ninode->effects; for (size_t i = 0; i < effects.length(); ++i) { if (!effects[i].empty()) handleEffect(effects[i].getPtr(), currentNode, imageManager); } const Nif::NodeList &children = ninode->children; const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) handleNode(children[i].getPtr(), ¤tParent, currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); } } if (nifNode->recType == Nif::RC_NiFltAnimationNode) activateSequenceNode(currentNode,nifNode); return node; } void handleMeshControllers(const Nif::Node *nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiUVController) { const Nif::NiUVController *niuvctrl = static_cast(ctrl.getPtr()); if (niuvctrl->data.empty()) continue; const unsigned int uvSet = niuvctrl->uvSet; std::set texUnits; // UVController should work only for textures which use a given UV Set, usually 0. for (unsigned int i=0; i uvctrl = new UVController(niuvctrl->data.getPtr(), texUnits); setupController(niuvctrl, uvctrl, animflags); composite->addController(uvctrl); } } } void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiKeyframeController) { const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); if (key->data.empty() && key->interpolator.empty()) continue; osg::ref_ptr callback = new KeyframeController(key); setupController(key, callback, animflags); node->addUpdateCallback(callback); isAnimated = true; } else if (ctrl->recType == Nif::RC_NiPathController) { const Nif::NiPathController *path = static_cast(ctrl.getPtr()); if (path->posData.empty() || path->floatData.empty()) continue; osg::ref_ptr callback(new PathController(path)); setupController(path, callback, animflags); node->addUpdateCallback(callback); isAnimated = true; } else if (ctrl->recType == Nif::RC_NiVisController) { const Nif::NiVisController *visctrl = static_cast(ctrl.getPtr()); if (visctrl->data.empty()) continue; osg::ref_ptr callback(new VisController(visctrl->data.getPtr(), Loader::getHiddenNodeMask())); setupController(visctrl, callback, animflags); node->addUpdateCallback(callback); } else if (ctrl->recType == Nif::RC_NiRollController) { const Nif::NiRollController *rollctrl = static_cast(ctrl.getPtr()); if (rollctrl->data.empty() && rollctrl->interpolator.empty()) continue; osg::ref_ptr callback; if (!rollctrl->interpolator.empty()) callback = new RollController(rollctrl->interpolator.getPtr()); else // if (!rollctrl->data.empty()) callback = new RollController(rollctrl->data.getPtr()); setupController(rollctrl, callback, animflags); node->addUpdateCallback(callback); isAnimated = true; } else if (ctrl->recType == Nif::RC_NiGeomMorpherController || ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController || ctrl->recType == Nif::RC_NiUVController) { // These controllers are handled elsewhere } else Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } } void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiAlphaController) { const Nif::NiAlphaController* alphactrl = static_cast(ctrl.getPtr()); if (alphactrl->data.empty() && alphactrl->interpolator.empty()) continue; osg::ref_ptr osgctrl; if (!alphactrl->interpolator.empty()) osgctrl = new AlphaController(alphactrl->interpolator.getPtr(), baseMaterial); else // if (!alphactrl->data.empty()) osgctrl = new AlphaController(alphactrl->data.getPtr(), baseMaterial); setupController(alphactrl, osgctrl, animflags); composite->addController(osgctrl); } else if (ctrl->recType == Nif::RC_NiMaterialColorController) { const Nif::NiMaterialColorController* matctrl = static_cast(ctrl.getPtr()); if (matctrl->data.empty() && matctrl->interpolator.empty()) continue; auto targetColor = static_cast(matctrl->targetColor); if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW && targetColor == MaterialColorController::TargetColor::Specular) continue; osg::ref_ptr osgctrl; if (!matctrl->interpolator.empty()) osgctrl = new MaterialColorController(matctrl->interpolator.getPtr(), targetColor, baseMaterial); else // if (!matctrl->data.empty()) osgctrl = new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial); setupController(matctrl, osgctrl, animflags); composite->addController(osgctrl); } else Log(Debug::Info) << "Unexpected material controller " << ctrl->recType << " in " << mFilename; } } void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet *stateset, int animflags) { for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiFlipController) { const Nif::NiFlipController* flipctrl = static_cast(ctrl.getPtr()); std::vector > textures; // inherit wrap settings from the target slot osg::Texture2D* inherit = dynamic_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXTURE)); osg::Texture2D::WrapMode wrapS = osg::Texture2D::REPEAT; osg::Texture2D::WrapMode wrapT = osg::Texture2D::REPEAT; if (inherit) { wrapS = inherit->getWrap(osg::Texture2D::WRAP_S); wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } for (unsigned int i=0; imSources.length(); ++i) { Nif::NiSourceTexturePtr st = flipctrl->mSources[i]; if (st.empty()) continue; osg::ref_ptr image (handleSourceTexture(st.getPtr(), imageManager)); osg::ref_ptr texture (new osg::Texture2D(image)); if (image) texture->setTextureSize(image->s(), image->t()); texture->setWrap(osg::Texture::WRAP_S, wrapS); texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); setupController(ctrl.getPtr(), callback, animflags); composite->addController(callback); } else Log(Debug::Info) << "Unexpected texture controller " << ctrl->recName << " in " << mFilename; } } void handleParticlePrograms(Nif::NiParticleModifierPtr affectors, Nif::NiParticleModifierPtr colliders, osg::Group *attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) { osgParticle::ModularProgram* program = new osgParticle::ModularProgram; attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); for (; !affectors.empty(); affectors = affectors->next) { if (affectors->recType == Nif::RC_NiParticleGrowFade) { const Nif::NiParticleGrowFade *gf = static_cast(affectors.getPtr()); program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime)); } else if (affectors->recType == Nif::RC_NiGravity) { const Nif::NiGravity* gr = static_cast(affectors.getPtr()); program->addOperator(new GravityAffector(gr)); } else if (affectors->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier *cl = static_cast(affectors.getPtr()); if (cl->data.empty()) continue; const Nif::NiColorData *clrdata = cl->data.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } else if (affectors->recType == Nif::RC_NiParticleRotation) { // unused } else Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; } for (; !colliders.empty(); colliders = colliders->next) { if (colliders->recType == Nif::RC_NiPlanarCollider) { const Nif::NiPlanarCollider* planarcollider = static_cast(colliders.getPtr()); program->addOperator(new PlanarCollider(planarcollider)); } else if (colliders->recType == Nif::RC_NiSphericalCollider) { const Nif::NiSphericalCollider* sphericalcollider = static_cast(colliders.getPtr()); program->addOperator(new SphericalCollider(sphericalcollider)); } else Log(Debug::Info) << "Unhandled particle collider " << colliders->recName << " in " << mFilename; } } // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. void handleParticleInitialState(const Nif::Node* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { auto particleNode = static_cast(nifNode); if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) { partsys->setQuota(partctrl->numParticles); return; } auto particledata = static_cast(particleNode->data.getPtr()); partsys->setQuota(particledata->numParticles); osg::BoundingBox box; int i=0; for (const auto& particle : partctrl->particles) { if (i++ >= particledata->activeCount) break; if (particle.lifespan <= 0) continue; if (particle.vertex >= particledata->vertices.size()) continue; ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); osgParticle::Particle* created = partsys->createParticle(&particletemplate); created->setLifeTime(particle.lifespan); // Note this position and velocity is not correct for a particle system with absolute reference frame, // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager. created->setVelocity(particle.velocity); const osg::Vec3f& position = particledata->vertices[particle.vertex]; created->setPosition(position); created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); created->setAlphaRange(osgParticle::rangef(1.f, 1.f)); float size = partctrl->size; if (particle.vertex < particledata->sizes.size()) size *= particledata->sizes[particle.vertex]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); } // radius may be used to force a larger bounding box box.expandBy(osg::BoundingSphere(osg::Vec3(0,0,0), particledata->radius)); partsys->setInitialBound(box); } osg::ref_ptr handleParticleEmitter(const Nif::NiParticleSystemController* partctrl) { std::vector targets; if (partctrl->recType == Nif::RC_NiBSPArrayController && !partctrl->emitAtVertex()) { getAllNiNodes(partctrl->emitter.getPtr(), targets); } osg::ref_ptr emitter = new Emitter(targets); osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter; if (partctrl->noAutoAdjust()) counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate); else if (partctrl->lifetime == 0 && partctrl->lifetimeRandom == 0) counter->setNumberOfParticlesPerSecondToCreate(0); else counter->setNumberOfParticlesPerSecondToCreate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); emitter->setCounter(counter); ParticleShooter* shooter = new ParticleShooter(partctrl->velocity - partctrl->velocityRandom*0.5f, partctrl->velocity + partctrl->velocityRandom*0.5f, partctrl->horizontalDir, partctrl->horizontalAngle, partctrl->verticalDir, partctrl->verticalAngle, partctrl->lifetime, partctrl->lifetimeRandom); emitter->setShooter(shooter); emitter->setFlags(partctrl->flags); if (partctrl->recType == Nif::RC_NiBSPArrayController && partctrl->emitAtVertex()) { emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); } else { osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); emitter->setPlacer(placer); } return emitter; } void handleQueuedParticleEmitters(osg::Group* rootNode, Nif::NIFFilePtr nif) { for (const auto& emitterPair : mEmitterQueue) { size_t recIndex = emitterPair.first; FindGroupByRecIndex findEmitterNode(recIndex); rootNode->accept(findEmitterNode); osg::Group* emitterNode = findEmitterNode.mFound; if (!emitterNode) { nif->warn("Failed to find particle emitter emitter node (node record index " + std::to_string(recIndex) + ")"); continue; } // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! emitterNode->addChild(emitterPair.second); DisableOptimizer disableOptimizer; emitterNode->accept(disableOptimizer); } mEmitterQueue.clear(); } void handleParticleSystem(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) partctrl = static_cast(ctrl.getPtr()); } if (!partctrl) { Log(Debug::Info) << "No particle controller found in " << mFilename; return; } osgParticle::ParticleProcessor::ReferenceFrame rf = (animflags & Nif::NiNode::ParticleFlag_LocalSpace) ? osgParticle::ParticleProcessor::RELATIVE_RF : osgParticle::ParticleProcessor::ABSOLUTE_RF; // HACK: ParticleSystem has no setReferenceFrame method if (rf == osgParticle::ParticleProcessor::ABSOLUTE_RF) { partsys->getOrCreateUserDataContainer()->addDescription("worldspace"); } partsys->setParticleScaleReferenceFrame(osgParticle::ParticleSystem::LOCAL_COORDINATES); handleParticleInitialState(nifNode, partsys, partctrl); partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size)); partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); if (!partctrl->emitter.empty()) { osg::ref_ptr emitter = handleParticleEmitter(partctrl); emitter->setParticleSystem(partsys); emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment. // If the emitter node is placed later than the particle node, it'll have a single frame delay in particle processing. // But that shouldn't be a game-breaking issue. mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); setupController(partctrl, callback, animflags); emitter->setUpdateCallback(callback); if (!(animflags & Nif::NiNode::ParticleFlag_AutoPlay)) { partsys->setFrozen(true); } // Due to odd code in the ParticleSystemUpdater, particle systems will not be updated in the first frame // So do that update manually osg::NodeVisitor nv; partsys->update(0.0, nv); } // affectors should be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct // localToWorldMatrix for transforming to particle space handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); // particle system updater (after the emitters and affectors in the scene graph) // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; updater->addParticleSystem(partsys); parentNode->addChild(updater); osg::Node* toAttach = partsys.get(); if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) parentNode->addChild(toAttach); else { osg::MatrixTransform* trans = new osg::MatrixTransform; trans->setUpdateCallback(new InverseWorldMatrix); trans->addChild(toAttach); parentNode->addChild(trans); } } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) { const auto& vertices = data->vertices; const auto& normals = data->normals; const auto& colors = data->colors; if (!vertices.empty()) geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); if (!normals.empty()) geometry->setNormalArray(new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); if (!colors.empty()) geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); const auto& uvlist = data->uvlist; int textureStage = 0; for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it, ++textureStage) { unsigned int uvSet = *it; if (uvSet >= uvlist.size()) { Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename; if (uvlist.empty()) continue; uvSet = 0; } geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX); } } void handleNiGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { const Nif::NiGeometry* niGeometry = static_cast(nifNode); if (niGeometry->data.empty()) return; const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape) { if (niGeometryData->recType != Nif::RC_NiTriShapeData) return; auto triangles = static_cast(niGeometryData)->triangles; if (triangles.empty()) return; geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(), (unsigned short*)triangles.data())); } else if (niGeometry->recType == Nif::RC_NiTriStrips) { if (niGeometryData->recType != Nif::RC_NiTriStripsData) return; auto data = static_cast(niGeometryData); bool hasGeometry = false; for (const auto& strip : data->strips) { if (strip.size() < 3) continue; geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), reinterpret_cast(strip.data()))); hasGeometry = true; } if (!hasGeometry) return; } else if (niGeometry->recType == Nif::RC_NiLines) { if (niGeometryData->recType != Nif::RC_NiLinesData) return; auto data = static_cast(niGeometryData); const auto& line = data->lines; if (line.empty()) return; geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), reinterpret_cast(line.data()))); } handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } void handleGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geom (new osg::Geometry); handleNiGeometry(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); // If the record had no valid geometry data in it, early-out if (geom->empty()) return; osg::ref_ptr drawable; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { const Nif::NiGeomMorpherController* nimorphctrl = static_cast(ctrl.getPtr()); if (nimorphctrl->data.empty()) continue; drawable = handleMorphGeometry(nimorphctrl, geom, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController(nimorphctrl); setupController(ctrl.getPtr(), morphctrl, animflags); drawable->setUpdateCallback(morphctrl); break; } } if (!drawable.get()) drawable = geom; drawable->setName(nifNode->name); parentNode->addChild(drawable); } osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, osg::ref_ptr sourceGeometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; morphGeom->setSourceGeometry(sourceGeometry); const std::vector& morphs = morpher->data.getPtr()->mMorphs; if (morphs.empty()) return morphGeom; if (morphs[0].mVertices.size() != static_cast(sourceGeometry->getVertexArray())->size()) return morphGeom; for (unsigned int i = 0; i < morphs.size(); ++i) morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); return morphGeom; } void handleSkinnedGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); handleNiGeometry(nifNode, parent, geometry, parentNode, composite, boundTextures, animflags); if (geometry->empty()) return; osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); rig->setName(nifNode->name); // Assign bone weights osg::ref_ptr map (new SceneUtil::RigGeometry::InfluenceMap); const Nif::NiSkinInstance *skin = static_cast(nifNode)->skin.getPtr(); const Nif::NiSkinData *data = skin->data.getPtr(); const Nif::NodeList &bones = skin->bones; for(size_t i = 0;i < bones.length();i++) { std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); SceneUtil::RigGeometry::BoneInfluence influence; const std::vector &weights = data->bones[i].weights; for(size_t j = 0;j < weights.size();j++) { influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight); } influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); map->mData.emplace_back(boneName, influence); } rig->setInfluenceMap(map); parentNode->addChild(rig); } osg::BlendFunc::BlendFuncMode getBlendMode(int mode) { switch(mode) { case 0: return osg::BlendFunc::ONE; case 1: return osg::BlendFunc::ZERO; case 2: return osg::BlendFunc::SRC_COLOR; case 3: return osg::BlendFunc::ONE_MINUS_SRC_COLOR; case 4: return osg::BlendFunc::DST_COLOR; case 5: return osg::BlendFunc::ONE_MINUS_DST_COLOR; case 6: return osg::BlendFunc::SRC_ALPHA; case 7: return osg::BlendFunc::ONE_MINUS_SRC_ALPHA; case 8: return osg::BlendFunc::DST_ALPHA; case 9: return osg::BlendFunc::ONE_MINUS_DST_ALPHA; case 10: return osg::BlendFunc::SRC_ALPHA_SATURATE; default: Log(Debug::Info) << "Unexpected blend mode: "<< mode << " in " << mFilename; return osg::BlendFunc::SRC_ALPHA; } } osg::AlphaFunc::ComparisonFunction getTestMode(int mode) { switch (mode) { case 0: return osg::AlphaFunc::ALWAYS; case 1: return osg::AlphaFunc::LESS; case 2: return osg::AlphaFunc::EQUAL; case 3: return osg::AlphaFunc::LEQUAL; case 4: return osg::AlphaFunc::GREATER; case 5: return osg::AlphaFunc::NOTEQUAL; case 6: return osg::AlphaFunc::GEQUAL; case 7: return osg::AlphaFunc::NEVER; default: Log(Debug::Info) << "Unexpected blend mode: " << mode << " in " << mFilename; return osg::AlphaFunc::LEQUAL; } } osg::Stencil::Function getStencilFunction(int func) { switch (func) { case 0: return osg::Stencil::NEVER; case 1: return osg::Stencil::LESS; case 2: return osg::Stencil::EQUAL; case 3: return osg::Stencil::LEQUAL; case 4: return osg::Stencil::GREATER; case 5: return osg::Stencil::NOTEQUAL; case 6: return osg::Stencil::GEQUAL; case 7: return osg::Stencil::ALWAYS; default: Log(Debug::Info) << "Unexpected stencil function: " << func << " in " << mFilename; return osg::Stencil::NEVER; } } osg::Stencil::Operation getStencilOperation(int op) { switch (op) { case 0: return osg::Stencil::KEEP; case 1: return osg::Stencil::ZERO; case 2: return osg::Stencil::REPLACE; case 3: return osg::Stencil::INCR; case 4: return osg::Stencil::DECR; case 5: return osg::Stencil::INVERT; default: Log(Debug::Info) << "Unexpected stencil operation: " << op << " in " << mFilename; return osg::Stencil::KEEP; } } osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) { osg::ref_ptr image (new osg::Image); GLenum pixelformat = 0; switch (pixelData->fmt) { case Nif::NiPixelData::NIPXFMT_RGB8: case Nif::NiPixelData::NIPXFMT_PAL8: pixelformat = GL_RGB; break; case Nif::NiPixelData::NIPXFMT_RGBA8: case Nif::NiPixelData::NIPXFMT_PALA8: pixelformat = GL_RGBA; break; default: Log(Debug::Info) << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename; return nullptr; } if (pixelData->mipmaps.empty()) return nullptr; int width = 0; int height = 0; std::vector mipmapVector; for (unsigned int i=0; imipmaps.size(); ++i) { const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; size_t mipSize = mip.height * mip.width * pixelData->bpp / 8; if (mipSize + mip.dataOffset > pixelData->data.size()) { Log(Debug::Info) << "Internal texture's mipmap data out of bounds, ignoring texture"; return nullptr; } if (i != 0) mipmapVector.push_back(mip.dataOffset); else { width = mip.width; height = mip.height; } } if (width <= 0 || height <= 0) { Log(Debug::Info) << "Internal Texture Width and height must be non zero, ignoring texture"; return nullptr; } const std::vector& pixels = pixelData->data; switch (pixelData->fmt) { case Nif::NiPixelData::NIPXFMT_RGB8: case Nif::NiPixelData::NIPXFMT_RGBA8: { unsigned char* data = new unsigned char[pixels.size()]; memcpy(data, pixels.data(), pixels.size()); image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); break; } case Nif::NiPixelData::NIPXFMT_PAL8: case Nif::NiPixelData::NIPXFMT_PALA8: { if (pixelData->palette.empty() || pixelData->bpp != 8) { Log(Debug::Info) << "Palettized texture in " << mFilename << " is invalid, ignoring"; return nullptr; } // We're going to convert the indices that pixel data contains // into real colors using the palette. const auto& palette = pixelData->palette->colors; const int numChannels = pixelformat == GL_RGBA ? 4 : 3; unsigned char* data = new unsigned char[pixels.size() * numChannels]; unsigned char* pixel = data; for (unsigned char index : pixels) { memcpy(pixel, &palette[index], sizeof(unsigned char) * numChannels); pixel += numChannels; } image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); break; } default: return nullptr; } image->setMipmapLevels(mipmapVector); image->flipVertical(); return image; } osg::ref_ptr createEmissiveTexEnv() { osg::ref_ptr texEnv(new osg::TexEnvCombine); // Sum the previous colour and the emissive colour. texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); // Keep the previous alpha. texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); return texEnv; } void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) { // overriding a parent NiTexturingProperty, so remove what was previously bound for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); boundTextures.clear(); } // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly. for (size_t i=0; itextures.size(); ++i) { if (texprop->textures[i].inUse || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->controller.empty())) { switch(i) { //These are handled later on case Nif::NiTexturingProperty::BaseTexture: case Nif::NiTexturingProperty::GlowTexture: case Nif::NiTexturingProperty::DarkTexture: case Nif::NiTexturingProperty::BumpTexture: case Nif::NiTexturingProperty::DetailTexture: case Nif::NiTexturingProperty::DecalTexture: case Nif::NiTexturingProperty::GlossTexture: break; default: { Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename; continue; } } unsigned int uvSet = 0; // create a new texture, will later attempt to share using the SharedStateManager osg::ref_ptr texture2d; if (texprop->textures[i].inUse) { const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; if(tex.texture.empty() && texprop->controller.empty()) { if (i == 0) Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName << "\" in " << mFilename; continue; } if (!tex.texture.empty()) { const Nif::NiSourceTexture *st = tex.texture.getPtr(); osg::ref_ptr image = handleSourceTexture(st, imageManager); texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); } else texture2d = new osg::Texture2D; texture2d->setWrap(osg::Texture::WRAP_S, tex.wrapS() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, tex.wrapT() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); uvSet = tex.uvSet; } else { // Texture only comes from NiFlipController, so tex is ignored, set defaults texture2d = new osg::Texture2D; texture2d->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture2d->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); uvSet = 0; } unsigned int texUnit = boundTextures.size(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); if (i == Nif::NiTexturingProperty::GlowTexture) { stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::DarkTexture) { osg::TexEnv* texEnv = new osg::TexEnv; // Modulate both the colour and the alpha with the dark map. texEnv->setMode(osg::TexEnv::MODULATE); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::DetailTexture) { osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; // Modulate previous colour... texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); // with the detail map's colour, texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); // and a twist: texEnv->setScale_RGB(2.f); // Keep the previous alpha. texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::BumpTexture) { // Bump maps offset the environment map. // Set this texture to Off by default since we can't render it with the fixed-function pipeline stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); osg::Matrix2 bumpMapMatrix(texprop->bumpMapMatrix.x(), texprop->bumpMapMatrix.y(), texprop->bumpMapMatrix.z(), texprop->bumpMapMatrix.w()); stateset->addUniform(new osg::Uniform("bumpMapMatrix", bumpMapMatrix)); stateset->addUniform(new osg::Uniform("envMapLumaBias", texprop->envMapLumaBias)); } else if (i == Nif::NiTexturingProperty::GlossTexture) { // A gloss map is an environment map mask. // Gloss maps are only implemented in the object shaders as well. stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); } else if (i == Nif::NiTexturingProperty::DecalTexture) { // This is only an inaccurate imitation of the original implementation, // see https://github.com/niftools/nifskope/issues/184 osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; // Interpolate to the decal texture's colour... texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); texEnv->setSource0_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR); // ...from the previous colour... texEnv->setSource1_RGB(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR); // using the decal texture's alpha as the factor. texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_ALPHA); // Keep the previous alpha. texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } switch (i) { case Nif::NiTexturingProperty::BaseTexture: texture2d->setName("diffuseMap"); break; case Nif::NiTexturingProperty::BumpTexture: texture2d->setName("bumpMap"); break; case Nif::NiTexturingProperty::GlowTexture: texture2d->setName("emissiveMap"); break; case Nif::NiTexturingProperty::DarkTexture: texture2d->setName("darkMap"); break; case Nif::NiTexturingProperty::DetailTexture: texture2d->setName("detailMap"); break; case Nif::NiTexturingProperty::DecalTexture: texture2d->setName("decalMap"); break; case Nif::NiTexturingProperty::GlossTexture: texture2d->setName("glossMap"); break; default: break; } boundTextures.push_back(uvSet); } } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector& boundTextures) { if (!boundTextures.empty()) { for (unsigned int i = 0; i < boundTextures.size(); ++i) stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); boundTextures.clear(); } const unsigned int uvSet = 0; for (size_t i = 0; i < textureSet->textures.size(); ++i) { if (textureSet->textures[i].empty()) continue; switch(i) { case Nif::BSShaderTextureSet::TextureType_Base: case Nif::BSShaderTextureSet::TextureType_Normal: case Nif::BSShaderTextureSet::TextureType_Glow: break; default: { Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename; continue; } } std::string filename = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); bool wrapT = clamp & 0x1; bool wrapS = (clamp >> 1) & 0x1; texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); unsigned int texUnit = boundTextures.size(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); // BSShaderTextureSet presence means there's no need for FFP support for the affected node switch (i) { case Nif::BSShaderTextureSet::TextureType_Base: texture2d->setName("diffuseMap"); break; case Nif::BSShaderTextureSet::TextureType_Normal: texture2d->setName("normalMap"); break; case Nif::BSShaderTextureSet::TextureType_Glow: texture2d->setName("emissiveMap"); break; } boundTextures.emplace_back(uvSet); } } std::string_view getBSShaderPrefix(unsigned int type) const { switch (static_cast(type)) { case Nif::BSShaderType::ShaderType_Default: return "nv_default"; case Nif::BSShaderType::ShaderType_NoLighting: return "nv_nolighting"; case Nif::BSShaderType::ShaderType_TallGrass: case Nif::BSShaderType::ShaderType_Sky: case Nif::BSShaderType::ShaderType_Skin: case Nif::BSShaderType::ShaderType_Water: case Nif::BSShaderType::ShaderType_Lighting30: case Nif::BSShaderType::ShaderType_Tile: Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; return "nv_default"; } Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; return "nv_default"; } std::string_view getBSLightingShaderPrefix(unsigned int type) const { switch (static_cast(type)) { case Nif::BSLightingShaderType::ShaderType_Default: return "nv_default"; case Nif::BSLightingShaderType::ShaderType_EnvMap: case Nif::BSLightingShaderType::ShaderType_Glow: case Nif::BSLightingShaderType::ShaderType_Parallax: case Nif::BSLightingShaderType::ShaderType_FaceTint: case Nif::BSLightingShaderType::ShaderType_SkinTint: case Nif::BSLightingShaderType::ShaderType_HairTint: case Nif::BSLightingShaderType::ShaderType_ParallaxOcc: case Nif::BSLightingShaderType::ShaderType_MultitexLand: case Nif::BSLightingShaderType::ShaderType_LODLand: case Nif::BSLightingShaderType::ShaderType_Snow: case Nif::BSLightingShaderType::ShaderType_MultiLayerParallax: case Nif::BSLightingShaderType::ShaderType_TreeAnim: case Nif::BSLightingShaderType::ShaderType_LODObjects: case Nif::BSLightingShaderType::ShaderType_SparkleSnow: case Nif::BSLightingShaderType::ShaderType_LODObjectsHD: case Nif::BSLightingShaderType::ShaderType_EyeEnvmap: case Nif::BSLightingShaderType::ShaderType_Cloud: case Nif::BSLightingShaderType::ShaderType_LODNoise: case Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend: case Nif::BSLightingShaderType::ShaderType_Dismemberment: Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; return "nv_default"; } Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; return "nv_default"; } void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags, bool hasStencilProperty) { switch (property->recType) { case Nif::RC_NiStencilProperty: { const Nif::NiStencilProperty* stencilprop = static_cast(property); osg::ref_ptr frontFace = new osg::FrontFace; switch (stencilprop->data.drawMode) { case 2: frontFace->setMode(osg::FrontFace::CLOCKWISE); break; case 0: case 1: default: frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE); break; } frontFace = shareAttribute(frontFace); osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setAttribute(frontFace, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, stencilprop->data.drawMode == 3 ? osg::StateAttribute::OFF : osg::StateAttribute::ON); if (stencilprop->data.enabled != 0) { mHasStencilProperty = true; osg::ref_ptr stencil = new osg::Stencil; stencil->setFunction(getStencilFunction(stencilprop->data.compareFunc), stencilprop->data.stencilRef, stencilprop->data.stencilMask); stencil->setStencilFailOperation(getStencilOperation(stencilprop->data.failAction)); stencil->setStencilPassAndDepthFailOperation(getStencilOperation(stencilprop->data.zFailAction)); stencil->setStencilPassAndDepthPassOperation(getStencilOperation(stencilprop->data.zPassAction)); stencil = shareAttribute(stencil); stateset->setAttributeAndModes(stencil, osg::StateAttribute::ON); } break; } case Nif::RC_NiWireframeProperty: { const Nif::NiWireframeProperty* wireprop = static_cast(property); osg::ref_ptr mode = new osg::PolygonMode; mode->setMode(osg::PolygonMode::FRONT_AND_BACK, wireprop->isEnabled() ? osg::PolygonMode::LINE : osg::PolygonMode::FILL); mode = shareAttribute(mode); node->getOrCreateStateSet()->setAttributeAndModes(mode, osg::StateAttribute::ON); break; } case Nif::RC_NiZBufferProperty: { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setMode(GL_DEPTH_TEST, zprop->depthTest() ? osg::StateAttribute::ON : osg::StateAttribute::OFF); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(zprop->depthWrite()); // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it uses a fixed depth function of GL_ALWAYS. if (hasStencilProperty) depth->setFunction(osg::Depth::ALWAYS); depth = shareAttribute(depth); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed case Nif::RC_NiMaterialProperty: case Nif::RC_NiVertexColorProperty: case Nif::RC_NiSpecularProperty: { // Handled on drawable level so we know whether vertex colors are available break; } case Nif::RC_NiAlphaProperty: { // Handled on drawable level to prevent RenderBin nesting issues break; } case Nif::RC_NiTexturingProperty: { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); handleTextureProperty(texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); break; } case Nif::RC_BSShaderPPLightingProperty: { auto texprop = static_cast(property); bool shaderRequired = true; node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->textureSet.empty()) { auto textureSet = texprop->textureSet.getPtr(); handleTextureSet(textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); break; } case Nif::RC_BSShaderNoLightingProperty: { auto texprop = static_cast(property); bool shaderRequired = true; node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->filename.empty()) { if (!boundTextures.empty()) { for (unsigned int i = 0; i < boundTextures.size(); ++i) stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); boundTextures.clear(); } std::string filename = Misc::ResourceHelpers::correctTexturePath(texprop->filename, imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) texture2d->setTextureSize(image->s(), image->t()); texture2d->setWrap(osg::Texture::WRAP_S, texprop->wrapS() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, texprop->wrapT() ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); const unsigned int texUnit = 0; const unsigned int uvSet = 0; stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); boundTextures.push_back(uvSet); } if (mBethVersion >= 27) { stateset->addUniform(new osg::Uniform("useFalloff", true)); stateset->addUniform(new osg::Uniform("falloffParams", texprop->falloffParams)); } else { stateset->addUniform(new osg::Uniform("useFalloff", false)); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); break; } case Nif::RC_BSLightingShaderProperty: { auto texprop = static_cast(property); bool shaderRequired = true; node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->mTextureSet.empty()) handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); break; } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: case Nif::RC_NiFogProperty: { break; } default: Log(Debug::Info) << "Unhandled " << property->recName << " in " << mFilename; break; } } struct CompareStateAttribute { bool operator() (const osg::ref_ptr& left, const osg::ref_ptr& right) const { return left->compare(*right) < 0; } }; // global sharing of State Attributes will reduce the number of GL calls as the osg::State will check by pointer to see if state is the same template Attribute* shareAttribute(const osg::ref_ptr& attr) { typedef std::set, CompareStateAttribute> Cache; static Cache sCache; static std::mutex sMutex; std::lock_guard lock(sMutex); typename Cache::iterator found = sCache.find(attr); if (found == sCache.end()) found = sCache.insert(attr).first; return *found; } void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF); // NIF material defaults don't match OpenGL defaults mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); bool hasMatCtrl = false; bool hasSortAlpha = false; osg::StateSet* blendFuncStateSet = nullptr; auto setBin_Transparent = [] (osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); }; auto setBin_BackToFront = [] (osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; auto setBin_Traversal = [] (osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; auto setBin_Inherit = [] (osg::StateSet* ss) { ss->setRenderBinToInherit(); }; int lightmode = 1; float emissiveMult = 1.f; float specStrength = 1.f; for (const Nif::Property* property : properties) { switch (property->recType) { case Nif::RC_NiSpecularProperty: { // Specular property can turn specular lighting off. // FIXME: NiMaterialColorController doesn't care about this. auto specprop = static_cast(property); specEnabled = specprop->isEnabled(); break; } case Nif::RC_NiMaterialProperty: { const Nif::NiMaterialProperty* matprop = static_cast(property); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); emissiveMult = matprop->data.emissiveMult; mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); if (!matprop->controller.empty()) { hasMatCtrl = true; handleMaterialControllers(matprop, composite, animflags, mat); } break; } case Nif::RC_NiVertexColorProperty: { const Nif::NiVertexColorProperty* vertprop = static_cast(property); lightmode = vertprop->data.lightmode; switch (vertprop->data.vertmode) { case 0: mat->setColorMode(osg::Material::OFF); break; case 1: mat->setColorMode(osg::Material::EMISSION); break; case 2: if (lightmode != 0) mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); else mat->setColorMode(osg::Material::OFF); break; } break; } case Nif::RC_NiAlphaProperty: { const Nif::NiAlphaProperty* alphaprop = static_cast(property); if (alphaprop->useAlphaBlending()) { osg::ref_ptr blendFunc (new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()), getBlendMode(alphaprop->destinationBlendMode()))); // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. // Either way, D3D8.1 doesn't do that, so adapt the destination factor. if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); if (!alphaprop->noSorter()) { hasSortAlpha = true; if (!mPushedSorter) setBin_Transparent(node->getStateSet()); } else { if (!mPushedSorter) setBin_Inherit(node->getStateSet()); } } else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); blendFuncStateSet = stateset; if (!mPushedSorter) blendFuncStateSet->setRenderBinToInherit(); } if (alphaprop->useAlphaTesting()) { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode(alphaprop->alphaTestMode()), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); } break; } case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); mat->setShininess(osg::Material::FRONT_AND_BACK, shaderprop->mGlossiness); emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; break; } default: break; } } // While NetImmerse and Gamebryo support specular lighting, Morrowind has its support disabled. if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || !specEnabled) mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); if (lightmode == 0) { osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse = osg::Vec4f(0,0,0,diffuse.a()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f()); } // If we're told to use vertex colors but there are none to use, use a default color instead. if (!hasVertexColors) { switch (mat->getColorMode()) { case osg::Material::AMBIENT: mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); break; case osg::Material::AMBIENT_AND_DIFFUSE: mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); break; case osg::Material::EMISSION: mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); break; default: break; } mat->setColorMode(osg::Material::OFF); } if (!mPushedSorter && !hasSortAlpha && mHasStencilProperty) setBin_Traversal(node->getOrCreateStateSet()); if (!mPushedSorter && !hasMatCtrl && mat->getColorMode() == osg::Material::OFF && mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1) && mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) && mat->getAmbient(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) && mat->getShininess(osg::Material::FRONT_AND_BACK) == 0 && mat->getSpecular(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0.f, 0.f, 0.f, 0.f)) { // default state, skip return; } mat = shareAttribute(mat); osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); if (emissiveMult != 1.f) stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); if (specStrength != 1.f) stateset->addUniform(new osg::Uniform("specStrength", specStrength)); if (!mPushedSorter) return; auto assignBin = [&] (int mode, int type) { if (mode == Nif::NiSortAdjustNode::SortingMode_Off) { setBin_Traversal(stateset); return; } if (type == Nif::RC_NiAlphaAccumulator) { if (hasSortAlpha) setBin_BackToFront(stateset); else setBin_Traversal(stateset); } else if (type == Nif::RC_NiClusterAccumulator) setBin_BackToFront(stateset); else Log(Debug::Error) << "Unrecognized NiAccumulator in " << mFilename; }; switch (mPushedSorter->mMode) { case Nif::NiSortAdjustNode::SortingMode_Inherit: { if (mLastAppliedNoInheritSorter) assignBin(mLastAppliedNoInheritSorter->mMode, mLastAppliedNoInheritSorter->mSubSorter->recType); else assignBin(mPushedSorter->mMode, Nif::RC_NiAlphaAccumulator); break; } case Nif::NiSortAdjustNode::SortingMode_Off: { setBin_Traversal(stateset); break; } case Nif::NiSortAdjustNode::SortingMode_Subsort: { assignBin(mPushedSorter->mMode, mPushedSorter->mSubSorter->recType); break; } } } }; osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager) { LoaderImpl impl(file->getFilename(), file->getVersion(), file->getUserVersion(), file->getBethVersion()); return impl.load(file, imageManager); } void Loader::loadKf(Nif::NIFFilePtr kf, SceneUtil::KeyframeHolder& target) { LoaderImpl impl(kf->getFilename(), kf->getVersion(), kf->getUserVersion(), kf->getBethVersion()); impl.loadKf(kf, target); } } openmw-openmw-0.48.0/components/nifosg/nifloader.hpp000066400000000000000000000043701445372753700225660ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_LOADER #define OPENMW_COMPONENTS_NIFOSG_LOADER #include #include #include #include #include #include "controller.hpp" namespace osg { class Node; } namespace Resource { class ImageManager; } namespace NifOsg { /// The main class responsible for loading NIF files into an OSG-Scenegraph. /// @par This scene graph is self-contained and can be cloned using osg::clone if desired. Particle emitters /// and programs hold a pointer to their ParticleSystem, which would need to be manually updated when cloning. class Loader { public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton if so. static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::NIFFilePtr kf, SceneUtil::KeyframeHolder& target); /// Set whether or not nodes marked as "MRK" should be shown. /// These should be hidden ingame, but visible in the editor. /// Default: false. static void setShowMarkers(bool show); static bool getShowMarkers(); /// Set the mask to use for hidden nodes. The default is 0, i.e. updates to those nodes can no longer happen. /// If you need to run animations or physics for hidden nodes, you may want to set this to a non-zero mask and remove exactly that mask from the camera's cull mask. static void setHiddenNodeMask(unsigned int mask); static unsigned int getHiddenNodeMask(); // Set the mask to use for nodes that ignore the crosshair intersection. The default is the default node mask. // This is used for NiCollisionSwitch nodes with NiCollisionSwitch state set to disabled. static void setIntersectionDisabledNodeMask(unsigned int mask); static unsigned int getIntersectionDisabledNodeMask(); private: static unsigned int sHiddenNodeMask; static unsigned int sIntersectionDisabledNodeMask; static bool sShowMarkers; }; } #endif openmw-openmw-0.48.0/components/nifosg/particle.cpp000066400000000000000000000510351445372753700224210ustar00rootroot00000000000000#include "particle.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace { class FindFirstGeometry : public osg::NodeVisitor { public: FindFirstGeometry() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mGeometry(nullptr) { } void apply(osg::Node& node) override { if (mGeometry) return; traverse(node); } void apply(osg::Drawable& drawable) override { if (auto morph = dynamic_cast(&drawable)) { mGeometry = morph->getSourceGeometry(); return; } else if (auto rig = dynamic_cast(&drawable)) { mGeometry = rig->getSourceGeometry(); return; } traverse(drawable); } void apply(osg::Geometry& geometry) override { mGeometry = &geometry; } osg::Geometry* mGeometry; }; class LocalToWorldAccumulator : public osg::NodeVisitor { public: LocalToWorldAccumulator(osg::Matrix& matrix) : osg::NodeVisitor(), mMatrix(matrix) {} virtual void apply(osg::Transform& transform) { if (&transform != mLastAppliedTransform) { mLastAppliedTransform = &transform; mLastMatrix = mMatrix; } transform.computeLocalToWorldMatrix(mMatrix, this); } void accumulate(const osg::NodePath& path) { if (path.empty()) return; size_t i = path.size(); for (auto rit = path.rbegin(); rit != path.rend(); rit++, --i) { const osg::Camera* camera = (*rit)->asCamera(); if (camera && (camera->getReferenceFrame() != osg::Transform::RELATIVE_RF || camera->getParents().empty())) break; } for(; i < path.size(); ++i) path[i]->accept(*this); } osg::Matrix& mMatrix; std::optional mLastMatrix; osg::Transform* mLastAppliedTransform = nullptr; }; } namespace NifOsg { ParticleSystem::ParticleSystem() : osgParticle::ParticleSystem() , mQuota(std::numeric_limits::max()) { mNormalArray = new osg::Vec3Array(1); mNormalArray->setBinding(osg::Array::BIND_OVERALL); (*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3); } ParticleSystem::ParticleSystem(const ParticleSystem ©, const osg::CopyOp ©op) : osgParticle::ParticleSystem(copy, copyop) , mQuota(copy.mQuota) { mNormalArray = new osg::Vec3Array(1); mNormalArray->setBinding(osg::Array::BIND_OVERALL); (*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3); // For some reason the osgParticle constructor doesn't copy the particles for (int i=0;iassignNormalArrayDispatcher(); state.getCurrentVertexArrayState()->setNormalArray(state, mNormalArray); } else { state.getAttributeDispatchers().activateNormalArray(mNormalArray); } osgParticle::ParticleSystem::drawImplementation(renderInfo); } void InverseWorldMatrix::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { osg::NodePath path = nv->getNodePath(); path.pop_back(); osg::Matrix mat = osg::computeLocalToWorld( path ); mat.orthoNormalize(mat); // don't undo the scale mat.invert(mat); node->setMatrix(mat); traverse(node,nv); } ParticleShooter::ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle, float verticalDir, float verticalAngle, float lifetime, float lifetimeRandom) : mMinSpeed(minSpeed), mMaxSpeed(maxSpeed), mHorizontalDir(horizontalDir) , mHorizontalAngle(horizontalAngle), mVerticalDir(verticalDir), mVerticalAngle(verticalAngle) , mLifetime(lifetime), mLifetimeRandom(lifetimeRandom) { } ParticleShooter::ParticleShooter() : mMinSpeed(0.f), mMaxSpeed(0.f), mHorizontalDir(0.f) , mHorizontalAngle(0.f), mVerticalDir(0.f), mVerticalAngle(0.f) , mLifetime(0.f), mLifetimeRandom(0.f) { } ParticleShooter::ParticleShooter(const ParticleShooter ©, const osg::CopyOp ©op) : osgParticle::Shooter(copy, copyop) { mMinSpeed = copy.mMinSpeed; mMaxSpeed = copy.mMaxSpeed; mHorizontalDir = copy.mHorizontalDir; mHorizontalAngle = copy.mHorizontalAngle; mVerticalDir = copy.mVerticalDir; mVerticalAngle = copy.mVerticalAngle; mLifetime = copy.mLifetime; mLifetimeRandom = copy.mLifetimeRandom; } void ParticleShooter::shoot(osgParticle::Particle *particle) const { float hdir = mHorizontalDir + mHorizontalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); float vdir = mVerticalDir + mVerticalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); osg::Vec3f dir = (osg::Quat(vdir, osg::Vec3f(0,1,0)) * osg::Quat(hdir, osg::Vec3f(0,0,1))) * osg::Vec3f(0,0,1); float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * Misc::Rng::rollClosedProbability(); particle->setVelocity(dir * vel); // Not supposed to set this here, but there doesn't seem to be a better way of doing it particle->setLifeTime(std::max(std::numeric_limits::epsilon(), mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability())); } GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime) : mGrowTime(growTime) , mFadeTime(fadeTime) , mCachedDefaultSize(0.f) { } GrowFadeAffector::GrowFadeAffector() : mGrowTime(0.f) , mFadeTime(0.f) , mCachedDefaultSize(0.f) { } GrowFadeAffector::GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop) : osgParticle::Operator(copy, copyop) { mGrowTime = copy.mGrowTime; mFadeTime = copy.mFadeTime; mCachedDefaultSize = copy.mCachedDefaultSize; } void GrowFadeAffector::beginOperate(osgParticle::Program *program) { mCachedDefaultSize = program->getParticleSystem()->getDefaultParticleTemplate().getSizeRange().minimum; } void GrowFadeAffector::operate(osgParticle::Particle* particle, double /* dt */) { float size = mCachedDefaultSize; if (particle->getAge() < mGrowTime && mGrowTime != 0.f) size *= particle->getAge() / mGrowTime; if (particle->getLifeTime() - particle->getAge() < mFadeTime && mFadeTime != 0.f) size *= (particle->getLifeTime() - particle->getAge()) / mFadeTime; particle->setSizeRange(osgParticle::rangef(size, size)); } ParticleColorAffector::ParticleColorAffector(const Nif::NiColorData *clrdata) : mData(clrdata->mKeyMap, osg::Vec4f(1,1,1,1)) { } ParticleColorAffector::ParticleColorAffector() { } ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector ©, const osg::CopyOp ©op) : osgParticle::Operator(copy, copyop) { mData = copy.mData; } void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */) { assert(particle->getLifeTime() > 0); float time = static_cast(particle->getAge()/particle->getLifeTime()); osg::Vec4f color = mData.interpKey(time); float alpha = color.a(); color.a() = 1.0f; particle->setColorRange(osgParticle::rangev4(color, color)); particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); } GravityAffector::GravityAffector(const Nif::NiGravity *gravity) : mForce(gravity->mForce) , mType(static_cast(gravity->mType)) , mPosition(gravity->mPosition) , mDirection(gravity->mDirection) , mDecay(gravity->mDecay) { } GravityAffector::GravityAffector() : mForce(0), mType(Type_Wind), mDecay(0.f) { } GravityAffector::GravityAffector(const GravityAffector ©, const osg::CopyOp ©op) : osgParticle::Operator(copy, copyop) { mForce = copy.mForce; mType = copy.mType; mPosition = copy.mPosition; mDirection = copy.mDirection; mDecay = copy.mDecay; mCachedWorldPosition = copy.mCachedWorldPosition; mCachedWorldDirection = copy.mCachedWorldDirection; } void GravityAffector::beginOperate(osgParticle::Program* program) { bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); if (mType == Type_Point || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; mCachedWorldDirection.normalize(); } void GravityAffector::operate(osgParticle::Particle *particle, double dt) { const float magic = 1.6f; switch (mType) { case Type_Wind: { float decayFactor = 1.f; if (mDecay != 0.f) { osg::Plane gravityPlane(mCachedWorldDirection, mCachedWorldPosition); float distance = std::abs(gravityPlane.distance(particle->getPosition())); decayFactor = std::exp(-1.f * mDecay * distance); } particle->addVelocity(mCachedWorldDirection * mForce * dt * decayFactor * magic); break; } case Type_Point: { osg::Vec3f diff = mCachedWorldPosition - particle->getPosition(); float decayFactor = 1.f; if (mDecay != 0.f) decayFactor = std::exp(-1.f * mDecay * diff.length()); diff.normalize(); particle->addVelocity(diff * mForce * dt * decayFactor * magic); break; } } } Emitter::Emitter() : osgParticle::Emitter() , mFlags(0) , mGeometryEmitterTarget(std::nullopt) { } Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) : osgParticle::Emitter(copy, copyop) , mTargets(copy.mTargets) , mPlacer(copy.mPlacer) , mShooter(copy.mShooter) // need a deep copy because the remainder is stored in the object , mCounter(static_cast(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL))) , mFlags(copy.mFlags) , mGeometryEmitterTarget(copy.mGeometryEmitterTarget) , mCachedGeometryEmitter(copy.mCachedGeometryEmitter) { } Emitter::Emitter(const std::vector &targets) : mTargets(targets) , mFlags(0) , mGeometryEmitterTarget(std::nullopt) { } void Emitter::emitParticles(double dt) { int n = mCounter->numParticlesToCreate(dt); if (n == 0) return; osg::Matrix worldToPs; // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths(); if (!partsysNodePaths.empty()) { osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]); worldToPs = osg::Matrix::inverse(psToWorld); } const osg::Matrix& ltw = getLocalToWorldMatrix(); osg::Matrix emitterToPs = ltw * worldToPs; osg::ref_ptr geometryVertices = nullptr; const bool useGeometryEmitter = mFlags & Nif::NiParticleSystemController::BSPArrayController_AtVertex; if (useGeometryEmitter || !mTargets.empty()) { int recIndex; if (useGeometryEmitter) { if (!mGeometryEmitterTarget.has_value()) return; recIndex = mGeometryEmitterTarget.value(); } else { int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); recIndex = mTargets[randomIndex]; } // we could use a map here for faster lookup FindGroupByRecIndex visitor(recIndex); getParent(0)->accept(visitor); if (!visitor.mFound) { Log(Debug::Info) << "Can't find emitter node" << recIndex; return; } if (useGeometryEmitter) { if (!mCachedGeometryEmitter.lock(geometryVertices)) { FindFirstGeometry geometryVisitor; visitor.mFound->accept(geometryVisitor); if (geometryVisitor.mGeometry) { if (auto* vertices = dynamic_cast(geometryVisitor.mGeometry->getVertexArray())) { mCachedGeometryEmitter = osg::observer_ptr(vertices); geometryVertices = vertices; } } } } osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); if (!useGeometryEmitter && (mFlags & Nif::NiParticleSystemController::BSPArrayController_AtNode) && path.size()) { osg::Matrix current; LocalToWorldAccumulator accum(current); accum.accumulate(path); osg::Matrix parent = accum.mLastMatrix.value_or(current); auto p1 = parent.getTrans(); auto p2 = current.getTrans(); current.setTrans((p2 - p1) * Misc::Rng::rollClosedProbability() + p1); emitterToPs = current * emitterToPs; } else { emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; } } emitterToPs.orthoNormalize(emitterToPs); if (useGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty())) return; for (int i=0; icreateParticle(nullptr); if (P) { if (useGeometryEmitter) P->setPosition((*geometryVertices)[Misc::Rng::rollDice(geometryVertices->getNumElements())]); else if (mPlacer) mPlacer->place(P); mShooter->shoot(P); P->transformPositionVelocity(emitterToPs); } } } FindGroupByRecIndex::FindGroupByRecIndex(unsigned int recIndex) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mFound(nullptr) , mRecIndex(recIndex) { } void FindGroupByRecIndex::apply(osg::Node &node) { applyNode(node); } void FindGroupByRecIndex::apply(osg::MatrixTransform &node) { applyNode(node); } void FindGroupByRecIndex::apply(osg::Geometry &node) { applyNode(node); } void FindGroupByRecIndex::applyNode(osg::Node &searchNode) { unsigned int recIndex; if (searchNode.getUserValue("recIndex", recIndex) && mRecIndex == recIndex) { osg::Group* group = searchNode.asGroup(); if (!group) group = searchNode.getParent(0); mFound = group; mFoundPath = getNodePath(); return; } traverse(searchNode); } PlanarCollider::PlanarCollider(const Nif::NiPlanarCollider *collider) : mBounceFactor(collider->mBounceFactor) , mExtents(collider->mExtents) , mPosition(collider->mPosition) , mXVector(collider->mXVector) , mYVector(collider->mYVector) , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance) { } PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp ©op) : osgParticle::Operator(copy, copyop) , mBounceFactor(copy.mBounceFactor) , mExtents(copy.mExtents) , mPosition(copy.mPosition) , mPositionInParticleSpace(copy.mPositionInParticleSpace) , mXVector(copy.mXVector) , mXVectorInParticleSpace(copy.mXVectorInParticleSpace) , mYVector(copy.mYVector) , mYVectorInParticleSpace(copy.mYVectorInParticleSpace) , mPlane(copy.mPlane) , mPlaneInParticleSpace(copy.mPlaneInParticleSpace) { } void PlanarCollider::beginOperate(osgParticle::Program *program) { mPositionInParticleSpace = mPosition; mPlaneInParticleSpace = mPlane; mXVectorInParticleSpace = mXVector; mYVectorInParticleSpace = mYVector; if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF) { mPositionInParticleSpace = program->transformLocalToWorld(mPosition); mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix()); mXVectorInParticleSpace = program->rotateLocalToWorld(mXVector); mYVectorInParticleSpace = program->rotateLocalToWorld(mYVector); } } void PlanarCollider::operate(osgParticle::Particle *particle, double dt) { // Does the particle in question move towards the collider? float velDotProduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal(); if (velDotProduct <= 0) return; // Does it intersect the collider's plane? osg::BoundingSphere bs(particle->getPosition(), 0.f); if (mPlaneInParticleSpace.intersect(bs) != 1) return; // Is it inside the collider's bounds? osg::Vec3f relativePos = particle->getPosition() - mPositionInParticleSpace; float xDotProduct = relativePos * mXVectorInParticleSpace; float yDotProduct = relativePos * mYVectorInParticleSpace; if (-mExtents.x() * 0.5f > xDotProduct || mExtents.x() * 0.5f < xDotProduct) return; if (-mExtents.y() * 0.5f > yDotProduct || mExtents.y() * 0.5f < yDotProduct) return; // Deflect the particle osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * velDotProduct); reflectedVelocity *= mBounceFactor; particle->setVelocity(reflectedVelocity); } SphericalCollider::SphericalCollider(const Nif::NiSphericalCollider* collider) : mBounceFactor(collider->mBounceFactor), mSphere(collider->mCenter, collider->mRadius) { } SphericalCollider::SphericalCollider() : mBounceFactor(1.0f) { } SphericalCollider::SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop) : osgParticle::Operator(copy, copyop) , mBounceFactor(copy.mBounceFactor) , mSphere(copy.mSphere) , mSphereInParticleSpace(copy.mSphereInParticleSpace) { } void SphericalCollider::beginOperate(osgParticle::Program* program) { mSphereInParticleSpace = mSphere; if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF) mSphereInParticleSpace.center() = program->transformLocalToWorld(mSphereInParticleSpace.center()); } void SphericalCollider::operate(osgParticle::Particle* particle, double dt) { osg::Vec3f cent = (particle->getPosition() - mSphereInParticleSpace.center()); // vector from sphere center to particle bool insideSphere = cent.length2() <= mSphereInParticleSpace.radius2(); if (insideSphere || (cent * particle->getVelocity() < 0.0f)) // if outside, make sure the particle is flying towards the sphere { // Collision test (finding point of contact) is performed by solving a quadratic equation: // ||vec(cent) + vec(vel)*k|| = R /^2 // k^2 + 2*k*(vec(cent)*vec(vel))/||vec(vel)||^2 + (||vec(cent)||^2 - R^2)/||vec(vel)||^2 = 0 float b = -(cent * particle->getVelocity()) / particle->getVelocity().length2(); osg::Vec3f u = cent + particle->getVelocity() * b; if (insideSphere || (u.length2() < mSphereInParticleSpace.radius2())) { float d = (mSphereInParticleSpace.radius2() - u.length2()) / particle->getVelocity().length2(); float k = insideSphere ? (std::sqrt(d) + b) : (b - std::sqrt(d)); if (k < dt) { // collision detected; reflect off the tangent plane osg::Vec3f contact = particle->getPosition() + particle->getVelocity() * k; osg::Vec3 normal = (contact - mSphereInParticleSpace.center()); normal.normalize(); float dotproduct = particle->getVelocity() * normal; osg::Vec3 reflectedVelocity = particle->getVelocity() - normal * (2 * dotproduct); reflectedVelocity *= mBounceFactor; particle->setVelocity(reflectedVelocity); } } } } } openmw-openmw-0.48.0/components/nifosg/particle.hpp000066400000000000000000000205041445372753700224230ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #include #include #include #include #include #include #include #include #include "controller.hpp" // ValueInterpolator namespace Nif { struct NiGravity; struct NiPlanarCollider; struct NiSphericalCollider; struct NiColorData; } namespace NifOsg { // Subclass ParticleSystem to support a limit on the number of active particles. class ParticleSystem : public osgParticle::ParticleSystem { public: ParticleSystem(); ParticleSystem(const ParticleSystem& copy, const osg::CopyOp& copyop); META_Object(NifOsg, ParticleSystem) osgParticle::Particle* createParticle(const osgParticle::Particle *ptemplate) override; void setQuota(int quota); void drawImplementation(osg::RenderInfo& renderInfo) const override; private: int mQuota; osg::ref_ptr mNormalArray; }; // HACK: Particle doesn't allow setting the initial age, but we need this for loading the particle system state class ParticleAgeSetter : public osgParticle::Particle { public: ParticleAgeSetter(float age) : Particle() { _t0 = age; } }; // Node callback used to set the inverse of the parent's world matrix on the MatrixTransform // that the callback is attached to. Used for certain particle systems, // so that the particles do not move with the node they are attached to. class InverseWorldMatrix : public SceneUtil::NodeCallback { public: InverseWorldMatrix() { } InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& copyop) : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, InverseWorldMatrix) void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); }; class ParticleShooter : public osgParticle::Shooter { public: ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle, float verticalDir, float verticalAngle, float lifetime, float lifetimeRandom); ParticleShooter(); ParticleShooter(const ParticleShooter& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); ParticleShooter& operator=(const ParticleShooter&) = delete; META_Object(NifOsg, ParticleShooter) void shoot(osgParticle::Particle* particle) const override; private: float mMinSpeed; float mMaxSpeed; float mHorizontalDir; float mHorizontalAngle; float mVerticalDir; float mVerticalAngle; float mLifetime; float mLifetimeRandom; }; class PlanarCollider : public osgParticle::Operator { public: PlanarCollider(const Nif::NiPlanarCollider* collider); PlanarCollider() = default; PlanarCollider(const PlanarCollider& copy, const osg::CopyOp& copyop); META_Object(NifOsg, PlanarCollider) void beginOperate(osgParticle::Program* program) override; void operate(osgParticle::Particle* particle, double dt) override; private: float mBounceFactor{0.f}; osg::Vec2f mExtents; osg::Vec3f mPosition, mPositionInParticleSpace; osg::Vec3f mXVector, mXVectorInParticleSpace; osg::Vec3f mYVector, mYVectorInParticleSpace; osg::Plane mPlane, mPlaneInParticleSpace; }; class SphericalCollider : public osgParticle::Operator { public: SphericalCollider(const Nif::NiSphericalCollider* collider); SphericalCollider(); SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop); META_Object(NifOsg, SphericalCollider) void beginOperate(osgParticle::Program* program) override; void operate(osgParticle::Particle* particle, double dt) override; private: float mBounceFactor; osg::BoundingSphere mSphere; osg::BoundingSphere mSphereInParticleSpace; }; class GrowFadeAffector : public osgParticle::Operator { public: GrowFadeAffector(float growTime, float fadeTime); GrowFadeAffector(); GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); GrowFadeAffector& operator=(const GrowFadeAffector&) = delete; META_Object(NifOsg, GrowFadeAffector) void beginOperate(osgParticle::Program* program) override; void operate(osgParticle::Particle* particle, double dt) override; private: float mGrowTime; float mFadeTime; float mCachedDefaultSize; }; class ParticleColorAffector : public osgParticle::Operator { public: ParticleColorAffector(const Nif::NiColorData* clrdata); ParticleColorAffector(); ParticleColorAffector(const ParticleColorAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); ParticleColorAffector& operator=(const ParticleColorAffector&) = delete; META_Object(NifOsg, ParticleColorAffector) void operate(osgParticle::Particle* particle, double dt) override; private: Vec4Interpolator mData; }; class GravityAffector : public osgParticle::Operator { public: GravityAffector(const Nif::NiGravity* gravity); GravityAffector(); GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); GravityAffector& operator=(const GravityAffector&) = delete; META_Object(NifOsg, GravityAffector) void operate(osgParticle::Particle* particle, double dt) override; void beginOperate(osgParticle::Program *) override ; private: float mForce; enum ForceType { Type_Wind, Type_Point }; ForceType mType; osg::Vec3f mPosition; osg::Vec3f mDirection; float mDecay; osg::Vec3f mCachedWorldPosition; osg::Vec3f mCachedWorldDirection; }; // NodeVisitor to find a Group node with the given record index, stored in the node's user data container. // Alternatively, returns the node's parent Group if that node is not a Group (i.e. a leaf node). class FindGroupByRecIndex : public osg::NodeVisitor { public: FindGroupByRecIndex(unsigned int recIndex); void apply(osg::Node &node) override; // Technically not required as the default implementation would trickle down to apply(Node&) anyway, // but we'll shortcut instead to avoid the chain of virtual function calls void apply(osg::MatrixTransform& node) override; void apply(osg::Geometry& node) override; void applyNode(osg::Node& searchNode); osg::Group* mFound; osg::NodePath mFoundPath; private: unsigned int mRecIndex; }; // Subclass emitter to support randomly choosing one of the child node's transforms for the emit position of new particles. class Emitter : public osgParticle::Emitter { public: Emitter(const std::vector& targets); Emitter(); Emitter(const Emitter& copy, const osg::CopyOp& copyop); META_Object(NifOsg, Emitter) void emitParticles(double dt) override; void setShooter(osgParticle::Shooter* shooter) { mShooter = shooter; } void setPlacer(osgParticle::Placer* placer) { mPlacer = placer; } void setCounter(osgParticle::Counter* counter) { mCounter = counter;} void setGeometryEmitterTarget(std::optional recIndex) { mGeometryEmitterTarget = recIndex; } void setFlags(int flags) { mFlags = flags; } private: // NIF Record indices std::vector mTargets; osg::ref_ptr mPlacer; osg::ref_ptr mShooter; osg::ref_ptr mCounter; int mFlags; std::optional mGeometryEmitterTarget; osg::observer_ptr mCachedGeometryEmitter; }; } #endif openmw-openmw-0.48.0/components/platform/000077500000000000000000000000001445372753700204455ustar00rootroot00000000000000openmw-openmw-0.48.0/components/platform/file.hpp000066400000000000000000000030611445372753700220750ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_PLATFORM_FILE_HPP #define OPENMW_COMPONENTS_PLATFORM_FILE_HPP #include #include #include namespace Platform::File { enum class Handle : intptr_t { Invalid = -1 }; enum class SeekType { Begin, Current, End }; Handle open(const char* filename); void close(Handle handle); size_t size(Handle handle); void seek(Handle handle, size_t Position, SeekType type = SeekType::Begin); size_t tell(Handle handle); size_t read(Handle handle, void* data, size_t size); class ScopedHandle { Handle mHandle{ Handle::Invalid }; public: ScopedHandle() noexcept = default; ScopedHandle(ScopedHandle& other) = delete; ScopedHandle(Handle handle) noexcept : mHandle(handle) {} ScopedHandle(ScopedHandle&& other) noexcept : mHandle(other.mHandle) { other.mHandle = Handle::Invalid; } ScopedHandle& operator=(const ScopedHandle& other) = delete; ScopedHandle& operator=(ScopedHandle&& other) noexcept { if (mHandle != Handle::Invalid) close(mHandle); mHandle = other.mHandle; other.mHandle = Handle::Invalid; return *this; } ~ScopedHandle() { if(mHandle != Handle::Invalid) close(mHandle); } operator Handle() const { return mHandle; } }; } #endif // OPENMW_COMPONENTS_PLATFORM_FILE_HPP openmw-openmw-0.48.0/components/platform/file.posix.cpp000066400000000000000000000050321445372753700232310ustar00rootroot00000000000000#include "file.hpp" #include #include #include #include #include #include #include #include namespace Platform::File { static auto getNativeHandle(Handle handle) { assert(handle != Handle::Invalid); return static_cast(handle); } static int getNativeSeekType(SeekType seek) { if (seek == SeekType::Begin) return SEEK_SET; if (seek == SeekType::Current) return SEEK_CUR; if (seek == SeekType::End) return SEEK_END; return -1; } Handle open(const char* filename) { #ifdef O_BINARY static const int openFlags = O_RDONLY | O_BINARY; #else static const int openFlags = O_RDONLY; #endif auto handle = ::open(filename, openFlags, 0); if (handle == -1) { throw std::runtime_error(std::string("Failed to open '") + filename + "' for reading: " + strerror(errno)); } return static_cast(handle); } void close(Handle handle) { auto nativeHandle = getNativeHandle(handle); ::close(nativeHandle); } void seek(Handle handle, size_t position, SeekType type /*= SeekType::Begin*/) { const auto nativeHandle = getNativeHandle(handle); const auto nativeSeekType = getNativeSeekType(type); if (::lseek(nativeHandle, position, nativeSeekType) == -1) { throw std::runtime_error("An lseek() call failed: " + std::string(strerror(errno))); } } size_t size(Handle handle) { const auto oldPos = tell(handle); seek(handle, 0, SeekType::End); const auto fileSize = tell(handle); seek(handle, oldPos, SeekType::Begin); return static_cast(fileSize); } size_t tell(Handle handle) { auto nativeHandle = getNativeHandle(handle); size_t position = ::lseek(nativeHandle, 0, SEEK_CUR); if (position == size_t(-1)) { throw std::runtime_error("An lseek() call failed: " + std::string(strerror(errno))); } return position; } size_t read(Handle handle, void* data, size_t size) { auto nativeHandle = getNativeHandle(handle); int amount = ::read(nativeHandle, data, size); if (amount == -1) { throw std::runtime_error("An attempt to read " + std::to_string(size) + " bytes failed: " + strerror(errno)); } return amount; } } openmw-openmw-0.48.0/components/platform/file.stdio.cpp000066400000000000000000000047711445372753700232220ustar00rootroot00000000000000#include "file.hpp" #include #include #include #include #include namespace Platform::File { static auto getNativeHandle(Handle handle) { assert(handle != Handle::Invalid); return reinterpret_cast(static_cast(handle)); } static int getNativeSeekType(SeekType seek) { if (seek == SeekType::Begin) return SEEK_SET; if (seek == SeekType::Current) return SEEK_CUR; if (seek == SeekType::End) return SEEK_END; return -1; } Handle open(const char* filename) { FILE* handle = fopen(filename, "rb"); if (handle == nullptr) { throw std::runtime_error(std::string("Failed to open '") + filename + "' for reading: " + strerror(errno)); } return static_cast(reinterpret_cast(handle)); } void close(Handle handle) { auto nativeHandle = getNativeHandle(handle); fclose(nativeHandle); } void seek(Handle handle, size_t position, SeekType type /*= SeekType::Begin*/) { const auto nativeHandle = getNativeHandle(handle); const auto nativeSeekType = getNativeSeekType(type); if (fseek(nativeHandle, position, nativeSeekType) != 0) { throw std::runtime_error(std::string("An fseek() call failed: ") + strerror(errno)); } } size_t size(Handle handle) { auto nativeHandle = getNativeHandle(handle); const auto oldPos = tell(handle); seek(handle, 0, SeekType::End); const auto fileSize = tell(handle); seek(handle, oldPos, SeekType::Begin); return static_cast(fileSize); } size_t tell(Handle handle) { auto nativeHandle = getNativeHandle(handle); long position = ftell(nativeHandle); if (position == -1) { throw std::runtime_error(std::string("An ftell() call failed: ") + strerror(errno)); } return static_cast(position); } size_t read(Handle handle, void* data, size_t size) { auto nativeHandle = getNativeHandle(handle); int amount = fread(data, 1, size, nativeHandle); if (amount == 0 && ferror(nativeHandle)) { throw std::runtime_error(std::string("An attempt to read ") + std::to_string(size) + " bytes failed: " + strerror(errno)); } return static_cast(amount); } } openmw-openmw-0.48.0/components/platform/file.win32.cpp000066400000000000000000000057221445372753700230370ustar00rootroot00000000000000#include "file.hpp" #include #include #include #include #include namespace Platform::File { static auto getNativeHandle(Handle handle) { assert(handle != Handle::Invalid); return reinterpret_cast(static_cast(handle)); } static int getNativeSeekType(SeekType seek) { if (seek == SeekType::Begin) return FILE_BEGIN; if (seek == SeekType::Current) return FILE_CURRENT; if (seek == SeekType::End) return FILE_END; return -1; } Handle open(const char* filename) { std::wstring wname = boost::locale::conv::utf_to_utf(filename); HANDLE handle = CreateFileW(wname.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if (handle == INVALID_HANDLE_VALUE) { throw std::runtime_error(std::string("Failed to open '") + filename + "' for reading: " + std::to_string(GetLastError())); } return static_cast(reinterpret_cast(handle)); } void close(Handle handle) { auto nativeHandle = getNativeHandle(handle); CloseHandle(nativeHandle); } void seek(Handle handle, size_t position, SeekType type /*= SeekType::Begin*/) { const auto nativeHandle = getNativeHandle(handle); const auto nativeSeekType = getNativeSeekType(type); LARGE_INTEGER li; li.QuadPart = position; if (!SetFilePointerEx(nativeHandle, li, nullptr, nativeSeekType)) { if (auto errCode = GetLastError(); errCode != ERROR_SUCCESS) { throw std::runtime_error(std::string("An fseek() call failed: ") + std::to_string(errCode)); } } } size_t size(Handle handle) { auto nativeHandle = getNativeHandle(handle); BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(nativeHandle, &info)) throw std::runtime_error("A query operation on a file failed."); if (info.nFileSizeHigh != 0) throw std::runtime_error("Files greater that 4GB are not supported."); return info.nFileSizeLow; } size_t tell(Handle handle) { auto nativeHandle = getNativeHandle(handle); DWORD value = SetFilePointer(nativeHandle, 0, nullptr, SEEK_CUR); if (value == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) throw std::runtime_error("A query operation on a file failed."); return value; } size_t read(Handle handle, void* data, size_t size) { auto nativeHandle = getNativeHandle(handle); DWORD bytesRead{}; if (!ReadFile(nativeHandle, data, static_cast(size), &bytesRead, nullptr)) throw std::runtime_error(std::string("A read operation on a file failed: ") + std::to_string(GetLastError())); return bytesRead; } } openmw-openmw-0.48.0/components/platform/platform.cpp000066400000000000000000000007161445372753700230010ustar00rootroot00000000000000#include "platform.hpp" #include namespace Platform { static void increaseFileHandleLimit() { #ifdef WIN32 // Increase limit for open files at the stream I/O level, see // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170#remarks _setmaxstdio(8192); #else // No-op on any other platform. #endif } void init() { increaseFileHandleLimit(); } } openmw-openmw-0.48.0/components/platform/platform.hpp000066400000000000000000000002761445372753700230070ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_PLATFORM_PLATFORM_HPP #define OPENMW_COMPONENTS_PLATFORM_PLATFORM_HPP namespace Platform { void init(); } #endif // OPENMW_COMPONENTS_PLATFORM_PLATFORM_HPP openmw-openmw-0.48.0/components/process/000077500000000000000000000000001445372753700202775ustar00rootroot00000000000000openmw-openmw-0.48.0/components/process/processinvoker.cpp000066400000000000000000000150341445372753700240620ustar00rootroot00000000000000#include "processinvoker.hpp" #include #include #include #include #if defined(Q_OS_MAC) #include #endif Process::ProcessInvoker::ProcessInvoker(QObject* parent) : QObject(parent) { mProcess = new QProcess(this); connect(mProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); mName = QString(); mArguments = QStringList(); } Process::ProcessInvoker::~ProcessInvoker() { } //void Process::ProcessInvoker::setProcessName(const QString &name) //{ // mName = name; //} //void Process::ProcessInvoker::setProcessArguments(const QStringList &arguments) //{ // mArguments = arguments; //} QProcess* Process::ProcessInvoker::getProcess() { return mProcess; } //QString Process::ProcessInvoker::getProcessName() //{ // return mName; //} //QStringList Process::ProcessInvoker::getProcessArguments() //{ // return mArguments; //} bool Process::ProcessInvoker::startProcess(const QString &name, const QStringList &arguments, bool detached) { // mProcess = new QProcess(this); mName = name; mArguments = arguments; mIgnoreErrors = false; QString path(name); #ifdef Q_OS_WIN path.append(QLatin1String(".exe")); #elif defined(Q_OS_MAC) QDir dir(QCoreApplication::applicationDirPath()); path = dir.absoluteFilePath(name); #else path.prepend(QLatin1String("./")); #endif QFileInfo info(path); if (!info.exists()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not find %1

\

The application is not found.

\

Please make sure OpenMW is installed correctly and try again.

").arg(info.fileName())); msgBox.exec(); return false; } if (!info.isExecutable()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not start %1

\

The application is not executable.

\

Please make sure you have the right permissions and try again.

").arg(info.fileName())); msgBox.exec(); return false; } // Start the executable if (detached) { if (!mProcess->startDetached(path, arguments)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not start %1

\

An error occurred while starting %1.

\

Press \"Show Details...\" for more information.

").arg(info.fileName())); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); return false; } } else { mProcess->start(path, arguments); /* if (!mProcess->waitForFinished()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Could not start %1

\

An error occurred while starting %1.

\

Press \"Show Details...\" for more information.

").arg(info.fileName())); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); return false; } if (mProcess->exitCode() != 0 || mProcess->exitStatus() == QProcess::CrashExit) { QString error(mProcess->readAllStandardError()); error.append(tr("\nArguments:\n")); error.append(arguments.join(" ")); QMessageBox msgBox; msgBox.setWindowTitle(tr("Error running executable")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Executable %1 returned an error

\

An error occurred while running %1.

\

Press \"Show Details...\" for more information.

").arg(info.fileName())); msgBox.setDetailedText(error); msgBox.exec(); return false; } */ } return true; } void Process::ProcessInvoker::processError(QProcess::ProcessError error) { if (mIgnoreErrors) return; QMessageBox msgBox; msgBox.setWindowTitle(tr("Error running executable")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Executable %1 returned an error

\

An error occurred while running %1.

\

Press \"Show Details...\" for more information.

").arg(mName)); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); } void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) { if (mIgnoreErrors) return; QString error(mProcess->readAllStandardError()); error.append(tr("\nArguments:\n")); error.append(mArguments.join(" ")); QMessageBox msgBox; msgBox.setWindowTitle(tr("Error running executable")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("

Executable %1 returned an error

\

An error occurred while running %1.

\

Press \"Show Details...\" for more information.

").arg(mName)); msgBox.setDetailedText(error); msgBox.exec(); } } void Process::ProcessInvoker::killProcess() { mIgnoreErrors = true; mProcess->kill(); } openmw-openmw-0.48.0/components/process/processinvoker.hpp000066400000000000000000000023211445372753700240620ustar00rootroot00000000000000#ifndef PROCESSINVOKER_HPP #define PROCESSINVOKER_HPP #include #include #include namespace Process { class ProcessInvoker : public QObject { Q_OBJECT public: ProcessInvoker(QObject* parent = nullptr); ~ProcessInvoker(); // void setProcessName(const QString &name); // void setProcessArguments(const QStringList &arguments); QProcess* getProcess(); // QString getProcessName(); // QStringList getProcessArguments(); // inline bool startProcess(bool detached = false) { return startProcess(mName, mArguments, detached); } inline bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } bool startProcess(const QString &name, const QStringList &arguments, bool detached = false); void killProcess(); private: QProcess *mProcess; QString mName; QStringList mArguments; bool mIgnoreErrors = false; private slots: void processError(QProcess::ProcessError error); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); }; } #endif // PROCESSINVOKER_HPP openmw-openmw-0.48.0/components/resource/000077500000000000000000000000001445372753700204505ustar00rootroot00000000000000openmw-openmw-0.48.0/components/resource/animation.cpp000066400000000000000000000017671445372753700231460ustar00rootroot00000000000000#include #include #include namespace Resource { Animation::Animation(const Animation& anim, const osg::CopyOp& copyop): osg::Object(anim, copyop), mDuration(0.0f), mStartTime(0.0f) { const osgAnimation::ChannelList& channels = anim.getChannels(); for (const auto& channel: channels) addChannel(channel.get()->clone()); } void Animation::addChannel(osg::ref_ptr pChannel) { mChannels.push_back(pChannel); } std::vector>& Animation::getChannels() { return mChannels; } const std::vector>& Animation::getChannels() const { return mChannels; } bool Animation::update (double time) { for (const auto& channel: mChannels) { channel->update(time, 1.0f, 0); } return true; } } openmw-openmw-0.48.0/components/resource/animation.hpp000066400000000000000000000020311445372753700231340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP #define OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP #include #include #include #include namespace Resource { /// Stripped down class of osgAnimation::Animation, only needed for OSG's plugin formats like dae class Animation : public osg::Object { public: META_Object(Resource, Animation) Animation() : mDuration(0.0), mStartTime(0) {} Animation(const Animation&, const osg::CopyOp&); ~Animation() {} void addChannel (osg::ref_ptr pChannel); std::vector>& getChannels(); const std::vector>& getChannels() const; bool update (double time); protected: double mDuration; double mStartTime; std::vector> mChannels; }; } #endif openmw-openmw-0.48.0/components/resource/bulletshape.cpp000066400000000000000000000070441445372753700234710ustar00rootroot00000000000000#include "bulletshape.hpp" #include #include #include #include #include #include namespace Resource { namespace { CollisionShapePtr duplicateCollisionShape(const btCollisionShape *shape) { if (shape == nullptr) return nullptr; if (shape->isCompound()) { const btCompoundShape *comp = static_cast(shape); std::unique_ptr newShape(new btCompoundShape); for (int i = 0, n = comp->getNumChildShapes(); i < n; ++i) { auto child = duplicateCollisionShape(comp->getChildShape(i)); const btTransform& trans = comp->getChildTransform(i); newShape->addChildShape(trans, child.release()); } return newShape; } if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) { const btBvhTriangleMeshShape* trishape = static_cast(shape); return CollisionShapePtr(new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f))); } if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) { const btBoxShape* boxshape = static_cast(shape); return CollisionShapePtr(new btBoxShape(*boxshape)); } if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) return CollisionShapePtr(new btHeightfieldTerrainShape(static_cast(*shape))); throw std::logic_error(std::string("Unhandled Bullet shape duplication: ") + shape->getName()); } void deleteShape(btCollisionShape* shape) { if (shape->isCompound()) { btCompoundShape* compound = static_cast(shape); for (int i = 0, n = compound->getNumChildShapes(); i < n; i++) if (btCollisionShape* child = compound->getChildShape(i)) deleteShape(child); } delete shape; } } void DeleteCollisionShape::operator()(btCollisionShape* shape) const { deleteShape(shape); } BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape.get())) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape.get())) , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) , mFileName(copy.mFileName) , mFileHash(copy.mFileHash) { } void BulletShape::setLocalScaling(const btVector3& scale) { mCollisionShape->setLocalScaling(scale); if (mAvoidCollisionShape) mAvoidCollisionShape->setLocalScaling(scale); } osg::ref_ptr makeInstance(osg::ref_ptr source) { return {new BulletShapeInstance(std::move(source))}; } BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : mSource(std::move(source)) { mCollisionBox = mSource->mCollisionBox; mAnimatedShapes = mSource->mAnimatedShapes; mVisualCollisionType = mSource->mVisualCollisionType; mCollisionShape = duplicateCollisionShape(mSource->mCollisionShape.get()); mAvoidCollisionShape = duplicateCollisionShape(mSource->mAvoidCollisionShape.get()); } } openmw-openmw-0.48.0/components/resource/bulletshape.hpp000066400000000000000000000060651445372753700235000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #include #include #include #include #include #include #include class btCollisionShape; namespace NifBullet { class BulletNifLoader; } namespace Resource { struct DeleteCollisionShape { void operator()(btCollisionShape* shape) const; }; using CollisionShapePtr = std::unique_ptr; struct CollisionBox { osg::Vec3f mExtents; osg::Vec3f mCenter; }; enum class VisualCollisionType { None, Default, Camera }; struct BulletShape : public osg::Object { CollisionShapePtr mCollisionShape; CollisionShapePtr mAvoidCollisionShape; // Used for actors and projectiles. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. CollisionBox mCollisionBox; // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape // will be a btCompoundShape (which consists of one or more child shapes). // In this map, for each animated collision shape, // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; std::string mFileName; std::string mFileHash; VisualCollisionType mVisualCollisionType = VisualCollisionType::None; BulletShape() = default; BulletShape(const BulletShape& copy, const osg::CopyOp& copyop); META_Object(Resource, BulletShape) void setLocalScaling(const btVector3& scale); bool isAnimated() const { return !mAnimatedShapes.empty(); } }; // An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. // Vertex data is shallow-copied where possible. A ref_ptr to the original shape is held to keep vertex pointers intact. class BulletShapeInstance : public BulletShape { public: BulletShapeInstance(osg::ref_ptr source); const osg::ref_ptr& getSource() const { return mSource; } private: osg::ref_ptr mSource; }; osg::ref_ptr makeInstance(osg::ref_ptr source); // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface struct TriangleMeshShape : public btBvhTriangleMeshShape { TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression, bool buildBvh = true) : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression, buildBvh) { } virtual ~TriangleMeshShape() { delete getTriangleInfoMap(); delete m_meshInterface; } }; } #endif openmw-openmw-0.48.0/components/resource/bulletshapemanager.cpp000066400000000000000000000157551445372753700250340ustar00rootroot00000000000000#include "bulletshapemanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "bulletshape.hpp" #include "scenemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" #include "multiobjectcache.hpp" namespace Resource { struct GetTriangleFunctor { GetTriangleFunctor() : mTriMesh(nullptr) { } void setTriMesh(btTriangleMesh* triMesh) { mTriMesh = triMesh; } void setMatrix(const osg::Matrixf& matrix) { mMatrix = matrix; } inline btVector3 toBullet(const osg::Vec3f& vec) { return btVector3(vec.x(), vec.y(), vec.z()); } void inline operator()( const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3, bool _temp=false ) // Note: unused temp argument left here for OSG versions less than 3.5.6 { if (mTriMesh) mTriMesh->addTriangle( toBullet(mMatrix.preMult(v1)), toBullet(mMatrix.preMult(v2)), toBullet(mMatrix.preMult(v3))); } btTriangleMesh* mTriMesh; osg::Matrixf mMatrix; }; /// Creates a BulletShape out of a Node hierarchy. class NodeToShapeVisitor : public osg::NodeVisitor { public: NodeToShapeVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mTriangleMesh(nullptr) { } void apply(osg::Drawable &drawable) override { if (!mTriangleMesh) mTriangleMesh.reset(new btTriangleMesh); osg::Matrixf worldMat = osg::computeLocalToWorld(getNodePath()); osg::TriangleFunctor functor; functor.setTriMesh(mTriangleMesh.get()); functor.setMatrix(worldMat); drawable.accept(functor); } osg::ref_ptr getShape() { if (!mTriangleMesh) return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); auto triangleMeshShape = std::make_unique(mTriangleMesh.release(), true); btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); shape->mCollisionBox.mExtents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; shape->mCollisionBox.mExtents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; shape->mCollisionBox.mExtents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; shape->mCollisionBox.mCenter = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, (aabbMax[1] + aabbMin[1]) / 2.0f, (aabbMax[2] + aabbMin[2]) / 2.0f ); shape->mCollisionShape.reset(triangleMeshShape.release()); return shape; } private: std::unique_ptr mTriangleMesh; }; BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) : ResourceManager(vfs) , mInstanceCache(new MultiObjectCache) , mSceneManager(sceneMgr) , mNifFileManager(nifFileManager) { } BulletShapeManager::~BulletShapeManager() { } osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) shape = osg::ref_ptr(static_cast(obj.get())); else { if (Misc::getFileExtension(normalized) == "nif") { NifBullet::BulletNifLoader loader; shape = loader.load(*mNifFileManager->get(normalized)); } else { // TODO: support .bullet shape files osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor // Check first if there's a custom collision node unsigned int visitAllNodesMask = 0xffffffff; SceneUtil::FindByNameVisitor nameFinder("Collision"); nameFinder.setTraversalMask(visitAllNodesMask); nameFinder.setNodeMaskOverride(visitAllNodesMask); node->accept(nameFinder); if (nameFinder.mFoundNode) { NodeToShapeVisitor visitor; visitor.setTraversalMask(visitAllNodesMask); visitor.setNodeMaskOverride(visitAllNodesMask); nameFinder.mFoundNode->accept(visitor); shape = visitor.getShape(); } // Generate a collision shape from the mesh if (!shape) { NodeToShapeVisitor visitor; node->accept(visitor); shape = visitor.getShape(); if (!shape) return osg::ref_ptr(); } if (shape != nullptr) { shape->mFileName = normalized; constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash); } } mCache->addEntryToObjectCache(normalized, shape); } return shape; } osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) { const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr instance = createInstance(normalized); if (instance) mInstanceCache->addEntryToObjectCache(normalized, instance.get()); return instance; } osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) { const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) return static_cast(obj.get()); else return createInstance(normalized); } osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) { osg::ref_ptr shape = getShape(name); if (shape) return makeInstance(std::move(shape)); return osg::ref_ptr(); } void BulletShapeManager::updateCache(double referenceTime) { ResourceManager::updateCache(referenceTime); mInstanceCache->removeUnreferencedObjectsInCache(); } void BulletShapeManager::clearCache() { ResourceManager::clearCache(); mInstanceCache->clear(); } void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); } } openmw-openmw-0.48.0/components/resource/bulletshapemanager.hpp000066400000000000000000000037261445372753700250340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H #define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H #include #include #include #include "bulletshape.hpp" #include "resourcemanager.hpp" namespace Resource { class SceneManager; class NifFileManager; struct BulletShape; class BulletShapeInstance; class MultiObjectCache; /// Handles loading, caching and "instancing" of bullet shapes. /// A shape 'instance' is a clone of another shape, with the goal of setting a different scale on this instance. /// @note May be used from any thread. class BulletShapeManager : public ResourceManager { public: BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); ~BulletShapeManager(); /// @note May return a null pointer if the object has no shape. osg::ref_ptr getShape(const std::string& name); /// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can simply return /// the cached instance instead of having to create a new one. /// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long as needed. osg::ref_ptr cacheInstance(const std::string& name); /// @note May return a null pointer if the object has no shape. osg::ref_ptr getInstance(const std::string& name); /// @see ResourceManager::updateCache void updateCache(double referenceTime) override; void clearCache() override; void reportStats(unsigned int frameNumber, osg::Stats *stats) const override; private: osg::ref_ptr createInstance(const std::string& name); osg::ref_ptr mInstanceCache; SceneManager* mSceneManager; NifFileManager* mNifFileManager; }; } #endif openmw-openmw-0.48.0/components/resource/foreachbulletobject.cpp000066400000000000000000000143101445372753700251610ustar00rootroot00000000000000#include "foreachbulletobject.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Resource { namespace { struct CellRef { ESM::RecNameInts mType; ESM::RefNum mRefNum; std::string mRefId; float mScale; ESM::Position mPos; CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos) : mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {} }; ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId) { const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), refId, EsmLoader::LessById {}); if (it == esmData.mRefIdTypes.end() || it->mId != refId) return {}; return it->mType; } std::vector loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, ESM::ReadersCache& readers) { std::vector> cellRefs; for (std::size_t i = 0; i < cell.mContextList.size(); i++) { const ESM::ReadersCache::BusyItem reader = readers.get(static_cast(cell.mContextList[i].index)); cell.restore(*reader, static_cast(i)); ESM::CellRef cellRef; bool deleted = false; while (ESM::Cell::getNextRef(*reader, cellRef, deleted)) { Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); if (type == ESM::RecNameInts {}) continue; cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), cellRef.mScale, cellRef.mPos); } } Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; const auto getKey = [] (const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; std::vector result = prepareRecords(cellRefs, getKey); Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; return result; } template void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, ESM::ReadersCache& readers, F&& f) { std::vector cellRefs = loadCellRefs(cell, esmData, readers); Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; for (CellRef& cellRef : cellRefs) { std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); if (model.empty()) continue; if (cellRef.mType != ESM::REC_STAT) model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); osg::ref_ptr shape = [&] { try { return bulletShapeManager.getShape("meshes/" + model); } catch (const std::exception& e) { Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what(); return osg::ref_ptr(); } } (); if (shape == nullptr) continue; switch (cellRef.mType) { case ESM::REC_ACTI: case ESM::REC_CONT: case ESM::REC_DOOR: case ESM::REC_STAT: f(BulletObject {std::move(shape), cellRef.mPos, cellRef.mScale}); break; default: break; } } } } void forEachBulletObject(ESM::ReadersCache& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, std::function callback) { Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; for (std::size_t i = 0; i < esmData.mCells.size(); ++i) { const ESM::Cell& cell = esmData.mCells[i]; const bool exterior = cell.isExterior(); Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; std::size_t objects = 0; forEachObject(cell, esmData, vfs, bulletShapeManager, readers, [&] (const BulletObject& object) { callback(cell, object); ++objects; }); Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription() << " with " << objects << " objects"; } } } openmw-openmw-0.48.0/components/resource/foreachbulletobject.hpp000066400000000000000000000016561445372753700251770ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H #define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H #include #include #include #include #include #include namespace ESM { class ReadersCache; struct Cell; } namespace VFS { class Manager; } namespace Resource { class BulletShapeManager; } namespace EsmLoader { struct EsmData; } namespace Resource { struct BulletObject { osg::ref_ptr mShape; ESM::Position mPosition; float mScale; }; void forEachBulletObject(ESM::ReadersCache& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, std::function callback); } #endif openmw-openmw-0.48.0/components/resource/imagemanager.cpp000066400000000000000000000165171445372753700236030ustar00rootroot00000000000000#include "imagemanager.hpp" #include #include #include #include #include #include "objectcache.hpp" #ifdef OSG_LIBRARY_STATIC // This list of plugins should match with the list in the top-level CMakelists.txt. USE_OSGPLUGIN(png) USE_OSGPLUGIN(tga) USE_OSGPLUGIN(dds) USE_OSGPLUGIN(jpeg) USE_OSGPLUGIN(bmp) USE_OSGPLUGIN(osg) USE_SERIALIZER_WRAPPER_LIBRARY(osg) #endif namespace { osg::ref_ptr createWarningImage() { osg::ref_ptr warningImage = new osg::Image; int width = 8, height = 8; warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE); assert (warningImage->isDataContiguous()); unsigned char* data = warningImage->data(); for (int i=0;igetPixelFormat()) { case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); if (exts && !exts->isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) { return false; } break; } // not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway default: return true; } return true; } osg::ref_ptr ImageManager::getImage(const std::string &filename, bool disableFlip) { const std::string normalized = mVFS->normalizeFilename(filename); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) return osg::ref_ptr(static_cast(obj.get())); else { Files::IStreamPtr stream; try { stream = mVFS->get(normalized); } catch (std::exception& e) { Log(Debug::Error) << "Failed to open image: " << e.what(); mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } const std::string ext(Misc::getFileExtension(normalized)); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); if (!reader) { Log(Debug::Error) << "Error loading " << filename << ": no readerwriter for '" << ext << "' found"; mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } bool killAlpha = false; if (reader->supportedExtensions().count("tga")) { // Morrowind ignores the alpha channel of 16bpp TGA files even when the header says not to unsigned char header[18]; stream->read((char*)header, 18); if (stream->gcount() != 18) { Log(Debug::Error) << "Error loading " << filename << ": couldn't read TGA header"; mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } int type = header[2]; int depth; if (type == 1 || type == 9) depth = header[7]; else depth = header[16]; int alphaBPP = header[17] & 0x0F; killAlpha = depth == 16 && alphaBPP == 1; stream->seekg(0); } osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, disableFlip ? mOptionsNoFlip : mOptions); if (!result.success()) { Log(Debug::Error) << "Error loading " << filename << ": " << result.message() << " code " << result.status(); mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } osg::ref_ptr image = result.getImage(); image->setFileName(normalized); if (!checkSupported(image, filename)) { static bool uncompress = (getenv("OPENMW_DECOMPRESS_TEXTURES") != nullptr); if (!uncompress) { Log(Debug::Error) << "Error loading " << filename << ": no S3TC texture compression support installed"; mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } else { // decompress texture in software if not supported by GPU // requires update to getColor() to be released with OSG 3.6 osg::ref_ptr newImage = new osg::Image; newImage->setFileName(image->getFileName()); newImage->allocateImage(image->s(), image->t(), image->r(), image->isImageTranslucent() ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE); for (int s=0; ss(); ++s) for (int t=0; tt(); ++t) for (int r=0; rr(); ++r) newImage->setColor(image->getColor(s,t,r), s,t,r); image = newImage; } } else if (killAlpha) { osg::ref_ptr newImage = new osg::Image; newImage->setFileName(image->getFileName()); newImage->allocateImage(image->s(), image->t(), image->r(), GL_RGB, GL_UNSIGNED_BYTE); // OSG just won't write the alpha as there's nowhere to put it. for (int s = 0; s < image->s(); ++s) for (int t = 0; t < image->t(); ++t) for (int r = 0; r < image->r(); ++r) newImage->setColor(image->getColor(s, t, r), s, t, r); image = newImage; } mCache->addEntryToObjectCache(normalized, image); return image; } } osg::Image *ImageManager::getWarningImage() { return mWarningImage; } void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); } } openmw-openmw-0.48.0/components/resource/imagemanager.hpp000066400000000000000000000021601445372753700235750ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H #define OPENMW_COMPONENTS_RESOURCE_IMAGEMANAGER_H #include #include #include #include #include #include "resourcemanager.hpp" namespace osgDB { class Options; } namespace Resource { /// @brief Handles loading/caching of Images. /// @note May be used from any thread. class ImageManager : public ResourceManager { public: ImageManager(const VFS::Manager* vfs); ~ImageManager(); /// Create or retrieve an Image /// Returns the dummy image if the given image is not found. osg::ref_ptr getImage(const std::string& filename, bool disableFlip = false); osg::Image* getWarningImage(); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: osg::ref_ptr mWarningImage; osg::ref_ptr mOptions; osg::ref_ptr mOptionsNoFlip; ImageManager(const ImageManager&); void operator = (const ImageManager&); }; } #endif openmw-openmw-0.48.0/components/resource/keyframemanager.cpp000066400000000000000000000161321445372753700243150ustar00rootroot00000000000000#include "keyframemanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "animation.hpp" #include "objectcache.hpp" #include "scenemanager.hpp" namespace Resource { RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, const std::string& normalized, const VFS::Manager* vfs) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager), mNormalized(normalized), mVFS(vfs) {} void RetrieveAnimationsVisitor::apply(osg::Node& node) { if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); std::vector emulatedAnimations; for (const auto& animation : mAnimationManager->getAnimationList()) { if (animation) { if (animation->getName() == "Default") //"Default" is osg dae plugin's default naming scheme for unnamed animations { animation->setName(std::string("idle")); // animation naming scheme "idle: start" and "idle: stop" is the default idle animation that OpenMW seems to want to play } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); mergedAnimationTrack->setName(animationName); const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } callback->addMergedAnimationTrack(mergedAnimationTrack); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; emulatedAnimation.mName = animationName; emulatedAnimations.emplace_back(emulatedAnimation); } } // mTextKeys is a nif-thing, used by OpenMW's animation system // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" // osgAnimation formats should have a .txt file with the same name, each line holding a textkey and whitespace separated time value // e.g. idle: start 0.0333 try { Files::IStreamPtr textKeysFile = mVFS->get(changeFileExtension(mNormalized, "txt")); std::string line; while ( getline (*textKeysFile, line) ) { mTarget.mTextKeys.emplace(parseTimeSignature(line), parseTextKey(line)); } } catch (std::exception&) { Log(Debug::Warning) << "No textkey file found for " << mNormalized; } callback->setEmulatedAnimations(emulatedAnimations); mTarget.mKeyframeControllers.emplace(node.getName(), callback); } traverse(node); } std::string RetrieveAnimationsVisitor::parseTextKey(const std::string& line) { size_t spacePos = line.find_last_of(' '); if (spacePos != std::string::npos) return line.substr(0, spacePos); return ""; } double RetrieveAnimationsVisitor::parseTimeSignature(const std::string& line) { size_t spacePos = line.find_last_of(' '); double time = 0.0; if (spacePos != std::string::npos && spacePos + 1 < line.size()) time = std::stod(line.substr(spacePos + 1)); return time; } std::string RetrieveAnimationsVisitor::changeFileExtension(const std::string& file, const std::string& ext) { size_t extPos = file.find_last_of('.'); if (extPos != std::string::npos && extPos+1 < file.size()) { return file.substr(0, extPos + 1) + ext; } return file; } } namespace Resource { KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager) : ResourceManager(vfs) , mSceneManager(sceneManager) { } KeyframeManager::~KeyframeManager() { } osg::ref_ptr KeyframeManager::get(const std::string &name) { const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) return osg::ref_ptr(static_cast(obj.get())); else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); if (Misc::getFileExtension(normalized) == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } else { osg::ref_ptr scene = const_cast ( mSceneManager->getTemplate(normalized).get() ); osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); if (bam) { Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); scene->accept(rav); } } mCache->addEntryToObjectCache(normalized, loaded); return loaded; } } void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize()); } } openmw-openmw-0.48.0/components/resource/keyframemanager.hpp000066400000000000000000000034721445372753700243250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_KEYFRAMEMANAGER_H #define OPENMW_COMPONENTS_KEYFRAMEMANAGER_H #include #include #include #include #include "resourcemanager.hpp" namespace Resource { /// @brief extract animations to OpenMW's animation system class RetrieveAnimationsVisitor : public osg::NodeVisitor { public: RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, const std::string& normalized, const VFS::Manager* vfs); virtual void apply(osg::Node& node) override; private: std::string changeFileExtension(const std::string& file, const std::string& ext); std::string parseTextKey(const std::string& line); double parseTimeSignature(const std::string& line); SceneUtil::KeyframeHolder& mTarget; osg::ref_ptr mAnimationManager; std::string mNormalized; const VFS::Manager* mVFS; }; } namespace Resource { class SceneManager; /// @brief Managing of keyframe resources /// @note May be used from any thread. class KeyframeManager : public ResourceManager { public: KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager); ~KeyframeManager(); /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: SceneManager* mSceneManager; }; } #endif openmw-openmw-0.48.0/components/resource/multiobjectcache.cpp000066400000000000000000000050271445372753700244650ustar00rootroot00000000000000#include "multiobjectcache.hpp" #include #include namespace Resource { MultiObjectCache::MultiObjectCache() { } MultiObjectCache::~MultiObjectCache() { } void MultiObjectCache::removeUnreferencedObjectsInCache() { std::vector > objectsToRemove; { std::lock_guard lock(_objectCacheMutex); // Remove unreferenced entries from object cache ObjectCacheMap::iterator oitr = _objectCache.begin(); while(oitr != _objectCache.end()) { if (oitr->second->referenceCount() <= 1) { objectsToRemove.push_back(oitr->second); _objectCache.erase(oitr++); } else { ++oitr; } } } // note, actual unref happens outside of the lock objectsToRemove.clear(); } void MultiObjectCache::clear() { std::lock_guard lock(_objectCacheMutex); _objectCache.clear(); } void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object) { if (!object) { OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl; return; } std::lock_guard lock(_objectCacheMutex); _objectCache.insert(std::make_pair(filename, object)); } osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string &fileName) { std::lock_guard lock(_objectCacheMutex); ObjectCacheMap::iterator found = _objectCache.find(fileName); if (found == _objectCache.end()) return osg::ref_ptr(); else { osg::ref_ptr object = found->second; _objectCache.erase(found); return object; } } void MultiObjectCache::releaseGLObjects(osg::State *state) { std::lock_guard lock(_objectCacheMutex); for(ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { osg::Object* object = itr->second.get(); object->releaseGLObjects(state); } } unsigned int MultiObjectCache::getCacheSize() const { std::lock_guard lock(_objectCacheMutex); return _objectCache.size(); } } openmw-openmw-0.48.0/components/resource/multiobjectcache.hpp000066400000000000000000000023511445372753700244670ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MULTIOBJECTCACHE_H #define OPENMW_COMPONENTS_MULTIOBJECTCACHE_H #include #include #include #include #include namespace osg { class Object; class State; } namespace Resource { /// @brief Cache for "non reusable" objects. class MultiObjectCache : public osg::Referenced { public: MultiObjectCache(); ~MultiObjectCache(); void removeUnreferencedObjectsInCache(); /** Remove all objects from the cache. */ void clear(); void addEntryToObjectCache(const std::string& filename, osg::Object* object); /** Take an Object from cache. Return nullptr if no object found. */ osg::ref_ptr takeFromObjectCache(const std::string& fileName); /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); unsigned int getCacheSize() const; protected: typedef std::multimap > ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; }; } #endif openmw-openmw-0.48.0/components/resource/niffilemanager.cpp000066400000000000000000000026151445372753700241270ustar00rootroot00000000000000#include "niffilemanager.hpp" #include #include #include #include #include "objectcache.hpp" namespace Resource { class NifFileHolder : public osg::Object { public: NifFileHolder(const Nif::NIFFilePtr& file) : mNifFile(file) { } NifFileHolder(const NifFileHolder& copy, const osg::CopyOp& copyop) : mNifFile(copy.mNifFile) { } NifFileHolder() { } META_Object(Resource, NifFileHolder) Nif::NIFFilePtr mNifFile; }; NifFileManager::NifFileManager(const VFS::Manager *vfs) : ResourceManager(vfs) { } NifFileManager::~NifFileManager() { } Nif::NIFFilePtr NifFileManager::get(const std::string &name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); if (obj) return static_cast(obj.get())->mNifFile; else { Nif::NIFFilePtr file (new Nif::NIFFile(mVFS->get(name), name)); obj = new NifFileHolder(file); mCache->addEntryToObjectCache(name, obj); return file; } } void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize()); } } openmw-openmw-0.48.0/components/resource/niffilemanager.hpp000066400000000000000000000015631445372753700241350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H #define OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H #include #include #include "resourcemanager.hpp" namespace Resource { /// @brief Handles caching of NIFFiles. /// @note May be used from any thread. class NifFileManager : public ResourceManager { public: NifFileManager(const VFS::Manager* vfs); ~NifFileManager(); /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. Nif::NIFFilePtr get(const std::string& name); void reportStats(unsigned int frameNumber, osg::Stats *stats) const override; }; } #endif openmw-openmw-0.48.0/components/resource/objectcache.hpp000066400000000000000000000172521445372753700234220ustar00rootroot00000000000000// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below. // Changes: // - removeExpiredObjectsInCache no longer keeps a lock while the unref happens. // - template allows customized KeyType. // - objects with uninitialized time stamp are not removed. /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #include #include #include #include #include #include namespace osg { class Object; class State; class NodeVisitor; } namespace Resource { template class GenericObjectCache : public osg::Referenced { public: GenericObjectCache() : osg::Referenced(true) {} /** For each object in the cache which has an reference count greater than 1 * (and therefore referenced by elsewhere in the application) set the time stamp * for that object in the cache to specified time. * This would typically be called once per frame by applications which are doing database paging, * and need to prune objects that are no longer required. * The time used should be taken from the FrameStamp::getReferenceTime().*/ void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) { // look for objects with external references and update their time stamp. std::lock_guard lock(_objectCacheMutex); for(typename ObjectCacheMap::iterator itr=_objectCache.begin(); itr!=_objectCache.end(); ++itr) { // If ref count is greater than 1, the object has an external reference. // If the timestamp is yet to be initialized, it needs to be updated too. if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0) itr->second.second = referenceTime; } } /** Removed object in the cache which have a time stamp at or before the specified expiry time. * This would typically be called once per frame by applications which are doing database paging, * and need to prune objects that are no longer required, and called after the a called * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ void removeExpiredObjectsInCache(double expiryTime) { std::vector > objectsToRemove; { std::lock_guard lock(_objectCacheMutex); // Remove expired entries from object cache typename ObjectCacheMap::iterator oitr = _objectCache.begin(); while(oitr != _objectCache.end()) { if (oitr->second.second<=expiryTime) { objectsToRemove.push_back(oitr->second.first); _objectCache.erase(oitr++); } else ++oitr; } } // note, actual unref happens outside of the lock objectsToRemove.clear(); } /** Remove all objects in the cache regardless of having external references or expiry times.*/ void clear() { std::lock_guard lock(_objectCacheMutex); _objectCache.clear(); } /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) { std::lock_guard lock(_objectCacheMutex); _objectCache[key]=ObjectTimeStampPair(object,timestamp); } /** Remove Object from cache.*/ void removeFromObjectCache(const KeyType& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr!=_objectCache.end()) _objectCache.erase(itr); } /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const KeyType& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr!=_objectCache.end()) return itr->second.first; else return nullptr; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const KeyType& key, double timeStamp) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr!=_objectCache.end()) { itr->second.second = timeStamp; return true; } else return false; } /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state) { std::lock_guard lock(_objectCacheMutex); for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { osg::Object* object = itr->second.first.get(); object->releaseGLObjects(state); } } /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv) { std::lock_guard lock(_objectCacheMutex); for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { osg::Object* object = itr->second.first.get(); if (object) { osg::Node* node = dynamic_cast(object); if (node) node->accept(nv); } } } /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template void call(Functor& f) { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) f(it->first, it->second.first.get()); } /** Get the number of objects in the cache. */ unsigned int getCacheSize() const { std::lock_guard lock(_objectCacheMutex); return _objectCache.size(); } protected: virtual ~GenericObjectCache() {} typedef std::pair, double > ObjectTimeStampPair; typedef std::map ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; }; class ObjectCache : public GenericObjectCache { }; } #endif openmw-openmw-0.48.0/components/resource/resourcemanager.hpp000066400000000000000000000047131445372753700243500ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_MANAGER_H #define OPENMW_COMPONENTS_RESOURCE_MANAGER_H #include #include "objectcache.hpp" namespace VFS { class Manager; } namespace osg { class Stats; class State; } namespace Resource { class BaseResourceManager { public: virtual ~BaseResourceManager() {} virtual void updateCache(double referenceTime) {} virtual void clearCache() {} virtual void setExpiryDelay(double expiryDelay) {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} virtual void releaseGLObjects(osg::State* state) {} }; /// @brief Base class for managers that require a virtual file system and object cache. /// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes. template class GenericResourceManager : public BaseResourceManager { public: typedef GenericObjectCache CacheType; GenericResourceManager(const VFS::Manager* vfs) : mVFS(vfs) , mCache(new CacheType) , mExpiryDelay(0.0) { } virtual ~GenericResourceManager() {} /// Clear cache entries that have not been referenced for longer than expiryDelay. void updateCache(double referenceTime) override { mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); } /// Clear all cache entries. void clearCache() override { mCache->clear(); } /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay) override { mExpiryDelay = expiryDelay; } float getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } void reportStats(unsigned int frameNumber, osg::Stats* stats) const override {} void releaseGLObjects(osg::State* state) override { mCache->releaseGLObjects(state); } protected: const VFS::Manager* mVFS; osg::ref_ptr mCache; double mExpiryDelay; }; class ResourceManager : public GenericResourceManager { public: ResourceManager(const VFS::Manager* vfs) : GenericResourceManager(vfs) {} }; } #endif openmw-openmw-0.48.0/components/resource/resourcesystem.cpp000066400000000000000000000066721445372753700242630ustar00rootroot00000000000000#include "resourcesystem.hpp" #include #include "scenemanager.hpp" #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "keyframemanager.hpp" namespace Resource { ResourceSystem::ResourceSystem(const VFS::Manager *vfs) : mVFS(vfs) { mNifFileManager = std::make_unique(vfs); mImageManager = std::make_unique(vfs); mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get()); mKeyframeManager = std::make_unique(vfs, mSceneManager.get()); addResourceManager(mNifFileManager.get()); addResourceManager(mKeyframeManager.get()); // note, scene references images so add images afterwards for correct implementation of updateCache() addResourceManager(mSceneManager.get()); addResourceManager(mImageManager.get()); } ResourceSystem::~ResourceSystem() { // this has to be defined in the .cpp file as we can't delete incomplete types mResourceManagers.clear(); } SceneManager* ResourceSystem::getSceneManager() { return mSceneManager.get(); } ImageManager* ResourceSystem::getImageManager() { return mImageManager.get(); } NifFileManager* ResourceSystem::getNifFileManager() { return mNifFileManager.get(); } KeyframeManager* ResourceSystem::getKeyframeManager() { return mKeyframeManager.get(); } void ResourceSystem::setExpiryDelay(double expiryDelay) { for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->setExpiryDelay(expiryDelay); // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, // so no point in using an expiry delay mNifFileManager->setExpiryDelay(0.0); } void ResourceSystem::updateCache(double referenceTime) { for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime); } void ResourceSystem::clearCache() { for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->clearCache(); } void ResourceSystem::addResourceManager(BaseResourceManager *resourceMgr) { mResourceManagers.push_back(resourceMgr); } void ResourceSystem::removeResourceManager(BaseResourceManager *resourceMgr) { std::vector::iterator found = std::find(mResourceManagers.begin(), mResourceManagers.end(), resourceMgr); if (found != mResourceManagers.end()) mResourceManagers.erase(found); } const VFS::Manager* ResourceSystem::getVFS() const { return mVFS; } void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) const { for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->reportStats(frameNumber, stats); } void ResourceSystem::releaseGLObjects(osg::State *state) { for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->releaseGLObjects(state); } } openmw-openmw-0.48.0/components/resource/resourcesystem.hpp000066400000000000000000000054551445372753700242660ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H #define OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H #include #include namespace VFS { class Manager; } namespace osg { class Stats; class State; } namespace Resource { class SceneManager; class ImageManager; class NifFileManager; class KeyframeManager; class BaseResourceManager; /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but /// are built around the use of a single virtual file system. class ResourceSystem { public: ResourceSystem(const VFS::Manager* vfs); ~ResourceSystem(); SceneManager* getSceneManager(); ImageManager* getImageManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer referenced. /// @note May be called from any thread if you do not add or remove resource managers at that point. void updateCache(double referenceTime); /// Indicates to each resource manager to clear the entire cache. /// @note May be called from any thread if you do not add or remove resource managers at that point. void clearCache(); /// Add this ResourceManager to be handled by the ResourceSystem. /// @note Does not transfer ownership. void addResourceManager(BaseResourceManager* resourceMgr); /// @note Do nothing if resourceMgr does not exist. /// @note Does not delete resourceMgr. void removeResourceManager(BaseResourceManager* resourceMgr); /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay(double expiryDelay); /// @note May be called from any thread. const VFS::Manager* getVFS() const; void reportStats(unsigned int frameNumber, osg::Stats* stats) const; /// Call releaseGLObjects for each resource manager. void releaseGLObjects(osg::State* state); private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; // Store the base classes separately to get convenient access to the common interface // Here users can register their own resourcemanager as well std::vector mResourceManagers; const VFS::Manager* mVFS; ResourceSystem(const ResourceSystem&); void operator = (const ResourceSystem&); }; } #endif openmw-openmw-0.48.0/components/resource/scenemanager.cpp000066400000000000000000001215521445372753700236120ustar00rootroot00000000000000#include "scenemanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" namespace { class InitWorldSpaceParticlesCallback : public SceneUtil::NodeCallback { public: void operator()(osgParticle::ParticleSystem* node, osg::NodeVisitor* nv) { // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to if (node->getNumParents() && node->getParent(0)->getNumParents()) transformInitialParticles(node, node->getParent(0)->getParent(0)); node->removeUpdateCallback(this); } void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { osg::NodePathList nodepaths = node->getParentalNodePaths(); if (nodepaths.empty()) return; osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node for (int i=0; inumParticles(); ++i) { partsys->getParticle(i)->transformPositionVelocity(worldMat); } // transform initial bounds to worldspace osg::BoundingSphere sphere(partsys->getInitialBound()); SceneUtil::transformBoundingSphere(worldMat, sphere); osg::BoundingBox box; box.expandBy(sphere); partsys->setInitialBound(box); } }; class InitParticlesVisitor : public osg::NodeVisitor { public: /// @param mask The node mask to set on ParticleSystem nodes. InitParticlesVisitor(unsigned int mask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mMask(mask) { } bool isWorldSpaceParticleSystem(osgParticle::ParticleSystem* partsys) { // HACK: ParticleSystem has no getReferenceFrame() return (partsys->getUserDataContainer() && partsys->getUserDataContainer()->getNumDescriptions() > 0 && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } void apply(osg::Drawable& drw) override { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) { if (isWorldSpaceParticleSystem(partsys)) { partsys->addUpdateCallback(new InitWorldSpaceParticlesCallback); } partsys->setNodeMask(mMask); } } private: unsigned int mMask; }; } namespace Resource { void TemplateMultiRef::addRef(const osg::Node* node) { mObjects.emplace_back(node); } class SharedStateManager : public osgDB::SharedStateManager { public: unsigned int getNumSharedTextures() const { return _sharedTextureList.size(); } unsigned int getNumSharedStateSets() const { return _sharedStateSetList.size(); } void clearCache() { std::lock_guard lock(_listMutex); _sharedTextureList.clear(); _sharedStateSetList.clear(); } }; /// Set texture filtering settings on textures contained in a FlipController. class SetFilterSettingsControllerVisitor : public SceneUtil::ControllerVisitor { public: SetFilterSettingsControllerVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) : mMinFilter(minFilter) , mMagFilter(magFilter) , mMaxAnisotropy(maxAnisotropy) { } void visit(osg::Node& node, SceneUtil::Controller& ctrl) override { if (NifOsg::FlipController* flipctrl = dynamic_cast(&ctrl)) { for (std::vector >::iterator it = flipctrl->getTextures().begin(); it != flipctrl->getTextures().end(); ++it) { osg::Texture* tex = *it; tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); } } } private: osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; int mMaxAnisotropy; }; /// Set texture filtering settings on textures contained in StateSets. class SetFilterSettingsVisitor : public osg::NodeVisitor { public: SetFilterSettingsVisitor(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mMinFilter(minFilter) , mMagFilter(magFilter) , mMaxAnisotropy(maxAnisotropy) { } void apply(osg::Node& node) override { osg::StateSet* stateset = node.getStateSet(); if (stateset) applyStateSet(stateset); traverse(node); } void applyStateSet(osg::StateSet* stateset) { const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (texture) applyStateAttribute(texture); } } void applyStateAttribute(osg::StateAttribute* attr) { osg::Texture* tex = attr->asTexture(); if (tex) { tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); } } private: osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; int mMaxAnisotropy; }; // Check Collada extra descriptions class ColladaDescriptionVisitor : public osg::NodeVisitor { public: ColladaDescriptionVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mSkeleton(nullptr) { } osg::AlphaFunc::ComparisonFunction getTestMode(std::string mode) { if (mode == "ALWAYS") return osg::AlphaFunc::ALWAYS; if (mode == "LESS") return osg::AlphaFunc::LESS; if (mode == "EQUAL") return osg::AlphaFunc::EQUAL; if (mode == "LEQUAL") return osg::AlphaFunc::LEQUAL; if (mode == "GREATER") return osg::AlphaFunc::GREATER; if (mode == "NOTEQUAL") return osg::AlphaFunc::NOTEQUAL; if (mode == "GEQUAL") return osg::AlphaFunc::GEQUAL; if (mode == "NEVER") return osg::AlphaFunc::NEVER; Log(Debug::Warning) << "Unexpected alpha testing mode: " << mode; return osg::AlphaFunc::LEQUAL; } void apply(osg::Node& node) override { if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) { osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN) { osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } } /* Check if the has correct format for OpenMW: alphatest mode value MaterialName e.g alphatest GEQUAL 0.8 MyAlphaTestedMaterial */ std::vector descriptions = node.getDescriptions(); for (const auto & description : descriptions) { mDescriptions.emplace_back(description); } // Iterate each description, and see if the current node uses the specified material for alpha testing if (node.getStateSet()) { for (const auto & description : mDescriptions) { std::vector descriptionParts; std::istringstream descriptionStringStream(description); for (std::string part; std::getline(descriptionStringStream, part, ' ');) { descriptionParts.emplace_back(part); } if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getStateSet()->getName()) { if (descriptionParts.at(0) == "alphatest") { osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); node.getStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } } if (descriptionParts.size() > (0) && descriptionParts.at(0) == "bodypart") { SceneUtil::FindByClassVisitor osgaRigFinder("RigGeometryHolder"); node.accept(osgaRigFinder); for(osg::Node* foundRigNode : osgaRigFinder.mFoundNodes) { if (SceneUtil::RigGeometryHolder* rigGeometryHolder = dynamic_cast (foundRigNode)) mRigGeometryHolders.emplace_back(osg::ref_ptr (rigGeometryHolder)); else Log(Debug::Error) << "Converted RigGeometryHolder is of a wrong type."; } if (!mRigGeometryHolders.empty()) { osgAnimation::RigGeometry::FindNearestParentSkeleton skeletonFinder; mRigGeometryHolders[0]->accept(skeletonFinder); if (skeletonFinder._root.valid()) mSkeleton = skeletonFinder._root; } } } } traverse(node); } private: std::vector mDescriptions; public: osgAnimation::Skeleton* mSkeleton; //pointer is valid only if the model is a bodypart, osg::ref_ptr std::vector> mRigGeometryHolders; }; SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) , mClampLighting(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) , mAdjustCoverageForAlphaTest(false) , mSupportsNormalsRT(false) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMagFilter(osg::Texture::LINEAR) , mMaxAnisotropy(1) , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) { } void SceneManager::setForceShaders(bool force) { mForceShaders = force; } bool SceneManager::getForceShaders() const { return mForceShaders; } void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); shaderVisitor->setAllowedToModifyStateSets(false); shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); } void SceneManager::reinstateRemovedState(osg::ref_ptr node) { osg::ref_ptr reinstateRemovedStateVisitor = new Shader::ReinstateRemovedStateVisitor(false); node->accept(*reinstateRemovedStateVisitor); } void SceneManager::setClampLighting(bool clamp) { mClampLighting = clamp; } bool SceneManager::getClampLighting() const { return mClampLighting; } void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; } void SceneManager::setNormalMapPattern(const std::string &pattern) { mNormalMapPattern = pattern; } void SceneManager::setNormalHeightMapPattern(const std::string &pattern) { mNormalHeightMapPattern = pattern; } void SceneManager::setAutoUseSpecularMaps(bool use) { mAutoUseSpecularMaps = use; } void SceneManager::setSpecularMapPattern(const std::string &pattern) { mSpecularMapPattern = pattern; } void SceneManager::setApplyLightingToEnvMaps(bool apply) { mApplyLightingToEnvMaps = apply; } void SceneManager::setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported) { mSupportedLightingMethods = supported; } bool SceneManager::isSupportedLightingMethod(SceneUtil::LightingMethod method) const { return mSupportedLightingMethods[static_cast(method)]; } void SceneManager::setLightingMethod(SceneUtil::LightingMethod method) { mLightingMethod = method; if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) { osg::ref_ptr program = new osg::Program; program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); mShaderManager->setProgramTemplate(program); } } SceneUtil::LightingMethod SceneManager::getLightingMethod() const { return mLightingMethod; } void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; } void SceneManager::setAdjustCoverageForAlphaTest(bool adjustCoverage) { mAdjustCoverageForAlphaTest = adjustCoverage; } void SceneManager::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) { mOpaqueDepthTex = { texturePing, texturePong }; } osg::ref_ptr SceneManager::getOpaqueDepthTex(size_t frame) { return mOpaqueDepthTex[frame % 2]; } SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types } Shader::ShaderManager &SceneManager::getShaderManager() { return *mShaderManager.get(); } void SceneManager::setShaderPath(const std::string &path) { mShaderManager->setShaderPath(path); } bool SceneManager::checkLoaded(const std::string &name, double timeStamp) { return mCache->checkInObjectCache(mVFS->normalizeFilename(name), timeStamp); } /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { public: ImageReadCallback(Resource::ImageManager* imageMgr) : mImageManager(imageMgr) { } osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options) override { boost::filesystem::path filePath(filename); if (filePath.is_absolute()) // It is a hack. Needed because either OSG or libcollada-dom tries to make an absolute path from // our relative VFS path by adding current working directory. filePath = boost::filesystem::relative(filename, osgDB::getCurrentWorkingDirectory()); try { return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filePath.string()), osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { return osgDB::ReaderWriter::ReadResult(e.what()); } } private: Resource::ImageManager* mImageManager; }; namespace { osg::ref_ptr loadNonNif(const std::string& normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { auto ext = Misc::getFileExtension(normalizedFilename); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { std::stringstream errormsg; errormsg << "Error loading " << normalizedFilename << ": no readerwriter for '" << ext << "' found" << std::endl; throw std::runtime_error(errormsg.str()); } osg::ref_ptr options (new osgDB::Options); // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system instead of the osgDB. // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. // but findFileCallback does not support virtual files, so we can't implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); const std::array fileHash = Files::getHash(normalizedFilename, model); osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) { std::stringstream errormsg; errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; throw std::runtime_error(errormsg.str()); } // Recognize and hide collision node unsigned int hiddenNodeMask = 0; SceneUtil::FindByNameVisitor nameFinder("Collision"); auto node = result.getNode(); node->accept(nameFinder); if (nameFinder.mFoundNode) nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); // Recognize and convert osgAnimation::RigGeometry to OpenMW-optimized type SceneUtil::FindByClassVisitor rigFinder("RigGeometry"); node->accept(rigFinder); for(osg::Node* foundRigNode : rigFinder.mFoundNodes) { if (foundRigNode->libraryName() == std::string("osgAnimation")) { osgAnimation::RigGeometry* foundRigGeometry = static_cast (foundRigNode); osg::ref_ptr newRig = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL); if (foundRigGeometry->getStateSet()) newRig->setStateSet(foundRigGeometry->getStateSet()); if (osg::Group* parent = dynamic_cast (foundRigGeometry->getParent(0))) { parent->removeChild(foundRigGeometry); parent->addChild(newRig); } } } if (ext == "dae") { Resource::ColladaDescriptionVisitor colladaDescriptionVisitor; node->accept(colladaDescriptionVisitor); if (colladaDescriptionVisitor.mSkeleton) { if ( osg::Group* group = dynamic_cast (node) ) { group->removeChildren(0, group->getNumChildren()); for (osg::ref_ptr newRiggeometryHolder : colladaDescriptionVisitor.mRigGeometryHolders) { osg::ref_ptr backToOriginTrans = new osg::MatrixTransform(); newRiggeometryHolder->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(newRiggeometryHolder->getGeometry(0))); backToOriginTrans->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(newRiggeometryHolder->getGeometry(0))); newRiggeometryHolder->setBodyPart(true); for (int i = 0; i < 2; ++i) { if (newRiggeometryHolder->getGeometry(i)) newRiggeometryHolder->getGeometry(i)->setSkeleton(nullptr); } backToOriginTrans->addChild(newRiggeometryHolder); group->addChild(backToOriginTrans); } } } node->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); } node->setUserValue(Misc::OsgUserValues::sFileHash, std::string(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t))); return node; } std::vector makeSortedReservedNames() { static constexpr std::string_view names[] = { "Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera", "Collision", "Right_Wrist", "Left_Wrist", "Shield_Bone", "Right_Forearm", "Left_Forearm", "Right_Upper_Arm", "Left_Clavicle", "Weapon_Bone", "Root_Bone", }; std::vector result; result.reserve(2 * std::size(names)); for (std::string_view name : names) { result.emplace_back(name); std::string prefixedName("Tri "); prefixedName += name; result.push_back(std::move(prefixedName)); } std::sort(result.begin(), result.end(), Misc::StringUtils::ciLess); return result; } } osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { auto ext = Misc::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else return loadNonNif(normalizedFilename, *vfs->get(normalizedFilename), imageManager); } class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: bool isReservedName(const std::string& name) const { if (name.empty()) return false; static const std::vector reservedNames = makeSortedReservedNames(); const auto it = Misc::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name); return it != reservedNames.end(); } bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const override { if (option & SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS) { if (node->asGeometry() && node->className() == std::string("Geometry")) return true; else return false; //ParticleSystem would have to convert space of all the processors, RigGeometry would have to convert bones... theoretically possible, but very complicated } return (option & optimizer->getPermissibleOptimizationsForObject(node))!=0; } bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const override { if (node->getNumDescriptions()>0) return false; if (node->getDataVariance() == osg::Object::DYNAMIC) return false; if (isReservedName(node->getName())) return false; return (option & optimizer->getPermissibleOptimizationsForObject(node))!=0; } }; bool canOptimize(const std::string& filename) { size_t slashpos = filename.find_last_of("\\/"); if (slashpos != std::string::npos && slashpos+1 < filename.size()) { std::string basename = filename.substr(slashpos+1); // xmesh.nif can not be optimized because there are keyframes added in post if (!basename.empty() && basename[0] == 'x') return false; // NPC skeleton files can not be optimized because of keyframes added in post // (most of them are usually named like 'xbase_anim.nif' anyway, but not all of them :( ) if (basename.compare(0, 9, "base_anim") == 0 || basename.compare(0, 4, "skin") == 0) return false; } // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly cautious - instead, decide on filename if (filename.find("vfx_pattern") != std::string::npos) return false; return true; } unsigned int getOptimizationOptions() { using namespace SceneUtil; const char* env = getenv("OPENMW_OPTIMIZE"); unsigned int options = Optimizer::FLATTEN_STATIC_TRANSFORMS|Optimizer::REMOVE_REDUNDANT_NODES|Optimizer::MERGE_GEOMETRY; if (env) { std::string str(env); if(str.find("OFF")!=std::string::npos || str.find('0')!= std::string::npos) options = 0; if(str.find("~FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options ^= Optimizer::FLATTEN_STATIC_TRANSFORMS; else if(str.find("FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options |= Optimizer::FLATTEN_STATIC_TRANSFORMS; if(str.find("~REMOVE_REDUNDANT_NODES")!=std::string::npos) options ^= Optimizer::REMOVE_REDUNDANT_NODES; else if(str.find("REMOVE_REDUNDANT_NODES")!=std::string::npos) options |= Optimizer::REMOVE_REDUNDANT_NODES; if(str.find("~MERGE_GEOMETRY")!=std::string::npos) options ^= Optimizer::MERGE_GEOMETRY; else if(str.find("MERGE_GEOMETRY")!=std::string::npos) options |= Optimizer::MERGE_GEOMETRY; } return options; } void SceneManager::shareState(osg::ref_ptr node) { mSharedStateMutex.lock(); mSharedStateManager->share(node.get()); mSharedStateMutex.unlock(); } osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) return osg::ref_ptr(static_cast(obj.get())); else { osg::ref_ptr loaded; try { loaded = load(normalized, mVFS, mImageManager, mNifFileManager); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); loaded->accept(extraDataVisitor); } catch (const std::exception& e) { static osg::ref_ptr errorMarkerNode = [&] { static const char* const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; for (unsigned int i=0; iexists(normalized)) return load(normalized, mVFS, mImageManager, mNifFileManager); } Files::IMemStream file(Misc::errorMarker.data(), Misc::errorMarker.size()); return loadNonNif("error_marker.osgt", file, mImageManager); }(); Log(Debug::Error) << "Failed to load '" << name << "': " << e.what() << ", using marker_error instead"; loaded = static_cast(errorMarkerNode->clone(osg::CopyOp::DEEP_COPY_ALL)); } // set filtering settings SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsVisitor); SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); SceneUtil::ReplaceDepthVisitor replaceDepthVisitor; loaded->accept(replaceDepthVisitor); osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); if (canOptimize(normalized)) { SceneUtil::Optimizer optimizer; optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex); optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); static const unsigned int options = getOptimizationOptions()|SceneUtil::Optimizer::SHARE_DUPLICATE_STATE; optimizer.optimize(loaded, options); } else shareState(loaded); if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); else loaded->getBound(); mCache->addEntryToObjectCache(normalized, loaded); return loaded; } } osg::ref_ptr SceneManager::getInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); return getInstance(scene); } osg::ref_ptr SceneManager::cloneNode(const osg::Node* base) { SceneUtil::CopyOp copyop; if (const osg::Drawable* drawable = base->asDrawable()) { if (drawable->asGeometry()) { Log(Debug::Warning) << "SceneManager::cloneNode: attempting to clone osg::Geometry. For safety reasons this will be expensive. Consider avoiding this call."; copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES); } } osg::ref_ptr cloned = static_cast(base->clone(copyop)); // add a ref to the original template to help verify the safety of shallow cloning operations // in addition, if this node is managed by a cache, we hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); return cloned; } osg::ref_ptr SceneManager::getInstance(const osg::Node *base) { osg::ref_ptr cloned = cloneNode(base); // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set if (cloned->getNumChildrenRequiringUpdateTraversal() > 0) { InitParticlesVisitor visitor (mParticleSystemMask); cloned->accept(visitor); } return cloned; } osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) { osg::ref_ptr cloned = getInstance(name); attachTo(cloned, parentNode); return cloned; } void SceneManager::attachTo(osg::Node *instance, osg::Group *parentNode) const { parentNode->addChild(instance); } void SceneManager::releaseGLObjects(osg::State *state) { mCache->releaseGLObjects(state); mShaderManager->releaseGLObjects(state); std::lock_guard lock(mSharedStateMutex); mSharedStateManager->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) { mIncrementalCompileOperation = ico; } osgUtil::IncrementalCompileOperation *SceneManager::getIncrementalCompileOperation() { return mIncrementalCompileOperation.get(); } Resource::ImageManager* SceneManager::getImageManager() { return mImageManager; } void SceneManager::setParticleSystemMask(unsigned int mask) { mParticleSystemMask = mask; } void SceneManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, const std::string &mipmap, int maxAnisotropy) { osg::Texture::FilterMode min = osg::Texture::LINEAR; osg::Texture::FilterMode mag = osg::Texture::LINEAR; if(magfilter == "nearest") mag = osg::Texture::NEAREST; else if(magfilter != "linear") Log(Debug::Warning) << "Warning: Invalid texture mag filter: "<< magfilter; if(minfilter == "nearest") min = osg::Texture::NEAREST; else if(minfilter != "linear") Log(Debug::Warning) << "Warning: Invalid texture min filter: "<< minfilter; if(mipmap == "nearest") { if(min == osg::Texture::NEAREST) min = osg::Texture::NEAREST_MIPMAP_NEAREST; else if(min == osg::Texture::LINEAR) min = osg::Texture::LINEAR_MIPMAP_NEAREST; } else if(mipmap != "none") { if(mipmap != "linear") Log(Debug::Warning) << "Warning: Invalid texture mipmap: " << mipmap; if(min == osg::Texture::NEAREST) min = osg::Texture::NEAREST_MIPMAP_LINEAR; else if(min == osg::Texture::LINEAR) min = osg::Texture::LINEAR_MIPMAP_LINEAR; } mMinFilter = min; mMagFilter = mag; mMaxAnisotropy = std::max(1, maxAnisotropy); SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); SetFilterSettingsVisitor setFilterSettingsVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); mCache->accept(setFilterSettingsVisitor); mCache->accept(setFilterSettingsControllerVisitor); } void SceneManager::applyFilterSettings(osg::Texture *tex) { tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); } void SceneManager::setUnRefImageDataAfterApply(bool unref) { mUnRefImageDataAfterApply = unref; } void SceneManager::updateCache(double referenceTime) { ResourceManager::updateCache(referenceTime); mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); if (mIncrementalCompileOperation) { std::lock_guard lock(*mIncrementalCompileOperation->getToCompiledMutex()); osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile(); for(osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();) { int refcount = (*it)->_subgraphToCompile->referenceCount(); if ((*it)->_subgraphToCompile->asDrawable()) refcount -= 1; // ref by CompileList. if (refcount <= 2) // ref by ObjectCache + ref by _subgraphToCompile. { // no other ref = not needed anymore. it = sets.erase(it); } else ++it; } } } void SceneManager::clearCache() { ResourceManager::clearCache(); std::lock_guard lock(mSharedStateMutex); mSharedStateManager->clearCache(); } void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { if (mIncrementalCompileOperation) { std::lock_guard lock(*mIncrementalCompileOperation->getToCompiledMutex()); stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size()); } { std::lock_guard lock(mSharedStateMutex); stats->setAttribute(frameNumber, "Texture", mSharedStateManager->getNumSharedTextures()); stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets()); } stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); } Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern); shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); shaderVisitor->setAdjustCoverageForAlphaTest(mAdjustCoverageForAlphaTest); shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT); return shaderVisitor; } } openmw-openmw-0.48.0/components/resource/scenemanager.hpp000066400000000000000000000227701445372753700236210ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H #define OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H #include #include #include #include #include #include #include #include #include "resourcemanager.hpp" #include namespace Resource { class ImageManager; class NifFileManager; class SharedStateManager; } namespace osgUtil { class IncrementalCompileOperation; } namespace osgDB { class SharedStateManager; } namespace Shader { class ShaderManager; class ShaderVisitor; } namespace Resource { class TemplateRef : public osg::Object { public: TemplateRef(const Object* object) : mObject(object) {} TemplateRef() {} TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} META_Object(Resource, TemplateRef) private: osg::ref_ptr mObject; }; class TemplateMultiRef : public osg::Object { public: TemplateMultiRef() {} TemplateMultiRef(const TemplateMultiRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} void addRef(const osg::Node* node); META_Object(Resource, TemplateMultiRef) private: std::vector> mObjects; }; /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. class SceneManager : public ResourceManager { public: SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. /// When editing such state, it should be reinstated before the edits, and shaders should be recreated afterwards. void reinstateRemovedState(osg::ref_ptr node); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); bool getForceShaders() const; void setClampLighting(bool clamp); bool getClampLighting() const; /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); /// @see ShaderVisitor::setNormalMapPattern void setNormalMapPattern(const std::string& pattern); /// @see ShaderVisitor::setNormalHeightMapPattern void setNormalHeightMapPattern(const std::string& pattern); void setAutoUseSpecularMaps(bool use); void setSpecularMapPattern(const std::string& pattern); void setApplyLightingToEnvMaps(bool apply); void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); osg::ref_ptr getOpaqueDepthTex(size_t frame); enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate LightBuffer, PostProcessor }; void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; void setConvertAlphaTestToAlphaToCoverage(bool convert); void setAdjustCoverageForAlphaTest(bool adjustCoverage); void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded bool checkLoaded(const std::string& name, double referenceTime); /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name, bool compile=true); /// Clone osg::Node safely. /// @note Thread safe. static osg::ref_ptr cloneNode(const osg::Node* base); void shareState(osg::ref_ptr node); /// Clone osg::Node and adjust it according to SceneManager's settings. /// @note Thread safe. osg::ref_ptr getInstance(const osg::Node* base); /// Instance the given scene template. /// @see getTemplate /// @note Thread safe. osg::ref_ptr getInstance(const std::string& name); /// Instance the given scene template and immediately attach it to a parent node /// @see getTemplate /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. osg::ref_ptr getInstance(const std::string& name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node /// @note You should have the parentNode in its intended position before calling this method, /// so that world space particles of the \a instance get transformed correctly. /// @note Assumes the given instance was not attached to any parents before. /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. void attachTo(osg::Node* instance, osg::Group* parentNode) const; /// Manually release created OpenGL objects for the given graphics context. This may be required /// in cases where multiple contexts are used over the lifetime of the application. void releaseGLObjects(osg::State* state) override; /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); /// @warning It is unsafe to call this method while the draw thread is using textures! call Viewer::stopThreading first. void setFilterSettings(const std::string &magfilter, const std::string &minfilter, const std::string &mipmap, int maxAnisotropy); /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate or createInstance) /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. void applyFilterSettings (osg::Texture* tex); /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, /// otherwise should be disabled to reduce memory usage. void setUnRefImageDataAfterApply(bool unref); /// @see ResourceManager::updateCache void updateCache(double referenceTime) override; void clearCache() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } void setSoftParticles(bool enabled) { mSoftParticles = enabled; } bool getSoftParticles() const { return mSoftParticles; } private: Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); std::unique_ptr mShaderManager; bool mForceShaders; bool mClampLighting; bool mAutoUseNormalMaps; std::string mNormalMapPattern; std::string mNormalHeightMapPattern; bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; bool mAdjustCoverageForAlphaTest; bool mSupportsNormalsRT; std::array, 2> mOpaqueDepthTex; bool mSoftParticles = false; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; int mMaxAnisotropy; bool mUnRefImageDataAfterApply; osg::ref_ptr mIncrementalCompileOperation; unsigned int mParticleSystemMask; SceneManager(const SceneManager&); void operator = (const SceneManager&); }; std::string getFileExtension(const std::string& file); } #endif openmw-openmw-0.48.0/components/resource/stats.cpp000066400000000000000000000377231445372753700223260ustar00rootroot00000000000000#include "stats.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace Resource { static bool collectStatRendering = false; static bool collectStatCameraObjects = false; static bool collectStatViewerObjects = false; static bool collectStatResource = false; static bool collectStatGPU = false; static bool collectStatEvent = false; static bool collectStatFrameRate = false; static bool collectStatUpdate = false; static bool collectStatEngine = false; constexpr std::string_view sFontName = "Fonts/DejaVuLGCSansMono.ttf"; static void setupStatCollection() { const char* envList = getenv("OPENMW_OSG_STATS_LIST"); if (envList == nullptr) return; std::string_view kwList(envList); auto kwBegin = kwList.begin(); while (kwBegin != kwList.end()) { auto kwEnd = std::find(kwBegin, kwList.end(), ';'); const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); if (kw.compare("gpu") == 0) collectStatGPU = true; else if (kw.compare("event") == 0) collectStatEvent = true; else if (kw.compare("frame_rate") == 0) collectStatFrameRate = true; else if (kw.compare("update") == 0) collectStatUpdate = true; else if (kw.compare("engine") == 0) collectStatEngine = true; else if (kw.compare("rendering") == 0) collectStatRendering = true; else if (kw.compare("cameraobjects") == 0) collectStatCameraObjects = true; else if (kw.compare("viewerobjects") == 0) collectStatViewerObjects = true; else if (kw.compare("resource") == 0) collectStatResource = true; else if (kw.compare("times") == 0) { collectStatGPU = true; collectStatEvent = true; collectStatFrameRate = true; collectStatUpdate = true; collectStatEngine = true; collectStatRendering = true; } if (kwEnd == kwList.end()) break; kwBegin = std::next(kwEnd); } } class SetFontVisitor : public osg::NodeVisitor { public: SetFontVisitor(osgText::Font* font) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mFont(font) {} void apply(osg::Drawable& node) override { if (osgText::Text* text = dynamic_cast(&node)) { text->setFont(mFont); } } private: osgText::Font* mFont; }; osg::ref_ptr getMonoFont(VFS::Manager* vfs) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs->exists(sFontName)) { Files::IStreamPtr streamPtr = vfs->get(sFontName); return osgText::readRefFontStream(*streamPtr.get()); } return nullptr; } StatsHandler::StatsHandler(bool offlineCollect, VFS::Manager* vfs): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _characterSize(18.0f) { _camera = new osg::Camera; _camera->getOrCreateStateSet()->setGlobalDefaults(); _camera->setRenderer(new osgViewer::Renderer(_camera.get())); _camera->setProjectionResizePolicy(osg::Camera::FIXED); _resourceStatsChildNum = 0; _textFont = getMonoFont(vfs); } Profiler::Profiler(bool offlineCollect, VFS::Manager* vfs): _offlineCollect(offlineCollect), _initFonts(false) { _characterSize = 18; _font.clear(); _textFont = getMonoFont(vfs); setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); setupStatCollection(); } void Profiler::setUpFonts() { if (_textFont != nullptr) { SetFontVisitor visitor(_textFont); _switch->accept(visitor); } _initFonts = true; } bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { osgViewer::ViewerBase* viewer = nullptr; bool handled = StatsHandler::handle(ea, aa); if (_initialized && !_initFonts) setUpFonts(); auto* view = dynamic_cast(&aa); if (view) viewer = view->getViewerBase(); if (viewer) { // Add/remove openmw stats to the osd as necessary viewer->getViewerStats()->collectStats("engine", _statsType >= StatsHandler::StatsType::VIEWER_STATS); if (_offlineCollect) CollectStatistics(viewer); } return handled; } bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { if (ea.getHandled()) return false; switch(ea.getEventType()) { case(osgGA::GUIEventAdapter::KEYDOWN): { if (ea.getKey()== _key) { osgViewer::View* myview = dynamic_cast(&aa); if (!myview) return false; osgViewer::ViewerBase* viewer = myview->getViewerBase(); toggle(viewer); if (_offlineCollect) CollectStatistics(viewer); aa.requestRedraw(); return true; } break; } case osgGA::GUIEventAdapter::RESIZE: { setWindowSize(ea.getWindowWidth(), ea.getWindowHeight()); break; } default: break; } return false; } void StatsHandler::setWindowSize(int width, int height) { if (width <= 0 || height <= 0) return; _camera->setViewport(0, 0, width, height); if (fabs(height*_statsWidth) <= fabs(width*_statsHeight)) { _camera->setProjectionMatrix(osg::Matrix::ortho2D(_statsWidth - width*_statsHeight/height, _statsWidth,0.0,_statsHeight)); } else { _camera->setProjectionMatrix(osg::Matrix::ortho2D(0.0,_statsWidth,_statsHeight-height*_statsWidth/width,_statsHeight)); } } void StatsHandler::toggle(osgViewer::ViewerBase *viewer) { if (!_initialized) { setUpHUDCamera(viewer); setUpScene(viewer); } _statsType = !_statsType; if (!_statsType) { _camera->setNodeMask(0); _switch->setAllChildrenOff(); viewer->getViewerStats()->collectStats("resource", false); } else { _camera->setNodeMask(0xffffffff); _switch->setSingleChildOn(_resourceStatsChildNum); viewer->getViewerStats()->collectStats("resource", true); } } void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) { // Try GraphicsWindow first so we're likely to get the main viewer window osg::GraphicsContext* context = dynamic_cast(_camera->getGraphicsContext()); if (!context) { osgViewer::Viewer::Windows windows; viewer->getWindows(windows); if (!windows.empty()) context = windows.front(); else { // No GraphicsWindows were found, so let's try to find a GraphicsContext context = _camera->getGraphicsContext(); if (!context) { osgViewer::Viewer::Contexts contexts; viewer->getContexts(contexts); if (contexts.empty()) return; context = contexts.front(); } } } _camera->setGraphicsContext(context); _camera->setRenderOrder(osg::Camera::POST_RENDER, 11); _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); _camera->setViewMatrix(osg::Matrix::identity()); setWindowSize(context->getTraits()->width, context->getTraits()->height); // only clear the depth buffer _camera->setClearMask(0); _camera->setAllowEventFocus(false); _camera->setRenderer(new osgViewer::Renderer(_camera.get())); _initialized = true; } osg::Geometry* createBackgroundRectangle(const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) { osg::StateSet *ss = new osg::StateSet; osg::Geometry* geometry = new osg::Geometry; geometry->setUseDisplayList(false); geometry->setStateSet(ss); osg::Vec3Array* vertices = new osg::Vec3Array; geometry->setVertexArray(vertices); vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); vertices->push_back(osg::Vec3(pos.x(), pos.y()-height,0)); vertices->push_back(osg::Vec3(pos.x()+width, pos.y()-height,0)); vertices->push_back(osg::Vec3(pos.x()+width, pos.y(),0)); osg::Vec4Array* colors = new osg::Vec4Array; colors->push_back(color); geometry->setColorArray(colors, osg::Array::BIND_OVERALL); osg::DrawElementsUShort *base = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN,0); base->push_back(0); base->push_back(1); base->push_back(2); base->push_back(3); geometry->addPrimitiveSet(base); return geometry; } class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback { public: ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) : mStats(stats) , mStatNames(statNames) { } void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const override { if (!mStats) return; osgText::Text* text = (osgText::Text*)(drawable); std::ostringstream viewStr; viewStr.setf(std::ios::left, std::ios::adjustfield); viewStr.width(14); // Used fixed formatting, as scientific will switch to "...e+.." notation for // large numbers of vertices/drawables/etc. viewStr.setf(std::ios::fixed); viewStr.precision(0); unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber()-1; for (const auto& statName : mStatNames.get()) { if (statName.empty()) viewStr << std::endl; else { double value = 0.0; if (mStats->getAttribute(frameNumber, statName, value)) viewStr << std::setw(8) << value << std::endl; else viewStr << std::setw(8) << "." << std::endl; } } text->setText(viewStr.str()); text->drawImplementation(renderInfo); } osg::ref_ptr mStats; std::reference_wrapper> mStatNames; }; void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) { _switch = new osg::Switch; _camera->addChild(_switch); osg::StateSet* stateset = _switch->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); stateset->setMode(GL_BLEND,osg::StateAttribute::ON); stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); #ifdef OSG_GL1_AVAILABLE stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); #endif osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); float backgroundMargin = 5; float backgroundSpacing = 3; // resource stats { osg::Group* group = new osg::Group; group->setCullingActive(false); _resourceStatsChildNum = _switch->getNumChildren(); _switch->addChild(group, false); static const std::vector statNames({ "FrameNumber", "", "Compiling", "WorkQueue", "WorkThread", "UnrefQueue", "", "Texture", "StateSet", "Node", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "", "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", "NavMesh Jobs", "NavMesh Waiting", "NavMesh Pushed", "NavMesh Processing", "NavMesh DbJobs", "NavMesh DbCacheHitRate", "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", "NavMesh CacheHitRate", "", "Mechanics Actors", "Mechanics Objects", "", "Physics Actors", "Physics Objects", "Physics Projectiles", "Physics HeightFields", "", "Lua UsedMemory", }); static const auto longest = std::max_element(statNames.begin(), statNames.end(), [] (const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; const float statTextWidth = 7 * _characterSize + 2 * backgroundMargin; const float statHeight = statNames.size() * _characterSize + 2 * backgroundMargin; osg::Vec3 pos(_statsWidth - statNamesWidth - backgroundSpacing - statTextWidth, statHeight, 0.0f); group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), statNamesWidth, statHeight, backgroundColor)); osg::ref_ptr staticText = new osgText::Text; group->addChild( staticText.get() ); staticText->setColor(staticTextColor); staticText->setCharacterSize(_characterSize); staticText->setPosition(pos); std::ostringstream viewStr; viewStr.clear(); viewStr.setf(std::ios::left, std::ios::adjustfield); viewStr.width(longest->size()); for (const auto& statName : statNames) { viewStr << statName << std::endl; } staticText->setText(viewStr.str()); pos.x() += statNamesWidth + backgroundSpacing; group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), statTextWidth, statHeight, backgroundColor)); osg::ref_ptr statsText = new osgText::Text; group->addChild( statsText.get() ); statsText->setColor(dynamicTextColor); statsText->setCharacterSize(_characterSize); statsText->setPosition(pos); statsText->setText(""); statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); if (_textFont) { staticText->setFont(_textFont); statsText->setFont(_textFont); } } } void StatsHandler::getUsage(osg::ApplicationUsage &usage) const { usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); } void CollectStatistics(osgViewer::ViewerBase* viewer) { osgViewer::Viewer::Cameras cameras; viewer->getCameras(cameras); for (auto* camera : cameras) { if (collectStatGPU) camera->getStats()->collectStats("gpu", true); if (collectStatRendering) camera->getStats()->collectStats("rendering", true); if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true); } if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true); if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true); if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true); if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true); if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true); if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true); } } openmw-openmw-0.48.0/components/resource/stats.hpp000066400000000000000000000035461445372753700223270ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_STATS_H #define OPENMW_COMPONENTS_RESOURCE_STATS_H #include namespace osgViewer { class ViewerBase; } namespace osg { class Switch; } namespace osgText { class Font; } namespace VFS { class Manager; } namespace Resource { class Profiler : public osgViewer::StatsHandler { public: Profiler(bool offlineCollect, VFS::Manager* vfs); bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; private: void setUpFonts(); bool _offlineCollect; bool _initFonts; osg::ref_ptr _textFont; }; class StatsHandler : public osgGA::GUIEventHandler { public: StatsHandler(bool offlineCollect, VFS::Manager* vfs); void setKey(int key) { _key = key; } int getKey() const { return _key; } bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; void setWindowSize(int w, int h); void toggle(osgViewer::ViewerBase* viewer); void setUpHUDCamera(osgViewer::ViewerBase* viewer); void setUpScene(osgViewer::ViewerBase* viewer); /** Get the keyboard and mouse usage of this manipulator.*/ void getUsage(osg::ApplicationUsage& usage) const override; private: osg::ref_ptr _switch; int _key; osg::ref_ptr _camera; bool _initialized; bool _statsType; bool _offlineCollect; float _statsWidth; float _statsHeight; float _characterSize; int _resourceStatsChildNum; osg::ref_ptr _textFont; }; void CollectStatistics(osgViewer::ViewerBase* viewer); } #endif openmw-openmw-0.48.0/components/sceneutil/000077500000000000000000000000001445372753700206145ustar00rootroot00000000000000openmw-openmw-0.48.0/components/sceneutil/actorutil.cpp000066400000000000000000000021461445372753700233310ustar00rootroot00000000000000#include "actorutil.hpp" #include namespace SceneUtil { std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) { if (!firstPerson) { if (isWerewolf) return Settings::Manager::getString("wolfskin", "Models"); else if (isBeast) return Settings::Manager::getString("baseanimkna", "Models"); else if (isFemale) return Settings::Manager::getString("baseanimfemale", "Models"); else return Settings::Manager::getString("baseanim", "Models"); } else { if (isWerewolf) return Settings::Manager::getString("wolfskin1st", "Models"); else if (isBeast) return Settings::Manager::getString("baseanimkna1st", "Models"); else if (isFemale) return Settings::Manager::getString("baseanimfemale1st", "Models"); else return Settings::Manager::getString("xbaseanim1st", "Models"); } } } openmw-openmw-0.48.0/components/sceneutil/actorutil.hpp000066400000000000000000000003641445372753700233360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP #define OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP #include namespace SceneUtil { std::string getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); } #endif openmw-openmw-0.48.0/components/sceneutil/agentpath.cpp000066400000000000000000000061051445372753700232750ustar00rootroot00000000000000#include "agentpath.hpp" #include "detourdebugdraw.hpp" #include #include #include #include namespace { void drawAgent(duDebugDraw& debugDraw, const osg::Vec3f& pos, const float radius, const float height, const float climb, const unsigned color) { debugDraw.depthMask(false); duDebugDrawCylinderWire(&debugDraw, pos.x() - radius, pos.z() + 0.02f, pos.y() - radius, pos.x() + radius, pos.z() + height, pos.y() + radius, color, radius * 0.2f); duDebugDrawCircle(&debugDraw, pos.x(), pos.z() + climb, pos.y(), radius, duRGBA(0, 0 , 0, 64), radius * 0.1f); const auto colb = duRGBA(0, 0, 0, 196); debugDraw.begin(DU_DRAW_LINES); debugDraw.vertex(pos.x(), pos.z() - climb, pos.y(), colb); debugDraw.vertex(pos.x(), pos.z() + climb, pos.y(), colb); debugDraw.vertex(pos.x() - radius / 2, pos.z() + 0.02f, pos.y(), colb); debugDraw.vertex(pos.x() + radius / 2, pos.z() + 0.02f, pos.y(), colb); debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() - radius / 2, colb); debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() + radius / 2, colb); debugDraw.end(); debugDraw.depthMask(true); } } namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; const osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, DebugDraw::makeStateSet(), osg::Vec3f(0, 0, 0), 1); const auto agentRadius = DetourNavigator::getAgentRadius(agentBounds); const auto agentHeight = DetourNavigator::getAgentHeight(agentBounds); const auto agentClimb = settings.mMaxClimb; const auto startColor = duRGBA(128, 25, 0, 192); const auto endColor = duRGBA(51, 102, 0, 129); drawAgent(debugDraw, start, agentRadius, agentHeight, agentClimb, startColor); drawAgent(debugDraw, end, agentRadius, agentHeight, agentClimb, endColor); const auto pathColor = duRGBA(0, 0, 0, 220); debugDraw.depthMask(false); debugDraw.begin(osg::PrimitiveSet::LINE_STRIP, agentRadius * 0.5f); debugDraw.vertex(osg::Vec3f(start.x(), start.z() + agentClimb, start.y()).ptr(), startColor); std::for_each(path.begin(), path.end(), [&] (const osg::Vec3f& v) { debugDraw.vertex(osg::Vec3f(v.x(), v.z() + agentClimb, v.y()).ptr(), pathColor); }); debugDraw.vertex(osg::Vec3f(end.x(), end.z() + agentClimb, end.y()).ptr(), endColor); debugDraw.end(); debugDraw.depthMask(true); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); group->getOrCreateStateSet()->setAttribute(material); return group; } } openmw-openmw-0.48.0/components/sceneutil/agentpath.hpp000066400000000000000000000010631445372753700233000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H #define OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H #include #include namespace osg { class Group; class Vec3f; } namespace DetourNavigator { struct RecastSettings; struct AgentBounds; } namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const DetourNavigator::RecastSettings& settings); } #endif openmw-openmw-0.48.0/components/sceneutil/attach.cpp000066400000000000000000000153331445372753700225710ustar00rootroot00000000000000#include "attach.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "visitor.hpp" namespace SceneUtil { class CopyRigVisitor : public osg::NodeVisitor { public: CopyRigVisitor(osg::ref_ptr parent, const std::string& filter) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mParent(parent) , mFilter(Misc::StringUtils::lowerCase(filter)) { mFilter2 = "tri " + mFilter; } void apply(osg::MatrixTransform& node) override { traverse(node); } void apply(osg::Node& node) override { traverse(node); } void apply(osg::Group& node) override { traverse(node); } void apply(osg::Drawable& drawable) override { if (!filterMatches(drawable.getName())) return; const osg::Node* node = &drawable; for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it) { const osg::Node* parent = *it; if (!filterMatches(parent->getName())) break; node = parent; } mToCopy.emplace(node); } void doCopy(Resource::SceneManager* sceneManager) { for (const osg::ref_ptr& node : mToCopy) { mParent->addChild(sceneManager->getInstance(node)); } mToCopy.clear(); } private: bool filterMatches(const std::string& name) const { std::string lowerName = Misc::StringUtils::lowerCase(name); return (lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0); } using NodeSet = std::set>; NodeSet mToCopy; osg::ref_ptr mParent; std::string mFilter; std::string mFilter2; }; void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) { if (!source) return; if (!target->getUserDataContainer()) target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); else { for (unsigned int i=0; igetNumUserObjects(); ++i) target->getUserDataContainer()->addUserObject(osg::clone(source->getUserObject(i), osg::CopyOp::SHALLOW_COPY)); } } osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode, Resource::SceneManager* sceneManager, const osg::Quat* attitude) { if (dynamic_cast(toAttach.get())) { osg::ref_ptr handle = new osg::Group; CopyRigVisitor copyVisitor(handle, filter); const_cast(toAttach.get())->accept(copyVisitor); copyVisitor.doCopy(sceneManager); // add a ref to the original template to hint to the cache that it is still being used and should be kept in cache. handle->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(toAttach)); if (handle->getNumChildren() == 1) { osg::ref_ptr newHandle = handle->getChild(0); handle->removeChild(newHandle); master->asGroup()->addChild(newHandle); mergeUserData(toAttach->getUserDataContainer(), newHandle); return newHandle; } else { master->asGroup()->addChild(handle); mergeUserData(toAttach->getUserDataContainer(), handle); return handle; } } else { osg::ref_ptr clonedToAttach = sceneManager->getInstance(toAttach); FindByNameVisitor findBoneOffset("BoneOffset"); clonedToAttach->accept(findBoneOffset); osg::ref_ptr trans; if (findBoneOffset.mFoundNode) { osg::MatrixTransform* boneOffset = dynamic_cast(findBoneOffset.mFoundNode); if (!boneOffset) throw std::runtime_error("BoneOffset must be a MatrixTransform"); trans = new osg::PositionAttitudeTransform; trans->setPosition(boneOffset->getMatrix().getTrans()); // Now that we used it, get rid of the redundant node. if (boneOffset->getNumChildren() == 0 && boneOffset->getNumParents() == 1) boneOffset->getParent(0)->removeChild(boneOffset); } if (attachNode->getName().find("Left") != std::string::npos) { if (!trans) trans = new osg::PositionAttitudeTransform; trans->setScale(osg::Vec3f(-1.f, 1.f, 1.f)); // Need to invert culling because of the negative scale // Note: for absolute correctness we would need to check the current front face for every mesh then invert it // However MW isn't doing this either, so don't. Assuming all meshes are using backface culling is more efficient. static osg::ref_ptr frontFaceStateSet; if (!frontFaceStateSet) { frontFaceStateSet = new osg::StateSet; osg::FrontFace* frontFace = new osg::FrontFace; frontFace->setMode(osg::FrontFace::CLOCKWISE); frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON); } trans->setStateSet(frontFaceStateSet); } if(attitude) { if (!trans) trans = new osg::PositionAttitudeTransform; trans->setAttitude(*attitude); } if (trans) { attachNode->addChild(trans); trans->addChild(clonedToAttach); return trans; } else { attachNode->addChild(clonedToAttach); return clonedToAttach; } } } } openmw-openmw-0.48.0/components/sceneutil/attach.hpp000066400000000000000000000020351445372753700225710ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H #define OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H #include #include namespace osg { class Node; class Group; class Quat; } namespace Resource { class SceneManager; } namespace SceneUtil { /// Clone and attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. /// If the \a toAttach scene graph contains skinned objects, we will attach only those (filtered by the \a filter). /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// @note The master scene graph is expected to include a skeleton. /// @return A newly created node that is directly attached to the master scene graph osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode, Resource::SceneManager *sceneManager, const osg::Quat* attitude = nullptr); } #endif openmw-openmw-0.48.0/components/sceneutil/clearcolor.hpp000077500000000000000000000023461445372753700234620ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H #define OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H #include #include namespace SceneUtil { class ClearColor : public osg::StateAttribute { public: ClearColor() : mMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) {} ClearColor(const osg::Vec4f& color, GLbitfield mask) : mColor(color), mMask(mask) {} ClearColor(const ClearColor& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mColor(copy.mColor), mMask(copy.mMask) {} META_StateAttribute(fx, ClearColor, static_cast(100)) int compare(const StateAttribute& sa) const override { COMPARE_StateAttribute_Types(ClearColor, sa); COMPARE_StateAttribute_Parameter(mColor); COMPARE_StateAttribute_Parameter(mMask); return 0; } void apply(osg::State& state) const override { glClearColor(mColor[0], mColor[1], mColor[2], mColor[3]); glClear(mMask); } private: osg::Vec4f mColor; GLbitfield mMask; }; } #endif openmw-openmw-0.48.0/components/sceneutil/clone.cpp000066400000000000000000000072211445372753700224220ustar00rootroot00000000000000#include "clone.hpp" #include #include #include #include #include #include #include #include #include namespace SceneUtil { CopyOp::CopyOp() { setCopyFlags(osg::CopyOp::DEEP_COPY_NODES // Controller might need different inputs per scene instance | osg::CopyOp::DEEP_COPY_CALLBACKS | osg::CopyOp::DEEP_COPY_USERDATA); } osg::Node* CopyOp::operator ()(const osg::Node* node) const { if (const osgParticle::ParticleProcessor* processor = dynamic_cast(node)) return operator()(processor); if (const osgParticle::ParticleSystemUpdater* updater = dynamic_cast(node)) { osgParticle::ParticleSystemUpdater* cloned = new osgParticle::ParticleSystemUpdater(*updater, osg::CopyOp::SHALLOW_COPY); mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } return osg::CopyOp::operator()(node); } osg::Drawable* CopyOp::operator ()(const osg::Drawable* drawable) const { if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) return operator()(partsys); if (dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable)) { return static_cast(drawable->clone(*this)); } return osg::CopyOp::operator()(drawable); } osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { osgParticle::ParticleProcessor* cloned = static_cast(processor->clone(osg::CopyOp::DEEP_COPY_CALLBACKS)); for (const auto& oldPsNewPsPair : mOldPsToNewPs) { if (processor->getParticleSystem() == oldPsNewPsPair.first) { cloned->setParticleSystem(oldPsNewPsPair.second); return cloned; } } mProcessorToOldPs[cloned] = processor->getParticleSystem(); return cloned; } osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const { osgParticle::ParticleSystem* cloned = static_cast(partsys->clone(*this)); for (const auto& processorPsPair : mProcessorToOldPs) { if (processorPsPair.second == partsys) { processorPsPair.first->setParticleSystem(cloned); } } for (const auto& updaterPsPair : mUpdaterToOldPs) { if (updaterPsPair.second == partsys) { osgParticle::ParticleSystemUpdater* updater = updaterPsPair.first; updater->removeParticleSystem(updater->getParticleSystem(0)); updater->addParticleSystem(cloned); } } // In rare situations a particle processor may be placed after the particle system in the scene graph. mOldPsToNewPs[partsys] = cloned; return cloned; } } openmw-openmw-0.48.0/components/sceneutil/clone.hpp000066400000000000000000000033001445372753700224210ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLONE_H #define OPENMW_COMPONENTS_SCENEUTIL_CLONE_H #include #include namespace osgParticle { class ParticleProcessor; class ParticleSystem; class ParticleSystemUpdater; } namespace SceneUtil { /// @par Defines the cloning behaviour we need: /// * Assigns updated ParticleSystem pointers on cloned emitters and programs. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. /// @warning Avoid using this class directly. The safety of cloning operations depends on the copy flags and the objects involved. Consider using SceneManager::cloneNode for additional safety. /// @warning Do not use an object of this class for more than one copy operation. class CopyOp : public osg::CopyOp { public: CopyOp(); virtual osgParticle::ParticleSystem* operator() (const osgParticle::ParticleSystem* partsys) const; virtual osgParticle::ParticleProcessor* operator() (const osgParticle::ParticleProcessor* processor) const; osg::Node* operator() (const osg::Node* node) const override; osg::Drawable* operator() (const osg::Drawable* drawable) const override; private: // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way mutable std::map mProcessorToOldPs; mutable std::map mUpdaterToOldPs; mutable std::map mOldPsToNewPs; }; } #endif openmw-openmw-0.48.0/components/sceneutil/color.cpp000066400000000000000000000075111445372753700224420ustar00rootroot00000000000000#include "color.hpp" #include #include #include #include #include namespace SceneUtil { bool isColorFormat(GLenum format) { static constexpr std::array formats = { GL_RGB, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB8_SNORM, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGB16_SNORM, GL_SRGB, GL_SRGB8, GL_RGB16F, GL_RGB32F, GL_R11F_G11F_B10F, GL_RGB9_E5, GL_RGB8I, GL_RGB8UI, GL_RGB16I, GL_RGB16UI, GL_RGB32I, GL_RGB32UI, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGBA8_SNORM, GL_RGB10_A2, GL_RGB10_A2UI, GL_RGBA12, GL_RGBA16, GL_RGBA16_SNORM, GL_SRGB_ALPHA8, GL_SRGB8_ALPHA8, GL_RGBA16F, GL_RGBA32F, GL_RGBA8I, GL_RGBA8UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI, }; return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } bool isFloatingPointColorFormat(GLenum format) { static constexpr std::array formats = { GL_RGB16F, GL_RGB32F, GL_R11F_G11F_B10F, GL_RGBA16F, GL_RGBA32F, }; return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } int getColorFormatChannelCount(GLenum format) { static constexpr std::array formats = { GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGBA8_SNORM, GL_RGB10_A2, GL_RGB10_A2UI, GL_RGBA12, GL_RGBA16, GL_RGBA16_SNORM, GL_SRGB_ALPHA8, GL_SRGB8_ALPHA8, GL_RGBA16F, GL_RGBA32F, GL_RGBA8I, GL_RGBA8UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI, }; if (std::find(formats.cbegin(), formats.cend(), format) != formats.cend()) return 4; return 3; } void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) { if (getColorFormatChannelCount(internalFormat == 4)) sourceFormat = GL_RGBA; else sourceFormat = GL_RGB; if (isFloatingPointColorFormat(internalFormat)) sourceType = GL_FLOAT; else sourceType = GL_UNSIGNED_BYTE; } namespace Color { GLenum sColorInternalFormat; GLenum sColorSourceFormat; GLenum sColorSourceType; GLenum colorInternalFormat() { return sColorInternalFormat; } GLenum colorSourceFormat() { return sColorSourceFormat; } GLenum colorSourceType() { return sColorSourceType; } void SelectColorFormatOperation::operator()([[maybe_unused]] osg::GraphicsContext* graphicsContext) { sColorInternalFormat = GL_RGB; for (auto supportedFormat : mSupportedFormats) { if (isColorFormat(supportedFormat)) { sColorInternalFormat = supportedFormat; break; } } getColorFormatSourceFormatAndType(sColorInternalFormat, sColorSourceFormat, sColorSourceType); } } } openmw-openmw-0.48.0/components/sceneutil/color.hpp000066400000000000000000000063511445372753700224500ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_COLOR_H #define OPENMW_COMPONENTS_SCENEUTIL_COLOR_H #include namespace SceneUtil { bool isColorFormat(GLenum format); bool isFloatingPointColorFormat(GLenum format); int getColorFormatChannelCount(GLenum format); void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); namespace Color { GLenum colorSourceFormat(); GLenum colorSourceType(); GLenum colorInternalFormat(); class SelectColorFormatOperation final : public osg::GraphicsOperation { public: SelectColorFormatOperation() : GraphicsOperation("SelectColorFormatOperation", false) {} void operator()(osg::GraphicsContext* graphicsContext) override; void setSupportedFormats(const std::vector& supportedFormats) { mSupportedFormats = supportedFormats; } private: std::vector mSupportedFormats; }; } } #ifndef GL_RGB #define GL_RGB 0x1907 #endif #ifndef GL_RGBA #define GL_RGBA 0x1908 #endif #ifndef GL_RGB4 #define GL_RGB4 0x804F #endif #ifndef GL_RGB5 #define GL_RGB5 0x8050 #endif #ifndef GL_RGB8 #define GL_RGB8 0x8051 #endif #ifndef GL_RGB8_SNORM #define GL_RGB8_SNORM 0x8F96 #endif #ifndef GL_RGB10 #define GL_RGB10 0x8052 #endif #ifndef GL_RGB12 #define GL_RGB12 0x8053 #endif #ifndef GL_RGB16 #define GL_RGB16 0x8054 #endif #ifndef GL_RGB16_SNORM #define GL_RGB16_SNORM 0x8F9A #endif #ifndef GL_RGBA2 #define GL_RGBA2 0x8055 #endif #ifndef GL_RGBA4 #define GL_RGBA4 0x8056 #endif #ifndef GL_RGB5_A1 #define GL_RGB5_A1 0x8057 #endif #ifndef GL_RGBA8 #define GL_RGBA8 0x8058 #endif #ifndef GL_RGBA8_SNORM #define GL_RGBA8_SNORM 0x8F97 #endif #ifndef GL_RGB10_A2 #define GL_RGB10_A2 0x906F #endif #ifndef GL_RGB10_A2UI #define GL_RGB10_A2UI 0x906F #endif #ifndef GL_RGBA12 #define GL_RGBA12 0x805A #endif #ifndef GL_RGBA16 #define GL_RGBA16 0x805B #endif #ifndef GL_RGBA16_SNORM #define GL_RGBA16_SNORM 0x8F9B #endif #ifndef GL_SRGB #define GL_SRGB 0x8C40 #endif #ifndef GL_SRGB8 #define GL_SRGB8 0x8C41 #endif #ifndef GL_SRGB_ALPHA8 #define GL_SRGB_ALPHA8 0x8C42 #endif #ifndef GL_SRGB8_ALPHA8 #define GL_SRGB8_ALPHA8 0x8C43 #endif #ifndef GL_RGB16F #define GL_RGB16F 0x881B #endif #ifndef GL_RGBA16F #define GL_RGBA16F 0x881A #endif #ifndef GL_RGB32F #define GL_RGB32F 0x8815 #endif #ifndef GL_RGBA32F #define GL_RGBA32F 0x8814 #endif #ifndef GL_R11F_G11F_B10F #define GL_R11F_G11F_B10F 0x8C3A #endif #ifndef GL_RGB8I #define GL_RGB8I 0x8D8F #endif #ifndef GL_RGB8UI #define GL_RGB8UI 0x8D7D #endif #ifndef GL_RGB16I #define GL_RGB16I 0x8D89 #endif #ifndef GL_RGB16UI #define GL_RGB16UI 0x8D77 #endif #ifndef GL_RGB32I #define GL_RGB32I 0x8D83 #endif #ifndef GL_RGB32UI #define GL_RGB32UI 0x8D71 #endif #ifndef GL_RGBA8I #define GL_RGBA8I 0x8D8E #endif #ifndef GL_RGBA8UI #define GL_RGBA8UI 0x8D7C #endif #ifndef GL_RGBA16I #define GL_RGBA16I 0x8D88 #endif #ifndef GL_RGBA16UI #define GL_RGBA16UI 0x8D76 #endif #ifndef GL_RGBA32I #define GL_RGBA32I 0x8D82 #endif #ifndef GL_RGBA32UI #define GL_RGBA32UI 0x8D70 #endif #ifndef GL_RGB9_E5 #define GL_RGB9_E5 0x8C3D #endif #endif openmw-openmw-0.48.0/components/sceneutil/controller.cpp000066400000000000000000000073371445372753700235150ustar00rootroot00000000000000#include "controller.hpp" #include #include "statesetupdater.hpp" #include #include #include #include namespace SceneUtil { Controller::Controller() { } bool Controller::hasInput() const { return mSource.get() != nullptr; } float Controller::getInputValue(osg::NodeVisitor* nv) { if (mFunction) return mFunction->calculate(mSource->getValue(nv)); else return mSource->getValue(nv); } void Controller::setSource(std::shared_ptr source) { mSource = source; } void Controller::setFunction(std::shared_ptr function) { mFunction = function; } std::shared_ptr Controller::getSource() const { return mSource; } std::shared_ptr Controller::getFunction() const { return mFunction; } FrameTimeSource::FrameTimeSource() { } float FrameTimeSource::getValue(osg::NodeVisitor *nv) { return nv->getFrameStamp()->getSimulationTime(); } ControllerVisitor::ControllerVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void ControllerVisitor::apply(osg::Node &node) { applyNode(node); } void ControllerVisitor::apply(osg::MatrixTransform &node) { applyNode(node); } void ControllerVisitor::apply(osg::Geometry &node) { applyNode(node); } void ControllerVisitor::applyNode(osg::Node &node) { osg::Callback* callback = node.getUpdateCallback(); while (callback) { if (Controller* ctrl = dynamic_cast(callback)) visit(node, *ctrl); if (CompositeStateSetUpdater* composite = dynamic_cast(callback)) { for (unsigned int i=0; igetNumControllers(); ++i) { StateSetUpdater* statesetcontroller = composite->getController(i); if (Controller* ctrl = dynamic_cast(statesetcontroller)) visit(node, *ctrl); } } callback = callback->getNestedCallback(); } if (node.getNumChildrenRequiringUpdateTraversal() > 0) traverse(node); } AssignControllerSourcesVisitor::AssignControllerSourcesVisitor() : ControllerVisitor() { } AssignControllerSourcesVisitor::AssignControllerSourcesVisitor(std::shared_ptr toAssign) : ControllerVisitor() , mToAssign(toAssign) { } void AssignControllerSourcesVisitor::visit(osg::Node&, Controller &ctrl) { if (!ctrl.getSource()) ctrl.setSource(mToAssign); } ForceControllerSourcesVisitor::ForceControllerSourcesVisitor() : AssignControllerSourcesVisitor() { } ForceControllerSourcesVisitor::ForceControllerSourcesVisitor(std::shared_ptr toAssign) : AssignControllerSourcesVisitor(toAssign) { } void ForceControllerSourcesVisitor::visit(osg::Node&, Controller &ctrl) { ctrl.setSource(mToAssign); } FindMaxControllerLengthVisitor::FindMaxControllerLengthVisitor() : SceneUtil::ControllerVisitor() , mMaxLength(0) { } void FindMaxControllerLengthVisitor::visit(osg::Node &, Controller &ctrl) { if (ctrl.getFunction()) mMaxLength = std::max(mMaxLength, ctrl.getFunction()->getMaximum()); } float FindMaxControllerLengthVisitor::getMaxLength() const { return mMaxLength; } } openmw-openmw-0.48.0/components/sceneutil/controller.hpp000066400000000000000000000071731445372753700235200ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_CONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_CONTROLLER_H #include #include namespace SceneUtil { class ControllerSource { public: virtual ~ControllerSource() { } virtual float getValue(osg::NodeVisitor* nv) = 0; }; class FrameTimeSource : public ControllerSource { public: FrameTimeSource(); float getValue(osg::NodeVisitor* nv) override; }; /// @note ControllerFunctions may be shared - you should not hold any state in it. That is why all its methods are declared const. class ControllerFunction { public: virtual ~ControllerFunction() = default; virtual float calculate(float input) const = 0; /// Get the "stop time" of the controller function, typically the maximum of the calculate() function. /// May not be meaningful for all types of controller functions. virtual float getMaximum() const = 0; }; class Controller { public: Controller(); virtual ~Controller() {} bool hasInput() const; float getInputValue(osg::NodeVisitor* nv); void setSource(std::shared_ptr source); void setFunction(std::shared_ptr function); std::shared_ptr getSource() const; std::shared_ptr getFunction() const; private: std::shared_ptr mSource; // The source value gets passed through this function before it's passed on to the DestValue. std::shared_ptr mFunction; }; /// Pure virtual base class - visit() all controllers that are attached as UpdateCallbacks in a scene graph. class ControllerVisitor : public osg::NodeVisitor { public: ControllerVisitor(); void apply(osg::Node& node) override; // Technically not required as the default implementation would trickle down to apply(Node&) anyway, // but we'll shortcut instead to avoid the chain of virtual function calls void apply(osg::MatrixTransform& node) override; void apply(osg::Geometry& node) override; void applyNode(osg::Node& node); virtual void visit(osg::Node& node, Controller& ctrl) = 0; }; class AssignControllerSourcesVisitor : public ControllerVisitor { public: AssignControllerSourcesVisitor(); AssignControllerSourcesVisitor(std::shared_ptr toAssign); /// Assign the wanted ControllerSource. May be overridden in derived classes. /// By default assigns the ControllerSource passed to the constructor of this class if no ControllerSource is assigned to that controller yet. void visit(osg::Node& node, Controller& ctrl) override; protected: std::shared_ptr mToAssign; }; class ForceControllerSourcesVisitor : public AssignControllerSourcesVisitor { public: ForceControllerSourcesVisitor(); ForceControllerSourcesVisitor(std::shared_ptr toAssign); /// Assign the wanted ControllerSource even if one is already assigned to the controller. void visit(osg::Node& node, Controller& ctrl) override; }; /// Finds the maximum of all controller functions in the given scene graph class FindMaxControllerLengthVisitor : public ControllerVisitor { public: FindMaxControllerLengthVisitor(); void visit(osg::Node& , Controller& ctrl) override; float getMaxLength() const; private: float mMaxLength; }; } #endif openmw-openmw-0.48.0/components/sceneutil/depth.cpp000066400000000000000000000144051445372753700224300ustar00rootroot00000000000000#include "depth.hpp" #include #include #include namespace SceneUtil { void setCameraClearDepth(osg::Camera* camera) { camera->setClearDepth(AutoDepth::isReversed() ? 0.0 : 1.0); } osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) { double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); return osg::Matrix( A/aspect, 0, 0, 0, 0, A, 0, 0, 0, 0, 0, -1, 0, 0, near, 0 ); } osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far) { double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); return osg::Matrix( A/aspect, 0, 0, 0, 0, A, 0, 0, 0, 0, near/(far-near), -1, 0, 0, (far*near)/(far - near), 0 ); } osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far) { return osg::Matrix( 2/(right-left), 0, 0, 0, 0, 2/(top-bottom), 0, 0, 0, 0, 1/(far-near), 0, (right+left)/(left-right), (top+bottom)/(bottom-top), far/(far-near), 1 ); } bool isDepthFormat(GLenum format) { constexpr std::array formats = { GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT32F_NV, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32, GL_DEPTH32F_STENCIL8, GL_DEPTH32F_STENCIL8_NV, GL_DEPTH24_STENCIL8, }; return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } bool isDepthStencilFormat(GLenum format) { constexpr std::array formats = { GL_DEPTH32F_STENCIL8, GL_DEPTH32F_STENCIL8_NV, GL_DEPTH24_STENCIL8, }; return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) { switch (internalFormat) { case GL_DEPTH_COMPONENT16: case GL_DEPTH_COMPONENT24: case GL_DEPTH_COMPONENT32: sourceType = GL_UNSIGNED_INT; sourceFormat = GL_DEPTH_COMPONENT; break; case GL_DEPTH_COMPONENT32F: case GL_DEPTH_COMPONENT32F_NV: sourceType = GL_FLOAT; sourceFormat = GL_DEPTH_COMPONENT; break; case GL_DEPTH24_STENCIL8: sourceType = GL_UNSIGNED_INT_24_8_EXT; sourceFormat = GL_DEPTH_STENCIL_EXT; break; case GL_DEPTH32F_STENCIL8: case GL_DEPTH32F_STENCIL8_NV: sourceType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; sourceFormat = GL_DEPTH_STENCIL_EXT; break; default: sourceType = GL_UNSIGNED_INT; sourceFormat = GL_DEPTH_COMPONENT; break; } } GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat) { switch (internalFormat) { case GL_DEPTH24_STENCIL8: return GL_DEPTH_COMPONENT24; break; case GL_DEPTH32F_STENCIL8: return GL_DEPTH_COMPONENT32F; break; case GL_DEPTH32F_STENCIL8_NV: return GL_DEPTH_COMPONENT32F_NV; break; default: return internalFormat; break; } } void SelectDepthFormatOperation::operator()(osg::GraphicsContext* graphicsContext) { bool enableReverseZ = false; if (Settings::Manager::getBool("reverse z", "Camera")) { osg::ref_ptr exts = osg::GLExtensions::Get(0, false); if (exts && exts->isClipControlSupported) { enableReverseZ = true; Log(Debug::Info) << "Using reverse-z depth buffer"; } else Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; } else Log(Debug::Info) << "Using standard depth buffer"; SceneUtil::AutoDepth::setReversed(enableReverseZ); constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; std::vector requestedFormats; unsigned int contextID = graphicsContext->getState()->getContextID(); if (SceneUtil::AutoDepth::isReversed()) { if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) { requestedFormats.push_back(GL_DEPTH32F_STENCIL8); } else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) { requestedFormats.push_back(GL_DEPTH32F_STENCIL8_NV); } else { Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; } } requestedFormats.push_back(GL_DEPTH24_STENCIL8); if (mSupportedFormats.empty()) { SceneUtil::AutoDepth::setDepthFormat(requestedFormats.front()); } else { for (auto requestedFormat : requestedFormats) { if (std::find(mSupportedFormats.cbegin(), mSupportedFormats.cend(), requestedFormat) != mSupportedFormats.cend()) { SceneUtil::AutoDepth::setDepthFormat(requestedFormat); break; } } } } void AutoDepth::setDepthFormat(GLenum format) { sDepthInternalFormat = format; getDepthFormatSourceFormatAndType(sDepthInternalFormat, sDepthSourceFormat, sDepthSourceType); } } openmw-openmw-0.48.0/components/sceneutil/depth.hpp000066400000000000000000000135761445372753700224450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_DEPTH_H #define OPENMW_COMPONENTS_SCENEUTIL_DEPTH_H #include #include "util.hpp" #ifndef GL_DEPTH32F_STENCIL8_NV #define GL_DEPTH32F_STENCIL8_NV 0x8DAC #endif #ifndef GL_DEPTH32F_STENCIL8 #define GL_DEPTH32F_STENCIL8 0x8CAD #endif #ifndef GL_FLOAT_32_UNSIGNED_INT_24_8_REV #define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD #endif #ifndef GL_DEPTH24_STENCIL8 #define GL_DEPTH24_STENCIL8 0x88F0 #endif #ifndef GL_DEPTH_STENCIL_EXT #define GL_DEPTH_STENCIL_EXT 0x84F9 #endif #ifndef GL_UNSIGNED_INT_24_8_EXT #define GL_UNSIGNED_INT_24_8_EXT 0x84FA #endif namespace SceneUtil { // Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise. void setCameraClearDepth(osg::Camera* camera); // Returns a perspective projection matrix for use with a reversed z-buffer // and an infinite far plane. This is derived by mapping the default z-range // of [0,1] to [1,0], then taking the limit as far plane approaches infinity. osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near); // Returns a perspective projection matrix for use with a reversed z-buffer. osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far); // Returns an orthographic projection matrix for use with a reversed z-buffer. osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); // Returns true if the GL format is a depth format bool isDepthFormat(GLenum format); // Returns true if the GL format is a depth+stencil format bool isDepthStencilFormat(GLenum format); // Returns the corresponding source format and type for the given internal format void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); // Converts depth-stencil formats to their corresponding depth formats. GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat); // Brief wrapper around an osg::Depth that applies the reversed depth function when a reversed depth buffer is in use class AutoDepth : public osg::Depth { public: AutoDepth(osg::Depth::Function func=osg::Depth::LESS, double zNear=0.0, double zFar=1.0, bool writeMask=true) { setFunction(func); setZNear(zNear); setZFar(zFar); setWriteMask(writeMask); } AutoDepth(const osg::Depth& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) : osg::Depth(copy, copyop) {} osg::Object* cloneType() const override { return new AutoDepth; } osg::Object* clone(const osg::CopyOp& copyop) const override { return new AutoDepth(*this,copyop); } void apply(osg::State& state) const override { glDepthFunc(static_cast(AutoDepth::isReversed() ? getReversedDepthFunction() : getFunction())); glDepthMask(static_cast(getWriteMask())); #if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) glDepthRangef(getZNear(),getZFar()); #else glDepthRange(getZNear(),getZFar()); #endif } static void setReversed(bool reverseZ) { static bool init = false; if (!init) { AutoDepth::sReversed = reverseZ; init = true; } } static bool isReversed() { return AutoDepth::sReversed; } static void setDepthFormat(GLenum format); static GLenum depthInternalFormat() { return AutoDepth::sDepthInternalFormat; } static GLenum depthSourceFormat() { return AutoDepth::sDepthSourceFormat; } static GLenum depthSourceType() { return AutoDepth::sDepthSourceType; } private: static inline bool sReversed = false; static inline GLenum sDepthSourceFormat = GL_DEPTH_COMPONENT; static inline GLenum sDepthInternalFormat = GL_DEPTH_COMPONENT24; static inline GLenum sDepthSourceType = GL_UNSIGNED_INT; osg::Depth::Function getReversedDepthFunction() const { const osg::Depth::Function func = getFunction(); switch (func) { case osg::Depth::LESS: return osg::Depth::GREATER; case osg::Depth::LEQUAL: return osg::Depth::GEQUAL; case osg::Depth::GREATER: return osg::Depth::LESS; case osg::Depth::GEQUAL: return osg::Depth::LEQUAL; default: return func; } } }; // Replaces all nodes osg::Depth state attributes with SceneUtil::AutoDepth. class ReplaceDepthVisitor : public osg::NodeVisitor { public: ReplaceDepthVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} void apply(osg::Node& node) override { osg::StateSet* stateSet = node.getStateSet(); if (stateSet) { if (osg::Depth* depth = static_cast(stateSet->getAttribute(osg::StateAttribute::DEPTH))) stateSet->setAttribute(new SceneUtil::AutoDepth(*depth)); }; traverse(node); } }; class SelectDepthFormatOperation : public osg::GraphicsOperation { public: SelectDepthFormatOperation() : GraphicsOperation("SelectDepthFormatOperation", false) {} void operator()(osg::GraphicsContext* graphicsContext) override; void setSupportedFormats(const std::vector& supportedFormats) { mSupportedFormats = supportedFormats; } private: std::vector mSupportedFormats; }; } #endif openmw-openmw-0.48.0/components/sceneutil/detourdebugdraw.cpp000066400000000000000000000070211445372753700245070ustar00rootroot00000000000000#include "detourdebugdraw.hpp" #include "util.hpp" #include #include #include namespace { osg::PrimitiveSet::Mode toOsgPrimitiveSetMode(duDebugDrawPrimitives value) { switch (value) { case DU_DRAW_POINTS: return osg::PrimitiveSet::POINTS; case DU_DRAW_LINES: return osg::PrimitiveSet::LINES; case DU_DRAW_TRIS: return osg::PrimitiveSet::TRIANGLES; case DU_DRAW_QUADS: return osg::PrimitiveSet::QUADS; } throw std::logic_error("Can't convert duDebugDrawPrimitives to osg::PrimitiveSet::Mode, value=" + std::to_string(value)); } } namespace SceneUtil { DebugDraw::DebugDraw(osg::Group& group, const osg::ref_ptr& stateSet, const osg::Vec3f& shift, float recastInvertedScaleFactor) : mGroup(group) , mStateSet(stateSet) , mShift(shift) , mRecastInvertedScaleFactor(recastInvertedScaleFactor) , mMode(osg::PrimitiveSet::POINTS) , mSize(1.0f) { } void DebugDraw::depthMask(bool) { } void DebugDraw::texture(bool) { } void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size) { mMode = mode; mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; mSize = size; } void DebugDraw::begin(duDebugDrawPrimitives prim, float size) { begin(toOsgPrimitiveSetMode(prim), size); } void DebugDraw::vertex(const float* pos, unsigned color) { vertex(pos[0], pos[1], pos[2], color); } void DebugDraw::vertex(const float x, const float y, const float z, unsigned color) { addVertex(osg::Vec3f(x, y, z)); addColor(SceneUtil::colourFromRGBA(color)); } void DebugDraw::vertex(const float* pos, unsigned color, const float* uv) { vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]); } void DebugDraw::vertex(const float x, const float y, const float z, unsigned color, const float, const float) { addVertex(osg::Vec3f(x, y, z)); addColor(SceneUtil::colourFromRGBA(color)); } void DebugDraw::end() { osg::ref_ptr geometry(new osg::Geometry); geometry->setStateSet(mStateSet); geometry->setVertexArray(mVertices); geometry->setColorArray(mColors, osg::Array::BIND_PER_VERTEX); geometry->addPrimitiveSet(new osg::DrawArrays(mMode, 0, static_cast(mVertices->size()))); mGroup.addChild(geometry); mColors.release(); mVertices.release(); } void DebugDraw::addVertex(osg::Vec3f&& position) { std::swap(position.y(), position.z()); mVertices->push_back(position * mRecastInvertedScaleFactor + mShift); } void DebugDraw::addColor(osg::Vec4f&& value) { mColors->push_back(value); } osg::ref_ptr DebugDraw::makeStateSet() { osg::ref_ptr stateSet = new osg::StateSet; stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateSet->setMode(GL_DEPTH, osg::StateAttribute::OFF); stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateSet->setAttributeAndModes(new osg::LineWidth()); stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); return stateSet; } } openmw-openmw-0.48.0/components/sceneutil/detourdebugdraw.hpp000066400000000000000000000030331445372753700245130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_DETOURDEBUGDRAW_H #define OPENMW_COMPONENTS_SCENEUTIL_DETOURDEBUGDRAW_H #include #include namespace osg { class Group; } namespace SceneUtil { class DebugDraw : public duDebugDraw { public: explicit DebugDraw(osg::Group& group, const osg::ref_ptr& stateSet, const osg::Vec3f& shift, float recastInvertedScaleFactor); static osg::ref_ptr makeStateSet(); void depthMask(bool state) override; void texture(bool state) override; void begin(osg::PrimitiveSet::Mode mode, float size); void begin(duDebugDrawPrimitives prim, float size) override; void vertex(const float* pos, unsigned int color) override; void vertex(const float x, const float y, const float z, unsigned int color) override; void vertex(const float* pos, unsigned int color, const float* uv) override; void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) override; void end() override; private: osg::Group& mGroup; osg::ref_ptr mStateSet; osg::Vec3f mShift; float mRecastInvertedScaleFactor; osg::PrimitiveSet::Mode mMode; float mSize; osg::ref_ptr mVertices; osg::ref_ptr mColors; void addVertex(osg::Vec3f&& position); void addColor(osg::Vec4f&& value); }; } #endifopenmw-openmw-0.48.0/components/sceneutil/extradata.cpp000066400000000000000000000045031445372753700232770ustar00rootroot00000000000000#include "extradata.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace SceneUtil { void ProcessExtraDataVisitor::setupSoftEffect(osg::Node& node, float size, bool falloff) { if (!mSceneMgr->getSoftParticles()) return; const int unitSoftEffect = mSceneMgr->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); osg::StateSet* stateset = node.getOrCreateStateSet(); stateset->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); stateset->addUniform(new osg::Uniform("particleSize", size)); stateset->addUniform(new osg::Uniform("particleFade", falloff)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); } void ProcessExtraDataVisitor::apply(osg::Node& node) { std::string source; if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) { YAML::Node root = YAML::Load(source); for (const auto& it : root["shader"]) { std::string key = it.first.as(); if (key == "soft_effect") { auto size = it.second["size"].as(45.f); auto falloff = it.second["falloff"].as(false); setupSoftEffect(node, size, falloff); } } node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); } else if (osgParticle::ParticleSystem* partsys = dynamic_cast(&node)) { setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); } traverse(node); } }openmw-openmw-0.48.0/components/sceneutil/extradata.hpp000066400000000000000000000012511445372753700233010ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H #define OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H #include #include #include namespace Resource { class SceneManager; } namespace osg { class Node; } namespace SceneUtil { class ProcessExtraDataVisitor : public osg::NodeVisitor { public: ProcessExtraDataVisitor(Resource::SceneManager* sceneMgr) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mSceneMgr(sceneMgr) {} void apply(osg::Node& node) override; private: void setupSoftEffect(osg::Node& node, float size, bool falloff); Resource::SceneManager* mSceneMgr; }; } #endif openmw-openmw-0.48.0/components/sceneutil/keyframe.hpp000066400000000000000000000037331445372753700231360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP #define OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP #include #include #include #include #include namespace SceneUtil { /// @note Derived classes are expected to derive from osg::Callback and implement getAsCallback(). class KeyframeController : public SceneUtil::Controller, public virtual osg::Object { public: KeyframeController() {} KeyframeController(const KeyframeController& copy) : SceneUtil::Controller(copy) {} virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } /// @note We could drop this function in favour of osg::Object::asCallback from OSG 3.6 on. virtual osg::Callback* getAsCallback() = 0; }; /// Wrapper object containing an animation track as a ref-countable osg::Object. struct TextKeyMapHolder : public osg::Object { public: TextKeyMapHolder() {} TextKeyMapHolder(const TextKeyMapHolder& copy, const osg::CopyOp& copyop) : osg::Object(copy, copyop) , mTextKeys(copy.mTextKeys) {} TextKeyMap mTextKeys; META_Object(SceneUtil, TextKeyMapHolder) }; /// Wrapper object containing the animation track and its KeyframeControllers. class KeyframeHolder : public osg::Object { public: KeyframeHolder() {} KeyframeHolder(const KeyframeHolder& copy, const osg::CopyOp& copyop) : mTextKeys(copy.mTextKeys) , mKeyframeControllers(copy.mKeyframeControllers) { } TextKeyMap mTextKeys; META_Object(SceneUtil, KeyframeHolder) /// Controllers mapped to node name. typedef std::map > KeyframeControllerMap; KeyframeControllerMap mKeyframeControllers; }; } #endif openmw-openmw-0.48.0/components/sceneutil/lightcontroller.cpp000066400000000000000000000042601445372753700245350ustar00rootroot00000000000000#include "lightcontroller.hpp" #include #include #include #include namespace SceneUtil { LightController::LightController() : mType(LT_Normal) , mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f) , mBrightness(0.675f) , mStartTime(0.0) , mLastTime(0.0) , mTicksToAdvance(0.f) { } void LightController::setType(LightController::LightType type) { mType = type; } void LightController::operator ()(SceneUtil::LightSource* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); if (mStartTime == 0) mStartTime = time; // disabled early out, light state needs to be set every frame regardless of change, due to the double buffering //if (time == mLastTime) // return; if (mType == LT_Normal) { node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); traverse(node, nv); return; } // Updating flickering at 15 FPS like vanilla. constexpr float updateRate = 15.f; mTicksToAdvance = static_cast(time - mStartTime - mLastTime) * updateRate * 0.25f + mTicksToAdvance * 0.75f; mLastTime = time - mStartTime; float speed = (mType == LT_Flicker || mType == LT_Pulse) ? 0.1f : 0.05f; if (mBrightness >= mPhase) mBrightness -= mTicksToAdvance * speed; else mBrightness += mTicksToAdvance * speed; if (std::abs(mBrightness - mPhase) < speed) { if (mType == LT_Flicker || mType == LT_FlickerSlow) mPhase = 0.25f + Misc::Rng::rollClosedProbability() * 0.75f; else // if (mType == LT_Pulse || mType == LT_PulseSlow) mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); traverse(node, nv); } void LightController::setDiffuse(const osg::Vec4f& color) { mDiffuseColor = color; } } openmw-openmw-0.48.0/components/sceneutil/lightcontroller.hpp000066400000000000000000000017501445372753700245430ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #include #include namespace SceneUtil { class LightSource; /// @brief Controller class to handle a pulsing and/or flickering light class LightController : public SceneUtil::NodeCallback { public: enum LightType { LT_Normal, LT_Flicker, LT_FlickerSlow, LT_Pulse, LT_PulseSlow }; LightController(); void setType(LightType type); void setDiffuse(const osg::Vec4f& color); void operator()(SceneUtil::LightSource* node, osg::NodeVisitor* nv); private: LightType mType; osg::Vec4f mDiffuseColor; float mPhase; float mBrightness; double mStartTime; double mLastTime; float mTicksToAdvance; }; } #endif openmw-openmw-0.48.0/components/sceneutil/lightmanager.cpp000066400000000000000000001405061445372753700237700ustar00rootroot00000000000000#include "lightmanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int maxLightsLowerLimit = 2; constexpr int maxLightsUpperLimit = 64; constexpr int ffpMaxLights = 8; bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) { static auto constexpr illuminationBias = 81.f; return left->mViewBound.center().length2() - left->mViewBound.radius2()*illuminationBias < right->mViewBound.center().length2() - right->mViewBound.radius2()*illuminationBias; } void configurePosition(osg::Matrixf& mat, const osg::Vec4& pos) { mat(0, 0) = pos.x(); mat(0, 1) = pos.y(); mat(0, 2) = pos.z(); } void configureAmbient(osg::Matrixf& mat, const osg::Vec4& color) { mat(1, 0) = color.r(); mat(1, 1) = color.g(); mat(1, 2) = color.b(); } void configureDiffuse(osg::Matrixf& mat, const osg::Vec4& color) { mat(2, 0) = color.r(); mat(2, 1) = color.g(); mat(2, 2) = color.b(); } void configureSpecular(osg::Matrixf& mat, const osg::Vec4& color) { mat(3, 0) = color.r(); mat(3, 1) = color.g(); mat(3, 2) = color.b(); mat(3, 3) = color.a(); } void configureAttenuation(osg::Matrixf& mat, float c, float l, float q, float r) { mat(0, 3) = c; mat(1, 3) = l; mat(2, 3) = q; mat(3, 3) = r; } } namespace SceneUtil { namespace { const std::unordered_map lightingMethodSettingMap = { {"legacy", LightingMethod::FFP}, {"shaders compatibility", LightingMethod::PerObjectUniform}, {"shaders", LightingMethod::SingleUBO}, }; } static int sLightId = 0; // Handles a GLSL shared layout by using configured offsets and strides to fill a continuous buffer, making the data upload to GPU simpler. class LightBuffer : public osg::Referenced { public: enum LayoutOffset { Diffuse, DiffuseSign, Ambient, Specular, Position, AttenuationRadius }; LightBuffer(int count) : mData(new osg::FloatArray(3*4*count)) , mEndian(osg::getCpuByteOrder()) , mCount(count) , mCachedSunPos(osg::Vec4()) { } LightBuffer(const LightBuffer&) = delete; void setDiffuse(int index, const osg::Vec4& value) { // Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component auto positiveColor = value; unsigned int signBit = 1; if (value[0] < 0) { positiveColor *= -1.0; signBit = ~0u; } unsigned int packedColor = asRGBA(positiveColor); std::memcpy(&(*mData)[getOffset(index, Diffuse)], &packedColor, sizeof(unsigned int)); std::memcpy(&(*mData)[getOffset(index, DiffuseSign)], &signBit, sizeof(unsigned int)); } void setAmbient(int index, const osg::Vec4& value) { unsigned int packed = asRGBA(value); std::memcpy(&(*mData)[getOffset(index, Ambient)], &packed, sizeof(unsigned int)); } void setSpecular(int index, const osg::Vec4& value) { unsigned int packed = asRGBA(value); std::memcpy(&(*mData)[getOffset(index, Specular)], &packed, sizeof(unsigned int)); } void setPosition(int index, const osg::Vec4& value) { std::memcpy(&(*mData)[getOffset(index, Position)], value.ptr(), sizeof(osg::Vec4f)); } void setAttenuationRadius(int index, const osg::Vec4& value) { std::memcpy(&(*mData)[getOffset(index, AttenuationRadius)], value.ptr(), sizeof(osg::Vec4f)); } auto& getData() { return mData; } void dirty() { mData->dirty(); } static constexpr int queryBlockSize(int sz) { return 3 * osg::Vec4::num_components * sizeof(GLfloat) * sz; } void setCachedSunPos(const osg::Vec4& pos) { mCachedSunPos = pos; } void uploadCachedSunPos(const osg::Matrix& viewMat) { osg::Vec4 viewPos = mCachedSunPos * viewMat; std::memcpy(&(*mData)[getOffset(0, Position)], viewPos.ptr(), sizeof(osg::Vec4f)); } unsigned int asRGBA(const osg::Vec4& value) const { return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); } int getOffset(int index, LayoutOffset slot) const { return mOffsets.get(index, slot); } void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride) { configureLayout(Offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride), size); } void configureLayout(const LightBuffer* other) { mOffsets = other->mOffsets; int size = other->mData->size(); configureLayout(mOffsets, size); } private: class Offsets { public: Offsets() : mStride(12) { mValues[Diffuse] = 0; mValues[Ambient] = 1; mValues[Specular] = 2; mValues[DiffuseSign] = 3; mValues[Position] = 4; mValues[AttenuationRadius] = 8; } Offsets(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int stride) : mStride((offsetAttenuationRadius + sizeof(GLfloat) * osg::Vec4::num_components + stride) / 4) { constexpr auto sizeofFloat = sizeof(GLfloat); const auto diffuseOffset = offsetColors / sizeofFloat; mValues[Diffuse] = diffuseOffset; mValues[Ambient] = diffuseOffset + 1; mValues[Specular] = diffuseOffset + 2; mValues[DiffuseSign] = diffuseOffset + 3; mValues[Position] = offsetPosition / sizeofFloat; mValues[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat; } int get(int index, LayoutOffset slot) const { return mStride * index + mValues[slot]; } private: int mStride; std::array mValues; }; void configureLayout(const Offsets& offsets, int size) { // Copy cloned data using current layout into current data using new layout. // This allows to preserve osg::FloatArray buffer object in mData. const auto data = mData->asVector(); mData->resizeArray(static_cast(size)); for (int i = 0; i < mCount; ++i) { std::memcpy(&(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f)); std::memcpy(&(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f)); std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius), sizeof(osg::Vec4f)); } mOffsets = offsets; } osg::ref_ptr mData; osg::Endian mEndian; int mCount; Offsets mOffsets; osg::Vec4 mCachedSunPos; }; struct LightStateCache { std::vector lastAppliedLight; }; LightStateCache* getLightStateCache(size_t contextid, size_t size = 8) { static std::vector cacheVector; if (cacheVector.size() < contextid+1) cacheVector.resize(contextid+1); cacheVector[contextid].lastAppliedLight.resize(size); return &cacheVector[contextid]; } void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode) { auto method = lightManager->getLightingMethod(); switch (method) { case LightingMethod::FFP: { break; } case LightingMethod::PerObjectUniform: { osg::Matrixf lightMat; configurePosition(lightMat, light->getPosition()); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); configureSpecular(lightMat, light->getSpecular()); stateset->addUniform(lightManager->generateLightBufferUniform(lightMat), mode); break; } case LightingMethod::SingleUBO: { osg::ref_ptr buffer = new LightBuffer(lightManager->getMaxLightsInScene()); buffer->setDiffuse(0, light->getDiffuse()); buffer->setAmbient(0, light->getAmbient()); buffer->setSpecular(0, light->getSpecular()); buffer->setPosition(0, light->getPosition()); osg::ref_ptr ubo = new osg::UniformBufferObject; buffer->getData()->setBufferObject(ubo); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); stateset->setAttributeAndModes(ubb, mode); break; } } } class DisableLight : public osg::StateAttribute { public: DisableLight() : mIndex(0) {} DisableLight(int index) : mIndex(index) {} DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} META_StateAttribute(SceneUtil, DisableLight, osg::StateAttribute::LIGHT) unsigned int getMember() const override { return mIndex; } bool getModeUsage(ModeUsage& usage) const override { usage.usesMode(GL_LIGHT0 + mIndex); return true; } int compare(const StateAttribute& sa) const override { throw std::runtime_error("DisableLight::compare: unimplemented"); } void apply(osg::State& state) const override { int lightNum = GL_LIGHT0 + mIndex; glLightfv(lightNum, GL_AMBIENT, mNullptr.ptr()); glLightfv(lightNum, GL_DIFFUSE, mNullptr.ptr()); glLightfv(lightNum, GL_SPECULAR, mNullptr.ptr()); LightStateCache* cache = getLightStateCache(state.getContextID()); cache->lastAppliedLight[mIndex] = nullptr; } private: size_t mIndex; osg::Vec4f mNullptr; }; class FFPLightStateAttribute : public osg::StateAttribute { public: FFPLightStateAttribute() : mIndex(0) {} FFPLightStateAttribute(size_t index, const std::vector>& lights) : mIndex(index), mLights(lights) {} FFPLightStateAttribute(const FFPLightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} unsigned int getMember() const override { return mIndex; } bool getModeUsage(ModeUsage& usage) const override { for (size_t i = 0; i < mLights.size(); ++i) usage.usesMode(GL_LIGHT0 + mIndex + i); return true; } int compare(const StateAttribute& sa) const override { throw std::runtime_error("FFPLightStateAttribute::compare: unimplemented"); } META_StateAttribute(SceneUtil, FFPLightStateAttribute, osg::StateAttribute::LIGHT) void apply(osg::State& state) const override { if (mLights.empty()) return; osg::Matrix modelViewMatrix = state.getModelViewMatrix(); state.applyModelViewMatrix(state.getInitialViewMatrix()); LightStateCache* cache = getLightStateCache(state.getContextID()); for (size_t i = 0; i < mLights.size(); ++i) { osg::Light* current = cache->lastAppliedLight[i+mIndex]; if (current != mLights[i].get()) { applyLight((GLenum)((int)GL_LIGHT0 + i + mIndex), mLights[i].get()); cache->lastAppliedLight[i+mIndex] = mLights[i].get(); } } state.applyModelViewMatrix(modelViewMatrix); } void applyLight(GLenum lightNum, const osg::Light* light) const { glLightfv(lightNum, GL_AMBIENT, light->getAmbient().ptr()); glLightfv(lightNum, GL_DIFFUSE, light->getDiffuse().ptr()); glLightfv(lightNum, GL_SPECULAR, light->getSpecular().ptr()); glLightfv(lightNum, GL_POSITION, light->getPosition().ptr()); // TODO: enable this once spot lights are supported // need to transform SPOT_DIRECTION by the world matrix? //glLightfv(lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr()); //glLightf(lightNum, GL_SPOT_EXPONENT, light->getSpotExponent()); //glLightf(lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff()); glLightf(lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation()); glLightf(lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation()); glLightf(lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation()); } private: size_t mIndex; std::vector> mLights; }; struct StateSetGenerator { LightManager* mLightManager; virtual ~StateSetGenerator() {} virtual osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) = 0; virtual void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) {} osg::Matrix mViewMatrix; }; struct StateSetGeneratorFFP : StateSetGenerator { osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; std::vector> lights; lights.reserve(lightList.size()); for (size_t i = 0; i < lightList.size(); ++i) lights.emplace_back(lightList[i]->mLightSource->getLight(frameNum)); // the first light state attribute handles the actual state setting for all lights // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary // don't use setAttributeAndModes, that does not support light indices! stateset->setAttribute(new FFPLightStateAttribute(mLightManager->getStartLight(), std::move(lights)), osg::StateAttribute::ON); for (size_t i = 0; i < lightList.size(); ++i) stateset->setMode(GL_LIGHT0 + mLightManager->getStartLight() + i, osg::StateAttribute::ON); // need to push some dummy attributes to ensure proper state tracking // lights need to reset to their default when the StateSet is popped for (size_t i = 1; i < lightList.size(); ++i) stateset->setAttribute(mLightManager->getDummies()[i + mLightManager->getStartLight()].get(), osg::StateAttribute::ON); return stateset; } }; struct StateSetGeneratorSingleUBO : StateSetGenerator { osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", mLightManager->getMaxLights()); int pointCount = 0; for (size_t i = 0; i < lightList.size(); ++i) { int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()]; indicesUni->setElement(pointCount++, bufIndex); } stateset->addUniform(indicesUni); stateset->addUniform(new osg::Uniform("PointLightCount", pointCount)); return stateset; } // Cached statesets must be revalidated in case the light indices change. There is no actual link between // a light's ID and the buffer index it will eventually be assigned (or reassigned) to. void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) override { int newCount = 0; int oldCount; auto uOldArray = stateset->getUniform("PointLightIndex"); auto uOldCount = stateset->getUniform("PointLightCount"); uOldCount->get(oldCount); // max lights count can change during runtime oldCount = std::min(mLightManager->getMaxLights(), oldCount); auto& lightData = mLightManager->getLightIndexMap(frameNum); for (int i = 0; i < oldCount; ++i) { auto* lightSource = lightList[i]->mLightSource; auto it = lightData.find(lightSource->getId()); if (it != lightData.end()) uOldArray->setElement(newCount++, it->second); } uOldArray->dirty(); uOldCount->set(newCount); } }; struct StateSetGeneratorPerObjectUniform : StateSetGenerator { osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr data = mLightManager->generateLightBufferUniform(mLightManager->getSunlightBuffer(frameNum)); for (size_t i = 0; i < lightList.size(); ++i) { auto* light = lightList[i]->mLightSource->getLight(frameNum); osg::Matrixf lightMat; configurePosition(lightMat, light->getPosition() * mViewMatrix); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius()); data->setElement(i+1, lightMat); } stateset->addUniform(data); stateset->addUniform(new osg::Uniform("PointLightCount", static_cast(lightList.size() + 1))); return stateset; } }; LightManager* findLightManager(const osg::NodePath& path) { for (size_t i = 0; i < path.size(); ++i) { if (LightManager* lightManager = dynamic_cast(path[i])) return lightManager; } return nullptr; } // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. class CollectLightCallback : public NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) : NodeCallback(copy, copyop) , mLightManager(nullptr) { } META_Object(SceneUtil, CollectLightCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (!mLightManager) { mLightManager = findLightManager(nv->getNodePath()); if (!mLightManager) throw std::runtime_error("can't find parent LightManager"); } mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); traverse(node, nv); } private: LightManager* mLightManager; }; // Set on a LightManager. Clears the data from the previous frame. class LightManagerUpdateCallback : public SceneUtil::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) { LightManager* lightManager = static_cast(node); lightManager->update(nv->getTraversalNumber()); traverse(node, nv); } }; class LightManagerCullCallback : public SceneUtil::NodeCallback { public: LightManagerCullCallback(LightManager* lightManager) { if (!lightManager->getUBOManager()) return; for (size_t i = 0; i < mUBBs.size(); ++i) { auto& buffer = lightManager->getUBOManager()->getLightBuffer(i); mUBBs[i] = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); } } void operator()(LightManager* node, osgUtil::CullVisitor* cv) { osg::ref_ptr stateset = new osg::StateSet; if (node->getLightingMethod() == LightingMethod::SingleUBO) { const size_t frameId = cv->getTraversalNumber() % 2; stateset->setAttributeAndModes(mUBBs[frameId], osg::StateAttribute::ON); auto& buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber()); if (auto sun = node->getSunlight()) { buffer->setCachedSunPos(sun->getPosition()); buffer->setAmbient(0, sun->getAmbient()); buffer->setDiffuse(0, sun->getDiffuse()); buffer->setSpecular(0, sun->getSpecular()); } } else if (node->getLightingMethod() == LightingMethod::PerObjectUniform) { if (auto sun = node->getSunlight()) { osg::Matrixf lightMat; configurePosition(lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); configureAmbient(lightMat, sun->getAmbient()); configureDiffuse(lightMat, sun->getDiffuse()); configureSpecular(lightMat, sun->getSpecular()); node->setSunlightBuffer(lightMat, cv->getTraversalNumber()); stateset->addUniform(node->generateLightBufferUniform(lightMat)); } } cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); if (node->getPPLightsBuffer() && cv->getCurrentCamera()->getName() == Constants::SceneCamera) node->getPPLightsBuffer()->updateCount(cv->getTraversalNumber()); } std::array, 2> mUBBs; }; UBOManager::UBOManager(int lightCount) : mDummyProgram(new osg::Program) , mInitLayout(false) , mDirty({ true, true }) , mTemplate(new LightBuffer(lightCount)) { static const std::string dummyVertSource = generateDummyShader(lightCount); // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Resource::SceneManager::UBOBinding::LightBuffer)); for (size_t i = 0; i < mLightBuffers.size(); ++i) { mLightBuffers[i] = new LightBuffer(lightCount); osg::ref_ptr ubo = new osg::UniformBufferObject; ubo->setUsage(GL_STREAM_DRAW); mLightBuffers[i]->getData()->setBufferObject(ubo); } } UBOManager::UBOManager(const UBOManager& copy, const osg::CopyOp& copyop) : osg::StateAttribute(copy,copyop), mDummyProgram(copy.mDummyProgram), mInitLayout(copy.mInitLayout) {} void UBOManager::releaseGLObjects(osg::State* state) const { mDummyProgram->releaseGLObjects(state); } int UBOManager::compare(const StateAttribute &sa) const { throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); } void UBOManager::apply(osg::State& state) const { unsigned int frame = state.getFrameStamp()->getFrameNumber(); unsigned int index = frame % 2; if (!mInitLayout) { mDummyProgram->apply(state); auto handle = mDummyProgram->getPCP(state)->getHandle(); auto* ext = state.get(); int activeUniformBlocks = 0; ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); // wait until the UBO binding is created if (activeUniformBlocks > 0) { initSharedLayout(ext, handle, frame); mInitLayout = true; } } else if (mDirty[index]) { mDirty[index] = false; mLightBuffers[index]->configureLayout(mTemplate); } mLightBuffers[index]->uploadCachedSunPos(state.getInitialViewMatrix()); mLightBuffers[index]->dirty(); } std::string UBOManager::generateDummyShader(int maxLightsInScene) { const std::string define = "@maxLightsInScene"; std::string shader = R"GLSL( #version 120 #extension GL_ARB_uniform_buffer_object : require struct LightData { ivec4 packedColors; vec4 position; vec4 attenuation; }; uniform LightBufferBinding { LightData LightBuffer[@maxLightsInScene]; }; void main() { gl_Position = vec4(0.0); } )GLSL"; shader.replace(shader.find(define), define.length(), std::to_string(maxLightsInScene)); return shader; } void UBOManager::initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const { constexpr std::array index = { static_cast(Resource::SceneManager::UBOBinding::LightBuffer) }; int totalBlockSize = -1; int stride = -1; ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize); ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride); std::array names = { "LightBuffer[0].packedColors", "LightBuffer[0].position", "LightBuffer[0].attenuation", }; std::vector indices(names.size()); std::vector offsets(names.size()); ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data()); ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); mTemplate->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); } LightingMethod LightManager::getLightingMethodFromString(const std::string& value) { auto it = lightingMethodSettingMap.find(value); if (it != lightingMethodSettingMap.end()) return it->second; constexpr const char* fallback = "shaders compatibility"; Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; return LightingMethod::PerObjectUniform; } std::string LightManager::getLightingMethodString(LightingMethod method) { for (const auto& p : lightingMethodSettingMap) if (p.second == method) return p.first; return ""; } LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) , mPointLightRadiusMultiplier(1.f) , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; mSupported[static_cast(LightingMethod::FFP)] = true; mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; mSupported[static_cast(LightingMethod::SingleUBO)] = supportsUBO && supportsGPU4; setUpdateCallback(new LightManagerUpdateCallback); if (ffp) { initFFP(ffpMaxLights); return; } std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); static bool hasLoggedWarnings = false; if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) Log(Debug::Warning) << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode"; if (!supportsGPU4) Log(Debug::Warning) << "GL_EXT_gpu_shader4 not supported: switching to shader compatibility lighting mode"; hasLoggedWarnings = true; } const int targetLights = std::clamp(Settings::Manager::getInt("max lights", "Shaders"), maxLightsLowerLimit, maxLightsUpperLimit); if (!supportsUBO || !supportsGPU4 || lightingMethod == LightingMethod::PerObjectUniform) initPerObjectUniform(targetLights); else initSingleUBO(targetLights); updateSettings(); getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); addCullCallback(new LightManagerCullCallback(this)); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) , mLightingMask(copy.mLightingMask) , mSun(copy.mSun) , mLightingMethod(copy.mLightingMethod) , mPointLightRadiusMultiplier(copy.mPointLightRadiusMultiplier) , mPointLightFadeEnd(copy.mPointLightFadeEnd) , mPointLightFadeStart(copy.mPointLightFadeStart) , mMaxLights(copy.mMaxLights) , mPPLightBuffer(copy.mPPLightBuffer) { } LightingMethod LightManager::getLightingMethod() const { return mLightingMethod; } bool LightManager::usingFFP() const { return mLightingMethod == LightingMethod::FFP; } int LightManager::getMaxLights() const { return mMaxLights; } void LightManager::setMaxLights(int value) { mMaxLights = value; } int LightManager::getMaxLightsInScene() const { static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); return max; } Shader::ShaderManager::DefineMap LightManager::getLightDefines() const { Shader::ShaderManager::DefineMap defines; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); defines["lightingMethodFFP"] = getLightingMethod() == LightingMethod::FFP ? "1" : "0"; defines["lightingMethodPerObjectUniform"] = getLightingMethod() == LightingMethod::PerObjectUniform ? "1" : "0"; defines["lightingMethodUBO"] = getLightingMethod() == LightingMethod::SingleUBO ? "1" : "0"; defines["useUBO"] = std::to_string(getLightingMethod() == LightingMethod::SingleUBO); // exposes bitwise operators defines["useGPUShader4"] = std::to_string(getLightingMethod() == LightingMethod::SingleUBO); defines["getLight"] = getLightingMethod() == LightingMethod::FFP ? "gl_LightSource" : "LightBuffer"; defines["startLight"] = getLightingMethod() == LightingMethod::SingleUBO ? "0" : "1"; defines["endLight"] = getLightingMethod() == LightingMethod::FFP ? defines["maxLights"] : "PointLightCount"; return defines; } void LightManager::processChangedSettings(const Settings::CategorySettingVector& changed) { updateSettings(); } void LightManager::updateMaxLights() { if (usingFFP()) return; setMaxLights(std::clamp(Settings::Manager::getInt("max lights", "Shaders"), maxLightsLowerLimit, maxLightsUpperLimit)); if (getLightingMethod() == LightingMethod::PerObjectUniform) { getStateSet()->removeUniform("LightBuffer"); getStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf())); } for (auto& cache : mStateSetCache) cache.clear(); } void LightManager::updateSettings() { if (getLightingMethod() == LightingMethod::FFP) return; mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 5.f); mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); if (mPointLightFadeEnd > 0) { mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; } } void LightManager::initFFP(int targetLights) { setLightingMethod(LightingMethod::FFP); setMaxLights(targetLights); for (int i = 0; i < getMaxLights(); ++i) mDummies.push_back(new FFPLightStateAttribute(i, std::vector>())); } void LightManager::initPerObjectUniform(int targetLights) { setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(targetLights); getOrCreateStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf())); } void LightManager::initSingleUBO(int targetLights) { setLightingMethod(LightingMethod::SingleUBO); setMaxLights(targetLights); mUBOManager = new UBOManager(getMaxLightsInScene()); getOrCreateStateSet()->setAttributeAndModes(mUBOManager); } void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; switch (method) { case LightingMethod::FFP: mStateSetGenerator = std::make_unique(); break; case LightingMethod::SingleUBO: mStateSetGenerator = std::make_unique(); break; case LightingMethod::PerObjectUniform: mStateSetGenerator = std::make_unique(); break; } mStateSetGenerator->mLightManager = this; } void LightManager::setLightingMask(size_t mask) { mLightingMask = mask; } size_t LightManager::getLightingMask() const { return mLightingMask; } void LightManager::setStartLight(int start) { mStartLight = start; if (!usingFFP()) return; // Set default light state to zero // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling // we'll have to set a light state that has no visible effect for (int i = start; i < getMaxLights(); ++i) { osg::ref_ptr defaultLight (new DisableLight(i)); getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); } } int LightManager::getStartLight() const { return mStartLight; } void LightManager::update(size_t frameNum) { if (mPPLightBuffer) mPPLightBuffer->clear(frameNum); getLightIndexMap(frameNum).clear(); mLights.clear(); mLightsInViewSpace.clear(); // Do an occasional cleanup for orphaned lights. for (int i = 0; i < 2; ++i) { if (mStateSetCache[i].size() > 5000) mStateSetCache[i].clear(); } } void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum) { LightSourceTransform l; l.mLightSource = lightSource; l.mWorldMatrix = worldMat; osg::Vec3f pos = osg::Vec3f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z()); lightSource->getLight(frameNum)->setPosition(osg::Vec4f(pos, 1.f)); mLights.push_back(l); } void LightManager::setSunlight(osg::ref_ptr sun) { if (usingFFP()) return; mSun = sun; } osg::ref_ptr LightManager::getSunlight() { return mSun; } size_t LightManager::HashLightIdList::operator()(const LightIdList& lightIdList) const { size_t hash = 0; for (size_t i = 0; i < lightIdList.size(); ++i) Misc::hashCombine(hash, lightIdList[i]); return hash; } osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) { if (getLightingMethod() == LightingMethod::PerObjectUniform) { mStateSetGenerator->mViewMatrix = *viewMatrix; return mStateSetGenerator->generate(lightList, frameNum); } // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) if (getLightingMethod() == LightingMethod::SingleUBO) { for (size_t i = 0; i < lightList.size(); ++i) { auto id = lightList[i]->mLightSource->getId(); if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end()) continue; int index = getLightIndexMap(frameNum).size() + 1; updateGPUPointLight(index, lightList[i]->mLightSource, frameNum, viewMatrix); getLightIndexMap(frameNum).emplace(id, index); } } auto& stateSetCache = mStateSetCache[frameNum%2]; LightIdList lightIdList; lightIdList.reserve(lightList.size()); std::transform(lightList.begin(), lightList.end(), std::back_inserter(lightIdList), [] (const LightSourceViewBound* l) { return l->mLightSource->getId(); }); auto found = stateSetCache.find(lightIdList); if (found != stateSetCache.end()) { mStateSetGenerator->update(found->second, lightList, frameNum); return found->second; } auto stateset = mStateSetGenerator->generate(lightList, frameNum); stateSetCache.emplace(lightIdList, stateset); return stateset; } const std::vector& LightManager::getLightsInViewSpace(osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::Camera* camera = cv->getCurrentCamera(); osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); if (it == mLightsInViewSpace.end()) { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); float radius = transform.mLightSource->getRadius(); osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius); transformBoundingSphere(worldViewMat, viewBound); if (transform.mLightSource->getLastAppliedFrame() != frameNum && mPointLightFadeEnd != 0.f) { const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; const float viewDelta = viewBound.center().length() - mPointLightFadeStart; float fade = 1 - std::clamp(viewDelta / fadeDelta, 0.f, 1.f); if (fade == 0.f) continue; auto* light = transform.mLightSource->getLight(frameNum); light->setDiffuse(light->getDiffuse() * fade); transform.mLightSource->setLastAppliedFrame(frameNum); } // remove lights culled by this camera if (!usingFFP()) { viewBound._radius *= 2.f; if (cv->getModelViewCullingStack().front().isCulled(viewBound)) continue; viewBound._radius /= 2.f; } viewBound._radius *= mPointLightRadiusMultiplier; LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; it->second.push_back(l); } const bool fillPPLights = mPPLightBuffer && it->first->getName() == Constants::SceneCamera; if (fillPPLights || getLightingMethod() == LightingMethod::SingleUBO) { auto sorter = [] (const LightSourceViewBound& left, const LightSourceViewBound& right) { return left.mViewBound.center().length2() - left.mViewBound.radius2() < right.mViewBound.center().length2() - right.mViewBound.radius2(); }; std::sort(it->second.begin(), it->second.end(), sorter); if (fillPPLights) { for (const auto& bound : it->second) { if (bound.mLightSource->getEmpty()) continue; const auto* light = bound.mLightSource->getLight(frameNum); if (light->getDiffuse().x() >= 0.f) mPPLightBuffer->setLight(frameNum, light, bound.mLightSource->getRadius()); } } if (it->second.size() > static_cast(getMaxLightsInScene() - 1)) it->second.resize(getMaxLightsInScene() - 1); } } return it->second; } void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum,const osg::RefMatrix* viewMatrix) { auto* light = lightSource->getLight(frameNum); auto& buf = getUBOManager()->getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); buf->setPosition(index, light->getPosition() * (*viewMatrix)); } osg::ref_ptr LightManager::generateLightBufferUniform(const osg::Matrixf& sun) { osg::ref_ptr uniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights()); uniform->setElement(0, sun); return uniform; } void LightManager::setCollectPPLights(bool enabled) { if (enabled) mPPLightBuffer = std::make_shared(); else mPPLightBuffer = nullptr; } LightSource::LightSource() : mRadius(0.f) , mActorFade(1.f) , mLastAppliedFrame(0) { setUpdateCallback(new CollectLightCallback); mId = sLightId++; } LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op) : osg::Node(copy, copyop) , mRadius(copy.mRadius) , mActorFade(copy.mActorFade) , mLastAppliedFrame(copy.mLastAppliedFrame) { mId = sLightId++; for (size_t i = 0; i < mLight.size(); ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } void LightListCallback::operator()(osg::Node *node, osgUtil::CullVisitor *cv) { bool pushedState = pushLightState(node, cv); traverse(node, cv); if (pushedState) cv->popStateSet(); } bool LightListCallback::pushLightState(osg::Node *node, osgUtil::CullVisitor *cv) { if (!mLightManager) { mLightManager = findLightManager(cv->getNodePath()); if (!mLightManager) return false; } if (!(cv->getTraversalMask() & mLightManager->getLightingMask())) return false; // Possible optimizations: // - organize lights in a quad tree mLastFrameNumber = cv->getTraversalNumber(); // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv, viewMatrix, mLastFrameNumber); // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; osg::Transform* transform = node->asTransform(); if (transform) { for (size_t i = 0; i < transform->getNumChildren(); ++i) nodeBound.expandBy(transform->getChild(i)->getBound()); } else nodeBound = node->getBound(); osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); mLightList.clear(); for (size_t i = 0; i < lights.size(); ++i) { const LightManager::LightSourceViewBound& l = lights[i]; if (mIgnoredLightSources.count(l.mLightSource)) continue; if (l.mViewBound.intersects(nodeBound)) mLightList.push_back(&l); } if (!mLightList.empty()) { size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); osg::ref_ptr stateset = nullptr; if (mLightList.size() > maxLights) { LightManager::LightList lightList = mLightList; if (mLightManager->usingFFP()) { for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { osg::BoundingSphere bs = (*it)->mViewBound; bs._radius = bs._radius * 2.0; if (cv->getModelViewCullingStack().front().isCulled(bs)) it = lightList.erase(it); else ++it; } } // sort by proximity to camera, then get rid of furthest away lights std::sort(lightList.begin(), lightList.end(), sortLights); while (lightList.size() > maxLights) lightList.pop_back(); stateset = mLightManager->getLightListStateSet(lightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); } else stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); cv->pushStateSet(stateset); return true; } return false; } } openmw-openmw-0.48.0/components/sceneutil/lightmanager.hpp000066400000000000000000000341671445372753700240020ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include #include #include #include #include #include #include #include #include #include #include namespace osgUtil { class CullVisitor; } namespace SceneUtil { class LightBuffer; struct StateSetGenerator; class PPLightBuffer { public: inline static constexpr auto sMaxPPLights = 40; inline static constexpr auto sMaxPPLightsArraySize = sMaxPPLights * 3; PPLightBuffer() { for (size_t i = 0; i < 2; ++i) { mIndex[i] = 0; mUniformBuffers[i] = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "omw_PointLights", sMaxPPLightsArraySize); mUniformCount[i] = new osg::Uniform("omw_PointLightsCount", static_cast(0)); } } void applyUniforms(size_t frame, osg::StateSet* stateset) { size_t frameId = frame % 2; if (!stateset->getUniform("omw_PointLights")) stateset->addUniform(mUniformBuffers[frameId]); if (!stateset->getUniform("omw_PointLightsCount")) stateset->addUniform(mUniformCount[frameId]); mUniformBuffers[frameId]->dirty(); mUniformCount[frameId]->dirty(); } void clear(size_t frame) { mIndex[frame % 2] = 0; } void setLight(size_t frame, const osg::Light* light, float radius) { size_t frameId = frame % 2; size_t i = mIndex[frameId]; if (i >= (sMaxPPLights - 1)) return; i *= 3; mUniformBuffers[frameId]->setElement(i + 0, light->getPosition()); mUniformBuffers[frameId]->setElement(i + 1, light->getDiffuse()); mUniformBuffers[frameId]->setElement(i + 2, osg::Vec4f(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), radius)); mIndex[frameId]++; } void updateCount(size_t frame) { size_t frameId = frame % 2; mUniformCount[frameId]->set(static_cast(mIndex[frameId])); } private: std::array mIndex; std::array, 2> mUniformBuffers; std::array, 2> mUniformCount; }; enum class LightingMethod { FFP, PerObjectUniform, SingleUBO, }; /// LightSource managed by a LightManager. /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene /// so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource instead. /// @note LightSources must be decorated by a LightManager node in order to have an effect. Typical use would /// be one LightManager as the root of the scene graph. /// @note One needs to attach LightListCallback's to the scene to have objects receive lighting from LightSources. /// See the documentation of LightListCallback for more information. /// @note The position of the contained osg::Light is automatically updated based on the LightSource's world position. class LightSource : public osg::Node { // double buffered osg::Light's, since one of them may be in use by the draw thread at any given time std::array, 2> mLight; // LightSource will affect objects within this radius float mRadius; int mId; float mActorFade; unsigned int mLastAppliedFrame; bool mEmpty = false; public: META_Node(SceneUtil, LightSource) LightSource(); LightSource(const LightSource& copy, const osg::CopyOp& copyop); float getRadius() const { return mRadius; } /// The LightSource will affect objects within this radius. void setRadius(float radius) { mRadius = radius; } void setActorFade(float alpha) { mActorFade = alpha; } float getActorFade() const { return mActorFade; } void setEmpty(bool empty) { mEmpty = empty; } bool getEmpty() const { return mEmpty; } /// Get the osg::Light safe for modification in the given frame. /// @par May be used externally to animate the light's color/attenuation properties, /// and is used internally to synchronize the light's position with the position of the LightSource. osg::Light* getLight(size_t frame) { return mLight[frame % 2]; } /// @warning It is recommended not to replace an existing osg::Light, because there might still be /// references to it in the light StateSet cache that are associated with this LightSource's ID. /// These references will stay valid due to ref_ptr but will point to the old object. /// @warning Do not modify the \a light after you've called this function. void setLight(osg::Light* light) { mLight[0] = light; mLight[1] = new osg::Light(*light); } /// Get the unique ID for this light source. int getId() const { return mId; } void setLastAppliedFrame(unsigned int lastAppliedFrame) { mLastAppliedFrame = lastAppliedFrame; } unsigned int getLastAppliedFrame() const { return mLastAppliedFrame; } }; class UBOManager : public osg::StateAttribute { public: UBOManager(int lightCount=1); UBOManager(const UBOManager& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); void releaseGLObjects(osg::State* state) const override; int compare(const StateAttribute& sa) const override; META_StateAttribute(SceneUtil, UBOManager, osg::StateAttribute::LIGHT) void apply(osg::State& state) const override; auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } private: std::string generateDummyShader(int maxLightsInScene); void initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const; osg::ref_ptr mDummyProgram; mutable bool mInitLayout; mutable std::array, 2> mLightBuffers; mutable std::array mDirty; osg::ref_ptr mTemplate; }; /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the subgraph. class LightManager : public osg::Group { public: static LightingMethod getLightingMethodFromString(const std::string& value); /// Returns string as used in settings file, or the empty string if the method is undefined static std::string getLightingMethodString(LightingMethod method); struct LightSourceTransform { LightSource* mLightSource; osg::Matrixf mWorldMatrix; }; struct LightSourceViewBound { LightSource* mLightSource; osg::BoundingSphere mViewBound; }; using LightList = std::vector; using SupportedMethods = std::array; META_Node(SceneUtil, LightManager) LightManager(bool ffp = true); LightManager(const LightManager& copy, const osg::CopyOp& copyop); /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include /// the lightingMask for a much faster cull and rendering. void setLightingMask(size_t mask); size_t getLightingMask() const; /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. void setStartLight(int start); int getStartLight() const; /// Internal use only, called automatically by the LightManager's UpdateCallback void update(size_t frameNum); /// Internal use only, called automatically by the LightSource's UpdateCallback void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); const std::vector& getLightsInViewSpace(osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix); void setSunlight(osg::ref_ptr sun); osg::ref_ptr getSunlight(); bool usingFFP() const; LightingMethod getLightingMethod() const; int getMaxLights() const; int getMaxLightsInScene() const; auto& getDummies() { return mDummies; } auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; } auto& getUBOManager() { return mUBOManager; } osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum%2]; } void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum%2] = buffer; } SupportedMethods getSupportedLightingMethods() { return mSupported; } std::map getLightDefines() const; void processChangedSettings(const Settings::CategorySettingVector& changed); /// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer void updateMaxLights(); osg::ref_ptr generateLightBufferUniform(const osg::Matrixf& sun); // Whether to collect main scene camera points lights into a buffer to be later sent to postprocessing shaders void setCollectPPLights(bool enabled); std::shared_ptr getPPLightsBuffer() { return mPPLightBuffer; } private: void initFFP(int targetLights); void initPerObjectUniform(int targetLights); void initSingleUBO(int targetLights); void updateSettings(); void setLightingMethod(LightingMethod method); void setMaxLights(int value); void updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum, const osg::RefMatrix* viewMatrix); std::vector mLights; using LightSourceViewBoundCollection = std::vector; std::map, LightSourceViewBoundCollection> mLightsInViewSpace; using LightIdList = std::vector; struct HashLightIdList { size_t operator()(const LightIdList&) const; }; using LightStateSetMap = std::unordered_map, HashLightIdList>; LightStateSetMap mStateSetCache[2]; std::vector> mDummies; int mStartLight; size_t mLightingMask; osg::ref_ptr mSun; osg::Matrixf mSunlightBuffers[2]; // < Light ID , Buffer Index > using LightIndexMap = std::unordered_map; LightIndexMap mLightIndexMaps[2]; std::unique_ptr mStateSetGenerator; osg::ref_ptr mUBOManager; LightingMethod mLightingMethod; float mPointLightRadiusMultiplier; float mPointLightFadeEnd; float mPointLightFadeStart; int mMaxLights; SupportedMethods mSupported; std::shared_ptr mPPLightBuffer; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via /// node->addCullCallback(new LightListCallback). Once a light list callback is added to a node, that node and all /// its child nodes can receive lighting. /// @par The placement of these LightListCallbacks affects the granularity of light lists. Having too fine grained /// light lists can result in degraded performance. Too coarse grained light lists can result in lights no longer /// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good /// starting point is to attach a LightListCallback to each game object's base node. /// @note Not thread safe for CullThreadPerCamera threading mode. /// @note Due to lack of OSG support, the callback does not work on Drawables. class LightListCallback : public SceneUtil::NodeCallback { public: LightListCallback() : mLightManager(nullptr) , mLastFrameNumber(0) {} LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) , mLightManager(copy.mLightManager) , mLastFrameNumber(0) , mIgnoredLightSources(copy.mIgnoredLightSources) {} META_Object(SceneUtil, LightListCallback) void operator()(osg::Node* node, osgUtil::CullVisitor* nv); bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv); std::set& getIgnoredLightSources() { return mIgnoredLightSources; } private: LightManager* mLightManager; size_t mLastFrameNumber; LightManager::LightList mLightList; std::set mIgnoredLightSources; }; void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } #endif openmw-openmw-0.48.0/components/sceneutil/lightutil.cpp000066400000000000000000000124041445372753700233260ustar00rootroot00000000000000#include "lightutil.hpp" #include #include #include #include #include #include "lightmanager.hpp" #include "lightcontroller.hpp" #include "util.hpp" #include "visitor.hpp" namespace { class CheckEmptyLightVisitor : public osg::NodeVisitor { public: CheckEmptyLightVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} void apply(osg::Drawable& drawable) override { if (!mEmpty) return; if (dynamic_cast(&drawable)) mEmpty = false; else traverse(drawable); } void apply(osg::Geometry& geometry) override { mEmpty = false; } bool mEmpty = true; }; } namespace SceneUtil { void configureLight(osg::Light *light, float radius, bool isExterior) { float quadraticAttenuation = 0.f; float linearAttenuation = 0.f; float constantAttenuation = 0.f; static const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant"); static const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear"); static const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic"); static const float constantValue = Fallback::Map::getFloat("LightAttenuation_ConstantValue"); static const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue"); static const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue"); static const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult"); static const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult"); static const int linearMethod = Fallback::Map::getInt("LightAttenuation_LinearMethod"); static const int quadraticMethod = Fallback::Map::getInt("LightAttenuation_QuadraticMethod"); static const bool outQuadInLin = Fallback::Map::getBool("LightAttenuation_OutQuadInLin"); if (useConstant) constantAttenuation = constantValue; if (useLinear) { linearAttenuation = linearMethod == 0 ? linearValue : 0.01f; float r = radius * linearRadiusMult; if (r && (linearMethod == 1 || linearMethod == 2)) linearAttenuation = linearValue / std::pow(r, linearMethod); } if (useQuadratic && (!outQuadInLin || isExterior)) { quadraticAttenuation = quadraticMethod == 0 ? quadraticValue : 0.01f; float r = radius * quadraticRadiusMult; if (r && (quadraticMethod == 1 || quadraticMethod == 2)) quadraticAttenuation = quadraticValue / std::pow(r, quadraticMethod); } light->setConstantAttenuation(constantAttenuation); light->setLinearAttenuation(linearAttenuation); light->setQuadraticAttenuation(quadraticAttenuation); } osg::ref_ptr addLight(osg::Group* node, const ESM::Light* esmLight, unsigned int lightMask, bool isExterior) { SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); osg::Group* attachTo = visitor.mFoundNode ? visitor.mFoundNode : node; osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior, osg::Vec4f(0,0,0,1)); attachTo->addChild(lightSource); CheckEmptyLightVisitor emptyVisitor; node->accept(emptyVisitor); lightSource->setEmpty(emptyVisitor.mEmpty); return lightSource; } osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient) { osg::ref_ptr lightSource (new SceneUtil::LightSource); osg::ref_ptr light (new osg::Light); lightSource->setNodeMask(lightMask); float radius = esmLight->mData.mRadius; lightSource->setRadius(radius); configureLight(light, radius, isExterior); osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); if (esmLight->mData.mFlags & ESM::Light::Negative) { diffuse *= -1; diffuse.a() = 1; } light->setDiffuse(diffuse); light->setAmbient(ambient); light->setSpecular(osg::Vec4f(0,0,0,0)); lightSource->setLight(light); osg::ref_ptr ctrl (new SceneUtil::LightController); ctrl->setDiffuse(light->getDiffuse()); if (esmLight->mData.mFlags & ESM::Light::Flicker) ctrl->setType(SceneUtil::LightController::LT_Flicker); if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); if (esmLight->mData.mFlags & ESM::Light::Pulse) ctrl->setType(SceneUtil::LightController::LT_Pulse); if (esmLight->mData.mFlags & ESM::Light::PulseSlow) ctrl->setType(SceneUtil::LightController::LT_PulseSlow); lightSource->addUpdateCallback(ctrl); return lightSource; } } openmw-openmw-0.48.0/components/sceneutil/lightutil.hpp000066400000000000000000000036311445372753700233350ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_LIGHTUTIL_H #define OPENMW_COMPONENTS_LIGHTUTIL_H #include #include namespace osg { class Group; class Light; } namespace ESM { struct Light; } namespace SceneUtil { class LightSource; /// @brief Set up global attenuation settings for an osg::Light. /// @param radius The radius of the light source. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. void configureLight (osg::Light *light, float radius, bool isExterior); /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and add it to a sub graph. /// @note If the sub graph contains a node named "AttachLight" (case insensitive), then the light is added to that. /// Otherwise, the light is attached directly to the root node of the subgraph. /// @param node The sub graph to add a light to /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. osg::ref_ptr addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int lightMask, bool isExterior); /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and return it. /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. /// @param ambient Ambient component of the light. osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1)); } #endif openmw-openmw-0.48.0/components/sceneutil/morphgeometry.cpp000066400000000000000000000161241445372753700242250ustar00rootroot00000000000000#include "morphgeometry.hpp" #include #include #include namespace SceneUtil { MorphGeometry::MorphGeometry() : mLastFrameNumber(0) , mDirty(true) , mMorphedBoundingBox(false) { } MorphGeometry::MorphGeometry(const MorphGeometry ©, const osg::CopyOp ©op) : osg::Drawable(copy, copyop) , mMorphTargets(copy.mMorphTargets) , mLastFrameNumber(0) , mDirty(true) , mMorphedBoundingBox(false) { setSourceGeometry(copy.getSourceGeometry()); } void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) { for (unsigned int i=0; i<2; ++i) mGeometry[i] = nullptr; mSourceGeometry = sourceGeom; for (unsigned int i=0; i<2; ++i) { // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. // In this specific case the operation is safe under the following two assumptions: // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateRef) // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below) mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry)); const osg::Geometry& from = *mSourceGeometry; osg::Geometry& to = *mGeometry[i]; to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class // vertices are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); to.setVertexArray(vertexArray); } } } void MorphGeometry::addMorphTarget(osg::Vec3Array *offsets, float weight) { mMorphTargets.push_back(MorphTarget(offsets, weight)); mMorphedBoundingBox = false; dirty(); } void MorphGeometry::dirty() { mDirty = true; if (!mMorphedBoundingBox) dirtyBound(); } osg::ref_ptr MorphGeometry::getSourceGeometry() const { return mSourceGeometry; } void MorphGeometry::accept(osg::NodeVisitor &nv) { if (!nv.validNodeMask(*this)) return; nv.pushOntoNodePath(this); if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { // The cull visitor won't be applied to the node itself, // but we want to use its state to render the child geometry. osg::StateSet* stateset = getStateSet(); osgUtil::CullVisitor* cv = static_cast(&nv); if (stateset) cv->pushStateSet(stateset); cull(&nv); if (stateset) cv->popStateSet(); } else nv.apply(*this); nv.popFromNodePath(); } void MorphGeometry::accept(osg::PrimitiveFunctor& func) const { getGeometry(mLastFrameNumber)->accept(func); } osg::BoundingBox MorphGeometry::computeBoundingBox() const { bool anyMorphTarget = false; for (unsigned int i=1; igetBoundingBox(); } // once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating else { mMorphedBoundingBox = true; const osg::Vec3Array* sourceVerts = static_cast(mSourceGeometry->getVertexArray()); if (mMorphTargets.size() != 0) sourceVerts = mMorphTargets[0].getOffsets(); std::vector vertBounds(sourceVerts->size()); // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. // Start with zero offsets which will happen when no morphs are applied. for (unsigned int i=0; igetTraversalNumber() || !mDirty || mMorphTargets.size() == 0) { osg::Geometry& geom = *getGeometry(mLastFrameNumber); nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); return; } mDirty = false; mLastFrameNumber = nv->getTraversalNumber(); osg::Geometry& geom = *getGeometry(mLastFrameNumber); const osg::Vec3Array* positionSrc = mMorphTargets[0].getOffsets(); osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); assert(positionSrc->size() == positionDst->size()); for (unsigned int vertex=0; vertexsize(); ++vertex) (*positionDst)[vertex] = (*positionSrc)[vertex]; for (unsigned int i=1; isize(); ++vertex) (*positionDst)[vertex] += (*offsets)[vertex] * weight; } positionDst->dirty(); geom.osg::Drawable::dirtyGLObjects(); nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); } osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const { return mGeometry[frame%2]; } } openmw-openmw-0.48.0/components/sceneutil/morphgeometry.hpp000066400000000000000000000064331445372753700242340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H #define OPENMW_COMPONENTS_MORPHGEOMETRY_H #include namespace SceneUtil { /// @brief Vertex morphing implementation. /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. class MorphGeometry : public osg::Drawable { public: MorphGeometry(); MorphGeometry(const MorphGeometry& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, MorphGeometry) /// Initialize this geometry from the source geometry. /// @note The source geometry will not be modified. void setSourceGeometry(osg::ref_ptr sourceGeom); // Currently empty as this is difficult to implement. Technically we would need to compile both internal geometries in separate frames but this method is only called once. Alternatively we could compile just the static parts of the model. void compileGLObjects(osg::RenderInfo& renderInfo) const override {} class MorphTarget { protected: osg::ref_ptr mOffsets; float mWeight; public: MorphTarget(osg::Vec3Array* offsets, float w = 1.0) : mOffsets(offsets), mWeight(w) {} void setWeight(float weight) { mWeight = weight; } float getWeight() const { return mWeight; } osg::Vec3Array* getOffsets() { return mOffsets.get(); } const osg::Vec3Array* getOffsets() const { return mOffsets.get(); } void setOffsets(osg::Vec3Array* offsets) { mOffsets = offsets; } }; typedef std::vector MorphTargetList; virtual void addMorphTarget( osg::Vec3Array* offsets, float weight = 1.0 ); /** Set the MorphGeometry dirty.*/ void dirty(); /** Get the list of MorphTargets.*/ const MorphTargetList& getMorphTargetList() const { return mMorphTargets; } /** Get the list of MorphTargets. Warning if you modify this array you will have to call dirty() */ MorphTargetList& getMorphTargetList() { return mMorphTargets; } /** Return the \c MorphTarget at position \c i.*/ inline const MorphTarget& getMorphTarget( unsigned int i ) const { return mMorphTargets[i]; } /** Return the \c MorphTarget at position \c i.*/ inline MorphTarget& getMorphTarget( unsigned int i ) { return mMorphTargets[i]; } osg::ref_ptr getSourceGeometry() const; void accept(osg::NodeVisitor &nv) override; bool supports(const osg::PrimitiveFunctor&) const override { return true; } void accept(osg::PrimitiveFunctor&) const override; osg::BoundingBox computeBoundingBox() const override; private: void cull(osg::NodeVisitor* nv); MorphTargetList mMorphTargets; osg::ref_ptr mSourceGeometry; osg::ref_ptr mGeometry[2]; osg::Geometry* getGeometry(unsigned int frame) const; unsigned int mLastFrameNumber; bool mDirty; // Have any morph targets changed? mutable bool mMorphedBoundingBox; }; } #endif openmw-openmw-0.48.0/components/sceneutil/mwshadowtechnique.cpp000066400000000000000000003767611445372753700251030ustar00rootroot00000000000000/* This file is based on OpenSceneGraph's src/osgShadow/ViewDependentShadowMap.cpp. * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. * The original copyright notice is listed below. */ /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include "mwshadowtechnique.hpp" #include #include #include #include #include #include #include #include #include #include "shadowsbin.hpp" namespace { using namespace osgShadow; using namespace SceneUtil; #define dbl_max std::numeric_limits::max() ////////////////////////////////////////////////////////////////// // fragment shader // #if 0 static const char fragmentShaderSource_withBaseTexture[] = "uniform sampler2D baseTexture; \n" "uniform sampler2DShadow shadowTexture; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture, gl_TexCoord[1] ).r ); \n" " gl_FragColor = color; \n" "} \n"; #else static const char fragmentShaderSource_withBaseTexture[] = "uniform sampler2D baseTexture; \n" "uniform int baseTextureUnit; \n" "uniform sampler2DShadow shadowTexture0; \n" "uniform int shadowTextureUnit0; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r ); \n" " gl_FragColor = color; \n" "} \n"; static const char fragmentShaderSource_withBaseTexture_twoShadowMaps[] = "uniform sampler2D baseTexture; \n" "uniform int baseTextureUnit; \n" "uniform sampler2DShadow shadowTexture0; \n" "uniform int shadowTextureUnit0; \n" "uniform sampler2DShadow shadowTexture1; \n" "uniform int shadowTextureUnit1; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" " float shadow0 = shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r; \n" " float shadow1 = shadow2DProj( shadowTexture1, gl_TexCoord[shadowTextureUnit1] ).r; \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow0*shadow1 ); \n" " gl_FragColor = color; \n" "} \n"; #endif std::string debugVertexShaderSource = "void main(void){gl_Position = gl_Vertex; gl_TexCoord[0]=gl_MultiTexCoord0;}"; std::string debugFragmentShaderSource = "uniform sampler2D texture; \n" " \n" "void main(void) \n" "{ \n" #if 1 " float f = texture2D(texture, gl_TexCoord[0].xy).r; \n" " \n" " f = 256.0 * f; \n" " float fC = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fS = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fH = floor( f ) / 256.0; \n" " \n" " fS *= 0.5; \n" " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" " \n" " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" " abs( fC - 0.333333 ), \n" " abs( fC - 0.666667 ) ); \n" " \n" " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" " \n" " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" " fMax = 1.0 / fMax; \n" " \n" " vec3 color = fMax * rgb; \n" " \n" " gl_FragColor = vec4( fS + fH * color, 1 ); \n" #else " gl_FragColor = texture2D(texture, gl_TexCoord[0].xy); \n" #endif "} \n"; std::string debugFrustumVertexShaderSource = "varying float depth; uniform mat4 transform; void main(void){gl_Position = transform * gl_Vertex; depth = gl_Position.z / gl_Position.w;}"; std::string debugFrustumFragmentShaderSource = "varying float depth; \n" " \n" "void main(void) \n" "{ \n" #if 1 " float f = depth; \n" " \n" " f = 256.0 * f; \n" " float fC = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fS = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fH = floor( f ) / 256.0; \n" " \n" " fS *= 0.5; \n" " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" " \n" " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" " abs( fC - 0.333333 ), \n" " abs( fC - 0.666667 ) ); \n" " \n" " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" " \n" " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" " fMax = 1.0 / fMax; \n" " \n" " vec3 color = fMax * rgb; \n" " \n" " gl_FragColor = vec4( fS + fH * color, 1 ); \n" #else " gl_FragColor = vec4(0.0, 0.0, 1.0, 0.0); \n" #endif "} \n"; template class RenderLeafTraverser : public T { public: RenderLeafTraverser() { } void traverse(const osgUtil::RenderStage* rs) { traverse(static_cast(rs)); } void traverse(const osgUtil::RenderBin* renderBin) { const osgUtil::RenderBin::RenderBinList& rbl = renderBin->getRenderBinList(); for(osgUtil::RenderBin::RenderBinList::const_iterator itr = rbl.begin(); itr != rbl.end(); ++itr) { traverse(itr->second.get()); } const osgUtil::RenderBin::RenderLeafList& rll = renderBin->getRenderLeafList(); for(osgUtil::RenderBin::RenderLeafList::const_iterator itr = rll.begin(); itr != rll.end(); ++itr) { handle(*itr); } const osgUtil::RenderBin::StateGraphList& rgl = renderBin->getStateGraphList(); for(osgUtil::RenderBin::StateGraphList::const_iterator itr = rgl.begin(); itr != rgl.end(); ++itr) { traverse(*itr); } } void traverse(const osgUtil::StateGraph* stateGraph) { const osgUtil::StateGraph::ChildList& cl = stateGraph->_children; for(osgUtil::StateGraph::ChildList::const_iterator itr = cl.begin(); itr != cl.end(); ++itr) { traverse(itr->second.get()); } const osgUtil::StateGraph::LeafList& ll = stateGraph->_leaves; for(osgUtil::StateGraph::LeafList::const_iterator itr = ll.begin(); itr != ll.end(); ++itr) { handle(itr->get()); } } inline void handle(const osgUtil::RenderLeaf* renderLeaf) { this->operator()(renderLeaf); } }; /////////////////////////////////////////////////////////////////////////////////////////////// // // VDSMCameraCullCallback // class VDSMCameraCullCallback : public osg::NodeCallback { public: VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope); void operator()(osg::Node*, osg::NodeVisitor* nv) override; osg::RefMatrix* getProjectionMatrix() { return _projectionMatrix.get(); } osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); } protected: MWShadowTechnique* _vdsm; osg::ref_ptr _projectionMatrix; osg::ref_ptr _renderStage; osg::Polytope _polytope; }; VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope): _vdsm(vdsm), _polytope(polytope) { } void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); osg::Camera* camera = node->asCamera(); OSG_INFO<<"VDSMCameraCullCallback::operator()(osg::Node* "<getProjectionCullingStack().back(); cs.setFrustum(_polytope); cv->pushCullingSet(); } #endif // bin has to go inside camera cull or the rendertexture stage will override it cv->pushStateSet(_vdsm->getOrCreateShadowsBinStateSet()); if (_vdsm->getShadowedScene()) { _vdsm->getShadowedScene()->osg::Group::traverse(*nv); } cv->popStateSet(); #if 1 if (!_polytope.empty()) { OSG_INFO<<"Popping custom Polytope"<popCullingSet(); } #endif _renderStage = cv->getCurrentRenderBin()->getStage(); OSG_INFO<<"VDSM second : _renderStage = "<<_renderStage<getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { // make sure that the near plane is computed correctly. cv->computeNearPlane(); osg::Matrixd projection = *(cv->getProjectionMatrix()); OSG_INFO<<"RTT Projection matrix "<setProjectionMatrix(projection); } _projectionMatrix = cv->getProjectionMatrix(); } } // namespace MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); } void MWShadowTechnique::ComputeLightSpaceBounds::reset() { osg::CullStack::reset(); _bb = osg::BoundingBox(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) { if (isCulled(node)) return; // push the culling mode. pushCurrentMask(); traverse(node); // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Group& node) { apply(static_cast(node)); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) { if (isCulled(drawable)) return; // push the culling mode. pushCurrentMask(); updateBound(drawable.getBoundingBox()); // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Geometry& drawable) { apply(static_cast(drawable)); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) { OSG_INFO << "Warning Billboards not yet supported" << std::endl; return; } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Projection&) { // projection nodes won't affect a shadow map so their subgraphs should be ignored return; } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform) { if (isCulled(transform)) return; // push the culling mode. pushCurrentMask(); // absolute transforms won't affect a shadow map so their subgraphs should be ignored. if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) { osg::RefMatrix* matrix = createOrReuseMatrix(*getModelViewMatrix()); transform.computeLocalToWorldMatrix(*matrix, this); pushModelViewMatrix(matrix, transform.getReferenceFrame()); traverse(transform); popModelViewMatrix(); } // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::MatrixTransform& transform) { apply(static_cast(transform)); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Camera&) { // camera nodes won't affect a shadow map so their subgraphs should be ignored return; } void MWShadowTechnique::ComputeLightSpaceBounds::updateBound(const osg::BoundingBox& bb) { if (!bb.valid()) return; const osg::Matrix& matrix = *getModelViewMatrix() * *getProjectionMatrix(); update(bb.corner(0) * matrix); update(bb.corner(1) * matrix); update(bb.corner(2) * matrix); update(bb.corner(3) * matrix); update(bb.corner(4) * matrix); update(bb.corner(5) * matrix); update(bb.corner(6) * matrix); update(bb.corner(7) * matrix); } void MWShadowTechnique::ComputeLightSpaceBounds::update(const osg::Vec3& v) { if (v.z()<-1.0f) { //OSG_NOTICE<<"discarding("<1.0f) x = 1.0f; float y = v.y(); if (y<-1.0f) y = -1.0f; if (y>1.0f) y = 1.0f; _bb.expandBy(osg::Vec3(x, y, v.z())); } /////////////////////////////////////////////////////////////////////////////////////////////// // // LightData // MWShadowTechnique::LightData::LightData(MWShadowTechnique::ViewDependentData* vdd): _viewDependentData(vdd), directionalLight(false) { } void MWShadowTechnique::LightData::setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix) { lightMatrix = lm; light = l; lightPos = light->getPosition(); directionalLight = (light->getPosition().w()== 0.0); if (directionalLight) { lightPos3.set(0.0, 0.0, 0.0); // directional light has no destinct position lightDir.set(-lightPos.x(), -lightPos.y(), -lightPos.z()); lightDir.normalize(); OSG_INFO<<" Directional light, lightPos="<setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); #ifndef __APPLE__ // workaround shadow issue on macOS, https://gitlab.com/OpenMW/openmw/-/issues/6057 _camera->setImplicitBufferAttachmentMask(0, 0); #endif //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_PRIMITIVES); // Now we are using Depth Clamping, we want to not cull things on the wrong side of the near plane. // When the near and far planes are computed, OSG always culls anything on the wrong side of the near plane, even if it's told not to. // Even if that weren't an issue, the near plane can't go past any shadow receivers or the depth-clamped fragments which ended up on the near plane can't cast shadows on those receivers. // Unfortunately, this change will make shadows have less depth precision when there are no casters outside the view frustum. // TODO: Find a better solution. E.g. detect when there are no casters outside the view frustum, write a new cull visitor that does all the wacky things we'd need it to. _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); // switch off small feature culling as this can cull out geometry that will still be large enough once perspective correction takes effect. _camera->setCullingMode(_camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING); // set viewport _camera->setViewport(0,0,textureSize.x(),textureSize.y()); if (debug) { // clear just the depth buffer _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // render after the main camera _camera->setRenderOrder(osg::Camera::POST_RENDER); // attach the texture and use it as the color buffer. //_camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); _camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); } else { // clear the depth and colour bufferson each clear. _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // set the camera to render before the main camera. _camera->setRenderOrder(osg::Camera::PRE_RENDER); // tell the camera to use OpenGL frame buffer object where supported. _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); // attach the texture and use it as the color buffer. _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); //_camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); } } void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const { OSG_INFO<<"MWShadowTechnique::ShadowData::releaseGLObjects"<releaseGLObjects(state); _camera->releaseGLObjects(state); } /////////////////////////////////////////////////////////////////////////////////////////////// // // Frustum // MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): useCustomClipSpace(false), corners(8), faces(6), edges(12) { projectionMatrix = *(cv->getProjectionMatrix()); modelViewMatrix = *(cv->getModelViewMatrix()); OSG_INFO<<"Projection matrix "<getComputeNearFarMode()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { osg::Matrix::value_type zNear = osg::maximum(cv->getCalculatedNearPlane(),minZNear); osg::Matrix::value_type zFar = osg::minimum(cv->getCalculatedFarPlane(),maxZFar); cv->clampProjectionMatrix(projectionMatrix, zNear, zFar); OSG_INFO<<"zNear = "<releaseGLObjects(state); } } /////////////////////////////////////////////////////////////////////////////////////////////// // // MWShadowTechnique // MWShadowTechnique::MWShadowTechnique(): ShadowTechnique(), _enableShadows(false), _debugHud(nullptr), _castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { _shadowRecievingPlaceholderStateSet = new osg::StateSet; mSetDummyStateWhenDisabled = false; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): ShadowTechnique(vdsm,copyop) , _castingPrograms(vdsm._castingPrograms) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; mSetDummyStateWhenDisabled = vdsm.mSetDummyStateWhenDisabled; } MWShadowTechnique::~MWShadowTechnique() { if (_shadowsBin != nullptr) osgUtil::RenderBin::removeRenderBinPrototype(_shadowsBin); } void MWShadowTechnique::init() { if (!_shadowedScene) return; OSG_INFO<<"MWShadowTechnique::init()"<getShadowSettings()->getNumShadowMapsPerLight()); } void SceneUtil::MWShadowTechnique::disableDebugHUD() { _debugHud = nullptr; } void SceneUtil::MWShadowTechnique::setSplitPointUniformLogarithmicRatio(double ratio) { _splitPointUniformLogRatio = ratio; } void SceneUtil::MWShadowTechnique::setSplitPointDeltaBias(double bias) { _splitPointDeltaBias = bias; } void SceneUtil::MWShadowTechnique::setPolygonOffset(float factor, float units) { _polygonOffsetFactor = factor; _polygonOffsetUnits = units; if (_polygonOffset) { _polygonOffset->setFactor(factor); _polygonOffset->setUnits(units); } } void SceneUtil::MWShadowTechnique::setShadowFadeStart(float shadowFadeStart) { _shadowFadeStart = shadowFadeStart; } void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() { _useFrontFaceCulling = true; if (_shadowCastingStateSet) { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); } } void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() { _useFrontFaceCulling = false; if (_shadowCastingStateSet) { _shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); } } void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", { }, osg::Shader::VERTEX); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, {"adjustCoverage", "1"}, {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) { return new ViewDependentData(this); } MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv) { std::lock_guard lock(_viewDependentDataMapMutex); ViewDependentDataMap::iterator itr = _viewDependentDataMap.find(cv); if (itr!=_viewDependentDataMap.end()) return itr->second.get(); osg::ref_ptr vdd = createViewDependentData(cv); _viewDependentDataMap[cv] = vdd; return vdd.release(); } void SceneUtil::MWShadowTechnique::copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs) { // Prepare for rendering shadows using the shadow map owned by rhs. // To achieve this i first copy all data that is not specific to this cv's camera and thus read-only, // trusting openmw and osg won't overwrite that data before this frame is done rendering. // This works due to the double buffering of CullVisitors by osg, but also requires that cull passes are serialized (relative to one another). // Then initialize new copies of the data that will be written with view-specific data // (the stateset and the texgens). lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap; auto* stateset = lhs->getStateSet(cv.getTraversalNumber()); stateset->clear(); lhs->_lightDataList = rhs->_lightDataList; lhs->_numValidShadows = rhs->_numValidShadows; ShadowDataList& sdl = lhs->getShadowDataList(); ShadowDataList previous_sdl; previous_sdl.swap(sdl); for (const auto& rhs_sd : rhs->getShadowDataList()) { osg::ref_ptr lhs_sd; if (previous_sdl.empty()) { OSG_INFO << "Create new ShadowData" << std::endl; lhs_sd = new ShadowData(lhs); } else { OSG_INFO << "Taking ShadowData from from of previous_sdl" << std::endl; lhs_sd = previous_sdl.front(); previous_sdl.erase(previous_sdl.begin()); } lhs_sd->_camera = rhs_sd->_camera; lhs_sd->_textureUnit = rhs_sd->_textureUnit; lhs_sd->_texture = rhs_sd->_texture; sdl.push_back(lhs_sd); } assignTexGenSettings(cv, lhs); if (lhs->_numValidShadows > 0) { prepareStateSetForRenderingShadow(*lhs, cv.getTraversalNumber()); } } void SceneUtil::MWShadowTechnique::setCustomFrustumCallback(CustomFrustumCallback* cfc) { _customFrustumCallback = cfc; } void SceneUtil::MWShadowTechnique::assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd) { for (const auto& sd : vdd->getShadowDataList()) { assignTexGenSettings(&cv, sd->_camera, sd->_textureUnit, sd->_texgen); } } void MWShadowTechnique::update(osg::NodeVisitor& nv) { OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<osg::Group::traverse(nv); } void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { if (!_enableShadows) { if (mSetDummyStateWhenDisabled) { osg::ref_ptr dummyState = new osg::StateSet(); ShadowSettings* settings = getShadowedScene()->getShadowSettings(); int baseUnit = settings->getBaseShadowTextureUnit(); int endUnit = baseUnit + settings->getNumShadowMapsPerLight(); for (int i = baseUnit; i < endUnit; ++i) { dummyState->setTextureAttributeAndModes(i, _fallbackShadowMapTexture, osg::StateAttribute::ON); dummyState->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseUnit)).c_str(), i)); dummyState->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseUnit)).c_str(), i)); } cv.pushStateSet(dummyState); } _shadowedScene->osg::Group::traverse(cv); if (mSetDummyStateWhenDisabled) cv.popStateSet(); return; } OSG_INFO<osg::Group::traverse(cv); return; } ViewDependentData* vdd = getViewDependentData(&cv); if (!vdd) { OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); return; } ShadowSettings* settings = getShadowedScene()->getShadowSettings(); OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getMaximumShadowMapDistance(),maxZFar); if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); //OSG_NOTICE<<"maxZFar "<operator()(cv, _customClipSpace, sharedFrustumHint); frustum.setCustomClipSpace(_customClipSpace); if (sharedFrustumHint) { // user hinted another view shares its frustum std::lock_guard lock(_viewDependentDataMapMutex); auto itr = _viewDependentDataMap.find(sharedFrustumHint); if (itr != _viewDependentDataMap.end()) { OSG_INFO << "User provided a valid shared frustum hint, re-using previously generated shadow map" << std::endl; copyShadowMap(cv, vdd, itr->second); // return compute near far mode back to it's original settings cv.setComputeNearFarMode(cachedNearFarMode); return; } else { OSG_INFO << "User provided a shared frustum hint, but it was not valid." << std::endl; } } } frustum.init(); if (_debugHud) { osg::ref_ptr vertexArray = new osg::Vec3Array(); for (osg::Vec3d &vertex : frustum.corners) vertexArray->push_back((osg::Vec3)vertex); _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); } double reducedNear, reducedFar; if (cv.getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { reducedNear = osg::maximum(cv.getCalculatedNearPlane(), minZNear); reducedFar = osg::minimum(cv.getCalculatedFarPlane(), maxZFar); } else { reducedNear = minZNear; reducedFar = maxZFar; } // return compute near far mode back to it's original settings cv.setComputeNearFarMode(cachedNearFarMode); OSG_INFO<<"frustum.eye="<1 &&*/ (_shadowedScene->getCastsShadowTraversalMask() & _worldMask) == 0) { // osg::ElapsedTime timer; osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); if (!_clsb) _clsb = new ComputeLightSpaceBounds; ComputeLightSpaceBounds& clsb = *_clsb; clsb.reset(); clsb.pushViewport(viewport); clsb.pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); clsb.pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); osg::Matrixd invertModelView; invertModelView.invert(viewMatrix); osg::Polytope local_polytope(polytope); local_polytope.transformProvidingInverse(invertModelView); osg::CullingSet& cs = clsb.getProjectionCullingStack().back(); cs.setFrustum(local_polytope); clsb.pushCullingSet(); _shadowedScene->accept(clsb); clsb.popCullingSet(); clsb.popModelViewMatrix(); clsb.popProjectionMatrix(); clsb.popViewport(); // OSG_NOTICE<<"Extents of LightSpace "<(maxZ, -corner.z()); minZ = osg::minimum(minZ, -corner.z()); } reducedNear = osg::maximum(reducedNear, minZ); reducedFar = osg::minimum(reducedFar, maxZ); // OSG_NOTICE<<" xMid="< camera = sd->_camera; camera->setProjectionMatrix(projectionMatrix); camera->setViewMatrix(viewMatrix); if (settings->getDebugDraw()) { camera->getViewport()->x() = pos_x; pos_x += static_cast(camera->getViewport()->width()) + 40; } // transform polytope in model coords into light spaces eye coords. osg::Matrixd invertModelView; invertModelView.invert(camera->getViewMatrix()); osg::Polytope local_polytope(polytope); local_polytope.transformProvidingInverse(invertModelView); double cascaseNear = reducedNear; double cascadeFar = reducedFar; if (numShadowMapsPerLight>1) { // compute the start and end range in non-dimensional coords #if 0 double r_start = (sm_i==0) ? -1.0 : (double(sm_i)/double(numShadowMapsPerLight)*2.0-1.0); double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : (double(sm_i+1)/double(numShadowMapsPerLight)*2.0-1.0); #elif 0 // hardwired for 2 splits double r_start = (sm_i==0) ? -1.0 : splitPoint; double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : splitPoint; #else double r_start, r_end; // split system based on the original Parallel Split Shadow Maps paper. double n = reducedNear; double f = reducedFar; double i = double(sm_i); double m = double(numShadowMapsPerLight); if (sm_i == 0) r_start = -1.0; else { // compute the split point in main camera view double ciLog = n * pow(f / n, i / m); double ciUniform = n + (f - n) * i / m; double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; cascaseNear = ci; // work out where this is in light space osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; r_start = lightSpacePos.y(); } if (sm_i + 1 == numShadowMapsPerLight) r_end = 1.0; else { // compute the split point in main camera view double ciLog = n * pow(f / n, (i + 1) / m); double ciUniform = n + (f - n) * (i + 1) / m; double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; cascadeFar = ci; // work out where this is in light space osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; r_end = lightSpacePos.y(); } #endif // for all by the last shadowmap shift the r_end so that it overlaps slightly with the next shadowmap // to prevent a seam showing through between the shadowmaps if (sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i>0) { // not the first shadowmap so insert a polytope to clip the scene from before r_start // plane in clip space coords osg::Plane plane(0.0,1.0,0.0,-r_start); // transform into eye coords plane.transformProvidingInverse(projectionMatrix); local_polytope.getPlaneList().push_back(plane); //OSG_NOTICE<<"Adding r_start plane "<getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT) { // OSG_NOTICE<<"Need to adjust RTT camera projection and view matrix here, r_start="< validRegionUniform; for (const auto & uniform : _uniforms[cv.getTraversalNumber() % 2]) { if (uniform->getName() == validRegionUniformName) validRegionUniform = uniform; } if (!validRegionUniform) { validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); _uniforms[cv.getTraversalNumber() % 2].push_back(validRegionUniform); } validRegionUniform->set(validRegionMatrix); } if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar); else adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), reducedNear, reducedFar); if (vdsmCallback->getProjectionMatrix()) { vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); } } // 4.4 compute main scene graph TexGen + uniform settings + setup state // assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); // mark the light as one that has active shadows and requires shaders pl.textureUnits.push_back(textureUnit); // pass on shadow data to ShadowDataList sd->_textureUnit = textureUnit; if (textureUnit >= 8) { OSG_NOTICE<<"Shadow texture unit is invalid for texgen, will not be used."<draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv); } } vdd->setNumValidShadows(numValidShadows); if (numValidShadows>0) { prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<getLightDataList(); LightDataList previous_ldl; previous_ldl.swap(pll); //MR testing giving a specific light osgUtil::RenderStage * rs = cv->getCurrentRenderBin()->getStage(); OSG_INFO<<"selectActiveLights osgUtil::RenderStage="<getModelViewMatrix()); osgUtil::PositionalStateContainer::AttrMatrixList& aml = rs->getPositionalStateContainer()->getAttrMatrixList(); const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); for(osgUtil::PositionalStateContainer::AttrMatrixList::reverse_iterator itr = aml.rbegin(); itr != aml.rend(); ++itr) { const osg::Light* light = dynamic_cast(itr->first.get()); if (light && light->getLightNum() >= 0) { // is LightNum matched to that defined in settings if (settings && settings->getLightNum()>=0 && light->getLightNum()!=settings->getLightNum()) continue; LightDataList::iterator pll_itr = pll.begin(); for(; pll_itr != pll.end(); ++pll_itr) { if ((*pll_itr)->light->getLightNum()==light->getLightNum()) break; } if (pll_itr==pll.end()) { OSG_INFO<<"Light num "<getLightNum()<setLightData(itr->second.get(), light, modelViewMatrix); pll.push_back(ld); } else { OSG_INFO<<"Light num "<getLightNum()<<" already used, ignore light"<getShadowSettings(); if (!settings->getDebugDraw()) { // note soft (attribute only no mode override) setting. When this works ? // 1. for objects prepared for backface culling // because they usually also set CullFace and CullMode on in their state // For them we override CullFace but CullMode remains set by them // 2. For one faced, trees, and similar objects which cannot use // backface nor front face so they usually use CullMode off set here. // In this case we will draw them in their entirety. if (_useFrontFaceCulling) { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // make sure GL_CULL_FACE is off by default // we assume that if object has cull face attribute set to back // it will also set cull face mode ON so no need for override _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); } else _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); } _polygonOffset = new osg::PolygonOffset(_polygonOffsetFactor, _polygonOffsetUnits); _shadowCastingStateSet->setAttribute(_polygonOffset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); osg::ref_ptr baseTextureSampler = new osg::Uniform("baseTexture",(int)_baseTextureUnit); osg::ref_ptr baseTextureUnit = new osg::Uniform("baseTextureUnit",(int)_baseTextureUnit); osg::ref_ptr maxDistance = new osg::Uniform("maximumShadowMapDistance", (float)settings->getMaximumShadowMapDistance()); osg::ref_ptr fadeStart = new osg::Uniform("shadowFadeStart", (float)_shadowFadeStart); for (auto& perFrameUniformList : _uniforms) { perFrameUniformList.clear(); perFrameUniformList.push_back(baseTextureSampler); perFrameUniformList.emplace_back(baseTextureUnit.get()); perFrameUniformList.push_back(maxDistance); perFrameUniformList.push_back(fadeStart); } for(unsigned int sm_i=0; sm_igetNumShadowMapsPerLight(); ++sm_i) { { std::stringstream sstr; sstr<<"shadowTexture"< shadowTextureSampler = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureSampler.get()); } { std::stringstream sstr; sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureUnit.get()); } } switch(settings->getShaderHint()) { case(ShadowSettings::NO_SHADERS): { OSG_INFO<<"No shaders provided by, user must supply own shaders"< fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_noBaseTexture); if (settings->getNumShadowMapsPerLight()==2) { _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture_twoShadowMaps)); } else { _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture)); } break; } } { osg::ref_ptr image = new osg::Image; image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE ); *(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF ); _fallbackBaseTexture = new osg::Texture2D(image.get()); _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); _fallbackBaseTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); _fallbackBaseTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture = new osg::Texture2D(image.get()); _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture->setShadowComparison(true); _fallbackShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); } if (!_castingPrograms[GL_ALWAYS - GL_NEVER]) OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary _shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); _shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader } osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight) { OSG_INFO<<"computeLightViewFrustumPolytope()"<getShadowSettings(); double dotProduct_v = positionedLight.lightDir * frustum.frustumCenterLine; double gamma_v = acos(dotProduct_v); if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180.0-settings->getPerspectiveShadowMapCutOffAngle())) { OSG_INFO<<"View direction and Light direction below tolerance"<=0.0 && d1>=0.0) { // OSG_NOTICE<<" Edge completely inside"<first; osg::Vec3d& v1 = itr->second; osg::Vec3d intersection = v0 - (v1-v0)*(d0/(d1-d0)); intersections.push_back(intersection); // OSG_NOTICE<<" Edge across clip plane, v0="<=side_y.length2()) ? side_x : side_y; side.normalize(); osg::Vec3d up = side ^ normal; up.normalize(); osg::Vec3d center; for(auto& vertex : intersections) { center += vertex; center.x() = osg::maximum(center.x(), -dbl_max); center.y() = osg::maximum(center.y(), -dbl_max); center.z() = osg::maximum(center.z(), -dbl_max); center.x() = osg::minimum(center.x(), dbl_max); center.y() = osg::minimum(center.y(), dbl_max); center.z() = osg::minimum(center.z(), dbl_max); } center /= double(intersections.size()); typedef std::map>> VertexMap; VertexMap vertexMap; for (const auto& vertex : intersections) { osg::Vec3d dv = vertex - center; double h = dv * side; double v = dv * up; double angle = atan2(h,v); // OSG_NOTICE<<"angle = "<_modelview.get()!=previous_modelview) { previous_modelview = renderLeaf->_modelview.get(); if (previous_modelview) { light_mvp.mult(*renderLeaf->_modelview, light_p); } else { // no modelview matrix (such as when LightPointNode is in the scene graph) so assume // that modelview matrix is indentity. light_mvp = light_p; } // OSG_INFO<<"Computing new light_mvp "<_drawable->getBoundingBox(); if (bb.valid()) { // OSG_NOTICE<<"checked extents of "<_drawable->getName()<max_z) { max_z=ls.z(); /* OSG_NOTICE<<" + ";*/ } // OSG_NOTICE<<" bb.z() in ls = "<& planeList) { osg::Matrixd light_p = camera->getProjectionMatrix(); osg::Matrixd light_v = camera->getViewMatrix(); osg::Matrixd light_vp = light_v * light_p; osg::Matrixd oldLightP = light_p; ConvexHull convexHull; convexHull.setToFrustum(frustum); osg::Vec3d nearPoint = frustum.eye + frustum.frustumCenterLine * viewNear; osg::Vec3d farPoint = frustum.eye + frustum.frustumCenterLine * viewFar; double nearDist = -frustum.frustumCenterLine * nearPoint; double farDist = frustum.frustumCenterLine * farPoint; convexHull.clip(osg::Plane(frustum.frustumCenterLine, nearDist)); convexHull.clip(osg::Plane(-frustum.frustumCenterLine, farDist)); convexHull.transform(light_vp); double xMin = -1.0, xMax = 1.0; double yMin = -1.0, yMax = 1.0; double zMin = -1.0, zMax = 1.0; if (convexHull.valid()) { xMin = osg::maximum(-1.0, convexHull.min(0)); xMax = osg::minimum(1.0, convexHull.max(0)); yMin = osg::maximum(-1.0, convexHull.min(1)); yMax = osg::minimum(1.0, convexHull.max(1)); zMin = osg::maximum(-1.0, convexHull.min(2)); zMax = osg::minimum(1.0, convexHull.max(2)); } else return false; if (xMin != -1.0 || yMin != -1.0 || zMin != -1.0 || xMax != 1.0 || yMax != 1.0 || zMax != 1.0) { osg::Matrix m; m.makeTranslate(osg::Vec3d(-0.5*(xMax + xMin), -0.5*(yMax + yMin), -0.5*(zMax + zMin))); m.postMultScale(osg::Vec3d(2.0 / (xMax - xMin), 2.0 / (yMax - yMin), 2.0 / (zMax - zMin))); light_p.postMult(m); camera->setProjectionMatrix(light_p); convexHull.transform(osg::Matrixd::inverse(oldLightP)); xMin = convexHull.min(0); xMax = convexHull.max(0); yMin = convexHull.min(1); yMax = convexHull.max(1); zMin = convexHull.min(2); planeList.emplace_back(0.0, -1.0, 0.0, yMax); planeList.emplace_back(0.0, 1.0, 0.0, -yMin); planeList.emplace_back(-1.0, 0.0, 0.0, xMax); planeList.emplace_back(1.0, 0.0, 0.0, -xMin); // In view space, the light is at the most positive value, and we want to cull stuff beyond the minimum value. planeList.emplace_back(0.0, 0.0, 1.0, -zMin); // Don't add a zMax culling plane - we still want those objects, but don't care about their depth buffer value. } return true; } bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& /*positionedLight*/, osg::Camera* camera, double viewNear, double viewFar) { const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); //frustum.projectionMatrix; //frustum.modelViewMatrix; osg::Matrixd light_p = camera->getProjectionMatrix(); osg::Matrixd light_v = camera->getViewMatrix(); osg::Matrixd light_vp = light_v * light_p; osg::Vec3d lightdir(0.0,0.0,-1.0); // check whether this light space projection is perspective or orthographic. bool orthographicLightSpaceProjection = light_p(0,3)==0.0 && light_p(1,3)==0.0 && light_p(2,3)==0.0; if (!orthographicLightSpaceProjection) { OSG_INFO<<"perspective light space projection not yet supported."<setProjectionMatrix(light_p); } #endif osg::Vec3d eye_v = frustum.eye * light_v; //osg::Vec3d centerNearPlane_v = frustum.centerNearPlane * light_v; osg::Vec3d center_v = frustum.center * light_v; osg::Vec3d viewdir_v = center_v-eye_v; viewdir_v.normalize(); double dotProduct_v = lightdir * viewdir_v; double gamma_v = acos(dotProduct_v); if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180-settings->getPerspectiveShadowMapCutOffAngle())) { // OSG_NOTICE<<"Light and view vectors near parallel - use standard shadow map."<getTraversalMask(); cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getReceivesShadowTraversalMask() ); _shadowedScene->osg::Group::traverse(*cv); cv->setTraversalMask( traversalMask ); return; } void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const { OSG_INFO<<"cullShadowCastingScene()"<getTraversalMask(); cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getCastsShadowTraversalMask() ); if (camera) camera->accept(*cv); cv->setTraversalMask( traversalMask ); return; } osg::StateSet* MWShadowTechnique::prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const { OSG_INFO<<" prepareStateSetForRenderingShadow() "< stateset = vdd.getStateSet(traversalNumber); stateset->clear(); stateset->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); for(const auto& uniform : _uniforms[traversalNumber % 2]) { OSG_INFO<<"addUniform("<getName()<<")"<addUniform(uniform); } if (_program.valid()) { stateset->setAttribute(_program.get()); } LightDataList& pll = vdd.getLightDataList(); for(LightDataList::iterator itr = pll.begin(); itr != pll.end(); ++itr) { // 3. create per light/per shadow map division of lightspace/frustum // create a list of light/shadow map data structures LightData& pl = (**itr); // if no texture units have been activated for this light then no shadow state required. if (pl.textureUnits.empty()) continue; for(LightData::ActiveTextureUnits::iterator atu_itr = pl.textureUnits.begin(); atu_itr != pl.textureUnits.end(); ++atu_itr) { OSG_INFO<<" Need to assign state for "<<*atu_itr<getShadowSettings(); unsigned int shadowMapModeValue = settings->getUseOverrideForShadowMapTexture() ? osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE : osg::StateAttribute::ON; ShadowDataList& sdl = vdd.getShadowDataList(); for(ShadowDataList::iterator itr = sdl.begin(); itr != sdl.end(); ++itr) { // 3. create per light/per shadow map division of lightspace/frustum // create a list of light/shadow map data structures ShadowData& sd = (**itr); OSG_INFO<<" ShadowData for "<setTextureAttributeAndModes(sd._textureUnit, sd._texture.get(), shadowMapModeValue); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON); } return stateset; } void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/) { // the way that ViewDependentData is mapped shouldn't } void MWShadowTechnique::releaseGLObjects(osg::State* state) const { std::lock_guard lock(_viewDependentDataMapMutex); for(ViewDependentDataMap::const_iterator itr = _viewDependentDataMap.begin(); itr != _viewDependentDataMap.end(); ++itr) { ViewDependentData* vdd = itr->second.get(); if (vdd) { vdd->releaseGLObjects(state); } } if (_debugHud) _debugHud->releaseGLObjects(state); } class DoubleBufferCallback : public osg::Callback { public: DoubleBufferCallback(osg::NodeList &children) : mChildren(children) {} bool run(osg::Object* node, osg::Object* visitor) override { // We can't use a static cast as NodeVisitor virtually inherits from Object osg::ref_ptr nodeVisitor = visitor->asNodeVisitor(); unsigned int traversalNumber = nodeVisitor->getTraversalNumber(); mChildren[traversalNumber % 2]->accept(*nodeVisitor); return true; } protected: osg::NodeList mChildren; }; SceneUtil::MWShadowTechnique::DebugHUD::DebugHUD(int numberOfShadowMapsPerLight) : mDebugProgram(new osg::Program) { osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, debugVertexShaderSource); mDebugProgram->addShader(vertexShader); osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFragmentShaderSource); mDebugProgram->addShader(fragmentShader); osg::ref_ptr frustumProgram = new osg::Program; vertexShader = new osg::Shader(osg::Shader::VERTEX, debugFrustumVertexShaderSource); frustumProgram->addShader(vertexShader); fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFrustumFragmentShaderSource); frustumProgram->addShader(fragmentShader); for (auto& frustumGeometry : mFrustumGeometries) { frustumGeometry = new osg::Geometry(); frustumGeometry->setCullingActive(false); frustumGeometry->getOrCreateStateSet()->setAttributeAndModes(frustumProgram, osg::StateAttribute::ON); } osg::ref_ptr frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP); for (auto & geom : mFrustumGeometries) geom->addPrimitiveSet(frustumDrawElements); frustumDrawElements->push_back(0); frustumDrawElements->push_back(1); frustumDrawElements->push_back(2); frustumDrawElements->push_back(3); frustumDrawElements->push_back(0); frustumDrawElements->push_back(4); frustumDrawElements->push_back(5); frustumDrawElements->push_back(6); frustumDrawElements->push_back(7); frustumDrawElements->push_back(4); frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES); for (auto & geom : mFrustumGeometries) geom->addPrimitiveSet(frustumDrawElements); frustumDrawElements->push_back(1); frustumDrawElements->push_back(5); frustumDrawElements->push_back(2); frustumDrawElements->push_back(6); frustumDrawElements->push_back(3); frustumDrawElements->push_back(7); for (int i = 0; i < numberOfShadowMapsPerLight; ++i) addAnotherShadowMap(); } void SceneUtil::MWShadowTechnique::DebugHUD::draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv) { // It might be possible to change shadow settings at runtime if (shadowMapNumber > mDebugCameras.size()) addAnotherShadowMap(); osg::ref_ptr stateSet = new osg::StateSet(); stateSet->setTextureAttributeAndModes(sDebugTextureUnit, texture, osg::StateAttribute::ON); auto frustumUniform = mFrustumUniforms[cv.getTraversalNumber() % 2][shadowMapNumber]; frustumUniform->set(matrix); stateSet->addUniform(frustumUniform); // Some of these calls may be superfluous. unsigned int traversalMask = cv.getTraversalMask(); cv.setTraversalMask(mDebugGeometry[shadowMapNumber]->getNodeMask()); cv.pushStateSet(stateSet); mDebugCameras[shadowMapNumber]->accept(cv); cv.popStateSet(); cv.setTraversalMask(traversalMask); // cv.getState()->setCheckForGLErrors(osg::State::ONCE_PER_ATTRIBUTE); } void SceneUtil::MWShadowTechnique::DebugHUD::releaseGLObjects(osg::State* state) const { for (auto const& camera : mDebugCameras) camera->releaseGLObjects(state); mDebugProgram->releaseGLObjects(state); for (auto const& node : mDebugGeometry) node->releaseGLObjects(state); for (auto const& node : mFrustumTransforms) node->releaseGLObjects(state); for (auto const& node : mFrustumGeometries) node->releaseGLObjects(state); } void SceneUtil::MWShadowTechnique::DebugHUD::setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber) { mFrustumGeometries[traversalNumber % 2]->setVertexArray(vertices); } void SceneUtil::MWShadowTechnique::DebugHUD::addAnotherShadowMap() { unsigned int shadowMapNumber = mDebugCameras.size(); mDebugCameras.push_back(new osg::Camera); mDebugCameras[shadowMapNumber]->setViewport(200 * shadowMapNumber, 0, 200, 200); mDebugCameras[shadowMapNumber]->setRenderOrder(osg::Camera::POST_RENDER); mDebugCameras[shadowMapNumber]->setClearColor(osg::Vec4(1.0, 1.0, 0.0, 1.0)); mDebugCameras[shadowMapNumber]->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mDebugGeometry.emplace_back(osg::createTexturedQuadGeometry(osg::Vec3(-1, -1, 0), osg::Vec3(2, 0, 0), osg::Vec3(0, 2, 0))); mDebugGeometry[shadowMapNumber]->setCullingActive(false); mDebugCameras[shadowMapNumber]->addChild(mDebugGeometry[shadowMapNumber]); osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); stateSet->setAttributeAndModes(mDebugProgram, osg::StateAttribute::ON); osg::ref_ptr textureUniform = new osg::Uniform("texture", sDebugTextureUnit); //textureUniform->setType(osg::Uniform::SAMPLER_2D); stateSet->addUniform(textureUniform.get()); mFrustumTransforms.push_back(new osg::Group); osg::NodeList frustumGeometryNodeList(mFrustumGeometries.cbegin(), mFrustumGeometries.cend()); mFrustumTransforms[shadowMapNumber]->setCullCallback(new DoubleBufferCallback(frustumGeometryNodeList)); mFrustumTransforms[shadowMapNumber]->setCullingActive(false); mDebugCameras[shadowMapNumber]->addChild(mFrustumTransforms[shadowMapNumber]); for(auto& uniformVector : mFrustumUniforms) uniformVector.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); } osg::ref_ptr SceneUtil::MWShadowTechnique::getOrCreateShadowsBinStateSet() { if (_shadowsBinStateSet == nullptr) { if (_shadowsBin == nullptr) { _shadowsBin = new ShadowsBin(_castingPrograms); osgUtil::RenderBin::addRenderBinPrototype(_shadowsBinName, _shadowsBin); } _shadowsBinStateSet = new osg::StateSet; _shadowsBinStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, _shadowsBinName, osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } return _shadowsBinStateSet; } openmw-openmw-0.48.0/components/sceneutil/mwshadowtechnique.hpp000066400000000000000000000322401445372753700250650ustar00rootroot00000000000000/* This file is based on OpenSceneGraph's include/osgShadow/ViewDependentShadowMap. * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. * The original copyright notice is listed below. */ /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H #define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1 #include #include #include #include #include #include #include #include #include #include namespace SceneUtil { /** ViewDependentShadowMap provides an base implementation of view dependent shadow mapping techniques.*/ class MWShadowTechnique : public osgShadow::ShadowTechnique { public: MWShadowTechnique(); MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); META_Object(SceneUtil, MWShadowTechnique); /** initialize the ShadowedScene and local cached data structures.*/ void init() override; /** run the update traversal of the ShadowedScene and update any loca chached data structures.*/ void update(osg::NodeVisitor& nv) override; /** run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/ void cull(osgUtil::CullVisitor& cv) override; /** Resize any per context GLObject buffers to specified size. */ void resizeGLObjectBuffers(unsigned int maxSize) override; /** If State is non-zero, this function releases any associated OpenGL objects for * the specified graphics context. Otherwise, releases OpenGL objects * for all graphics contexts. */ void releaseGLObjects(osg::State* = 0) const override; /** Clean scene graph from any shadow technique specific nodes, state and drawables.*/ void cleanSceneGraph() override; virtual void enableShadows(); virtual void disableShadows(bool setDummyState = false); virtual void enableDebugHUD(); virtual void disableDebugHUD(); virtual void setSplitPointUniformLogarithmicRatio(double ratio); virtual void setSplitPointDeltaBias(double bias); virtual void setPolygonOffset(float factor, float units); virtual void setShadowFadeStart(float shadowFadeStart); virtual void enableFrontFaceCulling(); virtual void disableFrontFaceCulling(); virtual void setupCastingShader(Shader::ShaderManager &shaderManager); class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack { public: ComputeLightSpaceBounds(); void apply(osg::Node& node) override final; void apply(osg::Group& node) override; void apply(osg::Drawable& drawable) override final; void apply(osg::Geometry& drawable) override; void apply(osg::Billboard&) override; void apply(osg::Projection&) override; void apply(osg::Transform& transform) override final; void apply(osg::MatrixTransform& transform) override; void apply(osg::Camera&) override; void updateBound(const osg::BoundingBox& bb); void update(const osg::Vec3& v); void reset() override; osg::BoundingBox _bb; }; struct Frustum { Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar); void setCustomClipSpace(const osg::BoundingBoxd& clipCornersOverride); void init(); osg::Matrixd projectionMatrix; osg::Matrixd modelViewMatrix; bool useCustomClipSpace; osg::BoundingBoxd customClipSpace; typedef std::vector Vertices; Vertices corners; typedef std::vector Indices; typedef std::vector Faces; Faces faces; typedef std::vector Edges; Edges edges; osg::Vec3d eye; osg::Vec3d centerNearPlane; osg::Vec3d centerFarPlane; osg::Vec3d center; osg::Vec3d frustumCenterLine; }; /** Custom frustum callback allowing the application to request shadow maps covering a * different furstum than the camera normally would cover, by customizing the corners of the clip space. */ struct CustomFrustumCallback : osg::Referenced { /** The callback operator. * Output the custum frustum to the boundingBox variable. * If sharedFrustumHint is set to a valid cull visitor, the shadow maps of that cull visitor will be re-used instead of recomputing new shadow maps * Note that the customClipSpace bounding box will be uninitialized when this operator is called. If it is not initalized, or a valid shared frustum hint set, * the resulting shadow map may be invalid. */ virtual void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) = 0; }; // forward declare class ViewDependentData; struct LightData : public osg::Referenced { LightData(ViewDependentData* vdd); virtual void setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix); ViewDependentData* _viewDependentData; osg::ref_ptr lightMatrix; osg::ref_ptr light; osg::Vec4d lightPos; osg::Vec3d lightPos3; osg::Vec3d lightDir; bool directionalLight; typedef std::vector ActiveTextureUnits; ActiveTextureUnits textureUnits; }; typedef std::list< osg::ref_ptr > LightDataList; struct ShadowData : public osg::Referenced { ShadowData(ViewDependentData* vdd); virtual void releaseGLObjects(osg::State* = 0) const; ViewDependentData* _viewDependentData; unsigned int _textureUnit; osg::ref_ptr _texture; osg::ref_ptr _texgen; osg::ref_ptr _camera; }; typedef std::list< osg::ref_ptr > ShadowDataList; class ViewDependentData : public osg::Referenced { public: ViewDependentData(MWShadowTechnique* vdsm); const MWShadowTechnique* getViewDependentShadowMap() const { return _viewDependentShadowMap; } LightDataList& getLightDataList() { return _lightDataList; } ShadowDataList& getShadowDataList() { return _shadowDataList; } osg::StateSet* getStateSet(unsigned int traversalNumber) { return _stateset[traversalNumber % 2].get(); } virtual void releaseGLObjects(osg::State* = 0) const; unsigned int numValidShadows(void) const { return _numValidShadows; } void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; } protected: friend class MWShadowTechnique; virtual ~ViewDependentData() {} MWShadowTechnique* _viewDependentShadowMap; std::array, 2> _stateset; LightDataList _lightDataList; ShadowDataList _shadowDataList; unsigned int _numValidShadows; }; virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); void copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs); void setCustomFrustumCallback(CustomFrustumCallback* cfc); void assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd); virtual void createShaders(); virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); virtual bool computeShadowCameraSettings(Frustum& frustum, LightData& positionedLight, osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector& planeList); virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar); virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen); virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; virtual osg::StateSet* prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; void setWorldMask(unsigned int worldMask) { _worldMask = worldMask; } osg::ref_ptr getOrCreateShadowsBinStateSet(); protected: virtual ~MWShadowTechnique(); osg::ref_ptr _clsb; typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; mutable std::mutex _viewDependentDataMapMutex; ViewDependentDataMap _viewDependentDataMap; osg::ref_ptr _customFrustumCallback; osg::BoundingBoxd _customClipSpace; osg::ref_ptr _shadowRecievingPlaceholderStateSet; osg::ref_ptr _shadowCastingStateSet; osg::ref_ptr _polygonOffset; osg::ref_ptr _fallbackBaseTexture; osg::ref_ptr _fallbackShadowMapTexture; typedef std::vector< osg::ref_ptr > Uniforms; std::array _uniforms; osg::ref_ptr _program; bool _enableShadows; bool mSetDummyStateWhenDisabled; double _splitPointUniformLogRatio = 0.5; double _splitPointDeltaBias = 0.0; float _polygonOffsetFactor = 1.1; float _polygonOffsetUnits = 4.0; bool _useFrontFaceCulling = true; float _shadowFadeStart = 0.0; unsigned int _worldMask = ~0u; class DebugHUD final : public osg::Referenced { public: DebugHUD(int numberOfShadowMapsPerLight); void draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv); void releaseGLObjects(osg::State* state = 0) const; void setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber); protected: void addAnotherShadowMap(); static const int sDebugTextureUnit = 0; std::vector> mDebugCameras; osg::ref_ptr mDebugProgram; std::vector> mDebugGeometry; std::vector> mFrustumTransforms; std::array>, 2> mFrustumUniforms; std::array, 2> mFrustumGeometries; }; osg::ref_ptr _debugHud; std::array, GL_ALWAYS - GL_NEVER + 1> _castingPrograms; const std::string _shadowsBinName = "ShadowsBin_" + std::to_string(reinterpret_cast(this)); osg::ref_ptr _shadowsBin; osg::ref_ptr _shadowsBinStateSet; }; } #endif openmw-openmw-0.48.0/components/sceneutil/navmesh.cpp000066400000000000000000000267531445372753700227760ustar00rootroot00000000000000#include "navmesh.hpp" #include "detourdebugdraw.hpp" #include "depth.hpp" #include #include #include #include #include #include namespace { // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L26-L38 float distancePtLine2d(const float* pt, const float* p, const float* q) { float pqx = q[0] - p[0]; float pqz = q[2] - p[2]; float dx = pt[0] - p[0]; float dz = pt[2] - p[2]; float d = pqx*pqx + pqz*pqz; float t = pqx*dx + pqz*dz; if (d != 0) t /= d; dx = p[0] + t*pqx - pt[0]; dz = p[2] + t*pqz - pt[2]; return dx*dx + dz*dz; } // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L40-L118 void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, const unsigned int col, const float linew, bool inner) { static const float thr = 0.01f*0.01f; dd->begin(DU_DRAW_LINES, linew); for (int i = 0; i < tile->header->polyCount; ++i) { const dtPoly* p = &tile->polys[i]; if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) continue; const dtPolyDetail* pd = &tile->detailMeshes[i]; for (int j = 0, nj = (int)p->vertCount; j < nj; ++j) { unsigned int c = col; if (inner) { if (p->neis[j] == 0) continue; if (p->neis[j] & DT_EXT_LINK) { bool con = false; for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) { if (tile->links[k].edge == j) { con = true; break; } } if (con) c = duRGBA(255,255,255,48); else c = duRGBA(0,0,0,48); } else c = duRGBA(0,48,64,32); } else { if (p->neis[j] != 0) continue; } const float* v0 = &tile->verts[p->verts[j]*3]; const float* v1 = &tile->verts[p->verts[(j+1) % nj]*3]; // Draw detail mesh edges which align with the actual poly edge. // This is really slow. for (int k = 0; k < pd->triCount; ++k) { const unsigned char* t = &tile->detailTris[(pd->triBase+k)*4]; const float* tv[3]; for (int m = 0; m < 3; ++m) { if (t[m] < p->vertCount) tv[m] = &tile->verts[p->verts[t[m]]*3]; else tv[m] = &tile->detailVerts[(pd->vertBase+(t[m]-p->vertCount))*3]; } for (int m = 0, n = 2; m < 3; n=m++) { if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0) continue; if (distancePtLine2d(tv[n],v0,v1) < thr && distancePtLine2d(tv[m],v0,v1) < thr) { dd->vertex(tv[n], c); dd->vertex(tv[m], c); } } } } } dd->end(); } float getHeat(unsigned salt, unsigned minSalt, unsigned maxSalt) { if (salt < minSalt) return 0; if (salt > maxSalt) return 1; if (maxSalt <= minSalt) return 0.5; return static_cast(salt - minSalt) / static_cast(maxSalt - minSalt); } int getRgbaComponent(float v, int base) { return static_cast(std::round(v * base)); } unsigned heatToColor(float heat, int alpha) { constexpr int min = 100; constexpr int max = 200; if (heat < 0.25f) return duRGBA(min, min + getRgbaComponent(4 * heat, max - min), max, alpha); if (heat < 0.5f) return duRGBA(min, max, min + getRgbaComponent(1 - 4 * (heat - 0.5f), max - min), alpha); if (heat < 0.75f) return duRGBA(min + getRgbaComponent(4 * (heat - 0.5f), max - min), max, min, alpha); return duRGBA(max, min + getRgbaComponent(1 - 4 * (heat - 0.75f), max - min), min, alpha); } // Based on https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L120-L235 void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery* query, const dtMeshTile* tile, unsigned char flags, float heat) { using namespace SceneUtil; dtPolyRef base = mesh.getPolyRefBase(tile); int tileNum = mesh.decodePolyIdTile(base); const unsigned alpha = tile->header->userId == 0 ? 64 : 128; const unsigned int tileNumColor = duIntToCol(tileNum, alpha); dd->depthMask(false); dd->begin(DU_DRAW_TRIS); for (int i = 0; i < tile->header->polyCount; ++i) { const dtPoly* p = &tile->polys[i]; if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links. continue; const dtPolyDetail* pd = &tile->detailMeshes[i]; unsigned int col; if (query && query->isInClosedList(base | (dtPolyRef)i)) col = duRGBA(255, 196, 0, alpha); else { if (flags & NavMeshTileDrawFlagsColorTiles) col = duTransCol(tileNumColor, alpha); else if (flags & NavMeshTileDrawFlagsHeat) col = heatToColor(heat, alpha); else col = duTransCol(dd->areaToCol(p->getArea()), alpha); } for (int j = 0; j < pd->triCount; ++j) { const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; for (int k = 0; k < 3; ++k) { if (t[k] < p->vertCount) dd->vertex(&tile->verts[p->verts[t[k]]*3], col); else dd->vertex(&tile->detailVerts[(pd->vertBase+t[k]-p->vertCount)*3], col); } } } dd->end(); // Draw inter poly boundaries drawPolyBoundaries(dd, tile, duRGBA(0,48,64,32), 1.5f, true); // Draw outer poly boundaries drawPolyBoundaries(dd, tile, duRGBA(0,48,64,220), 2.5f, false); if (flags & NavMeshTileDrawFlagsOffMeshConnections) { dd->begin(DU_DRAW_LINES, 2.0f); for (int i = 0; i < tile->header->polyCount; ++i) { const dtPoly* p = &tile->polys[i]; if (p->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) // Skip regular polys. continue; unsigned int col, col2; if (query && query->isInClosedList(base | (dtPolyRef)i)) col = duRGBA(255,196,0,220); else col = duDarkenCol(duTransCol(dd->areaToCol(p->getArea()), 220)); const dtOffMeshConnection* con = &tile->offMeshCons[i - tile->header->offMeshBase]; const float* va = &tile->verts[p->verts[0]*3]; const float* vb = &tile->verts[p->verts[1]*3]; // Check to see if start and end end-points have links. bool startSet = false; bool endSet = false; for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) { if (tile->links[k].edge == 0) startSet = true; if (tile->links[k].edge == 1) endSet = true; } // End points and their on-mesh locations. dd->vertex(va[0],va[1],va[2], col); dd->vertex(con->pos[0],con->pos[1],con->pos[2], col); col2 = startSet ? col : duRGBA(220,32,16,196); duAppendCircle(dd, con->pos[0],con->pos[1]+0.1f,con->pos[2], con->rad, col2); dd->vertex(vb[0],vb[1],vb[2], col); dd->vertex(con->pos[3],con->pos[4],con->pos[5], col); col2 = endSet ? col : duRGBA(220,32,16,196); duAppendCircle(dd, con->pos[3],con->pos[4]+0.1f,con->pos[5], con->rad, col2); // End point vertices. dd->vertex(con->pos[0],con->pos[1],con->pos[2], duRGBA(0,48,64,196)); dd->vertex(con->pos[0],con->pos[1]+0.2f,con->pos[2], duRGBA(0,48,64,196)); dd->vertex(con->pos[3],con->pos[4],con->pos[5], duRGBA(0,48,64,196)); dd->vertex(con->pos[3],con->pos[4]+0.2f,con->pos[5], duRGBA(0,48,64,196)); // Connection arc. duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f, (con->flags & 1) ? 0.6f : 0, 0.6f, col); } dd->end(); } const unsigned int vcol = duRGBA(0,0,0,196); dd->begin(DU_DRAW_POINTS, 3.0f); for (int i = 0; i < tile->header->vertCount; ++i) { const float* v = &tile->verts[i*3]; dd->vertex(v[0], v[1], v[2], vcol); } dd->end(); dd->depthMask(true); } } namespace SceneUtil { osg::ref_ptr makeNavMeshTileStateSet() { osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); const float polygonOffsetFactor = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; const float polygonOffsetUnits = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; osg::ref_ptr polygonOffset = new osg::PolygonOffset(polygonOffsetFactor, polygonOffsetUnits); osg::ref_ptr stateSet = new osg::StateSet; stateSet->setAttribute(material); stateSet->setAttributeAndModes(polygonOffset); return stateSet; } osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, const DetourNavigator::Settings& settings, const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, unsigned char flags, unsigned minSalt, unsigned maxSalt) { if (meshTile.header == nullptr) return nullptr; osg::ref_ptr group(new osg::Group); group->setStateSet(groupStateSet); constexpr float shift = 10.0f; DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecast.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; navMeshQuery.init(&navMesh, settings.mDetour.mMaxNavMeshQueryNodes); drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, flags, getHeat(meshTile.salt, minSalt, maxSalt)); return group; } } openmw-openmw-0.48.0/components/sceneutil/navmesh.hpp000066400000000000000000000016461445372753700227750ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H #define OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H #include class dtNavMesh; struct dtMeshTile; namespace osg { class Group; class StateSet; } namespace DetourNavigator { struct Settings; } namespace SceneUtil { enum NavMeshTileDrawFlags : unsigned char { NavMeshTileDrawFlagsOffMeshConnections = 1, NavMeshTileDrawFlagsClosedList = 1 << 1, NavMeshTileDrawFlagsColorTiles = 1 << 2, NavMeshTileDrawFlagsHeat = 1 << 3, }; osg::ref_ptr makeNavMeshTileStateSet(); osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, const DetourNavigator::Settings& settings, const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, unsigned char flags, unsigned minSalt, unsigned maxSalt); } #endif openmw-openmw-0.48.0/components/sceneutil/nodecallback.hpp000066400000000000000000000015611445372753700237320ustar00rootroot00000000000000#ifndef SCENEUTIL_NODECALLBACK_H #define SCENEUTIL_NODECALLBACK_H #include namespace osg { class Node; class NodeVisitor; } namespace SceneUtil { template class NodeCallback : public osg::Callback { public: NodeCallback(){} NodeCallback(const NodeCallback& nc,const osg::CopyOp& copyop): osg::Callback(nc, copyop) {} bool run(osg::Object* object, osg::Object* data) override { static_cast(this)->operator()((NodeType)object, (VisitorType)data->asNodeVisitor()); return true; } template void traverse(NodeType object, VT data) { if (_nestedCallback.valid()) _nestedCallback->run(object, data); else data->traverse(*object); } }; } #endif openmw-openmw-0.48.0/components/sceneutil/optimizer.cpp000066400000000000000000002156421445372753700233540ustar00rootroot00000000000000/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ /* Modified for OpenMW */ #include "optimizer.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace osgUtil; namespace SceneUtil { void Optimizer::reset() { } void Optimizer::optimize(osg::Node* node, unsigned int options) { StatsVisitor stats; if (osg::getNotifyLevel()>=osg::INFO) { node->accept(stats); stats.totalUpStats(); OSG_NOTICE<accept(fstv); result = fstv.removeTransforms(node); ++i; } while (result); // now combine any adjacent static transforms. CombineStaticTransformsVisitor cstv(this); node->accept(cstv); cstv.removeTransforms(node); } if (options & SHARE_DUPLICATE_STATE && _sharedStateManager) { if (_sharedStateMutex) _sharedStateMutex->lock(); _sharedStateManager->share(node); if (_sharedStateMutex) _sharedStateMutex->unlock(); } if (options & REMOVE_REDUNDANT_NODES) { OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<accept(renv); renv.removeEmptyNodes(); RemoveRedundantNodesVisitor rrnv(this); node->accept(rrnv); rrnv.removeRedundantNodes(); MergeGroupsVisitor mgrp(this); node->accept(mgrp); } if (options & MERGE_GEOMETRY) { OSG_INFO<<"Optimizer::optimize() doing MERGE_GEOMETRY"<tick(); MergeGeometryVisitor mgv(this); mgv.setTargetMaximumNumberOfVertices(1000000); mgv.setMergeAlphaBlending(_mergeAlphaBlending); mgv.setViewPoint(_viewPoint); node->accept(mgv); osg::Timer_t endTick = osg::Timer::instance()->tick(); OSG_INFO<<"MERGE_GEOMETRY took "<delta_s(startTick,endTick)<accept(vcv); vcv.optimizeVertices(); } if (options & VERTEX_PRETRANSFORM) { OSG_INFO<<"Optimizer::optimize() doing VERTEX_PRETRANSFORM"<accept(vaov); vaov.optimizeOrder(); } if (osg::getNotifyLevel()>=osg::INFO) { stats.reset(); node->accept(stats); stats.totalUpStats(); OSG_NOTICE<(nullptr)); } } void apply(osg::LOD& lod) override { _currentObjectList.push_back(&lod); traverse(lod); _currentObjectList.pop_back(); } void apply(osg::Transform& transform) override { // for all current objects associated this transform with them. registerWithCurrentObjects(&transform); } void apply(osg::MatrixTransform& transform) override { // for all current objects associated this transform with them. registerWithCurrentObjects(&transform); } void apply(osg::Node& node) override { traverse(node); } void apply(osg::Geometry& geometry) override { } void collectDataFor(osg::Node* node) { _currentObjectList.push_back(node); node->accept(*this); _currentObjectList.pop_back(); } void collectDataFor(osg::Billboard* billboard) { _currentObjectList.push_back(billboard); billboard->accept(*this); _currentObjectList.pop_back(); } void collectDataFor(osg::Drawable* drawable) { _currentObjectList.push_back(drawable); const osg::Drawable::ParentList& parents = drawable->getParents(); for(osg::Drawable::ParentList::const_iterator itr=parents.begin(); itr!=parents.end(); ++itr) { (*itr)->accept(*this); } _currentObjectList.pop_back(); } void setUpMaps(); void disableTransform(osg::Transform* transform); bool removeTransforms(osg::Node* nodeWeCannotRemove); inline bool isOperationPermissibleForObject(const osg::Object* object) const { const osg::Node* node = object->asNode(); if (node) { const osg::Drawable* drawable = node->asDrawable(); if (drawable) return isOperationPermissibleForObject(drawable); else return isOperationPermissibleForObject(node); } return true; } inline bool isOperationPermissibleForObject(const osg::Drawable* drawable) const { return BaseOptimizerVisitor::isOperationPermissibleForObject(drawable); } inline bool isOperationPermissibleForObject(const osg::Node* node) const { return BaseOptimizerVisitor::isOperationPermissibleForObject(node); } protected: struct TransformStruct { typedef std::set ObjectSet; TransformStruct():_canBeApplied(true) {} void add(osg::Object* obj) { _objectSet.insert(obj); } bool _canBeApplied; ObjectSet _objectSet; }; struct ObjectStruct { typedef std::set TransformSet; ObjectStruct():_canBeApplied(true),_moreThanOneMatrixRequired(false) {} inline const osg::Matrix& getMatrix(osg::MatrixTransform* transform) { return transform->getMatrix(); } osg::Matrix getMatrix(osg::Transform* transform) { osg::Matrix matrix; transform->computeLocalToWorldMatrix(matrix, 0); return matrix; } template void add(T* transform, bool canOptimize) { if (transform) { if (!canOptimize) _moreThanOneMatrixRequired=true; else if (transform->getReferenceFrame()!=osg::Transform::RELATIVE_RF) _moreThanOneMatrixRequired=true; else { if (_transformSet.empty()) _firstMatrix = getMatrix(transform); else { if (_firstMatrix!=getMatrix(transform)) _moreThanOneMatrixRequired=true; } } } else { if (!_transformSet.empty()) { if (!_firstMatrix.isIdentity()) _moreThanOneMatrixRequired=true; } } _transformSet.insert(transform); } bool _canBeApplied; bool _moreThanOneMatrixRequired; osg::Matrix _firstMatrix; TransformSet _transformSet; }; template void registerWithCurrentObjects(T* transform) { for(ObjectList::iterator itr=_currentObjectList.begin(); itr!=_currentObjectList.end(); ++itr) { _objectMap[*itr].add(transform, transform && isOperationPermissibleForObject(transform)); } } typedef std::map TransformMap; typedef std::map ObjectMap; typedef std::vector ObjectList; void disableObject(osg::Object* object) { disableObject(_objectMap.find(object)); } void disableObject(ObjectMap::iterator itr); void doTransform(osg::Object* obj,osg::Matrix& matrix); osgUtil::TransformAttributeFunctor _transformFunctor; TransformMap _transformMap; ObjectMap _objectMap; ObjectList _currentObjectList; }; void CollectLowestTransformsVisitor::doTransform(osg::Object* obj,osg::Matrix& matrix) { osg::Node* node = obj->asNode(); if (!node) return; osg::Drawable* drawable = node->asDrawable(); if (drawable) { osgUtil::TransformAttributeFunctor tf(matrix); drawable->accept(tf); osg::Geometry *geom = drawable->asGeometry(); osg::Vec4Array* tangents = geom ? dynamic_cast(geom->getTexCoordArray(7)) : nullptr; if (tangents) { for (unsigned int i=0; isize(); ++i) { osg::Vec4f& itr = (*tangents)[i]; osg::Vec3f vec3 (itr.x(), itr.y(), itr.z()); vec3 = osg::Matrix::transform3x3(tf._im, vec3); vec3.normalize(); itr = osg::Vec4f(vec3.x(), vec3.y(), vec3.z(), itr.w()); } } drawable->dirtyBound(); drawable->dirtyDisplayList(); return; } osg::LOD* lod = dynamic_cast(obj); if (lod) { osg::Matrix matrix_no_trans = matrix; matrix_no_trans.setTrans(0.0f,0.0f,0.0f); osg::Vec3 v111(1.0f,1.0f,1.0f); osg::Vec3 new_v111 = v111*matrix_no_trans; float ratio = new_v111.length()/v111.length(); // move center point. lod->setCenter(lod->getCenter()*matrix); // adjust ranges to new scale. for(unsigned int i=0;igetNumRanges();++i) { lod->setRange(i,lod->getMinRange(i)*ratio,lod->getMaxRange(i)*ratio); } lod->dirtyBound(); return; } osg::Billboard* billboard = dynamic_cast(obj); if (billboard) { osg::Matrix matrix_no_trans = matrix; matrix_no_trans.setTrans(0.0f,0.0f,0.0f); osgUtil::TransformAttributeFunctor tf(matrix_no_trans); osg::Vec3 axis = osg::Matrix::transform3x3(tf._im,billboard->getAxis()); axis.normalize(); billboard->setAxis(axis); osg::Vec3 normal = osg::Matrix::transform3x3(tf._im,billboard->getNormal()); normal.normalize(); billboard->setNormal(normal); for(unsigned int i=0;igetNumDrawables();++i) { billboard->setPosition(i,billboard->getPosition(i)*matrix); billboard->getDrawable(i)->accept(tf); billboard->getDrawable(i)->dirtyBound(); } billboard->dirtyBound(); return; } } void CollectLowestTransformsVisitor::disableObject(ObjectMap::iterator itr) { if (itr==_objectMap.end()) { return; } if (itr->second._canBeApplied) { // we haven't been disabled yet so we need to disable, itr->second._canBeApplied = false; // and then inform everybody we have been disabled. for(ObjectStruct::TransformSet::iterator titr = itr->second._transformSet.begin(); titr != itr->second._transformSet.end(); ++titr) { disableTransform(*titr); } } } void CollectLowestTransformsVisitor::disableTransform(osg::Transform* transform) { TransformMap::iterator itr=_transformMap.find(transform); if (itr==_transformMap.end()) { return; } if (itr->second._canBeApplied) { // we haven't been disabled yet so we need to disable, itr->second._canBeApplied = false; // and then inform everybody we have been disabled. for(TransformStruct::ObjectSet::iterator oitr = itr->second._objectSet.begin(); oitr != itr->second._objectSet.end(); ++oitr) { disableObject(*oitr); } } } void CollectLowestTransformsVisitor::setUpMaps() { // create the TransformMap from the ObjectMap ObjectMap::iterator oitr; for(oitr=_objectMap.begin(); oitr!=_objectMap.end(); ++oitr) { osg::Object* object = oitr->first; ObjectStruct& os = oitr->second; for(ObjectStruct::TransformSet::iterator titr = os._transformSet.begin(); titr != os._transformSet.end(); ++titr) { _transformMap[*titr].add(object); } } // disable all the objects which have more than one matrix associated // with them, and then disable all transforms which have an object associated // them that can't be applied, and then disable all objects which have // disabled transforms associated, recursing until all disabled // associativity. // and disable all objects that the operation is not permisable for) for(oitr=_objectMap.begin(); oitr!=_objectMap.end(); ++oitr) { osg::Object* object = oitr->first; ObjectStruct& os = oitr->second; if (os._canBeApplied) { if (os._moreThanOneMatrixRequired || !isOperationPermissibleForObject(object)) { disableObject(oitr); } } } } bool CollectLowestTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) { // transform the objects that can be applied. for(ObjectMap::iterator oitr=_objectMap.begin(); oitr!=_objectMap.end(); ++oitr) { osg::Object* object = oitr->first; ObjectStruct& os = oitr->second; if (os._canBeApplied) { doTransform(object,os._firstMatrix); } } bool transformRemoved = false; // clean up the transforms. for(TransformMap::iterator titr=_transformMap.begin(); titr!=_transformMap.end(); ++titr) { if (titr->first!=0 && titr->second._canBeApplied) { if (titr->first!=nodeWeCannotRemove) { transformRemoved = true; osg::ref_ptr transform = titr->first; osg::ref_ptr group = new osg::Group; group->setName( transform->getName() ); group->setDataVariance(osg::Object::STATIC); group->setNodeMask(transform->getNodeMask()); group->setStateSet(transform->getStateSet()); group->setUpdateCallback(transform->getUpdateCallback()); group->setEventCallback(transform->getEventCallback()); group->setCullCallback(transform->getCullCallback()); group->setUserDataContainer(transform->getUserDataContainer()); group->setDescriptions(transform->getDescriptions()); for(unsigned int i=0;igetNumChildren();++i) { group->addChild(transform->getChild(i)); } for(int i2=transform->getNumParents()-1;i2>=0;--i2) { transform->getParent(i2)->replaceChild(transform.get(),group.get()); } } else { osg::MatrixTransform* mt = titr->first->asMatrixTransform(); if (mt) mt->setMatrix(osg::Matrix::identity()); else { osg::PositionAttitudeTransform* pat = titr->first->asPositionAttitudeTransform(); if (pat) { pat->setPosition(osg::Vec3(0.0f,0.0f,0.0f)); pat->setAttitude(osg::Quat()); pat->setPivotPoint(osg::Vec3(0.0f,0.0f,0.0f)); } else { OSG_WARN<<"Warning:: during Optimize::CollectLowestTransformsVisitor::removeTransforms(Node*)"<first->className()<(array->clone(osg::CopyOp::DEEP_COPY_ALL)); if (!vbo) vbo = new osg::VertexBufferObject; array->setVertexBufferObject(vbo); return array; } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Geometry& geometry) { if(isOperationPermissibleForObject(&geometry)) { osg::VertexBufferObject* vbo = nullptr; if(geometry.getVertexArray() && geometry.getVertexArray()->referenceCount() > 1) geometry.setVertexArray(cloneArray(geometry.getVertexArray(), vbo, &geometry)); if(geometry.getNormalArray() && geometry.getNormalArray()->referenceCount() > 1) geometry.setNormalArray(cloneArray(geometry.getNormalArray(), vbo, &geometry)); if(geometry.getTexCoordArray(7) && geometry.getTexCoordArray(7)->referenceCount() > 1) // tangents geometry.setTexCoordArray(7, cloneArray(geometry.getTexCoordArray(7), vbo, &geometry)); } _drawableSet.insert(&geometry); } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) { _drawableSet.insert(&drawable); } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Billboard& billboard) { if (!_transformStack.empty()) { _billboardSet.insert(&billboard); } } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Transform& transform) { if (!_transformStack.empty()) { // we need to disable any transform higher in the list. _transformSet.insert(_transformStack.back()); } _transformStack.push_back(&transform); // simple traverse the children as if this Transform didn't exist. traverse(transform); _transformStack.pop_back(); } void Optimizer::FlattenStaticTransformsVisitor::apply(osg::MatrixTransform& transform) { apply(static_cast(transform)); } bool Optimizer::FlattenStaticTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) { CollectLowestTransformsVisitor cltv(_optimizer); for(NodeSet::iterator nitr=_excludedNodeSet.begin(); nitr!=_excludedNodeSet.end(); ++nitr) { cltv.collectDataFor(*nitr); } for(DrawableSet::iterator ditr=_drawableSet.begin(); ditr!=_drawableSet.end(); ++ditr) { cltv.collectDataFor(*ditr); } for(BillboardSet::iterator bitr=_billboardSet.begin(); bitr!=_billboardSet.end(); ++bitr) { cltv.collectDataFor(*bitr); } cltv.setUpMaps(); for(TransformSet::iterator titr=_transformSet.begin(); titr!=_transformSet.end(); ++titr) { cltv.disableTransform(*titr); } return cltv.removeTransforms(nodeWeCannotRemove); } //////////////////////////////////////////////////////////////////////////// // CombineStaticTransforms //////////////////////////////////////////////////////////////////////////// void Optimizer::CombineStaticTransformsVisitor::apply(osg::MatrixTransform& transform) { if (transform.getDataVariance()==osg::Object::STATIC && transform.getNumChildren()==1 && transform.getChild(0)->asTransform()!=0 && transform.getChild(0)->asTransform()->asMatrixTransform()!=0 && transform.getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC && isOperationPermissibleForObject(&transform) && isOperationPermissibleForObject(transform.getChild(0))) { _transformSet.insert(&transform); } traverse(transform); } bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) { if (nodeWeCannotRemove && nodeWeCannotRemove->asTransform()!=0 && nodeWeCannotRemove->asTransform()->asMatrixTransform()!=0) { // remove topmost node from transform set if it exists there. TransformSet::iterator itr = _transformSet.find(nodeWeCannotRemove->asTransform()->asMatrixTransform()); if (itr!=_transformSet.end()) _transformSet.erase(itr); } bool transformRemoved = false; while (!_transformSet.empty()) { // get the first available transform to combine. osg::ref_ptr transform = *_transformSet.begin(); _transformSet.erase(_transformSet.begin()); if (transform->getNumChildren()==1 && transform->getChild(0)->asTransform()!=0 && transform->getChild(0)->asTransform()->asMatrixTransform()!=0 && (!transform->getChild(0)->getStateSet() || transform->getChild(0)->getStateSet()->referenceCount()==1) && transform->getChild(0)->getDataVariance()==osg::Object::STATIC) { // now combine with its child. osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform(); osg::Matrix newMatrix = child->getMatrix()*transform->getMatrix(); child->setMatrix(newMatrix); if (transform->getStateSet()) { if(child->getStateSet()) child->getStateSet()->merge(*transform->getStateSet()); else child->setStateSet(transform->getStateSet()); } transformRemoved = true; osg::Node::ParentList parents = transform->getParents(); for(osg::Node::ParentList::iterator pitr=parents.begin(); pitr!=parents.end(); ++pitr) { (*pitr)->replaceChild(transform.get(),child); } } } return transformRemoved; } //////////////////////////////////////////////////////////////////////////// // RemoveEmptyNodes. //////////////////////////////////////////////////////////////////////////// void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group) { if (group.getNumParents()>0) { // only remove empty groups, but not empty occluders. if (group.getNumChildren()==0 && isOperationPermissibleForObject(&group) && (typeid(group)==typeid(osg::Group) || (group.asTransform())) && (group.getNumChildrenRequiringUpdateTraversal()==0 && group.getNumChildrenRequiringEventTraversal()==0) ) { _redundantNodeList.insert(&group); } } traverse(group); } void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes() { NodeList newEmptyGroups; // keep iterator through until scene graph is cleaned of empty nodes. while (!_redundantNodeList.empty()) { for(NodeList::iterator itr=_redundantNodeList.begin(); itr!=_redundantNodeList.end(); ++itr) { osg::ref_ptr nodeToRemove = (*itr); // take a copy of parents list since subsequent removes will modify the original one. osg::Node::ParentList parents = nodeToRemove->getParents(); for(osg::Node::ParentList::iterator pitr=parents.begin(); pitr!=parents.end(); ++pitr) { osg::Group* parent = *pitr; if (!parent->asSwitch() && !dynamic_cast(parent) && !dynamic_cast(parent)) { parent->removeChild(nodeToRemove.get()); if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); } } } _redundantNodeList.clear(); _redundantNodeList.swap(newEmptyGroups); } } //////////////////////////////////////////////////////////////////////////// // RemoveRedundantNodes. //////////////////////////////////////////////////////////////////////////// bool Optimizer::RemoveRedundantNodesVisitor::isOperationPermissible(osg::Node& node) { return node.getNumParents()>0 && !node.getStateSet() && !node.getCullCallback() && !node.getEventCallback() && !node.getUpdateCallback() && isOperationPermissibleForObject(&node); } void Optimizer::RemoveRedundantNodesVisitor::apply(osg::LOD& lod) { // don't remove any direct children of the LOD because they are used to define each LOD level. for (unsigned int i=0; i group = (*itr)->asGroup(); if (group.valid()) { // take a copy of parents list since subsequent removes will modify the original one. osg::Node::ParentList parents = group->getParents(); for(osg::Node::ParentList::iterator pitr=parents.begin(); pitr!=parents.end(); ++pitr) { unsigned int childIndex = (*pitr)->getChildIndex(group); for (unsigned int i=0; igetNumChildren(); ++i) { if (i==0) (*pitr)->setChild(childIndex, group->getChild(i)); else (*pitr)->insertChild(childIndex+i, group->getChild(i)); } } group->removeChildren(0, group->getNumChildren()); } else { OSG_WARN<<"Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() - failed dynamic_cast"<& lhs,const osg::ref_ptr& rhs) const { if (lhs->getStateSet()getStateSet()) return true; if (rhs->getStateSet()getStateSet()) return false; COMPARE_BINDING(lhs->getNormalArray(), rhs->getNormalArray()) COMPARE_BINDING(lhs->getColorArray(), rhs->getColorArray()) COMPARE_BINDING(lhs->getSecondaryColorArray(), rhs->getSecondaryColorArray()) COMPARE_BINDING(lhs->getFogCoordArray(), rhs->getFogCoordArray()) if (lhs->getNumTexCoordArrays()getNumTexCoordArrays()) return true; if (rhs->getNumTexCoordArrays()getNumTexCoordArrays()) return false; // therefore lhs->getNumTexCoordArrays()==rhs->getNumTexCoordArrays() unsigned int i; for(i=0;igetNumTexCoordArrays();++i) { if (rhs->getTexCoordArray(i)) { if (!lhs->getTexCoordArray(i)) return true; } else if (lhs->getTexCoordArray(i)) return false; } for(i=0;igetNumVertexAttribArrays();++i) { if (rhs->getVertexAttribArray(i)) { if (!lhs->getVertexAttribArray(i)) return true; } else if (lhs->getVertexAttribArray(i)) return false; } if (osg::getBinding(lhs->getNormalArray())==osg::Array::BIND_OVERALL) { // assumes that the bindings and arrays are set up correctly, this // should be the case after running computeCorrectBindingsAndArraySizes(); const osg::Array* lhs_normalArray = lhs->getNormalArray(); const osg::Array* rhs_normalArray = rhs->getNormalArray(); if (lhs_normalArray->getType()getType()) return true; if (rhs_normalArray->getType()getType()) return false; switch(lhs_normalArray->getType()) { case(osg::Array::Vec3bArrayType): if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; break; case(osg::Array::Vec3sArrayType): if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; break; case(osg::Array::Vec3ArrayType): if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; break; default: break; } } if (osg::getBinding(lhs->getColorArray())==osg::Array::BIND_OVERALL) { const osg::Array* lhs_colorArray = lhs->getColorArray(); const osg::Array* rhs_colorArray = rhs->getColorArray(); if (lhs_colorArray->getType()getType()) return true; if (rhs_colorArray->getType()getType()) return false; switch(lhs_colorArray->getType()) { case(osg::Array::Vec4ubArrayType): if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; break; case(osg::Array::Vec3ArrayType): if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; break; case(osg::Array::Vec4ArrayType): if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; break; default: break; } } return false; } }; struct LessGeometryViewPoint { osg::Vec3f _viewPoint; bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const { float len1 = (lhs->getBoundingBox().center() - _viewPoint).length2(); float len2 = (rhs->getBoundingBox().center() - _viewPoint).length2(); return len2 < len1; } }; struct LessGeometryPrimitiveType { bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const { for(unsigned int i=0; igetNumPrimitiveSets() && igetNumPrimitiveSets(); ++i) { if (lhs->getPrimitiveSet(i)->getType()getPrimitiveSet(i)->getType()) return true; else if (rhs->getPrimitiveSet(i)->getType()getPrimitiveSet(i)->getType()) return false; if (lhs->getPrimitiveSet(i)->getMode()getPrimitiveSet(i)->getMode()) return true; else if (rhs->getPrimitiveSet(i)->getMode()getPrimitiveSet(i)->getMode()) return false; } return lhs->getNumPrimitiveSets()getNumPrimitiveSets(); } }; /// Shortcut to get size of an array, even if pointer is nullptr. inline unsigned int getSize(const osg::Array * a) { return a ? a->getNumElements() : 0; } /// When merging geometries, tests if two arrays can be merged, regarding to their number of components, and the number of vertices. bool isArrayCompatible(unsigned int numVertice1, unsigned int numVertice2, const osg::Array* compare1, const osg::Array* compare2) { // Sumed up truth table: // If array (1 or 2) not empty and vertices empty => error, should not happen (allows simplification in formulae below) // If one side has both vertices and array, and the other side has only vertices => then arrays cannot be merged // Else, arrays can be merged //assert(numVertice1 || !getSize(compare1)); //assert(numVertice2 || !getSize(compare2)); return !( (numVertice1 && !getSize(compare1) && getSize(compare2)) || (numVertice2 && !getSize(compare2) && getSize(compare1)) ); } /// Return true only if both geometries have same array type and if arrays (such as TexCoords) are compatible (i.e. both empty or both filled) bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) { unsigned int numVertice1( getSize(g1.getVertexArray()) ); unsigned int numVertice2( getSize(g2.getVertexArray()) ); // first verify arrays size if (!isArrayCompatible(numVertice1,numVertice2,g1.getNormalArray(),g2.getNormalArray()) || !isArrayCompatible(numVertice1,numVertice2,g1.getColorArray(),g2.getColorArray()) || !isArrayCompatible(numVertice1,numVertice2,g1.getSecondaryColorArray(),g2.getSecondaryColorArray()) || !isArrayCompatible(numVertice1,numVertice2,g1.getFogCoordArray(),g2.getFogCoordArray()) || g1.getNumTexCoordArrays()!=g2.getNumTexCoordArrays()) return false; for (unsigned int eachTexCoordArray=0;eachTexCoordArraygetDataType()!=g2.getVertexArray()->getDataType()) return false; if (g1.getNormalArray() && g2.getNormalArray() && g1.getNormalArray()->getDataType()!=g2.getNormalArray()->getDataType()) return false; if (g1.getColorArray() && g2.getColorArray() && g1.getColorArray()->getDataType()!=g2.getColorArray()->getDataType()) return false; if (g1.getSecondaryColorArray() && g2.getSecondaryColorArray() && g1.getSecondaryColorArray()->getDataType()!=g2.getSecondaryColorArray()->getDataType()) return false; if (g1.getFogCoordArray() && g2.getNormalArray() && g1.getFogCoordArray()->getDataType()!=g2.getFogCoordArray()->getDataType()) return false; return true; } bool Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { if (!stateSet || stateSet->getRenderBinMode() & osg::StateSet::INHERIT_RENDERBIN_DETAILS) return false; _stateSetStack.push_back(stateSet); checkAlphaBlendingActive(); return true; } void Optimizer::MergeGeometryVisitor::popStateSet() { _stateSetStack.pop_back(); checkAlphaBlendingActive(); } void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive() { int renderingHint = 0; bool override = false; for (std::vector::const_iterator it = _stateSetStack.begin(); it != _stateSetStack.end(); ++it) { osg::StateSet* stateSet = *it; osg::StateSet::RenderBinMode mode = stateSet->getRenderBinMode(); if (override && !(mode & osg::StateSet::PROTECTED_RENDERBIN_DETAILS)) continue; if (mode & osg::StateSet::USE_RENDERBIN_DETAILS) renderingHint = stateSet->getRenderingHint(); if (mode & osg::StateSet::OVERRIDE_RENDERBIN_DETAILS) override = true; } // Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break. _alphaBlendingActive = renderingHint == osg::StateSet::TRANSPARENT_BIN; } void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) { bool pushed = pushStateSet(group.getStateSet()); if (!_alphaBlendingActive || _mergeAlphaBlending) mergeGroup(group); traverse(group); if (pushed) popStateSet(); } osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps, osg::ElementBufferObject*& ebo, const osg::Geometry* geom) { if (ps->referenceCount() <= 1) return ps; ps = static_cast(ps->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::DrawElements* drawElements = ps->getDrawElements(); if (!drawElements) return ps; if (!ebo) ebo = new osg::ElementBufferObject; drawElements->setElementBufferObject(ebo); return ps; } bool containsSharedPrimitives(const osg::Geometry* geom) { for (unsigned int i=0; igetNumPrimitiveSets(); ++i) if (geom->getPrimitiveSet(i)->referenceCount() > 1) return true; return false; } bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (!isOperationPermissibleForObject(&group)) return false; if (group.getNumChildren()>=2) { typedef std::vector< osg::ref_ptr > DuplicateList; typedef std::vector< osg::ref_ptr > Nodes; typedef std::map< osg::ref_ptr ,DuplicateList,LessGeometry> GeometryDuplicateMap; typedef std::vector MergeList; GeometryDuplicateMap geometryDuplicateMap; Nodes standardChildren; unsigned int i; for(i=0;iasGeometry(); if (geom) { if ( geom->getDataVariance()!=osg::Object::DYNAMIC && isOperationPermissibleForObject(geom)) { geometryDuplicateMap[geom].push_back(geom); } else { standardChildren.push_back(geom); } } else { standardChildren.push_back(child); } } // first try to group geometries with the same properties // (i.e. array types) to avoid loss of data during merging MergeList mergeListChecked; // List of drawables just before merging, grouped by "compatibility" and vertex limit MergeList mergeList; // Intermediate list of drawables, grouped ony by "compatibility" for(GeometryDuplicateMap::iterator itr=geometryDuplicateMap.begin(); itr!=geometryDuplicateMap.end(); ++itr) { if (itr->second.empty()) continue; if (itr->second.size()==1) { mergeList.push_back(DuplicateList()); DuplicateList* duplicateList = &mergeList.back(); duplicateList->push_back(itr->second[0]); continue; } std::sort(itr->second.begin(),itr->second.end(),LessGeometryPrimitiveType()); // initialize the temporary list by pushing the first geometry MergeList mergeListTmp; mergeListTmp.push_back(DuplicateList()); DuplicateList* duplicateList = &mergeListTmp.back(); duplicateList->push_back(itr->second[0]); for(DuplicateList::iterator dupItr=itr->second.begin()+1; dupItr!=itr->second.end(); ++dupItr) { osg::Geometry* geomToPush = dupItr->get(); // try to group geomToPush with another geometry MergeList::iterator eachMergeList=mergeListTmp.begin(); for(;eachMergeList!=mergeListTmp.end();++eachMergeList) { if (!eachMergeList->empty() && eachMergeList->front()!=nullptr && isAbleToMerge(*eachMergeList->front(),*geomToPush)) { eachMergeList->push_back(geomToPush); break; } } // if no suitable group was found, then a new one is created if (eachMergeList==mergeListTmp.end()) { mergeListTmp.push_back(DuplicateList()); duplicateList = &mergeListTmp.back(); duplicateList->push_back(geomToPush); } } // copy the group in the mergeListChecked for(MergeList::iterator eachMergeList=mergeListTmp.begin();eachMergeList!=mergeListTmp.end();++eachMergeList) { mergeListChecked.push_back(*eachMergeList); } } // then build merge list using _targetMaximumNumberOfVertices bool needToDoMerge = false; // dequeue each DuplicateList when vertices limit is reached or when all elements has been checked for(MergeList::iterator itr=mergeListChecked.begin(); itr!=mergeListChecked.end(); ++itr) { DuplicateList& duplicateList(*itr); if (duplicateList.size()==0) { continue; } if (duplicateList.size()==1) { mergeList.push_back(duplicateList); continue; } unsigned int totalNumberVertices = 0; DuplicateList subset; for(DuplicateList::iterator ditr = duplicateList.begin(); ditr != duplicateList.end(); ++ditr) { osg::Geometry* geometry = ditr->get(); unsigned int numVertices = (geometry->getVertexArray() ? geometry->getVertexArray()->getNumElements() : 0); if ((totalNumberVertices+numVertices)>_targetMaximumNumberOfVertices && !subset.empty()) { mergeList.push_back(subset); subset.clear(); totalNumberVertices = 0; } totalNumberVertices += numVertices; subset.push_back(geometry); if (subset.size()>1) needToDoMerge = true; } if (!subset.empty()) mergeList.push_back(subset); } if (needToDoMerge) { // to avoid performance issues associated with incrementally removing a large number children, we remove them all and add back the ones we need. group.removeChildren(0, group.getNumChildren()); for(Nodes::iterator itr = standardChildren.begin(); itr != standardChildren.end(); ++itr) { group.addChild(*itr); } // now do the merging of geometries for(MergeList::iterator mitr = mergeList.begin(); mitr != mergeList.end(); ++mitr) { DuplicateList& duplicateList = *mitr; if (!duplicateList.empty()) { if (_alphaBlendingActive) { LessGeometryViewPoint lgvp; lgvp._viewPoint = _viewPoint; std::sort(duplicateList.begin(), duplicateList.end(), lgvp); } DuplicateList::iterator ditr = duplicateList.begin(); osg::ref_ptr lhs = *ditr++; group.addChild(lhs.get()); for(; ditr != duplicateList.end(); ++ditr) { mergeGeometry(*lhs, **ditr); } } } } } // convert all polygon primitives which has 3 indices into TRIANGLES, 4 indices into QUADS. unsigned int i; for(i=0;iasDrawable(); if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); osg::ElementBufferObject* ebo = nullptr; if (geom) { osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); for(osg::Geometry::PrimitiveSetList::iterator itr=primitives.begin(); itr!=primitives.end(); ++itr) { osg::PrimitiveSet* prim = itr->get(); if (prim->getMode()==osg::PrimitiveSet::POLYGON) { if (prim->getNumIndices()==3) { prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::TRIANGLES); } else if (prim->getNumIndices()==4) { prim = clonePrimitive(prim, ebo, geom); (*itr) = prim; prim->setMode(osg::PrimitiveSet::QUADS); } } } } } // now merge any compatible primitives. for(i=0;iasDrawable(); if (!drawable) continue; osg::Geometry* geom = drawable->asGeometry(); osg::ElementBufferObject* ebo = nullptr; if (geom) { if (geom->getNumPrimitiveSets()>0 && osg::getBinding(geom->getNormalArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && osg::getBinding(geom->getColorArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && osg::getBinding(geom->getSecondaryColorArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && osg::getBinding(geom->getFogCoordArray())!=osg::Array::BIND_PER_PRIMITIVE_SET) { #if 1 bool doneCombine = false; std::set toremove; osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); unsigned int lhsNo=0; unsigned int rhsNo=1; while(rhsNogetType()==rhs->getType() && lhs->getMode()==rhs->getMode()) { switch(lhs->getMode()) { case(osg::PrimitiveSet::POINTS): case(osg::PrimitiveSet::LINES): case(osg::PrimitiveSet::TRIANGLES): case(osg::PrimitiveSet::QUADS): combine = true; break; } } if (combine) { lhs = clonePrimitive(lhs, ebo, geom); primitives[lhsNo] = lhs; switch(lhs->getType()) { case(osg::PrimitiveSet::DrawArraysPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; default: combine = false; break; } } if (combine) { // make this primitive set as invalid and needing cleaning up. toremove.insert(rhs); doneCombine = true; ++rhsNo; } else { lhsNo = rhsNo; ++rhsNo; } } #if 1 if (doneCombine) { // now need to clean up primitiveset so it no longer contains the rhs combined primitives. // first swap with a empty primitiveSet to empty it completely. osg::Geometry::PrimitiveSetList oldPrimitives; primitives.swap(oldPrimitives); // now add the active primitive sets for(osg::Geometry::PrimitiveSetList::iterator pitr = oldPrimitives.begin(); pitr != oldPrimitives.end(); ++pitr) { if (!toremove.count(*pitr)) primitives.push_back(*pitr); } } #endif #else osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); unsigned int primNo=0; while(primNo+1getType()==rhs->getType() && lhs->getMode()==rhs->getMode()) { switch(lhs->getMode()) { case(osg::PrimitiveSet::POINTS): case(osg::PrimitiveSet::LINES): case(osg::PrimitiveSet::TRIANGLES): case(osg::PrimitiveSet::QUADS): combine = true; break; } } if (combine) { switch(lhs->getType()) { case(osg::PrimitiveSet::DrawArraysPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); break; default: break; } } if (combine) { primitives.erase(primitives.begin()+primNo+1); } if (!combine) { primNo++; } } #endif if (doneCombine && !geom->containsSharedArrays() && !containsSharedPrimitives(geom)) { // prefer to use vbo for merged geometries as vbo uses less memory than display lists. geom->setUseVertexBufferObjects(true); geom->setUseDisplayList(false); } if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) { osg::ref_ptr d = new SceneUtil::AutoDepth; d->setWriteMask(false); geom->getOrCreateStateSet()->setAttribute(d); } } } } return false; } class MergeArrayVisitor : public osg::ArrayVisitor { protected: osg::Array* _lhs; public: MergeArrayVisitor() : _lhs(0) {} /// try to merge the content of two arrays. bool merge(osg::Array* lhs,osg::Array* rhs) { if (lhs==0 || rhs==0) return true; if (lhs->getType()!=rhs->getType()) return false; _lhs = lhs; rhs->accept(*this); return true; } template void _merge(T& rhs) { T* lhs = static_cast(_lhs); lhs->insert(lhs->end(),rhs.begin(),rhs.end()); } void apply(osg::Array&) override { OSG_WARN << "Warning: Optimizer's MergeArrayVisitor cannot merge Array type." << std::endl; } void apply(osg::ByteArray& rhs) override { _merge(rhs); } void apply(osg::ShortArray& rhs) override { _merge(rhs); } void apply(osg::IntArray& rhs) override { _merge(rhs); } void apply(osg::UByteArray& rhs) override { _merge(rhs); } void apply(osg::UShortArray& rhs) override { _merge(rhs); } void apply(osg::UIntArray& rhs) override { _merge(rhs); } void apply(osg::Vec4ubArray& rhs) override { _merge(rhs); } void apply(osg::Vec3ubArray& rhs) override{ _merge(rhs); } void apply(osg::Vec2ubArray& rhs) override { _merge(rhs); } void apply(osg::Vec4usArray& rhs) override { _merge(rhs); } void apply(osg::Vec3usArray& rhs) override { _merge(rhs); } void apply(osg::Vec2usArray& rhs) override { _merge(rhs); } void apply(osg::FloatArray& rhs) override { _merge(rhs); } void apply(osg::Vec2Array& rhs) override { _merge(rhs); } void apply(osg::Vec3Array& rhs) override { _merge(rhs); } void apply(osg::Vec4Array& rhs) override { _merge(rhs); } void apply(osg::DoubleArray& rhs) override { _merge(rhs); } void apply(osg::Vec2dArray& rhs) override { _merge(rhs); } void apply(osg::Vec3dArray& rhs) override { _merge(rhs); } void apply(osg::Vec4dArray& rhs) override { _merge(rhs); } void apply(osg::Vec2bArray& rhs) override { _merge(rhs); } void apply(osg::Vec3bArray& rhs) override { _merge(rhs); } void apply(osg::Vec4bArray& rhs) override { _merge(rhs); } void apply(osg::Vec2sArray& rhs) override { _merge(rhs); } void apply(osg::Vec3sArray& rhs) override { _merge(rhs); } void apply(osg::Vec4sArray& rhs) override { _merge(rhs); } }; bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) { MergeArrayVisitor merger; osg::VertexBufferObject* vbo = nullptr; unsigned int base = 0; if (lhs.getVertexArray() && rhs.getVertexArray()) { base = lhs.getVertexArray()->getNumElements(); if (lhs.getVertexArray()->referenceCount() > 1) lhs.setVertexArray(cloneArray(lhs.getVertexArray(), vbo, &lhs)); if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray())) { OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { if (lhs.getNormalArray()->referenceCount() > 1) lhs.setNormalArray(cloneArray(lhs.getNormalArray(), vbo, &lhs)); if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray())) { OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { if (lhs.getColorArray()->referenceCount() > 1) lhs.setColorArray(cloneArray(lhs.getColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getColorArray(),rhs.getColorArray())) { OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { if (lhs.getSecondaryColorArray()->referenceCount() > 1) lhs.setSecondaryColorArray(cloneArray(lhs.getSecondaryColorArray(), vbo, &lhs)); if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray())) { OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) { if (lhs.getFogCoordArray()->referenceCount() > 1) lhs.setFogCoordArray(cloneArray(lhs.getFogCoordArray(), vbo, &lhs)); if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray())) { OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <referenceCount() > 1) lhs.setTexCoordArray(unit, cloneArray(lhs.getTexCoordArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit))) { OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <referenceCount() > 1) lhs.setVertexAttribArray(unit, cloneArray(lhs.getVertexAttribArray(unit), vbo, &lhs)); if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit))) { OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <get(); switch(primitive->getType()) { case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): { osg::DrawElementsUByte* primitiveUByte = static_cast(primitive); unsigned int currentMaximum = 0; for(osg::DrawElementsUByte::iterator eitr=primitiveUByte->begin(); eitr!=primitiveUByte->end(); ++eitr) { currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr); } if ((base+currentMaximum)>=65536) { // must promote to a DrawElementsUInt osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); if (!ebo) ebo = new osg::ElementBufferObject; new_primitive->setElementBufferObject(ebo); std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else if ((base+currentMaximum)>=256) { // must promote to a DrawElementsUShort osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode()); if (!ebo) ebo = new osg::ElementBufferObject; new_primitive->setElementBufferObject(ebo); std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { (*primItr) = clonePrimitive(primitive, ebo, &lhs); (*primItr)->offsetIndices(base); } } break; case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): { osg::DrawElementsUShort* primitiveUShort = static_cast(primitive); unsigned int currentMaximum = 0; for(osg::DrawElementsUShort::iterator eitr=primitiveUShort->begin(); eitr!=primitiveUShort->end(); ++eitr) { currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr); } if ((base+currentMaximum)>=65536) { // must promote to a DrawElementsUInt osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); if (!ebo) ebo = new osg::ElementBufferObject; new_primitive->setElementBufferObject(ebo); std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive)); new_primitive->offsetIndices(base); (*primItr) = new_primitive; } else { (*primItr) = clonePrimitive(primitive, ebo, &lhs); (*primItr)->offsetIndices(base); } } break; case(osg::PrimitiveSet::DrawArraysPrimitiveType): case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): default: (*primItr) = clonePrimitive(primitive, ebo, &lhs); (*primItr)->offsetIndices(base); break; } } for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr) { lhs.addPrimitiveSet(primItr->get()); } lhs.dirtyBound(); lhs.dirtyDisplayList(); if (osg::UserDataContainer* rhsUserData = rhs.getUserDataContainer()) for (unsigned int i=0; igetNumUserObjects(); ++i) lhs.getOrCreateUserDataContainer()->addUserObject(rhsUserData->getUserObject(i)); return true; } bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs) { if (lhs.getFirst()+lhs.getCount()==rhs.getFirst()) { lhs.setCount(lhs.getCount()+rhs.getCount()); return true; } return false; } bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs) { int lhs_count = std::accumulate(lhs.begin(),lhs.end(),0); if (lhs.getFirst()+lhs_count==rhs.getFirst()) { lhs.insert(lhs.end(),rhs.begin(),rhs.end()); return true; } return false; } bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs) { lhs.insert(lhs.end(),rhs.begin(),rhs.end()); return true; } bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs) { lhs.insert(lhs.end(),rhs.begin(),rhs.end()); return true; } bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs) { lhs.insert(lhs.end(),rhs.begin(),rhs.end()); return true; } bool Optimizer::MergeGroupsVisitor::isOperationPermissible(osg::Group& node) { return !node.getCullCallback() && !node.getEventCallback() && !node.getUpdateCallback() && typeid(node)==typeid(osg::Group) && isOperationPermissibleForObject(&node); } void Optimizer::MergeGroupsVisitor::apply(osg::LOD &lod) { // don't merge the direct children of the LOD because they are used to define each LOD level. traverse(lod); } void Optimizer::MergeGroupsVisitor::apply(osg::Switch &switchNode) { // We should keep all switch child nodes since they reflect different switch states. traverse(switchNode); } void Optimizer::MergeGroupsVisitor::apply(osg::Sequence &sequenceNode) { // We should keep all sequence child nodes since they reflect different sequence states. traverse(sequenceNode); } void Optimizer::MergeGroupsVisitor::apply(osg::Group &group) { if (group.getNumChildren() <= 1) traverse(group); else { typedef std::map > GroupMap; GroupMap childGroups; for (unsigned int i=0; iasGroup(); if (childGroup && isOperationPermissible(*childGroup)) { childGroups[childGroup->getStateSet()].insert(childGroup); } } for (GroupMap::iterator it = childGroups.begin(); it != childGroups.end(); ++it) { const std::set& groupSet = it->second; if (groupSet.size() <= 1) continue; else { osg::Group* first = *groupSet.begin(); for (std::set::const_iterator groupIt = ++groupSet.begin(); groupIt != groupSet.end(); ++groupIt) { osg::Group* toMerge = *groupIt; for (unsigned int i=0; igetNumChildren(); ++i) first->addChild(toMerge->getChild(i)); toMerge->removeChildren(0, toMerge->getNumChildren()); group.removeChild(toMerge); } } } traverse(group); } } } openmw-openmw-0.48.0/components/sceneutil/optimizer.hpp000066400000000000000000000470711445372753700233600ustar00rootroot00000000000000/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ /* Modified for OpenMW */ #ifndef OPENMW_OSGUTIL_OPTIMIZER #define OPENMW_OSGUTIL_OPTIMIZER #include #include #include #include #include //#include #include #include namespace osgDB { class SharedStateManager; } //namespace osgUtil { namespace SceneUtil { // forward declare class Optimizer; /** Helper base class for implementing Optimizer techniques.*/ class BaseOptimizerVisitor : public osg::NodeVisitor { public: BaseOptimizerVisitor(Optimizer* optimizer, unsigned int operation): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _optimizer(optimizer), _operationType(operation) { setNodeMaskOverride(0xffffffff); } inline bool isOperationPermissibleForObject(const osg::StateSet* object) const; inline bool isOperationPermissibleForObject(const osg::StateAttribute* object) const; inline bool isOperationPermissibleForObject(const osg::Drawable* object) const; inline bool isOperationPermissibleForObject(const osg::Node* object) const; protected: Optimizer* _optimizer; unsigned int _operationType; }; /** Traverses scene graph to improve efficiency. See OptimizationOptions. * For example of usage see examples/osgimpostor or osgviewer. */ class Optimizer { public: Optimizer() : _mergeAlphaBlending(false), _sharedStateManager(nullptr), _sharedStateMutex(nullptr) {} virtual ~Optimizer() {} enum OptimizationOptions { FLATTEN_STATIC_TRANSFORMS = (1 << 0), REMOVE_REDUNDANT_NODES = (1 << 1), REMOVE_LOADED_PROXY_NODES = (1 << 2), COMBINE_ADJACENT_LODS = (1 << 3), SHARE_DUPLICATE_STATE = (1 << 4), MERGE_GEOMETRY = (1 << 5), CHECK_GEOMETRY = (1 << 6), // deprecated, currently no-op MAKE_FAST_GEOMETRY = (1 << 7), SPATIALIZE_GROUPS = (1 << 8), COPY_SHARED_NODES = (1 << 9), TRISTRIP_GEOMETRY = (1 << 10), TESSELLATE_GEOMETRY = (1 << 11), OPTIMIZE_TEXTURE_SETTINGS = (1 << 12), MERGE_GEODES = (1 << 13), FLATTEN_BILLBOARDS = (1 << 14), TEXTURE_ATLAS_BUILDER = (1 << 15), STATIC_OBJECT_DETECTION = (1 << 16), FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS = (1 << 17), INDEX_MESH = (1 << 18), VERTEX_POSTTRANSFORM = (1 << 19), VERTEX_PRETRANSFORM = (1 << 20), DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS | REMOVE_REDUNDANT_NODES | REMOVE_LOADED_PROXY_NODES | COMBINE_ADJACENT_LODS | SHARE_DUPLICATE_STATE | MERGE_GEOMETRY | MAKE_FAST_GEOMETRY | CHECK_GEOMETRY | OPTIMIZE_TEXTURE_SETTINGS | STATIC_OBJECT_DETECTION, ALL_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS | REMOVE_REDUNDANT_NODES | REMOVE_LOADED_PROXY_NODES | COMBINE_ADJACENT_LODS | SHARE_DUPLICATE_STATE | MERGE_GEODES | MERGE_GEOMETRY | MAKE_FAST_GEOMETRY | CHECK_GEOMETRY | SPATIALIZE_GROUPS | COPY_SHARED_NODES | TRISTRIP_GEOMETRY | OPTIMIZE_TEXTURE_SETTINGS | TEXTURE_ATLAS_BUILDER | STATIC_OBJECT_DETECTION }; void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } void setSharedStateManager(osgDB::SharedStateManager* sharedStateManager, std::mutex* sharedStateMutex) { _sharedStateMutex = sharedStateMutex; _sharedStateManager = sharedStateManager; } /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ void reset(); /** Traverse the node and its subgraph with a series of optimization * visitors, specified by the OptimizationOptions.*/ virtual void optimize(osg::Node* node, unsigned int options); /** Callback for customizing what operations are permitted on objects in the scene graph.*/ struct IsOperationPermissibleForObjectCallback : public osg::Referenced { virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateSet* stateset,unsigned int option) const { return optimizer->isOperationPermissibleForObjectImplementation(stateset,option); } virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateAttribute* attribute,unsigned int option) const { return optimizer->isOperationPermissibleForObjectImplementation(attribute,option); } virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Drawable* drawable,unsigned int option) const { return optimizer->isOperationPermissibleForObjectImplementation(drawable,option); } virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Node* node,unsigned int option) const { return optimizer->isOperationPermissibleForObjectImplementation(node,option); } }; /** Set the callback for customizing what operations are permitted on objects in the scene graph.*/ void setIsOperationPermissibleForObjectCallback(IsOperationPermissibleForObjectCallback* callback) { _isOperationPermissibleForObjectCallback=callback; } /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/ IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() { return _isOperationPermissibleForObjectCallback.get(); } /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/ const IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() const { return _isOperationPermissibleForObjectCallback.get(); } inline void setPermissibleOptimizationsForObject(const osg::Object* object, unsigned int options) { _permissibleOptimizationsMap[object] = options; } inline unsigned int getPermissibleOptimizationsForObject(const osg::Object* object) const { PermissibleOptimizationsMap::const_iterator itr = _permissibleOptimizationsMap.find(object); if (itr!=_permissibleOptimizationsMap.end()) return itr->second; else return 0xffffffff; } inline bool isOperationPermissibleForObject(const osg::StateSet* object, unsigned int option) const { if (_isOperationPermissibleForObjectCallback.valid()) return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); else return isOperationPermissibleForObjectImplementation(object,option); } inline bool isOperationPermissibleForObject(const osg::StateAttribute* object, unsigned int option) const { if (_isOperationPermissibleForObjectCallback.valid()) return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); else return isOperationPermissibleForObjectImplementation(object,option); } inline bool isOperationPermissibleForObject(const osg::Drawable* object, unsigned int option) const { if (_isOperationPermissibleForObjectCallback.valid()) return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); else return isOperationPermissibleForObjectImplementation(object,option); } inline bool isOperationPermissibleForObject(const osg::Node* object, unsigned int option) const { if (_isOperationPermissibleForObjectCallback.valid()) return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); else return isOperationPermissibleForObjectImplementation(object,option); } bool isOperationPermissibleForObjectImplementation(const osg::StateSet* stateset, unsigned int option) const { return (option & getPermissibleOptimizationsForObject(stateset))!=0; } bool isOperationPermissibleForObjectImplementation(const osg::StateAttribute* attribute, unsigned int option) const { return (option & getPermissibleOptimizationsForObject(attribute))!=0; } bool isOperationPermissibleForObjectImplementation(const osg::Drawable* drawable, unsigned int option) const { if (option & (REMOVE_REDUNDANT_NODES|MERGE_GEOMETRY)) { if (drawable->getUserData()) return false; if (drawable->getUpdateCallback()) return false; if (drawable->getEventCallback()) return false; if (drawable->getCullCallback()) return false; } return (option & getPermissibleOptimizationsForObject(drawable))!=0; } bool isOperationPermissibleForObjectImplementation(const osg::Node* node, unsigned int option) const { if (option & (REMOVE_REDUNDANT_NODES|COMBINE_ADJACENT_LODS|FLATTEN_STATIC_TRANSFORMS)) { if (node->getUserData()) return false; if (node->getUpdateCallback()) return false; if (node->getEventCallback()) return false; if (node->getCullCallback()) return false; if (node->getNumDescriptions()>0) return false; if (node->getStateSet()) return false; if (node->getNodeMask()!=0xffffffff) return false; // if (!node->getName().empty()) return false; } return (option & getPermissibleOptimizationsForObject(node))!=0; } protected: osg::ref_ptr _isOperationPermissibleForObjectCallback; typedef std::map PermissibleOptimizationsMap; PermissibleOptimizationsMap _permissibleOptimizationsMap; osg::Vec3f _viewPoint; bool _mergeAlphaBlending; osgDB::SharedStateManager* _sharedStateManager; mutable std::mutex* _sharedStateMutex; public: /** Flatten Static Transform nodes by applying their transform to the * geometry on the leaves of the scene graph, then removing the * now redundant transforms. Static transformed subgraphs that have multiple * parental paths above them are not flattened, if you require this then * the subgraphs have to be duplicated - for this use the * FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor. */ class FlattenStaticTransformsVisitor : public BaseOptimizerVisitor { public: FlattenStaticTransformsVisitor(Optimizer* optimizer=0): BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} void apply(osg::Node& node) override; void apply(osg::Geometry& geometry) override; void apply(osg::Drawable& drawable) override; void apply(osg::Billboard& billboard) override; void apply(osg::Transform& transform) override final; void apply(osg::MatrixTransform& transform) override; bool removeTransforms(osg::Node* nodeWeCannotRemove); protected: typedef std::vector TransformStack; typedef std::set DrawableSet; typedef std::set BillboardSet; typedef std::set NodeSet; typedef std::set TransformSet; TransformStack _transformStack; NodeSet _excludedNodeSet; DrawableSet _drawableSet; BillboardSet _billboardSet; TransformSet _transformSet; }; /** Combine Static Transform nodes that sit above one another.*/ class CombineStaticTransformsVisitor : public BaseOptimizerVisitor { public: CombineStaticTransformsVisitor(Optimizer* optimizer=0): BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} void apply(osg::MatrixTransform& transform) override; void apply(osg::Geometry&) override { } bool removeTransforms(osg::Node* nodeWeCannotRemove); protected: typedef std::set TransformSet; TransformSet _transformSet; }; /** Remove rendundant nodes, such as groups with one single child.*/ class RemoveEmptyNodesVisitor : public BaseOptimizerVisitor { public: typedef std::set NodeList; NodeList _redundantNodeList; RemoveEmptyNodesVisitor(Optimizer* optimizer=0): BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} void apply(osg::Group& group) override; void apply(osg::Geometry&) override { } void removeEmptyNodes(); }; /** Remove redundant nodes, such as groups with one single child.*/ class RemoveRedundantNodesVisitor : public BaseOptimizerVisitor { public: typedef std::set NodeList; NodeList _redundantNodeList; RemoveRedundantNodesVisitor(Optimizer* optimizer=0): BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} void apply(osg::Group& group) override; void apply(osg::Transform& transform) override; void apply(osg::LOD& lod) override; void apply(osg::Switch& switchNode) override; void apply(osg::Sequence& sequenceNode) override; void apply(osg::Geometry&) override { } bool isOperationPermissible(osg::Node& node); void removeRedundantNodes(); }; /** Merge adjacent Groups that have the same StateSet. */ class MergeGroupsVisitor : public SceneUtil::BaseOptimizerVisitor { public: MergeGroupsVisitor(SceneUtil::Optimizer* optimizer) : BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) { } bool isOperationPermissible(osg::Group& node); void apply(osg::Geometry&) override { } void apply(osg::Group& group) override; void apply(osg::LOD& lod) override; void apply(osg::Switch& switchNode) override; void apply(osg::Sequence& sequenceNode) override; }; class MergeGeometryVisitor : public BaseOptimizerVisitor { public: /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {} void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } void setTargetMaximumNumberOfVertices(unsigned int num) { _targetMaximumNumberOfVertices = num; } unsigned int getTargetMaximumNumberOfVertices() const { return _targetMaximumNumberOfVertices; } bool pushStateSet(osg::StateSet* stateSet); void popStateSet(); void checkAlphaBlendingActive(); void apply(osg::Geometry&) override { } void apply(osg::Group& group) override; void apply(osg::Billboard&) override { /* don't do anything*/ } bool mergeGroup(osg::Group& group); static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs); static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs); static bool mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs); static bool mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs); static bool mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs); static bool mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs); protected: unsigned int _targetMaximumNumberOfVertices; std::vector _stateSetStack; bool _alphaBlendingActive; bool _mergeAlphaBlending; osg::Vec3f _viewPoint; }; }; inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateSet* object) const { return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; } inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateAttribute* object) const { return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; } inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::Drawable* object) const { return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; } inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::Node* object) const { return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; } } #endif openmw-openmw-0.48.0/components/sceneutil/osgacontroller.cpp000066400000000000000000000137371445372753700243700ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include namespace SceneUtil { LinkVisitor::LinkVisitor() : osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ) { mAnimation = nullptr; } void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { const osgAnimation::ChannelList& channels = mAnimation->getChannels(); for (const auto& channel: channels) { const std::string& channelName = channel->getName(); const std::string& channelTargetName = channel->getTargetName(); if (channelTargetName != umt->getName()) continue; // check if we can link a StackedTransformElement to the current Channel for (const auto & stackedTransform : umt->getStackedTransforms()) { osgAnimation::StackedTransformElement* element = stackedTransform.get(); if (element && !element->getName().empty() && channelName == element->getName()) { osgAnimation::Target* target = element->getOrCreateTarget(); if (target) { channel->setTarget(target); } } } } } void LinkVisitor::setAnimation(Resource::Animation* animation) { mAnimation = animation; } void LinkVisitor::apply(osg::Node& node) { osg::Callback* cb = node.getUpdateCallback(); while (cb) { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt); cb = cb->getNestedCallback(); } if (node.getNumChildrenRequiringUpdateTraversal()) traverse( node ); } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : osg::Object(copy, copyop) , SceneUtil::KeyframeController(copy) , SceneUtil::NodeCallback(copy, copyop) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) { Resource::Animation* copiedAnimationTrack = static_cast(mergedAnimationTrack.get()->clone(copyop)); mMergedAnimationTracks.emplace_back(copiedAnimationTrack); } } osg::Vec3f OsgAnimationController::getTranslation(float time) const { osg::Vec3f translationValue; std::string animationName; float newTime = time; //Find the correct animation based on time for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; } } //Find the root transform track in animation for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) continue; const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); for (const auto& channel: channels) { if (channel->getTargetName() != "bip01" || channel->getName() != "transform") continue; if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast (channel->getSampler()) ) { osg::Matrixf matrix; templateSampler->getValueAt(newTime, matrix); translationValue = matrix.getTrans(); return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); } } } return osg::Vec3f(); } void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); } } void OsgAnimationController::operator() (osg::Node* node, osg::NodeVisitor* nv) { if (hasInput()) { if (mNeedToLink) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (!mLinker.valid()) mLinker = new LinkVisitor(); mLinker->setAnimation(mergedAnimationTrack); node->accept(*mLinker); } mNeedToLink = false; } float time = getInputValue(nv); for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) { update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); } } } traverse(node, nv); } void OsgAnimationController::setEmulatedAnimations(const std::vector& emulatedAnimations) { mEmulatedAnimations = emulatedAnimations; } void OsgAnimationController::addMergedAnimationTrack(osg::ref_ptr animationTrack) { mMergedAnimationTracks.emplace_back(animationTrack); } } openmw-openmw-0.48.0/components/sceneutil/osgacontroller.hpp000066400000000000000000000052061445372753700243650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP #define OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP #include #include #include #include #include #include #include #include namespace SceneUtil { struct EmulatedAnimation { float mStartTime; float mStopTime; std::string mName; }; class LinkVisitor : public osg::NodeVisitor { public: LinkVisitor(); virtual void link(osgAnimation::UpdateMatrixTransform* umt); virtual void setAnimation(Resource::Animation* animation); virtual void apply(osg::Node& node) override; protected: Resource::Animation* mAnimation; }; #ifdef _MSC_VER #pragma warning( push ) /* * Warning C4250: 'SceneUtil::OsgAnimationController': inherits 'osg::Callback::osg::Callback::asCallback' via dominance, * there is no way to solved this if an object must inherit from both osg::Object and osg::Callback */ #pragma warning( disable : 4250 ) #endif class OsgAnimationController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: /// @brief Handles the animation for osgAnimation formats OsgAnimationController() {}; OsgAnimationController(const OsgAnimationController& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, OsgAnimationController) osg::Callback* getAsCallback() override { return this; } /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; /// @brief Calls animation track update() void update(float time, const std::string& animationName); /// @brief Called every frame for osgAnimation void operator() (osg::Node*, osg::NodeVisitor*); /// @brief Sets details of the animations void setEmulatedAnimations(const std::vector& emulatedAnimations); /// @brief Adds an animation track to a model void addMergedAnimationTrack(osg::ref_ptr animationTrack); private: bool mNeedToLink = true; osg::ref_ptr mLinker; std::vector> mMergedAnimationTracks; // Used only by osgAnimation-based formats (e.g. dae) std::vector mEmulatedAnimations; }; #ifdef _MSC_VER #pragma warning( pop ) #endif } #endif openmw-openmw-0.48.0/components/sceneutil/pathgridutil.cpp000066400000000000000000000227421445372753700240270ustar00rootroot00000000000000#include "pathgridutil.hpp" #include #include #include namespace SceneUtil { const unsigned short DiamondVertexCount = 6; const unsigned short DiamondIndexCount = 24; const unsigned short DiamondWireframeIndexCount = 24; const unsigned short DiamondConnectorVertexCount = 4; const unsigned short DiamondTotalVertexCount = DiamondVertexCount + DiamondConnectorVertexCount; const float DiamondWireframeScalar = 1.1f; const osg::Vec3f DiamondPoints[DiamondVertexCount] = { osg::Vec3f( 0.f, 0.f, DiamondHalfHeight * 2.f), osg::Vec3f(-DiamondHalfWidth, -DiamondHalfWidth, DiamondHalfHeight), osg::Vec3f(-DiamondHalfWidth, DiamondHalfWidth, DiamondHalfHeight), osg::Vec3f( DiamondHalfWidth, -DiamondHalfWidth, DiamondHalfHeight), osg::Vec3f( DiamondHalfWidth, DiamondHalfWidth, DiamondHalfHeight), osg::Vec3f( 0.f, 0.f, 0.f) }; const unsigned short DiamondIndices[DiamondIndexCount] = { 0, 2, 1, 0, 1, 3, 0, 3, 4, 0, 4, 2, 5, 1, 2, 5, 3, 1, 5, 4, 3, 5, 2, 4 }; const unsigned short DiamondWireframeIndices[DiamondWireframeIndexCount] = { 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 2, 4, 4, 3, 3, 1, 5, 1, 5, 2, 5, 3, 5, 4 }; const unsigned short DiamondConnectorVertices[DiamondConnectorVertexCount] = { 1, 2, 3, 4 }; const osg::Vec4f DiamondColors[DiamondVertexCount] = { osg::Vec4f(0.f, 0.f, 1.f, 1.f), osg::Vec4f(0.f, .05f, .95f, 1.f), osg::Vec4f(0.f, .1f, .95f, 1.f), osg::Vec4f(0.f, .15f, .95f, 1.f), osg::Vec4f(0.f, .2f, .95f, 1.f), osg::Vec4f(0.f, .25f, 9.f, 1.f) }; const osg::Vec4f DiamondEdgeColor = osg::Vec4f(0.5f, 1.f, 1.f, 1.f); const osg::Vec4f DiamondWireColor = osg::Vec4f(0.72f, 0.f, 0.96f, 1.f); const osg::Vec4f DiamondFocusWireColor = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid) { const unsigned short PointCount = static_cast(pathgrid.mPoints.size()); const size_t EdgeCount = pathgrid.mEdges.size(); const unsigned short VertexCount = PointCount * DiamondTotalVertexCount; const unsigned short ColorCount = VertexCount; const size_t PointIndexCount = PointCount * DiamondIndexCount; const size_t EdgeIndexCount = EdgeCount * 2; osg::ref_ptr gridGeometry = new osg::Geometry(); if (PointIndexCount || EdgeIndexCount) { osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); osg::ref_ptr colors = new osg::Vec4Array(ColorCount); osg::ref_ptr pointIndices = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, PointIndexCount); osg::ref_ptr lineIndices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, EdgeIndexCount); // Add each point/node for (unsigned short pointIndex = 0; pointIndex < PointCount; ++pointIndex) { const ESM::Pathgrid::Point& point = pathgrid.mPoints[pointIndex]; osg::Vec3f position = osg::Vec3f(point.mX, point.mY, point.mZ); unsigned short vertexOffset = pointIndex * DiamondTotalVertexCount; unsigned short indexOffset = pointIndex * DiamondIndexCount; // Point for (unsigned short i = 0; i < DiamondVertexCount; ++i) { (*vertices)[vertexOffset + i] = position + DiamondPoints[i]; (*colors)[vertexOffset + i] = DiamondColors[i]; } for (unsigned short i = 0; i < DiamondIndexCount; ++i) { pointIndices->setElement(indexOffset + i, vertexOffset + DiamondIndices[i]); } // Connectors vertexOffset += DiamondVertexCount; for (unsigned short i = 0; i < DiamondConnectorVertexCount; ++i) { (*vertices)[vertexOffset + i] = position + DiamondPoints[DiamondConnectorVertices[i]]; (*colors)[vertexOffset + i] = DiamondEdgeColor; } } // Add edges unsigned short lineIndex = 0; for (ESM::Pathgrid::EdgeList::const_iterator edge = pathgrid.mEdges.begin(); edge != pathgrid.mEdges.end(); ++edge) { if (edge->mV0 == edge->mV1 || edge->mV0 < 0 || edge->mV0 >= PointCount || edge->mV1 < 0 || edge->mV1 >= PointCount) continue; const ESM::Pathgrid::Point& from = pathgrid.mPoints[edge->mV0]; const ESM::Pathgrid::Point& to = pathgrid.mPoints[edge->mV1]; osg::Vec3f fromPos = osg::Vec3f(from.mX, from.mY, from.mZ); osg::Vec3f toPos = osg::Vec3f(to.mX, to.mY, to.mZ); osg::Vec3f dir = toPos - fromPos; dir.normalize(); osg::Quat rot(static_cast(-osg::PI_2), osg::Vec3f(0, 0, 1)); dir = rot * dir; unsigned short diamondIndex = 0; if (dir.isNaN()) diamondIndex = 0; else if (dir.y() >= 0 && dir.x() > 0) diamondIndex = 3; else if (dir.x() <= 0 && dir.y() > 0) diamondIndex = 1; else if (dir.y() <= 0 && dir.x() < 0) diamondIndex = 0; else if (dir.x() >= 0 && dir.y() < 0) diamondIndex = 2; unsigned short fromIndex = static_cast(edge->mV0); unsigned short toIndex = static_cast(edge->mV1); lineIndices->setElement(lineIndex++, fromIndex * DiamondTotalVertexCount + DiamondVertexCount + diamondIndex); lineIndices->setElement(lineIndex++, toIndex * DiamondTotalVertexCount + DiamondVertexCount + diamondIndex); } lineIndices->resize(lineIndex); gridGeometry->setVertexArray(vertices); gridGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); if (PointIndexCount) gridGeometry->addPrimitiveSet(pointIndices); if (EdgeIndexCount) gridGeometry->addPrimitiveSet(lineIndices); gridGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); gridGeometry->getOrCreateStateSet()->setAttribute(material); return gridGeometry; } osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, const std::vector& selected) { const unsigned short PointCount = selected.size(); const unsigned short VertexCount = PointCount * DiamondVertexCount; const unsigned short ColorCount = VertexCount; const size_t IndexCount = PointCount * DiamondWireframeIndexCount; osg::ref_ptr wireframeGeometry = new osg::Geometry(); if (IndexCount) { osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); osg::ref_ptr colors = new osg::Vec4Array(ColorCount); osg::ref_ptr indices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, IndexCount); osg::Vec3f wireOffset = osg::Vec3f(0, 0, (1 - DiamondWireframeScalar) * DiamondHalfHeight); // Add each point/node for (unsigned short it = 0; it < PointCount; ++it) { const ESM::Pathgrid::Point& point = pathgrid.mPoints[selected[it]]; osg::Vec3f position = osg::Vec3f(point.mX, point.mY, point.mZ) + wireOffset; unsigned short vertexOffset = it * DiamondVertexCount; unsigned short indexOffset = it * DiamondWireframeIndexCount; // Point for (unsigned short i = 0; i < DiamondVertexCount; ++i) { (*vertices)[vertexOffset + i] = position + DiamondPoints[i] * DiamondWireframeScalar; if (it == PointCount - 1) (*colors)[vertexOffset + i] = DiamondFocusWireColor; else (*colors)[vertexOffset + i] = DiamondWireColor; } for (unsigned short i = 0; i < DiamondWireframeIndexCount; ++i) { indices->setElement(indexOffset + i, vertexOffset + DiamondWireframeIndices[i]); } } wireframeGeometry->setVertexArray(vertices); wireframeGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); wireframeGeometry->addPrimitiveSet(indices); wireframeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } return wireframeGeometry; } unsigned short getPathgridNode(unsigned short vertexIndex) { return vertexIndex / (DiamondVertexCount + DiamondConnectorVertexCount); } } openmw-openmw-0.48.0/components/sceneutil/pathgridutil.hpp000066400000000000000000000011151445372753700240230ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_PATHGRIDUTIL_H #define OPENMW_COMPONENTS_PATHGRIDUTIL_H #include #include namespace ESM { struct Pathgrid; } namespace SceneUtil { const float DiamondHalfHeight = 40.f; const float DiamondHalfWidth = 16.f; osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid); osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, const std::vector& selected); unsigned short getPathgridNode(unsigned short vertexIndex); } #endif openmw-openmw-0.48.0/components/sceneutil/positionattitudetransform.cpp000066400000000000000000000023741445372753700266720ustar00rootroot00000000000000#include "positionattitudetransform.hpp" namespace SceneUtil { PositionAttitudeTransform::PositionAttitudeTransform(): _scale(1.0,1.0,1.0) { } bool PositionAttitudeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const { if (_referenceFrame==RELATIVE_RF) { matrix.preMultTranslate(_position); matrix.preMultRotate(_attitude); matrix.preMultScale(_scale); } else // absolute { matrix.makeRotate(_attitude); matrix.postMultTranslate(_position); matrix.preMultScale(_scale); } return true; } bool PositionAttitudeTransform::computeWorldToLocalMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const { if (_scale.x() == 0.0 || _scale.y() == 0.0 || _scale.z() == 0.0) return false; if (_referenceFrame==RELATIVE_RF) { matrix.postMultTranslate(-_position); matrix.postMultRotate(_attitude.inverse()); matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); } else // absolute { matrix.makeRotate(_attitude.inverse()); matrix.preMultTranslate(-_position); matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); } return true; } } openmw-openmw-0.48.0/components/sceneutil/positionattitudetransform.hpp000066400000000000000000000031171445372753700266730ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H #define OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H #include namespace SceneUtil { /// @brief A customized version of osg::PositionAttitudeTransform optimized for speed. /// Uses single precision values. Also removed _pivotPoint which we don't need. class PositionAttitudeTransform : public osg::Transform { public : PositionAttitudeTransform(); PositionAttitudeTransform(const PositionAttitudeTransform& pat,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): Transform(pat,copyop), _position(pat._position), _attitude(pat._attitude), _scale(pat._scale){} META_Node(SceneUtil, PositionAttitudeTransform) inline void setPosition(const osg::Vec3f& pos) { _position = pos; dirtyBound(); } inline const osg::Vec3f& getPosition() const { return _position; } inline void setAttitude(const osg::Quat& quat) { _attitude = quat; dirtyBound(); } inline const osg::Quat& getAttitude() const { return _attitude; } inline void setScale(const osg::Vec3f& scale) { _scale = scale; dirtyBound(); } inline const osg::Vec3f& getScale() const { return _scale; } bool computeLocalToWorldMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const override; bool computeWorldToLocalMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const override; protected : virtual ~PositionAttitudeTransform() {} osg::Vec3f _position; osg::Quat _attitude; osg::Vec3f _scale; }; } #endif openmw-openmw-0.48.0/components/sceneutil/recastmesh.cpp000066400000000000000000000067521445372753700234700ustar00rootroot00000000000000#include "recastmesh.hpp" #include "detourdebugdraw.hpp" #include "depth.hpp" #include #include #include #include #include #include #include #include #include namespace { std::vector calculateNormals(const std::vector& vertices, const std::vector& indices) { std::vector result(indices.size()); for (std::size_t i = 0, n = indices.size(); i < n; i += 3) { const float* v0_ptr = &vertices[indices[i] * 3]; const float* v1_ptr = &vertices[indices[i + 1] * 3]; const float* v2_ptr = &vertices[indices[i + 2] * 3]; const osg::Vec3f v0(v0_ptr[0], v0_ptr[1], v0_ptr[2]); const osg::Vec3f v1(v1_ptr[0], v1_ptr[1], v1_ptr[2]); const osg::Vec3f v2(v2_ptr[0], v2_ptr[1], v2_ptr[2]); const osg::Vec3f e0 = v1 - v0; const osg::Vec3f e1 = v2 - v0; osg::Vec3f normal = e0 ^ e1; normal.normalize(); for (std::size_t j = 0; j < 3; ++j) result[i + j] = normal[j]; } return result; } } namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; const osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, DebugDraw::makeStateSet(), osg::Vec3f(0, 0, 0), 1.0f); const DetourNavigator::Mesh& mesh = recastMesh.getMesh(); std::vector indices = mesh.getIndices(); std::vector vertices = mesh.getVertices(); for (const Heightfield& heightfield : recastMesh.getHeightfields()) { const Mesh heightfieldMesh = makeMesh(heightfield); const int indexShift = static_cast(vertices.size() / 3); std::copy(heightfieldMesh.getVertices().begin(), heightfieldMesh.getVertices().end(), std::back_inserter(vertices)); std::transform(heightfieldMesh.getIndices().begin(), heightfieldMesh.getIndices().end(), std::back_inserter(indices), [&] (int index) { return index + indexShift; }); } for (std::size_t i = 0; i < vertices.size(); i += 3) std::swap(vertices[i + 1], vertices[i + 2]); const auto normals = calculateNormals(vertices, indices); const auto texScale = 1.0f / (settings.mCellSize * 10.0f); duDebugDrawTriMeshSlope(&debugDraw, vertices.data(), static_cast(vertices.size() / 3), indices.data(), normals.data(), static_cast(indices.size() / 3), settings.mMaxSlope, texScale); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); const float polygonOffsetFactor = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; const float polygonOffsetUnits = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; osg::ref_ptr polygonOffset = new osg::PolygonOffset(polygonOffsetFactor, polygonOffsetUnits); osg::ref_ptr stateSet = group->getOrCreateStateSet(); stateSet->setAttribute(material); stateSet->setAttributeAndModes(polygonOffset); return group; } } openmw-openmw-0.48.0/components/sceneutil/recastmesh.hpp000066400000000000000000000006531445372753700234670ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H #define OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H #include namespace osg { class Group; } namespace DetourNavigator { class RecastMesh; struct RecastSettings; } namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, const DetourNavigator::RecastSettings& settings); } #endif openmw-openmw-0.48.0/components/sceneutil/riggeometry.cpp000066400000000000000000000332401445372753700236570ustar00rootroot00000000000000#include "riggeometry.hpp" #include #include #include #include #include "skeleton.hpp" #include "util.hpp" namespace { inline void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, const float weight, osg::Matrixf& result) { osg::Matrixf m = invBindMatrix * matrix; float* ptr = m.ptr(); float* ptrresult = result.ptr(); ptrresult[0] += ptr[0] * weight; ptrresult[1] += ptr[1] * weight; ptrresult[2] += ptr[2] * weight; ptrresult[4] += ptr[4] * weight; ptrresult[5] += ptr[5] * weight; ptrresult[6] += ptr[6] * weight; ptrresult[8] += ptr[8] * weight; ptrresult[9] += ptr[9] * weight; ptrresult[10] += ptr[10] * weight; ptrresult[12] += ptr[12] * weight; ptrresult[13] += ptr[13] * weight; ptrresult[14] += ptr[14] * weight; } } namespace SceneUtil { RigGeometry::RigGeometry() : mSkeleton(nullptr) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { setNumChildrenRequiringUpdateTraversal(1); // update done in accept(NodeVisitor&) } RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) : Drawable(copy, copyop) , mSkeleton(nullptr) , mInfluenceMap(copy.mInfluenceMap) , mBone2VertexVector(copy.mBone2VertexVector) , mBoneSphereVector(copy.mBoneSphereVector) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { setSourceGeometry(copy.mSourceGeometry); setNumChildrenRequiringUpdateTraversal(1); } void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) { for (unsigned int i=0; i<2; ++i) mGeometry[i] = nullptr; mSourceGeometry = sourceGeometry; for (unsigned int i=0; i<2; ++i) { const osg::Geometry& from = *sourceGeometry; // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. // In this specific case the operation is safe under the following two assumptions: // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by mSourceGeometry) // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below) mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry)); osg::Geometry& to = *mGeometry[i]; to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class to.setComputeBoundingBoxCallback(new CopyBoundingBoxCallback()); to.setComputeBoundingSphereCallback(new CopyBoundingSphereCallback()); // vertices and normals are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); to.setVertexArray(vertexArray); } if (const osg::Array* normals = from.getNormalArray()) { osg::ref_ptr normalArray = static_cast(normals->clone(osg::CopyOp::DEEP_COPY_ALL)); if (normalArray) { normalArray->setVertexBufferObject(vbo); to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); } } if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) { mSourceTangents = tangents; osg::ref_ptr tangentArray = static_cast(tangents->clone(osg::CopyOp::DEEP_COPY_ALL)); tangentArray->setVertexBufferObject(vbo); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); } else mSourceTangents = nullptr; } } osg::ref_ptr RigGeometry::getSourceGeometry() const { return mSourceGeometry; } bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) { const osg::NodePath& path = nv->getNodePath(); for (osg::NodePath::const_reverse_iterator it = path.rbegin()+1; it != path.rend(); ++it) { osg::Node* node = *it; if (node->asTransform()) continue; if (Skeleton* skel = dynamic_cast(node)) { mSkeleton = skel; break; } } if (!mSkeleton) { Log(Debug::Error) << "Error: A RigGeometry did not find its parent skeleton"; return false; } if (!mInfluenceMap) { Log(Debug::Error) << "Error: No InfluenceMap set on RigGeometry"; return false; } mBoneNodesVector.clear(); for (auto& bonePair : mBoneSphereVector->mData) { const std::string& boneName = bonePair.first; Bone* bone = mSkeleton->getBone(boneName); if (!bone) { mBoneNodesVector.push_back(nullptr); Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; continue; } mBoneNodesVector.push_back(bone); } for (auto& pair : mBone2VertexVector->mData) { for (auto &weight : pair.first) { const std::string& boneName = weight.first.first; Bone* bone = mSkeleton->getBone(boneName); if (!bone) { mBoneNodesVector.push_back(nullptr); Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; continue; } mBoneNodesVector.push_back(bone); } } return true; } void RigGeometry::cull(osg::NodeVisitor* nv) { if (!mSkeleton) { Log(Debug::Error) << "Error: RigGeometry rendering with no skeleton, should have been initialized by UpdateVisitor"; // try to recover anyway, though rendering is likely to be incorrect. if (!initFromParentSkeleton(nv)) return; } unsigned int traversalNumber = nv->getTraversalNumber(); if (mLastFrameNumber == traversalNumber || (mLastFrameNumber != 0 && !mSkeleton->getActive())) { osg::Geometry& geom = *getGeometry(mLastFrameNumber); nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); return; } mLastFrameNumber = traversalNumber; osg::Geometry& geom = *getGeometry(mLastFrameNumber); mSkeleton->updateBoneMatrices(traversalNumber); // skinning const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); const osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); const osg::Vec4Array* tangentSrc = mSourceTangents; osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); int index = mBoneSphereVector->mData.size(); for (auto &pair : mBone2VertexVector->mData) { osg::Matrixf resultMat (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); for (auto &weight : pair.first) { Bone* bone = mBoneNodesVector[index]; if (bone == nullptr) continue; accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat); index++; } if (mGeomToSkelMatrix) resultMat *= (*mGeomToSkelMatrix); for (auto &vertex : pair.second) { (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); if (normalDst) (*normalDst)[vertex] = osg::Matrixf::transform3x3((*normalSrc)[vertex], resultMat); if (tangentDst) { const osg::Vec4f& srcTangent = (*tangentSrc)[vertex]; osg::Vec3f transformedTangent = osg::Matrixf::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); (*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w()); } } } positionDst->dirty(); if (normalDst) normalDst->dirty(); if (tangentDst) tangentDst->dirty(); geom.osg::Drawable::dirtyGLObjects(); nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); } void RigGeometry::updateBounds(osg::NodeVisitor *nv) { if (!mSkeleton) { if (!initFromParentSkeleton(nv)) return; } if (!mSkeleton->getActive() && !mBoundsFirstFrame) return; mBoundsFirstFrame = false; mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); updateGeomToSkelMatrix(nv->getNodePath()); osg::BoundingBox box; int index = 0; for (auto& boundPair : mBoneSphereVector->mData) { Bone* bone = mBoneNodesVector[index]; if (bone == nullptr) continue; index++; osg::BoundingSpheref bs = boundPair.second; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); else transformBoundingSphere(bone->mMatrixInSkeletonSpace, bs); box.expandBy(bs); } if (box != _boundingBox) { _boundingBox = box; _boundingSphere = osg::BoundingSphere(_boundingBox); _boundingSphereComputed = true; for (unsigned int i=0; idirtyBound(); for (unsigned int i = 0; i < 2; ++i) { osg::Geometry& geom = *mGeometry[i]; static_cast(geom.getComputeBoundingBoxCallback())->boundingBox = _boundingBox; static_cast(geom.getComputeBoundingSphereCallback())->boundingSphere = _boundingSphere; geom.dirtyBound(); } } } void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) { bool foundSkel = false; osg::RefMatrix* geomToSkelMatrix = mGeomToSkelMatrix; if (geomToSkelMatrix) geomToSkelMatrix->makeIdentity(); for (osg::NodePath::const_iterator it = nodePath.begin(); it != nodePath.end()-1; ++it) { osg::Node* node = *it; if (!foundSkel) { if (node == mSkeleton) foundSkel = true; } else { if (osg::Transform* trans = node->asTransform()) { osg::MatrixTransform* matrixTrans = trans->asMatrixTransform(); if (matrixTrans && matrixTrans->getMatrix().isIdentity()) continue; if (!geomToSkelMatrix) geomToSkelMatrix = mGeomToSkelMatrix = new osg::RefMatrix; trans->computeWorldToLocalMatrix(*geomToSkelMatrix, nullptr); } } } } void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) { mInfluenceMap = influenceMap; typedef std::map > Vertex2BoneMap; Vertex2BoneMap vertex2BoneMap; mBoneSphereVector = new BoneSphereVector; mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size()); mBone2VertexVector = new Bone2VertexVector; for (auto& influencePair : mInfluenceMap->mData) { const std::string& boneName = influencePair.first; const BoneInfluence& bi = influencePair.second; mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere); for (auto& weightPair: bi.mWeights) { std::vector& vec = vertex2BoneMap[weightPair.first]; vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second); } } Bone2VertexMap bone2VertexMap; for (auto& vertexPair : vertex2BoneMap) { bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first); } mBone2VertexVector->mData.reserve(bone2VertexMap.size()); mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end()); } void RigGeometry::accept(osg::NodeVisitor &nv) { if (!nv.validNodeMask(*this)) return; nv.pushOntoNodePath(this); if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { // The cull visitor won't be applied to the node itself, // but we want to use its state to render the child geometry. osg::StateSet* stateset = getStateSet(); osgUtil::CullVisitor* cv = static_cast(&nv); if (stateset) cv->pushStateSet(stateset); cull(&nv); if (stateset) cv->popStateSet(); } else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) updateBounds(&nv); else nv.apply(*this); nv.popFromNodePath(); } void RigGeometry::accept(osg::PrimitiveFunctor& func) const { getGeometry(mLastFrameNumber)->accept(func); } osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const { return mGeometry[frame%2].get(); } } openmw-openmw-0.48.0/components/sceneutil/riggeometry.hpp000066400000000000000000000106471445372753700236720ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H #define OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H #include #include namespace SceneUtil { class Skeleton; class Bone; // TODO: This class has a lot of issues. // - We require too many workarounds to ensure safety. // - mSourceGeometry should be const, but can not be const because of a use case in shadervisitor.cpp. // - We create useless mGeometry clones in template RigGeometries. // - We do not support compileGLObjects. // - We duplicate some code in MorphGeometry. /// @brief Mesh skinning implementation. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. class RigGeometry : public osg::Drawable { public: RigGeometry(); RigGeometry(const RigGeometry& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, RigGeometry) // Currently empty as this is difficult to implement. Technically we would need to compile both internal geometries in separate frames but this method is only called once. Alternatively we could compile just the static parts of the model. void compileGLObjects(osg::RenderInfo& renderInfo) const override {} struct BoneInfluence { osg::Matrixf mInvBindMatrix; osg::BoundingSpheref mBoundSphere; // std::vector> mWeights; }; struct InfluenceMap : public osg::Referenced { std::vector> mData; }; void setInfluenceMap(osg::ref_ptr influenceMap); /// Initialize this geometry from the source geometry. /// @note The source geometry will not be modified. void setSourceGeometry(osg::ref_ptr sourceGeom); osg::ref_ptr getSourceGeometry() const; void accept(osg::NodeVisitor &nv) override; bool supports(const osg::PrimitiveFunctor&) const override{ return true; } void accept(osg::PrimitiveFunctor&) const override; struct CopyBoundingBoxCallback : osg::Drawable::ComputeBoundingBoxCallback { osg::BoundingBox boundingBox; osg::BoundingBox computeBound(const osg::Drawable&) const override { return boundingBox; } }; struct CopyBoundingSphereCallback : osg::Node::ComputeBoundingSphereCallback { osg::BoundingSphere boundingSphere; osg::BoundingSphere computeBound(const osg::Node&) const override { return boundingSphere; } }; private: void cull(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv); osg::ref_ptr mGeometry[2]; osg::Geometry* getGeometry(unsigned int frame) const; osg::ref_ptr mSourceGeometry; osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; osg::ref_ptr mGeomToSkelMatrix; osg::ref_ptr mInfluenceMap; typedef std::pair BoneBindMatrixPair; typedef std::pair BoneWeight; typedef std::vector VertexList; typedef std::map, VertexList> Bone2VertexMap; struct Bone2VertexVector : public osg::Referenced { std::vector, VertexList>> mData; }; osg::ref_ptr mBone2VertexVector; struct BoneSphereVector : public osg::Referenced { std::vector> mData; }; osg::ref_ptr mBoneSphereVector; std::vector mBoneNodesVector; unsigned int mLastFrameNumber; bool mBoundsFirstFrame; bool initFromParentSkeleton(osg::NodeVisitor* nv); void updateGeomToSkelMatrix(const osg::NodePath& nodePath); }; } #endif openmw-openmw-0.48.0/components/sceneutil/riggeometryosgaextension.cpp000066400000000000000000000237251445372753700264750ustar00rootroot00000000000000#include "riggeometryosgaextension.hpp" #include #include #include #include #include #include namespace SceneUtil { OsgaRigGeometry::OsgaRigGeometry() : osgAnimation::RigGeometry() { setDataVariance(osg::Object::STATIC); } OsgaRigGeometry::OsgaRigGeometry(const osgAnimation::RigGeometry& copy, const osg::CopyOp& copyop) : osgAnimation::RigGeometry(copy, copyop) { setDataVariance(osg::Object::STATIC); } OsgaRigGeometry::OsgaRigGeometry(const OsgaRigGeometry& copy, const osg::CopyOp& copyop) : osgAnimation::RigGeometry(copy, copyop) { setDataVariance(osg::Object::STATIC); } void OsgaRigGeometry::computeMatrixFromRootSkeleton(osg::MatrixList mtxList) { if (!_root.valid()) { Log(Debug::Warning) << "Warning " << className() <<"::computeMatrixFromRootSkeleton if you have this message it means you miss to call buildTransformer(Skeleton* root), or your RigGeometry (" << getName() <<") is not attached to a Skeleton subgraph"; return; } osg::Matrix notRoot = _root->getMatrix(); _matrixFromSkeletonToGeometry = mtxList[0] * osg::Matrix::inverse(notRoot); _invMatrixFromSkeletonToGeometry = osg::Matrix::inverse(_matrixFromSkeletonToGeometry); _needToComputeMatrix = false; } RigGeometryHolder::RigGeometryHolder() : mBackToOrigin(nullptr), mLastFrameNumber(0), mIsBodyPart(false) { } RigGeometryHolder::RigGeometryHolder(const RigGeometryHolder& copy, const osg::CopyOp& copyop) : Drawable(copy, copyop), mBackToOrigin(copy.mBackToOrigin), mLastFrameNumber(0), mIsBodyPart(copy.mIsBodyPart) { setUseVertexBufferObjects(true); if (!copy.getSourceRigGeometry()) { Log(Debug::Error) << "copy constructor of RigGeometryHolder partially failed (no source RigGeometry)"; return; } osg::ref_ptr rigGeometry = new OsgaRigGeometry(*copy.getSourceRigGeometry(), copyop); setSourceRigGeometry(rigGeometry); } RigGeometryHolder::RigGeometryHolder(const osgAnimation::RigGeometry& copy, const osg::CopyOp& copyop) : mBackToOrigin(nullptr), mLastFrameNumber(0), mIsBodyPart(false) { setUseVertexBufferObjects(true); osg::ref_ptr rigGeometry = new OsgaRigGeometry(copy, copyop); setSourceRigGeometry(rigGeometry); } void RigGeometryHolder::setSourceRigGeometry(osg::ref_ptr sourceRigGeometry) { for (unsigned int i=0; i<2; ++i) mGeometry.at(i) = nullptr; mSourceRigGeometry = sourceRigGeometry; _boundingBox = mSourceRigGeometry->getComputeBoundingBoxCallback()->computeBound(*mSourceRigGeometry); _boundingSphere = osg::BoundingSphere(_boundingBox); for (unsigned int i=0; i<2; ++i) { const OsgaRigGeometry& from = *sourceRigGeometry; // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. // In this specific case the operation is safe under the following two assumptions: // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by mSourceRigGeometry, possibly also RigGeometry._geometry) // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. mGeometry.at(i) = new OsgaRigGeometry(from, osg::CopyOp::SHALLOW_COPY); mGeometry.at(i)->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceRigGeometry)); OsgaRigGeometry& to = *mGeometry.at(i); to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class to.setDataVariance(osg::Object::STATIC); to.setNeedToComputeMatrix(true); // vertices and normals are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. osg::ref_ptr vbo (new osg::VertexBufferObject); vbo->setUsage(GL_DYNAMIC_DRAW_ARB); osg::ref_ptr vertexArray = static_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); if (vertexArray) { vertexArray->setVertexBufferObject(vbo); to.setVertexArray(vertexArray); } if (const osg::Array* normals = from.getNormalArray()) { osg::ref_ptr normalArray = static_cast(normals->clone(osg::CopyOp::DEEP_COPY_ALL)); if (normalArray) { normalArray->setVertexBufferObject(vbo); to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); } } if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) { osg::ref_ptr tangentArray = static_cast(tangents->clone(osg::CopyOp::DEEP_COPY_ALL)); tangentArray->setVertexBufferObject(vbo); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); } } } osg::ref_ptr RigGeometryHolder::getSourceRigGeometry() const { return mSourceRigGeometry; } void RigGeometryHolder::updateRigGeometry(OsgaRigGeometry* geom, osg::NodeVisitor *nv) { if(!geom) return; if(!geom->getSkeleton() && !this->getParents().empty()) { osgAnimation::RigGeometry::FindNearestParentSkeleton finder; if(this->getParents().size() > 1) Log(Debug::Warning) << "A RigGeometry should not have multi parent ( " << geom->getName() << " )"; this->getParents()[0]->accept(finder); if(!finder._root.valid()) { Log(Debug::Warning) << "A RigGeometry did not find a parent skeleton for RigGeometry ( " << geom->getName() << " )"; return; } geom->getRigTransformImplementation()->prepareData(*geom); geom->setSkeleton(finder._root.get()); } if(!geom->getSkeleton()) return; if(geom->getNeedToComputeMatrix()) { osgAnimation::Skeleton* root = geom->getSkeleton(); if (!root) { Log(Debug::Warning) << "Warning: if you have this message it means you miss to call buildTransformer(Skeleton* root), or your RigGeometry is not attached to a Skeleton subgraph"; return; } osg::MatrixList mtxList = root->getWorldMatrices(root); //We always assume that RigGeometries have origin at their root geom->computeMatrixFromRootSkeleton(mtxList); if (mIsBodyPart && mBackToOrigin) updateBackToOriginTransform(geom); } if(geom->getSourceGeometry()) { osg::Drawable::UpdateCallback * up = dynamic_cast(geom->getSourceGeometry()->getUpdateCallback()); if(up) { up->update(nv, geom->getSourceGeometry()); } } geom->update(); } OsgaRigGeometry* RigGeometryHolder::getGeometry(int geometry) { return mGeometry.at(geometry).get(); } void RigGeometryHolder::updateBackToOriginTransform(OsgaRigGeometry* geometry) { osgAnimation::Skeleton* skeleton = geometry->getSkeleton(); if (skeleton) { osg::MatrixList mtxList = mBackToOrigin->getParents()[0]->getWorldMatrices(skeleton); osg::Matrix skeletonMatrix = skeleton->getMatrix(); osg::Matrixf matrixFromSkeletonToGeometry = mtxList[0] * osg::Matrix::inverse(skeletonMatrix); osg::Matrixf invMatrixFromSkeletonToGeometry = osg::Matrix::inverse(matrixFromSkeletonToGeometry); mBackToOrigin->setMatrix(invMatrixFromSkeletonToGeometry); } } void RigGeometryHolder::accept(osg::NodeVisitor &nv) { if (!nv.validNodeMask(*this)) return; nv.pushOntoNodePath(this); if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR && mSourceRigGeometry.get()) { // The cull visitor won't be applied to the node itself, // but we want to use its state to render the child geometry. osg::StateSet* stateset = getStateSet(); osgUtil::CullVisitor* cv = static_cast(&nv); if (stateset) cv->pushStateSet(stateset); unsigned int traversalNumber = nv.getTraversalNumber(); if (mLastFrameNumber == traversalNumber) { OsgaRigGeometry& geom = *getRigGeometryPerFrame(mLastFrameNumber); nv.pushOntoNodePath(&geom); nv.apply(geom); nv.popFromNodePath(); } else { mLastFrameNumber = traversalNumber; OsgaRigGeometry& geom = *getRigGeometryPerFrame(mLastFrameNumber); if (mIsBodyPart) { if (mBackToOrigin) updateBackToOriginTransform(&geom); else { osg::MatrixTransform* matrixTransform = dynamic_cast (this->getParents()[0]); if (matrixTransform) { mBackToOrigin = matrixTransform; updateBackToOriginTransform(&geom); } } } updateRigGeometry(&geom, &nv); nv.pushOntoNodePath(&geom); nv.apply(geom); nv.popFromNodePath(); } if (stateset) cv->popStateSet(); } else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { } else nv.apply(*this); nv.popFromNodePath(); } void RigGeometryHolder::accept(osg::PrimitiveFunctor& func) const { getRigGeometryPerFrame(mLastFrameNumber)->accept(func); } OsgaRigGeometry* RigGeometryHolder::getRigGeometryPerFrame(unsigned int frame) const { return mGeometry.at(frame%2).get(); } } openmw-openmw-0.48.0/components/sceneutil/riggeometryosgaextension.hpp000066400000000000000000000051071445372753700264740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_OSGAEXTENSION_RIGGEOMETRY_H #define OPENMW_COMPONENTS_OSGAEXTENSION_RIGGEOMETRY_H #include #include #include #include namespace SceneUtil { /// @brief Custom RigGeometry-class for osgAnimation-formats (collada) class OsgaRigGeometry : public osgAnimation::RigGeometry { public: OsgaRigGeometry(); OsgaRigGeometry(const osgAnimation::RigGeometry& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); OsgaRigGeometry(const OsgaRigGeometry& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); META_Object(SceneUtil, OsgaRigGeometry); void computeMatrixFromRootSkeleton(osg::MatrixList mtxList); }; /// @brief OpenMW-compatible double buffered static datavariance version of osgAnimation::RigGeometry /// This class is based on osgAnimation::RigGeometry and SceneUtil::RigGeometry class RigGeometryHolder : public osg::Drawable { public: RigGeometryHolder(); RigGeometryHolder(const RigGeometryHolder& copy, const osg::CopyOp& copyop); RigGeometryHolder(const osgAnimation::RigGeometry& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, RigGeometryHolder); void setSourceRigGeometry(osg::ref_ptr sourceRigGeometry); osg::ref_ptr getSourceRigGeometry() const; /// @brief Modified rig update, code based on osgAnimation::UpdateRigGeometry : public osg::Drawable::UpdateCallback void updateRigGeometry(OsgaRigGeometry* geom, osg::NodeVisitor *nv); OsgaRigGeometry* getGeometry(int geometry); void accept(osg::NodeVisitor &nv) override; void accept(osg::PrimitiveFunctor&) const override; bool supports(const osg::PrimitiveFunctor&) const override{ return true; } void setBackToOrigin(osg::MatrixTransform* backToOrigin) {mBackToOrigin = backToOrigin;} void setBodyPart(bool isBodyPart) {mIsBodyPart = isBodyPart;} private: std::array, 2> mGeometry; osg::ref_ptr mSourceRigGeometry; osg::MatrixTransform* mBackToOrigin; //This is used to move riggeometries from their slot locations to skeleton origin in order to get correct deformations for bodyparts unsigned int mLastFrameNumber; bool mIsBodyPart; void updateBackToOriginTransform(OsgaRigGeometry* geometry); OsgaRigGeometry* getRigGeometryPerFrame(unsigned int frame) const; }; } #endif openmw-openmw-0.48.0/components/sceneutil/rtt.cpp000066400000000000000000000253341445372753700221400ustar00rootroot00000000000000#include "rtt.hpp" #include "util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace SceneUtil { class CullCallback : public SceneUtil::NodeCallback { public: void operator()(RTTNode* node, osgUtil::CullVisitor* cv) { node->cull(cv); } }; RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness) : mTextureWidth(textureWidth) , mTextureHeight(textureHeight) , mSamples(samples) , mGenerateMipmaps(generateMipmaps) , mColorBufferInternalFormat(Color::colorInternalFormat()) , mDepthBufferInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) , mStereoAwareness(stereoAwareness) { addCullCallback(new CullCallback); setCullingActive(false); } RTTNode::~RTTNode() { for (auto& vdd : mViewDependentDataMap) { auto* camera = vdd.second->mCamera.get(); if (camera) { camera->removeChildren(0, camera->getNumChildren()); } } mViewDependentDataMap.clear(); } void RTTNode::cull(osgUtil::CullVisitor* cv) { auto frameNumber = cv->getFrameStamp()->getFrameNumber(); auto* vdd = getViewDependentData(cv); if (frameNumber > vdd->mFrameNumber) { apply(vdd->mCamera); auto& sm = Stereo::Manager::instance(); if (sm.getEye(cv) == Stereo::Eye::Left) applyLeft(vdd->mCamera); if (sm.getEye(cv) == Stereo::Eye::Right) applyRight(vdd->mCamera); vdd->mCamera->accept(*cv); } vdd->mFrameNumber = frameNumber; } void RTTNode::setColorBufferInternalFormat(GLint internalFormat) { mColorBufferInternalFormat = internalFormat; } void RTTNode::setDepthBufferInternalFormat(GLint internalFormat) { mDepthBufferInternalFormat = internalFormat; } bool RTTNode::shouldDoPerViewMapping() { if(mStereoAwareness != StereoAwareness::Aware) return false; if (!Stereo::getMultiview()) return true; return false; } bool RTTNode::shouldDoTextureArray() { if (mStereoAwareness == StereoAwareness::Unaware) return false; if (Stereo::getMultiview()) return true; return false; } bool RTTNode::shouldDoTextureView() { if (mStereoAwareness != StereoAwareness::Unaware_MultiViewShaders) return false; if (Stereo::getMultiview()) return true; return false; } osg::Texture2DArray* RTTNode::createTextureArray(GLint internalFormat) { osg::Texture2DArray* textureArray = new osg::Texture2DArray; textureArray->setTextureSize(mTextureWidth, mTextureHeight, 2); textureArray->setInternalFormat(internalFormat); GLenum sourceFormat = 0; GLenum sourceType = 0; if (SceneUtil::isDepthFormat(internalFormat)) { SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); } else { SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); } textureArray->setSourceFormat(sourceFormat); textureArray->setSourceType(sourceType); textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); return textureArray; } osg::Texture2D* RTTNode::createTexture(GLint internalFormat) { osg::Texture2D* texture = new osg::Texture2D; texture->setTextureSize(mTextureWidth, mTextureHeight); texture->setInternalFormat(internalFormat); GLenum sourceFormat = 0; GLenum sourceType = 0; if (SceneUtil::isDepthFormat(internalFormat)) { SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); } else { SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); } texture->setSourceFormat(sourceFormat); texture->setSourceType(sourceType); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); return texture; } osg::Texture* RTTNode::getColorTexture(osgUtil::CullVisitor* cv) { return getViewDependentData(cv)->mColorTexture; } osg::Texture* RTTNode::getDepthTexture(osgUtil::CullVisitor* cv) { return getViewDependentData(cv)->mDepthTexture; } osg::Camera* RTTNode::getCamera(osgUtil::CullVisitor* cv) { return getViewDependentData(cv)->mCamera; } RTTNode::ViewDependentData* RTTNode::getViewDependentData(osgUtil::CullVisitor* cv) { if (!shouldDoPerViewMapping()) // Always setting it to null is an easy way to disable per-view mapping when mDoPerViewMapping is false. // This is safe since the visitor is never dereferenced. cv = nullptr; if (mViewDependentDataMap.count(cv) == 0) { auto camera = new osg::Camera(); auto vdd = std::make_shared(); mViewDependentDataMap[cv] = vdd; mViewDependentDataMap[cv]->mCamera = camera; camera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrderNum); camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); camera->setViewport(0, 0, mTextureWidth, mTextureHeight); SceneUtil::setCameraClearDepth(camera); setDefaults(camera); if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER)) vdd->mColorTexture = camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture; if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER)) vdd->mDepthTexture = camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; if (shouldDoTextureArray()) { // Create any buffer attachments not added in setDefaults if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) { vdd->mColorTexture = createTextureArray(mColorBufferInternalFormat); camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), mGenerateMipmaps, mSamples); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), mGenerateMipmaps); } if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) { vdd->mDepthTexture = createTextureArray(mDepthBufferInternalFormat); camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), false, mSamples); } if (shouldDoTextureView()) { // In this case, shaders being set to multiview forces us to render to a multiview framebuffer even though we don't need that. // This forces us to make Texture2DArray. To make this possible to sample as a Texture2D, make a Texture2D view into the texture array. vdd->mColorTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mColorTexture.get()), 0); vdd->mDepthTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mDepthTexture.get()), 0); } } else { // Create any buffer attachments not added in setDefaults if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) { vdd->mColorTexture = createTexture(mColorBufferInternalFormat); camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps, mSamples); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps); } if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) { vdd->mDepthTexture = createTexture(mDepthBufferInternalFormat); camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, 0, false, mSamples); } } // OSG appears not to properly initialize this metadata. So when multisampling is enabled, OSG will use incorrect formats for the resolve buffers. if (mSamples > 1) { camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mColorBufferInternalFormat; camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = mGenerateMipmaps; camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mDepthBufferInternalFormat; camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = mGenerateMipmaps; } } return mViewDependentDataMap[cv].get(); } } openmw-openmw-0.48.0/components/sceneutil/rtt.hpp000066400000000000000000000112731445372753700221420ustar00rootroot00000000000000#ifndef OPENMW_RTT_H #define OPENMW_RTT_H #include #include #include namespace osg { class Texture2D; class Texture2DArray; class Camera; } namespace osgUtil { class CullVisitor; } namespace SceneUtil { class CreateTextureViewsCallback; /// @brief Implements per-view RTT operations. /// @par With a naive RTT implementation, subsequent views of multiple views will overwrite the results of the previous views, leading to /// the results of the last view being broadcast to all views. An error in all cases where the RTT result depends on the view. /// @par If using an RTTNode this is solved by mapping RTT operations to CullVisitors, which will be unique per view. This requires /// instancing one camera per view, and traversing only the camera mapped to that CV during cull traversals. /// @par Camera settings should be effectuated by overriding the setDefaults() and apply() methods, following a pattern similar to SceneUtil::StateSetUpdater /// @par When using the RTT texture in your statesets, it is recommended to use SceneUtil::StateSetUpdater as a cull callback to handle this as the appropriate /// textures can be retrieved during SceneUtil::StateSetUpdater::Apply() /// @par For any of COLOR_BUFFER or PACKED_DEPTH_STENCIL_BUFFER not added during setDefaults(), RTTNode will attach a default buffer. The default color buffer has an internal format of GL_RGB. /// The default depth buffer has internal format GL_DEPTH_COMPONENT24, source format GL_DEPTH_COMPONENT, and source type GL_UNSIGNED_INT. Default wrap is CLAMP_TO_EDGE and filter LINEAR. class RTTNode : public osg::Node { public: enum class StereoAwareness { Unaware, //! RTT does not vary by view. A single RTT context is created Aware, //! RTT varies by view. One RTT context per view is created. Textures are automatically created as arrays if multiview is enabled. Unaware_MultiViewShaders, //! RTT does not vary by view, but renders with multiview shaders and needs to create texture arrays if multiview is enabled. }; RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness); ~RTTNode(); osg::Texture* getColorTexture(osgUtil::CullVisitor* cv); osg::Texture* getDepthTexture(osgUtil::CullVisitor* cv); osg::Camera* getCamera(osgUtil::CullVisitor* cv); /// Set default settings - optionally override in derived classes virtual void setDefaults(osg::Camera* camera) {}; /// Apply state - to override in derived classes /// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame. virtual void apply(osg::Camera* camera) {}; /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() virtual void applyLeft(osg::Camera* camera) {} /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() virtual void applyRight(osg::Camera* camera) {} void cull(osgUtil::CullVisitor* cv); uint32_t width() const { return mTextureWidth; } uint32_t height() const { return mTextureHeight; } uint32_t samples() const { return mSamples; } bool generatesMipmaps() const { return mGenerateMipmaps; } void setColorBufferInternalFormat(GLint internalFormat); void setDepthBufferInternalFormat(GLint internalFormat); protected: bool shouldDoPerViewMapping(); bool shouldDoTextureArray(); bool shouldDoTextureView(); osg::Texture2DArray* createTextureArray(GLint internalFormat); osg::Texture2D* createTexture(GLint internalFormat); private: friend class CreateTextureViewsCallback; struct ViewDependentData { osg::ref_ptr mCamera; osg::ref_ptr mColorTexture; osg::ref_ptr mDepthTexture; unsigned int mFrameNumber = 0; }; ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); typedef std::map< osgUtil::CullVisitor*, std::shared_ptr > ViewDependentDataMap; ViewDependentDataMap mViewDependentDataMap; uint32_t mTextureWidth; uint32_t mTextureHeight; uint32_t mSamples; bool mGenerateMipmaps; GLint mColorBufferInternalFormat; GLint mDepthBufferInternalFormat; int mRenderOrderNum; StereoAwareness mStereoAwareness; }; } #endif openmw-openmw-0.48.0/components/sceneutil/screencapture.cpp000066400000000000000000000130361445372753700241660ustar00rootroot00000000000000#include "screencapture.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { class ScreenCaptureWorkItem : public SceneUtil::WorkItem { public: ScreenCaptureWorkItem(const osg::ref_ptr& impl, const osg::Image& image, unsigned int contextId) : mImpl(impl), mImage(new osg::Image(image)), mContextId(contextId) { assert(mImpl != nullptr); } void doWork() override { if (mAborted) return; try { (*mImpl)(*mImage, mContextId); } catch (const std::exception& e) { Log(Debug::Error) << "ScreenCaptureWorkItem exception: " << e.what(); } } void abort() override { mAborted = true; } private: const osg::ref_ptr mImpl; const osg::ref_ptr mImage; const unsigned int mContextId; std::atomic_bool mAborted {false}; }; } namespace SceneUtil { std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, const osg::Image& image) { // Count screenshots. int shotCount = 0; // Find the first unused filename with a do-while std::ostringstream stream; std::string lastFileName; std::string lastFilePath; do { // Reset the stream stream.str(""); stream.clear(); stream << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << screenshotFormat; lastFileName = stream.str(); lastFilePath = screenshotPath + "/" + lastFileName; } while (boost::filesystem::exists(lastFilePath)); boost::filesystem::ofstream outStream; outStream.open(boost::filesystem::path(std::move(lastFilePath)), std::ios::binary); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(screenshotFormat); if (!readerwriter) { Log(Debug::Error) << "Error: Can't write screenshot, no '" << screenshotFormat << "' readerwriter found"; return std::string(); } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); return std::string(); } return lastFileName; } WriteScreenshotToFileOperation::WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat, std::function callback) : mScreenshotPath(screenshotPath) , mScreenshotFormat(screenshotFormat) , mCallback(callback) { } void WriteScreenshotToFileOperation::operator()(const osg::Image& image, const unsigned int /*context_id*/) { std::string fileName; try { fileName = writeScreenshotToFile(mScreenshotPath, mScreenshotFormat, image); } catch (const std::exception& e) { Log(Debug::Error) << "Failed to write screenshot to file with path=\"" << mScreenshotPath << "\", format=\"" << mScreenshotFormat << "\": " << e.what(); } if (fileName.empty()) mCallback("Failed to save screenshot"); else mCallback(fileName + " has been saved"); } AsyncScreenCaptureOperation::AsyncScreenCaptureOperation(osg::ref_ptr queue, osg::ref_ptr impl) : mQueue(std::move(queue)), mImpl(std::move(impl)) { assert(mQueue != nullptr); assert(mImpl != nullptr); } AsyncScreenCaptureOperation::~AsyncScreenCaptureOperation() { stop(); } void AsyncScreenCaptureOperation::stop() { for (const osg::ref_ptr& item : *mWorkItems.lockConst()) item->abort(); for (const osg::ref_ptr& item : *mWorkItems.lockConst()) item->waitTillDone(); } void AsyncScreenCaptureOperation::operator()(const osg::Image& image, const unsigned int context_id) { osg::ref_ptr item(new ScreenCaptureWorkItem(mImpl, image, context_id)); mQueue->addWorkItem(item); const auto isDone = [] (const osg::ref_ptr& v) { return v->isDone(); }; const auto workItems = mWorkItems.lock(); workItems->erase(std::remove_if(workItems->begin(), workItems->end(), isDone), workItems->end()); workItems->emplace_back(std::move(item)); } } openmw-openmw-0.48.0/components/sceneutil/screencapture.hpp000066400000000000000000000035311445372753700241720ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H #define OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H #include #include #include #include #include namespace osg { class Image; } namespace SceneUtil { class WorkQueue; class WorkItem; std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, const osg::Image& image); class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation { public: WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat, std::function callback); void operator()(const osg::Image& image, const unsigned int context_id) override; private: const std::string mScreenshotPath; const std::string mScreenshotFormat; const std::function mCallback; }; class AsyncScreenCaptureOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation { public: AsyncScreenCaptureOperation(osg::ref_ptr queue, osg::ref_ptr impl); ~AsyncScreenCaptureOperation(); void stop(); void operator()(const osg::Image& image, const unsigned int context_id) override; private: const osg::ref_ptr mQueue; const osg::ref_ptr mImpl; Misc::ScopeGuarded>> mWorkItems; }; } #endif openmw-openmw-0.48.0/components/sceneutil/serialize.cpp000066400000000000000000000157321445372753700233170ustar00rootroot00000000000000#include "serialize.hpp" #include #include #include #include #include #include #include #include namespace SceneUtil { template static osg::Object* createInstanceFunc() { return new Cls; } class PositionAttitudeTransformSerializer : public osgDB::ObjectWrapper { public: PositionAttitudeTransformSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::PositionAttitudeTransform", "osg::Object osg::Node osg::Group osg::Transform SceneUtil::PositionAttitudeTransform") { addSerializer( new osgDB::PropByRefSerializer< SceneUtil::PositionAttitudeTransform, osg::Vec3f >( "position", osg::Vec3f(), &SceneUtil::PositionAttitudeTransform::getPosition, &SceneUtil::PositionAttitudeTransform::setPosition), osgDB::BaseSerializer::RW_VEC3F ); addSerializer( new osgDB::PropByRefSerializer< SceneUtil::PositionAttitudeTransform, osg::Quat >( "attitude", osg::Quat(), &SceneUtil::PositionAttitudeTransform::getAttitude, &SceneUtil::PositionAttitudeTransform::setAttitude), osgDB::BaseSerializer::RW_QUAT ); addSerializer( new osgDB::PropByRefSerializer< SceneUtil::PositionAttitudeTransform, osg::Vec3f >( "scale", osg::Vec3f(), &SceneUtil::PositionAttitudeTransform::getScale, &SceneUtil::PositionAttitudeTransform::setScale), osgDB::BaseSerializer::RW_VEC3F ); } }; class SkeletonSerializer : public osgDB::ObjectWrapper { public: SkeletonSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::Skeleton", "osg::Object osg::Node osg::Group SceneUtil::Skeleton") { } }; class RigGeometrySerializer : public osgDB::ObjectWrapper { public: RigGeometrySerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry") { } }; class RigGeometryHolderSerializer : public osgDB::ObjectWrapper { public: RigGeometryHolderSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometryHolder", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometryHolder") { } }; class OsgaRigGeometrySerializer : public osgDB::ObjectWrapper { public: OsgaRigGeometrySerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::OsgaRigGeometry", "osg::Object osg::Node osg::Geometry osgAnimation::RigGeometry SceneUtil::OsgaRigGeometry") { } }; class MorphGeometrySerializer : public osgDB::ObjectWrapper { public: MorphGeometrySerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry") { } }; class LightManagerSerializer : public osgDB::ObjectWrapper { public: LightManagerSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::LightManager", "osg::Object osg::Node osg::Group SceneUtil::LightManager") { } }; class CameraRelativeTransformSerializer : public osgDB::ObjectWrapper { public: CameraRelativeTransformSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "MWRender::CameraRelativeTransform", "osg::Object osg::Node osg::Group MWRender::CameraRelativeTransform") { } }; class MatrixTransformSerializer : public osgDB::ObjectWrapper { public: MatrixTransformSerializer() : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Group osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") { } }; osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) { return new osgDB::ObjectWrapper(createInstanceFunc, classname, "osg::Object"); } class GeometrySerializer : public osgDB::ObjectWrapper { public: GeometrySerializer() : osgDB::ObjectWrapper(createInstanceFunc, "osg::Geometry", "osg::Object osg::Drawable osg::Geometry") { } }; void registerSerializers() { static bool done = false; if (!done) { osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager(); mgr->addWrapper(new PositionAttitudeTransformSerializer); mgr->addWrapper(new SkeletonSerializer); mgr->addWrapper(new RigGeometrySerializer); mgr->addWrapper(new RigGeometryHolderSerializer); mgr->addWrapper(new OsgaRigGeometrySerializer); mgr->addWrapper(new MorphGeometrySerializer); mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); mgr->addWrapper(new MatrixTransformSerializer); // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read. mgr->removeWrapper(mgr->findWrapper("osg::Geometry")); mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; for (size_t i=0; iaddWrapper(makeDummySerializer(ignore[i])); } done = true; } } } openmw-openmw-0.48.0/components/sceneutil/serialize.hpp000066400000000000000000000003761445372753700233220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_SERIALIZE_H #define OPENMW_COMPONENTS_SCENEUTIL_SERIALIZE_H namespace SceneUtil { /// Register osg node serializers for certain SceneUtil classes if not already done so void registerSerializers(); } #endif openmw-openmw-0.48.0/components/sceneutil/shadow.cpp000066400000000000000000000213241445372753700226070ustar00rootroot00000000000000#include "shadow.hpp" #include #include #include #include #include #include "mwshadowtechnique.hpp" namespace SceneUtil { using namespace osgShadow; void ShadowManager::setupShadowSettings() { mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); if (!mEnableShadows) { mShadowTechnique->disableShadows(); return; } mShadowTechnique->enableShadows(); mShadowSettings->setLightNum(0); mShadowSettings->setReceivesShadowTraversalMask(~0u); const int numberOfShadowMapsPerLight = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight); const float maximumShadowMapDistance = Settings::Manager::getFloat("maximum shadow map distance", "Shadows"); if (maximumShadowMapDistance > 0) { const float shadowFadeStart = std::clamp(Settings::Manager::getFloat("shadow fade start", "Shadows"), 0.f, 1.f); mShadowSettings->setMaximumShadowMapDistance(maximumShadowMapDistance); mShadowTechnique->setShadowFadeStart(maximumShadowMapDistance * shadowFadeStart); } mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); std::string computeSceneBounds = Settings::Manager::getString("compute scene bounds", "Shadows"); if (Misc::StringUtils::lowerCase(computeSceneBounds) == "primitives") mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); else if (Misc::StringUtils::lowerCase(computeSceneBounds) == "bounds") mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); mShadowTechnique->setSplitPointUniformLogarithmicRatio(Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), Settings::Manager::getFloat("polygon offset units", "Shadows")); if (Settings::Manager::getBool("use front face culling", "Shadows")) mShadowTechnique->enableFrontFaceCulling(); else mShadowTechnique->disableFrontFaceCulling(); if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); else mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); if (Settings::Manager::getBool("enable debug hud", "Shadows")) mShadowTechnique->enableDebugHUD(); else mShadowTechnique->disableDebugHUD(); } void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) { if (!Settings::Manager::getBool("enable shadows", "Shadows")) return; const int numberOfShadowMapsPerLight = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; osg::ref_ptr fakeShadowMapImage = new osg::Image(); fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); osg::ref_ptr fakeShadowMapTexture = new osg::Texture2D(fakeShadowMapImage); fakeShadowMapTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) { stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); stateset->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); } } ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), mShadowTechnique(new MWShadowTechnique), mOutdoorShadowCastingMask(outdoorShadowCastingMask), mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); Stereo::Manager::instance().setShadowTechnique(mShadowTechnique); mShadowedScene->addChild(sceneRoot); rootNode->addChild(mShadowedScene); mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); setupShadowSettings(); mShadowTechnique->setupCastingShader(shaderManager); mShadowTechnique->setWorldMask(worldMask); enableOutdoorMode(); } ShadowManager::~ShadowManager() { Stereo::Manager::instance().setShadowTechnique(nullptr); } Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() { if (!mEnableShadows) return getShadowsDisabledDefines(); Shader::ShaderManager::DefineMap definesWithShadows; definesWithShadows["shadows_enabled"] = "1"; for (unsigned int i = 0; i < mShadowSettings->getNumShadowMapsPerLight(); ++i) definesWithShadows["shadow_texture_unit_list"] += std::to_string(i) + ","; // remove extra comma definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr(0, definesWithShadows["shadow_texture_unit_list"].length() - 1); definesWithShadows["shadowMapsOverlap"] = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; definesWithShadows["useShadowDebugOverlay"] = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; // switch this to reading settings if it's ever exposed to the user definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; definesWithShadows["disableNormalOffsetShadows"] = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; definesWithShadows["shadowNormalOffset"] = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); definesWithShadows["limitShadowMapDistance"] = Settings::Manager::getFloat("maximum shadow map distance", "Shadows") > 0 ? "1" : "0"; return definesWithShadows; } Shader::ShaderManager::DefineMap ShadowManager::getShadowsDisabledDefines() { Shader::ShaderManager::DefineMap definesWithoutShadows; definesWithoutShadows["shadows_enabled"] = "0"; definesWithoutShadows["shadow_texture_unit_list"] = ""; definesWithoutShadows["shadowMapsOverlap"] = "0"; definesWithoutShadows["useShadowDebugOverlay"] = "0"; definesWithoutShadows["perspectiveShadowMaps"] = "0"; definesWithoutShadows["disableNormalOffsetShadows"] = "0"; definesWithoutShadows["shadowNormalOffset"] = "0.0"; definesWithoutShadows["limitShadowMapDistance"] = "0"; return definesWithoutShadows; } void ShadowManager::enableIndoorMode() { if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); else mShadowTechnique->disableShadows(true); } void ShadowManager::enableOutdoorMode() { if (mEnableShadows) mShadowTechnique->enableShadows(); mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); } } openmw-openmw-0.48.0/components/sceneutil/shadow.hpp000066400000000000000000000024441445372753700226160ustar00rootroot00000000000000#ifndef COMPONENTS_SCENEUTIL_SHADOW_H #define COMPONENTS_SCENEUTIL_SHADOW_H #include namespace osg { class StateSet; class Group; } namespace osgShadow { class ShadowSettings; class ShadowedScene; } namespace SceneUtil { class MWShadowTechnique; class ShadowManager { public: static void disableShadowsForStateSet(osg::ref_ptr stateSet); static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, Shader::ShaderManager &shaderManager); ~ShadowManager(); void setupShadowSettings(); Shader::ShaderManager::DefineMap getShadowDefines(); void enableIndoorMode(); void enableOutdoorMode(); protected: bool mEnableShadows; osg::ref_ptr mShadowedScene; osg::ref_ptr mShadowSettings; osg::ref_ptr mShadowTechnique; unsigned int mOutdoorShadowCastingMask; unsigned int mIndoorShadowCastingMask; }; } #endif //COMPONENTS_SCENEUTIL_SHADOW_H openmw-openmw-0.48.0/components/sceneutil/shadowsbin.cpp000066400000000000000000000203371445372753700234660ustar00rootroot00000000000000#include "shadowsbin.hpp" #include #include #include #include #include #include using namespace osgUtil; namespace { template inline void accumulateState(T& currentValue, T newValue, bool& isOverride, unsigned int overrideFlags) { if (isOverride && !(overrideFlags & osg::StateAttribute::PROTECTED)) return; if (overrideFlags & osg::StateAttribute::OVERRIDE) isOverride = true; currentValue = newValue; } inline void accumulateModeState(const osg::StateSet* ss, bool& currentValue, bool& isOverride, int mode) { const osg::StateSet::ModeList& l = ss->getModeList(); osg::StateSet::ModeList::const_iterator mf = l.find(mode); if (mf == l.end()) return; unsigned int flags = mf->second; bool newValue = flags & osg::StateAttribute::ON; accumulateState(currentValue, newValue, isOverride, flags); } inline bool materialNeedShadows(osg::Material* m) { // I'm pretty sure this needs to check the colour mode - vertex colours might override this value. return m->getDiffuse(osg::Material::FRONT).a() > 0.5; } } namespace SceneUtil { ShadowsBin::ShadowsBin(const CastingPrograms& castingPrograms) { mNoTestStateSet = new osg::StateSet; mNoTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); mNoTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", false)); mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); mShaderAlphaTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < castingPrograms.size(); ++i) { mAlphaFuncShaders[i] = new osg::StateSet; mAlphaFuncShaders[i]->setAttribute(castingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); } } StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache, bool cullFaceOverridden) { std::vector return_path; State state; StateGraph* sg_new = sg; do { if (uninterestingCache.find(sg_new) != uninterestingCache.end()) break; return_path.push_back(sg_new); sg_new = sg_new->_parent; } while (sg_new && sg_new != root); for(auto itr=return_path.rbegin(); itr!=return_path.rend(); ++itr) { const osg::StateSet* ss = (*itr)->getStateSet(); if (!ss) continue; accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND); const osg::StateSet::AttributeList& attributes = ss->getAttributeList(); osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0)); if (found != attributes.end()) { const osg::StateSet::RefAttributePair& rap = found->second; accumulateState(state.mMaterial, static_cast(rap.first.get()), state.mMaterialOverride, rap.second); if (state.mMaterial && !materialNeedShadows(state.mMaterial)) state.mMaterial = nullptr; } found = attributes.find(std::make_pair(osg::StateAttribute::ALPHAFUNC, 0)); if (found != attributes.end()) { // As force shaders is on, we know this is really a RemovedAlphaFunc const osg::StateSet::RefAttributePair& rap = found->second; accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); } if (!cullFaceOverridden) { // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it unless GL_CULL_FACE is off or we flip face culling. found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); if (found != attributes.end()) state.mImportantState = true; } if ((*itr) != sg && !state.interesting()) uninterestingCache.insert(*itr); } if (!state.needShadows()) return nullptr; if (!state.needTexture() && !state.mImportantState) { for (RenderLeaf* leaf : sg->_leaves) { leaf->_parent = root; root->_leaves.push_back(leaf); } return nullptr; } if (state.mAlphaBlend) { sg_new = sg->find_or_insert(mShaderAlphaTestStateSet); sg_new->_leaves = std::move(sg->_leaves); for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; sg = sg_new; } // GL_ALWAYS is set by default by mwshadowtechnique if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) { sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); sg_new->_leaves = std::move(sg->_leaves); for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; sg = sg_new; } return sg; } inline bool ShadowsBin::State::needTexture() const { return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS); } bool ShadowsBin::State::needShadows() const { if (mAlphaFunc && mAlphaFunc->getFunction() == GL_NEVER) return false; // other alpha func + material combinations might be skippable if (mAlphaBlend && mMaterial) return materialNeedShadows(mMaterial); return true; } void ShadowsBin::sortImplementation() { // The cull visitor contains a stategraph. // When a stateset is pushed, it's added/found as a child of the current stategraph node, then that node becomes the new current stategraph node. // When a drawable is added, the current stategraph node is added to the current renderbin (if it's not there already) and the drawable is added as a renderleaf to the stategraph // This means our list only contains stategraph nodes with directly-attached renderleaves, but they might have parents with more state set that needs to be considered. if (!_stateGraphList.size()) return; StateGraph* root = _stateGraphList[0]; while (root->_parent) { root = root->_parent; const osg::StateSet* ss = root->getStateSet(); if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertarget's sg just in case break; if (!root->_parent) return; } StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get()); // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state bool cullFaceOverridden = false; while (root->_parent) { root = root->_parent; if (!root->getStateSet()) continue; unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE); if (cullFaceFlags & osg::StateAttribute::OVERRIDE && !(cullFaceFlags & osg::StateAttribute::ON)) { cullFaceOverridden = true; break; } } noTestRoot->_leaves.reserve(_stateGraphList.size()); StateGraphList newList; std::unordered_set uninterestingCache; for (StateGraph* graph : _stateGraphList) { // Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList. // Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList. // Graphs containing other leaves need to be in newList. StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache, cullFaceOverridden); if (graphToAdd) newList.push_back(graphToAdd); } if (!noTestRoot->_leaves.empty()) newList.push_back(noTestRoot); _stateGraphList = std::move(newList); } } openmw-openmw-0.48.0/components/sceneutil/shadowsbin.hpp000066400000000000000000000046061445372753700234740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #include #include #include namespace osg { class Material; class AlphaFunc; } namespace SceneUtil { /// renderbin which culls redundant state for shadow map rendering class ShadowsBin : public osgUtil::RenderBin { public: template using Array = std::array; using CastingPrograms = Array>; META_Object(SceneUtil, ShadowsBin) ShadowsBin(const CastingPrograms& castingPrograms); ShadowsBin(const ShadowsBin& rhs, const osg::CopyOp& copyop) : osgUtil::RenderBin(rhs, copyop) , mNoTestStateSet(rhs.mNoTestStateSet) , mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet) , mAlphaFuncShaders(rhs.mAlphaFuncShaders) {} void sortImplementation() override; struct State { State() : mAlphaBlend(false) , mAlphaBlendOverride(false) , mAlphaFunc(nullptr) , mAlphaFuncOverride(false) , mMaterial(nullptr) , mMaterialOverride(false) , mImportantState(false) {} bool mAlphaBlend; bool mAlphaBlendOverride; osg::AlphaFunc* mAlphaFunc; bool mAlphaFuncOverride; osg::Material* mMaterial; bool mMaterialOverride; bool mImportantState; bool needTexture() const; bool needShadows() const; // A state is interesting if there's anything about it that might affect whether we can optimise child state bool interesting() const { return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaFuncOverride || mMaterialOverride || mImportantState; } }; osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); private: ShadowsBin() {} osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; Array> mAlphaFuncShaders; }; } #endif openmw-openmw-0.48.0/components/sceneutil/skeleton.cpp000066400000000000000000000077701445372753700231570ustar00rootroot00000000000000#include "skeleton.hpp" #include #include #include #include namespace SceneUtil { class InitBoneCacheVisitor : public osg::NodeVisitor { public: typedef std::vector TransformPath; InitBoneCacheVisitor(std::unordered_map& cache) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCache(cache) { } void apply(osg::MatrixTransform &node) override { mPath.push_back(&node); mCache[Misc::StringUtils::lowerCase(node.getName())] = mPath; traverse(node); mPath.pop_back(); } private: TransformPath mPath; std::unordered_map& mCache; }; Skeleton::Skeleton() : mBoneCacheInit(false) , mNeedToUpdateBoneMatrices(true) , mActive(Active) , mLastFrameNumber(0) , mLastCullFrameNumber(0) { } Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mBoneCacheInit(false) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) , mLastCullFrameNumber(0) { } Bone* Skeleton::getBone(const std::string &name) { if (!mBoneCacheInit) { InitBoneCacheVisitor visitor(mBoneCache); accept(visitor); mBoneCacheInit = true; } BoneCache::iterator found = mBoneCache.find(Misc::StringUtils::lowerCase(name)); if (found == mBoneCache.end()) return nullptr; // find or insert in the bone hierarchy if (!mRootBone.get()) { mRootBone = std::make_unique(); } Bone* bone = mRootBone.get(); for (osg::MatrixTransform* matrixTransform : found->second) { const auto it = std::find_if(bone->mChildren.begin(), bone->mChildren.end(), [&] (const auto& v) { return v->mNode == matrixTransform; }); if (it == bone->mChildren.end()) { bone = bone->mChildren.emplace_back(std::make_unique()).get(); mNeedToUpdateBoneMatrices = true; } else bone = it->get(); bone->mNode = matrixTransform; } return bone; } void Skeleton::updateBoneMatrices(unsigned int traversalNumber) { if (traversalNumber != mLastFrameNumber) mNeedToUpdateBoneMatrices = true; mLastFrameNumber = traversalNumber; if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) { for (const auto& child : mRootBone->mChildren) child->update(nullptr); } mNeedToUpdateBoneMatrices = false; } } void Skeleton::setActive(ActiveType active) { mActive = active; } bool Skeleton::getActive() const { return mActive != Inactive; } void Skeleton::markDirty() { mLastFrameNumber = 0; mBoneCache.clear(); mBoneCacheInit = false; } void Skeleton::traverse(osg::NodeVisitor& nv) { if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { if (mActive == Inactive && mLastFrameNumber != 0) return; if (mActive == SemiActive && mLastFrameNumber != 0 && mLastCullFrameNumber+3 <= nv.getTraversalNumber()) return; } else if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) mLastCullFrameNumber = nv.getTraversalNumber(); osg::Group::traverse(nv); } void Skeleton::childInserted(unsigned int) { markDirty(); } void Skeleton::childRemoved(unsigned int, unsigned int) { markDirty(); } Bone::Bone() : mNode(nullptr) { } void Bone::update(const osg::Matrixf* parentMatrixInSkeletonSpace) { if (!mNode) { Log(Debug::Error) << "Error: Bone without node"; return; } if (parentMatrixInSkeletonSpace) mMatrixInSkeletonSpace = mNode->getMatrix() * (*parentMatrixInSkeletonSpace); else mMatrixInSkeletonSpace = mNode->getMatrix(); for (const auto& child : mChildren) child->update(&mMatrixInSkeletonSpace); } } openmw-openmw-0.48.0/components/sceneutil/skeleton.hpp000066400000000000000000000051241445372753700231530ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_NIFOSG_SKELETON_H #define OPENMW_COMPONENTS_NIFOSG_SKELETON_H #include #include #include namespace SceneUtil { /// @brief Defines a Bone hierarchy, used for updating of skeleton-space bone matrices. /// @note To prevent unnecessary updates, only bones that are used for skinning will be added to this hierarchy. class Bone { public: Bone(); osg::Matrixf mMatrixInSkeletonSpace; osg::MatrixTransform* mNode; std::vector> mChildren; /// Update the skeleton-space matrix of this bone and all its children. void update(const osg::Matrixf* parentMatrixInSkeletonSpace); }; /// @brief Handles the bone matrices for any number of child RigGeometries. /// @par Bones should be created as osg::MatrixTransform children of the skeleton. /// To be a referenced by a RigGeometry, a bone needs to have a unique name. class Skeleton : public osg::Group { public: Skeleton(); Skeleton(const Skeleton& copy, const osg::CopyOp& copyop); META_Node(SceneUtil, Skeleton) /// Retrieve a bone by name. Bone* getBone(const std::string& name); /// Request an update of bone matrices. May be a no-op if already updated in this frame. void updateBoneMatrices(unsigned int traversalNumber); enum ActiveType { Inactive=0, SemiActive, /// Like Active, but don't bother with Update (including new bounding box) if we're off-screen Active }; /// Set the skinning active flag. Inactive skeletons will not have their child rigs updated. /// You should set this flag to false if you know that bones are not currently moving. void setActive(ActiveType active); bool getActive() const; void traverse(osg::NodeVisitor& nv) override; void markDirty(); void childInserted(unsigned int) override; void childRemoved(unsigned int, unsigned int) override; private: // The root bone is not a "real" bone, it has no corresponding node in the scene graph. // As far as the scene graph goes we support multiple root bones. std::unique_ptr mRootBone; typedef std::unordered_map > BoneCache; BoneCache mBoneCache; bool mBoneCacheInit; bool mNeedToUpdateBoneMatrices; ActiveType mActive; unsigned int mLastFrameNumber; unsigned int mLastCullFrameNumber; }; } #endif openmw-openmw-0.48.0/components/sceneutil/statesetupdater.cpp000066400000000000000000000072471445372753700245530ustar00rootroot00000000000000#include "statesetupdater.hpp" #include #include #include #include namespace SceneUtil { void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) { bool isCullVisitor = nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (isCullVisitor) return applyCull(node, static_cast(nv)); else return applyUpdate(node, nv); } void StateSetUpdater::applyUpdate(osg::Node* node, osg::NodeVisitor* nv) { if (!mStateSetsUpdate[0]) { for (int i = 0; i < 2; ++i) { mStateSetsUpdate[i] = new osg::StateSet(*node->getOrCreateStateSet(), osg::CopyOp::SHALLOW_COPY); // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first in setDefaults setDefaults(mStateSetsUpdate[i]); } } osg::ref_ptr stateset = mStateSetsUpdate[nv->getTraversalNumber() % 2]; apply(stateset, nv); node->setStateSet(stateset); traverse(node, nv); } void StateSetUpdater::applyCull(osg::Node* node, osgUtil::CullVisitor* cv) { auto stateset = getCvDependentStateset(cv); apply(stateset, cv); auto* sm = &Stereo::Manager::instance(); if (sm != nullptr) { if (sm->getEye(cv) == Stereo::Eye::Left) applyLeft(stateset, cv); if (sm->getEye(cv) == Stereo::Eye::Right) applyRight(stateset, cv); } cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); } osg::StateSet* StateSetUpdater::getCvDependentStateset(osgUtil::CullVisitor* cv) { auto it = mStateSetsCull.find(cv); if (it == mStateSetsCull.end()) { osg::ref_ptr stateset = new osg::StateSet; mStateSetsCull.emplace(cv, stateset); setDefaults(stateset); return stateset; } return it->second; } void StateSetUpdater::reset() { mStateSetsUpdate[0] = nullptr; mStateSetsUpdate[1] = nullptr; mStateSetsCull.clear(); } StateSetUpdater::StateSetUpdater() { } StateSetUpdater::StateSetUpdater(const StateSetUpdater ©, const osg::CopyOp ©op) : SceneUtil::NodeCallback(copy, copyop) { } // ---------------------------------------------------------------------------------- void CompositeStateSetUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { for (unsigned int i=0; iapply(stateset, nv); } void CompositeStateSetUpdater::setDefaults(osg::StateSet *stateset) { for (unsigned int i=0; isetDefaults(stateset); } CompositeStateSetUpdater::CompositeStateSetUpdater() { } CompositeStateSetUpdater::CompositeStateSetUpdater(const CompositeStateSetUpdater ©, const osg::CopyOp ©op) : StateSetUpdater(copy, copyop) { for (unsigned int i=0; i #include #include namespace osgUtil { class CullVisitor; } namespace SceneUtil { /// @brief Implements efficient per-frame updating of StateSets. /// @par With a naive update there would be race conditions when the OSG draw thread of the last frame /// queues up a StateSet that we want to modify for the next frame. To solve this we could set the StateSet to /// DYNAMIC data variance but that would undo all the benefits of the threading model - having the cull and draw /// traversals run in parallel can yield up to 200% framerates. /// @par Must be set as UpdateCallback or CullCallback on a Node. If set as a CullCallback, the StateSetUpdater operates on an empty StateSet, /// otherwise it operates on a clone of the node's existing StateSet. /// @par If set as an UpdateCallback, race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. /// @par If set as a CullCallback, race conditions are prevented by mapping statesets to cull visitors - OSG has two cull visitors that take turns, /// allowing the updater to automatically scale for the number of views. /// @note When used as a CullCallback, StateSetUpdater will have no effect on leaf nodes such as osg::Geometry and must be used on branch nodes only. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetUpdaters on the same Node as they will conflict - instead use the CompositeStateSetUpdater. class StateSetUpdater : public SceneUtil::NodeCallback { public: StateSetUpdater(); StateSetUpdater(const StateSetUpdater& copy, const osg::CopyOp& copyop); void operator()(osg::Node* node, osg::NodeVisitor* nv); /// Apply state - to override in derived classes /// @note Due to the double buffering approach you *have* to apply all state /// even if it has not changed since the last frame. virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) {} /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback virtual void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback virtual void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} /// Reset mStateSets, forcing a setDefaults() on the next frame. Can be used to change the defaults if needed. void reset(); private: void applyCull(osg::Node* node, osgUtil::CullVisitor* cv); void applyUpdate(osg::Node* node, osg::NodeVisitor* nv); osg::StateSet* getCvDependentStateset(osgUtil::CullVisitor* cv); std::array, 2> mStateSetsUpdate; std::map> mStateSetsCull; }; /// @brief A variant of the StateSetController that can be made up of multiple controllers all controlling the same target. class CompositeStateSetUpdater : public StateSetUpdater { public: CompositeStateSetUpdater(); CompositeStateSetUpdater(const CompositeStateSetUpdater& copy, const osg::CopyOp& copyop); META_Object(SceneUtil, CompositeStateSetUpdater) unsigned int getNumControllers(); StateSetUpdater* getController(int i); void addController(StateSetUpdater* ctrl); void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; protected: void setDefaults(osg::StateSet *stateset) override; std::vector > mCtrls; }; } #endif openmw-openmw-0.48.0/components/sceneutil/textkeymap.hpp000066400000000000000000000042331445372753700235220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP #define OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP #include #include #include #include #include namespace SceneUtil { class TextKeyMap { public: using ConstIterator = std::multimap::const_iterator; auto begin() const noexcept { return mTextKeyByTime.begin(); } auto end() const noexcept { return mTextKeyByTime.end(); } auto rbegin() const noexcept { return mTextKeyByTime.rbegin(); } auto rend() const noexcept { return mTextKeyByTime.rend(); } auto lowerBound(float time) const { return mTextKeyByTime.lower_bound(time); } auto upperBound(float time) const { return mTextKeyByTime.upper_bound(time); } void emplace(float time, std::string&& textKey) { const auto separator = textKey.find(": "); if (separator != std::string::npos) mGroups.emplace(textKey.substr(0, separator)); mTextKeyByTime.emplace(time, std::move(textKey)); } bool empty() const noexcept { return mTextKeyByTime.empty(); } auto findGroupStart(std::string_view groupName) const { return std::find_if(mTextKeyByTime.begin(), mTextKeyByTime.end(), IsGroupStart{groupName}); } bool hasGroupStart(std::string_view groupName) const { return mGroups.count(groupName) > 0; } private: struct IsGroupStart { std::string_view mGroupName; bool operator ()(const std::multimap::value_type& value) const { return value.second.compare(0, mGroupName.size(), mGroupName) == 0 && value.second.compare(mGroupName.size(), 2, ": ") == 0; } }; std::set> mGroups; std::multimap mTextKeyByTime; }; } #endif openmw-openmw-0.48.0/components/sceneutil/unrefqueue.cpp000066400000000000000000000014521445372753700235060ustar00rootroot00000000000000#include "unrefqueue.hpp" namespace SceneUtil { namespace { struct ClearVector final : SceneUtil::WorkItem { std::vector> mObjects; explicit ClearVector(std::vector>&& objects) : mObjects(std::move(objects)) {} void doWork() override { mObjects.clear(); } }; } void UnrefQueue::flush(SceneUtil::WorkQueue& workQueue) { if (mObjects.empty()) return; // Move only objects to keep allocated storage in mObjects workQueue.addWorkItem(new ClearVector(std::vector>( std::move_iterator(mObjects.begin()), std::move_iterator(mObjects.end())))); mObjects.clear(); } } openmw-openmw-0.48.0/components/sceneutil/unrefqueue.hpp000066400000000000000000000021521445372753700235110ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H #define OPENMW_COMPONENTS_UNREFQUEUE_H #include "workqueue.hpp" #include #include #include namespace SceneUtil { class WorkQueue; /// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario /// would be the main thread pushing objects that are no longer needed, and the background thread deleting them. class UnrefQueue { public: /// Adds an object to the list of objects to be unreferenced. Call from the main thread. void push(osg::ref_ptr&& obj) { mObjects.push_back(std::move(obj)); } void push(const osg::ref_ptr& obj) { mObjects.push_back(obj); } /// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, /// thus unreferencing them. Call from the main thread. void flush(SceneUtil::WorkQueue& workQueue); std::size_t getSize() const { return mObjects.size(); } private: std::vector> mObjects; }; } #endif openmw-openmw-0.48.0/components/sceneutil/util.cpp000066400000000000000000000213271445372753700223020ustar00rootroot00000000000000#include "util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace SceneUtil { class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor { public: FindLowestUnusedTexUnitVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mLowestUnusedTexUnit(0) { } void apply(osg::Node& node) override { if (osg::StateSet* stateset = node.getStateSet()) mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); traverse(node); } int mLowestUnusedTexUnit; }; GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) : mTexUnit(texUnit) , mColor(color) , mOriginalColor(color) , mTextures(textures) , mNode(node) , mDuration(duration) , mOriginalDuration(duration) , mStartingTime(0) , mResourceSystem(resourcesystem) , mColorChanged(false) , mDone(false) { } void GlowUpdater::setDefaults(osg::StateSet *stateset) { if (mDone) removeTexture(stateset); else { stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); osg::TexGen* texGen = new osg::TexGen; texGen->setMode(osg::TexGen::SPHERE_MAP); stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setConstantColor(mColor); texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("envMapColor", mColor)); } } void GlowUpdater::removeTexture(osg::StateSet* stateset) { stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); stateset->removeUniform("envMapColor"); osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); while (list.size() && list.rbegin()->empty()) list.pop_back(); } void GlowUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { if (mColorChanged){ this->reset(); setDefaults(stateset); mColorChanged = false; } if (mDone) return; // Set the starting time to measure glow duration from if this is a temporary glow if ((mDuration >= 0) && mStartingTime == 0) mStartingTime = nv->getFrameStamp()->getSimulationTime(); float time = nv->getFrameStamp()->getSimulationTime(); int index = (int)(time*16) % mTextures.size(); stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration { if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation { removeTexture(stateset); this->reset(); mDone = true; // normally done in StateSetUpdater::operator(), but needs doing here so the shader visitor sees the right StateSet mNode->setStateSet(stateset); mResourceSystem->getSceneManager()->recreateShaders(mNode); } if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow { mDuration = mOriginalDuration; mStartingTime = 0; mColor = mOriginalColor; this->reset(); setDefaults(stateset); } } } bool GlowUpdater::isPermanentGlowUpdater() { return (mDuration < 0); } bool GlowUpdater::isDone() { return mDone; } void GlowUpdater::setColor(const osg::Vec4f& color) { mColor = color; mColorChanged = true; } void GlowUpdater::setDuration(float duration) { mDuration = duration; } osg::Vec4f colourFromRGB(unsigned int clr) { osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, ((clr >> 8) & 0xFF) / 255.0f, ((clr >> 16) & 0xFF) / 255.0f, 1.f); return colour; } osg::Vec4f colourFromRGBA(unsigned int value) { return osg::Vec4f(makeOsgColorComponent(value, 0), makeOsgColorComponent(value, 8), makeOsgColorComponent(value, 16), makeOsgColorComponent(value, 24)); } float makeOsgColorComponent(unsigned int value, unsigned int shift) { return float((value >> shift) & 0xFFu) / 255.0f; } bool hasUserDescription(const osg::Node* node, const std::string& pattern) { if (node == nullptr) return false; const osg::UserDataContainer* udc = node->getUserDataContainer(); if (udc && udc->getNumDescriptions() > 0) { for (auto& descr : udc->getDescriptions()) { if (descr == pattern) return true; } } return false; } osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, const osg::Vec4f& glowColor, float glowDuration) { std::vector > textures; for (int i=0; i<32; ++i) { std::stringstream stream; stream << "textures/magicitem/caust"; stream << std::setw(2); stream << std::setfill('0'); stream << i; stream << ".dds"; osg::ref_ptr image = resourceSystem->getImageManager()->getImage(stream.str()); osg::ref_ptr tex (new osg::Texture2D(image)); tex->setName("envMap"); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); resourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; node->accept(findLowestUnusedTexUnitVisitor); int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, resourceSystem); node->addUpdateCallback(glowUpdater); // set a texture now so that the ShaderVisitor can find it osg::ref_ptr writableStateSet = nullptr; if (!node->getStateSet()) writableStateSet = node->getOrCreateStateSet(); else { writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); node->setStateSet(writableStateSet); } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); resourceSystem->getSceneManager()->recreateShaders(node); return glowUpdater; } bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration) { unsigned int samples = 0; unsigned int colourSamples = 0; bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; if (addMSAAIntermediateTarget) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. // For some reason, two samples are needed, at least with some drivers. samples = 2; colourSamples = 1; } camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples); return addMSAAIntermediateTarget; } OperationSequence::OperationSequence(bool keep) : Operation("OperationSequence", keep) , mOperationQueue(new osg::OperationQueue()) { } void OperationSequence::operator()(osg::Object* object) { mOperationQueue->runOperations(object); } void OperationSequence::add(osg::Operation* operation) { mOperationQueue->add(operation); } } openmw-openmw-0.48.0/components/sceneutil/util.hpp000066400000000000000000000072321445372753700223060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_UTIL_H #define OPENMW_COMPONENTS_SCENEUTIL_UTIL_H #include #include #include #include #include #include #include "statesetupdater.hpp" namespace SceneUtil { class GlowUpdater : public SceneUtil::StateSetUpdater { public: GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem); void setDefaults(osg::StateSet *stateset) override; void removeTexture(osg::StateSet* stateset); void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; bool isPermanentGlowUpdater(); bool isDone(); void setColor(const osg::Vec4f& color); void setDuration(float duration); private: int mTexUnit; osg::Vec4f mColor; osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes std::vector > mTextures; osg::Node* mNode; float mDuration; float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one float mStartingTime; Resource::ResourceSystem* mResourceSystem; bool mColorChanged; bool mDone; }; // Transform a bounding sphere by a matrix // based off private code in osg::Transform // TODO: patch osg to make public template inline void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphereImpl& bsphere) { VT xdash = bsphere._center; xdash.x() += bsphere._radius; xdash = xdash*matrix; VT ydash = bsphere._center; ydash.y() += bsphere._radius; ydash = ydash*matrix; VT zdash = bsphere._center; zdash.z() += bsphere._radius; zdash = zdash*matrix; bsphere._center = bsphere._center*matrix; xdash -= bsphere._center; typename VT::value_type sqrlen_xdash = xdash.length2(); ydash -= bsphere._center; typename VT::value_type sqrlen_ydash = ydash.length2(); zdash -= bsphere._center; typename VT::value_type sqrlen_zdash = zdash.length2(); bsphere._radius = sqrlen_xdash; if (bsphere._radius addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, const osg::Vec4f& glowColor, float glowDuration=-1); // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); class OperationSequence : public osg::Operation { public: OperationSequence(bool keep); void operator()(osg::Object* object) override; void add(osg::Operation* operation); protected: osg::ref_ptr mOperationQueue; }; } #endif openmw-openmw-0.48.0/components/sceneutil/visitor.cpp000066400000000000000000000105171445372753700230230ustar00rootroot00000000000000#include "visitor.hpp" #include #include #include #include #include #include #include namespace SceneUtil { bool FindByNameVisitor::checkGroup(osg::Group &group) { if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) { mFoundNode = &group; return true; } return false; } void FindByClassVisitor::apply(osg::Node &node) { if (Misc::StringUtils::ciEqual(std::string_view(node.className()), mNameToFind)) mFoundNodes.push_back(&node); traverse(node); } void FindByNameVisitor::apply(osg::Group &group) { if (!mFoundNode && !checkGroup(group)) traverse(group); } void FindByNameVisitor::apply(osg::MatrixTransform &node) { if (!mFoundNode && !checkGroup(node)) traverse(node); } void FindByNameVisitor::apply(osg::Geometry&) { } void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Choose first found node in file if (trans.libraryName() == std::string("osgAnimation")) { std::string nodeName = trans.getName(); // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) std::replace(nodeName.begin(), nodeName.end(), '_', ' '); mMap.emplace(nodeName, &trans); } else mMap.emplace(trans.getName(), &trans); traverse(trans); } void RemoveVisitor::remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) { if (!it->second->removeChild(it->first)) Log(Debug::Error) << "error removing " << it->first->getName(); } } void CleanObjectRootVisitor::apply(osg::Drawable& drw) { applyDrawable(drw); } void CleanObjectRootVisitor::apply(osg::Group& node) { applyNode(node); } void CleanObjectRootVisitor::apply(osg::MatrixTransform& node) { applyNode(node); } void CleanObjectRootVisitor::apply(osg::Node& node) { applyNode(node); } void CleanObjectRootVisitor::applyNode(osg::Node& node) { if (node.getStateSet()) node.setStateSet(nullptr); if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) mToRemove.emplace_back(&node, node.getParent(0)); else traverse(node); } void CleanObjectRootVisitor::applyDrawable(osg::Node& node) { osg::NodePath::iterator parent = getNodePath().end()-2; // We know that the parent is a Group because only Groups can have children. osg::Group* parentGroup = static_cast(*parent); // Try to prune nodes that would be empty after the removal if (parent != getNodePath().begin()) { // This could be extended to remove the parent's parent, and so on if they are empty as well. // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. osg::Group* parentParent = static_cast(*(parent - 1)); if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) { mToRemove.emplace_back(parentGroup, parentParent); return; } } mToRemove.emplace_back(&node, parentGroup); } void RemoveTriBipVisitor::apply(osg::Drawable& drw) { applyImpl(drw); } void RemoveTriBipVisitor::apply(osg::Group& node) { traverse(node); } void RemoveTriBipVisitor::apply(osg::MatrixTransform& node) { traverse(node); } void RemoveTriBipVisitor::applyImpl(osg::Node& node) { const std::string toFind = "tri bip"; if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) { osg::Group* parent = static_cast(*(getNodePath().end()-2)); // Not safe to remove in apply(), since the visitor is still iterating the child list mToRemove.emplace_back(&node, parent); } } } openmw-openmw-0.48.0/components/sceneutil/visitor.hpp000066400000000000000000000060551445372753700230320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #include #include #include #include // Commonly used scene graph visitors namespace SceneUtil { // Find a Group by name, case-insensitive // If not found, mFoundNode will be nullptr class FindByNameVisitor : public osg::NodeVisitor { public: FindByNameVisitor(const std::string& nameToFind) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mNameToFind(nameToFind) , mFoundNode(nullptr) { } void apply(osg::Group& group) override; void apply(osg::MatrixTransform& node) override; void apply(osg::Geometry& node) override; bool checkGroup(osg::Group& group); std::string mNameToFind; osg::Group* mFoundNode; }; class FindByClassVisitor : public osg::NodeVisitor { public: FindByClassVisitor(const std::string& nameToFind) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mNameToFind(nameToFind) { } void apply(osg::Node &node) override; std::string mNameToFind; std::vector mFoundNodes; }; /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: typedef std::unordered_map, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> NodeMap; NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) { } void apply(osg::MatrixTransform& trans) override; private: NodeMap& mMap; }; /// @brief Base class for visitors that remove nodes from a scene graph. /// Subclasses need to fill the mToRemove vector. /// To use, node->accept(removeVisitor); removeVisitor.remove(); class RemoveVisitor : public osg::NodeVisitor { public: RemoveVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } void remove(); protected: // typedef std::vector > RemoveVec; std::vector > mToRemove; }; // Removes all drawables from a graph. class CleanObjectRootVisitor : public RemoveVisitor { public: void apply(osg::Drawable& drw) override; void apply(osg::Group& node) override; void apply(osg::MatrixTransform& node) override; void apply(osg::Node& node) override; void applyNode(osg::Node& node); void applyDrawable(osg::Node& node); }; class RemoveTriBipVisitor : public RemoveVisitor { public: void apply(osg::Drawable& drw) override; void apply(osg::Group& node) override; void apply(osg::MatrixTransform& node) override; void applyImpl(osg::Node& node); }; } #endif openmw-openmw-0.48.0/components/sceneutil/waterutil.cpp000066400000000000000000000066431445372753700233510ustar00rootroot00000000000000#include "waterutil.hpp" #include #include #include #include #include "depth.hpp" namespace SceneUtil { // disable nonsense test against a worldsize bb what will always pass class WaterBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback { osg::BoundingBox computeBound(const osg::Drawable&) const override { return osg::BoundingBox(); } }; osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats) { osg::ref_ptr verts (new osg::Vec3Array); osg::ref_ptr texcoords (new osg::Vec2Array); // some drivers don't like huge triangles, so we do some subdivisons // a paged solution would be even better const float step = size/segments; const float texCoordStep = textureRepeats / segments; for (int x=0; xpush_back(osg::Vec3f(x1, y2, 0.f)); verts->push_back(osg::Vec3f(x1, y1, 0.f)); verts->push_back(osg::Vec3f(x2, y1, 0.f)); verts->push_back(osg::Vec3f(x2, y2, 0.f)); float u1 = x*texCoordStep; float v1 = y*texCoordStep; float u2 = u1 + texCoordStep; float v2 = v1 + texCoordStep; texcoords->push_back(osg::Vec2f(u1, v2)); texcoords->push_back(osg::Vec2f(u1, v1)); texcoords->push_back(osg::Vec2f(u2, v1)); texcoords->push_back(osg::Vec2f(u2, v2)); } } osg::ref_ptr waterGeom (new osg::Geometry); waterGeom->setVertexArray(verts); waterGeom->setTexCoordArray(0, texcoords); osg::ref_ptr normal (new osg::Vec3Array); normal->push_back(osg::Vec3f(0,0,1)); waterGeom->setNormalArray(normal, osg::Array::BIND_OVERALL); waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size())); waterGeom->setComputeBoundingBoxCallback(new WaterBoundCallback); waterGeom->setCullingActive(false); return waterGeom; } osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin) { osg::ref_ptr stateset (new osg::StateSet); osg::ref_ptr material (new osg::Material); material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, alpha)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); material->setColorMode(osg::Material::OFF); stateset->setAttributeAndModes(material, osg::StateAttribute::ON); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); stateset->setRenderBinDetails(renderBin, "RenderBin"); return stateset; } } openmw-openmw-0.48.0/components/sceneutil/waterutil.hpp000066400000000000000000000005751445372753700233540ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WATERUTIL_H #define OPENMW_COMPONENTS_WATERUTIL_H #include namespace osg { class Geometry; class StateSet; } namespace SceneUtil { osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats); osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin); } #endif openmw-openmw-0.48.0/components/sceneutil/workqueue.cpp000066400000000000000000000052231445372753700233510ustar00rootroot00000000000000#include "workqueue.hpp" #include #include namespace SceneUtil { void WorkItem::waitTillDone() { if (mDone) return; std::unique_lock lock(mMutex); while (!mDone) { mCondition.wait(lock); } } void WorkItem::signalDone() { { std::unique_lock lock(mMutex); mDone = true; } mCondition.notify_all(); } bool WorkItem::isDone() const { return mDone; } WorkQueue::WorkQueue(std::size_t workerThreads) : mIsReleased(false) { start(workerThreads); } WorkQueue::~WorkQueue() { stop(); } void WorkQueue::start(std::size_t workerThreads) { { const std::lock_guard lock(mMutex); mIsReleased = false; } while (mThreads.size() < workerThreads) mThreads.emplace_back(std::make_unique(*this)); } void WorkQueue::stop() { { std::unique_lock lock(mMutex); while (!mQueue.empty()) mQueue.pop_back(); mIsReleased = true; mCondition.notify_all(); } mThreads.clear(); } void WorkQueue::addWorkItem(osg::ref_ptr item, bool front) { if (item->isDone()) { Log(Debug::Error) << "Error: trying to add a work item that is already completed"; return; } std::unique_lock lock(mMutex); if (front) mQueue.push_front(std::move(item)); else mQueue.push_back(std::move(item)); mCondition.notify_one(); } osg::ref_ptr WorkQueue::removeWorkItem() { std::unique_lock lock(mMutex); while (mQueue.empty() && !mIsReleased) { mCondition.wait(lock); } if (!mQueue.empty()) { osg::ref_ptr item = std::move(mQueue.front()); mQueue.pop_front(); return item; } return nullptr; } unsigned int WorkQueue::getNumItems() const { std::unique_lock lock(mMutex); return mQueue.size(); } unsigned int WorkQueue::getNumActiveThreads() const { return std::accumulate(mThreads.begin(), mThreads.end(), 0u, [] (auto r, const auto& t) { return r + t->isActive(); }); } WorkThread::WorkThread(WorkQueue& workQueue) : mWorkQueue(&workQueue) , mActive(false) , mThread([this] { run(); }) { } WorkThread::~WorkThread() { mThread.join(); } void WorkThread::run() { while (true) { osg::ref_ptr item = mWorkQueue->removeWorkItem(); if (!item) return; mActive = true; item->doWork(); item->signalDone(); mActive = false; } } bool WorkThread::isActive() const { return mActive; } } openmw-openmw-0.48.0/components/sceneutil/workqueue.hpp000066400000000000000000000053621445372753700233620ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H #define OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H #include #include #include #include #include #include #include namespace SceneUtil { class WorkItem : public osg::Referenced { public: /// Override in a derived WorkItem to perform actual work. virtual void doWork() {} bool isDone() const; /// Wait until the work is completed. Usually called from the main thread. void waitTillDone(); /// Internal use by the WorkQueue. void signalDone(); /// Set abort flag in order to return from doWork() as soon as possible. May not be respected by all WorkItems. virtual void abort() {} private: std::atomic_bool mDone {false}; std::mutex mMutex; std::condition_variable mCondition; }; class WorkThread; /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. /// @note Work items will be processed in the order that they were given in, however /// if multiple work threads are involved then it is possible for a later item to complete before earlier items. class WorkQueue : public osg::Referenced { public: WorkQueue(std::size_t workerThreads); ~WorkQueue(); void start(std::size_t workerThreads); void stop(); /// Add a new work item to the back of the queue. /// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete. /// @param front If true, add item to the front of the queue. If false (default), add to the back. void addWorkItem(osg::ref_ptr item, bool front=false); /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. /// If the workqueue is in the process of being destroyed, may return nullptr. /// @par Used internally by the WorkThread. osg::ref_ptr removeWorkItem(); unsigned int getNumItems() const; unsigned int getNumActiveThreads() const; private: bool mIsReleased; std::deque > mQueue; mutable std::mutex mMutex; std::condition_variable mCondition; std::vector> mThreads; }; /// Internally used by WorkQueue. class WorkThread { public: WorkThread(WorkQueue& workQueue); ~WorkThread(); bool isActive() const; private: WorkQueue* mWorkQueue; std::atomic mActive; std::thread mThread; void run(); }; } #endif openmw-openmw-0.48.0/components/sceneutil/writescene.cpp000066400000000000000000000013661445372753700234760ustar00rootroot00000000000000#include "writescene.hpp" #include #include #include #include "serialize.hpp" void SceneUtil::writeScene(osg::Node *node, const std::string& filename, const std::string& format) { registerSerializers(); osgDB::ReaderWriter* rw = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); if (!rw) throw std::runtime_error("can not find readerwriter for " + format); boost::filesystem::ofstream stream; stream.open(filename); osg::ref_ptr options = new osgDB::Options; options->setPluginStringData("fileType", format); options->setPluginStringData("WriteImageHint", "UseExternal"); rw->writeNode(*node, stream, options); } openmw-openmw-0.48.0/components/sceneutil/writescene.hpp000066400000000000000000000004051445372753700234740ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WRITESCENE_H #define OPENMW_COMPONENTS_WRITESCENE_H #include namespace osg { class Node; } namespace SceneUtil { void writeScene(osg::Node* node, const std::string& filename, const std::string& format); } #endif openmw-openmw-0.48.0/components/sdlutil/000077500000000000000000000000001445372753700203015ustar00rootroot00000000000000openmw-openmw-0.48.0/components/sdlutil/events.hpp000066400000000000000000000050231445372753700223160ustar00rootroot00000000000000#ifndef _SFO_EVENTS_H #define _SFO_EVENTS_H #include #include #include //////////// // Events // //////////// namespace SDLUtil { /** Extended mouse event struct where we treat the wheel like an axis, like everyone expects */ struct MouseMotionEvent : SDL_MouseMotionEvent { Sint32 zrel; Sint32 z; }; struct TouchEvent { int mDevice; int mFinger; float mX; float mY; float mPressure; #if SDL_VERSION_ATLEAST(2, 0, 14) explicit TouchEvent(const SDL_ControllerTouchpadEvent& arg) : mDevice(arg.touchpad) , mFinger(arg.finger) , mX(arg.x) , mY(arg.y) , mPressure(arg.pressure) {} #endif }; /////////////// // Listeners // /////////////// class MouseListener { public: virtual ~MouseListener() {} virtual void mouseMoved( const MouseMotionEvent &arg ) = 0; virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; virtual void mouseWheelMoved( const SDL_MouseWheelEvent &arg) = 0; }; class SensorListener { public: virtual ~SensorListener() {} virtual void sensorUpdated(const SDL_SensorEvent &arg) = 0; virtual void displayOrientationChanged() = 0; }; class KeyListener { public: virtual ~KeyListener() {} virtual void textInput (const SDL_TextInputEvent& arg) {} virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; }; class ControllerListener { public: virtual ~ControllerListener() {} virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; virtual void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) = 0; virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0; virtual void controllerRemoved(const SDL_ControllerDeviceEvent &arg) = 0; virtual void touchpadMoved(int deviceId, const TouchEvent& arg) = 0; virtual void touchpadPressed(int deviceId, const TouchEvent& arg) = 0; virtual void touchpadReleased(int deviceId, const TouchEvent& arg) = 0; }; class WindowListener { public: virtual ~WindowListener() {} /** @remarks The window's visibility changed */ virtual void windowVisibilityChange( bool visible ) {} virtual void windowClosed () {} virtual void windowResized (int x, int y) {} }; } #endif openmw-openmw-0.48.0/components/sdlutil/gl4es_init.cpp000066400000000000000000000014561445372753700230540ustar00rootroot00000000000000// EGL does not work reliably for feature detection. // Instead, we initialize gl4es manually. #ifdef OPENMW_GL4ES_MANUAL_INIT #include "gl4es_init.h" // For glHint #include extern "C" { #include #include static SDL_Window *gWindow; void openmw_gl4es_GetMainFBSize(int *width, int *height) { SDL_GetWindowSize(gWindow, width, height); } void openmw_gl4es_init(SDL_Window *window) { gWindow = window; set_getprocaddress(SDL_GL_GetProcAddress); set_getmainfbsize(openmw_gl4es_GetMainFBSize); initialize_gl4es(); // merge glBegin/glEnd in beams and console glHint(GL_BEGINEND_HINT_GL4ES, 1); // dxt unpacked to 16-bit looks ugly glHint(GL_AVOID16BITS_HINT_GL4ES, 1); } } // extern "C" #endif // OPENMW_GL4ES_MANUAL_INIT openmw-openmw-0.48.0/components/sdlutil/gl4es_init.h000066400000000000000000000006701445372753700225160ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H #define OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H #ifdef OPENMW_GL4ES_MANUAL_INIT #include // Must be called once SDL video mode has been set, // which creates a context. // // GL4ES can then query the context for features and extensions. extern "C" void openmw_gl4es_init(SDL_Window *window); #endif // OPENMW_GL4ES_MANUAL_INIT #endif // OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H openmw-openmw-0.48.0/components/sdlutil/imagetosurface.cpp000066400000000000000000000016251445372753700240070ustar00rootroot00000000000000#include "imagetosurface.hpp" #include #include namespace SDLUtil { SurfaceUniquePtr imageToSurface(osg::Image *image, bool flip) { int width = image->s(); int height = image->t(); SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); for(int x = 0; x < width; ++x) for(int y = 0; y < height; ++y) { osg::Vec4f clr = image->getColor(x, flip ? ((height-1)-y) : y); int bpp = surface->format->BytesPerPixel; Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; *(Uint32*)(p) = SDL_MapRGBA(surface->format, static_cast(clr.r() * 255), static_cast(clr.g() * 255), static_cast(clr.b() * 255), static_cast(clr.a() * 255)); } return SurfaceUniquePtr(surface, SDL_FreeSurface); } } openmw-openmw-0.48.0/components/sdlutil/imagetosurface.hpp000066400000000000000000000006411445372753700240110ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_IMAGETOSURFACE_H #define OPENMW_COMPONENTS_SDLUTIL_IMAGETOSURFACE_H #include struct SDL_Surface; namespace osg { class Image; } namespace SDLUtil { typedef std::unique_ptr SurfaceUniquePtr; /// Convert an osg::Image to an SDL_Surface. SurfaceUniquePtr imageToSurface(osg::Image* image, bool flip=false); } #endif openmw-openmw-0.48.0/components/sdlutil/sdlcursormanager.cpp000066400000000000000000000116111445372753700243600ustar00rootroot00000000000000#include "sdlcursormanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "imagetosurface.hpp" #if defined(OSG_LIBRARY_STATIC) && (!defined(ANDROID) || OSG_VERSION_GREATER_THAN(3, 6, 5)) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() #endif namespace SDLUtil { SDLCursorManager::SDLCursorManager() : mEnabled(false), mInitialized(false) { } SDLCursorManager::~SDLCursorManager() { CursorMap::const_iterator curs_iter = mCursorMap.begin(); while(curs_iter != mCursorMap.end()) { SDL_FreeCursor(curs_iter->second); ++curs_iter; } mCursorMap.clear(); } void SDLCursorManager::setEnabled(bool enabled) { if(mInitialized && enabled == mEnabled) return; mInitialized = true; mEnabled = enabled; //turn on hardware cursors if(enabled) { _setGUICursor(mCurrentCursor); } //turn off hardware cursors else { SDL_ShowCursor(SDL_FALSE); } } void SDLCursorManager::cursorChanged(const std::string& name) { mCurrentCursor = name; _setGUICursor(name); } void SDLCursorManager::_setGUICursor(const std::string &name) { auto it = mCursorMap.find(name); if (it != mCursorMap.end()) SDL_SetCursor(it->second); } void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { #ifndef ANDROID _createCursorFromResource(name, rotDegrees, image, hotspot_x, hotspot_y); #endif } SDLUtil::SurfaceUniquePtr decompress(osg::ref_ptr source, float rotDegrees) { int width = source->s(); int height = source->t(); bool useAlpha = source->isImageTranslucent(); osg::ref_ptr decompressedImage = new osg::Image; decompressedImage->setFileName(source->getFileName()); decompressedImage->allocateImage(width, height, 1, useAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE); for (int s=0; ssetColor(source->getColor(s,t,0), s,t,0); Uint32 redMask = 0x000000ff; Uint32 greenMask = 0x0000ff00; Uint32 blueMask = 0x00ff0000; Uint32 alphaMask = useAlpha ? 0xff000000 : 0; SDL_Surface *cursorSurface = SDL_CreateRGBSurfaceFrom(decompressedImage->data(), width, height, decompressedImage->getPixelSizeInBits(), decompressedImage->getRowSizeInBytes(), redMask, greenMask, blueMask, alphaMask); SDL_Surface *targetSurface = SDL_CreateRGBSurface(0, width, height, 32, redMask, greenMask, blueMask, alphaMask); SDL_Renderer *renderer = SDL_CreateSoftwareRenderer(targetSurface); SDL_RenderClear(renderer); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); SDL_Texture *cursorTexture = SDL_CreateTextureFromSurface(renderer, cursorSurface); SDL_RenderCopyEx(renderer, cursorTexture, nullptr, nullptr, -rotDegrees, nullptr, SDL_FLIP_VERTICAL); SDL_DestroyTexture(cursorTexture); SDL_FreeSurface(cursorSurface); SDL_DestroyRenderer(renderer); return SDLUtil::SurfaceUniquePtr(targetSurface, SDL_FreeSurface); } void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { if (mCursorMap.find(name) != mCursorMap.end()) return; try { auto surface = decompress(image, static_cast(rotDegrees)); //set the cursor and store it for later SDL_Cursor* curs = SDL_CreateColorCursor(surface.get(), hotspot_x, hotspot_y); mCursorMap.insert(CursorMap::value_type(std::string(name), curs)); } catch (std::exception& e) { Log(Debug::Warning) << e.what(); Log(Debug::Warning) << "Using default cursor."; return; } } } openmw-openmw-0.48.0/components/sdlutil/sdlcursormanager.hpp000066400000000000000000000024261445372753700243710ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLCURSORMANAGER_H #define OPENMW_COMPONENTS_SDLUTIL_SDLCURSORMANAGER_H #include #include #include struct SDL_Cursor; struct SDL_Surface; namespace osg { class Image; } namespace SDLUtil { class SDLCursorManager { public: SDLCursorManager(); virtual ~SDLCursorManager(); /// \brief sets whether to actively manage cursors or not virtual void setEnabled(bool enabled); /// \brief Tell the manager that the cursor has changed, giving the /// name of the cursor we changed to ("arrow", "ibeam", etc) virtual void cursorChanged(const std::string &name); virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); private: void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); typedef std::map CursorMap; CursorMap mCursorMap; std::string mCurrentCursor; bool mEnabled; bool mInitialized; }; } #endif openmw-openmw-0.48.0/components/sdlutil/sdlgraphicswindow.cpp000066400000000000000000000146761445372753700245560ustar00rootroot00000000000000#include "sdlgraphicswindow.hpp" #include #ifdef OPENMW_GL4ES_MANUAL_INIT #include "gl4es_init.h" #endif namespace SDLUtil { GraphicsWindowSDL2::~GraphicsWindowSDL2() { close(true); } GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits *traits) : mWindow(nullptr) , mContext(nullptr) , mValid(false) , mRealized(false) , mOwnsWindow(false) { _traits = traits; init(); if(GraphicsWindowSDL2::valid()) { setState(new osg::State); getState()->setGraphicsContext(this); if(_traits.valid() && _traits->sharedContext.valid()) { getState()->setContextID(_traits->sharedContext->getState()->getContextID()); incrementContextIDUsageCount(getState()->getContextID()); } else { getState()->setContextID(osg::GraphicsContext::createNewContextID()); } } } bool GraphicsWindowSDL2::setWindowDecorationImplementation(bool flag) { if(!mWindow) return false; SDL_SetWindowBordered(mWindow, flag ? SDL_TRUE : SDL_FALSE); return true; } bool GraphicsWindowSDL2::setWindowRectangleImplementation(int x, int y, int width, int height) { if(!mWindow) return false; SDL_SetWindowPosition(mWindow, x, y); SDL_SetWindowSize(mWindow, width, height); return true; } void GraphicsWindowSDL2::setWindowName(const std::string &name) { if(!mWindow) return; SDL_SetWindowTitle(mWindow, name.c_str()); _traits->windowName = name; } void GraphicsWindowSDL2::setCursor(MouseCursor mouseCursor) { _traits->useCursor = false; } void GraphicsWindowSDL2::init() { if(mValid) return; if(!_traits.valid()) return; WindowData *inheritedWindowData = dynamic_cast(_traits->inheritedWindowData.get()); mWindow = inheritedWindowData ? inheritedWindowData->mWindow : nullptr; mOwnsWindow = (mWindow == nullptr); if(mOwnsWindow) { OSG_FATAL<<"Error: No SDL window provided."<vsync); // Update traits with what we've actually been given // Use intermediate to avoid signed/unsigned mismatch int intermediateLocation; SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &intermediateLocation); _traits->red = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &intermediateLocation); _traits->green = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &intermediateLocation); _traits->blue = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &intermediateLocation); _traits->alpha = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &intermediateLocation); _traits->depth = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &intermediateLocation); _traits->stencil = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &intermediateLocation); _traits->doubleBuffer = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &intermediateLocation); _traits->sampleBuffers = intermediateLocation; SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &intermediateLocation); _traits->samples = intermediateLocation; SDL_GL_MakeCurrent(oldWin, oldCtx); mValid = true; getEventQueue()->syncWindowRectangleWithGraphicsContext(); } bool GraphicsWindowSDL2::realizeImplementation() { if(mRealized) { OSG_NOTICE<< "GraphicsWindowSDL2::realizeImplementation() Already realized" <syncWindowRectangleWithGraphicsContext(); mRealized = true; return true; } bool GraphicsWindowSDL2::makeCurrentImplementation() { if(!mRealized) { OSG_WARN<<"Warning: GraphicsWindow not realized, cannot do makeCurrent."< #include namespace SDLUtil { class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow { SDL_Window* mWindow; SDL_GLContext mContext; bool mValid; bool mRealized; bool mOwnsWindow; void init(); virtual ~GraphicsWindowSDL2(); public: GraphicsWindowSDL2(osg::GraphicsContext::Traits *traits); bool isSameKindAs(const Object* object) const override { return dynamic_cast(object)!=nullptr; } const char* libraryName() const override { return "osgViewer"; } const char* className() const override { return "GraphicsWindowSDL2"; } bool valid() const override { return mValid; } /** Realise the GraphicsContext.*/ bool realizeImplementation()override ; /** Return true if the graphics context has been realised and is ready to use.*/ bool isRealizedImplementation() const override { return mRealized; } /** Close the graphics context.*/ void closeImplementation() override; /** Make this graphics context current.*/ bool makeCurrentImplementation() override; /** Release the graphics context.*/ bool releaseContextImplementation() override; /** Swap the front and back buffers.*/ void swapBuffersImplementation() override; /** Set sync-to-vblank. */ void setSyncToVBlank(bool on) override; /** Set Window decoration.*/ bool setWindowDecorationImplementation(bool flag) override; /** Raise specified window */ void raiseWindow() override; /** Set the window's position and size.*/ bool setWindowRectangleImplementation(int x, int y, int width, int height) override; /** Set the name of the window */ void setWindowName(const std::string &name) override; /** Set mouse cursor to a specific shape.*/ void setCursor(MouseCursor cursor) override; /** Get focus.*/ void grabFocus() override {} /** Get focus on if the pointer is in this window.*/ void grabFocusIfPointerInWindow() override {} /** WindowData is used to pass in the SDL2 window handle attached to the GraphicsContext::Traits structure. */ struct WindowData : public osg::Referenced { WindowData(SDL_Window *window) : mWindow(window) { } SDL_Window *mWindow; }; private: void setSwapInterval(bool enable); }; } #endif /* OSGGRAPHICSWINDOW_H */ openmw-openmw-0.48.0/components/sdlutil/sdlinputwrapper.cpp000066400000000000000000000355401445372753700242570ustar00rootroot00000000000000#include "sdlinputwrapper.hpp" #include #include #include namespace SDLUtil { InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr viewer, bool grab) : mSDLWindow(window), mViewer(viewer), mMouseListener(nullptr), mSensorListener(nullptr), mKeyboardListener(nullptr), mWindowListener(nullptr), mConListener(nullptr), mWarpX(0), mWarpY(0), mWarpCompensate(false), mWrapPointer(false), mAllowGrab(grab), mWantMouseVisible(false), mWantGrab(false), mWantRelative(false), mGrabPointer(false), mMouseRelative(false), mFirstMouseMove(true), mMouseZ(0), mMouseX(0), mMouseY(0), mWindowHasFocus(true), mMouseInWindow(true) { Uint32 flags = SDL_GetWindowFlags(mSDLWindow); mWindowHasFocus = (flags & SDL_WINDOW_INPUT_FOCUS); mMouseInWindow = (flags & SDL_WINDOW_MOUSE_FOCUS); } InputWrapper::~InputWrapper() { } void InputWrapper::capture(bool windowEventsOnly) { mViewer->getEventQueue()->frame(0.f); SDL_PumpEvents(); SDL_Event evt; if (windowEventsOnly) { // During loading, handle window events, discard button presses and mouse movement and keep others for later while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT) > 0) handleWindowEvent(evt); SDL_FlushEvent(SDL_KEYDOWN); SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN); SDL_FlushEvent(SDL_MOUSEBUTTONDOWN); SDL_FlushEvent(SDL_MOUSEMOTION); SDL_FlushEvent(SDL_MOUSEWHEEL); return; } while(SDL_PollEvent(&evt)) { switch(evt.type) { case SDL_MOUSEMOTION: // Ignore this if it happened due to a warp if(!_handleWarpMotion(evt.motion)) { // If in relative mode, don't trigger events unless window has focus if (!mWantRelative || mWindowHasFocus) mMouseListener->mouseMoved(_packageMouseMotion(evt)); // Try to keep the mouse inside the window if (mWindowHasFocus) _wrapMousePointer(evt.motion); } break; case SDL_MOUSEWHEEL: mMouseListener->mouseMoved(_packageMouseMotion(evt)); mMouseListener->mouseWheelMoved(evt.wheel); break; case SDL_SENSORUPDATE: mSensorListener->sensorUpdated(evt.sensor); break; case SDL_MOUSEBUTTONDOWN: mMouseListener->mousePressed(evt.button, evt.button.button); break; case SDL_MOUSEBUTTONUP: mMouseListener->mouseReleased(evt.button, evt.button.button); break; case SDL_KEYDOWN: mKeyboardListener->keyPressed(evt.key); if (!isModifierHeld(KMOD_ALT) && evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) { mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); } break; case SDL_KEYUP: if (!evt.key.repeat) { mKeyboardListener->keyReleased(evt.key); if (!isModifierHeld(KMOD_ALT) && evt.key.keysym.sym >= SDLK_F1 && evt.key.keysym.sym <= SDLK_F12) mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F1 + (evt.key.keysym.sym - SDLK_F1)); } break; case SDL_TEXTEDITING: break; case SDL_TEXTINPUT: mKeyboardListener->textInput(evt.text); break; case SDL_KEYMAPCHANGED: break; case SDL_JOYHATMOTION: //As we manage everything with GameController, don't even bother with these. case SDL_JOYAXISMOTION: case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYDEVICEADDED: case SDL_JOYDEVICEREMOVED: break; case SDL_CONTROLLERDEVICEADDED: if(mConListener) mConListener->controllerAdded(1, evt.cdevice); //We only support one joystick, so give everything a generic deviceID break; case SDL_CONTROLLERDEVICEREMOVED: if(mConListener) mConListener->controllerRemoved(evt.cdevice); break; case SDL_CONTROLLERBUTTONDOWN: if(mConListener) mConListener->buttonPressed(1, evt.cbutton); break; case SDL_CONTROLLERBUTTONUP: if(mConListener) mConListener->buttonReleased(1, evt.cbutton); break; case SDL_CONTROLLERAXISMOTION: if(mConListener) mConListener->axisMoved(1, evt.caxis); break; #if SDL_VERSION_ATLEAST(2, 0, 14) case SDL_CONTROLLERSENSORUPDATE: // controller sensor data is received on demand break; case SDL_CONTROLLERTOUCHPADDOWN: mConListener->touchpadPressed(1, TouchEvent(evt.ctouchpad)); break; case SDL_CONTROLLERTOUCHPADMOTION: mConListener->touchpadMoved(1, TouchEvent(evt.ctouchpad)); break; case SDL_CONTROLLERTOUCHPADUP: mConListener->touchpadReleased(1, TouchEvent(evt.ctouchpad)); break; #endif case SDL_WINDOWEVENT: handleWindowEvent(evt); break; case SDL_QUIT: if (mWindowListener) mWindowListener->windowClosed(); break; case SDL_DISPLAYEVENT: switch (evt.display.event) { case SDL_DISPLAYEVENT_ORIENTATION: if (mSensorListener && evt.display.display == (unsigned int) Settings::Manager::getInt("screen", "Video")) mSensorListener->displayOrientationChanged(); break; default: break; } break; case SDL_CLIPBOARDUPDATE: break; // We don't need this event, clipboard is retrieved on demand case SDL_FINGERDOWN: case SDL_FINGERUP: case SDL_FINGERMOTION: case SDL_DOLLARGESTURE: case SDL_DOLLARRECORD: case SDL_MULTIGESTURE: // No use for touch & gesture events break; case SDL_APP_WILLENTERBACKGROUND: case SDL_APP_WILLENTERFOREGROUND: case SDL_APP_DIDENTERBACKGROUND: case SDL_APP_DIDENTERFOREGROUND: // We do not need background/foreground switch event for mobile devices so far break; case SDL_APP_TERMINATING: // There is nothing we can do here. break; case SDL_APP_LOWMEMORY: Log(Debug::Warning) << "System reports that free RAM on device is running low. You may encounter an unexpected behaviour."; break; default: Log(Debug::Info) << "Unhandled SDL event of type 0x" << std::hex << evt.type; break; } } } void InputWrapper::handleWindowEvent(const SDL_Event& evt) { switch (evt.window.event) { case SDL_WINDOWEVENT_ENTER: mMouseInWindow = true; updateMouseSettings(); break; case SDL_WINDOWEVENT_LEAVE: mMouseInWindow = false; updateMouseSettings(); break; case SDL_WINDOWEVENT_MOVED: // I'm not sure what OSG is using the window position for, but I don't think it's needed, // so we ignore window moved events (improves window movement performance) break; case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; SDL_GetWindowSize(mSDLWindow, &w, &h); int x,y; SDL_GetWindowPosition(mSDLWindow, &x,&y); mViewer->getCamera()->getGraphicsContext()->resized(x,y,w,h); mViewer->getEventQueue()->windowResize(x,y,w,h); if (mWindowListener) mWindowListener->windowResized(w, h); break; case SDL_WINDOWEVENT_RESIZED: // This should also fire SIZE_CHANGED, so no need to handle break; case SDL_WINDOWEVENT_FOCUS_GAINED: mWindowHasFocus = true; updateMouseSettings(); break; case SDL_WINDOWEVENT_FOCUS_LOST: mWindowHasFocus = false; updateMouseSettings(); break; case SDL_WINDOWEVENT_CLOSE: break; case SDL_WINDOWEVENT_SHOWN: case SDL_WINDOWEVENT_RESTORED: if (mWindowListener) mWindowListener->windowVisibilityChange(true); break; case SDL_WINDOWEVENT_HIDDEN: case SDL_WINDOWEVENT_MINIMIZED: if (mWindowListener) mWindowListener->windowVisibilityChange(false); break; } } bool InputWrapper::isModifierHeld(int mod) { return (SDL_GetModState() & mod) != 0; } bool InputWrapper::isKeyDown(SDL_Scancode key) { return (SDL_GetKeyboardState(nullptr)[key]) != 0; } /// \brief Moves the mouse to the specified point within the viewport void InputWrapper::warpMouse(int x, int y) { SDL_WarpMouseInWindow(mSDLWindow, x, y); mWarpCompensate = true; mWarpX = x; mWarpY = y; } /// \brief Locks the pointer to the window void InputWrapper::setGrabPointer(bool grab) { mWantGrab = grab; updateMouseSettings(); } /// \brief Set the mouse to relative positioning. Doesn't move the cursor /// and disables mouse acceleration. void InputWrapper::setMouseRelative(bool relative) { mWantRelative = relative; updateMouseSettings(); } void InputWrapper::setMouseVisible(bool visible) { mWantMouseVisible = visible; updateMouseSettings(); } void InputWrapper::updateMouseSettings() { mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; SDL_SetWindowGrab(mSDLWindow, mGrabPointer && mAllowGrab ? SDL_TRUE : SDL_FALSE); SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); bool relative = mWantRelative && mMouseInWindow && mWindowHasFocus; if(mMouseRelative == relative) return; mMouseRelative = relative; mWrapPointer = false; // eep, wrap the pointer manually if the input driver doesn't support // relative positioning natively // also use wrapping if no-grab was specified in options (SDL_SetRelativeMouseMode // appears to eat the mouse cursor when pausing in a debugger) bool success = mAllowGrab && SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; if(relative && !success) mWrapPointer = true; //now remove all mouse events using the old setting from the queue SDL_PumpEvents(); SDL_FlushEvent(SDL_MOUSEMOTION); } /// \brief Internal method for ignoring relative motions as a side effect /// of warpMouse() bool InputWrapper::_handleWarpMotion(const SDL_MouseMotionEvent& evt) { if(!mWarpCompensate) return false; //this was a warp event, signal the caller to eat it. if(evt.x == mWarpX && evt.y == mWarpY) { mWarpCompensate = false; return true; } return false; } /// \brief Wrap the mouse to the viewport void InputWrapper::_wrapMousePointer(const SDL_MouseMotionEvent& evt) { //don't wrap if we don't want relative movements, support relative //movements natively, or aren't grabbing anyways if(!mMouseRelative || !mWrapPointer || !mGrabPointer) return; int width = 0; int height = 0; SDL_GetWindowSize(mSDLWindow, &width, &height); const int FUDGE_FACTOR_X = width/4; const int FUDGE_FACTOR_Y = height/4; //warp the mouse if it's about to go outside the window if(evt.x - FUDGE_FACTOR_X < 0 || evt.x + FUDGE_FACTOR_X > width || evt.y - FUDGE_FACTOR_Y < 0 || evt.y + FUDGE_FACTOR_Y > height) { warpMouse(width / 2, height / 2); } } /// \brief Package mouse and mousewheel motions into a single event MouseMotionEvent InputWrapper::_packageMouseMotion(const SDL_Event &evt) { MouseMotionEvent pack_evt = {}; pack_evt.x = mMouseX; pack_evt.y = mMouseY; pack_evt.z = mMouseZ; if(evt.type == SDL_MOUSEMOTION) { pack_evt.x = mMouseX = evt.motion.x; pack_evt.y = mMouseY = evt.motion.y; pack_evt.xrel = evt.motion.xrel; pack_evt.yrel = evt.motion.yrel; pack_evt.type = SDL_MOUSEMOTION; if (mFirstMouseMove) { // first event should be treated as non-relative, since there's no point of reference // SDL then (incorrectly) uses (0,0) as point of reference, on Linux at least... pack_evt.xrel = pack_evt.yrel = 0; mFirstMouseMove = false; } } else if(evt.type == SDL_MOUSEWHEEL) { mMouseZ += pack_evt.zrel = (evt.wheel.y * 120); pack_evt.z = mMouseZ; pack_evt.type = SDL_MOUSEWHEEL; } else { throw std::runtime_error("Tried to package non-motion event!"); } return pack_evt; } } openmw-openmw-0.48.0/components/sdlutil/sdlinputwrapper.hpp000066400000000000000000000045261445372753700242640ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLINPUTWRAPPER_H #define OPENMW_COMPONENTS_SDLUTIL_SDLINPUTWRAPPER_H #include #include #include #include #include "events.hpp" namespace osgViewer { class Viewer; } namespace SDLUtil { /// \brief A wrapper around SDL's event queue, mostly used for handling input-related events. class InputWrapper { public: InputWrapper(SDL_Window *window, osg::ref_ptr viewer, bool grab); ~InputWrapper(); void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } void setSensorEventCallback(SensorListener* listen) { mSensorListener = listen; } void setKeyboardEventCallback(KeyListener* listen) { mKeyboardListener = listen; } void setWindowEventCallback(WindowListener* listen) { mWindowListener = listen; } void setControllerEventCallback(ControllerListener* listen) { mConListener = listen; } void capture(bool windowEventsOnly); bool isModifierHeld(int mod); bool isKeyDown(SDL_Scancode key); void setMouseVisible (bool visible); void setMouseRelative(bool relative); bool getMouseRelative() { return mMouseRelative; } void setGrabPointer(bool grab); void warpMouse(int x, int y); void updateMouseSettings(); private: void handleWindowEvent(const SDL_Event& evt); bool _handleWarpMotion(const SDL_MouseMotionEvent& evt); void _wrapMousePointer(const SDL_MouseMotionEvent &evt); MouseMotionEvent _packageMouseMotion(const SDL_Event& evt); SDL_Window* mSDLWindow; osg::ref_ptr mViewer; MouseListener* mMouseListener; SensorListener* mSensorListener; KeyListener* mKeyboardListener; WindowListener* mWindowListener; ControllerListener* mConListener; Uint16 mWarpX; Uint16 mWarpY; bool mWarpCompensate; bool mWrapPointer; bool mAllowGrab; bool mWantMouseVisible; bool mWantGrab; bool mWantRelative; bool mGrabPointer; bool mMouseRelative; bool mFirstMouseMove; Sint32 mMouseZ; Sint32 mMouseX; Sint32 mMouseY; bool mWindowHasFocus; bool mMouseInWindow; }; } #endif openmw-openmw-0.48.0/components/sdlutil/sdlmappings.cpp000066400000000000000000000243501445372753700233320ustar00rootroot00000000000000#include "sdlmappings.hpp" #include #include #include #include namespace SDLUtil { std::string sdlControllerButtonToString(int button) { switch(button) { case SDL_CONTROLLER_BUTTON_A: return "A Button"; case SDL_CONTROLLER_BUTTON_B: return "B Button"; case SDL_CONTROLLER_BUTTON_BACK: return "Back Button"; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return "DPad Down"; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return "DPad Left"; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return "DPad Right"; case SDL_CONTROLLER_BUTTON_DPAD_UP: return "DPad Up"; case SDL_CONTROLLER_BUTTON_GUIDE: return "Guide Button"; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return "Left Shoulder"; case SDL_CONTROLLER_BUTTON_LEFTSTICK: return "Left Stick Button"; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return "Right Shoulder"; case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return "Right Stick Button"; case SDL_CONTROLLER_BUTTON_START: return "Start Button"; case SDL_CONTROLLER_BUTTON_X: return "X Button"; case SDL_CONTROLLER_BUTTON_Y: return "Y Button"; default: return "Button " + std::to_string(button); } } std::string sdlControllerAxisToString(int axis) { switch(axis) { case SDL_CONTROLLER_AXIS_LEFTX: return "Left Stick X"; case SDL_CONTROLLER_AXIS_LEFTY: return "Left Stick Y"; case SDL_CONTROLLER_AXIS_RIGHTX: return "Right Stick X"; case SDL_CONTROLLER_AXIS_RIGHTY: return "Right Stick Y"; case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return "Left Trigger"; case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return "Right Trigger"; default: return "Axis " + std::to_string(axis); } } MyGUI::MouseButton sdlMouseButtonToMyGui(Uint8 button) { //The right button is the second button, according to MyGUI if(button == SDL_BUTTON_RIGHT) button = SDL_BUTTON_MIDDLE; else if(button == SDL_BUTTON_MIDDLE) button = SDL_BUTTON_RIGHT; //MyGUI's buttons are 0 indexed return MyGUI::MouseButton::Enum(button - 1); } Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button) { Uint8 value = button.getValue() + 1; if (value == SDL_BUTTON_RIGHT) value = SDL_BUTTON_MIDDLE; else if (value == SDL_BUTTON_MIDDLE) value = SDL_BUTTON_RIGHT; return value; } namespace { std::map initKeyMap() { std::map keyMap; keyMap[SDLK_UNKNOWN] = MyGUI::KeyCode::None; keyMap[SDLK_ESCAPE] = MyGUI::KeyCode::Escape; keyMap[SDLK_1] = MyGUI::KeyCode::One; keyMap[SDLK_2] = MyGUI::KeyCode::Two; keyMap[SDLK_3] = MyGUI::KeyCode::Three; keyMap[SDLK_4] = MyGUI::KeyCode::Four; keyMap[SDLK_5] = MyGUI::KeyCode::Five; keyMap[SDLK_6] = MyGUI::KeyCode::Six; keyMap[SDLK_7] = MyGUI::KeyCode::Seven; keyMap[SDLK_8] = MyGUI::KeyCode::Eight; keyMap[SDLK_9] = MyGUI::KeyCode::Nine; keyMap[SDLK_0] = MyGUI::KeyCode::Zero; keyMap[SDLK_MINUS] = MyGUI::KeyCode::Minus; keyMap[SDLK_EQUALS] = MyGUI::KeyCode::Equals; keyMap[SDLK_BACKSPACE] = MyGUI::KeyCode::Backspace; keyMap[SDLK_TAB] = MyGUI::KeyCode::Tab; keyMap[SDLK_q] = MyGUI::KeyCode::Q; keyMap[SDLK_w] = MyGUI::KeyCode::W; keyMap[SDLK_e] = MyGUI::KeyCode::E; keyMap[SDLK_r] = MyGUI::KeyCode::R; keyMap[SDLK_t] = MyGUI::KeyCode::T; keyMap[SDLK_y] = MyGUI::KeyCode::Y; keyMap[SDLK_u] = MyGUI::KeyCode::U; keyMap[SDLK_i] = MyGUI::KeyCode::I; keyMap[SDLK_o] = MyGUI::KeyCode::O; keyMap[SDLK_p] = MyGUI::KeyCode::P; keyMap[SDLK_RETURN] = MyGUI::KeyCode::Return; keyMap[SDLK_a] = MyGUI::KeyCode::A; keyMap[SDLK_s] = MyGUI::KeyCode::S; keyMap[SDLK_d] = MyGUI::KeyCode::D; keyMap[SDLK_f] = MyGUI::KeyCode::F; keyMap[SDLK_g] = MyGUI::KeyCode::G; keyMap[SDLK_h] = MyGUI::KeyCode::H; keyMap[SDLK_j] = MyGUI::KeyCode::J; keyMap[SDLK_k] = MyGUI::KeyCode::K; keyMap[SDLK_l] = MyGUI::KeyCode::L; keyMap[SDLK_SEMICOLON] = MyGUI::KeyCode::Semicolon; keyMap[SDLK_QUOTE] = MyGUI::KeyCode::Apostrophe; keyMap[SDLK_BACKQUOTE] = MyGUI::KeyCode::Grave; keyMap[SDLK_LSHIFT] = MyGUI::KeyCode::LeftShift; keyMap[SDLK_BACKSLASH] = MyGUI::KeyCode::Backslash; keyMap[SDLK_z] = MyGUI::KeyCode::Z; keyMap[SDLK_x] = MyGUI::KeyCode::X; keyMap[SDLK_c] = MyGUI::KeyCode::C; keyMap[SDLK_v] = MyGUI::KeyCode::V; keyMap[SDLK_b] = MyGUI::KeyCode::B; keyMap[SDLK_n] = MyGUI::KeyCode::N; keyMap[SDLK_m] = MyGUI::KeyCode::M; keyMap[SDLK_COMMA] = MyGUI::KeyCode::Comma; keyMap[SDLK_PERIOD] = MyGUI::KeyCode::Period; keyMap[SDLK_SLASH] = MyGUI::KeyCode::Slash; keyMap[SDLK_RSHIFT] = MyGUI::KeyCode::RightShift; keyMap[SDLK_KP_MULTIPLY] = MyGUI::KeyCode::Multiply; keyMap[SDLK_LALT] = MyGUI::KeyCode::LeftAlt; keyMap[SDLK_SPACE] = MyGUI::KeyCode::Space; keyMap[SDLK_CAPSLOCK] = MyGUI::KeyCode::Capital; keyMap[SDLK_F1] = MyGUI::KeyCode::F1; keyMap[SDLK_F2] = MyGUI::KeyCode::F2; keyMap[SDLK_F3] = MyGUI::KeyCode::F3; keyMap[SDLK_F4] = MyGUI::KeyCode::F4; keyMap[SDLK_F5] = MyGUI::KeyCode::F5; keyMap[SDLK_F6] = MyGUI::KeyCode::F6; keyMap[SDLK_F7] = MyGUI::KeyCode::F7; keyMap[SDLK_F8] = MyGUI::KeyCode::F8; keyMap[SDLK_F9] = MyGUI::KeyCode::F9; keyMap[SDLK_F10] = MyGUI::KeyCode::F10; keyMap[SDLK_NUMLOCKCLEAR] = MyGUI::KeyCode::NumLock; keyMap[SDLK_SCROLLLOCK] = MyGUI::KeyCode::ScrollLock; keyMap[SDLK_KP_7] = MyGUI::KeyCode::Numpad7; keyMap[SDLK_KP_8] = MyGUI::KeyCode::Numpad8; keyMap[SDLK_KP_9] = MyGUI::KeyCode::Numpad9; keyMap[SDLK_KP_MINUS] = MyGUI::KeyCode::Subtract; keyMap[SDLK_KP_4] = MyGUI::KeyCode::Numpad4; keyMap[SDLK_KP_5] = MyGUI::KeyCode::Numpad5; keyMap[SDLK_KP_6] = MyGUI::KeyCode::Numpad6; keyMap[SDLK_KP_PLUS] = MyGUI::KeyCode::Add; keyMap[SDLK_KP_1] = MyGUI::KeyCode::Numpad1; keyMap[SDLK_KP_2] = MyGUI::KeyCode::Numpad2; keyMap[SDLK_KP_3] = MyGUI::KeyCode::Numpad3; keyMap[SDLK_KP_0] = MyGUI::KeyCode::Numpad0; keyMap[SDLK_KP_PERIOD] = MyGUI::KeyCode::Decimal; keyMap[SDLK_F11] = MyGUI::KeyCode::F11; keyMap[SDLK_F12] = MyGUI::KeyCode::F12; keyMap[SDLK_F13] = MyGUI::KeyCode::F13; keyMap[SDLK_F14] = MyGUI::KeyCode::F14; keyMap[SDLK_F15] = MyGUI::KeyCode::F15; keyMap[SDLK_KP_EQUALS] = MyGUI::KeyCode::NumpadEquals; keyMap[SDLK_COLON] = MyGUI::KeyCode::Colon; keyMap[SDLK_KP_ENTER] = MyGUI::KeyCode::NumpadEnter; keyMap[SDLK_KP_DIVIDE] = MyGUI::KeyCode::Divide; keyMap[SDLK_SYSREQ] = MyGUI::KeyCode::SysRq; keyMap[SDLK_RALT] = MyGUI::KeyCode::RightAlt; keyMap[SDLK_HOME] = MyGUI::KeyCode::Home; keyMap[SDLK_UP] = MyGUI::KeyCode::ArrowUp; keyMap[SDLK_PAGEUP] = MyGUI::KeyCode::PageUp; keyMap[SDLK_LEFT] = MyGUI::KeyCode::ArrowLeft; keyMap[SDLK_RIGHT] = MyGUI::KeyCode::ArrowRight; keyMap[SDLK_END] = MyGUI::KeyCode::End; keyMap[SDLK_DOWN] = MyGUI::KeyCode::ArrowDown; keyMap[SDLK_PAGEDOWN] = MyGUI::KeyCode::PageDown; keyMap[SDLK_INSERT] = MyGUI::KeyCode::Insert; keyMap[SDLK_DELETE] = MyGUI::KeyCode::Delete; keyMap[SDLK_APPLICATION] = MyGUI::KeyCode::AppMenu; //The function of the Ctrl and Meta keys are switched on macOS compared to other platforms. //For instance] = Cmd+C versus Ctrl+C to copy from the system clipboard #if defined(__APPLE__) keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftControl; keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightControl; keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftWindows; keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightWindows; #else keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftWindows; keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightWindows; keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftControl; keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightControl; #endif return keyMap; } std::map reverseKeyMap(const std::map& map) { std::map result; for (auto& [sdl, mygui] : map) result[mygui] = sdl; return result; } } MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code) { static std::map keyMap = initKeyMap(); MyGUI::KeyCode kc = MyGUI::KeyCode::None; auto foundKey = keyMap.find(code); if (foundKey != keyMap.end()) kc = foundKey->second; return kc; } SDL_Keycode myGuiKeyToSdl(MyGUI::KeyCode button) { static auto keyMap = reverseKeyMap(initKeyMap()); SDL_Keycode kc = 0; auto foundKey = keyMap.find(button); if (foundKey != keyMap.end()) kc = foundKey->second; return kc; } } openmw-openmw-0.48.0/components/sdlutil/sdlmappings.hpp000066400000000000000000000010671445372753700233370ustar00rootroot00000000000000#ifndef SDLUTIL_SDLMAPPINGS #define SDLUTIL_SDLMAPPINGS #include #include #include namespace MyGUI { struct MouseButton; } namespace SDLUtil { std::string sdlControllerButtonToString(int button); std::string sdlControllerAxisToString(int axis); MyGUI::MouseButton sdlMouseButtonToMyGui(Uint8 button); Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button); MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code); SDL_Keycode myGuiKeyToSdl(MyGUI::KeyCode button); } #endif // !SDLUTIL_SDLMAPPINGS openmw-openmw-0.48.0/components/sdlutil/sdlvideowrapper.cpp000066400000000000000000000072711445372753700242260ustar00rootroot00000000000000#include "sdlvideowrapper.hpp" #include #include #include #include namespace SDLUtil { VideoWrapper::VideoWrapper(SDL_Window *window, osg::ref_ptr viewer) : mWindow(window) , mViewer(viewer) , mGamma(1.f) , mContrast(1.f) , mHasSetGammaContrast(false) { SDL_GetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); } VideoWrapper::~VideoWrapper() { SDL_SetWindowFullscreen(mWindow, 0); // If user hasn't touched the defaults no need to restore if (mHasSetGammaContrast) SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); } void VideoWrapper::setSyncToVBlank(bool sync) { osgViewer::Viewer::Windows windows; mViewer->getWindows(windows); mViewer->stopThreading(); for (osgViewer::Viewer::Windows::iterator it = windows.begin(); it != windows.end(); ++it) { osgViewer::GraphicsWindow* win = *it; win->setSyncToVBlank(sync); } mViewer->startThreading(); } void VideoWrapper::setGammaContrast(float gamma, float contrast) { if (gamma == mGamma && contrast == mContrast) return; mGamma = gamma; mContrast = contrast; mHasSetGammaContrast = true; Uint16 red[256], green[256], blue[256]; for (int i = 0; i < 256; i++) { float k = i/256.0f; k = (k - 0.5f) * contrast + 0.5f; k = pow(k, 1.f/gamma); k *= 256; float value = k*256; if (value > 65535) value = 65535; else if (value < 0) value = 0; red[i] = green[i] = blue[i] = static_cast(value); } if (SDL_SetWindowGammaRamp(mWindow, red, green, blue) < 0) Log(Debug::Warning) << "Couldn't set gamma: " << SDL_GetError(); } void VideoWrapper::setVideoMode(int width, int height, Settings::WindowMode windowMode, bool windowBorder) { SDL_SetWindowFullscreen(mWindow, 0); if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED) SDL_RestoreWindow(mWindow); if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { SDL_DisplayMode mode; SDL_GetWindowDisplayMode(mWindow, &mode); mode.w = width; mode.h = height; SDL_SetWindowDisplayMode(mWindow, &mode); SDL_SetWindowFullscreen(mWindow, windowMode == Settings::WindowMode::Fullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowSize(mWindow, width, height); SDL_SetWindowBordered(mWindow, windowBorder ? SDL_TRUE : SDL_FALSE); centerWindow(); } } void VideoWrapper::centerWindow() { // Resize breaks the sdl window in some cases; see issue: #5539 SDL_Rect rect{}; int x = 0; int y = 0; int w = 0; int h = 0; auto index = SDL_GetWindowDisplayIndex(mWindow); SDL_GetDisplayBounds(index, &rect); SDL_GetWindowSize(mWindow, &w, &h); x = rect.x; y = rect.y; // Center dimensions that do not fill the screen if (w < rect.w) { x = rect.x + rect.w / 2 - w / 2; } if (h < rect.h) { y = rect.y + rect.h / 2 - h / 2; } SDL_SetWindowPosition(mWindow, x, y); } } openmw-openmw-0.48.0/components/sdlutil/sdlvideowrapper.hpp000066400000000000000000000017461445372753700242340ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H #define OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H #include #include struct SDL_Window; namespace osgViewer { class Viewer; } namespace Settings { enum class WindowMode; } namespace SDLUtil { class VideoWrapper { public: VideoWrapper(SDL_Window* window, osg::ref_ptr viewer); ~VideoWrapper(); void setSyncToVBlank(bool sync); void setGammaContrast(float gamma, float contrast); void setVideoMode(int width, int height, Settings::WindowMode windowMode, bool windowBorder); void centerWindow(); private: SDL_Window* mWindow; osg::ref_ptr mViewer; float mGamma; float mContrast; bool mHasSetGammaContrast; // Store system gamma ramp on window creation. Restore system gamma ramp on exit Uint16 mOldSystemGammaRamp[256*3]; }; } #endif openmw-openmw-0.48.0/components/serialization/000077500000000000000000000000001445372753700214765ustar00rootroot00000000000000openmw-openmw-0.48.0/components/serialization/binaryreader.hpp000066400000000000000000000046161445372753700246650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H #define OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H #include #include #include #include #include #include #include namespace Serialization { struct NotEnoughData : std::runtime_error { NotEnoughData() : std::runtime_error("Not enough data") {} }; class BinaryReader { public: explicit BinaryReader(const std::byte* pos, const std::byte* end) : mPos(pos), mEnd(end) { assert(mPos <= mEnd); } BinaryReader(const BinaryReader&) = delete; template void operator()(Format&& format, T& value) { if constexpr (std::is_enum_v) (*this)(std::forward(format), static_cast&>(value)); else if constexpr (std::is_arithmetic_v) { if (mEnd - mPos < static_cast(sizeof(T))) throw NotEnoughData(); std::memcpy(&value, mPos, sizeof(T)); mPos += sizeof(T); value = Misc::toLittleEndian(value); } else if constexpr (std::is_pointer_v) value = reinterpret_cast(mPos); else { format(*this, value); } } template auto operator()(Format&& format, T* data, std::size_t count) { if constexpr (std::is_enum_v) (*this)(std::forward(format), reinterpret_cast*>(data), count); else if constexpr (std::is_arithmetic_v) { const std::size_t size = sizeof(T) * count; if (mEnd - mPos < static_cast(size)) throw NotEnoughData(); std::memcpy(data, mPos, size); mPos += size; if constexpr (!Misc::IS_LITTLE_ENDIAN) std::for_each(data, data + count, [&] (T& v) { v = Misc::fromLittleEndian(v); }); } else { format(*this, data, count); } } private: const std::byte* mPos; const std::byte* const mEnd; }; } #endif openmw-openmw-0.48.0/components/serialization/binarywriter.hpp000066400000000000000000000047621445372753700247410ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H #define OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H #include #include #include #include #include #include #include namespace Serialization { struct NotEnoughSpace : std::runtime_error { NotEnoughSpace() : std::runtime_error("Not enough space") {} }; struct BinaryWriter { public: explicit BinaryWriter(std::byte* dest, const std::byte* end) : mDest(dest), mEnd(end) { assert(mDest <= mEnd); } BinaryWriter(const BinaryWriter&) = delete; template void operator()(Format&& format, const T& value) { if constexpr (std::is_enum_v) (*this)(std::forward(format), static_cast>(value)); else if constexpr (std::is_arithmetic_v) { if (mEnd - mDest < static_cast(sizeof(T))) throw NotEnoughSpace(); writeValue(value); } else { format(*this, value); } } template auto operator()(Format&& format, const T* data, std::size_t count) { if constexpr (std::is_enum_v) (*this)(std::forward(format), reinterpret_cast*>(data), count); else if constexpr (std::is_arithmetic_v) { const std::size_t size = sizeof(T) * count; if (mEnd - mDest < static_cast(size)) throw NotEnoughSpace(); if constexpr (Misc::IS_LITTLE_ENDIAN) { std::memcpy(mDest, data, size); mDest += size; } else std::for_each(data, data + count, [&] (const T& v) { writeValue(v); }); } else { format(*this, data, count); } } private: std::byte* mDest; const std::byte* const mEnd; template void writeValue(const T& value) noexcept { T coverted = Misc::toLittleEndian(value); std::memcpy(mDest, &coverted, sizeof(T)); mDest += sizeof(T); } }; } #endif openmw-openmw-0.48.0/components/serialization/format.hpp000066400000000000000000000040701445372753700235000ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H #define OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H #include #include #include #include #include #include #include namespace Serialization { enum class Mode { Read, Write, }; template struct IsContiguousContainer : std::false_type {}; template struct IsContiguousContainer> : std::true_type {}; template struct IsContiguousContainer> : std::true_type {}; template inline constexpr bool isContiguousContainer = IsContiguousContainer>::value; template struct Format { template void operator()(Visitor&& visitor, T* data, std::size_t size) const { if constexpr (std::is_arithmetic_v || std::is_enum_v) visitor(self(), data, size); else std::for_each(data, data + size, [&] (auto& v) { visitor(self(), v); }); } template void operator()(Visitor&& visitor, T(& data)[size]) const { self()(std::forward(visitor), data, size); } template auto operator()(Visitor&& visitor, T&& value) const -> std::enable_if_t> { if constexpr (mode == Mode::Write) visitor(self(), static_cast(value.size())); else { static_assert(mode == Mode::Read); std::uint64_t size = 0; visitor(self(), size); value.resize(static_cast(size)); } self()(std::forward(visitor), value.data(), value.size()); } const Derived& self() const { return static_cast(*this); } }; } #endif openmw-openmw-0.48.0/components/serialization/osgyaml.hpp000066400000000000000000000031321445372753700236610ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H #define OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H #include #include #include #include namespace Serialization { template YAML::Node encodeOSGVec(const OSGVec& rhs) { YAML::Node node; for (int i = 0; i < OSGVec::num_components; ++i) node.push_back(rhs[i]); return node; } template bool decodeOSGVec(const YAML::Node& node, OSGVec& rhs) { if (!node.IsSequence() || node.size() != OSGVec::num_components) return false; for (int i = 0; i < OSGVec::num_components; ++i) rhs[i] = node[i].as(); return true; } } namespace YAML { template<> struct convert { static Node encode(const osg::Vec2f& rhs) { return Serialization::encodeOSGVec(rhs); } static bool decode(const Node& node, osg::Vec2f& rhs) { return Serialization::decodeOSGVec(node, rhs); } }; template<> struct convert { static Node encode(const osg::Vec3f& rhs) { return Serialization::encodeOSGVec(rhs); } static bool decode(const Node& node, osg::Vec3f& rhs) { return Serialization::decodeOSGVec(node, rhs); } }; template<> struct convert { static Node encode(const osg::Vec4f& rhs) { return Serialization::encodeOSGVec(rhs); } static bool decode(const Node& node, osg::Vec4f& rhs) { return Serialization::decodeOSGVec(node, rhs); } }; } #endifopenmw-openmw-0.48.0/components/serialization/sizeaccumulator.hpp000066400000000000000000000020211445372753700254140ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H #define OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H #include #include namespace Serialization { class SizeAccumulator { public: SizeAccumulator() = default; SizeAccumulator(const SizeAccumulator&) = delete; std::size_t value() const { return mValue; } template void operator()(Format&& format, const T& value) { if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += sizeof(T); else format(*this, value); } template auto operator()(Format&& format, const T* data, std::size_t count) { if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += count * sizeof(T); else format(*this, data, count); } private: std::size_t mValue = 0; }; } #endif openmw-openmw-0.48.0/components/settings/000077500000000000000000000000001445372753700204615ustar00rootroot00000000000000openmw-openmw-0.48.0/components/settings/categories.hpp000066400000000000000000000013151445372753700233170ustar00rootroot00000000000000#ifndef COMPONENTS_SETTINGS_CATEGORIES_H #define COMPONENTS_SETTINGS_CATEGORIES_H #include #include #include #include #include namespace Settings { struct Less { using is_transparent = void; bool operator()(const std::pair& l, const std::pair& r) const { return l < r; } }; using CategorySetting = std::pair; using CategorySettingVector = std::set; using CategorySettingValueMap = std::map; } #endif // COMPONENTS_SETTINGS_CATEGORIES_H openmw-openmw-0.48.0/components/settings/parser.cpp000066400000000000000000000322601445372753700224640ustar00rootroot00000000000000#include "parser.hpp" #include #include #include #include #include void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64Encoded, bool overrideExisting) { mFile = file; boost::filesystem::ifstream fstream; fstream.open(boost::filesystem::path(file)); auto stream = std::ref(fstream); std::istringstream decodedStream; if (base64Encoded) { std::string base64String(std::istreambuf_iterator(fstream), {}); std::string decodedString; auto result = Base64::Base64::Decode(base64String, decodedString); if (!result.empty()) fail("Could not decode Base64 file: " + result); // Move won't do anything until C++20, but won't hurt to do it anyway. decodedStream.str(std::move(decodedString)); stream = std::ref(decodedStream); } Log(Debug::Info) << "Loading settings file: " << file; std::string currentCategory; mLine = 0; while (!stream.get().eof() && !stream.get().fail()) { ++mLine; std::string line; std::getline( stream.get(), line ); size_t i = 0; if (!skipWhiteSpace(i, line)) continue; if (line[i] == '#') // skip comment continue; if (line[i] == '[') { size_t end = line.find(']', i); if (end == std::string::npos) fail("unterminated category"); currentCategory = line.substr(i+1, end - (i+1)); Misc::StringUtils::trim(currentCategory); i = end+1; } if (!skipWhiteSpace(i, line)) continue; if (currentCategory.empty()) fail("empty category name"); size_t settingEnd = line.find('=', i); if (settingEnd == std::string::npos) fail("unterminated setting name"); std::string setting = line.substr(i, (settingEnd-i)); Misc::StringUtils::trim(setting); size_t valueBegin = settingEnd+1; std::string value = line.substr(valueBegin); Misc::StringUtils::trim(value); if (overrideExisting) settings[std::make_pair(currentCategory, setting)] = value; else if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } } void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings) { using CategorySettingStatusMap = std::map; // No options have been written to the file yet. CategorySettingStatusMap written; for (auto it = settings.begin(); it != settings.end(); ++it) { written[it->first] = false; } // Have we substantively changed the settings file? bool changed = false; // Were there any lines at all in the file? bool existing = false; // Is an entirely blank line queued to be added? bool emptyLineQueued = false; // The category/section we're currently in. std::string currentCategory; // Open the existing settings.cfg file to copy comments. This might not be the same file // as the output file if we're copying the setting from the default settings.cfg for the // first time. A minor change in API to pass the source file might be in order here. boost::filesystem::ifstream istream; boost::filesystem::path ipath(file); istream.open(ipath); // Create a new string stream to write the current settings to. It's likely that the // input file and the output file are the same, so this stream serves as a temporary file // of sorts. The setting files aren't very large so there's no performance issue. std::stringstream ostream; // For every line in the input file... while (!istream.eof() && !istream.fail()) { std::string line; std::getline(istream, line); // The current character position in the line. size_t i = 0; // An empty line was queued. if (emptyLineQueued) { emptyLineQueued = false; // We're still going through the current category, so we should copy it. if (currentCategory.empty() || istream.eof() || line[i] != '[') ostream << std::endl; } // Don't add additional newlines at the end of the file otherwise. if (istream.eof()) continue; // Queue entirely blank lines to add them if desired. if (!skipWhiteSpace(i, line)) { emptyLineQueued = true; continue; } // There were at least some comments in the input file. existing = true; // Copy comments. if (line[i] == '#') { ostream << line << std::endl; continue; } // Category heading. if (line[i] == '[') { size_t end = line.find(']', i); // This should never happen unless the player edited the file while playing. if (end == std::string::npos) { ostream << "# unterminated category: " << line << std::endl; changed = true; continue; } if (!currentCategory.empty()) { // Ensure that all options in the current category have been written. for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " << mit->first.second << " = " << settings.at(mit->first); ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; mit->second = true; changed = true; } } // Add an empty line after the last option in a category. ostream << std::endl; } // Update the current category. currentCategory = line.substr(i+1, end - (i+1)); Misc::StringUtils::trim(currentCategory); // Write the (new) current category to the file. ostream << "[" << currentCategory << "]" << std::endl; // Log(Debug::Verbose) << "Wrote category: " << currentCategory; // A setting can apparently follow the category on an input line. That's rather // inconvenient, since it makes it more likely to have duplicative sections, // which our algorithm doesn't like. Do the best we can. i = end+1; } // Truncate trailing whitespace, since we're changing the file anayway. if (!skipWhiteSpace(i, line)) continue; // If we've found settings before the first category, something's wrong. This // should never happen unless the player edited the file while playing, since // the loadSettingsFile() logic rejects it. if (currentCategory.empty()) { ostream << "# empty category name: " << line << std::endl; changed = true; continue; } // Which setting was at this location in the input file? size_t settingEnd = line.find('=', i); // This should never happen unless the player edited the file while playing. if (settingEnd == std::string::npos) { ostream << "# unterminated setting name: " << line << std::endl; changed = true; continue; } std::string setting = line.substr(i, (settingEnd-i)); Misc::StringUtils::trim(setting); // Get the existing value so we can see if we've changed it. size_t valueBegin = settingEnd+1; std::string value = line.substr(valueBegin); Misc::StringUtils::trim(value); // Construct the setting map key to determine whether the setting has already been // written to the file. CategorySetting key = std::make_pair(currentCategory, setting); CategorySettingStatusMap::iterator finder = written.find(key); // Settings not in the written map are definitely invalid. Currently, this can only // happen if the player edited the file while playing, because loadSettingsFile() // will accept anything and pass it along in the map, but in the future, we might // want to handle invalid settings more gracefully here. if (finder == written.end()) { ostream << "# invalid setting: " << line << std::endl; changed = true; continue; } // Write the current value of the setting to the file. The key must exist in the // settings map because of how written was initialized and finder != end(). ostream << setting << " = " << settings.at(key) << std::endl; // Mark that setting as written. finder->second = true; // Did we really change it? if (value != settings.at(key)) { Log(Debug::Verbose) << "Changed setting: [" << currentCategory << "] " << setting << " = " << settings.at(key); changed = true; } // No need to write the current line, because we just emitted a replacement. // Curiously, it appears that comments at the ends of lines with settings are not // allowed, and the comment becomes part of the value. Was that intended? } // We're done with the input stream file. istream.close(); // Ensure that all options in the current category have been written. We must complete // the current category at the end of the file before moving on to any new categories. for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " << mit->first.second << " = " << settings.at(mit->first); ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; mit->second = true; changed = true; } } // If there was absolutely nothing in the file (or more likely the file didn't // exist), start the newly created file with a helpful comment. if (!existing) { ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; ostream << "# to its default, simply remove it from this file. For available" << std::endl; ostream << "# settings, see the file 'files/settings-default.cfg' in our source repo or the documentation at:" << std::endl; ostream << "#" << std::endl; ostream << "# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl; } // We still have one more thing to do before we're completely done writing the file. // It's possible that there are entirely new categories, or that the input file had // disappeared completely, so we need ensure that all settings are written to the file // regardless of those issues. for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { // If the setting hasn't been written yet. if (mit->second == false) { // If the catgory has changed, write a new category header. if (mit->first.first != currentCategory) { currentCategory = mit->first.first; Log(Debug::Verbose) << "Created new setting section: " << mit->first.first; ostream << std::endl; ostream << "[" << currentCategory << "]" << std::endl; } Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " << mit->first.second << " = " << settings.at(mit->first); // Then write the setting. No need to mark it as written because we're done. ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; changed = true; } } // Now install the newly written file in the requested place. if (changed) { Log(Debug::Info) << "Updating settings file: " << ipath; boost::filesystem::ofstream ofstream; ofstream.open(ipath); ofstream << ostream.rdbuf(); ofstream.close(); } } bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) { while (i < str.size() && std::isspace(str[i], std::locale::classic())) { ++i; } return i < str.size(); } [[noreturn]] void Settings::SettingsFileParser::fail(const std::string& message) { std::stringstream error; error << "Error on line " << mLine << " in " << mFile << ":\n" << message; throw std::runtime_error(error.str()); } openmw-openmw-0.48.0/components/settings/parser.hpp000066400000000000000000000016111445372753700224650ustar00rootroot00000000000000#ifndef COMPONENTS_SETTINGS_PARSER_H #define COMPONENTS_SETTINGS_PARSER_H #include "categories.hpp" #include namespace Settings { class SettingsFileParser { public: void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64encoded = false, bool overrideExisting = false); void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings); private: /// Increment i until it longer points to a whitespace character /// in the string or has reached the end of the string. /// @return false if we have reached the end of the string bool skipWhiteSpace(size_t& i, std::string& str); [[noreturn]] void fail(const std::string& message); std::string mFile; int mLine = 0; }; } #endif // _COMPONENTS_SETTINGS_PARSER_H openmw-openmw-0.48.0/components/settings/settings.cpp000066400000000000000000000210001445372753700230160ustar00rootroot00000000000000#include "settings.hpp" #include "parser.hpp" #include #include #include namespace Settings { CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); void Manager::clear() { mDefaultSettings.clear(); mUserSettings.clear(); mChangedSettings.clear(); } std::string Manager::load(const Files::ConfigurationManager& cfgMgr, bool loadEditorSettings) { SettingsFileParser parser; const std::vector& paths = cfgMgr.getActiveConfigPaths(); if (paths.empty()) throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); // Create file name strings for either the engine or the editor. std::string defaultSettingsFile; std::string userSettingsFile; if (!loadEditorSettings) { defaultSettingsFile = "defaults.bin"; userSettingsFile = "settings.cfg"; } else { defaultSettingsFile = "defaults-cs.bin"; userSettingsFile = "openmw-cs.cfg"; } // Create the settings manager and load default settings file. const std::string defaultsBin = (paths.front() / defaultSettingsFile).string(); if (!boost::filesystem::exists(defaultsBin)) throw std::runtime_error ("No default settings file found! Make sure the file \"" + defaultSettingsFile + "\" was properly installed."); parser.loadSettingsFile(defaultsBin, mDefaultSettings, true, false); // Load "settings.cfg" or "openmw-cs.cfg" from every config dir except the last one as additional default settings. for (int i = 0; i < static_cast(paths.size()) - 1; ++i) { const std::string additionalDefaults = (paths[i] / userSettingsFile).string(); if (boost::filesystem::exists(additionalDefaults)) parser.loadSettingsFile(additionalDefaults, mDefaultSettings, false, true); } // Load "settings.cfg" or "openmw-cs.cfg" from the last config dir as user settings. This path will be used to save modified settings. std::string settingspath = (paths.back() / userSettingsFile).string(); if (boost::filesystem::exists(settingspath)) parser.loadSettingsFile(settingspath, mUserSettings, false, false); return settingspath; } void Manager::saveUser(const std::string &file) { SettingsFileParser parser; parser.saveSettingsFile(file, mUserSettings); } std::string Manager::getString(std::string_view setting, std::string_view category) { const auto key = std::make_pair(category, setting); CategorySettingValueMap::iterator it = mUserSettings.find(key); if (it != mUserSettings.end()) return it->second; it = mDefaultSettings.find(key); if (it != mDefaultSettings.end()) return it->second; std::string error("Trying to retrieve a non-existing setting: "); error += setting; error += ".\nMake sure the defaults.bin file was properly installed."; throw std::runtime_error(error); } std::vector Manager::getStringArray(std::string_view setting, std::string_view category) { // TODO: it is unclear how to handle empty value - // there is no difference between empty serialized array // and a serialized array which has one empty value std::vector values; const std::string& value = getString(setting, category); if (value.empty()) return values; Misc::StringUtils::split(value, values, ","); for (auto& item : values) Misc::StringUtils::trim(item); return values; } float Manager::getFloat(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); float number = 0.f; stream >> number; return number; } double Manager::getDouble(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); double number = 0.0; stream >> number; return number; } int Manager::getInt(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); int number = 0; stream >> number; return number; } std::int64_t Manager::getInt64(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); std::int64_t number = 0; stream >> number; return number; } bool Manager::getBool(std::string_view setting, std::string_view category) { const std::string& string = getString(setting, category); return Misc::StringUtils::ciEqual(string, "true"); } osg::Vec2f Manager::getVector2(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); float x, y; stream >> x >> y; if (stream.fail()) throw std::runtime_error(std::string("Can't parse 2d vector: " + value)); return {x, y}; } osg::Vec3f Manager::getVector3(std::string_view setting, std::string_view category) { const std::string& value = getString(setting, category); std::stringstream stream(value); float x, y, z; stream >> x >> y >> z; if (stream.fail()) throw std::runtime_error(std::string("Can't parse 3d vector: " + value)); return {x, y, z}; } void Manager::setString(std::string_view setting, std::string_view category, const std::string &value) { auto found = mUserSettings.find(std::make_pair(category, setting)); if (found != mUserSettings.end()) { if (found->second == value) return; } CategorySettingValueMap::key_type key(category, setting); mUserSettings[key] = value; mChangedSettings.insert(std::move(key)); } void Manager::setStringArray(std::string_view setting, std::string_view category, const std::vector &value) { std::stringstream stream; // TODO: escape delimeters, new line characters, etc. for (size_t i = 0; i < value.size(); ++i) { std::string item = value[i]; Misc::StringUtils::trim(item); stream << item; if (i < value.size() - 1) stream << ","; } setString(setting, category, stream.str()); } void Manager::setInt(std::string_view setting, std::string_view category, const int value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setInt64(std::string_view setting, std::string_view category, const std::int64_t value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setFloat (std::string_view setting, std::string_view category, const float value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setDouble (std::string_view setting, std::string_view category, const double value) { std::ostringstream stream; stream << value; setString(setting, category, stream.str()); } void Manager::setBool(std::string_view setting, std::string_view category, const bool value) { setString(setting, category, value ? "true" : "false"); } void Manager::setVector2 (std::string_view setting, std::string_view category, const osg::Vec2f value) { std::ostringstream stream; stream << value.x() << " " << value.y(); setString(setting, category, stream.str()); } void Manager::setVector3 (std::string_view setting, std::string_view category, const osg::Vec3f value) { std::ostringstream stream; stream << value.x() << ' ' << value.y() << ' ' << value.z(); setString(setting, category, stream.str()); } CategorySettingVector Manager::getPendingChanges() { return mChangedSettings; } CategorySettingVector Manager::getPendingChanges(const CategorySettingVector& filter) { CategorySettingVector intersection; std::set_intersection(mChangedSettings.begin(), mChangedSettings.end(), filter.begin(), filter.end(), std::inserter(intersection, intersection.begin())); return intersection; } void Manager::resetPendingChanges() { mChangedSettings.clear(); } void Manager::resetPendingChanges(const CategorySettingVector& filter) { for (const auto& key : filter) { mChangedSettings.erase(key); } } } openmw-openmw-0.48.0/components/settings/settings.hpp000066400000000000000000000066261445372753700230440ustar00rootroot00000000000000#ifndef COMPONENTS_SETTINGS_H #define COMPONENTS_SETTINGS_H #include "categories.hpp" #include #include #include #include #include #include #include #include namespace Files { struct ConfigurationManager; } namespace Settings { enum class WindowMode { Fullscreen = 0, WindowedFullscreen, Windowed }; /// /// \brief Settings management (can change during runtime) /// class Manager { public: static CategorySettingValueMap mDefaultSettings; static CategorySettingValueMap mUserSettings; static CategorySettingVector mChangedSettings; ///< tracks all the settings that were changed since the last apply() call static void clear(); ///< clears all settings and default settings static std::string load(const Files::ConfigurationManager& cfgMgr, bool loadEditorSettings = false); ///< load settings from all active config dirs. Returns the path of the last loaded file. static void saveUser (const std::string& file); ///< save user settings to file static void resetPendingChanges(); ///< resets the list of all pending changes static void resetPendingChanges(const CategorySettingVector& filter); ///< resets only the pending changes listed in the filter static CategorySettingVector getPendingChanges(); ///< returns the list of changed settings static CategorySettingVector getPendingChanges(const CategorySettingVector& filter); ///< returns the list of changed settings intersecting with the filter static int getInt(std::string_view setting, std::string_view category); static std::int64_t getInt64(std::string_view setting, std::string_view category); static float getFloat(std::string_view setting, std::string_view category); static double getDouble(std::string_view setting, std::string_view category); static std::string getString(std::string_view setting, std::string_view category); static std::vector getStringArray(std::string_view setting, std::string_view category); static bool getBool(std::string_view setting, std::string_view category); static osg::Vec2f getVector2(std::string_view setting, std::string_view category); static osg::Vec3f getVector3(std::string_view setting, std::string_view category); static void setInt(std::string_view setting, std::string_view category, int value); static void setInt64(std::string_view setting, std::string_view category, std::int64_t value); static void setFloat(std::string_view setting, std::string_view category, float value); static void setDouble(std::string_view setting, std::string_view category, double value); static void setString(std::string_view setting, std::string_view category, const std::string& value); static void setStringArray(std::string_view setting, std::string_view category, const std::vector &value); static void setBool(std::string_view setting, std::string_view category, bool value); static void setVector2(std::string_view setting, std::string_view category, osg::Vec2f value); static void setVector3(std::string_view setting, std::string_view category, osg::Vec3f value); }; } #endif // COMPONENTS_SETTINGS_H openmw-openmw-0.48.0/components/settings/shadermanager.hpp000066400000000000000000000111141445372753700237710ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H #define OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H #include #include #include #include #include #include #include #include #include #include #include #include namespace Settings { /* * Manages the shader.yaml file which is auto-generated and lives next to settings.cfg. * This YAML file is simply a mapping of technique name to a list of uniforms and their values. * Currently only vec2f, vec3f, vec4f, int, and float uniforms are supported. * * config: * TECHNIQUE: * MY_FLOAT: 10.34 * MY_VEC2: [0.23, 0.34] * TECHNIQUE2: * MY_VEC3: [0.22, 0.33, 0.20] */ class ShaderManager { public: enum class Mode { Normal, Debug }; ShaderManager() = default; ShaderManager(ShaderManager const&) = delete; void operator=(ShaderManager const&) = delete; static ShaderManager& get() { static ShaderManager instance; return instance; } Mode getMode() { return mMode; } void setMode(Mode mode) { mMode = mode; } const YAML::Node& getRoot() { return mData; } template bool setValue(const std::string& tname, const std::string& uname, const T& value) { if (mData.IsNull()) { Log(Debug::Warning) << "Failed setting " << tname << ", " << uname << " : shader settings failed to load"; return false; } mData["config"][tname][uname] = value; return true; } template std::optional getValue(const std::string& tname, const std::string& uname) { if (mData.IsNull()) { Log(Debug::Warning) << "Failed getting " << tname << ", " << uname << " : shader settings failed to load"; return std::nullopt; } try { auto value = mData["config"][tname][uname]; if (!value) return std::nullopt; return value.as(); } catch(const YAML::BadConversion&) { Log(Debug::Warning) << "Failed retrieving " << tname << ", " << uname << " : mismatched types in config file."; } return std::nullopt; } bool load(const std::string& path) { mData = YAML::Null; mPath = boost::filesystem::path(path); Log(Debug::Info) << "Loading shader settings file: " << mPath; if (!boost::filesystem::exists(mPath)) { boost::filesystem::ofstream fout(mPath); if (!fout) { Log(Debug::Error) << "Failed creating shader settings file: " << mPath; return false; } } try { boost::filesystem::ifstream fin(mPath); mData = YAML::Load(fin); mData.SetStyle(YAML::EmitterStyle::Block); if (!mData["config"]) mData["config"] = YAML::Node(); return true; } catch(const YAML::Exception& e) { Log(Debug::Error) << "Shader settings failed to load, " << e.msg; } return false; } bool save() { if (mData.IsNull()) { Log(Debug::Error) << "Shader settings failed to load, settings will not be saved: " << mPath; return false; } Log(Debug::Info) << "Saving shader settings file: " << mPath; YAML::Emitter out; out.SetMapFormat(YAML::Block); out << mData; boost::filesystem::ofstream fout(mPath); fout << out.c_str(); if (!fout) { Log(Debug::Error) << "Failed saving shader settings file: " << mPath; return false; } return true; } private: boost::filesystem::path mPath; YAML::Node mData; Mode mMode = Mode::Normal; }; } #endif openmw-openmw-0.48.0/components/shader/000077500000000000000000000000001445372753700200675ustar00rootroot00000000000000openmw-openmw-0.48.0/components/shader/removedalphafunc.cpp000066400000000000000000000012011445372753700241100ustar00rootroot00000000000000#include "removedalphafunc.hpp" #include #include namespace Shader { std::array, GL_ALWAYS - GL_NEVER + 1> RemovedAlphaFunc::sInstances{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) { assert(func >= GL_NEVER && func <= GL_ALWAYS); if (!sInstances[func - GL_NEVER]) sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); return sInstances[func - GL_NEVER]; } } openmw-openmw-0.48.0/components/shader/removedalphafunc.hpp000066400000000000000000000023701445372753700241250ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H #define OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H #include #include namespace Shader { // State attribute used when shader visitor replaces the deprecated alpha function with a shader // Prevents redundant glAlphaFunc calls and lets the shadowsbin know the stateset had alpha testing class RemovedAlphaFunc : public osg::AlphaFunc { public: // Get a singleton-like instance with the right func (but a default threshold) static osg::ref_ptr getInstance(GLenum func); RemovedAlphaFunc() : osg::AlphaFunc() {} RemovedAlphaFunc(ComparisonFunction func, float ref) : osg::AlphaFunc(func, ref) {} RemovedAlphaFunc(const RemovedAlphaFunc& raf, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) : osg::AlphaFunc(raf, copyop) {} META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); void apply(osg::State& state) const override {} protected: virtual ~RemovedAlphaFunc() = default; static std::array, GL_ALWAYS - GL_NEVER + 1> sInstances; }; } #endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H openmw-openmw-0.48.0/components/shader/shadermanager.cpp000066400000000000000000000671541445372753700234110ustar00rootroot00000000000000#include "shadermanager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Shader { ShaderManager::ShaderManager() { mHotReloadManager = std::make_unique(); } ShaderManager::~ShaderManager() = default; void ShaderManager::setShaderPath(const std::string &path) { mPath = path; } bool addLineDirectivesAfterConditionalBlocks(std::string& source) { for (size_t position = 0; position < source.length(); ) { size_t foundPos = source.find("#endif", position); foundPos = std::min(foundPos, source.find("#elif", position)); foundPos = std::min(foundPos, source.find("#else", position)); if (foundPos == std::string::npos) break; foundPos = source.find_first_of("\n\r", foundPos); foundPos = source.find_first_not_of("\n\r", foundPos); if (foundPos == std::string::npos) break; size_t lineDirectivePosition = source.rfind("#line", foundPos); int lineNumber; if (lineDirectivePosition != std::string::npos) { size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); lineNumber = std::stoi(lineNumberString) - 1; } else { lineDirectivePosition = 0; lineNumber = 1; } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); source.replace(foundPos, 0, "#line " + std::to_string(lineNumber) + "\n"); position = foundPos; } return true; } // Recursively replaces include statements with the actual source of the included files. // Adjusts #line statements accordingly and detects cyclic includes. // cycleIncludeChecker is the set of files that include this file directly or indirectly, and is intentionally not a reference to allow automatic cleanup. static bool parseIncludes(const boost::filesystem::path& shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set cycleIncludeChecker,std::set& includedFiles) { includedFiles.insert(shaderPath / fileName); // An include is cyclic if it is being included by itself if (cycleIncludeChecker.insert(shaderPath/fileName).second == false) { Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes"; return false; } Misc::StringUtils::replaceAll(source, "\r\n", "\n"); size_t foundPos = 0; while ((foundPos = source.find("#include")) != std::string::npos) { size_t start = source.find('"', foundPos); if (start == std::string::npos || start == source.size() - 1) { Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; return false; } size_t end = source.find('"', start + 1); if (end == std::string::npos) { Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; return false; } std::string includeFilename = source.substr(start + 1, end - (start + 1)); boost::filesystem::path includePath = shaderPath / includeFilename; // Determine the line number that will be used for the #line directive following the included source size_t lineDirectivePosition = source.rfind("#line", foundPos); int lineNumber; if (lineDirectivePosition != std::string::npos) { size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); lineNumber = std::stoi(lineNumberString) - 1; } else { lineDirectivePosition = 0; lineNumber = 0; } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); // Include the file recursively boost::filesystem::ifstream includeFstream; includeFstream.open(includePath); if (includeFstream.fail()) { Log(Debug::Error) << "Shader " << fileName << " error: Failed to open include " << includePath.string(); return false; } int includedFileNumber = fileNumber++; std::stringstream buffer; buffer << includeFstream.rdbuf(); std::string stringRepresentation = buffer.str(); if (!addLineDirectivesAfterConditionalBlocks(stringRepresentation) || !parseIncludes(shaderPath, stringRepresentation, includeFilename, fileNumber, cycleIncludeChecker, includedFiles)) { Log(Debug::Error) << "In file included from " << fileName << "." << lineNumber; return false; } std::stringstream toInsert; toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; source.replace(foundPos, (end - foundPos + 1), toInsert.str()); } return true; } bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos) { size_t iterNameStart = foundPos + strlen("$foreach") + 1; size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); if (iterNameEnd == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); size_t listStart = iterNameEnd + 1; size_t listEnd = source.find_first_of("\n\r", listStart); if (listEnd == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string list = source.substr(listStart, listEnd - listStart); std::vector listElements; if (list != "") Misc::StringUtils::split(list, listElements, ","); size_t contentStart = source.find_first_not_of("\n\r", listEnd); size_t contentEnd = source.find("$endforeach", contentStart); if (contentEnd == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string content = source.substr(contentStart, contentEnd - contentStart); size_t overallEnd = contentEnd + std::string("$endforeach").length(); size_t lineDirectivePosition = source.rfind("#line", overallEnd); int lineNumber; if (lineDirectivePosition != std::string::npos) { size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); lineNumber = std::stoi(lineNumberString); } else { lineDirectivePosition = 0; lineNumber = 2; } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); std::string replacement; for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) { std::string contentInstance = content; size_t foundIterator; while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) contentInstance.replace(foundIterator, iteratorName.length(), *element); replacement += contentInstance; } replacement += "\n#line " + std::to_string(lineNumber); source.replace(foundPos, overallEnd - foundPos, replacement); return true; } bool parseLinkDirective(std::string& source, std::string& linkTarget, const std::string& templateName, size_t foundPos) { size_t endPos = foundPos + 5; size_t lineEnd = source.find_first_of('\n', endPos); // If lineEnd = npos, this is the last line, so no need to check std::string linkStatement = source.substr(endPos, lineEnd - endPos); std::regex linkRegex( R"r(\s*"([^"]+)"\s*)r" // Find any quoted string as the link name -> match[1] R"r((if\s+)r" // Begin optional condition -> match[2] R"r((!)?\s*)r" // Optional ! -> match[3] R"r(([_a-zA-Z0-9]+)?)r" // The condition -> match[4] R"r()?\s*)r" // End optional condition -> match[2] ); std::smatch linkMatch; bool hasCondition = false; std::string linkConditionExpression; if (std::regex_match(linkStatement, linkMatch, linkRegex)) { linkTarget = linkMatch[1].str(); hasCondition = !linkMatch[2].str().empty(); linkConditionExpression = linkMatch[4].str(); } else { Log(Debug::Error) << "Shader " << templateName << " error: Expected a shader filename to link"; return false; } if (linkTarget.empty()) { Log(Debug::Error) << "Shader " << templateName << " error: Empty link name"; return false; } if (hasCondition) { bool condition = !(linkConditionExpression.empty() || linkConditionExpression == "0"); if (linkMatch[3].str() == "!") condition = !condition; if (!condition) linkTarget.clear(); } source.replace(foundPos, lineEnd - foundPos, ""); return true; } bool parseDirectives(std::string& source, std::vector& linkedShaderTemplateNames, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName) { const char escapeCharacter = '$'; size_t foundPos = 0; while ((foundPos = source.find(escapeCharacter, foundPos)) != std::string::npos) { size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string directive = source.substr(foundPos + 1, endPos - (foundPos + 1)); if (directive == "foreach") { if (!parseForeachDirective(source, templateName, foundPos)) return false; } else if (directive == "link") { std::string linkTarget; if (!parseLinkDirective(source, linkTarget, templateName, foundPos)) return false; if (!linkTarget.empty() && linkTarget != templateName) linkedShaderTemplateNames.push_back(linkTarget); } else { Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << directive; return false; } } return true; } bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName) { const char escapeCharacter = '@'; size_t foundPos = 0; std::vector forIterators; while ((foundPos = source.find(escapeCharacter)) != std::string::npos) { size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); if (define == "foreach") { source.replace(foundPos, 1, "$"); size_t iterNameStart = endPos + 1; size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); if (iterNameEnd == std::string::npos) { Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); } else if (define == "endforeach") { source.replace(foundPos, 1, "$"); if (forIterators.empty()) { Log(Debug::Error) << "Shader " << templateName << " error: endforeach without foreach"; return false; } else forIterators.pop_back(); } else if (define == "link") { source.replace(foundPos, 1, "$"); } else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) { source.replace(foundPos, 1, "$"); } else if (defineFound != defines.end()) { source.replace(foundPos, endPos - foundPos, defineFound->second); } else if (globalDefineFound != globalDefines.end()) { source.replace(foundPos, endPos - foundPos, globalDefineFound->second); } else { Log(Debug::Error) << "Shader " << templateName << " error: Undefined " << define; return false; } } return true; } struct HotReloadManager { using KeysHolder = std::set; std::unordered_map mShaderFiles; std::unordered_map> templateIncludedFiles; std::chrono::time_point mLastAutoRecompileTime; bool mHotReloadEnabled; bool mTriggerReload; HotReloadManager() { mTriggerReload = false; mHotReloadEnabled = false; mLastAutoRecompileTime = std::chrono::system_clock::now(); } void addShaderFiles(const std::string& templateName,const ShaderManager::DefineMap& defines ) { const std::set& shaderFiles = templateIncludedFiles[templateName]; for (const boost::filesystem::path& file : shaderFiles) { mShaderFiles[file.string()].insert(std::make_pair(templateName, defines)); } } void update(ShaderManager& Manager,osgViewer::Viewer& viewer) { auto timeSinceLastCheckMillis = std::chrono::duration_cast(std::chrono::system_clock::now() - mLastAutoRecompileTime); if ((mHotReloadEnabled && timeSinceLastCheckMillis.count() > 200) || mTriggerReload == true) { reloadTouchedShaders(Manager, viewer); } mTriggerReload = false; } void reloadTouchedShaders(ShaderManager& Manager, osgViewer::Viewer& viewer) { bool threadsRunningToStop = false; for (auto& [pathShaderToTest, shaderKeys]: mShaderFiles) { auto write_time = std::chrono::system_clock::from_time_t(boost::filesystem::last_write_time(pathShaderToTest)); if (write_time.time_since_epoch() > mLastAutoRecompileTime.time_since_epoch()) { if (!threadsRunningToStop) { threadsRunningToStop = viewer.areThreadsRunning(); if (threadsRunningToStop) viewer.stopThreading(); } for (const auto& [templateName, shaderDefines]: shaderKeys) { ShaderManager::ShaderMap::iterator shaderIt = Manager.mShaders.find(std::make_pair(templateName, shaderDefines)); ShaderManager::TemplateMap::iterator templateIt = Manager.mShaderTemplates.find(templateName); //Can't be Null, if we're here it means the template was added std::string& shaderSource = templateIt->second; std::set insertedPaths; boost::filesystem::path path = (boost::filesystem::path(Manager.mPath) / templateName); boost::filesystem::ifstream stream; stream.open(path); if (stream.fail()) { Log(Debug::Error) << "Failed to open " << path.string(); } std::stringstream buffer; buffer << stream.rdbuf(); // parse includes int fileNumber = 1; std::string source = buffer.str(); if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(boost::filesystem::path(Manager.mPath), source, templateName, fileNumber, {}, insertedPaths)) { break; } shaderSource = source; std::vector linkedShaderNames; if (!Manager.createSourceFromTemplate(shaderSource, linkedShaderNames, templateName, shaderDefines)) { break; } shaderIt->second->setShaderSource(shaderSource); } } } if (threadsRunningToStop) viewer.startThreading(); mLastAutoRecompileTime = std::chrono::system_clock::now(); } }; osg::ref_ptr ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) { std::unique_lock lock(mMutex); // read the template if we haven't already TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); std::set insertedPaths; if (templateIt == mShaderTemplates.end()) { boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName); boost::filesystem::ifstream stream; stream.open(path); if (stream.fail()) { Log(Debug::Error) << "Failed to open " << path.string(); return nullptr; } std::stringstream buffer; buffer << stream.rdbuf(); // parse includes int fileNumber = 1; std::string source = buffer.str(); if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {}, insertedPaths)) return nullptr; mHotReloadManager->templateIncludedFiles[templateName] = insertedPaths; templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; } ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines)); if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; std::vector linkedShaderNames; if (!createSourceFromTemplate(shaderSource, linkedShaderNames, templateName, defines)) { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(templateName, defines), nullptr)); return nullptr; } osg::ref_ptr shader (new osg::Shader(shaderType)); shader->setShaderSource(shaderSource); // Assign a unique prefix to allow the SharedStateManager to compare shaders efficiently. // Append shader source filename for debugging. static unsigned int counter = 0; shader->setName(Misc::StringUtils::format("%u %s", counter++, templateName)); mHotReloadManager->addShaderFiles(templateName, defines); lock.unlock(); getLinkedShaders(shader, linkedShaderNames, defines); lock.lock(); shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; } return shaderIt->second; } osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate) { std::lock_guard lock(mMutex); ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); if (found == mPrograms.end()) { if (!programTemplate) programTemplate = mProgramTemplate; osg::ref_ptr program = programTemplate ? cloneProgram(programTemplate) : osg::ref_ptr(new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); addLinkedShaders(vertexShader, program); addLinkedShaders(fragmentShader, program); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; } osg::ref_ptr ShaderManager::cloneProgram(const osg::Program* src) { osg::ref_ptr program = static_cast(src->clone(osg::CopyOp::SHALLOW_COPY)); for (auto& [name, idx] : src->getUniformBlockBindingList()) program->addBindUniformBlock(name, idx); return program; } ShaderManager::DefineMap ShaderManager::getGlobalDefines() { return DefineMap(mGlobalDefines); } void ShaderManager::setGlobalDefines(DefineMap & globalDefines) { mGlobalDefines = globalDefines; for (const auto& [key, shader]: mShaders) { std::string templateId = key.first; ShaderManager::DefineMap defines = key.second; if (shader == nullptr) // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. continue; std::string shaderSource = mShaderTemplates[templateId]; std::vector linkedShaderNames; if (!createSourceFromTemplate(shaderSource, linkedShaderNames, templateId, defines)) // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. continue; shader->setShaderSource(shaderSource); getLinkedShaders(shader, linkedShaderNames, defines); } } void ShaderManager::releaseGLObjects(osg::State *state) { std::lock_guard lock(mMutex); for (const auto& [_, shader] : mShaders) { if (shader != nullptr) shader->releaseGLObjects(state); } for (const auto& [_, program] : mPrograms) program->releaseGLObjects(state); } bool ShaderManager::createSourceFromTemplate(std::string& source, std::vector& linkedShaderTemplateNames, const std::string& templateName, const ShaderManager::DefineMap& defines) { if (!parseDefines(source, defines, mGlobalDefines, templateName)) return false; if (!parseDirectives(source, linkedShaderTemplateNames, defines, mGlobalDefines, templateName)) return false; return true; } void ShaderManager::getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines) { mLinkedShaders.erase(shader); if (linkedShaderNames.empty()) return; for (auto& linkedShaderName : linkedShaderNames) { auto linkedShader = getShader(linkedShaderName, defines, shader->getType()); if (linkedShader) mLinkedShaders[shader].emplace_back(linkedShader); } } void ShaderManager::addLinkedShaders(osg::ref_ptr shader, osg::ref_ptr program) { auto linkedIt = mLinkedShaders.find(shader); if (linkedIt != mLinkedShaders.end()) for (const auto& linkedShader : linkedIt->second) program->addShader(linkedShader); } int ShaderManager::reserveGlobalTextureUnits(Slot slot) { int unit = mReservedTextureUnitsBySlot[static_cast(slot)]; if (unit >= 0) return unit; { // Texture units from `8 - numberOfShadowMaps` to `8` are used for shadows, so we skip them here. // TODO: Maybe instead of fixed texture units use `reserveGlobalTextureUnits` for shadows as well. static const int numberOfShadowMaps = Settings::Manager::getBool("enable shadows", "Shadows") ? std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8) : 0; if (getAvailableTextureUnits() >= 8 && getAvailableTextureUnits() - 1 < 8) mReservedTextureUnits = mMaxTextureUnits - (8 - numberOfShadowMaps); } if (getAvailableTextureUnits() < 2) throw std::runtime_error("Can't reserve texture unit; no available units"); mReservedTextureUnits++; unit = mMaxTextureUnits - mReservedTextureUnits; mReservedTextureUnitsBySlot[static_cast(slot)] = unit; return unit; } void ShaderManager::update(osgViewer::Viewer& viewer) { mHotReloadManager->update(*this, viewer); } void ShaderManager::setHotReloadEnabled(bool value) { mHotReloadManager->mHotReloadEnabled = value; } void ShaderManager::triggerShaderReload() { mHotReloadManager->mTriggerReload = true; } } openmw-openmw-0.48.0/components/shader/shadermanager.hpp000066400000000000000000000117471445372753700234130ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H #define OPENMW_COMPONENTS_SHADERMANAGER_H #include #include #include #include #include #include #include #include #include namespace osgViewer { class Viewer; } namespace Shader { struct HotReloadManager; /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. /// @par Shader templates can get the value of a define with the syntax @define. class ShaderManager { public: friend HotReloadManager; ShaderManager(); ~ShaderManager(); void setShaderPath(const std::string& path); typedef std::map DefineMap; /// Create or retrieve a shader instance. /// @param shaderTemplate The filename of the shader template. /// @param defines Define values that can be retrieved by the shader template. /// @param shaderType The type of shader (usually vertex or fragment shader). /// @note May return nullptr on failure. /// @note Thread safe. osg::ref_ptr getShader(const std::string& templateName, const DefineMap& defines, osg::Shader::Type shaderType); osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate=nullptr); const osg::Program* getProgramTemplate() const { return mProgramTemplate; } void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; } /// Clone an osg::Program including bindUniformBlocks that osg::Program::clone does not copy for some reason. static osg::ref_ptr cloneProgram(const osg::Program*); /// Get (a copy of) the DefineMap used to construct all shaders DefineMap getGlobalDefines(); /// Set the DefineMap used to construct all shaders /// @param defines The DefineMap to use /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. void setGlobalDefines(DefineMap & globalDefines); void releaseGLObjects(osg::State* state); bool createSourceFromTemplate(std::string& source, std::vector& linkedShaderTemplateNames, const std::string& templateName, const ShaderManager::DefineMap& defines); void setMaxTextureUnits(int maxTextureUnits) { mMaxTextureUnits = maxTextureUnits; } int getMaxTextureUnits() const { return mMaxTextureUnits; } int getAvailableTextureUnits() const { return mMaxTextureUnits - mReservedTextureUnits; } enum class Slot { OpaqueDepthTexture, SkyTexture, }; int reserveGlobalTextureUnits(Slot slot); void update(osgViewer::Viewer& viewer); void setHotReloadEnabled(bool value); void triggerShaderReload(); private: void getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines); void addLinkedShaders(osg::ref_ptr shader, osg::ref_ptr program); std::string mPath; DefineMap mGlobalDefines; // typedef std::map TemplateMap; TemplateMap mShaderTemplates; typedef std::pair MapKey; typedef std::map > ShaderMap; ShaderMap mShaders; typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; typedef std::vector > ShaderList; typedef std::map, ShaderList> LinkedShadersMap; LinkedShadersMap mLinkedShaders; std::mutex mMutex; osg::ref_ptr mProgramTemplate; int mMaxTextureUnits = 0; int mReservedTextureUnits = 0; std::unique_ptr mHotReloadManager; std::array mReservedTextureUnitsBySlot = {-1, -1}; }; bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos); bool parseLinkDirective(std::string& source, std::string& linkTarget, const std::string& templateName, size_t foundPos); bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName); bool parseDirectives(std::string& source, std::vector& linkedShaderTemplateNames, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName); } #endif openmw-openmw-0.48.0/components/shader/shadervisitor.cpp000066400000000000000000001276021445372753700234710ustar00rootroot00000000000000#include "shadervisitor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader { /** * Miniature version of osg::StateSet used to track state added by the shader visitor which should be ignored when * it's applied a second time, and removed when shaders are removed. * Actual StateAttributes aren't kept as they're recoverable from the StateSet this is attached to - we just want * the TypeMemberPair as that uniquely identifies which of those StateAttributes it was we're tracking. * Not all StateSet features have been added yet - we implement an equivalently-named method to each of the StateSet * methods called in createProgram, and implement new ones as they're needed. * When expanding tracking to cover new things, ensure they're accounted for in ensureFFP. */ class AddedState : public osg::Object { public: AddedState() = default; AddedState(const AddedState& rhs, const osg::CopyOp& copyOp) : osg::Object(rhs, copyOp) , mUniforms(rhs.mUniforms) , mModes(rhs.mModes) , mAttributes(rhs.mAttributes) , mTextureModes(rhs.mTextureModes) { } void addUniform(const std::string& name) { mUniforms.emplace(name); } void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } void setAttribute(const osg::StateAttribute* attribute) { mAttributes.emplace(attribute->getTypeMemberPair()); } template void setAttribute(osg::ref_ptr attribute) { setAttribute(attribute.get()); } void setAttributeAndModes(const osg::StateAttribute* attribute) { setAttribute(attribute); InterrogateModesHelper helper(this); attribute->getModeUsage(helper); } template void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } void setTextureMode(unsigned int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setTextureAttribute(int unit, osg::StateAttribute::TypeMemberPair typeMemberPair) { mTextureAttributes[unit].emplace(typeMemberPair); } void setTextureAttribute(unsigned int unit, const osg::StateAttribute* attribute) { mTextureAttributes[unit].emplace(attribute->getTypeMemberPair()); } template void setTextureAttribute(unsigned int unit, osg::ref_ptr attribute) { setTextureAttribute(unit, attribute.get()); } void setTextureAttributeAndModes(unsigned int unit, const osg::StateAttribute* attribute) { setTextureAttribute(unit, attribute); InterrogateModesHelper helper(this, unit); attribute->getModeUsage(helper); } template void setTextureAttributeAndModes(unsigned int unit, osg::ref_ptr attribute) { setTextureAttributeAndModes(unit, attribute.get()); } bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const osg::StateAttribute::TypeMemberPair &typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } bool hasTextureMode(int unit, osg::StateAttribute::GLMode mode) { auto it = mTextureModes.find(unit); if (it == mTextureModes.cend()) return false; return it->second.count(mode); } const std::set& getAttributes() { return mAttributes; } const std::unordered_map>& getTextureAttributes() { return mTextureAttributes; } bool empty() { return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty() && mTextureAttributes.empty(); } META_Object(Shader, AddedState) private: class InterrogateModesHelper : public osg::StateAttribute::ModeUsage { public: InterrogateModesHelper(AddedState* tracker, unsigned int textureUnit = 0) : mTracker(tracker) , mTextureUnit(textureUnit) {} void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } void usesTextureMode(osg::StateAttribute::GLMode mode) override { mTracker->setTextureMode(mTextureUnit, mode); } private: AddedState* mTracker; unsigned int mTextureUnit; }; using ModeSet = std::unordered_set; using AttributeSet = std::set; std::unordered_set mUniforms; ModeSet mModes; AttributeSet mAttributes; std::unordered_map mTextureModes; std::unordered_map mTextureAttributes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) , mAlphaTestOverridden(false) , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) , mAlphaBlend(false) , mBlendFuncOverridden(false) , mAdditiveBlending(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) { } ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) , mAdjustCoverageForAlphaTest(false) , mSupportsNormalsRT(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { } void ShaderVisitor::setForceShaders(bool force) { mForceShaders = force; } void ShaderVisitor::apply(osg::Node& node) { bool needPop = false; if (node.getStateSet() || mRequirements.empty()) { needPop = true; pushRequirements(node); if (node.getStateSet()) applyStateSet(node.getStateSet(), node); } traverse(node); if (needPop) popRequirements(); } osg::StateSet* getWritableStateSet(osg::Node& node) { if (!node.getStateSet()) return node.getOrCreateStateSet(); osg::ref_ptr newStateSet = new osg::StateSet(*node.getStateSet(), osg::CopyOp::SHALLOW_COPY); node.setStateSet(newStateSet); return newStateSet.get(); } osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object) { if (!object.getUserDataContainer()) return object.getOrCreateUserDataContainer(); osg::ref_ptr newUserData = static_cast(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY)); object.setUserDataContainer(newUserData); return newUserData.get(); } osg::StateSet* getRemovedState(osg::StateSet& stateSet) { if (!stateSet.getUserDataContainer()) return nullptr; return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); } void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) userData.setUserObject(index, removedState); else userData.addUserObject(removedState); removedState->setName("removedState"); } AddedState* getAddedState(osg::StateSet& stateSet) { if (!stateSet.getUserDataContainer()) return nullptr; return static_cast(stateSet.getUserDataContainer()->getUserObject("addedState")); } void updateAddedState(osg::UserDataContainer& userData, AddedState* addedState) { unsigned int index = userData.getUserObjectIndex("addedState"); if (index < userData.getNumUserObjects()) userData.setUserObject(index, addedState); else userData.addUserObject(addedState); addedState->setName("addedState"); } const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap", "glossMap" }; bool isTextureNameRecognized(std::string_view name) { return std::find(std::begin(defaultTextures), std::end(defaultTextures), name) != std::end(defaultTextures); } void ShaderVisitor::applyStateSet(osg::ref_ptr stateset, osg::Node& node) { osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); bool shaderRequired = false; if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; bool softEffect = false; if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; const osg::Texture* normalMap = nullptr; const osg::Texture* specularMap = nullptr; const osg::Texture* bumpMap = nullptr; for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { // If textures ever get removed in createProgram, expand this to check we're operating on main texture attribute list // rather than the removed list if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) continue; const osg::Texture* texture = attr->asTexture(); if (texture) { std::string texName = texture->getName(); if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) texName = "diffuseMap"; if (texName == "normalHeightMap") { mRequirements.back().mNormalHeight = true; texName = "normalMap"; } if (!texName.empty()) { mRequirements.back().mTextures[unit] = texName; if (texName == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); normalMap = texture; } else if (texName == "diffuseMap") diffuseMap = texture; else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") { bumpMap = texture; mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // Bump maps are off by default as well writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); } else if (texName == "envMap" && mApplyLightingToEnvMaps) { mRequirements.back().mShaderRequired = true; } else if (texName == "glossMap") { mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // As well as gloss maps writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); } } else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } } if (mAutoUseNormalMaps && diffuseMap != nullptr && normalMap == nullptr && diffuseMap->getImage(0)) { std::string normalMapFileName = diffuseMap->getImage(0)->getFileName(); osg::ref_ptr image; bool normalHeight = false; std::string normalHeightMap = normalMapFileName; Misc::StringUtils::replaceLast(normalHeightMap, ".", mNormalHeightMapPattern + "."); if (mImageManager.getVFS()->exists(normalHeightMap)) { image = mImageManager.getImage(normalHeightMap); normalHeight = true; } else { Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + "."); if (mImageManager.getVFS()->exists(normalMapFileName)) { image = mImageManager.getImage(normalMapFileName); } } // Avoid using the auto-detected normal map if it's already being used as a bump map. // It's probably not an actual normal map. bool hasNamesakeBumpMap = image && bumpMap && bumpMap->getImage(0) && image->getFileName() == bumpMap->getImage(0)->getFileName(); if (!hasNamesakeBumpMap && image) { osg::ref_ptr normalMapTex (new osg::Texture2D(image)); normalMapTex->setTextureSize(image->s(), image->t()); normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); normalMapTex->setName("normalMap"); int unit = texAttributes.size(); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "normalMap"; mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mShaderRequired = true; mRequirements.back().mNormalHeight = normalHeight; } } if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + "."); if (mImageManager.getVFS()->exists(specularMapFileName)) { osg::ref_ptr image (mImageManager.getImage(specularMapFileName)); osg::ref_ptr specularMapTex (new osg::Texture2D(image)); specularMapTex->setTextureSize(image->s(), image->t()); specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); specularMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); specularMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); specularMapTex->setName("specularMap"); int unit = texAttributes.size(); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttributeAndModes(unit, specularMapTex, osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "specularMap"; mRequirements.back().mShaderRequired = true; } } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { for (osg::StateSet::AttributeList::const_iterator it = attributeMap->begin(); it != attributeMap->end(); ++it) { if (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) continue; if (it->first.first == osg::StateAttribute::MATERIAL) { // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mMaterialOverridden = true; const osg::Material* mat = static_cast(it->second.first.get()); int colorMode; switch (mat->getColorMode()) { case osg::Material::OFF: colorMode = 0; break; case osg::Material::EMISSION: colorMode = 1; break; default: case osg::Material::AMBIENT_AND_DIFFUSE: colorMode = 2; break; case osg::Material::AMBIENT: colorMode = 3; break; case osg::Material::DIFFUSE: colorMode = 4; break; case osg::Material::SPECULAR: colorMode = 5; break; } mRequirements.back().mColorMode = colorMode; } } else if (it->first.first == osg::StateAttribute::ALPHAFUNC) { if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mAlphaTestOverridden = true; const osg::AlphaFunc* alpha = static_cast(it->second.first.get()); mRequirements.back().mAlphaFunc = alpha->getFunction(); mRequirements.back().mAlphaRef = alpha->getReferenceValue(); } } else if (it->first.first == osg::StateAttribute::BLENDFUNC) { if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mBlendFuncOverridden = true; const osg::BlendFunc* blend = static_cast(it->second.first.get()); mRequirements.back().mAdditiveBlending = blend->getSource() == osg::BlendFunc::SRC_ALPHA && blend->getDestination() == osg::BlendFunc::ONE; } } } } unsigned int alphaBlend = stateset->getMode(GL_BLEND); if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED)) { if (alphaBlend & osg::StateAttribute::OVERRIDE) mRequirements.back().mAlphaBlendOverridden = true; mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON; } } void ShaderVisitor::pushRequirements(osg::Node& node) { if (mRequirements.empty()) mRequirements.emplace_back(); else mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; } void ShaderVisitor::popRequirements() { mRequirements.pop_back(); } void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { if (!reqs.mShaderRequired && !mForceShaders) { ensureFFP(*reqs.mNode); return; } /** * The shader visitor is supposed to be idempotent and undoable. * That means we need to back up state we've removed (so it can be restored and/or considered by further * applications of the visitor) and track which state we added (so it can be removed and/or ignored by further * applications of the visitor). * Before editing writableStateSet in a way that explicitly removes state or might overwrite existing state, it * should be copied to removedState, another StateSet, unless it's there already or was added by a previous * application of the visitor (is in previousAddedState). * If it's a new class of state that's not already handled by ReinstateRemovedStateVisitor::apply, make sure to * add handling there. * Similarly, any time new state is added to writableStateSet, the equivalent method should be called on * addedState. * If that method doesn't exist yet, implement it - we don't use a full StateSet as we only need to check * existence, not equality, and don't need to actually get the value as we can get it from writableStateSet * instead. */ osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); else writableStateSet = getWritableStateSet(node); osg::ref_ptr addedState = new AddedState; osg::ref_ptr previousAddedState = getAddedState(*writableStateSet); if (!previousAddedState) previousAddedState = new AddedState; ShaderManager::DefineMap defineMap; for (unsigned int i=0; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { defineMap[texIt->second] = "1"; defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } if (defineMap["diffuseMap"] == "0") { writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); addedState->addUniform("useDiffuseMapForShadowAlpha"); } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); defineMap["additiveBlending"] = reqs.mAdditiveBlending ? "1" : "0"; osg::ref_ptr removedState; if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); if (!removedState) removedState = new osg::StateSet(); defineMap["alphaToCoverage"] = "0"; defineMap["adjustCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); addedState->addUniform("alphaRef"); if (!removedState->getAttributePair(osg::StateAttribute::ALPHAFUNC)) { const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); if (alphaFunc && !previousAddedState->hasAttribute(osg::StateAttribute::ALPHAFUNC, 0)) removedState->setAttribute(alphaFunc->first, alphaFunc->second); } // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); addedState->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc)); // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); addedState->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); defineMap["alphaToCoverage"] = "1"; } // Adjusting coverage isn't safe with blending on as blending requires the alpha to be intact. // Maybe we could also somehow (e.g. userdata) detect when the diffuse map has coverage-preserving mip maps in the future if (mAdjustCoverageForAlphaTest && !reqs.mAlphaBlend) defineMap["adjustCoverage"] = "1"; // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size osg::ref_ptr exts = osg::GLExtensions::Get(0, false); if (exts && exts->isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } bool simpleLighting = false; node.getUserValue("simpleLighting", simpleLighting); if (simpleLighting) defineMap["endLight"] = "0"; if (simpleLighting || dynamic_cast(&node)) defineMap["forcePPL"] = "0"; if (reqs.mAlphaBlend && mSupportsNormalsRT) { if (reqs.mSoftParticles) defineMap["disableNormals"] = "1"; writableStateSet->setAttribute(new osg::ColorMaski(1, false, false, false, false)); } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); addedState->setMode(GL_ALPHA_TEST); if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) { // user data is normally shallow copied so shared with the original stateset osg::ref_ptr writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getOrCreateUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); updateRemovedState(*writableUserData, removedState); } defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; osg::ref_ptr vertexShader (mShaderManager.getShader(shaderPrefix + "_vertex.glsl", defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(shaderPrefix + "_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); if (vertexShader && fragmentShader) { auto program = mShaderManager.getProgram(vertexShader, fragmentShader, mProgramTemplate); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); addedState->setAttributeAndModes(program); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); addedState->addUniform(texIt->second); } } if (!addedState->empty()) { // user data is normally shallow copied so shared with the original stateset osg::ref_ptr writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getOrCreateUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); updateAddedState(*writableUserData, addedState); } } void ShaderVisitor::ensureFFP(osg::Node& node) { if (!node.getStateSet() || !node.getStateSet()->getAttribute(osg::StateAttribute::PROGRAM)) return; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); else writableStateSet = getWritableStateSet(node); /** * We might have been using shaders temporarily with the node (e.g. if a GlowUpdater applied a temporary * environment map for a temporary enchantment). * We therefore need to remove any state doing so added, and restore any that it removed. * This is kept track of in createProgram in the StateSet's userdata. * If new classes of state get added, handling it here is required - not all StateSet features are implemented * in AddedState yet as so far they've not been necessary. * Removed state requires no particular special handling as it's dealt with by merging StateSets. * We don't need to worry about state in writableStateSet having the OVERRIDE flag as if it's in both, it's also * in addedState, and gets removed first. */ // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits osg::ref_ptr writableUserData; if (osg::ref_ptr addedState = getAddedState(*writableStateSet)) { if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); unsigned int index = writableUserData->getUserObjectIndex("addedState"); writableUserData->removeUserObject(index); // O(n log n) to use StateSet::removeX, but this is O(n) for (auto itr = writableStateSet->getUniformList().begin(); itr != writableStateSet->getUniformList().end();) { if (addedState->hasUniform(itr->first)) writableStateSet->getUniformList().erase(itr++); else ++itr; } for (auto itr = writableStateSet->getModeList().begin(); itr != writableStateSet->getModeList().end();) { if (addedState->hasMode(itr->first)) writableStateSet->getModeList().erase(itr++); else ++itr; } // StateAttributes track the StateSets they're attached to // We don't have access to the function to do that, and can't call removeAttribute with an iterator for (const auto& [type, member] : addedState->getAttributes()) writableStateSet->removeAttribute(type, member); for (unsigned int unit = 0; unit < writableStateSet->getTextureModeList().size(); ++unit) { for (auto itr = writableStateSet->getTextureModeList()[unit].begin(); itr != writableStateSet->getTextureModeList()[unit].end();) { if (addedState->hasTextureMode(unit, itr->first)) writableStateSet->getTextureModeList()[unit].erase(itr++); else ++itr; } } for (const auto& [unit, attributeList] : addedState->getTextureAttributes()) { for (const auto& [type, member] : attributeList) writableStateSet->removeTextureAttribute(unit, type); } } if (osg::ref_ptr removedState = getRemovedState(*writableStateSet)) { if (!writableUserData) { if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); } unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index); writableStateSet->merge(*removedState); } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) { bool useShader = reqs.mShaderRequired || mForceShaders; bool generateTangents = reqs.mTexStageRequiringTangents != -1; bool changed = false; if (mAllowedToModifyStateSets && (useShader || generateTangents)) { // make sure that all UV sets are there for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) { if (sourceGeometry.getTexCoordArray(it->first) == nullptr) { sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0)); changed = true; } } if (generateTangents) { osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents); sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); changed = true; } } return changed; } void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = geometry.getStateSet() || mRequirements.empty(); if (needPop) pushRequirements(geometry); if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it applyStateSet(geometry.getStateSet(), geometry); if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); adjustGeometry(geometry, reqs); createProgram(reqs); } else ensureFFP(geometry); if (needPop) popRequirements(); } void ShaderVisitor::apply(osg::Drawable& drawable) { bool needPop = drawable.getStateSet() || mRequirements.empty(); // We need to push and pop a requirements object because particle systems can have // different shader requirements to other drawables, so might need a different shader variant. if (!needPop && dynamic_cast(&drawable)) needPop = true; if (needPop) { pushRequirements(drawable); if (drawable.getStateSet()) applyStateSet(drawable.getStateSet(), drawable); } const ShaderRequirements& reqs = mRequirements.back(); createProgram(reqs); if (auto rig = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) rig->setSourceGeometry(sourceGeometry); } else if (auto morph = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) morph->setSourceGeometry(sourceGeometry); } else if (auto osgaRig = dynamic_cast(&drawable)) { osg::ref_ptr sourceOsgaRigGeometry = osgaRig->getSourceRigGeometry(); osg::ref_ptr sourceGeometry = sourceOsgaRigGeometry->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) { sourceOsgaRigGeometry->setSourceGeometry(sourceGeometry); osgaRig->setSourceRigGeometry(sourceOsgaRigGeometry); } } if (needPop) popRequirements(); } void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) { mAllowedToModifyStateSets = allowed; } void ShaderVisitor::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; } void ShaderVisitor::setNormalMapPattern(const std::string &pattern) { mNormalMapPattern = pattern; } void ShaderVisitor::setNormalHeightMapPattern(const std::string &pattern) { mNormalHeightMapPattern = pattern; } void ShaderVisitor::setAutoUseSpecularMaps(bool use) { mAutoUseSpecularMaps = use; } void ShaderVisitor::setSpecularMapPattern(const std::string &pattern) { mSpecularMapPattern = pattern; } void ShaderVisitor::setApplyLightingToEnvMaps(bool apply) { mApplyLightingToEnvMaps = apply; } void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; } void ShaderVisitor::setAdjustCoverageForAlphaTest(bool adjustCoverage) { mAdjustCoverageForAlphaTest = adjustCoverage; } ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) { } void ReinstateRemovedStateVisitor::apply(osg::Node& node) { // TODO: this may eventually need to remove added state. // If so, we can migrate from explicitly copying removed state to just calling osg::StateSet::merge. // Not everything is transferred from removedState yet - implement more when createProgram starts marking more // as removed. if (node.getStateSet()) { osg::ref_ptr removedState = getRemovedState(*node.getStateSet()); if (removedState) { osg::ref_ptr writableStateSet; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); else writableStateSet = getWritableStateSet(node); // user data is normally shallow copied so shared with the original stateset osg::ref_ptr writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index); for (const auto&[mode, value] : removedState->getModeList()) writableStateSet->setMode(mode, value); for (const auto& attribute : removedState->getAttributeList()) writableStateSet->setAttribute(attribute.second.first, attribute.second.second); for (unsigned int unit = 0; unit < removedState->getTextureModeList().size(); ++unit) { for (const auto&[mode, value] : removedState->getTextureModeList()[unit]) writableStateSet->setTextureMode(unit, mode, value); } } } traverse(node); } } openmw-openmw-0.48.0/components/shader/shadervisitor.hpp000066400000000000000000000103721445372753700234710ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H #define OPENMW_COMPONENTS_SHADERVISITOR_H #include #include #include #include namespace Resource { class ImageManager; } namespace Shader { class ShaderManager; /// @brief Adjusts the given subgraph to render using shaders. class ShaderVisitor : public osg::NodeVisitor { public: ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix); void setProgramTemplate(const osg::Program* programTemplate) { mProgramTemplate = programTemplate; } /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. void setForceShaders(bool force); /// Set if we are allowed to modify StateSets encountered in the graph (default true). /// @par If set to false, then instead of modifying, the StateSet will be cloned and this new StateSet will be assigned to the node. /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. void setAllowedToModifyStateSets(bool allowed); /// Automatically use normal maps if a file with suitable name exists (see normal map pattern). void setAutoUseNormalMaps(bool use); void setNormalMapPattern(const std::string& pattern); void setNormalHeightMapPattern(const std::string& pattern); void setAutoUseSpecularMaps(bool use); void setSpecularMapPattern(const std::string& pattern); void setApplyLightingToEnvMaps(bool apply); void setConvertAlphaTestToAlphaToCoverage(bool convert); void setAdjustCoverageForAlphaTest(bool adjustCoverage); void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; void apply(osg::Geometry& geometry) override; void applyStateSet(osg::ref_ptr stateset, osg::Node& node); void pushRequirements(osg::Node& node); void popRequirements(); private: bool mForceShaders; bool mAllowedToModifyStateSets; bool mAutoUseNormalMaps; std::string mNormalMapPattern; std::string mNormalHeightMapPattern; bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; bool mConvertAlphaTestToAlphaToCoverage; bool mAdjustCoverageForAlphaTest; bool mSupportsNormalsRT; ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; struct ShaderRequirements { ShaderRequirements(); ~ShaderRequirements() = default; // std::map mTextures; bool mShaderRequired; int mColorMode; bool mMaterialOverridden; bool mAlphaTestOverridden; bool mAlphaBlendOverridden; GLenum mAlphaFunc; float mAlphaRef; bool mAlphaBlend; bool mBlendFuncOverridden; bool mAdditiveBlending; bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required int mTexStageRequiringTangents; bool mSoftParticles; // the Node that requested these requirements osg::Node* mNode; }; std::vector mRequirements; std::string mDefaultShaderPrefix; void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor { public: ReinstateRemovedStateVisitor(bool allowedToModifyStateSets); void apply(osg::Node& node) override; private: bool mAllowedToModifyStateSets; }; } #endif openmw-openmw-0.48.0/components/sqlite3/000077500000000000000000000000001445372753700202055ustar00rootroot00000000000000openmw-openmw-0.48.0/components/sqlite3/db.cpp000066400000000000000000000024741445372753700213050ustar00rootroot00000000000000#include "db.hpp" #include #include #include #include namespace Sqlite3 { void CloseSqlite3::operator()(sqlite3* handle) const noexcept { sqlite3_close_v2(handle); } Db makeDb(std::string_view path, const char* schema) { sqlite3* handle = nullptr; // All uses of NavMeshDb are protected by a mutex (navmeshtool) or serialized in a single thread (DbWorker) // so additional synchronization between threads is not required and SQLITE_OPEN_NOMUTEX can be used. // This is unsafe to use NavMeshDb without external synchronization because of internal state. const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; if (const int ec = sqlite3_open_v2(std::string(path).c_str(), &handle, flags, nullptr); ec != SQLITE_OK) { const std::string message(sqlite3_errmsg(handle)); sqlite3_close(handle); throw std::runtime_error("Failed to open database: " + message); } Db result(handle); if (const int ec = sqlite3_exec(result.get(), schema, nullptr, nullptr, nullptr); ec != SQLITE_OK) throw std::runtime_error("Failed create database schema: " + std::string(sqlite3_errmsg(handle))); return result; } } openmw-openmw-0.48.0/components/sqlite3/db.hpp000066400000000000000000000005711445372753700213060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SQLITE3_DB_H #define OPENMW_COMPONENTS_SQLITE3_DB_H #include #include struct sqlite3; namespace Sqlite3 { struct CloseSqlite3 { void operator()(sqlite3* handle) const noexcept; }; using Db = std::unique_ptr; Db makeDb(std::string_view path, const char* schema); } #endif openmw-openmw-0.48.0/components/sqlite3/request.hpp000066400000000000000000000266041445372753700224160ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SQLITE3_REQUEST_H #define OPENMW_COMPONENTS_SQLITE3_REQUEST_H #include "statement.hpp" #include "types.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace Sqlite3 { inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, int value) { if (const int ec = sqlite3_bind_int(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error("Failed to bind int to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::int64_t value) { if (const int ec = sqlite3_bind_int64(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error("Failed to bind int64 to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, double value) { if (const int ec = sqlite3_bind_double(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error("Failed to bind double to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::string_view value) { if (sqlite3_bind_text(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error("Failed to bind text to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const std::vector& value) { if (sqlite3_bind_blob(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const ConstBlob& value) { if (sqlite3_bind_blob(&stmt, index, value.mData, value.mSize, SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } template inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value) { const int index = sqlite3_bind_parameter_index(&stmt, name); if (index == 0) throw std::logic_error("Parameter \"" + std::string(name) + "\" is not found"); bindParameter(db, stmt, index, value); } inline std::string sqliteTypeToString(int value) { switch (value) { case SQLITE_INTEGER: return "SQLITE_INTEGER"; case SQLITE_FLOAT: return "SQLITE_FLOAT"; case SQLITE_TEXT: return "SQLITE_TEXT"; case SQLITE_BLOB: return "SQLITE_BLOB"; case SQLITE_NULL: return "SQLITE_NULL"; } return "unsupported(" + std::to_string(value) + ")"; } template inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& /*statement*/, int index, int type, T*& value) { if (type != SQLITE_NULL) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT"); value = nullptr; } template inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& statement, int index, int type, T& value) { switch (type) { case SQLITE_INTEGER: value = static_cast(sqlite3_column_int64(&statement, index)); return; case SQLITE_FLOAT: value = static_cast(sqlite3_column_double(&statement, index)); return; case SQLITE_NULL: value = std::decay_t{}; return; } throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT or SQLITE_NULL"); } inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::string& value) { if (type != SQLITE_TEXT) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_TEXT"); const unsigned char* const text = sqlite3_column_text(&statement, index); if (text == nullptr) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error("Failed to read text from column " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } const int size = sqlite3_column_bytes(&statement, index); if (size <= 0) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error("Failed to get column bytes " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } value.reserve(static_cast(size)); value.assign(reinterpret_cast(text), reinterpret_cast(text) + size); } inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::vector& value) { if (type != SQLITE_BLOB) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_BLOB"); const void* const blob = sqlite3_column_blob(&statement, index); if (blob == nullptr) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error("Failed to read blob from column " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } const int size = sqlite3_column_bytes(&statement, index); if (size <= 0) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error("Failed to get column bytes " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } value.reserve(static_cast(size)); value.assign(static_cast(blob), static_cast(blob) + size); } template inline void getColumnsImpl(sqlite3& db, sqlite3_stmt& statement, T& row) { if constexpr (0 < index && index <= std::tuple_size_v) { const int column = index - 1; if (const int number = sqlite3_column_count(&statement); column >= number) throw std::out_of_range("Column number is out of range: " + std::to_string(column) + " >= " + std::to_string(number)); const int type = sqlite3_column_type(&statement, column); switch (type) { case SQLITE_INTEGER: case SQLITE_FLOAT: case SQLITE_TEXT: case SQLITE_BLOB: case SQLITE_NULL: copyColumn(db, statement, column, type, std::get(row)); break; default: throw std::runtime_error("Column " + std::to_string(column) + " has unnsupported column type: " + sqliteTypeToString(type)); } getColumnsImpl(db, statement, row); } } template inline void getColumns(sqlite3& db, sqlite3_stmt& statement, T& row) { getColumnsImpl>(db, statement, row); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, T& row) { auto tuple = std::tie(row); getColumns(db, statement, tuple); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::tuple& row) { getColumns(db, statement, row); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::back_insert_iterator& it) { typename T::value_type row; getRow(db, statement, row); it = std::move(row); } template inline void prepare(sqlite3& db, Statement& statement, Args&& ... args) { if (statement.mNeedReset) { if (sqlite3_reset(statement.mHandle.get()) == SQLITE_OK && sqlite3_clear_bindings(statement.mHandle.get()) == SQLITE_OK) statement.mNeedReset = false; else statement.mHandle = makeStatementHandle(db, statement.mQuery.text()); } statement.mQuery.bind(db, *statement.mHandle, std::forward(args) ...); } template inline bool executeStep(sqlite3& db, const Statement& statement) { switch (sqlite3_step(statement.mHandle.get())) { case SQLITE_ROW: return true; case SQLITE_DONE: return false; } throw std::runtime_error("Failed to execute statement step: " + std::string(sqlite3_errmsg(&db))); } template inline I request(sqlite3& db, Statement& statement, I out, std::size_t max, Args&& ... args) { try { statement.mNeedReset = true; prepare(db, statement, std::forward(args) ...); for (std::size_t i = 0; executeStep(db, statement) && i < max; ++i) getRow(db, *statement.mHandle, *out++); return out; } catch (const std::exception& e) { throw std::runtime_error("Failed perform request \"" + std::string(statement.mQuery.text()) + "\": " + std::string(e.what())); } } template inline int execute(sqlite3& db, Statement& statement, Args&& ... args) { try { statement.mNeedReset = true; prepare(db, statement, std::forward(args) ...); if (executeStep(db, statement)) throw std::logic_error("Execute cannot return rows"); return sqlite3_changes(&db); } catch (const std::exception& e) { throw std::runtime_error("Failed to execute statement \"" + std::string(statement.mQuery.text()) + "\": " + std::string(e.what())); } } } #endif openmw-openmw-0.48.0/components/sqlite3/statement.cpp000066400000000000000000000013401445372753700227130ustar00rootroot00000000000000#include "statement.hpp" #include #include #include #include namespace Sqlite3 { void CloseSqlite3Stmt::operator()(sqlite3_stmt* handle) const noexcept { sqlite3_finalize(handle); } StatementHandle makeStatementHandle(sqlite3& db, std::string_view query) { sqlite3_stmt* stmt = nullptr; if (const int ec = sqlite3_prepare_v2(&db, query.data(), static_cast(query.size()), &stmt, nullptr); ec != SQLITE_OK) throw std::runtime_error("Failed to prepare statement for query \"" + std::string(query) + "\": " + std::string(sqlite3_errmsg(&db))); return StatementHandle(stmt); } } openmw-openmw-0.48.0/components/sqlite3/statement.hpp000066400000000000000000000014411445372753700227220ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SQLITE3_STATEMENT_H #define OPENMW_COMPONENTS_SQLITE3_STATEMENT_H #include #include #include struct sqlite3; struct sqlite3_stmt; namespace Sqlite3 { struct CloseSqlite3Stmt { void operator()(sqlite3_stmt* handle) const noexcept; }; using StatementHandle = std::unique_ptr; StatementHandle makeStatementHandle(sqlite3& db, std::string_view query); template struct Statement { bool mNeedReset = false; StatementHandle mHandle; Query mQuery; explicit Statement(sqlite3& db, Query query = Query {}) : mHandle(makeStatementHandle(db, query.text())), mQuery(std::move(query)) {} }; } #endif openmw-openmw-0.48.0/components/sqlite3/transaction.cpp000066400000000000000000000034151445372753700232410ustar00rootroot00000000000000#include "transaction.hpp" #include #include #include #include namespace Sqlite3 { namespace { const char* getBeginStatement(TransactionMode mode) { switch (mode) { case TransactionMode::Default: return "BEGIN"; case TransactionMode::Deferred: return "BEGIN DEFERRED"; case TransactionMode::Immediate: return "BEGIN IMMEDIATE"; case TransactionMode::Exclusive: return "BEGIN EXCLUSIVE"; } throw std::logic_error("Invalid transaction mode: " + std::to_string(static_cast>(mode))); } } void Rollback::operator()(sqlite3* db) const { if (db == nullptr) return; if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK) Log(Debug::Debug) << "Failed to rollback SQLite3 transaction: " << sqlite3_errmsg(db) << " (" << ec << ")"; } Transaction::Transaction(sqlite3& db, TransactionMode mode) : mDb(&db) { if (const int ec = sqlite3_exec(&db, getBeginStatement(mode), nullptr, nullptr, nullptr); ec != SQLITE_OK) { (void) mDb.release(); throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(&db)) + " (" + std::to_string(ec) + ")"); } } void Transaction::commit() { if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK) throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get())) + " (" + std::to_string(ec) + ")"); (void) mDb.release(); } } openmw-openmw-0.48.0/components/sqlite3/transaction.hpp000066400000000000000000000011141445372753700232400ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H #define OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H #include struct sqlite3; namespace Sqlite3 { struct Rollback { void operator()(sqlite3* handle) const; }; enum class TransactionMode { Default, Deferred, Immediate, Exclusive, }; class Transaction { public: explicit Transaction(sqlite3& db, TransactionMode mode = TransactionMode::Default); void commit(); private: std::unique_ptr mDb; }; } #endif openmw-openmw-0.48.0/components/sqlite3/types.hpp000066400000000000000000000003271445372753700220640ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_SQLITE3_TYPES_H #define OPENMW_COMPONENTS_SQLITE3_TYPES_H #include namespace Sqlite3 { struct ConstBlob { const char* mData; int mSize; }; } #endif openmw-openmw-0.48.0/components/std140/000077500000000000000000000000001445372753700176405ustar00rootroot00000000000000openmw-openmw-0.48.0/components/std140/ubo.hpp000066400000000000000000000105601445372753700211400ustar00rootroot00000000000000#ifndef COMPONENTS_STD140_UBO_H #define COMPONENTS_STD140_UBO_H #include #include #include #include #include #include #include #include namespace std140 { struct Mat4 { using Value = osg::Matrixf; Value mValue; static constexpr size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "mat4"; }; struct Vec4 { using Value = osg::Vec4f; Value mValue; static constexpr size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "vec4"; }; struct Vec3 { using Value = osg::Vec3f; Value mValue; static constexpr std::size_t sAlign = 4 * sizeof(osg::Vec3f::value_type); static constexpr std::string_view sTypeName = "vec3"; }; struct Vec2 { using Value = osg::Vec2f; Value mValue; static constexpr std::size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "vec2"; }; struct Float { using Value = float; Value mValue; static constexpr std::size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "float"; }; struct Int { using Value = std::int32_t; Value mValue; static constexpr std::size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "int"; }; struct UInt { using Value = std::uint32_t; Value mValue; static constexpr std::size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "uint"; }; struct Bool { using Value = std::int32_t; Value mValue; static constexpr std::size_t sAlign = sizeof(Value); static constexpr std::string_view sTypeName = "bool"; }; template class UBO { private: template struct contains : std::bool_constant<(std::is_base_of_v || ...)> { }; static_assert((contains() && ...)); static constexpr size_t roundUpRemainder(size_t x, size_t multiple) { size_t remainder = x % multiple; if (remainder == 0) return 0; return multiple - remainder; } template static constexpr std::size_t getOffset() { bool found = false; std::size_t size = 0; (( found = found || std::is_same_v, size += (found ? 0 : sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign)) ) , ...); return size + roundUpRemainder(size, T::sAlign); } public: static constexpr size_t getGPUSize() { std::size_t size = 0; ((size += (sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign))), ...); return size; } static std::string getDefinition(const std::string& name) { std::string structDefinition = "struct " + name + " {\n"; ((structDefinition += (" " + std::string(CArgs::sTypeName) + " " + std::string(CArgs::sName) + ";\n")), ...); return structDefinition + "};"; } using BufferType = std::array; using TupleType = std::tuple; template typename T::Value& get() { return std::get(mData).mValue; } template const typename T::Value& get() const { return std::get(mData).mValue; } void copyTo(BufferType& buffer) const { const auto copy = [&] (const auto& v) { static_assert(std::is_standard_layout_v>); constexpr std::size_t offset = getOffset>(); std::memcpy(buffer.data() + offset, &v.mValue, sizeof(v.mValue)); }; std::apply([&] (const auto& ... v) { (copy(v) , ...); }, mData); } const auto& getData() const { return mData; } private: std::tuple mData; }; } #endif openmw-openmw-0.48.0/components/stereo/000077500000000000000000000000001445372753700201225ustar00rootroot00000000000000openmw-openmw-0.48.0/components/stereo/frustum.cpp000066400000000000000000000113431445372753700223350ustar00rootroot00000000000000#include "stereomanager.hpp" #include "multiview.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "frustum.hpp" namespace Stereo { struct MultiviewFrustumCallback final : public Stereo::InitialFrustumCallback { MultiviewFrustumCallback(StereoFrustumManager* sfm, osg::Camera* camera) : Stereo::InitialFrustumCallback(camera) , mSfm(sfm) { } void setInitialFrustum(osg::CullStack& cullStack, osg::BoundingBoxd& bb, bool& nearCulling, bool& farCulling) const override { auto cm = cullStack.getCullingMode(); nearCulling = !!(cm & osg::CullSettings::NEAR_PLANE_CULLING); farCulling = !!(cm & osg::CullSettings::FAR_PLANE_CULLING); bb = mSfm->boundingBox(); } StereoFrustumManager* mSfm; }; struct ShadowFrustumCallback final : public SceneUtil::MWShadowTechnique::CustomFrustumCallback { ShadowFrustumCallback(StereoFrustumManager* parent) : mParent(parent) { } void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) override { mParent->customFrustumCallback(cv, customClipSpace, sharedFrustumHint); } StereoFrustumManager* mParent; }; void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb) { static const std::array clipCorners = {{ {-1.0, -1.0, -1.0}, { 1.0, -1.0, -1.0}, { 1.0, -1.0, 1.0}, {-1.0, -1.0, 1.0}, {-1.0, 1.0, -1.0}, { 1.0, 1.0, -1.0}, { 1.0, 1.0, 1.0}, {-1.0, 1.0, 1.0} }}; osg::Matrix slaveClipToView; slaveClipToView.invert(slaveProjection); for (const auto& clipCorner : clipCorners) { auto masterViewVertice = clipCorner * slaveClipToView; auto masterClipVertice = masterViewVertice * masterProjection; bb.expandBy(masterClipVertice); } } StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) : mCamera(camera) , mShadowTechnique(nullptr) , mShadowFrustumCallback(nullptr) { if (Stereo::getMultiview()) { mMultiviewFrustumCallback = std::make_unique(this, camera); } if (Settings::Manager::getBool("shared shadow maps", "Stereo")) { mShadowFrustumCallback = new ShadowFrustumCallback(this); auto* renderer = static_cast(mCamera->getRenderer()); for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) { mSharedFrustums[sceneView->getCullVisitorRight()] = sceneView->getCullVisitorLeft(); } } } StereoFrustumManager::~StereoFrustumManager() { if (mShadowTechnique) mShadowTechnique->setCustomFrustumCallback(nullptr); } void StereoFrustumManager::setShadowTechnique( SceneUtil::MWShadowTechnique* shadowTechnique) { if (mShadowTechnique) mShadowTechnique->setCustomFrustumCallback(nullptr); mShadowTechnique = shadowTechnique; if (mShadowTechnique) mShadowTechnique->setCustomFrustumCallback(mShadowFrustumCallback); } void StereoFrustumManager::customFrustumCallback( osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) { auto it = mSharedFrustums.find(&cv); if (it != mSharedFrustums.end()) { sharedFrustumHint = it->second; } customClipSpace = mBoundingBox; } void StereoFrustumManager::update(std::array projections) { mBoundingBox.init(); for (auto& projection : projections) joinBoundingBoxes(mCamera->getProjectionMatrix(), projection, mBoundingBox); } } openmw-openmw-0.48.0/components/stereo/frustum.hpp000066400000000000000000000031661445372753700223460ustar00rootroot00000000000000#ifndef STEREO_FRUSTUM_H #define STEREO_FRUSTUM_H #include #include #include #include #include #include #include #include #include namespace osg { class FrameBufferObject; class Texture2D; class Texture2DMultisample; class Texture2DArray; } namespace osgViewer { class Viewer; } namespace usgUtil { class CullVisitor; } namespace SceneUtil { class MWShadowTechnique; } namespace Stereo { struct MultiviewFrustumCallback; struct ShadowFrustumCallback; void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb); class StereoFrustumManager { public: StereoFrustumManager(osg::Camera* camera); ~StereoFrustumManager(); void update(std::array projections); const osg::BoundingBoxd& boundingBox() const { return mBoundingBox; } void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); void customFrustumCallback(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint); private: osg::ref_ptr mCamera; osg::ref_ptr mShadowTechnique; osg::ref_ptr mShadowFrustumCallback; std::map< osgUtil::CullVisitor*, osgUtil::CullVisitor*> mSharedFrustums; osg::BoundingBoxd mBoundingBox; std::unique_ptr mMultiviewFrustumCallback; }; } #endif openmw-openmw-0.48.0/components/stereo/multiview.cpp000066400000000000000000000762151445372753700226660ustar00rootroot00000000000000#include "multiview.hpp" #include #include #include #include #include #include #include #ifdef OSG_HAS_MULTIVIEW #include #endif #include #include #include #include #include namespace Stereo { namespace { bool getMultiviewSupportedImpl(unsigned int contextID) { #ifdef OSG_HAS_MULTIVIEW if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview")) { Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview\" not supported)"; return false; } if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview2")) { Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview2\" not supported)"; return false; } return true; #else Log(Debug::Verbose) << "Disabling Multiview (OSG does not support multiview)"; return false; #endif } bool getMultiviewSupported(unsigned int contextID) { static bool supported = getMultiviewSupportedImpl(contextID); return supported; } bool getTextureViewSupportedImpl(unsigned int contextID) { if (!osg::isGLExtensionOrVersionSupported(contextID, "ARB_texture_view", 4.3)) { Log(Debug::Verbose) << "Disabling texture views (opengl extension \"ARB_texture_view\" not supported)"; return false; } return true; } bool getTextureViewSupported(unsigned int contextID) { static bool supported = getTextureViewSupportedImpl(contextID); return supported; } bool getMultiviewImpl(unsigned int contextID) { if (!Stereo::getStereo()) { Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; return false; } if (!Settings::Manager::getBool("multiview", "Stereo")) { Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; return false; } if (!getMultiviewSupported(contextID)) { return false; } if (!getTextureViewSupported(contextID)) { Log(Debug::Verbose) << "Disabling Multiview (texture views not supported)"; return false; } Log(Debug::Verbose) << "Enabling Multiview"; return true; } bool getMultiview(unsigned int contextID) { static bool multiView = getMultiviewImpl(contextID); return multiView; } } bool getTextureViewSupported() { return getTextureViewSupported(0); } bool getMultiview() { return getMultiview(0); } void configureExtensions(unsigned int contextID) { getTextureViewSupported(contextID); getMultiviewSupported(contextID); getMultiview(contextID); } void setVertexBufferHint() { if (getStereo() && Settings::Manager::getBool("multiview", "Stereo")) { auto* ds = osg::DisplaySettings::instance().get(); if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) { // Note that this only works if this code is executed before realize() is called on the viewer. // The hint is read by the state object only once, before the user realize operations are run. // Therefore we have to set this hint without access to a graphics context to let us determine // if multiview will actually be supported or not. So if the user has requested multiview, we // will just have to set it regardless. ds->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT); Log(Debug::Verbose) << "Disabling display lists"; } } } class Texture2DViewSubloadCallback : public osg::Texture2D::SubloadCallback { public: Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer); void load(const osg::Texture2D& texture, osg::State& state) const override; void subload(const osg::Texture2D& texture, osg::State& state) const override; private: osg::ref_ptr mTextureArray; int mLayer; }; Texture2DViewSubloadCallback::Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer) : mTextureArray(textureArray) , mLayer(layer) { } void Texture2DViewSubloadCallback::load(const osg::Texture2D& texture, osg::State& state) const { state.checkGLErrors("before Texture2DViewSubloadCallback::load()"); auto contextId = state.getContextID(); auto* gl = osg::GLExtensions::Get(contextId, false); mTextureArray->apply(state); auto sourceTextureObject = mTextureArray->getTextureObject(contextId); if (!sourceTextureObject) { Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2DArray did not have a texture object"; return; } osg::Texture::TextureObject* const targetTextureObject = texture.getTextureObject(contextId); if (targetTextureObject == nullptr) { Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2D did not have a texture object"; return; } // OSG already bound this texture ID, giving it a target. // Delete it and make a new texture ID. glBindTexture(GL_TEXTURE_2D, 0); glDeleteTextures(1, &targetTextureObject->_id); glGenTextures(1, &targetTextureObject->_id); auto sourceId = sourceTextureObject->_id; auto targetId = targetTextureObject->_id; auto internalFormat = sourceTextureObject->_profile._internalFormat; auto levels = std::max(1, sourceTextureObject->_profile._numMipmapLevels); { ////// OSG BUG // Texture views require immutable storage. // OSG should always give immutable storage to sized internal formats, but does not do so for depth formats. // Fortunately, we can just call glTexStorage3D here to make it immutable. This probably discards depth info for that frame, but whatever. #ifndef GL_TEXTURE_IMMUTABLE_FORMAT #define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F #endif // Store any current binding and re-apply it after so i don't mess with state. GLint oldBinding = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &oldBinding); // Bind the source texture and check if it's immutable. glBindTexture(GL_TEXTURE_2D_ARRAY, sourceId); GLint immutable = 0; glGetTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable); if(!immutable) { // It wasn't immutable, so make it immutable. gl->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internalFormat, sourceTextureObject->_profile._width, sourceTextureObject->_profile._height, 2); state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTexStorage3D"); } glBindTexture(GL_TEXTURE_2D_ARRAY, oldBinding); } gl->glTextureView(targetId, GL_TEXTURE_2D, sourceId, internalFormat, 0, levels, mLayer, 1); state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTextureView"); glBindTexture(GL_TEXTURE_2D, targetId); } void Texture2DViewSubloadCallback::subload(const osg::Texture2D& texture, osg::State& state) const { // Nothing to do } osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer) { if (!getTextureViewSupported()) { Log(Debug::Error) << "createTextureView_Texture2DFromTexture2DArray: Tried to use a texture view but glTextureView is not supported"; return nullptr; } osg::ref_ptr texture2d = new osg::Texture2D; texture2d->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture2d->setSubloadCallback(new Texture2DViewSubloadCallback(textureArray, layer)); texture2d->setTextureSize(textureArray->getTextureWidth(), textureArray->getTextureHeight()); texture2d->setBorderColor(textureArray->getBorderColor()); texture2d->setBorderWidth(textureArray->getBorderWidth()); texture2d->setLODBias(textureArray->getLODBias()); texture2d->setFilter(osg::Texture::FilterParameter::MAG_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MAG_FILTER)); texture2d->setFilter(osg::Texture::FilterParameter::MIN_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MIN_FILTER)); texture2d->setInternalFormat(textureArray->getInternalFormat()); texture2d->setNumMipmapLevels(textureArray->getNumMipmapLevels()); return texture2d; } #ifdef OSG_HAS_MULTIVIEW //! Draw callback that, if set on a RenderStage, resolves MSAA after draw. Needed when using custom fbo/resolve fbos on renderstages in combination with multiview. struct MultiviewMSAAResolveCallback : public osgUtil::RenderBin::DrawCallback { void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override { osgUtil::RenderStage* stage = static_cast(bin); auto msaaFbo = stage->getFrameBufferObject(); auto resolveFbo = stage->getMultisampleResolveFramebufferObject(); if (msaaFbo != mMsaaFbo) { mMsaaFbo = msaaFbo; setupMsaaLayers(); } if (resolveFbo != mFbo) { mFbo = resolveFbo; setupLayers(); } // Null the resolve framebuffer to keep osg from doing redundant work. stage->setMultisampleResolveFramebufferObject(nullptr); // Do the actual render work bin->drawImplementation(renderInfo, previous); // Blit layers osg::State& state = *renderInfo.getState(); osg::GLExtensions* ext = state.get(); for (int i = 0; i < 2; i++) { mLayers[i]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); mMsaaLayers[i]->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, GL_NEAREST); } msaaFbo->apply(state, osg::FrameBufferObject::READ_DRAW_FRAMEBUFFER); } void setupLayers() { const auto& attachments = mFbo->getAttachmentMap(); for (int i = 0; i < 2; i++) { mLayers[i] = new osg::FrameBufferObject; // Intentionally not using ref& so attachment can be non-const for (auto [component, attachment] : attachments) { osg::Texture2DArray* texture = static_cast(attachment.getTexture()); mLayers[i]->setAttachment(component, osg::FrameBufferAttachment(texture, i)); mWidth = texture->getTextureWidth(); mHeight = texture->getTextureHeight(); } } } void setupMsaaLayers() { const auto& attachments = mMsaaFbo->getAttachmentMap(); for (int i = 0; i < 2; i++) { mMsaaLayers[i] = new osg::FrameBufferObject; // Intentionally not using ref& so attachment can be non-const for (auto [component, attachment] : attachments) { osg::Texture2DMultisampleArray* texture = static_cast(attachment.getTexture()); mMsaaLayers[i]->setAttachment(component, osg::FrameBufferAttachment(texture, i)); mWidth = texture->getTextureWidth(); mHeight = texture->getTextureHeight(); } } } osg::ref_ptr mFbo; osg::ref_ptr mMsaaFbo; osg::ref_ptr mLayers[2]; osg::ref_ptr mMsaaLayers[2]; int mWidth; int mHeight; }; #endif void setMultiviewMSAAResolveCallback(osgUtil::RenderStage* renderStage) { #ifdef OSG_HAS_MULTIVIEW if (Stereo::getMultiview()) { renderStage->setDrawCallback(new MultiviewMSAAResolveCallback); } #endif } void setMultiviewMatrices(osg::StateSet* stateset, const std::array& projection, bool createInverseMatrices) { auto* projUniform = stateset->getUniform("projectionMatrixMultiView"); if (!projUniform) { projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2); stateset->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); } projUniform->setElement(0, projection[0]); projUniform->setElement(1, projection[1]); if (createInverseMatrices) { auto* invUniform = stateset->getUniform("invProjectionMatrixMultiView"); if (!invUniform) { invUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "invProjectionMatrixMultiView", 2); stateset->addUniform(invUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); } invUniform->setElement(0, osg::Matrix::inverse(projection[0])); invUniform->setElement(1, osg::Matrix::inverse(projection[1])); } } void setMultiviewCompatibleTextureSize(osg::Texture* tex, int w, int h) { switch (tex->getTextureTarget()) { case GL_TEXTURE_2D: static_cast(tex)->setTextureSize(w, h); break; case GL_TEXTURE_2D_ARRAY: static_cast(tex)->setTextureSize(w, h, 2); break; case GL_TEXTURE_2D_MULTISAMPLE: static_cast(tex)->setTextureSize(w, h); break; #ifdef OSG_HAS_MULTIVIEW case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: static_cast(tex)->setTextureSize(w, h, 2); break; #endif default: throw std::logic_error("Invalid texture type received"); } } osg::ref_ptr createMultiviewCompatibleTexture(int width, int height, int samples) { #ifdef OSG_HAS_MULTIVIEW if (Stereo::getMultiview()) { if (samples > 1) { auto tex = new osg::Texture2DMultisampleArray(); tex->setTextureSize(width, height, 2); tex->setNumSamples(samples); return tex; } else { auto tex = new osg::Texture2DArray(); tex->setTextureSize(width, height, 2); return tex; } } else #endif { if (samples > 1) { auto tex = new osg::Texture2DMultisample(); tex->setTextureSize(width, height); tex->setNumSamples(samples); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); return tex; } else { auto tex = new osg::Texture2D(); tex->setTextureSize(width, height); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); return tex; } } } osg::FrameBufferAttachment createMultiviewCompatibleAttachment(osg::Texture* tex) { switch (tex->getTextureTarget()) { case GL_TEXTURE_2D: { auto* tex2d = static_cast(tex); return osg::FrameBufferAttachment(tex2d); } case GL_TEXTURE_2D_MULTISAMPLE: { auto* tex2dMsaa = static_cast(tex); return osg::FrameBufferAttachment(tex2dMsaa); } #ifdef OSG_HAS_MULTIVIEW case GL_TEXTURE_2D_ARRAY: { auto* tex2dArray = static_cast(tex); return osg::FrameBufferAttachment(tex2dArray, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0); } case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: { auto* tex2dMsaaArray = static_cast(tex); return osg::FrameBufferAttachment(tex2dMsaaArray, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0); } #endif default: throw std::logic_error("Invalid texture type received"); } } unsigned int osgFaceControlledByMultiviewShader() { #ifdef OSG_HAS_MULTIVIEW return osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER; #else return 0; #endif } class UpdateRenderStagesCallback : public SceneUtil::NodeCallback { public: UpdateRenderStagesCallback(Stereo::MultiviewFramebuffer* multiviewFramebuffer) : mMultiviewFramebuffer(multiviewFramebuffer) { mViewport = new osg::Viewport(0, 0, multiviewFramebuffer->width(), multiviewFramebuffer->height()); mViewportStateset = new osg::StateSet(); mViewportStateset->setAttribute(mViewport.get()); } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); bool msaa = mMultiviewFramebuffer->samples() > 1; if (!Stereo::getMultiview()) { auto eye = static_cast(Stereo::Manager::instance().getEye(cv)); if (msaa) { renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerMsaaFbo(eye)); renderStage->setMultisampleResolveFramebufferObject(mMultiviewFramebuffer->layerFbo(eye)); } else { renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerFbo(eye)); } } // OSG tries to do a horizontal split, but we want to render to separate framebuffers instead. renderStage->setViewport(mViewport); cv->pushStateSet(mViewportStateset.get()); traverse(node, cv); cv->popStateSet(); } private: Stereo::MultiviewFramebuffer* mMultiviewFramebuffer; osg::ref_ptr mViewport; osg::ref_ptr mViewportStateset; }; MultiviewFramebuffer::MultiviewFramebuffer(int width, int height, int samples) : mWidth(width) , mHeight(height) , mSamples(samples) , mMultiview(getMultiview()) , mMultiviewFbo{ new osg::FrameBufferObject } , mLayerFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } , mLayerMsaaFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } { } MultiviewFramebuffer::~MultiviewFramebuffer() { } void MultiviewFramebuffer::attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) { if (mMultiview) { #ifdef OSG_HAS_MULTIVIEW mMultiviewColorTexture = createTextureArray(sourceFormat, sourceType, internalFormat); mMultiviewFbo->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMultiviewColorTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); for (unsigned i = 0; i < 2; i++) { mColorTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewColorTexture.get(), i); mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); } #endif } else { for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); } } } void MultiviewFramebuffer::attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) { if (mMultiview) { #ifdef OSG_HAS_MULTIVIEW mMultiviewDepthTexture = createTextureArray(sourceFormat, sourceType, internalFormat); mMultiviewFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMultiviewDepthTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); for (unsigned i = 0; i < 2; i++) { mDepthTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewDepthTexture.get(), i); mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); } #endif } else { for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); } } } osg::FrameBufferObject* MultiviewFramebuffer::multiviewFbo() { return mMultiviewFbo; } osg::FrameBufferObject* MultiviewFramebuffer::layerFbo(int i) { return mLayerFbo[i]; } osg::FrameBufferObject* MultiviewFramebuffer::layerMsaaFbo(int i) { return mLayerMsaaFbo[i]; } osg::Texture2DArray* MultiviewFramebuffer::multiviewColorBuffer() { return mMultiviewColorTexture; } osg::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer() { return mMultiviewDepthTexture; } osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) { return mColorTexture[i]; } osg::Texture2D* MultiviewFramebuffer::layerDepthBuffer(int i) { return mDepthTexture[i]; } void MultiviewFramebuffer::attachTo(osg::Camera* camera) { #ifdef OSG_HAS_MULTIVIEW if (mMultiview) { if (mMultiviewColorTexture) { camera->attach(osg::Camera::COLOR_BUFFER, mMultiviewColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mMultiviewColorTexture->getInternalFormat(); camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = false; } if (mMultiviewDepthTexture) { camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mMultiviewDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mMultiviewDepthTexture->getInternalFormat(); camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = false; } } #endif camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); if (!mCullCallback) mCullCallback = new UpdateRenderStagesCallback(this); camera->addCullCallback(mCullCallback); } void MultiviewFramebuffer::detachFrom(osg::Camera* camera) { #ifdef OSG_HAS_MULTIVIEW if (mMultiview) { if (mMultiviewColorTexture) { camera->detach(osg::Camera::COLOR_BUFFER); } if (mMultiviewDepthTexture) { camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER); } } #endif camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); if (mCullCallback) camera->removeCullCallback(mCullCallback); } osg::Texture2D* MultiviewFramebuffer::createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat) { osg::Texture2D* texture = new osg::Texture2D; texture->setTextureSize(mWidth, mHeight); texture->setSourceFormat(sourceFormat); texture->setSourceType(sourceType); texture->setInternalFormat(internalFormat); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); return texture; } osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat) { osg::Texture2DArray* textureArray = new osg::Texture2DArray; textureArray->setTextureSize(mWidth, mHeight, 2); textureArray->setSourceFormat(sourceFormat); textureArray->setSourceType(sourceType); textureArray->setInternalFormat(internalFormat); textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); return textureArray; } osg::FrameBufferAttachment makeSingleLayerAttachmentFromMultilayerAttachment(osg::FrameBufferAttachment attachment, int layer) { osg::Texture* tex = attachment.getTexture(); if (tex->getTextureTarget() == GL_TEXTURE_2D_ARRAY) return osg::FrameBufferAttachment(static_cast(tex), layer, 0); #ifdef OSG_HAS_MULTIVIEW if (tex->getTextureTarget() == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) return osg::FrameBufferAttachment(static_cast(tex), layer, 0); #endif Log(Debug::Error) << "Attempted to extract a layer from an unlayered texture"; return osg::FrameBufferAttachment(); } MultiviewFramebufferResolve::MultiviewFramebufferResolve(osg::FrameBufferObject* msaaFbo, osg::FrameBufferObject* resolveFbo, GLbitfield blitMask) : mResolveFbo(resolveFbo) , mMsaaFbo(msaaFbo) , mBlitMask(blitMask) { } void MultiviewFramebufferResolve::resolveImplementation(osg::State& state) { if (mDirtyLayers) setupLayers(); osg::GLExtensions* ext = state.get(); for (int view : {0, 1}) { mResolveLayers[view]->apply(state, osg::FrameBufferObject::BindTarget::DRAW_FRAMEBUFFER); mMsaaLayers[view]->apply(state, osg::FrameBufferObject::BindTarget::READ_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST); } } void MultiviewFramebufferResolve::setupLayers() { mDirtyLayers = false; std::vector components; if (mBlitMask & GL_DEPTH_BUFFER_BIT) components.push_back(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER); if (mBlitMask & GL_COLOR_BUFFER_BIT) components.push_back(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER); mMsaaLayers = { new osg::FrameBufferObject, new osg::FrameBufferObject }; mResolveLayers = { new osg::FrameBufferObject, new osg::FrameBufferObject }; for (auto component : components) { const auto& msaaAttachment = mMsaaFbo->getAttachment(component); mMsaaLayers[0]->setAttachment(component, makeSingleLayerAttachmentFromMultilayerAttachment(msaaAttachment, 0)); mMsaaLayers[1]->setAttachment(component, makeSingleLayerAttachmentFromMultilayerAttachment(msaaAttachment, 1)); const auto& resolveAttachment = mResolveFbo->getAttachment(component); mResolveLayers[0]->setAttachment(component, makeSingleLayerAttachmentFromMultilayerAttachment(resolveAttachment, 0)); mResolveLayers[1]->setAttachment(component, makeSingleLayerAttachmentFromMultilayerAttachment(resolveAttachment, 1)); mWidth = msaaAttachment.getTexture()->getTextureWidth(); mHeight = msaaAttachment.getTexture()->getTextureHeight(); } } #ifdef OSG_HAS_MULTIVIEW namespace { struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback { MultiviewFrustumCallback(Stereo::InitialFrustumCallback* ifc) : mIfc(ifc) { } void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override { bool nearCulling = false; bool farCulling = false; osg::BoundingBoxd bb; mIfc->setInitialFrustum(cullStack, bb, nearCulling, farCulling); frustum.setToBoundingBox(bb, nearCulling, farCulling); } Stereo::InitialFrustumCallback* mIfc; }; } #endif InitialFrustumCallback::InitialFrustumCallback(osg::Camera* camera) : mCamera(camera) { #ifdef OSG_HAS_MULTIVIEW camera->setInitialFrustumCallback(new MultiviewFrustumCallback(this)); #endif } InitialFrustumCallback::~InitialFrustumCallback() { #ifdef OSG_HAS_MULTIVIEW osg::ref_ptr camera; if(mCamera.lock(camera)) camera->setInitialFrustumCallback(nullptr); #endif } } openmw-openmw-0.48.0/components/stereo/multiview.hpp000066400000000000000000000134761445372753700226730ustar00rootroot00000000000000#ifndef STEREO_MULTIVIEW_H #define STEREO_MULTIVIEW_H #include #include #include #include #include #include namespace osg { class FrameBufferObject; class Texture; class Texture2D; class Texture2DArray; } namespace osgUtil { class RenderStage; } namespace Stereo { class UpdateRenderStagesCallback; //! Check if TextureView is supported. Results are undefined if called before configureExtensions(). bool getTextureViewSupported(); //! Check if Multiview should be used. Results are undefined if called before configureExtensions(). bool getMultiview(); //! Use the provided context to check what extensions are supported and configure use of multiview based on extensions and settings. void configureExtensions(unsigned int contextID); //! Sets the appropriate vertex buffer hint on OSG's display settings if needed void setVertexBufferHint(); //! Creates a Texture2D as a texture view into a Texture2DArray osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer); //! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling class MultiviewFramebuffer { public: MultiviewFramebuffer(int width, int height, int samples); ~MultiviewFramebuffer(); void attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); void attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); osg::FrameBufferObject* multiviewFbo(); osg::FrameBufferObject* layerFbo(int i); osg::FrameBufferObject* layerMsaaFbo(int i); osg::Texture2DArray* multiviewColorBuffer(); osg::Texture2DArray* multiviewDepthBuffer(); osg::Texture2D* layerColorBuffer(int i); osg::Texture2D* layerDepthBuffer(int i); void attachTo(osg::Camera* camera); void detachFrom(osg::Camera* camera); int width() const { return mWidth; } int height() const { return mHeight; } int samples() const { return mSamples; }; private: osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat); int mWidth; int mHeight; int mSamples; bool mMultiview; osg::ref_ptr mCullCallback; osg::ref_ptr mMultiviewFbo; std::array, 2> mLayerFbo; std::array, 2> mLayerMsaaFbo; osg::ref_ptr mMultiviewColorTexture; osg::ref_ptr mMultiviewDepthTexture; std::array, 2> mColorTexture; std::array, 2> mDepthTexture; }; //! Sets up a draw callback on the render stage that performs the MSAA resolve operation void setMultiviewMSAAResolveCallback(osgUtil::RenderStage* renderStage); //! Sets up or updates multiview matrices for the given stateset void setMultiviewMatrices(osg::StateSet* stateset, const std::array& projection, bool createInverseMatrices = false); //! Sets the width/height of a texture by first down-casting it to the appropriate type. Sets depth to 2 always for Texture2DArray and Texture2DMultisampleArray. void setMultiviewCompatibleTextureSize(osg::Texture* tex, int w, int h); //! Creates a texture (Texture2D, Texture2DMultisample, Texture2DArray, or Texture2DMultisampleArray) based on multiview settings and sample count. osg::ref_ptr createMultiviewCompatibleTexture(int width, int height, int samples); //! Returns a framebuffer attachment from the texture, returning a multiview attachment if the texture is one of Texture2DArray or Texture2DMultisampleArray osg::FrameBufferAttachment createMultiviewCompatibleAttachment(osg::Texture* tex); //! If OSG has multiview, returns the magic number used to tell OSG to create a multiview attachment. Otherwise returns 0. unsigned int osgFaceControlledByMultiviewShader(); //! Implements resolving a multisamples multiview framebuffer. Does not automatically reflect changes to the fbo attachments, must call dirty() when the fbo attachments change. class MultiviewFramebufferResolve { public: MultiviewFramebufferResolve(osg::FrameBufferObject* msaaFbo, osg::FrameBufferObject* resolveFbo, GLbitfield blitMask); void resolveImplementation(osg::State& state); void dirty() { mDirtyLayers = true; } const osg::FrameBufferObject* resolveFbo() const { return mResolveFbo; }; const osg::FrameBufferObject* msaaFbo() const { return mMsaaFbo; }; private: void setupLayers(); osg::ref_ptr mResolveFbo; std::array, 2> mResolveLayers{}; osg::ref_ptr mMsaaFbo; std::array, 2> mMsaaLayers{}; GLbitfield mBlitMask; bool mDirtyLayers = true; int mWidth = -1; int mHeight = -1; }; //! Wrapper for osg::CullSettings::InitialFrustumCallback, to avoid exposing osg multiview interfaces outside of multiview.cpp struct InitialFrustumCallback { InitialFrustumCallback(osg::Camera* camera); virtual ~InitialFrustumCallback(); virtual void setInitialFrustum(osg::CullStack& cullStack, osg::BoundingBoxd& bb, bool& nearCulling, bool& farCulling) const = 0; osg::observer_ptr mCamera; }; } #endif openmw-openmw-0.48.0/components/stereo/stereomanager.cpp000066400000000000000000000410221445372753700234610ustar00rootroot00000000000000#include "stereomanager.hpp" #include "multiview.hpp" #include "frustum.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Stereo { // Update stereo view/projection during update class StereoUpdateCallback final : public osg::Callback { public: StereoUpdateCallback(Manager* stereoView) : stereoView(stereoView) {} bool run(osg::Object* object, osg::Object* data) override { auto b = traverse(object, data); stereoView->update(); return b; } Manager* stereoView; }; // Update states during cull class BruteForceStereoStatesetUpdateCallback final : public SceneUtil::StateSetUpdater { public: BruteForceStereoStatesetUpdateCallback(Manager* manager) : mManager(manager) { } protected: virtual void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); } virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { } void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); } void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); } private: Manager* mManager; }; // Update states during cull class MultiviewStereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater { public: MultiviewStereoStatesetUpdateCallback(Manager* manager) : mManager(manager) { } protected: virtual void setDefaults(osg::StateSet* stateset) { stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "invProjectionMatrixMultiView", 2)); } virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { mManager->updateMultiviewStateset(stateset); } private: Manager* mManager; }; static Manager* sInstance = nullptr; Manager& Manager::instance() { return *sInstance; } struct CustomViewCallback : public Manager::UpdateViewCallback { public: CustomViewCallback(); void updateView(View& left, View& right) override; private: View mLeft; View mRight; }; Manager::Manager(osgViewer::Viewer* viewer) : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0,0) , mFrustumManager(nullptr) , mUpdateViewCallback(nullptr) { if (sInstance) throw std::logic_error("Double instance of Stereo::Manager"); sInstance = this; if (Settings::Manager::getBool("use custom view", "Stereo")) mUpdateViewCallback = std::make_shared(); if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) { osg::Vec2i eyeResolution = osg::Vec2i(); eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); overrideEyeResolution(eyeResolution); } } Manager::~Manager() { } void Manager::initializeStereo(osg::GraphicsContext* gc) { mMainCamera->addUpdateCallback(mUpdateCallback); mFrustumManager = std::make_unique(mViewer->getCamera()); auto ci = gc->getState()->getContextID(); configureExtensions(ci); if(getMultiview()) setupOVRMultiView2Technique(); else setupBruteForceTechnique(); updateStereoFramebuffer(); } void Manager::shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const { if (getMultiview()) { defines["useOVR_multiview"] = "1"; defines["numViews"] = "2"; } else { defines["useOVR_multiview"] = "0"; defines["numViews"] = "1"; } } void Manager::overrideEyeResolution(const osg::Vec2i& eyeResolution) { mEyeResolutionOverride = eyeResolution; mEyeResolutionOverriden = true; //if (mMultiviewFramebuffer) // updateStereoFramebuffer(); } void Manager::screenResolutionChanged() { updateStereoFramebuffer(); } osg::Vec2i Manager::eyeResolution() { if (mEyeResolutionOverriden) return mEyeResolutionOverride; auto width = mMainCamera->getViewport()->width() / 2; auto height = mMainCamera->getViewport()->height(); return osg::Vec2i(width, height); } void Manager::disableStereoForNode(osg::Node* node) { // Re-apply the main camera's full viewport to return to full screen rendering. node->getOrCreateStateSet()->setAttribute(mMainCamera->getViewport()); } void Manager::setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique) { if (mFrustumManager) mFrustumManager->setShadowTechnique(shadowTechnique); } void Manager::setupBruteForceTechnique() { auto* ds = osg::DisplaySettings::instance().get(); ds->setStereo(true); ds->setStereoMode(osg::DisplaySettings::StereoMode::HORIZONTAL_SPLIT); ds->setUseSceneViewForStereoHint(true); mMainCamera->addCullCallback(new BruteForceStereoStatesetUpdateCallback(this)); struct ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback { ComputeStereoMatricesCallback(Manager* sv) : mManager(sv) { } osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override { (void)projection; return mManager->computeEyeProjection(0, false); } osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override { return view * mManager->computeEyeViewOffset(0); } osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override { (void)projection; return mManager->computeEyeProjection(1, false); } osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override { return view * mManager->computeEyeViewOffset(1); } Manager* mManager; }; auto* renderer = static_cast(mMainCamera->getRenderer()); for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) { sceneView->setComputeStereoMatricesCallback(new ComputeStereoMatricesCallback(this)); auto* cvMain = sceneView->getCullVisitor(); auto* cvLeft = sceneView->getCullVisitorLeft(); auto* cvRight = sceneView->getCullVisitorRight(); if (!cvMain) sceneView->setCullVisitor(cvMain = new osgUtil::CullVisitor()); if (!cvLeft) sceneView->setCullVisitor(cvLeft = cvMain->clone()); if (!cvRight) sceneView->setCullVisitor(cvRight = cvMain->clone()); // Osg by default gives cullVisitorLeft and cullVisitor the same identifier. // So we make our own to avoid confusion cvMain->setIdentifier(mIdentifierMain); cvLeft->setIdentifier(mIdentifierLeft); cvRight->setIdentifier(mIdentifierRight); } } void Manager::setupOVRMultiView2Technique() { auto* ds = osg::DisplaySettings::instance().get(); ds->setStereo(false); mMainCamera->addCullCallback(new MultiviewStereoStatesetUpdateCallback(this)); } void Manager::updateStereoFramebuffer() { //VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created //auto samples = Settings::Manager::getInt("antialiasing", "Video"); //auto eyeRes = eyeResolution(); //if (mMultiviewFramebuffer) // mMultiviewFramebuffer->detachFrom(mMainCamera); //mMultiviewFramebuffer = std::make_shared(static_cast(eyeRes.x()), static_cast(eyeRes.y()), samples); //mMultiviewFramebuffer->attachColorComponent(SceneUtil::Color::colorSourceFormat(), SceneUtil::Color::colorSourceType(), SceneUtil::Color::colorInternalFormat()); //mMultiviewFramebuffer->attachDepthComponent(SceneUtil::AutoDepth::depthSourceFormat(), SceneUtil::AutoDepth::depthSourceType(), SceneUtil::AutoDepth::depthInternalFormat()); //mMultiviewFramebuffer->attachTo(mMainCamera); } void Manager::update() { double near_ = 1.f; double far_ = 10000.f; near_ = Settings::Manager::getFloat("near clip", "Camera"); far_ = Settings::Manager::getFloat("viewing distance", "Camera"); if (mUpdateViewCallback) { mUpdateViewCallback->updateView(mView[0], mView[1]); mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true); mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); if (SceneUtil::AutoDepth::isReversed()) { mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); } View masterView; masterView.fov.angleDown = std::min(mView[0].fov.angleDown, mView[1].fov.angleDown); masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); auto projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else { auto* ds = osg::DisplaySettings::instance().get(); auto viewMatrix = mMainCamera->getViewMatrix(); auto projectionMatrix = mMainCamera->getProjectionMatrix(); auto s = ds->getEyeSeparation() * Constants::UnitsPerMeter; mViewOffsetMatrix[0] = osg::Matrixd( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, s, 0.0, 0.0, 1.0); mViewOffsetMatrix[1] = osg::Matrixd( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -s, 0.0, 0.0, 1.0); mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix); mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix); if (SceneUtil::AutoDepth::isReversed()) { mProjectionMatrixReverseZ[0] = ds->computeLeftEyeProjectionImplementation(mMasterProjectionMatrix); mProjectionMatrixReverseZ[1] = ds->computeRightEyeProjectionImplementation(mMasterProjectionMatrix); } } mFrustumManager->update( { mProjectionMatrix[0], mProjectionMatrix[1] }); } void Manager::updateMultiviewStateset(osg::StateSet* stateset) { std::array projectionMatrices; for (int view : {0, 1}) projectionMatrices[view] = computeEyeViewOffset(view) * computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()); Stereo::setMultiviewMatrices(stateset, projectionMatrices, true); } void Manager::setUpdateViewCallback(std::shared_ptr cb) { mUpdateViewCallback = cb; } void Manager::setCullCallback(osg::ref_ptr cb) { mMainCamera->setCullCallback(cb); } osg::Matrixd Manager::computeEyeProjection(int view, bool reverseZ) const { return reverseZ ? mProjectionMatrixReverseZ[view] : mProjectionMatrix[view]; } osg::Matrixd Manager::computeEyeViewOffset(int view) const { return mViewOffsetMatrix[view]; } Eye Manager::getEye(const osgUtil::CullVisitor* cv) const { if (cv->getIdentifier() == mIdentifierMain) return Eye::Center; if (cv->getIdentifier() == mIdentifierLeft) return Eye::Left; if (cv->getIdentifier() == mIdentifierRight) return Eye::Right; return Eye::Center; } bool getStereo() { static bool stereo = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); return stereo; } CustomViewCallback::CustomViewCallback() { mLeft.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); mLeft.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); mLeft.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); mLeft.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); mLeft.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); mLeft.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); mLeft.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); mLeft.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); mLeft.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); mLeft.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); mLeft.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); mRight.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); mRight.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); mRight.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); mRight.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); mRight.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); mRight.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); mRight.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); mRight.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); mRight.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); mRight.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); mRight.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); } void CustomViewCallback::updateView(View& left, View& right) { left = mLeft; right = mRight; } } openmw-openmw-0.48.0/components/stereo/stereomanager.hpp000066400000000000000000000106351445372753700234740ustar00rootroot00000000000000#ifndef STEREO_MANAGER_H #define STEREO_MANAGER_H #include #include #include #include #include #include #include #include #include namespace osg { class FrameBufferObject; class Texture2D; class Texture2DArray; } namespace osgViewer { class Viewer; } namespace SceneUtil { class MWShadowTechnique; } namespace Stereo { class MultiviewFramebuffer; class StereoFrustumManager; class MultiviewStereoStatesetUpdateCallback; bool getStereo(); //! Class that provides tools for managing stereo mode class Manager { public: struct UpdateViewCallback { virtual ~UpdateViewCallback() = default; //! Called during the update traversal of every frame to update stereo views. virtual void updateView(View& left, View& right) = 0; }; //! Gets the singleton instance static Manager& instance(); Manager(osgViewer::Viewer* viewer); ~Manager(); //! Called during update traversal void update(); void initializeStereo(osg::GraphicsContext* gc); //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); //! Set the cull callback on the appropriate camera object void setCullCallback(osg::ref_ptr cb); osg::Matrixd computeEyeProjection(int view, bool reverseZ) const; osg::Matrixd computeEyeViewOffset(int view) const; //! Sets up any definitions necessary for stereo rendering void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const; const std::shared_ptr& multiviewFramebuffer() { return mMultiviewFramebuffer; }; //! Sets rendering resolution of each eye to eyeResolution. //! Once set, there will no longer be any connection between rendering resolution and screen/window resolution. void overrideEyeResolution(const osg::Vec2i& eyeResolution); //! Notify stereo manager that the screen/window resolution has changed. void screenResolutionChanged(); //! Get current eye resolution osg::Vec2i eyeResolution(); //! The projection intended for rendering. When reverse Z is enabled, this is not the same as the camera's projection matrix, //! and therefore must be provided to the manager explicitly. void setMasterProjectionMatrix(const osg::Matrixd& projectionMatrix) { mMasterProjectionMatrix = projectionMatrix; } //! Causes the subgraph represented by the node to draw to the full viewport. //! This has no effect if stereo is not enabled void disableStereoForNode(osg::Node* node); void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); /// Determine which view the cull visitor belongs to Eye getEye(const osgUtil::CullVisitor* cv) const; private: friend class MultiviewStereoStatesetUpdateCallback; void updateMultiviewStateset(osg::StateSet* stateset); void updateStereoFramebuffer(); void setupBruteForceTechnique(); void setupOVRMultiView2Technique(); osg::ref_ptr mViewer; osg::ref_ptr mMainCamera; osg::ref_ptr mUpdateCallback; std::string mError; osg::Matrixd mMasterProjectionMatrix; std::shared_ptr mMultiviewFramebuffer; bool mEyeResolutionOverriden; osg::Vec2i mEyeResolutionOverride; std::array mView; std::array mViewOffsetMatrix; std::array mProjectionMatrix; std::array mProjectionMatrixReverseZ; std::unique_ptr mFrustumManager; std::shared_ptr mUpdateViewCallback; using Identifier = osgUtil::CullVisitor::Identifier; osg::ref_ptr mIdentifierMain = new Identifier(); osg::ref_ptr mIdentifierLeft = new Identifier(); osg::ref_ptr mIdentifierRight = new Identifier(); }; } #endif openmw-openmw-0.48.0/components/stereo/types.cpp000066400000000000000000000105541445372753700217770ustar00rootroot00000000000000#include "types.hpp" #include namespace Stereo { Pose Pose::operator+(const Pose& rhs) { Pose pose = *this; pose.position += this->orientation * rhs.position; pose.orientation = rhs.orientation * this->orientation; return pose; } const Pose& Pose::operator+=(const Pose& rhs) { *this = *this + rhs; return *this; } Pose Pose::operator*(float scalar) { Pose pose = *this; pose.position *= scalar; return pose; } const Pose& Pose::operator*=(float scalar) { *this = *this * scalar; return *this; } Pose Pose::operator/(float scalar) { Pose pose = *this; pose.position /= scalar; return pose; } const Pose& Pose::operator/=(float scalar) { *this = *this / scalar; return *this; } bool Pose::operator==(const Pose& rhs) const { return position == rhs.position && orientation == rhs.orientation; } osg::Matrix View::viewMatrix(bool useGLConventions) { auto position = pose.position; auto orientation = pose.orientation; if (useGLConventions) { // When applied as an offset to an existing view matrix, // that view matrix will already convert points to a camera space // with opengl conventions. So we need to convert offsets to opengl // conventions. float y = position.y(); float z = position.z(); position.y() = z; position.z() = -y; y = orientation.y(); z = orientation.z(); orientation.y() = z; orientation.z() = -y; osg::Matrix viewMatrix; viewMatrix.setTrans(-position); viewMatrix.postMultRotate(orientation.conj()); return viewMatrix; } else { osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0); osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1); osg::Matrix viewMatrix; viewMatrix.makeLookAt(position, position + forward, up); return viewMatrix; } } osg::Matrix View::perspectiveMatrix(float near, float far, bool reverseZ) { const float tanLeft = tanf(fov.angleLeft); const float tanRight = tanf(fov.angleRight); const float tanDown = tanf(fov.angleDown); const float tanUp = tanf(fov.angleUp); const float tanWidth = tanRight - tanLeft; const float tanHeight = tanUp - tanDown; float matrix[16] = {}; matrix[0] = 2 / tanWidth; matrix[4] = 0; matrix[8] = (tanRight + tanLeft) / tanWidth; matrix[12] = 0; matrix[1] = 0; matrix[5] = 2 / tanHeight; matrix[9] = (tanUp + tanDown) / tanHeight; matrix[13] = 0; if (reverseZ) { matrix[2] = 0; matrix[6] = 0; matrix[10] = (2.f * near) / (far - near); matrix[14] = ((2.f * near) * far) / (far - near); } else { matrix[2] = 0; matrix[6] = 0; matrix[10] = -(far + near) / (far - near); matrix[14] = -(far * (2.f * near)) / (far - near); } matrix[3] = 0; matrix[7] = 0; matrix[11] = -1; matrix[15] = 0; return osg::Matrix(matrix); } bool FieldOfView::operator==(const FieldOfView& rhs) const { return angleDown == rhs.angleDown && angleUp == rhs.angleUp && angleLeft == rhs.angleLeft && angleRight == rhs.angleRight; } bool View::operator==(const View& rhs) const { return pose == rhs.pose && fov == rhs.fov; } std::ostream& operator <<( std::ostream& os, const Pose& pose) { os << "position=" << pose.position << ", orientation=" << pose.orientation; return os; } std::ostream& operator <<( std::ostream& os, const FieldOfView& fov) { os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp; return os; } std::ostream& operator <<( std::ostream& os, const View& view) { os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >"; return os; } } openmw-openmw-0.48.0/components/stereo/types.hpp000066400000000000000000000027021445372753700220000ustar00rootroot00000000000000#ifndef STEREO_TYPES_H #define STEREO_TYPES_H #include #include namespace Stereo { enum class Eye { Left = 0, Right = 1, Center = 2 }; struct Pose { //! Position in space osg::Vec3 position{ 0,0,0 }; //! Orientation in space. osg::Quat orientation{ 0,0,0,1 }; //! Add one pose to another Pose operator+(const Pose& rhs); const Pose& operator+=(const Pose& rhs); //! Scale a pose (does not affect orientation) Pose operator*(float scalar); const Pose& operator*=(float scalar); Pose operator/(float scalar); const Pose& operator/=(float scalar); bool operator==(const Pose& rhs) const; }; struct FieldOfView { float angleLeft{ 0.f }; float angleRight{ 0.f }; float angleUp{ 0.f }; float angleDown{ 0.f }; bool operator==(const FieldOfView& rhs) const; }; struct View { Pose pose; FieldOfView fov; bool operator==(const View& rhs) const; osg::Matrix viewMatrix(bool useGLConventions); osg::Matrix perspectiveMatrix(float near, float far, bool reverseZ); }; std::ostream& operator <<(std::ostream& os, const Pose& pose); std::ostream& operator <<(std::ostream& os, const FieldOfView& fov); std::ostream& operator <<(std::ostream& os, const View& view); } #endif openmw-openmw-0.48.0/components/terrain/000077500000000000000000000000001445372753700202655ustar00rootroot00000000000000openmw-openmw-0.48.0/components/terrain/buffercache.cpp000066400000000000000000000217311445372753700232320ustar00rootroot00000000000000#include "buffercache.hpp" #include #include #include "defs.hpp" namespace { template osg::ref_ptr createIndexBuffer(unsigned int flags, unsigned int verts) { // LOD level n means every 2^n-th vertex is kept, but we currently handle LOD elsewhere. size_t lodLevel = 0;//(flags >> (4*4)); size_t lodDeltas[4]; for (int i=0; i<4; ++i) lodDeltas[i] = (flags >> (4*i)) & (0xf); bool anyDeltas = (lodDeltas[Terrain::North] || lodDeltas[Terrain::South] || lodDeltas[Terrain::West] || lodDeltas[Terrain::East]); size_t increment = static_cast(1) << lodLevel; assert(increment < verts); osg::ref_ptr indices (new IndexArrayType(osg::PrimitiveSet::TRIANGLES)); indices->reserve((verts-1)*(verts-1)*2*3 / increment); size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; // If any edge needs stitching we'll skip all edges at this point, // mainly because stitching one edge would have an effect on corners and on the adjacent edges if (anyDeltas) { colStart += increment; colEnd -= increment; rowEnd -= increment; rowStart += increment; } for (size_t row = rowStart; row < rowEnd; row += increment) { for (size_t col = colStart; col < colEnd; col += increment) { // diamond pattern if ((row + col%2) % 2 == 1) { indices->push_back(verts*(col+increment)+row); indices->push_back(verts*(col+increment)+row+increment); indices->push_back(verts*col+row+increment); indices->push_back(verts*col+row); indices->push_back(verts*(col+increment)+row); indices->push_back(verts*(col)+row+increment); } else { indices->push_back(verts*col+row); indices->push_back(verts*(col+increment)+row+increment); indices->push_back(verts*col+row+increment); indices->push_back(verts*col+row); indices->push_back(verts*(col+increment)+row); indices->push_back(verts*(col+increment)+row+increment); } } } size_t innerStep = increment; if (anyDeltas) { // Now configure LOD transitions at the edges - this is pretty tedious, // and some very long and boring code, but it works great // South size_t row = 0; size_t outerStep = static_cast(1) << (lodDeltas[Terrain::South] + lodLevel); for (size_t col = 0; col < verts-1; col += outerStep) { indices->push_back(verts*col+row); indices->push_back(verts*(col+outerStep)+row); // Make sure not to touch the right edge if (col+outerStep == verts-1) indices->push_back(verts*(col+outerStep-innerStep)+row+innerStep); else indices->push_back(verts*(col+outerStep)+row+innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges if (col+i == 0 || col+i == verts-1-innerStep) continue; indices->push_back(verts*(col)+row); indices->push_back(verts*(col+i+innerStep)+row+innerStep); indices->push_back(verts*(col+i)+row+innerStep); } } // North row = verts-1; outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel); for (size_t col = 0; col < verts-1; col += outerStep) { indices->push_back(verts*(col+outerStep)+row); indices->push_back(verts*col+row); // Make sure not to touch the left edge if (col == 0) indices->push_back(verts*(col+innerStep)+row-innerStep); else indices->push_back(verts*col+row-innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges if (col+i == 0 || col+i == verts-1-innerStep) continue; indices->push_back(verts*(col+i)+row-innerStep); indices->push_back(verts*(col+i+innerStep)+row-innerStep); indices->push_back(verts*(col+outerStep)+row); } } // West size_t col = 0; outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel); for (row = 0; row < verts-1; row += outerStep) { indices->push_back(verts*col+row+outerStep); indices->push_back(verts*col+row); // Make sure not to touch the top edge if (row+outerStep == verts-1) indices->push_back(verts*(col+innerStep)+row+outerStep-innerStep); else indices->push_back(verts*(col+innerStep)+row+outerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == verts-1-innerStep) continue; indices->push_back(verts*col+row); indices->push_back(verts*(col+innerStep)+row+i); indices->push_back(verts*(col+innerStep)+row+i+innerStep); } } // East col = verts-1; outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel); for (row = 0; row < verts-1; row += outerStep) { indices->push_back(verts*col+row); indices->push_back(verts*col+row+outerStep); // Make sure not to touch the bottom edge if (row == 0) indices->push_back(verts*(col-innerStep)+row+innerStep); else indices->push_back(verts*(col-innerStep)+row); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == verts-1-innerStep) continue; indices->push_back(verts*col+row+outerStep); indices->push_back(verts*(col-innerStep)+row+i+innerStep); indices->push_back(verts*(col-innerStep)+row+i); } } } return indices; } } namespace Terrain { osg::ref_ptr BufferCache::getUVBuffer(unsigned int numVerts) { std::lock_guard lock(mUvBufferMutex); if (mUvBufferMap.find(numVerts) != mUvBufferMap.end()) { return mUvBufferMap[numVerts]; } int vertexCount = numVerts * numVerts; osg::ref_ptr uvs (new osg::Vec2Array(osg::Array::BIND_PER_VERTEX)); uvs->reserve(vertexCount); for (unsigned int col = 0; col < numVerts; ++col) { for (unsigned int row = 0; row < numVerts; ++row) { uvs->push_back(osg::Vec2f(col / static_cast(numVerts-1), ((numVerts-1) - row) / static_cast(numVerts-1))); } } // Assign a VBO here to enable state sharing between different Geometries. uvs->setVertexBufferObject(new osg::VertexBufferObject); mUvBufferMap[numVerts] = uvs; return uvs; } osg::ref_ptr BufferCache::getIndexBuffer(unsigned int numVerts, unsigned int flags) { std::pair id = std::make_pair(numVerts, flags); std::lock_guard lock(mIndexBufferMutex); if (mIndexBufferMap.find(id) != mIndexBufferMap.end()) { return mIndexBufferMap[id]; } osg::ref_ptr buffer; if (numVerts*numVerts <= (0xffffu)) buffer = createIndexBuffer(flags, numVerts); else buffer = createIndexBuffer(flags, numVerts); // Assign a EBO here to enable state sharing between different Geometries. buffer->setElementBufferObject(new osg::ElementBufferObject); mIndexBufferMap[id] = buffer; return buffer; } void BufferCache::clearCache() { { std::lock_guard lock(mIndexBufferMutex); mIndexBufferMap.clear(); } { std::lock_guard lock(mUvBufferMutex); mUvBufferMap.clear(); } } void BufferCache::releaseGLObjects(osg::State *state) { { std::lock_guard lock(mIndexBufferMutex); for (const auto& [_, indexbuffer] : mIndexBufferMap) indexbuffer->releaseGLObjects(state); } { std::lock_guard lock(mUvBufferMutex); for (const auto& [_, uvbuffer] : mUvBufferMap) uvbuffer->releaseGLObjects(state); } } } openmw-openmw-0.48.0/components/terrain/buffercache.hpp000066400000000000000000000024161445372753700232360ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H #define COMPONENTS_TERRAIN_BUFFERCACHE_H #include #include #include #include #include namespace Terrain { /// @brief Implements creation and caching of vertex buffers for terrain chunks. class BufferCache { public: /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) /// @note Thread safe. osg::ref_ptr getIndexBuffer (unsigned int numVerts, unsigned int flags); /// @note Thread safe. osg::ref_ptr getUVBuffer(unsigned int numVerts); void clearCache(); void releaseGLObjects(osg::State* state); private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. std::map, osg::ref_ptr > mIndexBufferMap; std::mutex mIndexBufferMutex; std::map > mUvBufferMap; std::mutex mUvBufferMutex; }; } #endif openmw-openmw-0.48.0/components/terrain/cellborder.cpp000066400000000000000000000066011445372753700231110ustar00rootroot00000000000000#include "cellborder.hpp" #include #include #include #include "world.hpp" #include #include #include namespace Terrain { CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, Resource::SceneManager* sceneManager) : mWorld(world) , mSceneManager(sceneManager) , mRoot(root) , mBorderMask(borderMask) { } osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset, osg::Vec4f color) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); size *= cellSize; osg::ref_ptr vertices = new osg::Vec3Array; osg::ref_ptr colors = new osg::Vec4Array; osg::ref_ptr normals = new osg::Vec3Array; normals->push_back(osg::Vec3(0.0f,-1.0f, 0.0f)); float borderStep = size / ((float)borderSegments); for (int i = 0; i <= 2 * borderSegments; ++i) { osg::Vec3f pos = i < borderSegments ? osg::Vec3(i * borderStep,0.0f,0.0f) : osg::Vec3(size, (i - borderSegments) * borderStep,0.0f); pos += cellCorner; pos += osg::Vec3f(0,0, terrain->getHeightAt(pos) + offset); vertices->push_back(pos); osg::Vec4f col = i % 2 == 0 ? osg::Vec4f(0,0,0,1) : color; colors->push_back(col); } osg::ref_ptr border = new osg::Geometry; border->setVertexArray(vertices.get()); border->setNormalArray(normals.get()); border->setNormalBinding(osg::Geometry::BIND_OVERALL); border->setColorArray(colors.get()); border->setColorBinding(osg::Geometry::BIND_PER_VERTEX); border->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP,0,vertices->size())); osg::ref_ptr borderGroup = new osg::Group; borderGroup->addChild(border.get()); osg::StateSet *stateSet = borderGroup->getOrCreateStateSet(); osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); stateSet->setAttribute(material); osg::PolygonMode* polygonmode = new osg::PolygonMode; polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); sceneManager->recreateShaders(borderGroup, "debug"); borderGroup->setNodeMask(mask); return borderGroup; } void CellBorder::createCellBorderGeometry(int x, int y) { auto borderGroup = createBorderGeometry(x, y, 1.f, mWorld->getStorage(), mSceneManager, mBorderMask); mRoot->addChild(borderGroup); mCellBorderNodes[std::make_pair(x,y)] = borderGroup; } void CellBorder::destroyCellBorderGeometry(int x, int y) { CellGrid::iterator it = mCellBorderNodes.find(std::make_pair(x,y)); if (it == mCellBorderNodes.end()) return; osg::ref_ptr borderNode = it->second; mRoot->removeChild(borderNode); mCellBorderNodes.erase(it); } void CellBorder::destroyCellBorderGeometry() { for (const auto& v : mCellBorderNodes) mRoot->removeChild(v.second); mCellBorderNodes.clear(); } } openmw-openmw-0.48.0/components/terrain/cellborder.hpp000066400000000000000000000021641445372753700231160ustar00rootroot00000000000000#ifndef GAME_RENDER_CELLBORDER #define GAME_RENDER_CELLBORDER #include #include namespace Resource { class SceneManager; } namespace Terrain { class Storage; class World; /** * @Brief Handles the debug cell borders. */ class CellBorder { public: typedef std::map, osg::ref_ptr > CellGrid; CellBorder(Terrain::World *world, osg::Group *root, int borderMask, Resource::SceneManager* sceneManager); void createCellBorderGeometry(int x, int y); void destroyCellBorderGeometry(int x, int y); /** Destroys the geometry for all borders. */ void destroyCellBorderGeometry(); static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); protected: Terrain::World *mWorld; Resource::SceneManager* mSceneManager; osg::Group *mRoot; CellGrid mCellBorderNodes; int mBorderMask; }; } #endif openmw-openmw-0.48.0/components/terrain/chunkmanager.cpp000066400000000000000000000272411445372753700234420ustar00rootroot00000000000000#include "chunkmanager.hpp" #include #include #include #include #include #include #include "terraindrawable.hpp" #include "material.hpp" #include "storage.hpp" #include "texturemanager.hpp" #include "compositemaprenderer.hpp" namespace Terrain { ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer) : GenericResourceManager(nullptr) , mStorage(storage) , mSceneManager(sceneMgr) , mTextureManager(textureManager) , mCompositeMapRenderer(renderer) , mNodeMask(0) , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } struct FindChunkTemplate { void operator() (ChunkId id, osg::Object* obj) { if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId)) mFoundTemplate = obj; } ChunkId mId; osg::ref_ptr mFoundTemplate; }; osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { // Override lod with the vertexLodMod adjusted value. // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. lod = static_cast(lodFlags >> (4*4)); ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); else { FindChunkTemplate find; find.mId = id; mCache->call(find); TerrainDrawable* templateGeometry = find.mFoundTemplate ? static_cast(find.mFoundTemplate.get()) : nullptr; osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(id, node.get()); return node; } } void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize()); } void ChunkManager::clearCache() { GenericResourceManager::clearCache(); mBufferCache.clearCache(); } void ChunkManager::releaseGLObjects(osg::State *state) { GenericResourceManager::releaseGLObjects(state); mBufferCache.releaseGLObjects(state); } osg::ref_ptr ChunkManager::createCompositeMapRTT() { osg::ref_ptr texture = new osg::Texture2D; texture->setTextureWidth(mCompositeMapSize); texture->setTextureHeight(mCompositeMapSize); texture->setInternalFormat(GL_RGB); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); return texture; } void ChunkManager::createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap) { if (chunkSize > mMaxCompGeometrySize) { createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); } else { float left = texCoords.x()*2.f-1; float top = texCoords.y()*2.f-1; float width = texCoords.z()*2.f; float height = texCoords.w()*2.f; std::vector > passes = createPasses(chunkSize, chunkCenter, true); for (std::vector >::iterator it = passes.begin(); it != passes.end(); ++it) { osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3(left,top,0), osg::Vec3(width,0,0), osg::Vec3(0,height,0)); geom->setUseDisplayList(false); // don't bother making a display list for an object that is just rendered once. geom->setUseVertexBufferObjects(false); geom->setTexCoordArray(1, geom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); geom->setStateSet(*it); compositeMap.mDrawables.emplace_back(geom); } } } std::vector > ChunkManager::createPasses(float chunkSize, const osg::Vec2f &chunkCenter, bool forCompositeMap) { std::vector layerList; std::vector > blendmaps; mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList); bool useShaders = mSceneManager->getForceShaders(); if (!mSceneManager->getClampLighting()) useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps std::vector layers; { for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { TextureLayer textureLayer; textureLayer.mParallax = it->mParallax; textureLayer.mSpecular = it->mSpecular; textureLayer.mDiffuseMap = mTextureManager->getTexture(it->mDiffuseMap); if (!forCompositeMap && !it->mNormalMap.empty()) textureLayer.mNormalMap = mTextureManager->getTexture(it->mNormalMap); if (it->requiresShaders()) useShaders = true; layers.push_back(textureLayer); } } if (forCompositeMap) useShaders = false; std::vector > blendmapTextures; for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) { osg::ref_ptr texture (new osg::Texture2D); texture->setImage(*it); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setResizeNonPowerOfTwoHint(false); blendmapTextures.push_back(texture); } float blendmapScale = mStorage->getBlendmapScale(chunkSize); return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) { osg::ref_ptr geometry (new TerrainDrawable); if (!templateGeometry) { osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); osg::ref_ptr colors (new osg::Vec4ubArray); colors->setNormalize(true); mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); osg::ref_ptr vbo (new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); geometry->setVertexArray(positions); geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); } else { // Unfortunately we need to copy vertex data because of poor coupling with VertexBufferObject. osg::ref_ptr positions = static_cast(templateGeometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr normals = static_cast(templateGeometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr colors = static_cast(templateGeometry->getColorArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr vbo (new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); normals->setVertexBufferObject(vbo); colors->setVertexBufferObject(vbo); geometry->setVertexArray(positions); geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); } geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); if (chunkSize <= 1.f) geometry->setLightListCallback(new SceneUtil::LightListCallback); unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1; geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags)); bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; geometry->setTexCoordArrayList(osg::Geometry::ArrayList(numUvSets, mBufferCache.getUVBuffer(numVerts))); geometry->createClusterCullingCallback(); geometry->setStateSet(mMultiPassRoot); if (templateGeometry) { if (templateGeometry->getCompositeMap()) { geometry->setCompositeMap(templateGeometry->getCompositeMap()); geometry->setCompositeMapRenderer(mCompositeMapRenderer); } geometry->setPasses(templateGeometry->getPasses()); } else { if (useCompositeMap) { osg::ref_ptr compositeMap = new CompositeMap; compositeMap->mTexture = createCompositeMapRTT(); createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap); mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); geometry->setCompositeMap(compositeMap); geometry->setCompositeMapRenderer(mCompositeMapRenderer); TextureLayer layer; layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); } } geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } geometry->setNodeMask(mNodeMask); return geometry; } } openmw-openmw-0.48.0/components/terrain/chunkmanager.hpp000066400000000000000000000051261445372753700234450ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H #define OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H #include #include #include "buffercache.hpp" #include "quadtreeworld.hpp" namespace osg { class Group; class Texture2D; } namespace Resource { class SceneManager; } namespace Terrain { class TextureManager; class CompositeMapRenderer; class Storage; class CompositeMap; class TerrainDrawable; typedef std::tuple ChunkId; // Center, Lod, Lod Flags /// @brief Handles loading and caching of terrain chunks class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } void setNodeMask(unsigned int mask) { mNodeMask = mask; } unsigned int getNodeMask() override { return mNodeMask; } void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void clearCache() override; void releaseGLObjects(osg::State* state) override; private: osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry); osg::ref_ptr createCompositeMapRTT(); void createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map); std::vector > createPasses(float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap); Terrain::Storage* mStorage; Resource::SceneManager* mSceneManager; TextureManager* mTextureManager; CompositeMapRenderer* mCompositeMapRenderer; BufferCache mBufferCache; osg::ref_ptr mMultiPassRoot; unsigned int mNodeMask; unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; }; } #endif openmw-openmw-0.48.0/components/terrain/compositemaprenderer.cpp000066400000000000000000000125661445372753700252320ustar00rootroot00000000000000#include "compositemaprenderer.hpp" #include #include #include #include namespace Terrain { CompositeMapRenderer::CompositeMapRenderer() : mTargetFrameRate(120) , mMinimumTimeAvailable(0.0025) { setSupportsDisplayList(false); setCullingActive(false); mFBO = new osg::FrameBufferObject; getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } CompositeMapRenderer::~CompositeMapRenderer() { } void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const { double dt = mTimer.time_s(); dt = std::min(dt, 0.2); mTimer.setStartTick(); double targetFrameTime = 1.0/static_cast(mTargetFrameRate); double conservativeTimeRatio(0.75); double availableTime = std::max((targetFrameTime - dt)*conservativeTimeRatio, mMinimumTimeAvailable); std::lock_guard lock(mMutex); if (mImmediateCompileSet.empty() && mCompileSet.empty()) return; while (!mImmediateCompileSet.empty()) { osg::ref_ptr node = *mImmediateCompileSet.begin(); mImmediateCompileSet.erase(node); mMutex.unlock(); compile(*node, renderInfo, nullptr); mMutex.lock(); } double timeLeft = availableTime; while (!mCompileSet.empty() && timeLeft > 0) { osg::ref_ptr node = *mCompileSet.begin(); mCompileSet.erase(node); mMutex.unlock(); compile(*node, renderInfo, &timeLeft); mMutex.lock(); if (node->mCompiled < node->mDrawables.size()) { // We did not compile the map fully. // Place it back to queue to continue work in the next time. mCompileSet.insert(node); } } mTimer.setStartTick(); } void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo &renderInfo, double* timeLeft) const { // if there are no more external references we can assume the texture is no longer required if (compositeMap.mTexture->referenceCount() <= 1) { compositeMap.mCompiled = compositeMap.mDrawables.size(); return; } osg::Timer timer; osg::State& state = *renderInfo.getState(); osg::GLExtensions* ext = state.get(); if (!mFBO) return; if (!ext->isFrameBufferObjectSupported) return; osg::FrameBufferAttachment attach (compositeMap.mTexture); mFBO->setAttachment(osg::Camera::COLOR_BUFFER, attach); mFBO->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); GLenum status = ext->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0; ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId); OSG_ALWAYS << "Error attaching FBO" << std::endl; return; } // inform State that Texture attribute has changed due to compiling of FBO texture // should OSG be doing this on its own? state.haveAppliedTextureAttribute(state.getActiveTextureUnit(), osg::StateAttribute::TEXTURE); for (unsigned int i=compositeMap.mCompiled; igetStateSet(); if (stateset) renderInfo.getState()->pushStateSet(stateset); renderInfo.getState()->apply(); glViewport(0,0,compositeMap.mTexture->getTextureWidth(), compositeMap.mTexture->getTextureHeight()); drw->drawImplementation(renderInfo); if (stateset) renderInfo.getState()->popStateSet(); ++compositeMap.mCompiled; compositeMap.mDrawables[i] = nullptr; if (timeLeft) { *timeLeft -= timer.time_s(); timer.setStartTick(); if (*timeLeft <= 0) break; } } if (compositeMap.mCompiled == compositeMap.mDrawables.size()) compositeMap.mDrawables = std::vector>(); state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT); GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0; ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId); } void CompositeMapRenderer::setMinimumTimeAvailableForCompile(double time) { mMinimumTimeAvailable = time; } void CompositeMapRenderer::setTargetFrameRate(float framerate) { mTargetFrameRate = framerate; } void CompositeMapRenderer::addCompositeMap(CompositeMap* compositeMap, bool immediate) { std::lock_guard lock(mMutex); if (immediate) mImmediateCompileSet.insert(compositeMap); else mCompileSet.insert(compositeMap); } void CompositeMapRenderer::setImmediate(CompositeMap* compositeMap) { std::lock_guard lock(mMutex); CompileSet::iterator found = mCompileSet.find(compositeMap); if (found == mCompileSet.end()) return; else { mImmediateCompileSet.insert(compositeMap); mCompileSet.erase(found); } } unsigned int CompositeMapRenderer::getCompileSetSize() const { std::lock_guard lock(mMutex); return mCompileSet.size(); } CompositeMap::CompositeMap() : mCompiled(0) { } CompositeMap::~CompositeMap() { } } openmw-openmw-0.48.0/components/terrain/compositemaprenderer.hpp000066400000000000000000000037231445372753700252320ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H #define OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H #include #include #include namespace osg { class FrameBufferObject; class RenderInfo; class Texture2D; } namespace Terrain { class CompositeMap : public osg::Referenced { public: CompositeMap(); ~CompositeMap(); std::vector > mDrawables; osg::ref_ptr mTexture; unsigned int mCompiled; }; /** * @brief The CompositeMapRenderer is responsible for updating composite map textures in a blocking or non-blocking way. */ class CompositeMapRenderer : public osg::Drawable { public: CompositeMapRenderer(); ~CompositeMapRenderer(); void drawImplementation(osg::RenderInfo& renderInfo) const override; void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const; /// Set the available time in seconds for compiling (non-immediate) composite maps each frame void setMinimumTimeAvailableForCompile(double time); /// If current frame rate is higher than this, the extra time will be set aside to do more compiling void setTargetFrameRate(float framerate); /// Add a composite map to be rendered void addCompositeMap(CompositeMap* map, bool immediate=false); /// Mark this composite map to be required for the current frame void setImmediate(CompositeMap* map); unsigned int getCompileSetSize() const; private: float mTargetFrameRate; double mMinimumTimeAvailable; mutable osg::Timer mTimer; typedef std::set > CompileSet; mutable CompileSet mCompileSet; mutable CompileSet mImmediateCompileSet; mutable std::mutex mMutex; osg::ref_ptr mFBO; }; } #endif openmw-openmw-0.48.0/components/terrain/defs.hpp000066400000000000000000000010471445372753700217210ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP #include namespace Terrain { enum Direction { North = 0, East = 1, South = 2, West = 3 }; struct LayerInfo { std::string mDiffuseMap; std::string mNormalMap; bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; } #endif openmw-openmw-0.48.0/components/terrain/heightcull.hpp000066400000000000000000000022641445372753700231320ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_HEIGHTCULL_H #define COMPONENTS_TERRAIN_HEIGHTCULL_H #include #include #include #include namespace osg { class Node; class NodeVisitor; } namespace Terrain { class HeightCullCallback : public SceneUtil::NodeCallback { public: void setLowZ(float z) { mLowZ = z; } float getLowZ() const { return mLowZ; } void setHighZ(float highZ) { mHighZ = highZ; } float getHighZ() const { return mHighZ; } void setCullMask(unsigned int mask) { mMask = mask; } unsigned int getCullMask() const { return mMask; } void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (mLowZ <= mHighZ) traverse(node, nv); } private: float mLowZ{ -std::numeric_limits::max() }; float mHighZ{ std::numeric_limits::max() }; unsigned int mMask{ ~0u }; }; } #endif openmw-openmw-0.48.0/components/terrain/material.cpp000066400000000000000000000242271445372753700225760ustar00rootroot00000000000000#include "material.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace { class BlendmapTexMat { public: static const osg::ref_ptr& value(const int blendmapScale) { static BlendmapTexMat instance; return instance.get(blendmapScale); } const osg::ref_ptr& get(const int blendmapScale) { const std::lock_guard lock(mMutex); auto texMat = mTexMatMap.find(blendmapScale); if (texMat == mTexMatMap.end()) { osg::Matrixf matrix; float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); // We need to nudge the blendmap to look like vanilla. // This causes visible seams unless the blendmap's resolution is doubled, but Vanilla also doubles the blendmap, apparently. matrix.preMultTranslate(osg::Vec3f(1.0f/blendmapScale/4.0f, 1.0f/blendmapScale/4.0f, 0.f)); texMat = mTexMatMap.insert(std::make_pair(blendmapScale, new osg::TexMat(matrix))).first; } return texMat->second; } private: std::mutex mMutex; std::map> mTexMatMap; }; class LayerTexMat { public: static const osg::ref_ptr& value(const float layerTileSize) { static LayerTexMat instance; return instance.get(layerTileSize); } const osg::ref_ptr& get(const float layerTileSize) { const std::lock_guard lock(mMutex); auto texMat = mTexMatMap.find(layerTileSize); if (texMat == mTexMatMap.end()) { texMat = mTexMatMap.insert(std::make_pair(layerTileSize, new osg::TexMat(osg::Matrix::scale(osg::Vec3f(layerTileSize, layerTileSize, 1.f))))).first; } return texMat->second; } private: std::mutex mMutex; std::map> mTexMatMap; }; class EqualDepth { public: static const osg::ref_ptr& value() { static EqualDepth instance; return instance.mValue; } private: osg::ref_ptr mValue; EqualDepth() : mValue(new SceneUtil::AutoDepth) { mValue->setFunction(osg::Depth::EQUAL); } }; class LequalDepth { public: static const osg::ref_ptr& value() { static LequalDepth instance; return instance.mValue; } private: osg::ref_ptr mValue; LequalDepth() : mValue(new SceneUtil::AutoDepth(osg::Depth::LEQUAL)) { } }; class BlendFuncFirst { public: static const osg::ref_ptr& value() { static BlendFuncFirst instance; return instance.mValue; } private: osg::ref_ptr mValue; BlendFuncFirst() : mValue(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ZERO)) { } }; class BlendFunc { public: static const osg::ref_ptr& value() { static BlendFunc instance; return instance.mValue; } private: osg::ref_ptr mValue; BlendFunc() : mValue(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE)) { } }; class TexEnvCombine { public: static const osg::ref_ptr& value() { static TexEnvCombine instance; return instance.mValue; } private: osg::ref_ptr mValue; TexEnvCombine() : mValue(new osg::TexEnvCombine) { mValue->setCombine_RGB(osg::TexEnvCombine::REPLACE); mValue->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); } }; class UniformCollection { public: static const UniformCollection& value() { static UniformCollection instance; return instance; } osg::ref_ptr mDiffuseMap; osg::ref_ptr mBlendMap; osg::ref_ptr mNormalMap; osg::ref_ptr mColorMode; UniformCollection() : mDiffuseMap(new osg::Uniform("diffuseMap", 0)) , mBlendMap(new osg::Uniform("blendMap", 1)) , mNormalMap(new osg::Uniform("normalMap", 2)) , mColorMode(new osg::Uniform("colorMode", 2)) { } }; } namespace Terrain { std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { auto& shaderManager = sceneManager->getShaderManager(); std::vector > passes; unsigned int blendmapIndex = 0; for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { bool firstLayer = (it == layers.begin()); osg::ref_ptr stateset (new osg::StateSet); if (!blendmaps.empty()) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); if (sceneManager->getSupportsNormalsRT()) stateset->setAttribute(new osg::Disablei(GL_BLEND, 1)); stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); } else { stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); } } if (useShaders) { stateset->setTextureAttributeAndModes(0, it->mDiffuseMap); if (layerTileSize != 1.f) stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); stateset->addUniform(UniformCollection::value().mDiffuseMap); if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(1, blendmap.get()); stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(UniformCollection::value().mBlendMap); } if (it->mNormalMap) { stateset->setTextureAttributeAndModes(2, it->mNormalMap); stateset->addUniform(UniformCollection::value().mNormalMap); } Shader::ShaderManager::DefineMap defineMap; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above return createPasses(false, sceneManager, layers, blendmaps, blendmapScale, layerTileSize); } stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); stateset->addUniform(UniformCollection::value().mColorMode); } else { // Add the actual layer texture osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(0, tex.get()); if (layerTileSize != 1.f) stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); // Multiply by the alpha map if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } passes.push_back(stateset); } return passes; } } openmw-openmw-0.48.0/components/terrain/material.hpp000066400000000000000000000014571445372753700226030ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H #include #include "defs.hpp" namespace osg { class Texture2D; } namespace Resource { class SceneManager; } namespace Terrain { struct TextureLayer { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional bool mParallax; bool mSpecular; }; std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); } #endif openmw-openmw-0.48.0/components/terrain/quadtreenode.cpp000066400000000000000000000074201445372753700234540ustar00rootroot00000000000000#include "quadtreenode.hpp" #include #include #include "defs.hpp" #include "viewdata.hpp" namespace Terrain { float distance(const osg::BoundingBox& box, const osg::Vec3f& v) { if (box.contains(v)) return 0; else { osg::Vec3f maxDist(0,0,0); if (v.x() < box.xMin()) maxDist.x() = box.xMin() - v.x(); else if (v.x() > box.xMax()) maxDist.x() = v.x() - box.xMax(); if (v.y() < box.yMin()) maxDist.y() = box.yMin() - v.y(); else if (v.y() > box.yMax()) maxDist.y() = v.y() - box.yMax(); if (v.z() < box.zMin()) maxDist.z() = box.zMin() - v.z(); else if (v.z() > box.zMax()) maxDist.z() = v.z() - box.zMax(); return maxDist.length(); } } ChildDirection reflect(ChildDirection dir, Direction dir2) { assert(dir != Root); const int lookupTable[4][4] = { // NW NE SW SE { SW, SE, NW, NE }, // N { NE, NW, SE, SW }, // E { SW, SE, NW, NE }, // S { NE, NW, SE, SW } // W }; return (ChildDirection)lookupTable[dir2][dir]; } bool adjacent(ChildDirection dir, Direction dir2) { assert(dir != Root); const bool lookupTable[4][4] = { // NW NE SW SE { true, true, false, false }, // N { false, true, false, true }, // E { false, false, true, true }, // S { true, false, true, false } // W }; return lookupTable[dir2][dir]; } QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir) { if (currentNode->getDirection() == Root) return nullptr; // Arrived at root node, the root node does not have neighbours QuadTreeNode* nextNode; if (adjacent(currentNode->getDirection(), dir)) nextNode = searchNeighbour(currentNode->getParent(), dir); else nextNode = currentNode->getParent(); if (nextNode && nextNode->getNumChildren()) return nextNode->getChild(reflect(currentNode->getDirection(), dir)); else return nullptr; } QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center) : mParent(parent) , mDirection(direction) , mValidBounds(false) , mSize(size) , mCenter(center) { for (unsigned int i=0; i<4; ++i) mNeighbours[i] = nullptr; } QuadTreeNode::~QuadTreeNode() { } QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir) { return mNeighbours[dir]; } float QuadTreeNode::distance(const osg::Vec3f& v) const { const osg::BoundingBox& box = getBoundingBox(); return Terrain::distance(box, v); } void QuadTreeNode::initNeighbours() { for (int i=0; i<4; ++i) mNeighbours[i] = searchNeighbour(this, (Direction)i); for (unsigned int i=0; iinitNeighbours(); } void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback) { if (!hasValidBounds()) return; LodCallback::ReturnValue lodResult = lodCallback->isSufficientDetail(this, distance(viewPoint)); if (lodResult == LodCallback::StopTraversal) return; else if (lodResult == LodCallback::Deeper && getNumChildren()) { for (unsigned int i=0; itraverseNodes(vd, viewPoint, lodCallback); } else vd->add(this); } void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) { mBoundingBox = boundingBox; mValidBounds = boundingBox.valid(); } const osg::BoundingBox &QuadTreeNode::getBoundingBox() const { return mBoundingBox; } float QuadTreeNode::getSize() const { return mSize; } const osg::Vec2f &QuadTreeNode::getCenter() const { return mCenter; } } openmw-openmw-0.48.0/components/terrain/quadtreenode.hpp000066400000000000000000000053241445372753700234620ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #include #include "defs.hpp" namespace Terrain { enum ChildDirection { NW = 0, NE = 1, SW = 2, SE = 3, Root }; class QuadTreeNode; class LodCallback : public osg::Referenced { public: virtual ~LodCallback() {} enum ReturnValue { Deeper, StopTraversal, StopTraversalAndUse }; virtual ReturnValue isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; class ViewData; float distance(const osg::BoundingBox&, const osg::Vec3f& v); class QuadTreeNode : public osg::Group { public: QuadTreeNode(QuadTreeNode* parent, ChildDirection dir, float size, const osg::Vec2f& center); virtual ~QuadTreeNode(); inline QuadTreeNode* getParent() { return mParent; } inline QuadTreeNode* getChild(unsigned int i) { return static_cast(Group::getChild(i)); } inline unsigned int getNumChildren() const override { return _children.size(); } // osg::Group::addChild() does a lot of unrelated stuff, but we just really want to add a child node. void addChildNode(QuadTreeNode* child) { // QuadTree node should not contain more than 4 child nodes. // Reserve enough space if this node is supposed to have child nodes. _children.reserve(4); _children.push_back(child); child->addParent(this); }; float distance(const osg::Vec3f& v) const; /// Returns our direction relative to the parent node, or Root if we are the root node. ChildDirection getDirection() { return mDirection; } /// Get neighbour node in this direction QuadTreeNode* getNeighbour (Direction dir); /// Initialize neighbours - do this after the quadtree is built void initNeighbours(); void setBoundingBox(const osg::BoundingBox& boundingBox); const osg::BoundingBox& getBoundingBox() const; bool hasValidBounds() const { return mValidBounds; } /// size in cell coordinates float getSize() const; /// center in cell coordinates const osg::Vec2f& getCenter() const; /// Traverse nodes according to LOD selection. void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback); private: QuadTreeNode* mParent; QuadTreeNode* mNeighbours[4]; ChildDirection mDirection; osg::BoundingBox mBoundingBox; bool mValidBounds; float mSize; osg::Vec2f mCenter; }; } #endif openmw-openmw-0.48.0/components/terrain/quadtreeworld.cpp000066400000000000000000000517751445372753700236720ustar00rootroot00000000000000#include "quadtreeworld.hpp" #include #include #include #include #include #include #include #include #include #include "quadtreenode.hpp" #include "storage.hpp" #include "viewdata.hpp" #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" #include "terraindrawable.hpp" #include "heightcull.hpp" namespace { bool isPowerOfTwo(int x) { return ( (x > 0) && ((x & (x - 1)) == 0) ); } int nextPowerOfTwo (int v) { if (isPowerOfTwo(v)) return v; int depth=0; while(v) { v >>= 1; depth++; } return 1 << depth; } unsigned int Log2( unsigned int n ) { unsigned int targetlevel = 0; while (n >>= 1) ++targetlevel; return targetlevel; } } namespace Terrain { class DefaultLodCallback : public LodCallback { public: DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid, float distanceModifier=0.f) : mFactor(factor) , mMinSize(minSize) , mViewDistance(viewDistance) , mActiveGrid(grid) , mDistanceModifier(distanceModifier) { } ReturnValue isSufficientDetail(QuadTreeNode* node, float dist) override { const osg::Vec2f& center = node->getCenter(); bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w()); if (node->getSize()>1) { float halfSize = node->getSize()/2; osg::Vec4i nodeBounds (static_cast(center.x() - halfSize), static_cast(center.y() - halfSize), static_cast(center.x() + halfSize), static_cast(center.y() + halfSize)); bool intersects = (std::max(nodeBounds.x(), mActiveGrid.x()) < std::min(nodeBounds.z(), mActiveGrid.z()) && std::max(nodeBounds.y(), mActiveGrid.y()) < std::min(nodeBounds.w(), mActiveGrid.w())); // to prevent making chunks who will cross the activegrid border if (intersects) return Deeper; } dist = std::max(0.f, dist + mDistanceModifier); if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) ? StopTraversalAndUse : Deeper; } static unsigned int getNativeLodLevel(const QuadTreeNode* node, float minSize) { return Log2(static_cast(node->getSize()/minSize)); } static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor) { return Log2(static_cast(dist/(Constants::CellSizeInUnits*minSize*factor))); } private: float mFactor; float mMinSize; float mViewDistance; osg::Vec4i mActiveGrid; float mDistanceModifier; }; class RootNode : public QuadTreeNode { public: RootNode(float size, const osg::Vec2f& center) : QuadTreeNode(nullptr, Root, size, center) , mWorld(nullptr) { } void setWorld(QuadTreeWorld* world) { mWorld = world; } void accept(osg::NodeVisitor &nv) override { if (!nv.validNodeMask(*this)) return; nv.pushOntoNodePath(this); mWorld->accept(nv); nv.popFromNodePath(); } private: QuadTreeWorld* mWorld; }; class QuadTreeBuilder { public: QuadTreeBuilder(Terrain::Storage* storage, float minSize) : mStorage(storage) , mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f) , mMinSize(minSize) { } void build() { mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY); int origSizeX = static_cast(mMaxX - mMinX); int origSizeY = static_cast(mMaxY - mMinY); // Dividing a quad tree only works well for powers of two, so round up to the nearest one int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY)); addChildren(mRootNode); mRootNode->initNeighbours(); float cellWorldSize = mStorage->getCellWorldSize(); mRootNode->setInitialBound(osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX*cellWorldSize, mMinY*cellWorldSize, 0), osg::Vec3(mMaxX*cellWorldSize, mMaxY*cellWorldSize, 0)))); } void addChildren(QuadTreeNode* parent) { float halfSize = parent->getSize()/2.f; osg::BoundingBox boundingBox; for (unsigned int i=0; i<4; ++i) { osg::ref_ptr child = addChild(parent, static_cast(i), halfSize); if (child) { boundingBox.expandBy(child->getBoundingBox()); parent->addChildNode(child); } } if (!boundingBox.valid()) parent->removeChildren(0, 4); else parent->setBoundingBox(boundingBox); } osg::ref_ptr addChild(QuadTreeNode* parent, ChildDirection direction, float size) { float halfSize = size/2.f; osg::Vec2f center; switch (direction) { case SW: center = parent->getCenter() + osg::Vec2f(-halfSize,-halfSize); break; case SE: center = parent->getCenter() + osg::Vec2f(halfSize, -halfSize); break; case NW: center = parent->getCenter() + osg::Vec2f(-halfSize, halfSize); break; case NE: center = parent->getCenter() + osg::Vec2f(halfSize, halfSize); break; default: break; } osg::ref_ptr node = new QuadTreeNode(parent, direction, size, center); if (center.x() - halfSize > mMaxX || center.x() + halfSize < mMinX || center.y() - halfSize > mMaxY || center.y() + halfSize < mMinY ) // Out of bounds of the actual terrain - this will happen because // we rounded the size up to the next power of two { // Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children. return node; } // Do not add child nodes for default cells without data. // size = 1 means that the single shape covers the whole cell. if (node->getSize() == 1 && !mStorage->hasData(center.x()-0.5, center.y()-0.5)) return node; if (node->getSize() <= mMinSize) { // We arrived at a leaf. // Since the tree is used for LOD level selection instead of culling, we do not need to load the actual height data here. constexpr float minZ = -std::numeric_limits::max(); constexpr float maxZ = std::numeric_limits::max(); float cellWorldSize = mStorage->getCellWorldSize(); osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); node->setBoundingBox(boundingBox); return node; } else { addChildren(node); return node; } } osg::ref_ptr getRootNode() { return mRootNode; } private: Terrain::Storage* mStorage; float mMinX, mMaxX, mMinY, mMaxY; float mMinSize; osg::ref_ptr mRootNode; }; class DebugChunkManager : public QuadTreeWorld::ChunkManager { public: DebugChunkManager(Resource::SceneManager* sceneManager, Storage* storage, unsigned int nodeMask) : mSceneManager(sceneManager), mStorage(storage), mNodeMask(nodeMask) {} osg::ref_ptr getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, mStorage, mSceneManager, mNodeMask, 5.f, { 1, 0, 0, 0 }); osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; pat->setPosition(-center*Constants::CellSizeInUnits); pat->addChild(chunkBorder); return pat; } unsigned int getNodeMask() { return mNodeMask; } private: Resource::SceneManager* mSceneManager; Storage* mStorage; unsigned int mNodeMask; }; QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); if (mDebugTerrainChunks) { mDebugChunkManager = std::make_unique(mResourceSystem->getSceneManager(), mStorage, borderMask); addChunkManager(mDebugChunkManager.get()); } } QuadTreeWorld::~QuadTreeWorld() { } /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the vertex data set, /// NOT relative to mMinSize as is the case with node LODs. unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) { unsigned int vertexLod = DefaultLodCallback::getNativeLodLevel(node, 1); if (vertexLodMod > 0) { vertexLod = static_cast(std::max(0, static_cast(vertexLod)-vertexLodMod)); } else if (vertexLodMod < 0) { float size = node->getSize(); // Stop to simplify at this level since with size = 1 the node already covers the whole cell and has getCellVertices() vertices. while (size < 1) { size *= 2; vertexLodMod = std::min(0, vertexLodMod+1); } vertexLod += std::abs(vertexLodMod); } return vertexLod; } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly unsigned int getLodFlags(QuadTreeNode* node, unsigned int ourVertexLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) { QuadTreeNode* neighbour = node->getNeighbour(static_cast(i)); // If the neighbour isn't currently rendering itself, // go up until we find one. NOTE: We don't need to go down, // because in that case neighbour's detail would be higher than // our detail and the neighbour would handle stitching by itself. while (neighbour && !vd->contains(neighbour)) neighbour = neighbour->getParent(); unsigned int lod = 0; if (neighbour) lod = getVertexLod(neighbour, vertexLodMod); if (lod <= ourVertexLod) // We only need to worry about neighbours less detailed than we are - lod = 0; // neighbours with more detail will do the stitching themselves // Use 4 bits for each LOD delta if (lod > 0) { lodFlags |= (lod - ourVertexLod) << (4*i); } } // Use the remaining bits for our vertex LOD lodFlags |= (ourVertexLod << (4*4)); return lodFlags; } void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; if (vd->hasChanged()) { unsigned int ourVertexLod = getVertexLod(entry.mNode, mVertexLodMod); // have to recompute the lodFlags in case a neighbour has changed LOD. unsigned int lodFlags = getLodFlags(entry.mNode, ourVertexLod, mVertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; entry.mLodFlags = lodFlags; } } if (!entry.mRenderingNode) { osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; pat->setPosition(osg::Vec3f(entry.mNode->getCenter().x()*cellWorldSize, entry.mNode->getCenter().y()*cellWorldSize, 0.f)); const osg::Vec2f& center = entry.mNode->getCenter(); bool activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w()); for (QuadTreeWorld::ChunkManager* m : mChunkManagers) { osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); } entry.mRenderingNode = pat; } } void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) { if (!(cv->getTraversalMask() & callback->getCullMask())) return; float lowZ = std::numeric_limits::max(); float highZ = callback->getHighZ(); if (cv->getEyePoint().z() <= highZ || outofworld) { callback->setLowZ(-std::numeric_limits::max()); return; } cv->pushCurrentMask(); static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; for (unsigned int i=0; igetNumEntries(); ++i) { ViewDataEntry& entry = vd->getEntry(i); osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); bb._min += ofs; bb._max += ofs; bb._min.z() = highZ; bb._max.z() = highZ; if (cv->isCulled(bb)) continue; lowZ = bb._min.z(); if (!debug) break; osg::Box* b = new osg::Box; b->set(bb.center(), bb._max - bb.center()); osg::ShapeDrawable* drw = new osg::ShapeDrawable(b); static osg::ref_ptr stateset = nullptr; if (!stateset) { stateset = new osg::StateSet; stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); osg::Material* m = new osg::Material; m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,1,1)); m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); stateset->setAttributeAndModes(m, osg::StateAttribute::ON); stateset->setRenderBinDetails(100,"RenderBin"); } drw->setStateSet(stateset); drw->accept(*cv); } callback->setLowZ(lowZ); cv->popCurrentMask(); } void QuadTreeWorld::accept(osg::NodeVisitor &nv) { bool isCullVisitor = nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (!isCullVisitor && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) return; osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; osg::Vec3f viewPoint = viewer ? nv.getViewPoint() : nv.getEyePoint(); ViewData *vd = mViewDataMap->getViewData(viewer, viewPoint, mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); } const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries(); ++i) { ViewDataEntry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false); entry.mRenderingNode->accept(nv); } if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->setChanged(false); double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0; if (referenceTime != 0.0) { vd->setLastUsageTimeStamp(referenceTime); mViewDataMap->clearUnusedViews(referenceTime); } } void QuadTreeWorld::ensureQuadTreeBuilt() { std::lock_guard lock(mQuadTreeMutex); if (mQuadTreeBuilt) return; QuadTreeBuilder builder(mStorage, mMinSize); builder.build(); mRootNode = builder.getRootNode(); mRootNode->setWorld(this); mQuadTreeBuilt = true; } void QuadTreeWorld::enable(bool enabled) { if (enabled) { ensureQuadTreeBuilt(); if (!mRootNode->getNumParents()) mTerrainRoot->addChild(mRootNode); } else if (mRootNode) mTerrainRoot->removeChild(mRootNode); } View* QuadTreeWorld::createView() { return mViewDataMap->createIndependentView(); } void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, Loading::Reporter& reporter) { ensureQuadTreeBuilt(); const float cellWorldSize = mStorage->getCellWorldSize(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); for (unsigned int pass=0; pass<3; ++pass) { unsigned int startEntry = vd->getNumEntries(); float distanceModifier=0.f; if (pass == 1) distanceModifier = 1024; else if (pass == 2) distanceModifier = -1024; DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, distanceModifier); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (pass==0) { std::size_t progressTotal = 0; for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) progressTotal += vd->getEntry(i).mNode->getSize(); reporter.addTotal(progressTotal); } for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) { ViewDataEntry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, cellWorldSize, grid, true); if (pass==0) reporter.addProgress(entry.mNode->getSize()); entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass } } } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) { if (mCompositeMapRenderer) stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } void QuadTreeWorld::loadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::loadCell(x,y); else World::loadCell(x,y); } void QuadTreeWorld::unloadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::unloadCell(x,y); else World::unloadCell(x,y); } void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) { mChunkManagers.push_back(m); mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); if (m->getViewDistance()) m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor)); } void QuadTreeWorld::rebuildViews() { mViewDataMap->rebuildViews(); } void QuadTreeWorld::setViewDistance(float viewDistance) { if (mViewDistance == viewDistance) return; mViewDistance = viewDistance; mViewDataMap->rebuildViews(); } } openmw-openmw-0.48.0/components/terrain/quadtreeworld.hpp000066400000000000000000000061611445372753700236640ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H #define COMPONENTS_TERRAIN_QUADTREEWORLD_H #include "terraingrid.hpp" #include #include #include namespace osg { class NodeVisitor; class Group; class Stats; } namespace Terrain { class RootNode; class ViewDataMap; class ViewData; struct ViewDataEntry; class DebugChunkManager; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks); ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); void enable(bool enabled) override; void setViewDistance(float distance) override; void cacheCell(View *view, int x, int y) override {} /// @note Not thread safe. void loadCell(int x, int y) override; /// @note Not thread safe. void unloadCell(int x, int y) override; View* createView() override; void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) override; void rebuildViews() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) override; class ChunkManager { public: virtual ~ChunkManager(){} virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } void setViewDistance(float viewDistance) { mViewDistance = viewDistance; } float getViewDistance() const { return mViewDistance; } // Automatically set by addChunkManager based on getViewDistance() unsigned int getMaxLodLevel() const { return mMaxLodLevel; } void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; } private: float mViewDistance = 0.f; unsigned int mMaxLodLevel = ~0u; }; void addChunkManager(ChunkManager*); private: void ensureQuadTreeBuilt(); void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile); osg::ref_ptr mRootNode; osg::ref_ptr mViewDataMap; std::vector mChunkManagers; std::mutex mQuadTreeMutex; bool mQuadTreeBuilt; float mLodFactor; int mVertexLodMod; float mViewDistance; float mMinSize; bool mDebugTerrainChunks; std::unique_ptr mDebugChunkManager; }; } #endif openmw-openmw-0.48.0/components/terrain/storage.hpp000066400000000000000000000100331445372753700224370ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H #include #include #include #include #include #include "defs.hpp" namespace osg { class Image; } namespace Terrain { /// We keep storage of terrain data abstract here since we need different implementations for game and editor /// @note The implementation must be thread safe. class Storage { public: virtual ~Storage() {} public: /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; /// Return true if there is land data for this cell /// May be overriden for a faster implementation virtual bool hasData(int cellX, int cellY) { float dummy; return getMinMaxHeights(1, osg::Vec2f(cellX+0.5, cellY+0.5), dummy, dummy); } /// Get the minimum and maximum heights of a terrain region. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units /// @param min min height will be stored here /// @param max max height will be stored here /// @return true if there was data available for this terrain chunk virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) = 0; /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). /// The specified positions should be in local space, i.e. relative to the center of the terrain chunk. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units /// @param positions buffer to write vertices /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, osg::ref_ptr colours) = 0; typedef std::vector > ImageVector; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector& layerList) = 0; virtual float getHeightAt (const osg::Vec3f& worldPos) = 0; /// Get the transformation factor for mapping cell units to world units. virtual float getCellWorldSize() = 0; /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices() = 0; virtual int getBlendmapScale(float chunkSize) = 0; }; } #endif openmw-openmw-0.48.0/components/terrain/terraindrawable.cpp000066400000000000000000000113051445372753700241370ustar00rootroot00000000000000#include "terraindrawable.hpp" #include #include #include #include "compositemaprenderer.hpp" namespace Terrain { TerrainDrawable::TerrainDrawable() { } TerrainDrawable::~TerrainDrawable() { } TerrainDrawable::TerrainDrawable(const TerrainDrawable ©, const osg::CopyOp ©op) : osg::Geometry(copy, copyop) , mPasses(copy.mPasses) , mLightListCallback(copy.mLightListCallback) { } void TerrainDrawable::accept(osg::NodeVisitor &nv) { if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR) { osg::Geometry::accept(nv); } else if (nv.validNodeMask(*this)) { nv.pushOntoNodePath(this); cull(static_cast(&nv)); nv.popFromNodePath(); } } inline float distance(const osg::Vec3& coord,const osg::Matrix& matrix) { return -((float)coord[0]*(float)matrix(0,2)+(float)coord[1]*(float)matrix(1,2)+(float)coord[2]*(float)matrix(2,2)+matrix(3,2)); } //canot use ClusterCullingCallback::cull: viewpoint != eyepoint // !osgfixpotential! bool clusterCull(osg::ClusterCullingCallback* cb, const osg::Vec3f& eyePoint, bool shadowcam) { float _deviation = cb->getDeviation(); const osg::Vec3& _controlPoint = cb->getControlPoint(); osg::Vec3 _normal = cb->getNormal(); if (shadowcam) _normal = _normal * -1; //inverting for shadowcam frontfaceculing float _radius = cb->getRadius(); if (_deviation<=-1.0f) return false; osg::Vec3 eye_cp = eyePoint - _controlPoint; float radius = eye_cp.length(); if (radius<_radius) return false; float deviation = (eye_cp * _normal)/radius; return deviation < _deviation; } void TerrainDrawable::cull(osgUtil::CullVisitor *cv) { const osg::BoundingBox& bb = getBoundingBox(); if (_cullingActive && cv->isCulled(getBoundingBox())) return; bool shadowcam = cv->getCurrentCamera()->getName() == "ShadowCamera"; if (cv->getCullingMode() & osg::CullStack::CLUSTER_CULLING && clusterCull(mClusterCullingCallback, cv->getEyePoint(), shadowcam)) return; osg::RefMatrix& matrix = *cv->getModelViewMatrix(); if (cv->getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR && bb.valid()) { if (!cv->updateCalculatedNearFar(matrix, *this, false)) return; } float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f; if (osg::isNaN(depth)) return; if (shadowcam) { cv->addDrawableAndDepth(this, &matrix, depth); return; } if (mCompositeMap && mCompositeMapRenderer) { mCompositeMapRenderer->setImmediate(mCompositeMap); mCompositeMapRenderer = nullptr; } bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); osg::StateSet* stateset = getStateSet(); if (stateset) cv->pushStateSet(stateset); for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) { cv->pushStateSet(*it); cv->addDrawableAndDepth(this, &matrix, depth); cv->popStateSet(); } if (stateset) cv->popStateSet(); if (pushedLight) cv->popStateSet(); } void TerrainDrawable::createClusterCullingCallback() { mClusterCullingCallback = new osg::ClusterCullingCallback(this); } void TerrainDrawable::setPasses(const TerrainDrawable::PassVector &passes) { mPasses = passes; } void TerrainDrawable::setLightListCallback(SceneUtil::LightListCallback *lightListCallback) { mLightListCallback = lightListCallback; } void TerrainDrawable::setupWaterBoundingBox(float waterheight, float margin) { osg::Vec3Array* vertices = static_cast(getVertexArray()); for (unsigned int i=0; isize(); ++i) { const osg::Vec3f& vertex = (*vertices)[i]; if (vertex.z() <= waterheight) mWaterBoundingBox.expandBy(vertex); } if (mWaterBoundingBox.valid()) { const osg::BoundingBox& bb = getBoundingBox(); mWaterBoundingBox.xMin() = std::max(bb.xMin(), mWaterBoundingBox.xMin() - margin); mWaterBoundingBox.yMin() = std::max(bb.yMin(), mWaterBoundingBox.yMin() - margin); mWaterBoundingBox.xMax() = std::min(bb.xMax(), mWaterBoundingBox.xMax() + margin); mWaterBoundingBox.xMax() = std::min(bb.xMax(), mWaterBoundingBox.xMax() + margin); } } void TerrainDrawable::compileGLObjects(osg::RenderInfo &renderInfo) const { for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) { osg::StateSet* stateset = *it; stateset->compileGLObjects(*renderInfo.getState()); } osg::Geometry::compileGLObjects(renderInfo); } } openmw-openmw-0.48.0/components/terrain/terraindrawable.hpp000066400000000000000000000047031445372753700241500ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H #define OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H #include namespace osg { class ClusterCullingCallback; } namespace osgUtil { class CullVisitor; } namespace SceneUtil { class LightListCallback; } namespace Terrain { class CompositeMap; class CompositeMapRenderer; /** * Subclass of Geometry that supports built in multi-pass rendering and built in LightListCallback. */ class TerrainDrawable : public osg::Geometry { public: osg::Object* cloneType() const override { return new TerrainDrawable (); } osg::Object* clone(const osg::CopyOp& copyop) const override { return new TerrainDrawable (*this,copyop); } bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } const char* className() const override { return "TerrainDrawable"; } const char* libraryName() const override { return "Terrain"; } TerrainDrawable(); ~TerrainDrawable(); // has to be defined in the cpp file because we only forward declared some members. TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop); void accept(osg::NodeVisitor &nv) override; void cull(osgUtil::CullVisitor* cv); typedef std::vector > PassVector; void setPasses (const PassVector& passes); const PassVector& getPasses() const { return mPasses; } void setLightListCallback(SceneUtil::LightListCallback* lightListCallback); void createClusterCullingCallback(); void compileGLObjects(osg::RenderInfo& renderInfo) const override; void setupWaterBoundingBox(float waterheight, float margin); const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } CompositeMap* getCompositeMap() { return mCompositeMap; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: osg::BoundingBox mWaterBoundingBox; PassVector mPasses; osg::ref_ptr mClusterCullingCallback; osg::ref_ptr mLightListCallback; osg::ref_ptr mCompositeMap; osg::ref_ptr mCompositeMapRenderer; }; } #endif openmw-openmw-0.48.0/components/terrain/terraingrid.cpp000066400000000000000000000073431445372753700233120ustar00rootroot00000000000000#include "terraingrid.hpp" #include #include #include #include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" #include "view.hpp" #include "storage.hpp" #include "heightcull.hpp" namespace Terrain { class MyView : public View { public: osg::ref_ptr mLoaded; void reset() override {} }; TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask) : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mNumSplits(4) { } TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, unsigned int nodeMask) : Terrain::World(parent, storage, nodeMask) , mNumSplits(4) { } TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) { TerrainGrid::unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); } } void TerrainGrid::cacheCell(View* view, int x, int y) { osg::Vec2f center(x+0.5f, y+0.5f); static_cast(view)->mLoaded = buildTerrain(nullptr, 1.f, center); } osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { if (chunkSize * mNumSplits > 1.f) { // keep splitting osg::ref_ptr group (new osg::Group); if (parent) parent->addChild(group); float newChunkSize = chunkSize/2.f; buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f)); buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f)); return group; } else { osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true); if (!node) return nullptr; const float cellWorldSize = mStorage->getCellWorldSize(); osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; pat->setPosition(osg::Vec3f(chunkCenter.x()*cellWorldSize, chunkCenter.y()*cellWorldSize, 0.f)); pat->addChild(node); if (parent) parent->addChild(pat); return pat; } } void TerrainGrid::loadCell(int x, int y) { if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded osg::Vec2f center(x+0.5f, y+0.5f); osg::ref_ptr terrainNode = buildTerrain(nullptr, 1.f, center); if (!terrainNode) return; // no terrain defined TerrainGrid::World::loadCell(x,y); mTerrainRoot->addChild(terrainNode); mGrid[std::make_pair(x,y)] = terrainNode; updateWaterCulling(); } void TerrainGrid::unloadCell(int x, int y) { CellBorder::CellGrid::iterator it = mGrid.find(std::make_pair(x,y)); if (it == mGrid.end()) return; Terrain::World::unloadCell(x,y); osg::ref_ptr terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); mGrid.erase(it); updateWaterCulling(); } void TerrainGrid::updateWaterCulling() { if (!mHeightCullCallback) return; osg::ComputeBoundsVisitor computeBoundsVisitor; mTerrainRoot->accept(computeBoundsVisitor); float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); mHeightCullCallback->setLowZ(lowZ); } View *TerrainGrid::createView() { return new MyView; } } openmw-openmw-0.48.0/components/terrain/terraingrid.hpp000066400000000000000000000026551445372753700233200ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H #include #include #include "world.hpp" namespace osg { class Group; class Stats; } namespace Resource { class ResourceSystem; } namespace Terrain { class Storage; /// @brief Simple terrain implementation that loads cells in a grid, with no LOD. Only requested cells are loaded. class TerrainGrid : public Terrain::World { public: TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask=~0u, unsigned int borderMask=0); TerrainGrid(osg::Group* parent, Storage* storage, unsigned int nodeMask=~0u); ~TerrainGrid(); void cacheCell(View* view, int x, int y) override; /// @note Not thread safe. void loadCell(int x, int y) override; /// @note Not thread safe. void unloadCell(int x, int y) override; View* createView() override; protected: bool isGridEmpty() const { return mGrid.empty(); } private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); void updateWaterCulling(); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; CellBorder::CellGrid mGrid; }; } #endif openmw-openmw-0.48.0/components/terrain/texturemanager.cpp000066400000000000000000000034241445372753700240270ustar00rootroot00000000000000#include "texturemanager.hpp" #include #include #include #include #include namespace Terrain { TextureManager::TextureManager(Resource::SceneManager *sceneMgr) : ResourceManager(sceneMgr->getVFS()) , mSceneManager(sceneMgr) { } struct UpdateTextureFilteringFunctor { UpdateTextureFilteringFunctor(Resource::SceneManager* sceneMgr) : mSceneManager(sceneMgr) { } Resource::SceneManager* mSceneManager; void operator()(std::string, osg::Object* obj) { mSceneManager->applyFilterSettings(static_cast(obj)); } }; void TextureManager::updateTextureFiltering() { UpdateTextureFilteringFunctor f(mSceneManager); mCache->call(f); } osg::ref_ptr TextureManager::getTexture(const std::string &name) { // don't bother with case folding, since there is only one way of referring to terrain textures we can assume the case is always the same osg::ref_ptr obj = mCache->getRefFromObjectCache(name); if (obj) return static_cast(obj.get()); else { osg::ref_ptr texture (new osg::Texture2D(mSceneManager->getImageManager()->getImage(name))); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mSceneManager->applyFilterSettings(texture); mCache->addEntryToObjectCache(name, texture.get()); return texture; } } void TextureManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize()); } } openmw-openmw-0.48.0/components/terrain/texturemanager.hpp000066400000000000000000000012751445372753700240360ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H #define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H #include #include namespace Resource { class SceneManager; } namespace osg { class Texture2D; } namespace Terrain { class TextureManager : public Resource::ResourceManager { public: TextureManager(Resource::SceneManager* sceneMgr); void updateTextureFiltering(); osg::ref_ptr getTexture(const std::string& name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: Resource::SceneManager* mSceneManager; }; } #endif openmw-openmw-0.48.0/components/terrain/view.hpp000066400000000000000000000012021445372753700217430ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_VIEW_H #define COMPONENTS_TERRAIN_VIEW_H #include #include #include namespace Terrain { /** * @brief A View is a collection of rendering objects that are visible from a given camera/intersection. * The base View class is part of the interface for usage in conjunction with preload feature. */ class View : public osg::Referenced { public: virtual ~View() {} /// Reset internal structure so that the next addition to the view will override the previous frame's contents. virtual void reset() = 0; }; } #endif openmw-openmw-0.48.0/components/terrain/viewdata.cpp000066400000000000000000000120601445372753700225740ustar00rootroot00000000000000#include "viewdata.hpp" #include "quadtreenode.hpp" namespace Terrain { ViewData::ViewData() : mNumEntries(0) , mLastUsageTimeStamp(0.0) , mChanged(false) , mHasViewPoint(false) , mWorldUpdateRevision(0) { } ViewData::~ViewData() { } void ViewData::copyFrom(const ViewData& other) { mNumEntries = other.mNumEntries; mEntries = other.mEntries; mChanged = other.mChanged; mHasViewPoint = other.mHasViewPoint; mViewPoint = other.mViewPoint; mActiveGrid = other.mActiveGrid; mWorldUpdateRevision = other.mWorldUpdateRevision; } void ViewData::add(QuadTreeNode *node) { unsigned int index = mNumEntries++; if (index+1 > mEntries.size()) mEntries.resize(index+1); ViewDataEntry& entry = mEntries[index]; if (entry.set(node)) mChanged = true; } void ViewData::setViewPoint(const osg::Vec3f &viewPoint) { mViewPoint = viewPoint; mHasViewPoint = true; } // NOTE: As a performance optimisation, we cache mRenderingNodes from previous frames here. // If this cache becomes invalid (e.g. through mWorldUpdateRevision), we need to use clear() instead of reset(). void ViewData::reset() { // clear any unused entries for (unsigned int i=mNumEntries; isecond; needsUpdate = false; if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { if (other->suitableToUse(activeGrid) && other->getWorldUpdateRevision() >= mWorldUpdateRevision) { float dist = (viewPoint-other->getViewPoint()).length2(); if (dist < shortestDist) { shortestDist = dist; mostSuitableView = other; } } } if (mostSuitableView && mostSuitableView != vd) { vd->copyFrom(*mostSuitableView); return vd; } else if (!mostSuitableView) { if (vd->getWorldUpdateRevision() != mWorldUpdateRevision) { vd->setWorldUpdateRevision(mWorldUpdateRevision); vd->clear(); } vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); needsUpdate = true; } } return vd; } ViewData *ViewDataMap::createOrReuseView() { ViewData* vd = nullptr; if (mUnusedViews.size()) { vd = mUnusedViews.front(); mUnusedViews.pop_front(); } else { mViewVector.emplace_back(); vd = &mViewVector.back(); } mUsedViews.push_back(vd); vd->setWorldUpdateRevision(mWorldUpdateRevision); return vd; } ViewData *ViewDataMap::createIndependentView() const { ViewData* vd = new ViewData; vd->setWorldUpdateRevision(mWorldUpdateRevision); return vd; } void ViewDataMap::clearUnusedViews(double referenceTime) { for (ViewerMap::iterator it = mViewers.begin(); it != mViewers.end(); ) { if (it->second->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) mViewers.erase(it++); else ++it; } for (std::deque::iterator it = mUsedViews.begin(); it != mUsedViews.end(); ) { if ((*it)->getLastUsageTimeStamp() + mExpiryDelay < referenceTime) { (*it)->clear(); mUnusedViews.push_back(*it); it = mUsedViews.erase(it); } else ++it; } } void ViewDataMap::rebuildViews() { ++mWorldUpdateRevision; } } openmw-openmw-0.48.0/components/terrain/viewdata.hpp000066400000000000000000000071451445372753700226110ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H #define OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H #include #include #include #include "view.hpp" namespace Terrain { class QuadTreeNode; struct ViewDataEntry { ViewDataEntry(); bool set(QuadTreeNode* node); QuadTreeNode* mNode; unsigned int mLodFlags; osg::ref_ptr mRenderingNode; }; class ViewData : public View { public: ViewData(); ~ViewData(); void add(QuadTreeNode* node); void reset() override; bool suitableToUse(const osg::Vec4i& activeGrid) const; void clear(); bool contains(QuadTreeNode* node) const; void copyFrom(const ViewData& other); unsigned int getNumEntries() const { return mNumEntries; } ViewDataEntry& getEntry(unsigned int i) { return mEntries[i]; } double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; } void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; } /// Indicates at least one mNode of mEntries has changed. /// @note Such changes may necessitate a revalidation of cached mRenderingNodes elsewhere depending /// on the parameters that affect the creation of mRenderingNode. bool hasChanged() const { return mChanged; } void setChanged(bool changed) { mChanged = changed; } bool hasViewPoint() const { return mHasViewPoint; } void setViewPoint(const osg::Vec3f& viewPoint); const osg::Vec3f& getViewPoint() const { return mViewPoint; } void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} } const osg::Vec4i &getActiveGrid() const { return mActiveGrid;} unsigned int getWorldUpdateRevision() const { return mWorldUpdateRevision; } void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; } private: std::vector mEntries; unsigned int mNumEntries; double mLastUsageTimeStamp; bool mChanged; osg::Vec3f mViewPoint; bool mHasViewPoint; osg::Vec4i mActiveGrid; unsigned int mWorldUpdateRevision; }; class ViewDataMap : public osg::Referenced { public: ViewDataMap() : mReuseDistance(150) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. // this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time. , mExpiryDelay(1.f) , mWorldUpdateRevision(0) {} ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate); ViewData* createOrReuseView(); ViewData* createIndependentView() const; void clearUnusedViews(double referenceTime); void rebuildViews(); float getReuseDistance() const { return mReuseDistance; } private: std::list mViewVector; typedef std::map, ViewData*> ViewerMap; ViewerMap mViewers; float mReuseDistance; float mExpiryDelay; // time in seconds for unused view to be removed unsigned int mWorldUpdateRevision; std::deque mUsedViews; std::deque mUnusedViews; }; } #endif openmw-openmw-0.48.0/components/terrain/world.cpp000066400000000000000000000103161445372753700221210ustar00rootroot00000000000000#include "world.hpp" #include #include #include #include #include "storage.hpp" #include "texturemanager.hpp" #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" #include "heightcull.hpp" namespace Terrain { World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask) : mStorage(storage) , mParent(parent) , mResourceSystem(resourceSystem) , mBorderVisible(false) , mHeightCullCallback(new HeightCullCallback) { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->setName("Terrain Root"); osg::ref_ptr compositeCam = new osg::Camera; compositeCam->setRenderOrder(osg::Camera::PRE_RENDER, -1); compositeCam->setProjectionMatrix(osg::Matrix::identity()); compositeCam->setViewMatrix(osg::Matrix::identity()); compositeCam->setReferenceFrame(osg::Camera::ABSOLUTE_RF); compositeCam->setClearMask(0); compositeCam->setNodeMask(preCompileMask); mCompositeMapCamera = compositeCam; compileRoot->addChild(compositeCam); mCompositeMapRenderer = new CompositeMapRenderer; compositeCam->addChild(mCompositeMapRenderer); mParent->addChild(mTerrainRoot); mTextureManager = std::make_unique(mResourceSystem->getSceneManager()); mChunkManager = std::make_unique(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer); mChunkManager->setNodeMask(nodeMask); mCellBorder = std::make_unique(this,mTerrainRoot.get(),borderMask,mResourceSystem->getSceneManager()); mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mTextureManager.get()); } World::World(osg::Group* parent, Storage* storage, unsigned int nodeMask) : mStorage(storage) , mParent(parent) , mCompositeMapCamera(nullptr) , mCompositeMapRenderer(nullptr) , mResourceSystem(nullptr) , mTextureManager(nullptr) , mChunkManager(nullptr) , mCellBorder(nullptr) , mBorderVisible(false) , mHeightCullCallback(nullptr) { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); mParent->addChild(mTerrainRoot); } World::~World() { if (mResourceSystem && mChunkManager) mResourceSystem->removeResourceManager(mChunkManager.get()); if (mResourceSystem && mTextureManager) mResourceSystem->removeResourceManager(mTextureManager.get()); mParent->removeChild(mTerrainRoot); if (mCompositeMapCamera && mCompositeMapRenderer) { mCompositeMapCamera->removeChild(mCompositeMapRenderer); mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); } } void World::setBordersVisible(bool visible) { mBorderVisible = visible; if (visible) { for (std::set>::iterator it = mLoadedCells.begin(); it != mLoadedCells.end(); ++it) mCellBorder->createCellBorderGeometry(it->first,it->second); } else mCellBorder->destroyCellBorderGeometry(); } void World::loadCell(int x, int y) { if (mBorderVisible) mCellBorder->createCellBorderGeometry(x,y); mLoadedCells.insert(std::pair(x,y)); } void World::unloadCell(int x, int y) { if (mBorderVisible) mCellBorder->destroyCellBorderGeometry(x,y); mLoadedCells.erase(std::pair(x,y)); } void World::setTargetFrameRate(float rate) { mCompositeMapRenderer->setTargetFrameRate(rate); } float World::getHeightAt(const osg::Vec3f &worldPos) { return mStorage->getHeightAt(worldPos); } void World::updateTextureFiltering() { if (mTextureManager) mTextureManager->updateTextureFiltering(); } void World::clearAssociatedCaches() { if (mChunkManager) mChunkManager->clearCache(); } osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { if (!mHeightCullCallback) return nullptr; mHeightCullCallback->setHighZ(highz); mHeightCullCallback->setCullMask(mask); return mHeightCullCallback; } } openmw-openmw-0.48.0/components/terrain/world.hpp000066400000000000000000000075111445372753700221310ustar00rootroot00000000000000#ifndef COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H #include #include #include #include #include #include #include "defs.hpp" #include "cellborder.hpp" namespace osg { class Group; class Stats; } namespace Resource { class ResourceSystem; } namespace Loading { class Reporter; } namespace Terrain { class Storage; class TextureManager; class ChunkManager; class CompositeMapRenderer; class View; class HeightCullCallback; /** * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed * is up to the implementation. */ class World { public: /// @note takes ownership of \a storage /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask); World(osg::Group* parent, Storage* storage, unsigned int nodeMask); virtual ~World(); /// See CompositeMapRenderer::setTargetFrameRate void setTargetFrameRate(float rate); /// Apply the scene manager's texture filtering settings to all cached textures. /// @note Thread safe. void updateTextureFiltering(); float getHeightAt (const osg::Vec3f& worldPos); /// Clears the cached land and landtexture data. /// @note Thread safe. virtual void clearAssociatedCaches(); /// Load a terrain cell and store it in the View for later use. /// @note Thread safe. virtual void cacheCell(View* view, int x, int y) {} /// Load the cell into the scene graph. /// @note Not thread safe. virtual void loadCell(int x, int y); /// Remove the cell from the scene graph. /// @note Not thread safe. virtual void unloadCell(int x, int y); virtual void enable(bool enabled) {} virtual void setBordersVisible(bool visible); virtual bool getBordersVisible() { return mBorderVisible; } /// Create a View to use with preload feature. The caller is responsible for deleting the view. /// @note Thread safe. virtual View* createView() { return nullptr; } /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) {} virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} virtual void setViewDistance(float distance) {} Storage* getStorage() { return mStorage; } osg::Callback* getHeightCullCallback(float highz, unsigned int mask); void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; } protected: Storage* mStorage; osg::ref_ptr mParent; osg::ref_ptr mTerrainRoot; osg::ref_ptr mCompositeMapCamera; osg::ref_ptr mCompositeMapRenderer; Resource::ResourceSystem* mResourceSystem; std::unique_ptr mTextureManager; std::unique_ptr mChunkManager; std::unique_ptr mCellBorder; bool mBorderVisible; std::set> mLoadedCells; osg::ref_ptr mHeightCullCallback; osg::Vec4i mActiveGrid; }; } #endif openmw-openmw-0.48.0/components/to_utf8/000077500000000000000000000000001445372753700202115ustar00rootroot00000000000000openmw-openmw-0.48.0/components/to_utf8/.gitignore000066400000000000000000000000121445372753700221720ustar00rootroot00000000000000gen_iconv openmw-openmw-0.48.0/components/to_utf8/Makefile000066400000000000000000000002001445372753700216410ustar00rootroot00000000000000tables_gen.hpp: gen_iconv ./gen_iconv > tables_gen.hpp gen_iconv: gen_iconv.cpp g++ -Wall $^ -o $@ clean: rm -f ./gen_iconvopenmw-openmw-0.48.0/components/to_utf8/gen_iconv.cpp000066400000000000000000000061441445372753700226710ustar00rootroot00000000000000// This program generates the file tables_gen.hpp #include #include #include void tab() { std::cout << " "; } // write one number with a space in front of it and a comma after it void num(char i, bool last) { // Convert i to its integer value, i.e. -128 to 127. Printing it directly // would result in non-printable characters in the source code, which is bad. std::cout << " " << static_cast(i); if(!last) std::cout << ","; } // Write one table entry (UTF8 value), 1-5 bytes void writeChar(char *value, int length, bool last, const std::string &comment="") { assert(length >= 1 && length <= 5); tab(); num(length, false); for(int i=0;i<5;i++) num(value[i], last && i==4); if(comment != "") std::cout << " // " << comment; std::cout << std::endl; } // What to write on missing characters void writeMissing(bool last) { // Just write a space character char value[5]; value[0] = ' '; for(int i=1; i<5; i++) value[i] = 0; writeChar(value, 1, last, "not part of this charset"); } int write_table(const std::string &charset, const std::string &tableName) { // Write table header std::cout << "const static signed char " << tableName << "[] =\n{\n"; // Open conversion system iconv_t cd = iconv_open ("UTF-8", charset.c_str()); // Convert each character from 0 to 255 for(int i=0; i<256; i++) { bool last = (i==255); char input = i; char *iptr = &input; size_t ileft = 1; char output[5]; for(int k=0; k<5; k++) output[k] = 0; char *optr = output; size_t oleft = 5; size_t res = iconv(cd, &iptr, &ileft, &optr, &oleft); if(res) writeMissing(last); else writeChar(output, 5-oleft, last); } iconv_close (cd); // Finish table std::cout << "};\n"; return 0; } int main() { // Write header guard std::cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; // Write namespace std::cout << "namespace ToUTF8\n{\n\n"; // Central European and Eastern European languages that use Latin script, such as // Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian. std::cout << "\n/// Central European and Eastern European languages that use Latin script," "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," "\n/// Serbian (Latin script), Romanian and Albanian." "\n"; write_table("WINDOWS-1250", "windows_1250"); // Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages std::cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" "\n/// and other languages" "\n"; write_table("WINDOWS-1251", "windows_1251"); // English std::cout << "\n/// Latin alphabet used by English and some other Western languages" "\n"; write_table("WINDOWS-1252", "windows_1252"); write_table("CP437", "cp437"); // Close namespace std::cout << "\n}\n\n"; // Close header guard std::cout << "#endif\n\n"; return 0; } openmw-openmw-0.48.0/components/to_utf8/tables_gen.hpp000066400000000000000000000640061445372753700230330ustar00rootroot00000000000000#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H #define COMPONENTS_TOUTF8_TABLE_GEN_H namespace ToUTF8 { /// Central European and Eastern European languages that use Latin script, /// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, /// Serbian (Latin script), Romanian and Albanian. const static signed char windows_1250[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 1, 9, 0, 0, 0, 0, 1, 10, 0, 0, 0, 0, 1, 11, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0, 1, 13, 0, 0, 0, 0, 1, 14, 0, 0, 0, 0, 1, 15, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 1, 17, 0, 0, 0, 0, 1, 18, 0, 0, 0, 0, 1, 19, 0, 0, 0, 0, 1, 20, 0, 0, 0, 0, 1, 21, 0, 0, 0, 0, 1, 22, 0, 0, 0, 0, 1, 23, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 1, 25, 0, 0, 0, 0, 1, 26, 0, 0, 0, 0, 1, 27, 0, 0, 0, 0, 1, 28, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, 1, 30, 0, 0, 0, 0, 1, 31, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 33, 0, 0, 0, 0, 1, 34, 0, 0, 0, 0, 1, 35, 0, 0, 0, 0, 1, 36, 0, 0, 0, 0, 1, 37, 0, 0, 0, 0, 1, 38, 0, 0, 0, 0, 1, 39, 0, 0, 0, 0, 1, 40, 0, 0, 0, 0, 1, 41, 0, 0, 0, 0, 1, 42, 0, 0, 0, 0, 1, 43, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 1, 45, 0, 0, 0, 0, 1, 46, 0, 0, 0, 0, 1, 47, 0, 0, 0, 0, 1, 48, 0, 0, 0, 0, 1, 49, 0, 0, 0, 0, 1, 50, 0, 0, 0, 0, 1, 51, 0, 0, 0, 0, 1, 52, 0, 0, 0, 0, 1, 53, 0, 0, 0, 0, 1, 54, 0, 0, 0, 0, 1, 55, 0, 0, 0, 0, 1, 56, 0, 0, 0, 0, 1, 57, 0, 0, 0, 0, 1, 58, 0, 0, 0, 0, 1, 59, 0, 0, 0, 0, 1, 60, 0, 0, 0, 0, 1, 61, 0, 0, 0, 0, 1, 62, 0, 0, 0, 0, 1, 63, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 65, 0, 0, 0, 0, 1, 66, 0, 0, 0, 0, 1, 67, 0, 0, 0, 0, 1, 68, 0, 0, 0, 0, 1, 69, 0, 0, 0, 0, 1, 70, 0, 0, 0, 0, 1, 71, 0, 0, 0, 0, 1, 72, 0, 0, 0, 0, 1, 73, 0, 0, 0, 0, 1, 74, 0, 0, 0, 0, 1, 75, 0, 0, 0, 0, 1, 76, 0, 0, 0, 0, 1, 77, 0, 0, 0, 0, 1, 78, 0, 0, 0, 0, 1, 79, 0, 0, 0, 0, 1, 80, 0, 0, 0, 0, 1, 81, 0, 0, 0, 0, 1, 82, 0, 0, 0, 0, 1, 83, 0, 0, 0, 0, 1, 84, 0, 0, 0, 0, 1, 85, 0, 0, 0, 0, 1, 86, 0, 0, 0, 0, 1, 87, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 1, 89, 0, 0, 0, 0, 1, 90, 0, 0, 0, 0, 1, 91, 0, 0, 0, 0, 1, 92, 0, 0, 0, 0, 1, 93, 0, 0, 0, 0, 1, 94, 0, 0, 0, 0, 1, 95, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 1, 97, 0, 0, 0, 0, 1, 98, 0, 0, 0, 0, 1, 99, 0, 0, 0, 0, 1, 100, 0, 0, 0, 0, 1, 101, 0, 0, 0, 0, 1, 102, 0, 0, 0, 0, 1, 103, 0, 0, 0, 0, 1, 104, 0, 0, 0, 0, 1, 105, 0, 0, 0, 0, 1, 106, 0, 0, 0, 0, 1, 107, 0, 0, 0, 0, 1, 108, 0, 0, 0, 0, 1, 109, 0, 0, 0, 0, 1, 110, 0, 0, 0, 0, 1, 111, 0, 0, 0, 0, 1, 112, 0, 0, 0, 0, 1, 113, 0, 0, 0, 0, 1, 114, 0, 0, 0, 0, 1, 115, 0, 0, 0, 0, 1, 116, 0, 0, 0, 0, 1, 117, 0, 0, 0, 0, 1, 118, 0, 0, 0, 0, 1, 119, 0, 0, 0, 0, 1, 120, 0, 0, 0, 0, 1, 121, 0, 0, 0, 0, 1, 122, 0, 0, 0, 0, 1, 123, 0, 0, 0, 0, 1, 124, 0, 0, 0, 0, 1, 125, 0, 0, 0, 0, 1, 126, 0, 0, 0, 0, 1, 127, 0, 0, 0, 0, 3, -30, -126, -84, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -102, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -98, 0, 0, 3, -30, -128, -90, 0, 0, 3, -30, -128, -96, 0, 0, 3, -30, -128, -95, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -80, 0, 0, 2, -59, -96, 0, 0, 0, 3, -30, -128, -71, 0, 0, 2, -59, -102, 0, 0, 0, 2, -59, -92, 0, 0, 0, 2, -59, -67, 0, 0, 0, 2, -59, -71, 0, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -104, 0, 0, 3, -30, -128, -103, 0, 0, 3, -30, -128, -100, 0, 0, 3, -30, -128, -99, 0, 0, 3, -30, -128, -94, 0, 0, 3, -30, -128, -109, 0, 0, 3, -30, -128, -108, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -124, -94, 0, 0, 2, -59, -95, 0, 0, 0, 3, -30, -128, -70, 0, 0, 2, -59, -101, 0, 0, 0, 2, -59, -91, 0, 0, 0, 2, -59, -66, 0, 0, 0, 2, -59, -70, 0, 0, 0, 2, -62, -96, 0, 0, 0, 2, -53, -121, 0, 0, 0, 2, -53, -104, 0, 0, 0, 2, -59, -127, 0, 0, 0, 2, -62, -92, 0, 0, 0, 2, -60, -124, 0, 0, 0, 2, -62, -90, 0, 0, 0, 2, -62, -89, 0, 0, 0, 2, -62, -88, 0, 0, 0, 2, -62, -87, 0, 0, 0, 2, -59, -98, 0, 0, 0, 2, -62, -85, 0, 0, 0, 2, -62, -84, 0, 0, 0, 2, -62, -83, 0, 0, 0, 2, -62, -82, 0, 0, 0, 2, -59, -69, 0, 0, 0, 2, -62, -80, 0, 0, 0, 2, -62, -79, 0, 0, 0, 2, -53, -101, 0, 0, 0, 2, -59, -126, 0, 0, 0, 2, -62, -76, 0, 0, 0, 2, -62, -75, 0, 0, 0, 2, -62, -74, 0, 0, 0, 2, -62, -73, 0, 0, 0, 2, -62, -72, 0, 0, 0, 2, -60, -123, 0, 0, 0, 2, -59, -97, 0, 0, 0, 2, -62, -69, 0, 0, 0, 2, -60, -67, 0, 0, 0, 2, -53, -99, 0, 0, 0, 2, -60, -66, 0, 0, 0, 2, -59, -68, 0, 0, 0, 2, -59, -108, 0, 0, 0, 2, -61, -127, 0, 0, 0, 2, -61, -126, 0, 0, 0, 2, -60, -126, 0, 0, 0, 2, -61, -124, 0, 0, 0, 2, -60, -71, 0, 0, 0, 2, -60, -122, 0, 0, 0, 2, -61, -121, 0, 0, 0, 2, -60, -116, 0, 0, 0, 2, -61, -119, 0, 0, 0, 2, -60, -104, 0, 0, 0, 2, -61, -117, 0, 0, 0, 2, -60, -102, 0, 0, 0, 2, -61, -115, 0, 0, 0, 2, -61, -114, 0, 0, 0, 2, -60, -114, 0, 0, 0, 2, -60, -112, 0, 0, 0, 2, -59, -125, 0, 0, 0, 2, -59, -121, 0, 0, 0, 2, -61, -109, 0, 0, 0, 2, -61, -108, 0, 0, 0, 2, -59, -112, 0, 0, 0, 2, -61, -106, 0, 0, 0, 2, -61, -105, 0, 0, 0, 2, -59, -104, 0, 0, 0, 2, -59, -82, 0, 0, 0, 2, -61, -102, 0, 0, 0, 2, -59, -80, 0, 0, 0, 2, -61, -100, 0, 0, 0, 2, -61, -99, 0, 0, 0, 2, -59, -94, 0, 0, 0, 2, -61, -97, 0, 0, 0, 2, -59, -107, 0, 0, 0, 2, -61, -95, 0, 0, 0, 2, -61, -94, 0, 0, 0, 2, -60, -125, 0, 0, 0, 2, -61, -92, 0, 0, 0, 2, -60, -70, 0, 0, 0, 2, -60, -121, 0, 0, 0, 2, -61, -89, 0, 0, 0, 2, -60, -115, 0, 0, 0, 2, -61, -87, 0, 0, 0, 2, -60, -103, 0, 0, 0, 2, -61, -85, 0, 0, 0, 2, -60, -101, 0, 0, 0, 2, -61, -83, 0, 0, 0, 2, -61, -82, 0, 0, 0, 2, -60, -113, 0, 0, 0, 2, -60, -111, 0, 0, 0, 2, -59, -124, 0, 0, 0, 2, -59, -120, 0, 0, 0, 2, -61, -77, 0, 0, 0, 2, -61, -76, 0, 0, 0, 2, -59, -111, 0, 0, 0, 2, -61, -74, 0, 0, 0, 2, -61, -73, 0, 0, 0, 2, -59, -103, 0, 0, 0, 2, -59, -81, 0, 0, 0, 2, -61, -70, 0, 0, 0, 2, -59, -79, 0, 0, 0, 2, -61, -68, 0, 0, 0, 2, -61, -67, 0, 0, 0, 2, -59, -93, 0, 0, 0, 2, -53, -103, 0, 0, 0 }; /// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic /// and other languages const static signed char windows_1251[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 1, 9, 0, 0, 0, 0, 1, 10, 0, 0, 0, 0, 1, 11, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0, 1, 13, 0, 0, 0, 0, 1, 14, 0, 0, 0, 0, 1, 15, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 1, 17, 0, 0, 0, 0, 1, 18, 0, 0, 0, 0, 1, 19, 0, 0, 0, 0, 1, 20, 0, 0, 0, 0, 1, 21, 0, 0, 0, 0, 1, 22, 0, 0, 0, 0, 1, 23, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 1, 25, 0, 0, 0, 0, 1, 26, 0, 0, 0, 0, 1, 27, 0, 0, 0, 0, 1, 28, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, 1, 30, 0, 0, 0, 0, 1, 31, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 33, 0, 0, 0, 0, 1, 34, 0, 0, 0, 0, 1, 35, 0, 0, 0, 0, 1, 36, 0, 0, 0, 0, 1, 37, 0, 0, 0, 0, 1, 38, 0, 0, 0, 0, 1, 39, 0, 0, 0, 0, 1, 40, 0, 0, 0, 0, 1, 41, 0, 0, 0, 0, 1, 42, 0, 0, 0, 0, 1, 43, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 1, 45, 0, 0, 0, 0, 1, 46, 0, 0, 0, 0, 1, 47, 0, 0, 0, 0, 1, 48, 0, 0, 0, 0, 1, 49, 0, 0, 0, 0, 1, 50, 0, 0, 0, 0, 1, 51, 0, 0, 0, 0, 1, 52, 0, 0, 0, 0, 1, 53, 0, 0, 0, 0, 1, 54, 0, 0, 0, 0, 1, 55, 0, 0, 0, 0, 1, 56, 0, 0, 0, 0, 1, 57, 0, 0, 0, 0, 1, 58, 0, 0, 0, 0, 1, 59, 0, 0, 0, 0, 1, 60, 0, 0, 0, 0, 1, 61, 0, 0, 0, 0, 1, 62, 0, 0, 0, 0, 1, 63, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 65, 0, 0, 0, 0, 1, 66, 0, 0, 0, 0, 1, 67, 0, 0, 0, 0, 1, 68, 0, 0, 0, 0, 1, 69, 0, 0, 0, 0, 1, 70, 0, 0, 0, 0, 1, 71, 0, 0, 0, 0, 1, 72, 0, 0, 0, 0, 1, 73, 0, 0, 0, 0, 1, 74, 0, 0, 0, 0, 1, 75, 0, 0, 0, 0, 1, 76, 0, 0, 0, 0, 1, 77, 0, 0, 0, 0, 1, 78, 0, 0, 0, 0, 1, 79, 0, 0, 0, 0, 1, 80, 0, 0, 0, 0, 1, 81, 0, 0, 0, 0, 1, 82, 0, 0, 0, 0, 1, 83, 0, 0, 0, 0, 1, 84, 0, 0, 0, 0, 1, 85, 0, 0, 0, 0, 1, 86, 0, 0, 0, 0, 1, 87, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 1, 89, 0, 0, 0, 0, 1, 90, 0, 0, 0, 0, 1, 91, 0, 0, 0, 0, 1, 92, 0, 0, 0, 0, 1, 93, 0, 0, 0, 0, 1, 94, 0, 0, 0, 0, 1, 95, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 1, 97, 0, 0, 0, 0, 1, 98, 0, 0, 0, 0, 1, 99, 0, 0, 0, 0, 1, 100, 0, 0, 0, 0, 1, 101, 0, 0, 0, 0, 1, 102, 0, 0, 0, 0, 1, 103, 0, 0, 0, 0, 1, 104, 0, 0, 0, 0, 1, 105, 0, 0, 0, 0, 1, 106, 0, 0, 0, 0, 1, 107, 0, 0, 0, 0, 1, 108, 0, 0, 0, 0, 1, 109, 0, 0, 0, 0, 1, 110, 0, 0, 0, 0, 1, 111, 0, 0, 0, 0, 1, 112, 0, 0, 0, 0, 1, 113, 0, 0, 0, 0, 1, 114, 0, 0, 0, 0, 1, 115, 0, 0, 0, 0, 1, 116, 0, 0, 0, 0, 1, 117, 0, 0, 0, 0, 1, 118, 0, 0, 0, 0, 1, 119, 0, 0, 0, 0, 1, 120, 0, 0, 0, 0, 1, 121, 0, 0, 0, 0, 1, 122, 0, 0, 0, 0, 1, 123, 0, 0, 0, 0, 1, 124, 0, 0, 0, 0, 1, 125, 0, 0, 0, 0, 1, 126, 0, 0, 0, 0, 1, 127, 0, 0, 0, 0, 2, -48, -126, 0, 0, 0, 2, -48, -125, 0, 0, 0, 3, -30, -128, -102, 0, 0, 2, -47, -109, 0, 0, 0, 3, -30, -128, -98, 0, 0, 3, -30, -128, -90, 0, 0, 3, -30, -128, -96, 0, 0, 3, -30, -128, -95, 0, 0, 3, -30, -126, -84, 0, 0, 3, -30, -128, -80, 0, 0, 2, -48, -119, 0, 0, 0, 3, -30, -128, -71, 0, 0, 2, -48, -118, 0, 0, 0, 2, -48, -116, 0, 0, 0, 2, -48, -117, 0, 0, 0, 2, -48, -113, 0, 0, 0, 2, -47, -110, 0, 0, 0, 3, -30, -128, -104, 0, 0, 3, -30, -128, -103, 0, 0, 3, -30, -128, -100, 0, 0, 3, -30, -128, -99, 0, 0, 3, -30, -128, -94, 0, 0, 3, -30, -128, -109, 0, 0, 3, -30, -128, -108, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -124, -94, 0, 0, 2, -47, -103, 0, 0, 0, 3, -30, -128, -70, 0, 0, 2, -47, -102, 0, 0, 0, 2, -47, -100, 0, 0, 0, 2, -47, -101, 0, 0, 0, 2, -47, -97, 0, 0, 0, 2, -62, -96, 0, 0, 0, 2, -48, -114, 0, 0, 0, 2, -47, -98, 0, 0, 0, 2, -48, -120, 0, 0, 0, 2, -62, -92, 0, 0, 0, 2, -46, -112, 0, 0, 0, 2, -62, -90, 0, 0, 0, 2, -62, -89, 0, 0, 0, 2, -48, -127, 0, 0, 0, 2, -62, -87, 0, 0, 0, 2, -48, -124, 0, 0, 0, 2, -62, -85, 0, 0, 0, 2, -62, -84, 0, 0, 0, 2, -62, -83, 0, 0, 0, 2, -62, -82, 0, 0, 0, 2, -48, -121, 0, 0, 0, 2, -62, -80, 0, 0, 0, 2, -62, -79, 0, 0, 0, 2, -48, -122, 0, 0, 0, 2, -47, -106, 0, 0, 0, 2, -46, -111, 0, 0, 0, 2, -62, -75, 0, 0, 0, 2, -62, -74, 0, 0, 0, 2, -62, -73, 0, 0, 0, 2, -47, -111, 0, 0, 0, 3, -30, -124, -106, 0, 0, 2, -47, -108, 0, 0, 0, 2, -62, -69, 0, 0, 0, 2, -47, -104, 0, 0, 0, 2, -48, -123, 0, 0, 0, 2, -47, -107, 0, 0, 0, 2, -47, -105, 0, 0, 0, 2, -48, -112, 0, 0, 0, 2, -48, -111, 0, 0, 0, 2, -48, -110, 0, 0, 0, 2, -48, -109, 0, 0, 0, 2, -48, -108, 0, 0, 0, 2, -48, -107, 0, 0, 0, 2, -48, -106, 0, 0, 0, 2, -48, -105, 0, 0, 0, 2, -48, -104, 0, 0, 0, 2, -48, -103, 0, 0, 0, 2, -48, -102, 0, 0, 0, 2, -48, -101, 0, 0, 0, 2, -48, -100, 0, 0, 0, 2, -48, -99, 0, 0, 0, 2, -48, -98, 0, 0, 0, 2, -48, -97, 0, 0, 0, 2, -48, -96, 0, 0, 0, 2, -48, -95, 0, 0, 0, 2, -48, -94, 0, 0, 0, 2, -48, -93, 0, 0, 0, 2, -48, -92, 0, 0, 0, 2, -48, -91, 0, 0, 0, 2, -48, -90, 0, 0, 0, 2, -48, -89, 0, 0, 0, 2, -48, -88, 0, 0, 0, 2, -48, -87, 0, 0, 0, 2, -48, -86, 0, 0, 0, 2, -48, -85, 0, 0, 0, 2, -48, -84, 0, 0, 0, 2, -48, -83, 0, 0, 0, 2, -48, -82, 0, 0, 0, 2, -48, -81, 0, 0, 0, 2, -48, -80, 0, 0, 0, 2, -48, -79, 0, 0, 0, 2, -48, -78, 0, 0, 0, 2, -48, -77, 0, 0, 0, 2, -48, -76, 0, 0, 0, 2, -48, -75, 0, 0, 0, 2, -48, -74, 0, 0, 0, 2, -48, -73, 0, 0, 0, 2, -48, -72, 0, 0, 0, 2, -48, -71, 0, 0, 0, 2, -48, -70, 0, 0, 0, 2, -48, -69, 0, 0, 0, 2, -48, -68, 0, 0, 0, 2, -48, -67, 0, 0, 0, 2, -48, -66, 0, 0, 0, 2, -48, -65, 0, 0, 0, 2, -47, -128, 0, 0, 0, 2, -47, -127, 0, 0, 0, 2, -47, -126, 0, 0, 0, 2, -47, -125, 0, 0, 0, 2, -47, -124, 0, 0, 0, 2, -47, -123, 0, 0, 0, 2, -47, -122, 0, 0, 0, 2, -47, -121, 0, 0, 0, 2, -47, -120, 0, 0, 0, 2, -47, -119, 0, 0, 0, 2, -47, -118, 0, 0, 0, 2, -47, -117, 0, 0, 0, 2, -47, -116, 0, 0, 0, 2, -47, -115, 0, 0, 0, 2, -47, -114, 0, 0, 0, 2, -47, -113, 0, 0, 0 }; /// Latin alphabet used by English and some other Western languages const static signed char windows_1252[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 1, 9, 0, 0, 0, 0, 1, 10, 0, 0, 0, 0, 1, 11, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0, 1, 13, 0, 0, 0, 0, 1, 14, 0, 0, 0, 0, 1, 15, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 1, 17, 0, 0, 0, 0, 1, 18, 0, 0, 0, 0, 1, 19, 0, 0, 0, 0, 1, 20, 0, 0, 0, 0, 1, 21, 0, 0, 0, 0, 1, 22, 0, 0, 0, 0, 1, 23, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 1, 25, 0, 0, 0, 0, 1, 26, 0, 0, 0, 0, 1, 27, 0, 0, 0, 0, 1, 28, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, 1, 30, 0, 0, 0, 0, 1, 31, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 33, 0, 0, 0, 0, 1, 34, 0, 0, 0, 0, 1, 35, 0, 0, 0, 0, 1, 36, 0, 0, 0, 0, 1, 37, 0, 0, 0, 0, 1, 38, 0, 0, 0, 0, 1, 39, 0, 0, 0, 0, 1, 40, 0, 0, 0, 0, 1, 41, 0, 0, 0, 0, 1, 42, 0, 0, 0, 0, 1, 43, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 1, 45, 0, 0, 0, 0, 1, 46, 0, 0, 0, 0, 1, 47, 0, 0, 0, 0, 1, 48, 0, 0, 0, 0, 1, 49, 0, 0, 0, 0, 1, 50, 0, 0, 0, 0, 1, 51, 0, 0, 0, 0, 1, 52, 0, 0, 0, 0, 1, 53, 0, 0, 0, 0, 1, 54, 0, 0, 0, 0, 1, 55, 0, 0, 0, 0, 1, 56, 0, 0, 0, 0, 1, 57, 0, 0, 0, 0, 1, 58, 0, 0, 0, 0, 1, 59, 0, 0, 0, 0, 1, 60, 0, 0, 0, 0, 1, 61, 0, 0, 0, 0, 1, 62, 0, 0, 0, 0, 1, 63, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 65, 0, 0, 0, 0, 1, 66, 0, 0, 0, 0, 1, 67, 0, 0, 0, 0, 1, 68, 0, 0, 0, 0, 1, 69, 0, 0, 0, 0, 1, 70, 0, 0, 0, 0, 1, 71, 0, 0, 0, 0, 1, 72, 0, 0, 0, 0, 1, 73, 0, 0, 0, 0, 1, 74, 0, 0, 0, 0, 1, 75, 0, 0, 0, 0, 1, 76, 0, 0, 0, 0, 1, 77, 0, 0, 0, 0, 1, 78, 0, 0, 0, 0, 1, 79, 0, 0, 0, 0, 1, 80, 0, 0, 0, 0, 1, 81, 0, 0, 0, 0, 1, 82, 0, 0, 0, 0, 1, 83, 0, 0, 0, 0, 1, 84, 0, 0, 0, 0, 1, 85, 0, 0, 0, 0, 1, 86, 0, 0, 0, 0, 1, 87, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 1, 89, 0, 0, 0, 0, 1, 90, 0, 0, 0, 0, 1, 91, 0, 0, 0, 0, 1, 92, 0, 0, 0, 0, 1, 93, 0, 0, 0, 0, 1, 94, 0, 0, 0, 0, 1, 95, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 1, 97, 0, 0, 0, 0, 1, 98, 0, 0, 0, 0, 1, 99, 0, 0, 0, 0, 1, 100, 0, 0, 0, 0, 1, 101, 0, 0, 0, 0, 1, 102, 0, 0, 0, 0, 1, 103, 0, 0, 0, 0, 1, 104, 0, 0, 0, 0, 1, 105, 0, 0, 0, 0, 1, 106, 0, 0, 0, 0, 1, 107, 0, 0, 0, 0, 1, 108, 0, 0, 0, 0, 1, 109, 0, 0, 0, 0, 1, 110, 0, 0, 0, 0, 1, 111, 0, 0, 0, 0, 1, 112, 0, 0, 0, 0, 1, 113, 0, 0, 0, 0, 1, 114, 0, 0, 0, 0, 1, 115, 0, 0, 0, 0, 1, 116, 0, 0, 0, 0, 1, 117, 0, 0, 0, 0, 1, 118, 0, 0, 0, 0, 1, 119, 0, 0, 0, 0, 1, 120, 0, 0, 0, 0, 1, 121, 0, 0, 0, 0, 1, 122, 0, 0, 0, 0, 1, 123, 0, 0, 0, 0, 1, 124, 0, 0, 0, 0, 1, 125, 0, 0, 0, 0, 1, 126, 0, 0, 0, 0, 1, 127, 0, 0, 0, 0, 3, -30, -126, -84, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -102, 0, 0, 2, -58, -110, 0, 0, 0, 3, -30, -128, -98, 0, 0, 3, -30, -128, -90, 0, 0, 3, -30, -128, -96, 0, 0, 3, -30, -128, -95, 0, 0, 2, -53, -122, 0, 0, 0, 3, -30, -128, -80, 0, 0, 2, -59, -96, 0, 0, 0, 3, -30, -128, -71, 0, 0, 2, -59, -110, 0, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 2, -59, -67, 0, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 1, 32, 0, 0, 0, 0, // not part of this charset 3, -30, -128, -104, 0, 0, 3, -30, -128, -103, 0, 0, 3, -30, -128, -100, 0, 0, 3, -30, -128, -99, 0, 0, 3, -30, -128, -94, 0, 0, 3, -30, -128, -109, 0, 0, 3, -30, -128, -108, 0, 0, 2, -53, -100, 0, 0, 0, 3, -30, -124, -94, 0, 0, 2, -59, -95, 0, 0, 0, 3, -30, -128, -70, 0, 0, 2, -59, -109, 0, 0, 0, 1, 32, 0, 0, 0, 0, // not part of this charset 2, -59, -66, 0, 0, 0, 2, -59, -72, 0, 0, 0, 2, -62, -96, 0, 0, 0, 2, -62, -95, 0, 0, 0, 2, -62, -94, 0, 0, 0, 2, -62, -93, 0, 0, 0, 2, -62, -92, 0, 0, 0, 2, -62, -91, 0, 0, 0, 2, -62, -90, 0, 0, 0, 2, -62, -89, 0, 0, 0, 2, -62, -88, 0, 0, 0, 2, -62, -87, 0, 0, 0, 2, -62, -86, 0, 0, 0, 2, -62, -85, 0, 0, 0, 2, -62, -84, 0, 0, 0, 2, -62, -83, 0, 0, 0, 2, -62, -82, 0, 0, 0, 2, -62, -81, 0, 0, 0, 2, -62, -80, 0, 0, 0, 2, -62, -79, 0, 0, 0, 2, -62, -78, 0, 0, 0, 2, -62, -77, 0, 0, 0, 2, -62, -76, 0, 0, 0, 2, -62, -75, 0, 0, 0, 2, -62, -74, 0, 0, 0, 2, -62, -73, 0, 0, 0, 2, -62, -72, 0, 0, 0, 2, -62, -71, 0, 0, 0, 2, -62, -70, 0, 0, 0, 2, -62, -69, 0, 0, 0, 2, -62, -68, 0, 0, 0, 2, -62, -67, 0, 0, 0, 2, -62, -66, 0, 0, 0, 2, -62, -65, 0, 0, 0, 2, -61, -128, 0, 0, 0, 2, -61, -127, 0, 0, 0, 2, -61, -126, 0, 0, 0, 2, -61, -125, 0, 0, 0, 2, -61, -124, 0, 0, 0, 2, -61, -123, 0, 0, 0, 2, -61, -122, 0, 0, 0, 2, -61, -121, 0, 0, 0, 2, -61, -120, 0, 0, 0, 2, -61, -119, 0, 0, 0, 2, -61, -118, 0, 0, 0, 2, -61, -117, 0, 0, 0, 2, -61, -116, 0, 0, 0, 2, -61, -115, 0, 0, 0, 2, -61, -114, 0, 0, 0, 2, -61, -113, 0, 0, 0, 2, -61, -112, 0, 0, 0, 2, -61, -111, 0, 0, 0, 2, -61, -110, 0, 0, 0, 2, -61, -109, 0, 0, 0, 2, -61, -108, 0, 0, 0, 2, -61, -107, 0, 0, 0, 2, -61, -106, 0, 0, 0, 2, -61, -105, 0, 0, 0, 2, -61, -104, 0, 0, 0, 2, -61, -103, 0, 0, 0, 2, -61, -102, 0, 0, 0, 2, -61, -101, 0, 0, 0, 2, -61, -100, 0, 0, 0, 2, -61, -99, 0, 0, 0, 2, -61, -98, 0, 0, 0, 2, -61, -97, 0, 0, 0, 2, -61, -96, 0, 0, 0, 2, -61, -95, 0, 0, 0, 2, -61, -94, 0, 0, 0, 2, -61, -93, 0, 0, 0, 2, -61, -92, 0, 0, 0, 2, -61, -91, 0, 0, 0, 2, -61, -90, 0, 0, 0, 2, -61, -89, 0, 0, 0, 2, -61, -88, 0, 0, 0, 2, -61, -87, 0, 0, 0, 2, -61, -86, 0, 0, 0, 2, -61, -85, 0, 0, 0, 2, -61, -84, 0, 0, 0, 2, -61, -83, 0, 0, 0, 2, -61, -82, 0, 0, 0, 2, -61, -81, 0, 0, 0, 2, -61, -80, 0, 0, 0, 2, -61, -79, 0, 0, 0, 2, -61, -78, 0, 0, 0, 2, -61, -77, 0, 0, 0, 2, -61, -76, 0, 0, 0, 2, -61, -75, 0, 0, 0, 2, -61, -74, 0, 0, 0, 2, -61, -73, 0, 0, 0, 2, -61, -72, 0, 0, 0, 2, -61, -71, 0, 0, 0, 2, -61, -70, 0, 0, 0, 2, -61, -69, 0, 0, 0, 2, -61, -68, 0, 0, 0, 2, -61, -67, 0, 0, 0, 2, -61, -66, 0, 0, 0, 2, -61, -65, 0, 0, 0 }; const static signed char cp437[] = { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 1, 9, 0, 0, 0, 0, 1, 10, 0, 0, 0, 0, 1, 11, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0, 1, 13, 0, 0, 0, 0, 1, 14, 0, 0, 0, 0, 1, 15, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 1, 17, 0, 0, 0, 0, 1, 18, 0, 0, 0, 0, 1, 19, 0, 0, 0, 0, 1, 20, 0, 0, 0, 0, 1, 21, 0, 0, 0, 0, 1, 22, 0, 0, 0, 0, 1, 23, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 1, 25, 0, 0, 0, 0, 1, 26, 0, 0, 0, 0, 1, 27, 0, 0, 0, 0, 1, 28, 0, 0, 0, 0, 1, 29, 0, 0, 0, 0, 1, 30, 0, 0, 0, 0, 1, 31, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 33, 0, 0, 0, 0, 1, 34, 0, 0, 0, 0, 1, 35, 0, 0, 0, 0, 1, 36, 0, 0, 0, 0, 1, 37, 0, 0, 0, 0, 1, 38, 0, 0, 0, 0, 1, 39, 0, 0, 0, 0, 1, 40, 0, 0, 0, 0, 1, 41, 0, 0, 0, 0, 1, 42, 0, 0, 0, 0, 1, 43, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 1, 45, 0, 0, 0, 0, 1, 46, 0, 0, 0, 0, 1, 47, 0, 0, 0, 0, 1, 48, 0, 0, 0, 0, 1, 49, 0, 0, 0, 0, 1, 50, 0, 0, 0, 0, 1, 51, 0, 0, 0, 0, 1, 52, 0, 0, 0, 0, 1, 53, 0, 0, 0, 0, 1, 54, 0, 0, 0, 0, 1, 55, 0, 0, 0, 0, 1, 56, 0, 0, 0, 0, 1, 57, 0, 0, 0, 0, 1, 58, 0, 0, 0, 0, 1, 59, 0, 0, 0, 0, 1, 60, 0, 0, 0, 0, 1, 61, 0, 0, 0, 0, 1, 62, 0, 0, 0, 0, 1, 63, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 65, 0, 0, 0, 0, 1, 66, 0, 0, 0, 0, 1, 67, 0, 0, 0, 0, 1, 68, 0, 0, 0, 0, 1, 69, 0, 0, 0, 0, 1, 70, 0, 0, 0, 0, 1, 71, 0, 0, 0, 0, 1, 72, 0, 0, 0, 0, 1, 73, 0, 0, 0, 0, 1, 74, 0, 0, 0, 0, 1, 75, 0, 0, 0, 0, 1, 76, 0, 0, 0, 0, 1, 77, 0, 0, 0, 0, 1, 78, 0, 0, 0, 0, 1, 79, 0, 0, 0, 0, 1, 80, 0, 0, 0, 0, 1, 81, 0, 0, 0, 0, 1, 82, 0, 0, 0, 0, 1, 83, 0, 0, 0, 0, 1, 84, 0, 0, 0, 0, 1, 85, 0, 0, 0, 0, 1, 86, 0, 0, 0, 0, 1, 87, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 1, 89, 0, 0, 0, 0, 1, 90, 0, 0, 0, 0, 1, 91, 0, 0, 0, 0, 1, 92, 0, 0, 0, 0, 1, 93, 0, 0, 0, 0, 1, 94, 0, 0, 0, 0, 1, 95, 0, 0, 0, 0, 1, 96, 0, 0, 0, 0, 1, 97, 0, 0, 0, 0, 1, 98, 0, 0, 0, 0, 1, 99, 0, 0, 0, 0, 1, 100, 0, 0, 0, 0, 1, 101, 0, 0, 0, 0, 1, 102, 0, 0, 0, 0, 1, 103, 0, 0, 0, 0, 1, 104, 0, 0, 0, 0, 1, 105, 0, 0, 0, 0, 1, 106, 0, 0, 0, 0, 1, 107, 0, 0, 0, 0, 1, 108, 0, 0, 0, 0, 1, 109, 0, 0, 0, 0, 1, 110, 0, 0, 0, 0, 1, 111, 0, 0, 0, 0, 1, 112, 0, 0, 0, 0, 1, 113, 0, 0, 0, 0, 1, 114, 0, 0, 0, 0, 1, 115, 0, 0, 0, 0, 1, 116, 0, 0, 0, 0, 1, 117, 0, 0, 0, 0, 1, 118, 0, 0, 0, 0, 1, 119, 0, 0, 0, 0, 1, 120, 0, 0, 0, 0, 1, 121, 0, 0, 0, 0, 1, 122, 0, 0, 0, 0, 1, 123, 0, 0, 0, 0, 1, 124, 0, 0, 0, 0, 1, 125, 0, 0, 0, 0, 1, 126, 0, 0, 0, 0, 1, 127, 0, 0, 0, 0, 2, -61, -121, 0, 0, 0, 2, -61, -68, 0, 0, 0, 2, -61, -87, 0, 0, 0, 2, -61, -94, 0, 0, 0, 2, -61, -92, 0, 0, 0, 2, -61, -96, 0, 0, 0, 2, -61, -91, 0, 0, 0, 2, -61, -89, 0, 0, 0, 2, -61, -86, 0, 0, 0, 2, -61, -85, 0, 0, 0, 2, -61, -88, 0, 0, 0, 2, -61, -81, 0, 0, 0, 2, -61, -82, 0, 0, 0, 2, -61, -84, 0, 0, 0, 2, -61, -124, 0, 0, 0, 2, -61, -123, 0, 0, 0, 2, -61, -119, 0, 0, 0, 2, -61, -90, 0, 0, 0, 2, -61, -122, 0, 0, 0, 2, -61, -76, 0, 0, 0, 2, -61, -74, 0, 0, 0, 2, -61, -78, 0, 0, 0, 2, -61, -69, 0, 0, 0, 2, -61, -71, 0, 0, 0, 2, -61, -65, 0, 0, 0, 2, -61, -106, 0, 0, 0, 2, -61, -100, 0, 0, 0, 2, -62, -94, 0, 0, 0, 2, -62, -93, 0, 0, 0, 2, -62, -91, 0, 0, 0, 3, -30, -126, -89, 0, 0, 2, -58, -110, 0, 0, 0, 2, -61, -95, 0, 0, 0, 2, -61, -83, 0, 0, 0, 2, -61, -77, 0, 0, 0, 2, -61, -70, 0, 0, 0, 2, -61, -79, 0, 0, 0, 2, -61, -111, 0, 0, 0, 2, -62, -86, 0, 0, 0, 2, -62, -70, 0, 0, 0, 2, -62, -65, 0, 0, 0, 3, -30, -116, -112, 0, 0, 2, -62, -84, 0, 0, 0, 2, -62, -67, 0, 0, 0, 2, -62, -68, 0, 0, 0, 2, -62, -95, 0, 0, 0, 2, -62, -85, 0, 0, 0, 2, -62, -69, 0, 0, 0, 3, -30, -106, -111, 0, 0, 3, -30, -106, -110, 0, 0, 3, -30, -106, -109, 0, 0, 3, -30, -108, -126, 0, 0, 3, -30, -108, -92, 0, 0, 3, -30, -107, -95, 0, 0, 3, -30, -107, -94, 0, 0, 3, -30, -107, -106, 0, 0, 3, -30, -107, -107, 0, 0, 3, -30, -107, -93, 0, 0, 3, -30, -107, -111, 0, 0, 3, -30, -107, -105, 0, 0, 3, -30, -107, -99, 0, 0, 3, -30, -107, -100, 0, 0, 3, -30, -107, -101, 0, 0, 3, -30, -108, -112, 0, 0, 3, -30, -108, -108, 0, 0, 3, -30, -108, -76, 0, 0, 3, -30, -108, -84, 0, 0, 3, -30, -108, -100, 0, 0, 3, -30, -108, -128, 0, 0, 3, -30, -108, -68, 0, 0, 3, -30, -107, -98, 0, 0, 3, -30, -107, -97, 0, 0, 3, -30, -107, -102, 0, 0, 3, -30, -107, -108, 0, 0, 3, -30, -107, -87, 0, 0, 3, -30, -107, -90, 0, 0, 3, -30, -107, -96, 0, 0, 3, -30, -107, -112, 0, 0, 3, -30, -107, -84, 0, 0, 3, -30, -107, -89, 0, 0, 3, -30, -107, -88, 0, 0, 3, -30, -107, -92, 0, 0, 3, -30, -107, -91, 0, 0, 3, -30, -107, -103, 0, 0, 3, -30, -107, -104, 0, 0, 3, -30, -107, -110, 0, 0, 3, -30, -107, -109, 0, 0, 3, -30, -107, -85, 0, 0, 3, -30, -107, -86, 0, 0, 3, -30, -108, -104, 0, 0, 3, -30, -108, -116, 0, 0, 3, -30, -106, -120, 0, 0, 3, -30, -106, -124, 0, 0, 3, -30, -106, -116, 0, 0, 3, -30, -106, -112, 0, 0, 3, -30, -106, -128, 0, 0, 2, -50, -79, 0, 0, 0, 2, -61, -97, 0, 0, 0, 2, -50, -109, 0, 0, 0, 2, -49, -128, 0, 0, 0, 2, -50, -93, 0, 0, 0, 2, -49, -125, 0, 0, 0, 2, -62, -75, 0, 0, 0, 2, -49, -124, 0, 0, 0, 2, -50, -90, 0, 0, 0, 2, -50, -104, 0, 0, 0, 2, -50, -87, 0, 0, 0, 2, -50, -76, 0, 0, 0, 3, -30, -120, -98, 0, 0, 2, -49, -122, 0, 0, 0, 2, -50, -75, 0, 0, 0, 3, -30, -120, -87, 0, 0, 3, -30, -119, -95, 0, 0, 2, -62, -79, 0, 0, 0, 3, -30, -119, -91, 0, 0, 3, -30, -119, -92, 0, 0, 3, -30, -116, -96, 0, 0, 3, -30, -116, -95, 0, 0, 2, -61, -73, 0, 0, 0, 3, -30, -119, -120, 0, 0, 2, -62, -80, 0, 0, 0, 3, -30, -120, -103, 0, 0, 2, -62, -73, 0, 0, 0, 3, -30, -120, -102, 0, 0, 3, -30, -127, -65, 0, 0, 2, -62, -78, 0, 0, 0, 3, -30, -106, -96, 0, 0, 2, -62, -96, 0, 0, 0 }; } #endif openmw-openmw-0.48.0/components/to_utf8/to_utf8.cpp000066400000000000000000000274011445372753700223110ustar00rootroot00000000000000#include "to_utf8.hpp" #include #include #include #include #include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library is designed to be extened to support more source encodings later, which means that we may add support for Russian, Polish and Chinese files and so on. The code does not depend on any external library at runtime. Instead, it uses a pregenerated table made with iconv (see gen_iconv.cpp and the Makefile) which is located in tables_gen.hpp. This is both faster and uses less dependencies. The tables would only need to be regenerated if we are adding support more input encodings. As such, there is no need to make the generator code platform independent. The library is optimized for the case of pure ASCII input strings, which is the vast majority of cases at least for the English version. A test of my version of Morrowind.esm got 130 non-ASCII vs 236195 ASCII strings, or less than 0.06% of strings containing non-ASCII characters. To optmize for this, ff the first pass of the string does not find any non-ASCII characters, the entire string is passed along without any modification. Most of the non-ASCII strings are books, and are quite large. (The non-ASCII characters are typically starting and ending quotation marks.) Within these, almost all the characters are ASCII. For this purpose, the library is also optimized for mostly-ASCII contents even in the cases where some conversion is necessary. */ // Generated tables #include "tables_gen.hpp" using namespace ToUTF8; namespace { std::string_view::iterator skipAscii(std::string_view input) { return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); } std::basic_string_view getTranslationArray(FromType sourceEncoding) { switch (sourceEncoding) { case ToUTF8::WINDOWS_1252: return {ToUTF8::windows_1252, std::size(ToUTF8::windows_1252)}; case ToUTF8::WINDOWS_1250: return {ToUTF8::windows_1250, std::size(ToUTF8::windows_1250)}; case ToUTF8::WINDOWS_1251: return {ToUTF8::windows_1251, std::size(ToUTF8::windows_1251)}; case ToUTF8::CP437: return {ToUTF8::cp437, std::size(ToUTF8::cp437)}; } throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); } // Make sure the output vector is large enough for 'size' bytes, // including a terminating zero after it. void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) { if (buffer.size() > size) { buffer[size] = 0; return; } if (buffer.size() == size) return; switch (bufferAllocationPolicy) { case BufferAllocationPolicy::FitToRequiredSize: buffer.resize(size); break; case BufferAllocationPolicy::UseGrowFactor: // Add some extra padding to reduce the chance of having to resize // again later. buffer.resize(3 * size); // And make sure the string is zero terminated buffer[size] = 0; break; } } } StatelessUtf8Encoder::StatelessUtf8Encoder(FromType sourceEncoding) : mTranslationArray(getTranslationArray(sourceEncoding)) { } std::string_view StatelessUtf8Encoder::getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans // to add more encodings to this module (we are using utf8 for new // content files), so that shouldn't be an issue. // Compute output length, and check for pure ascii input at the same // time. const auto [outlen, ascii] = getLength(input); // If we're pure ascii, then don't bother converting anything. if(ascii) return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen, bufferAllocationPolicy, buffer); char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0; ++it) copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes assert((out - buffer.data()) == (int)outlen); // And make extra sure the output is null terminated assert(buffer.size() >= outlen); assert(buffer[outlen] == 0); return std::string_view(buffer.data(), outlen); } std::string_view StatelessUtf8Encoder::getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input // encoding shares its first 128 values (0-127) with ASCII. These // conditions must be checked again if you add more input encodings // later. // Compute output length, and check for pure ascii input at the same // time. const auto [outlen, ascii] = getLengthLegacyEnc(input); // If we're pure ascii, then don't bother converting anything. if(ascii) return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen, bufferAllocationPolicy, buffer); char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0;) copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes assert((out - buffer.data()) == static_cast(outlen)); // And make extra sure the output is null terminated assert(buffer.size() >= outlen); assert(buffer[outlen] == 0); return std::string_view(buffer.data(), outlen); } /** Get the total length length needed to decode the given string with the given translation array. The arrays are encoded with 6 bytes per character, with the first giving the length and the next 5 the actual data. The function serves a dual purpose for optimization reasons: it checks if the input is pure ascii (all values are <= 127). If this is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ std::pair StatelessUtf8Encoder::getLength(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. if (it == input.end() || *it == 0) return {it - input.begin(), true}; std::size_t len = it - input.begin(); do { // Find the translated length of this character in the // lookup table. len += mTranslationArray[static_cast(*it) * 6]; ++it; } while (it != input.end() && *it != 0); return {len, false}; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. void StatelessUtf8Encoder::copyFromArray(unsigned char ch, char* &out) const { // Optimize for ASCII values if (ch < 128) { *(out++) = ch; return; } const signed char *in = &mTranslationArray[ch * 6]; int len = *(in++); memcpy(out, in, len); out += len; } std::pair StatelessUtf8Encoder::getLengthLegacyEnc(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. if (it == input.end() || *it == 0) return {it - input.begin(), true}; std::size_t len = it - input.begin(); std::size_t symbolLen = 0; do { symbolLen += 1; // Find the translated length of this character in the // lookup table. switch (static_cast(*it)) { case 0xe2: symbolLen -= 2; break; case 0xc2: case 0xcb: case 0xc4: case 0xc6: case 0xc3: case 0xd0: case 0xd1: case 0xd2: case 0xc5: symbolLen -= 1; break; default: len += symbolLen; symbolLen = 0; break; } ++it; } while (it != input.end() && *it != 0); return {len, false}; } void StatelessUtf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values if (ch < 128) { *(out++) = ch; return; } int len = 1; switch (ch) { case 0xe2: len = 3; break; case 0xc2: case 0xcb: case 0xc4: case 0xc6: case 0xc3: case 0xd0: case 0xd1: case 0xd2: case 0xc5: len = 2; break; } if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space) { *(out++) = ch; return; } if (chp == end) return; unsigned char ch2 = *(chp++); unsigned char ch3 = '\0'; if (len == 3) { if (chp == end) return; ch3 = *(chp++); } for (int i = 128; i < 256; i++) { unsigned char b1 = mTranslationArray[i*6 + 1], b2 = mTranslationArray[i*6 + 2], b3 = mTranslationArray[i*6 + 3]; if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) { *(out++) = (char)i; return; } } Log(Debug::Info) << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3; *(out++) = ch; // Could not find glyph, just put whatever } Utf8Encoder::Utf8Encoder(FromType sourceEncoding) : mBuffer(50 * 1024, '\0') , mImpl(sourceEncoding) { } std::string_view Utf8Encoder::getUtf8(std::string_view input) { return mImpl.getUtf8(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); } std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) { return mImpl.getLegacyEnc(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); } ToUTF8::FromType ToUTF8::calculateEncoding(std::string_view encodingName) { if (encodingName == "win1250") return ToUTF8::WINDOWS_1250; else if (encodingName == "win1251") return ToUTF8::WINDOWS_1251; else if (encodingName == "win1252") return ToUTF8::WINDOWS_1252; else throw std::runtime_error("Unknown encoding '" + std::string(encodingName) + "', see openmw --help for available options."); } std::string ToUTF8::encodingUsingMessage(std::string_view encodingName) { if (encodingName == "win1250") return "Using Central and Eastern European font encoding."; else if (encodingName == "win1251") return "Using Cyrillic font encoding."; else if (encodingName == "win1252") return "Using default (English) font encoding."; else throw std::runtime_error("Unknown encoding '" + std::string(encodingName) + "', see openmw --help for available options."); } openmw-openmw-0.48.0/components/to_utf8/to_utf8.hpp000066400000000000000000000055221445372753700223160ustar00rootroot00000000000000#ifndef COMPONENTS_TOUTF8_H #define COMPONENTS_TOUTF8_H #include #include #include #include namespace ToUTF8 { // These are all the currently supported code pages enum FromType { WINDOWS_1250, // Central ane Eastern European languages WINDOWS_1251, // Cyrillic languages WINDOWS_1252, // Used by English version of Morrowind (and // probably others) CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files. }; enum class BufferAllocationPolicy { FitToRequiredSize, UseGrowFactor, }; FromType calculateEncoding(std::string_view encodingName); std::string encodingUsingMessage(std::string_view encodingName); class StatelessUtf8Encoder { public: explicit StatelessUtf8Encoder(FromType sourceEncoding); /// Convert to UTF8 from the previously given code page. /// Returns a view to passed buffer that will be resized to fit output if it's too small. std::string_view getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; /// Convert from UTF-8 to sourceEncoding. /// Returns a view to passed buffer that will be resized to fit output if it's too small. std::string_view getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; private: inline std::pair getLength(std::string_view input) const; inline void copyFromArray(unsigned char chp, char* &out) const; inline std::pair getLengthLegacyEnc(std::string_view input) const; inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; const std::basic_string_view mTranslationArray; }; class Utf8Encoder { public: explicit Utf8Encoder(FromType sourceEncoding); /// Convert to UTF8 from the previously given code page. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getUtf8(std::string_view input); /// Convert from UTF-8 to sourceEncoding. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); private: std::string mBuffer; StatelessUtf8Encoder mImpl; }; } #endif openmw-openmw-0.48.0/components/translation/000077500000000000000000000000001445372753700211575ustar00rootroot00000000000000openmw-openmw-0.48.0/components/translation/translation.cpp000066400000000000000000000074021445372753700242240ustar00rootroot00000000000000#include "translation.hpp" #include namespace Translation { Storage::Storage() : mEncoder(nullptr) { } void Storage::loadTranslationData(const Files::Collections& dataFileCollections, const std::string& esmFileName) { std::string esmNameNoExtension(Misc::StringUtils::lowerCase(esmFileName)); //changing the extension size_t dotPos = esmNameNoExtension.rfind('.'); if (dotPos != std::string::npos) esmNameNoExtension.resize(dotPos); loadData(mCellNamesTranslations, esmNameNoExtension, ".cel", dataFileCollections); loadData(mPhraseForms, esmNameNoExtension, ".top", dataFileCollections); loadData(mTopicIDs, esmNameNoExtension, ".mrk", dataFileCollections); } void Storage::loadData(ContainerType& container, const std::string& fileNameNoExtension, const std::string& extension, const Files::Collections& dataFileCollections) { std::string fileName = fileNameNoExtension + extension; if (dataFileCollections.getCollection (extension).doesExist (fileName)) { boost::filesystem::ifstream stream ( dataFileCollections.getCollection (extension).getPath (fileName)); if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); loadDataFromStream(container, stream); } } void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) { std::string line; while (!stream.eof() && !stream.fail()) { std::getline( stream, line ); if (!line.empty() && *line.rbegin() == '\r') line.resize(line.size() - 1); if (!line.empty()) { const std::string_view utf8 = mEncoder->getUtf8(line); size_t tab_pos = utf8.find('\t'); if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8.size() - 1) { const std::string_view key = utf8.substr(0, tab_pos); const std::string_view value = utf8.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.emplace(key, value); } } } } std::string Storage::translateCellName(const std::string& cellName) const { std::map::const_iterator entry = mCellNamesTranslations.find(cellName); if (entry == mCellNamesTranslations.end()) return cellName; return entry->second; } std::string Storage::topicID(const std::string& phrase) const { std::string result = topicStandardForm(phrase); //seeking for the topic ID std::map::const_iterator topicIDIterator = mTopicIDs.find(result); if (topicIDIterator != mTopicIDs.end()) result = topicIDIterator->second; return result; } std::string Storage::topicStandardForm(const std::string& phrase) const { std::map::const_iterator phraseFormsIterator = mPhraseForms.find(phrase); if (phraseFormsIterator != mPhraseForms.end()) return phraseFormsIterator->second; else return phrase; } void Storage::setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } bool Storage::hasTranslation() const { return !mCellNamesTranslations.empty() || !mTopicIDs.empty() || !mPhraseForms.empty(); } } openmw-openmw-0.48.0/components/translation/translation.hpp000066400000000000000000000023711445372753700242310ustar00rootroot00000000000000#ifndef COMPONENTS_TRANSLATION_DATA_H #define COMPONENTS_TRANSLATION_DATA_H #include #include namespace Translation { class Storage { public: Storage(); void loadTranslationData(const Files::Collections& dataFileCollections, const std::string& esmFileName); std::string translateCellName(const std::string& cellName) const; std::string topicID(const std::string& phrase) const; // Standard form usually means nominative case std::string topicStandardForm(const std::string& phrase) const; void setEncoder(ToUTF8::Utf8Encoder* encoder); bool hasTranslation() const; private: typedef std::map ContainerType; void loadData(ContainerType& container, const std::string& fileNameNoExtension, const std::string& extension, const Files::Collections& dataFileCollections); void loadDataFromStream(ContainerType& container, std::istream& stream); ToUTF8::Utf8Encoder* mEncoder; ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms; }; } #endif openmw-openmw-0.48.0/components/version/000077500000000000000000000000001445372753700203065ustar00rootroot00000000000000openmw-openmw-0.48.0/components/version/version.cpp000066400000000000000000000015101445372753700224740ustar00rootroot00000000000000#include "version.hpp" #include #include namespace Version { Version getOpenmwVersion(const std::string &resourcePath) { boost::filesystem::path path (resourcePath + "/version"); boost::filesystem::ifstream stream (path); Version v; std::getline(stream, v.mVersion); std::getline(stream, v.mCommitHash); std::getline(stream, v.mTagHash); return v; } std::string Version::describe() { std::string str = "OpenMW version " + mVersion; std::string rev = mCommitHash; if (!rev.empty()) { rev = rev.substr(0, 10); str += "\nRevision: " + rev; } return str; } std::string getOpenmwVersionDescription(const std::string &resourcePath) { Version v = getOpenmwVersion(resourcePath); return v.describe(); } } openmw-openmw-0.48.0/components/version/version.hpp000066400000000000000000000010431445372753700225020ustar00rootroot00000000000000#ifndef VERSION_HPP #define VERSION_HPP #include namespace Version { struct Version { std::string mVersion; std::string mCommitHash; std::string mTagHash; std::string describe(); }; /// Read OpenMW version from the version file located in resourcePath. Version getOpenmwVersion(const std::string& resourcePath); /// Helper function to getOpenmwVersion and describe() it std::string getOpenmwVersionDescription(const std::string& resourcePath); } #endif // VERSION_HPP openmw-openmw-0.48.0/components/vfs/000077500000000000000000000000001445372753700174175ustar00rootroot00000000000000openmw-openmw-0.48.0/components/vfs/archive.hpp000066400000000000000000000015541445372753700215560ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H #define OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H #include #include namespace VFS { class File { public: virtual ~File() {} virtual Files::IStreamPtr open() = 0; virtual std::string getPath() = 0; }; class Archive { public: virtual ~Archive() {} /// List all resources contained in this archive, and run the resource names through the given normalize function. virtual void listResources(std::map& out, char (*normalize_function) (char)) = 0; /// True if this archive contains the provided normalized file. virtual bool contains(const std::string& file, char (*normalize_function) (char)) const = 0; virtual std::string getDescription() const = 0; }; } #endif openmw-openmw-0.48.0/components/vfs/bsaarchive.cpp000066400000000000000000000062601445372753700222360ustar00rootroot00000000000000#include "bsaarchive.hpp" #include #include #include namespace VFS { BsaArchive::BsaArchive(const std::string &filename) { mFile = std::make_unique(); mFile->open(filename); const Bsa::BSAFile::FileList &filelist = mFile->getList(); for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) { mResources.emplace_back(&*it, mFile.get()); } } BsaArchive::BsaArchive() { } BsaArchive::~BsaArchive() { } void BsaArchive::listResources(std::map &out, char (*normalize_function)(char)) { for (std::vector::iterator it = mResources.begin(); it != mResources.end(); ++it) { std::string ent = it->mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); out[ent] = &*it; } } bool BsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const { for (const auto& it : mResources) { std::string ent = it.mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); if(file == ent) return true; } return false; } std::string BsaArchive::getDescription() const { return std::string{"BSA: "} + mFile->getFilename(); } // ------------------------------------------------------------------------------ BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) : mInfo(info) , mFile(bsa) { } Files::IStreamPtr BsaArchiveFile::open() { return mFile->getFile(mInfo); } CompressedBsaArchive::CompressedBsaArchive(const std::string &filename) : Archive() { mCompressedFile = std::make_unique(); mCompressedFile->open(filename); const Bsa::BSAFile::FileList &filelist = mCompressedFile->getList(); for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) { mCompressedResources.emplace_back(&*it, mCompressedFile.get()); } } void CompressedBsaArchive::listResources(std::map &out, char (*normalize_function)(char)) { for (std::vector::iterator it = mCompressedResources.begin(); it != mCompressedResources.end(); ++it) { std::string ent = it->mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); out[ent] = &*it; } } bool CompressedBsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const { for (const auto& it : mCompressedResources) { std::string ent = it.mInfo->name(); std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); if(file == ent) return true; } return false; } std::string CompressedBsaArchive::getDescription() const { return std::string{"BSA: "} + mCompressedFile->getFilename(); } CompressedBsaArchiveFile::CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::CompressedBSAFile* bsa) : mInfo(info) , mCompressedFile(bsa) { } Files::IStreamPtr CompressedBsaArchiveFile::open() { return mCompressedFile->getFile(mInfo); } } openmw-openmw-0.48.0/components/vfs/bsaarchive.hpp000066400000000000000000000037521445372753700222460ustar00rootroot00000000000000#ifndef VFS_BSAARCHIVE_HPP_ #define VFS_BSAARCHIVE_HPP_ #include "archive.hpp" #include #include namespace VFS { class BsaArchiveFile : public File { public: BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::BSAFile* bsa); Files::IStreamPtr open() override; std::string getPath() override { return mInfo->name(); } const Bsa::BSAFile::FileStruct* mInfo; Bsa::BSAFile* mFile; }; class CompressedBsaArchiveFile : public File { public: CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::CompressedBSAFile* bsa); Files::IStreamPtr open() override; std::string getPath() override { return mInfo->name(); } const Bsa::BSAFile::FileStruct* mInfo; Bsa::CompressedBSAFile* mCompressedFile; }; class BsaArchive : public Archive { public: BsaArchive(const std::string& filename); BsaArchive(); virtual ~BsaArchive(); void listResources(std::map& out, char (*normalize_function) (char)) override; bool contains(const std::string& file, char (*normalize_function) (char)) const override; std::string getDescription() const override; protected: std::unique_ptr mFile; std::vector mResources; }; class CompressedBsaArchive : public Archive { public: CompressedBsaArchive(const std::string& filename); virtual ~CompressedBsaArchive() {} void listResources(std::map& out, char (*normalize_function) (char)) override; bool contains(const std::string& file, char (*normalize_function) (char)) const override; std::string getDescription() const override; private: std::unique_ptr mCompressedFile; std::vector mCompressedResources; }; } #endif openmw-openmw-0.48.0/components/vfs/filesystemarchive.cpp000066400000000000000000000046551445372753700236630ustar00rootroot00000000000000#include "filesystemarchive.hpp" #include #include #include #include namespace VFS { FileSystemArchive::FileSystemArchive(const std::string &path) : mBuiltIndex(false) , mPath(path) { } void FileSystemArchive::listResources(std::map &out, char (*normalize_function)(char)) { if (!mBuiltIndex) { typedef boost::filesystem::recursive_directory_iterator directory_iterator; directory_iterator end; size_t prefix = mPath.size (); if (mPath.size () > 0 && mPath [prefix - 1] != '\\' && mPath [prefix - 1] != '/') ++prefix; for (directory_iterator i (mPath); i != end; ++i) { if(boost::filesystem::is_directory (*i)) continue; std::string proper = i->path ().string (); FileSystemArchiveFile file(proper); std::string searchable; std::transform(proper.begin() + prefix, proper.end(), std::back_inserter(searchable), normalize_function); const auto inserted = mIndex.insert(std::make_pair(searchable, file)); if (!inserted.second) Log(Debug::Warning) << "Warning: found duplicate file for '" << proper << "', please check your file system for two files with the same name in different cases."; else out[inserted.first->first] = &inserted.first->second; } mBuiltIndex = true; } else { for (index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) { out[it->first] = &it->second; } } } bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const { return mIndex.find(file) != mIndex.end(); } std::string FileSystemArchive::getDescription() const { return std::string{"DIR: "} + mPath; } // ---------------------------------------------------------------------------------- FileSystemArchiveFile::FileSystemArchiveFile(const std::string &path) : mPath(path) { } Files::IStreamPtr FileSystemArchiveFile::open() { return Files::openConstrainedFileStream(mPath); } } openmw-openmw-0.48.0/components/vfs/filesystemarchive.hpp000066400000000000000000000017451445372753700236650ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #define OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #include "archive.hpp" #include namespace VFS { class FileSystemArchiveFile : public File { public: FileSystemArchiveFile(const std::string& path); Files::IStreamPtr open() override; std::string getPath() override { return mPath; } private: std::string mPath; }; class FileSystemArchive : public Archive { public: FileSystemArchive(const std::string& path); void listResources(std::map& out, char (*normalize_function) (char)) override; bool contains(const std::string& file, char (*normalize_function) (char)) const override; std::string getDescription() const override; private: typedef std::map index; index mIndex; bool mBuiltIndex; std::string mPath; }; } #endif openmw-openmw-0.48.0/components/vfs/manager.cpp000066400000000000000000000070201445372753700215340ustar00rootroot00000000000000#include "manager.hpp" #include #include #include #include "archive.hpp" namespace { char strict_normalize_char(char ch) { return ch == '\\' ? '/' : ch; } char nonstrict_normalize_char(char ch) { return ch == '\\' ? '/' : Misc::StringUtils::toLower(ch); } void normalize_path(std::string& path, bool strict) { char (*normalize_char)(char) = strict ? &strict_normalize_char : &nonstrict_normalize_char; std::transform(path.begin(), path.end(), path.begin(), normalize_char); } } namespace VFS { Manager::Manager(bool strict) : mStrict(strict) { } Manager::~Manager() {} void Manager::reset() { mIndex.clear(); mArchives.clear(); } void Manager::addArchive(std::unique_ptr&& archive) { mArchives.push_back(std::move(archive)); } void Manager::buildIndex() { mIndex.clear(); for (const auto& archive : mArchives) archive->listResources(mIndex, mStrict ? &strict_normalize_char : &nonstrict_normalize_char); } Files::IStreamPtr Manager::get(std::string_view name) const { std::string normalized(name); normalize_path(normalized, mStrict); return getNormalized(normalized); } Files::IStreamPtr Manager::getNormalized(const std::string &normalizedName) const { std::map::const_iterator found = mIndex.find(normalizedName); if (found == mIndex.end()) throw std::runtime_error("Resource '" + normalizedName + "' not found"); return found->second->open(); } bool Manager::exists(std::string_view name) const { std::string normalized(name); normalize_path(normalized, mStrict); return mIndex.find(normalized) != mIndex.end(); } std::string Manager::normalizeFilename(std::string_view name) const { std::string result(name); normalize_path(result, mStrict); return result; } std::string Manager::getArchive(std::string_view name) const { std::string normalized(name); normalize_path(normalized, mStrict); for(auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { if((*it)->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) return (*it)->getDescription(); } return {}; } std::string Manager::getAbsoluteFileName(std::string_view name) const { std::string normalized(name); normalize_path(normalized, mStrict); std::map::const_iterator found = mIndex.find(normalized); if (found == mIndex.end()) throw std::runtime_error("Resource '" + normalized + "' not found"); return found->second->getPath(); } namespace { bool startsWith(std::string_view text, std::string_view start) { return text.rfind(start, 0) == 0; } } Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const { if (path.empty()) return { mIndex.begin(), mIndex.end() }; auto normalized = normalizeFilename(path); const auto it = mIndex.lower_bound(normalized); if (it == mIndex.end() || !startsWith(it->first, normalized)) return { it, it }; ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } } openmw-openmw-0.48.0/components/vfs/manager.hpp000066400000000000000000000101551445372753700215440ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_RESOURCEMANAGER_H #define OPENMW_COMPONENTS_RESOURCEMANAGER_H #include #include #include #include #include namespace VFS { class Archive; class File; template class IteratorPair { public: IteratorPair(Iterator first, Iterator last) : mFirst(first), mLast(last) {} Iterator begin() const { return mFirst; } Iterator end() const { return mLast; } private: Iterator mFirst; Iterator mLast; }; /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is /// contained in multiple archives, the last added archive will have priority. /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { class RecursiveDirectoryIterator { public: RecursiveDirectoryIterator(std::map::const_iterator it) : mIt(it) {} const std::string& operator*() const { return mIt->first; } const std::string* operator->() const { return &mIt->first; } bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } RecursiveDirectoryIterator& operator++() { ++mIt; return *this; } private: std::map::const_iterator mIt; }; using RecursiveDirectoryRange = IteratorPair; public: /// @param strict Use strict path handling? If enabled, no case folding will /// be done, but slash/backslash conversions are always done. Manager(bool strict); ~Manager(); // Empty the file index and unregister archives. void reset(); /// Register the given archive. All files contained in it will be added to the index on the next buildIndex() call. void addArchive(std::unique_ptr&& archive); /// Build the file index. Should be called when all archives have been registered. void buildIndex(); /// Does a file with this name exist? /// @note May be called from any thread once the index has been built. bool exists(std::string_view name) const; /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. [[nodiscard]] std::string normalizeFilename(std::string_view name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(std::string_view name) const; /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; std::string getArchive(std::string_view name) const; /// Recursivly iterate over the elements of the given path /// In practice it return all files of the VFS starting with the given path /// @note the path is normalized /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. std::string getAbsoluteFileName(std::string_view name) const; private: bool mStrict; std::vector> mArchives; std::map mIndex; }; } #endif openmw-openmw-0.48.0/components/vfs/registerarchives.cpp000066400000000000000000000040631445372753700234770ustar00rootroot00000000000000#include "registerarchives.hpp" #include #include #include #include #include #include namespace VFS { void registerArchives(VFS::Manager *vfs, const Files::Collections &collections, const std::vector &archives, bool useLooseFiles) { const Files::PathContainer& dataDirs = collections.getPaths(); for (std::vector::const_iterator archive = archives.begin(); archive != archives.end(); ++archive) { if (collections.doesExist(*archive)) { // Last BSA has the highest priority const std::string archivePath = collections.getPath(*archive).string(); Log(Debug::Info) << "Adding BSA archive " << archivePath; Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(archivePath); if (bsaVersion == Bsa::BSAVER_COMPRESSED) vfs->addArchive(std::make_unique(archivePath)); else vfs->addArchive(std::make_unique(archivePath)); } else { throw std::runtime_error("Archive '" + *archive + "' not found"); } } if (useLooseFiles) { std::set seen; for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { if (seen.insert(*iter).second) { Log(Debug::Info) << "Adding data directory " << iter->string(); // Last data dir has the highest priority vfs->addArchive(std::make_unique(iter->string())); } else Log(Debug::Info) << "Ignoring duplicate data directory " << iter->string(); } } vfs->buildIndex(); } } openmw-openmw-0.48.0/components/vfs/registerarchives.hpp000066400000000000000000000006721445372753700235060ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_VFS_REGISTER_ARCHIVES_H #define OPENMW_COMPONENTS_VFS_REGISTER_ARCHIVES_H #include namespace VFS { class Manager; /// @brief Register BSA and file system archives based on the given OpenMW configuration. void registerArchives (VFS::Manager* vfs, const Files::Collections& collections, const std::vector& archives, bool useLooseFiles); } #endif openmw-openmw-0.48.0/components/widgets/000077500000000000000000000000001445372753700202675ustar00rootroot00000000000000openmw-openmw-0.48.0/components/widgets/box.cpp000066400000000000000000000367421445372753700215770ustar00rootroot00000000000000#include "box.hpp" #include #include namespace Gui { // TODO: Since 3.4.2 MyGUI is supposed to automatically translate tags // If the 3.4.2 become a required minimum version, the ComboBox class may be removed. void ComboBox::setPropertyOverride(const std::string& _key, const std::string& _value) { #if MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3,4,2) MyGUI::ComboBox::setPropertyOverride (_key, _value); #else if (_key == "AddItem") { const std::string value = MyGUI::LanguageManager::getInstance().replaceTags(_value); MyGUI::ComboBox::setPropertyOverride (_key, value); } else { MyGUI::ComboBox::setPropertyOverride (_key, _value); } #endif } void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w) { MyGUI::Widget * parent = w->getParent(); if (parent != nullptr) { if (mExpandDirection.isLeft()) { int hdiff = getRequestedSize ().width - w->getSize().width; w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0)); } w->setSize(getRequestedSize ()); while (parent != nullptr) { Box * b = dynamic_cast(parent); if (b) b->notifyChildrenSizeChanged(); else break; parent = parent->getParent(); } } } MyGUI::IntSize AutoSizedTextBox::getRequestedSize() { return getCaption().empty() ? MyGUI::IntSize{0, 0} : getTextSize(); } void AutoSizedTextBox::setCaption(const MyGUI::UString& _value) { TextBox::setCaption(_value); notifySizeChange (this); } void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value) { if (_key == "ExpandDirection") { mExpandDirection = MyGUI::Align::parse (_value); } else { Gui::TextBox::setPropertyOverride (_key, _value); } } int AutoSizedEditBox::getWidth() { // If the widget has the one short text line, we can shrink widget to avoid a lot of empty space. int textWidth = mMaxWidth; if (mShrink) { // MyGUI needs to know the widget size for wordwrapping, but we will know the widget size only after wordwrapping. // To solve this issue, use the maximum tooltip width first for wordwrapping, then resize widget to its actual used space. if (mWasResized) { int maxLineWidth = 0; const MyGUI::VectorLineInfo & lines = getSubWidgetText()->castType()->getLineInfo(); for (unsigned int i = 0; i < lines.size(); ++i) maxLineWidth = std::max(maxLineWidth, lines[i].width); textWidth = std::min(maxLineWidth, textWidth); } else { mWasResized = true; } } return textWidth; } MyGUI::IntSize AutoSizedEditBox::getRequestedSize() { if (getAlign().isHStretch()) throw std::runtime_error("AutoSizedEditBox can't have HStretch align (" + getName() + ")"); return MyGUI::IntSize(getWidth(), getTextSize().height); } void AutoSizedEditBox::setCaption(const MyGUI::UString& _value) { EditBox::setCaption(_value); mWasResized = false; notifySizeChange (this); } void AutoSizedEditBox::initialiseOverride() { mMaxWidth = getSize().width; Base::initialiseOverride(); setNeedKeyFocus(false); setEditStatic(true); } void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value) { if (_key == "ExpandDirection") { mExpandDirection = MyGUI::Align::parse (_value); } else if (_key == "Shrink") { mShrink = MyGUI::utility::parseValue(_value); } else { Gui::EditBox::setPropertyOverride (_key, _value); } } MyGUI::IntSize AutoSizedButton::getRequestedSize() { MyGUI::IntSize padding(24, 8); if (isUserString("TextPadding")) padding = MyGUI::IntSize::parse(getUserString("TextPadding")); MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width,padding.height); return size; } void AutoSizedButton::setCaption(const MyGUI::UString& _value) { Button::setCaption(_value); notifySizeChange (this); } void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value) { if (_key == "ExpandDirection") { mExpandDirection = MyGUI::Align::parse (_value); } else { Gui::Button::setPropertyOverride (_key, _value); } } Box::Box() : mSpacing(4) , mPadding(0) , mAutoResize(false) { } void Box::notifyChildrenSizeChanged () { align(); } bool Box::_setPropertyImpl(const std::string& _key, const std::string& _value) { if (_key == "Spacing") mSpacing = MyGUI::utility::parseValue(_value); else if (_key == "Padding") mPadding = MyGUI::utility::parseValue(_value); else if (_key == "AutoResize") mAutoResize = MyGUI::utility::parseValue(_value); else return false; return true; } void HBox::align () { unsigned int count = getChildCount (); size_t h_stretched_count = 0; int total_width = 0; int total_height = 0; std::vector< std::pair > sizes; sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); bool hstretch = w->getUserString ("HStretch") == "true"; bool hidden = w->getUserString("Hidden") == "true"; if (hidden) continue; h_stretched_count += hstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch); total_width += aw->getRequestedSize ().width; total_height = std::max(total_height, aw->getRequestedSize ().height); } else { sizes[i] = std::make_pair(w->getSize(), hstretch); total_width += w->getSize().width; if (!(w->getUserString("VStretch") == "true")) total_height = std::max(total_height, w->getSize().height); } if (i != count-1) total_width += mSpacing; } if (mAutoResize && (total_width+mPadding*2 != getClientCoord().width || total_height+mPadding*2 != getClientCoord().height)) { int xmargin = getSize().width - getClientCoord().width; int ymargin = getSize().height - getClientCoord().height; setSize(MyGUI::IntSize(total_width+mPadding*2 + xmargin, total_height+mPadding*2 + ymargin)); return; } int curX = 0; for (unsigned int i = 0; i < count; ++i) { if (i == 0) curX += mPadding; MyGUI::Widget* w = getChildAt(i); bool hidden = w->getUserString("Hidden") == "true"; if (hidden) continue; bool vstretch = w->getUserString ("VStretch") == "true"; int max_height = getClientCoord().height - mPadding*2; int height = vstretch ? max_height : sizes[i].first.height; MyGUI::IntCoord widgetCoord; widgetCoord.left = curX; widgetCoord.top = mPadding + (getClientCoord().height-mPadding*2 - height) / 2; int width = 0; if (sizes[i].second) { if (h_stretched_count == 0) throw std::logic_error("unexpected"); width = sizes[i].first.width + (getClientCoord().width-mPadding*2 - total_width)/h_stretched_count; } else width = sizes[i].first.width; widgetCoord.width = width; widgetCoord.height = height; w->setCoord(widgetCoord); curX += width; if (i != count-1) curX += mSpacing; } } void HBox::setPropertyOverride(const std::string& _key, const std::string& _value) { if (!Box::_setPropertyImpl (_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); } void HBox::setSize (const MyGUI::IntSize& _value) { MyGUI::Widget::setSize (_value); align(); } void HBox::setCoord (const MyGUI::IntCoord& _value) { MyGUI::Widget::setCoord (_value); align(); } void HBox::initialiseOverride() { Base::initialiseOverride(); MyGUI::Widget* client = nullptr; assignWidget(client, "Client"); setWidgetClient(client); } void HBox::onWidgetCreated(MyGUI::Widget* _widget) { align(); } MyGUI::IntSize HBox::getRequestedSize () { MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; if (hidden) continue; AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { MyGUI::IntSize requested = w->getRequestedSize (); size.height = std::max(size.height, requested.height); size.width = size.width + requested.width; if (i != getChildCount()-1) size.width += mSpacing; } else { MyGUI::IntSize requested = getChildAt(i)->getSize (); size.height = std::max(size.height, requested.height); if (getChildAt(i)->getUserString("HStretch") != "true") size.width = size.width + requested.width; if (i != getChildCount()-1) size.width += mSpacing; } size.height += mPadding*2; size.width += mPadding*2; } return size; } void VBox::align () { unsigned int count = getChildCount (); size_t v_stretched_count = 0; int total_height = 0; int total_width = 0; std::vector< std::pair > sizes; sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); bool hidden = w->getUserString("Hidden") == "true"; if (hidden) continue; bool vstretch = w->getUserString ("VStretch") == "true"; v_stretched_count += vstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch); total_height += aw->getRequestedSize ().height; total_width = std::max(total_width, aw->getRequestedSize ().width); } else { sizes[i] = std::make_pair(w->getSize(), vstretch); total_height += w->getSize().height; if (!(w->getUserString("HStretch") == "true")) total_width = std::max(total_width, w->getSize().width); } if (i != count-1) total_height += mSpacing; } if (mAutoResize && (total_width+mPadding*2 != getClientCoord().width || total_height+mPadding*2 != getClientCoord().height)) { int xmargin = getSize().width - getClientCoord().width; int ymargin = getSize().height - getClientCoord().height; setSize(MyGUI::IntSize(total_width+mPadding*2 + xmargin, total_height+mPadding*2 + ymargin)); return; } int curY = 0; for (unsigned int i = 0; i < count; ++i) { if (i==0) curY += mPadding; MyGUI::Widget* w = getChildAt(i); bool hidden = w->getUserString("Hidden") == "true"; if (hidden) continue; bool hstretch = w->getUserString ("HStretch") == "true"; int maxWidth = getClientCoord().width - mPadding*2; int width = hstretch ? maxWidth : sizes[i].first.width; MyGUI::IntCoord widgetCoord; widgetCoord.top = curY; widgetCoord.left = mPadding + (getClientCoord().width-mPadding*2 - width) / 2; int height = 0; if (sizes[i].second) { if (v_stretched_count == 0) throw std::logic_error("unexpected"); height = sizes[i].first.height + (getClientCoord().height-mPadding*2 - total_height)/v_stretched_count; } else height = sizes[i].first.height; widgetCoord.height = height; widgetCoord.width = width; w->setCoord(widgetCoord); curY += height; if (i != count-1) curY += mSpacing; } } void VBox::setPropertyOverride(const std::string& _key, const std::string& _value) { if (!Box::_setPropertyImpl (_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); } void VBox::setSize (const MyGUI::IntSize& _value) { MyGUI::Widget::setSize (_value); align(); } void VBox::setCoord (const MyGUI::IntCoord& _value) { MyGUI::Widget::setCoord (_value); align(); } void VBox::initialiseOverride() { Base::initialiseOverride(); MyGUI::Widget* client = nullptr; assignWidget(client, "Client"); setWidgetClient(client); } MyGUI::IntSize VBox::getRequestedSize () { MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; if (hidden) continue; AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { MyGUI::IntSize requested = w->getRequestedSize (); size.width = std::max(size.width, requested.width); size.height = size.height + requested.height; if (i != getChildCount()-1) size.height += mSpacing; } else { MyGUI::IntSize requested = getChildAt(i)->getSize (); size.width = std::max(size.width, requested.width); if (getChildAt(i)->getUserString("VStretch") != "true") size.height = size.height + requested.height; if (i != getChildCount()-1) size.height += mSpacing; } size.height += mPadding*2; size.width += mPadding*2; } return size; } void VBox::onWidgetCreated(MyGUI::Widget* _widget) { align(); } Spacer::Spacer() { setUserString("HStretch", "true"); setUserString("VStretch", "true"); } } openmw-openmw-0.48.0/components/widgets/box.hpp000066400000000000000000000105441445372753700215740ustar00rootroot00000000000000#ifndef OPENMW_WIDGETS_BOX_H #define OPENMW_WIDGETS_BOX_H #include #include #include #include #include #include "fontwrapper.hpp" namespace Gui { class ComboBox : public FontWrapper { MYGUI_RTTI_DERIVED( ComboBox ) protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override; }; class Button : public FontWrapper { MYGUI_RTTI_DERIVED( Button ) }; class TextBox : public FontWrapper { MYGUI_RTTI_DERIVED( TextBox ) }; class EditBox : public FontWrapper { MYGUI_RTTI_DERIVED( EditBox ) }; class AutoSizedWidget { public: AutoSizedWidget() : mExpandDirection(MyGUI::Align::Right) {} virtual MyGUI::IntSize getRequestedSize() = 0; virtual ~AutoSizedWidget() = default; protected: void notifySizeChange(MyGUI::Widget* w); MyGUI::Align mExpandDirection; }; class AutoSizedTextBox : public AutoSizedWidget, public TextBox { MYGUI_RTTI_DERIVED( AutoSizedTextBox ) public: MyGUI::IntSize getRequestedSize() override; void setCaption(const MyGUI::UString& _value) override; protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override; std::string mFontSize; }; class AutoSizedEditBox : public AutoSizedWidget, public EditBox { MYGUI_RTTI_DERIVED( AutoSizedEditBox ) public: MyGUI::IntSize getRequestedSize() override; void setCaption(const MyGUI::UString& _value) override; void initialiseOverride() override; protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override; int getWidth(); std::string mFontSize; bool mShrink = false; bool mWasResized = false; int mMaxWidth = 0; }; class AutoSizedButton : public AutoSizedWidget, public Button { MYGUI_RTTI_DERIVED( AutoSizedButton ) public: MyGUI::IntSize getRequestedSize() override; void setCaption(const MyGUI::UString& _value) override; protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override; std::string mFontSize; }; /** * @brief A container widget that automatically sizes its children * @note the box being an AutoSizedWidget as well allows to put boxes inside a box */ class Box : public AutoSizedWidget { public: Box(); virtual ~Box() = default; void notifyChildrenSizeChanged(); protected: virtual void align() = 0; virtual bool _setPropertyImpl(const std::string& _key, const std::string& _value); int mSpacing; // how much space to put between elements int mPadding; // outer padding bool mAutoResize; // auto resize the box so that it exactly fits all elements }; class Spacer : public AutoSizedWidget, public MyGUI::Widget { MYGUI_RTTI_DERIVED( Spacer ) public: Spacer(); MyGUI::IntSize getRequestedSize() override { return MyGUI::IntSize(0,0); } }; class HBox : public Box, public MyGUI::Widget { MYGUI_RTTI_DERIVED( HBox ) public: void setSize (const MyGUI::IntSize &_value) override; void setCoord (const MyGUI::IntCoord &_value) override; protected: void initialiseOverride() override; void align() override; MyGUI::IntSize getRequestedSize() override; void setPropertyOverride(const std::string& _key, const std::string& _value) override; void onWidgetCreated(MyGUI::Widget* _widget) override; }; class VBox : public Box, public MyGUI::Widget { MYGUI_RTTI_DERIVED( VBox) public: void setSize (const MyGUI::IntSize &_value) override; void setCoord (const MyGUI::IntCoord &_value) override; protected: void initialiseOverride() override; void align() override; MyGUI::IntSize getRequestedSize() override; void setPropertyOverride(const std::string& _key, const std::string& _value) override; void onWidgetCreated(MyGUI::Widget* _widget) override; }; } #endif openmw-openmw-0.48.0/components/widgets/fontwrapper.hpp000066400000000000000000000022671445372753700233560ustar00rootroot00000000000000#ifndef OPENMW_WIDGETS_WRAPPER_H #define OPENMW_WIDGETS_WRAPPER_H #include "widgets.hpp" #include namespace Gui { template class FontWrapper : public T { public: void setFontName(const std::string& name) override { T::setFontName(name); T::setPropertyOverride ("FontHeight", getFontSize()); } protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override { T::setPropertyOverride (_key, _value); // There is a bug in MyGUI: when it initializes the FontName property, it reset the font height. // We should restore it. if (_key == "FontName") { T::setPropertyOverride ("FontHeight", getFontSize()); } } private: std::string getFontSize() { // Note: we can not use the FontLoader here, so there is a code duplication a bit. static const std::string fontSize = std::to_string(std::clamp(Settings::Manager::getInt("font size", "GUI"), 12, 18)); return fontSize; } }; } #endif openmw-openmw-0.48.0/components/widgets/imagebutton.cpp000066400000000000000000000110541445372753700233120ustar00rootroot00000000000000#include "imagebutton.hpp" #include #include #include namespace Gui { bool ImageButton::sDefaultNeedKeyFocus = true; ImageButton::ImageButton() : Base() , mMouseFocus(false) , mMousePress(false) , mKeyFocus(false) , mUseWholeTexture(true) , mTextureRect(MyGUI::IntCoord(0, 0, 0, 0)) { setNeedKeyFocus(sDefaultNeedKeyFocus); } void ImageButton::setDefaultNeedKeyFocus(bool enabled) { sDefaultNeedKeyFocus = enabled; } void ImageButton::setTextureRect(MyGUI::IntCoord coord) { mTextureRect = coord; mUseWholeTexture = (coord == MyGUI::IntCoord(0, 0, 0, 0)); updateImage(); } void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value) { if (_key == "ImageHighlighted") mImageHighlighted = _value; else if (_key == "ImagePushed") mImagePushed = _value; else if (_key == "ImageNormal") { if (mImageNormal == "") { setImageTexture(_value); } mImageNormal = _value; } else if (_key == "TextureRect") { mTextureRect = MyGUI::IntCoord::parse(_value); mUseWholeTexture = (mTextureRect == MyGUI::IntCoord(0, 0, 0, 0)); } else ImageBox::setPropertyOverride(_key, _value); } void ImageButton::onMouseSetFocus(Widget* _old) { mMouseFocus = true; updateImage(); Base::onMouseSetFocus(_old); } void ImageButton::onMouseLostFocus(Widget* _new) { mMouseFocus = false; updateImage(); Base::onMouseLostFocus(_new); } void ImageButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) { mMousePress = true; updateImage(); } Base::onMouseButtonPressed(_left, _top, _id); } void ImageButton::updateImage() { std::string textureName = mImageNormal; if (mMousePress) textureName = mImagePushed; else if (mMouseFocus || mKeyFocus) textureName = mImageHighlighted; if (!mUseWholeTexture) { float scale = 1.f; MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(textureName); if (texture && getHeight() != 0) scale = static_cast(texture->getHeight()) / getHeight(); const int width = static_cast(std::round(mTextureRect.width * scale)); const int height = static_cast(std::round(mTextureRect.height * scale)); setImageTile(MyGUI::IntSize(width, height)); MyGUI::IntCoord scaledSize(static_cast(std::round(mTextureRect.left * scale)), static_cast(std::round(mTextureRect.top * scale)), width, height); setImageCoord(scaledSize); } setImageTexture(textureName); } MyGUI::IntSize ImageButton::getRequestedSize() { MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(mImageNormal); if (!texture) { Log(Debug::Error) << "ImageButton: can't find image " << mImageNormal; return MyGUI::IntSize(0,0); } if (mUseWholeTexture) return MyGUI::IntSize(texture->getWidth(), texture->getHeight()); return MyGUI::IntSize(mTextureRect.width, mTextureRect.height); } void ImageButton::setImage(const std::string &image) { size_t extpos = image.find_last_of('.'); std::string imageNoExt = image.substr(0, extpos); std::string ext = image.substr(extpos); mImageNormal = imageNoExt + "_idle" + ext; mImageHighlighted = imageNoExt + "_over" + ext; mImagePushed = imageNoExt + "_pressed" + ext; updateImage(); } void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) { mMousePress = false; updateImage(); } Base::onMouseButtonReleased(_left, _top, _id); } void ImageButton::onKeySetFocus(MyGUI::Widget *_old) { mKeyFocus = true; updateImage(); } void ImageButton::onKeyLostFocus(MyGUI::Widget *_new) { mKeyFocus = false; updateImage(); } } openmw-openmw-0.48.0/components/widgets/imagebutton.hpp000066400000000000000000000031151445372753700233160ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WIDGETS_IMAGEBUTTON_H #define OPENMW_COMPONENTS_WIDGETS_IMAGEBUTTON_H #include namespace Gui { /** * @brief allows using different image textures depending on the button state */ class ImageButton final : public MyGUI::ImageBox { MYGUI_RTTI_DERIVED(ImageButton) public: MyGUI::IntSize getRequestedSize(); ImageButton(); static void setDefaultNeedKeyFocus(bool enabled); /// Set mImageNormal, mImageHighlighted and mImagePushed based on file convention (image_idle.ext, image_over.ext and image_pressed.ext) void setImage(const std::string& image); void setTextureRect(MyGUI::IntCoord coord); private: void updateImage(); static bool sDefaultNeedKeyFocus; protected: void setPropertyOverride(const std::string& _key, const std::string& _value) override; void onMouseLostFocus(MyGUI::Widget* _new) override; void onMouseSetFocus(MyGUI::Widget* _old) override; void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) override; void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) override; void onKeySetFocus(MyGUI::Widget* _old) override; void onKeyLostFocus(MyGUI::Widget* _new) override; std::string mImageHighlighted; std::string mImageNormal; std::string mImagePushed; bool mMouseFocus; bool mMousePress; bool mKeyFocus; bool mUseWholeTexture; MyGUI::IntCoord mTextureRect; }; } #endif openmw-openmw-0.48.0/components/widgets/list.cpp000066400000000000000000000124361445372753700217540ustar00rootroot00000000000000#include "list.hpp" #include #include #include namespace Gui { MWList::MWList() : mScrollView(nullptr) , mClient(nullptr) , mItemHeight(0) { } void MWList::initialiseOverride() { Base::initialiseOverride(); assignWidget(mClient, "Client"); if (mClient == nullptr) mClient = this; mScrollView = mClient->createWidgetReal( "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0), MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); } void MWList::addItem(const std::string& name) { mItems.push_back(name); } void MWList::addSeparator() { mItems.emplace_back(""); } void MWList::adjustSize() { redraw(); } void MWList::redraw(bool scrollbarShown) { const int _scrollBarWidth = 20; // fetch this from skin? const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; const int spacing = 3; int viewPosition = -mScrollView->getViewOffset().top; while (mScrollView->getChildCount()) { MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); } mItemHeight = 0; int i=0; for (std::vector::const_iterator it=mItems.begin(); it!=mItems.end(); ++it) { if (*it != "") { if (mListItemSkin.empty()) return; MyGUI::Button* button = mScrollView->createWidget( mListItemSkin, MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24), MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + (*it)); button->setCaption((*it)); button->getSubWidgetText()->setWordWrap(true); button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheelMoved); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); button->setNeedKeyFocus(true); int height = button->getTextSize().height; button->setSize(MyGUI::IntSize(button->getSize().width, height)); button->setUserData(i); mItemHeight += height + spacing; } else { MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth() - scrollBarWidth - 4, 18), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); separator->setNeedMouseFocus(false); mItemHeight += 18 + spacing; } ++i; } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); mScrollView->setVisibleVScroll(true); if (!scrollbarShown && mItemHeight > mClient->getSize().height) redraw(true); int viewRange = mScrollView->getCanvasSize().height; if(viewPosition > viewRange) viewPosition = viewRange; mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); } void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) { if (_key == "ListItemSkin") mListItemSkin = _value; else Base::setPropertyOverride(_key, _value); } unsigned int MWList::getItemCount() { return static_cast(mItems.size()); } std::string MWList::getItemNameAt(unsigned int at) { assert(at < mItems.size() && "List item out of bounds"); return mItems[at]; } void MWList::removeItem(const std::string& name) { assert( std::find(mItems.begin(), mItems.end(), name) != mItems.end() ); mItems.erase( std::find(mItems.begin(), mItems.end(), name) ); } void MWList::clear() { mItems.clear(); } void MWList::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) { //NB view offset is negative if (mScrollView->getViewOffset().top + _rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + _rel*0.3))); } void MWList::onItemSelected(MyGUI::Widget* _sender) { std::string name = _sender->castType()->getCaption(); int id = *_sender->getUserData(); eventItemSelected(name, id); eventWidgetSelected(_sender); } MyGUI::Button *MWList::getItemWidget(const std::string& name) { return mScrollView->findWidget (getName() + "_item_" + name)->castType(); } void MWList::scrollToTop() { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); } } openmw-openmw-0.48.0/components/widgets/list.hpp000066400000000000000000000042721445372753700217600ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WIDGETS_LIST_HPP #define OPENMW_COMPONENTS_WIDGETS_LIST_HPP #include namespace Gui { /** * \brief a very simple list widget that supports word-wrapping entries * \note if the width or height of the list changes, you must call adjustSize() method */ class MWList : public MyGUI::Widget { MYGUI_RTTI_DERIVED(MWList) public: MWList(); typedef MyGUI::delegates::CMultiDelegate2 EventHandle_StringInt; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Widget; /** * Event: Item selected with the mouse. * signature: void method(std::string itemName, int index) */ EventHandle_StringInt eventItemSelected; /** * Event: Item selected with the mouse. * signature: void method(MyGUI::Widget* sender) */ EventHandle_Widget eventWidgetSelected; /** * Call after the size of the list changed, or items were inserted/removed */ void adjustSize(); void addItem(const std::string& name); void addSeparator(); ///< add a seperator between the current and the next item. void removeItem(const std::string& name); unsigned int getItemCount(); std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is void clear(); MyGUI::Button* getItemWidget(const std::string& name); ///< get widget for an item name, useful to set up tooltip void scrollToTop(); void setPropertyOverride(const std::string& _key, const std::string& _value) override; protected: void initialiseOverride() override; void redraw(bool scrollbarShown = false); void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); void onItemSelected(MyGUI::Widget* _sender); private: MyGUI::ScrollView* mScrollView; MyGUI::Widget* mClient; std::string mListItemSkin; std::vector mItems; int mItemHeight; // height of all items }; } #endif openmw-openmw-0.48.0/components/widgets/numericeditbox.cpp000066400000000000000000000046041445372753700240200ustar00rootroot00000000000000#include #include "numericeditbox.hpp" namespace Gui { void NumericEditBox::initialiseOverride() { Base::initialiseOverride(); eventEditTextChange += MyGUI::newDelegate(this, &NumericEditBox::onEditTextChange); mValue = 0; setCaption("0"); } void NumericEditBox::shutdownOverride() { Base::shutdownOverride(); eventEditTextChange -= MyGUI::newDelegate(this, &NumericEditBox::onEditTextChange); } void NumericEditBox::onEditTextChange(MyGUI::EditBox *sender) { std::string newCaption = sender->getCaption(); if (newCaption.empty()) { return; } try { mValue = std::stoi(newCaption); int capped = std::clamp(mValue, mMinValue, mMaxValue); if (capped != mValue) { mValue = capped; setCaption(MyGUI::utility::toString(mValue)); } } catch (const std::invalid_argument&) { setCaption(MyGUI::utility::toString(mValue)); } catch (const std::out_of_range&) { setCaption(MyGUI::utility::toString(mValue)); } eventValueChanged(mValue); } void NumericEditBox::setValue(int value) { if (value != mValue) { setCaption(MyGUI::utility::toString(value)); mValue = value; } } int NumericEditBox::getValue() { return mValue; } void NumericEditBox::setMinValue(int minValue) { mMinValue = minValue; } void NumericEditBox::setMaxValue(int maxValue) { mMaxValue = maxValue; } void NumericEditBox::onKeyLostFocus(MyGUI::Widget* _new) { Base::onKeyLostFocus(_new); setCaption(MyGUI::utility::toString(mValue)); } void NumericEditBox::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) { setValue(std::min(mValue+1, mMaxValue)); eventValueChanged(mValue); } else if (key == MyGUI::KeyCode::ArrowDown) { setValue(std::max(mValue-1, mMinValue)); eventValueChanged(mValue); } else if (character == 0 || (character >= '0' && character <= '9')) Base::onKeyButtonPressed(key, character); } } openmw-openmw-0.48.0/components/widgets/numericeditbox.hpp000066400000000000000000000023311445372753700240200ustar00rootroot00000000000000#ifndef OPENMW_NUMERIC_EDIT_BOX_H #define OPENMW_NUMERIC_EDIT_BOX_H #include #include "fontwrapper.hpp" namespace Gui { /** * @brief A variant of the EditBox that only allows integer inputs */ class NumericEditBox final : public FontWrapper { MYGUI_RTTI_DERIVED(NumericEditBox) public: NumericEditBox() : mValue(0), mMinValue(std::numeric_limits::min()), mMaxValue(std::numeric_limits::max()) { } void initialiseOverride() override; void shutdownOverride() override; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ValueChanged; EventHandle_ValueChanged eventValueChanged; /// @note Does not trigger eventValueChanged void setValue (int value); int getValue(); void setMinValue(int minValue); void setMaxValue(int maxValue); private: void onEditTextChange(MyGUI::EditBox* sender); void onKeyLostFocus(MyGUI::Widget* _new) override; void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char character) override; int mValue; int mMinValue; int mMaxValue; }; } #endif openmw-openmw-0.48.0/components/widgets/sharedstatebutton.cpp000066400000000000000000000065011445372753700245400ustar00rootroot00000000000000#include "sharedstatebutton.hpp" namespace Gui { SharedStateButton::SharedStateButton() : mIsMousePressed(false) , mIsMouseFocus(false) { } void SharedStateButton::shutdownOverride() { ButtonGroup group = mSharedWith; // make a copy so that we don't nuke the vector during iteration for (ButtonGroup::iterator it = group.begin(); it != group.end(); ++it) { (*it)->shareStateWith(ButtonGroup()); } } void SharedStateButton::shareStateWith(const ButtonGroup &shared) { mSharedWith = shared; } void SharedStateButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) { mIsMousePressed = true; Base::onMouseButtonPressed(_left, _top, _id); updateButtonState(); } void SharedStateButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) { mIsMousePressed = false; Base::onMouseButtonReleased(_left, _top, _id); updateButtonState(); } void SharedStateButton::onMouseSetFocus(MyGUI::Widget *_old) { mIsMouseFocus = true; Base::onMouseSetFocus(_old); updateButtonState(); } void SharedStateButton::onMouseLostFocus(MyGUI::Widget *_new) { mIsMouseFocus = false; Base::onMouseLostFocus(_new); updateButtonState(); } void SharedStateButton::baseUpdateEnable() { Base::baseUpdateEnable(); updateButtonState(); } void SharedStateButton::setStateSelected(bool _value) { Base::setStateSelected(_value); updateButtonState(); for (ButtonGroup::iterator it = mSharedWith.begin(); it != mSharedWith.end(); ++it) { (*it)->MyGUI::Button::setStateSelected(getStateSelected()); } } bool SharedStateButton::_setState(const std::string &_value) { bool ret = _setWidgetState(_value); if (ret) { for (ButtonGroup::iterator it = mSharedWith.begin(); it != mSharedWith.end(); ++it) { (*it)->_setWidgetState(_value); } } return ret; } void SharedStateButton::updateButtonState() { if (getStateSelected()) { if (!getInheritedEnabled()) { if (!_setState("disabled_checked")) _setState("disabled"); } else if (mIsMousePressed) { if (!_setState("pushed_checked")) _setState("pushed"); } else if (mIsMouseFocus) { if (!_setState("highlighted_checked")) _setState("pushed"); } else _setState("normal_checked"); } else { if (!getInheritedEnabled()) _setState("disabled"); else if (mIsMousePressed) _setState("pushed"); else if (mIsMouseFocus) _setState("highlighted"); else _setState("normal"); } } void SharedStateButton::createButtonGroup(ButtonGroup group) { for (ButtonGroup::iterator it = group.begin(); it != group.end(); ++it) { (*it)->shareStateWith(group); } } } openmw-openmw-0.48.0/components/widgets/sharedstatebutton.hpp000066400000000000000000000026751445372753700245550ustar00rootroot00000000000000#ifndef OPENMW_WIDGETS_SHAREDSTATEBUTTON_HPP #define OPENMW_WIDGETS_SHAREDSTATEBUTTON_HPP #include #include "fontwrapper.hpp" namespace Gui { class SharedStateButton; typedef std::vector ButtonGroup; /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a ButtonGroup. class SharedStateButton final : public FontWrapper { MYGUI_RTTI_DERIVED(SharedStateButton) public: SharedStateButton(); protected: void updateButtonState(); void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) override; void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) override; void onMouseSetFocus(MyGUI::Widget* _old) override; void onMouseLostFocus(MyGUI::Widget* _new) override; void baseUpdateEnable() override; void shutdownOverride() override; bool _setState(const std::string &_value); public: void shareStateWith(const ButtonGroup &shared); /// @note The ButtonGroup connection will be destroyed when any widget in the group gets destroyed. static void createButtonGroup(ButtonGroup group); //! Set button selected state void setStateSelected(bool _value); private: ButtonGroup mSharedWith; bool mIsMousePressed; bool mIsMouseFocus; }; } #endif openmw-openmw-0.48.0/components/widgets/tags.cpp000066400000000000000000000035711445372753700217370ustar00rootroot00000000000000#include "tags.hpp" #include #include namespace Gui { bool replaceTag(const MyGUI::UString& tag, MyGUI::UString& out) { std::string fontcolour = "fontcolour="; size_t fontcolourLength = fontcolour.length(); std::string fontcolourhtml = "fontcolourhtml="; size_t fontcolourhtmlLength = fontcolourhtml.length(); if (tag.compare(0, fontcolourLength, fontcolour) == 0) { std::string fallbackName = "FontColor_color_" + tag.substr(fontcolourLength); std::string str = Fallback::Map::getString(fallbackName); if (str.empty()) throw std::runtime_error("Unknown fallback name: " + fallbackName); std::string ret[3]; unsigned int j=0; for(unsigned int i=0;i #include #include namespace Gui { /// Try to replace a tag. Returns true on success and writes the result to \a out. bool replaceTag (const MyGUI::UString& tag, MyGUI::UString& out); } #endif openmw-openmw-0.48.0/components/widgets/widgets.cpp000066400000000000000000000031171445372753700224430ustar00rootroot00000000000000#include "widgets.hpp" #include #include "list.hpp" #include "numericeditbox.hpp" #include "box.hpp" #include "imagebutton.hpp" #include "sharedstatebutton.hpp" #include "windowcaption.hpp" namespace Gui { void registerAllWidgets() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } } openmw-openmw-0.48.0/components/widgets/widgets.hpp000066400000000000000000000003551445372753700224510ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WIDGETS_H #define OPENMW_COMPONENTS_WIDGETS_H extern int GuiFontHeight; namespace Gui { /// Register all widgets from this component with MyGUI's factory manager. void registerAllWidgets(); } #endif openmw-openmw-0.48.0/components/widgets/windowcaption.cpp000066400000000000000000000026701445372753700236650ustar00rootroot00000000000000#include "windowcaption.hpp" #include namespace Gui { WindowCaption::WindowCaption() : mLeft(nullptr) , mRight(nullptr) , mClient(nullptr) { } void WindowCaption::initialiseOverride() { Base::initialiseOverride(); assignWidget(mLeft, "Left"); assignWidget(mRight, "Right"); assignWidget(mClient, "Client"); if (!mClient) throw std::runtime_error("WindowCaption needs an EditBox Client widget in its skin"); } void WindowCaption::setCaption(const MyGUI::UString &_value) { EditBox::setCaption(_value); align(); } void WindowCaption::setSize(const MyGUI::IntSize& _value) { Base::setSize(_value); align(); } void WindowCaption::setCoord(const MyGUI::IntCoord& _value) { Base::setCoord(_value); align(); } void WindowCaption::align() { MyGUI::IntSize textSize = getTextSize(); MyGUI::Widget* caption = mClient; caption->setSize(textSize.width + 24, caption->getHeight()); int barwidth = (getWidth()-caption->getWidth())/2; caption->setPosition(barwidth, caption->getTop()); if (mLeft) mLeft->setCoord(0, mLeft->getTop(), barwidth, mLeft->getHeight()); if (mRight) mRight->setCoord(barwidth + caption->getWidth(), mRight->getTop(), barwidth, mRight->getHeight()); } } openmw-openmw-0.48.0/components/widgets/windowcaption.hpp000066400000000000000000000014321445372753700236650ustar00rootroot00000000000000#ifndef OPENMW_WIDGETS_WINDOWCAPTION_H #define OPENMW_WIDGETS_WINDOWCAPTION_H #include namespace Gui { /// Window caption that automatically adjusts "Left" and "Right" widgets in its skin /// based on the text size of the caption in the middle class WindowCaption final : public MyGUI::EditBox { MYGUI_RTTI_DERIVED(WindowCaption) public: WindowCaption(); void setCaption(const MyGUI::UString &_value) override; void initialiseOverride() override; void setSize(const MyGUI::IntSize& _value) override; void setCoord(const MyGUI::IntCoord& _value) override; private: MyGUI::Widget* mLeft; MyGUI::Widget* mRight; MyGUI::Widget* mClient; void align(); }; } #endif openmw-openmw-0.48.0/components/windows.hpp000066400000000000000000000001761445372753700210300ustar00rootroot00000000000000#ifndef OPENMW_COMPONENTS_WINDOWS_H #define OPENMW_COMPONENTS_WINDOWS_H #include #undef far #undef near #endif openmw-openmw-0.48.0/docker/000077500000000000000000000000001445372753700157035ustar00rootroot00000000000000openmw-openmw-0.48.0/docker/Dockerfile.ubuntu000066400000000000000000000015371445372753700212240ustar00rootroot00000000000000FROM ubuntu LABEL maintainer="Wassim DHIF " ENV NPROC=1 RUN apt-get update \ && apt-get install -y --no-install-recommends software-properties-common apt-utils \ && add-apt-repository ppa:openmw/openmw \ && apt-get update \ && apt-get install -y --no-install-recommends openmw openmw-launcher \ && apt-get install -y --no-install-recommends git build-essential cmake \ libopenal-dev libopenscenegraph-dev libbullet-dev libsdl2-dev \ libmygui-dev libunshield-dev liblz4-dev libtinyxml-dev libqt5opengl5-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-iostreams-dev \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev \ librecast-dev libsqlite3-dev libluajit-5.1-dev COPY build.sh /build.sh RUN mkdir /openmw WORKDIR /openmw ENTRYPOINT ["/build.sh"] openmw-openmw-0.48.0/docker/README.md000066400000000000000000000011531445372753700171620ustar00rootroot00000000000000# Build OpenMW using Docker ## Build Docker image Replace `LINUX_VERSION` with the Linux distribution you wish to use. ``` docker build -f Dockerfile.LINUX_VERSION -t openmw.LINUX_VERSION . ``` ## Build OpenMW using Docker Labeling systems like SELinux require that proper labels are placed on volume content mounted into a container. Without a label, the security system might prevent the processes running inside the container from using the content. The Z option tells Docker to label the content with a private unshared label. ``` docker run -v /path/to/openmw:/openmw:Z -e NPROC=2 -it openmw.LINUX_VERSION ``` openmw-openmw-0.48.0/docker/build.sh000077500000000000000000000002401445372753700173350ustar00rootroot00000000000000#!/usr/bin/env bash set -xe # Creating build directory... mkdir -p build cd build # Running CMake... cmake ../ # Building with $NPROC CPU... make -j $NPROC openmw-openmw-0.48.0/docs/000077500000000000000000000000001445372753700153645ustar00rootroot00000000000000openmw-openmw-0.48.0/docs/Doxyfile.cmake000066400000000000000000003077241445372753700201660ustar00rootroot00000000000000# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See https://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = OpenMW # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @OpenMW_BINARY_DIR@/docs/Doxygen # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. Do not use file names with spaces, bibtex cannot handle them. See # also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = @OpenMW_SOURCE_DIR@/apps \ @OpenMW_SOURCE_DIR@/components \ @OpenMW_SOURCE_DIR@/libs \ @OpenMW_BINARY_DIR@/docs/mainpage.hpp # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: https://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.f90 \ *.f \ *.vhd \ *.vhdl # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = NO # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: https://developer.apple.com/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "OpenMW Documentation" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.openmw # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.openmw # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = OpenMW # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.openmw # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.openmw # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = YES # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # https://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. # The default value is: https://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: https://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /